数据结构与算法笔记:实战篇 - 剖析微服务接口鉴权限流背后的数据结构和算法

概述

微服务是最近几年才兴起的概念。简单点将,就是把复杂的大应用,解耦成几个小的应用 。这样做的好处有很多。比如,这样有利于团队组织架构的拆分,比较团队越大协作的难度越大;再比如,每个应用都可以独立运行,独立扩容,独立上线,各个应用之间互不影响。不用像原来那样,一个小功能的上限,整个大应用都要重新发布。

不过,有利就有弊。大应用拆分成微服务之后,服务之间的调用关系变得更复杂,平台的整体负载熵升高,出错的概率、debug 问题的难度都高了好几个数量级。所以,为了解决这些问题,服务治理便成了微服务的一个技术重点。

所谓服务治理,简单点讲,就是管理微服务,保证平台整体正常、平稳地运行。服务治理涉及的内容比较多,比如鉴权、限流、降级、熔断、监控告警等等。这些服务治理功能的实现,底层依赖大类的数据结构和算法。本章,就拿其中的鉴权和限流这两个功能,来带你看看,它们的实现过程都要用到哪些数据结构和算法。


鉴权背景介绍

以防你之前可能对微服务不了解,所以我对鉴权的背景做了简化。

假设我们有一个微服务叫用户服务(User Service)。它提供很多用户相关的接口,比如获取用户信息、注册、登录等等,给公司内部的其他应用使用。但是,并不是公司内部所有应用,都可以访问这个用户服务,也并不是每个有权限访问的应用,都可以访问用户的所有接口。

给你举个例子。下图只有 A、B、C、D 四个应用可以访问用户服务,并且,每个应用只能访问用户服务的部分接口。

在这里插入图片描述

要求实现鉴权功能,我们需要事先将应用对接口的访问权限规则设置好。当某个应用访问其中一个接口时,我们就可以拿应用的请求 URL,在规则中进行匹配。如果匹配成功,就说明允许访问;如果没有可以匹配的规则,那就说明这个应用没有这个接口的访问权限,我们就拒绝服务。

如何实现快速鉴权?

接口的格式有很多,有类似 Dubbo 这样的 RPC 接口,也有类似 Spring Cloud 这样的 HTTP 接口。不同的鉴权实现方式是类似的,这里主要拿 HTTP 接口给你讲解。

鉴权的原理比较简单。那具体到实现层面,我们应该用什么样的数据结构来存储规则呢?用户请求在规则中快速匹配,又该用什么样的算法呢?

实际上,不同的规则和匹配模式,对应的数据结构和匹配算法也是不一样的。所以,关于这个问题,我们继续细化为三个更加详细的需求给你讲解。

1.如何实现精确匹配规则?

先来看最简单的一种匹配模式。只有当请求 URL 跟规则中配置的某个接口精确匹配时,这个请求才会被接受、处理。

在这里插入图片描述

不同的应用对应不同的规则集合。我们可以采用散列表来存储这种对应关系。每个应用对应的规则集合,该如何存储和匹配呢?

针对这种匹配模式,我们可以将每个应用对应的权限规则,存储在一个字符串数组中。当用户请求到来时,我们拿用户的请求 URL,在这个字符串数组中逐一匹配,匹配的算法就是我们之前学过的字符串匹配算法(比如 KMP、BM、BF 等)。

规则不会经常变动,所以,为了加快匹配速度,我们可以按字符串的大小给规则排序,把它组织称有序数组这种数据结构。当要查找某个 URL 能够匹配其中某条规则时,我们可以采用二分查找算法,在有序数组中进行匹配。

而二分查找算法的时间复杂度是 O ( l o g n ) O(logn) O(logn)(n 表示规则的个数),这比起时间复杂度是 O ( n ) O(n) O(n) 的顺序遍历快了很多。对于规则中接口长度比较长,并且鉴权功能调用量非常大的情况,这种优化方法带来的性能提升还是非常可观的。

2.如何实现前缀匹配

再来看一下稍微复杂的匹配模式。只要某条规则可以匹配请求 URL 的前缀,我们就说这条规则能够跟这个请求 URL 匹配。

在这里插入图片描述

不同的应用对应不同的规则集合。我们采用散列表来存储这种对应关系。下面着重讲解下,每个应用的规则集合,最适合用什么样的数据结构来存储。

在 Trie 树章节中我们讲到,Trie 树非常适合用来做前缀匹配。所以,针对这个需求,我们可以将每个用户的规则集合,组织成 Trie 树这种数据结构。

不过,Trie 树中的每个节点不是存储单个字符,而是存储接口被 “/” 分割后的子目录(比如 “/user/name” 被分割成 “user” “name” 两个子目录)。因为规则并不会经常变动,所以在 Trie 树中,我们可以把每个节点的子节点们,组织成有序数组这种数据结构。在匹配的过程中,可以利用二分查找算法,决定从一个节点应该跳到哪一个子节点。

在这里插入图片描述

3.如何实现模糊匹配规则?

如果我们的规则更加复杂,规则中包含通配符,比如 “**” 表示匹配任意多个子目录, “*” 表示匹配任意一个子目录。只要用户请求 URL 可以跟某条规则模糊匹配,我们就说这条规则适用于这个请求。

在这里插入图片描述

不同的应用对应不同的规则集合。我们还是采用散列表来存储这种对应关系。下面看下,每个用户对应的规则集合,该用什么结构来存储?针对这种包含通配符的模糊匹配,我们又该使用什么算法来实现呢?还记得我们在回溯算法章节讲的正则表达式的例子吗?我们可以借助正则表达式那个里子的解决思路,来解决这个问题。我们采用回溯算法,拿请求 URL 跟每条规则逐一进行模糊匹配。如何用回溯算法进行模糊匹配,这部分就不重复讲了。

不过,这个解决思路的时间复杂度是非常高的。我们需要拿每一个规则,跟请求 URL 匹配一遍。那有没有办法可以继续优化一下呢?

实际上,我们可以结合实际情况,挖掘出这样一个隐形的条件,那就是,并不是每条规则都包含通配符,包含通配符的只是少数。于是我们可以把不包含通配符的规则和包含通配符的规则分开处理。

我们把不包含通配符的规则,组织成有序数组或者 Trie 树(具体组织成什么结构,视具体需求而定,是精确匹配,就组织成有序数组,是前缀匹配,就组织成 Trie 树),而这一部分的匹配就会非常高效。剩下的是少数包含通配符的规则,我们只要把它们简单存储在一个数组中就可以了。尽管匹配起来会比较慢,但是毕竟这种规则比较少,所以这种方法也是可以接受的。

当接收到一个请求 URL 之后,我们就可以先在不包含通配符的有序数组或者 Trie 树中查找。如果能够匹配,就不需要继续继续在通配符规则中匹配了;如果不匹配,就继续在通配符规则中查找匹配。

限流背景介绍

讲完了鉴权的思路,我们再来看一下限流。

所谓限流,顾名思义,就是对接口调用的频率进行限制。比如每秒中不能超过 100 次调用,超过之后,我们就拒绝服务。限流的原则听起来非常简单,但它在很多场景中,发挥着重要的作用。比如秒杀、大促、618 等场景中,限流已经成为了保证系统平稳运行的一种标配的技术解决方案。

按照不同的限流粒度,限流可以分为很多种类型。比如给每个接口限制不同的访问频率,或者给所有接口限制总的访问频率,又或者更细力度地限制某个应用对某个接口的访问频率等等。

不同粒度的限流功能的实现思路都差不多,所以,本章主要针对限制所有接口总的访问频率这样一个限流需求来讲解。其他粒度限流需求的实现思路,你可以自己思考。

如何实现精准限流?

最简单的限流算法叫固定时间窗口限流算法。这种算法是如何工作的呢?首先我们需要先选定一个时间起点,之后,每当有接口请求到来,我们就将计数器加一。如果在当前时间窗口内,根据限流规则(比如每秒最大允许 100 次访问请求),出现累加访问次数超过限流值的情况时,我们就拒绝后续的访问请求。当进入下一个时间窗口之后,计数器就清零重新计数。

在这里插入图片描述

这种基于时间窗口的限流算法的缺点是,限流规则过于粗略,无法应对时间窗口临界时间内的突发流量。这是怎么回事呢?我举个例子给你解释下。

假设我们的限流规则是,每秒不能超过 100 次接口请求。第一个 1s 时间窗口内,100 次接口请求都集中在最后 10ms 内。在第二个 1s 的时间窗口内,100 次请求都集中在最开始的 10ms 内。虽然两个时间窗口内流量都符合限流要求,但在两个时间窗口临界的 20ms 内,会集中有 200 次接口请求。固定时间窗口限流算法并不能对这种情况做限制,所以,集中在这 20ms 内的 200 次请求就有可能压垮系统。

在这里插入图片描述

为了解决这个问题,我们可以对固定时间窗口限流宣发稍加改造。我们可以限制任意时间窗口(比如 1s)内,接口请求数都不能超过某个阈值(比如 100 次)。因此,相对于固定时间窗口限流算法,这个算法叫滑动时间窗口限流算法

流量经过滑动时间窗口限流算法整形之后,可以保证任意一个 1s 的时间窗口内,都不会超过最大允许的限流值,从流量曲线上来看会更加平滑。那具体到实现层面,我们该如何做呢?

假设限流规则是,在任意 1s 内,接口的请求次数不能大于 k 次。我们就维护一个大小为 k+1 的循环队列,用来记录 1s 内到来的请求。注意,这里的循环队列的大小等于限流次数加一,因为循环队列存储数据时,会浪费一个存储单元。

当有新的请求到来时,我们将与这个新请求的时间间隔超过 1s 的请求,从队列中删除。然后,我们再来看循环队列中是否有空闲位置。如果有,则把新请求存储在队列尾部(tail 指针所指的位置);如果没有,则说明这 1 秒内的请求次数已经超过了限流值 k,所以这个请求被拒绝服务。

为了方便你理解,我举了一个例子。在这个例子中,我们假设限流的规则是,任意 1 秒内,接口的请求次数都不能大于 6 次。

在这里插入图片描述

即便滑动时间窗口限流算法可以保证任意时间窗口内,接口请求次数不会超过最大限流值,但是仍然不能防止,在西事件力度上访问过于集中的问题。

比如刚刚举的例子,第一个 1s 的时间窗口内,100 次请求都集中在最后 10ms,也就是说,基于时间窗口的限流算法,不管是固定时间窗口还是滑动时间窗口,只能在选定的时间粒度上限流,对选定时间粒度内的更加细粒度的访问频率不做限制。

实际上,针对这个问题,还有很多更加平滑的限流算法,比如令牌桶算法、漏桶算法等。如果感兴趣,你可以自己去研究一下。

总结

本章,讲解了跟微服务相关的接口鉴权和限流功能的实现思路。

关于鉴权,我们讲了三种不同的规则匹配模式。不管是哪种匹配模式,我们都可以用散列表来存储不同应用对应的不同规则集合。对于每个应用的规则集合的存储,三种匹配模式使用不同的数据结构。

  • 对于第一种精确匹配模式,我们利用有序数组来存储每个应用的规则集合,并通过二分查找和字符串匹配算法,来匹配请求 URL 与规则。
  • 对于第二种前缀匹配模式,我们利用 Trie 树来存储每个应用的规则集合。
  • 对于第三种模糊匹配模式,我们利用普通的数组来存储包含通配符的规则,通过回溯算法,来进行请求 URL 与规则的匹配。

关于限流,我们讲了两种限流算法,第一种是固定时间窗口限流算法,第二种是滑动时间窗口限流算法。对于滑动时间窗口限流算法,我们用了之前学过的循环队列来实现。比如固定时间窗口限流算法,它对流量的整形效果更佳,流量更加平滑。

从本章的学习中,也可以看出,对于基础架构工程师来说,如果不精通数据结构和算法,我们就很难开发出性能卓越的基础架构、中间件。这其实就体现了数据结构和算法的重要性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/367704.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nginx优化和防盗链

1、隐藏版本号 [roottest1 conf]# vim nginx.conf ​ server_tokens off; ​ 2、防盗链 修改用户和所在组 [roottest1 conf]# vim nginx.conf ​ #user nginx nginx; #表示主进程master会有root创建,子进程会有nginx用户来创建。 3、设置页面的缓存时间 主要是…

2024-2025年本田维修电路图线路图接线图资料更新

此次更新了2024-2025年本田车系电路图资料,覆盖市面上99%车型,包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对照表位置等等! 汽修帮手汽…

15- 22题聚合函数 - 高频 SQL 50 题基础版

目录 1. 相关知识点2. 例子2.15 - 有趣的电影2.16 - 平均售价2.17 - 项目员工 I2.18 - 各赛事的用户注册率2.19 - 查询结果的质量和占比2.20 - 每月交易 I2.21 - 即时食物配送 II2.22 - 游戏玩法分析 IV 1. 相关知识点 函数 函数含义order by排序group by分组between 小值 an…

Sping源码(九)—— Bean的初始化(非懒加载)—mergeBeanDefinitionPostProcessor

序言 前几篇文章详细介绍了Spring中实例化Bean的各种方式,其中包括采用FactoryBean的方式创建对象、使用反射创建对象、自定义BeanFactoryPostProcessor以及构造器方式创建对象。 创建对象 这里再来简单回顾一下对象的创建,不知道大家有没有这样一个疑…

边缘混合计算智慧矿山视频智能综合管理方案:矿山安全生产智能转型升级之路

一、智慧矿山方案介绍 智慧矿山是以矿山数字化、信息化为前提和基础,通过物联网、人工智能等技术进行主动感知、自动分析、快速处理,实现安全矿山、高效矿山的矿山智能化建设。旭帆科技TSINGSEE青犀基于图像的前端计算、边缘计算技术,结合煤…

【原创实现 设计模式】Spring+策略+模版+工厂模式去掉if-else,实现开闭原则,优雅扩展

1 定义与优点 1.1 定义 策略模式(Strategy Pattern)属于对象的⾏为模式。他主要是用于针对同一个抽象行为,在程序运行时根据客户端不同的参数或者上下文,动态的选择不同的具体实现方式,即类的行为可以在运行时更改。…

WIN32核心编程 - 数据类型 错误处理 字符处理

公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 数据类型 基本数据类型 Win32基本数据类型 错误处理 C语言中的错误处理 C中的错误处理 Win32中的错误处理 字符处理 C/C WIN32 字符处理 数据类型 基本数据类型 C/C语言定义了一系列…

双指针系列第 8 篇:盛水最多的容器。几句话讲明白!

Leetcode 题目链接 思路 取首尾双指针和水量如下所示&#xff0c;设高度函数为 h ( i ) h(i) h(i)&#xff0c;在下图中 h ( l ) < h ( r ) h(l) < h(r) h(l)<h(r)。 观察以 l l l 为左边界所能构成的其他水量&#xff0c;与矮的右边界搭配结果如下。 与高的…

Vue移动端地图App:van-uploader导致的卡顿问题

问题描述 基于Vue3+Vant IU 4开发的移动端地图App,在进行地图点位上报、上报记录查看过程中,出现App卡顿、甚至闪退的问题,进行问题定位之后,发现是van-uploader组件导致的问题。 van-uploader文件上传组件 van-uploader组件用于将本地的图片或文件上传至服务器,并在上传…

GOROOT GOPATH GOPROXY GO111MODULE

GOROOT GOROOT代表Go的安装目录。可执行程序go(或go.exe)和gofmt(或gofmt.exe)位于 GOROOT/bin目录中。 配置GOROOT环境变量&#xff0c;其值为Go的安装目录&#xff1b;然后在环境变量PATH中添加GOROOT/bin路径。 注意&#xff1a;GOROOT变量只是代表了安装目录&#xff0c;不…

Python基础小知识问答系列-可迭代型变量赋值

1. 问题&#xff1a; 怎样简洁的把列表中的元素赋值给单个变量&#xff1f; 当需要列表中指定几个值时&#xff0c;剩余的变量都收集在一起&#xff0c;该怎么进行变量赋值&#xff1f; 当只需要列表中指定某几个值&#xff0c;其他值都忽略时&#xff0c;该怎么…

红酒与建筑:品味历史与艺术的交汇

在时间的长河中&#xff0c;红酒与建筑都是人类智慧的结晶&#xff0c;它们各自承载着历史的厚重与艺术的韵味。当这两者交汇时&#xff0c;仿佛是一场穿越时空的对话&#xff0c;将我们带入一个既古老又现代、既深沉又温柔的世界。今天&#xff0c;就让我们一起走进这个奇妙的…

【前端VUE】VUE3第一节—vite创建vue3工程

什么是VUE Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0…

网页计算器的实现

简介 该项目实现了一个功能完备、交互友好的网页计算器应用。只使用了 HTML、CSS 和 JavaScript &#xff0c;用于检验web前端基础水平。 开发环境&#xff1a;Visual Studio Code开发工具&#xff1a;HTML5、CSS3、JavaScript实现效果 功能设计和模块划分 显示模块&#…

[数据集][目标检测]围栏破损检测数据集VOC+YOLO格式1196张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1196 标注数量(xml文件个数)&#xff1a;1196 标注数量(txt文件个数)&#xff1a;1196 标注…

1、音视频解封装流程---解复用

对于一个视频文件(mp4格式/flv格式)&#xff0c;audio_pkt或者video_pkt是其最基本的数据单元&#xff0c;即视频文件是由独立的视频编码包或者音频编码包组成的。 解复用就是从视频文件中把视频包/音频包单独读取出来保存成独立文件&#xff0c;那么如何得知packet是视频包还是…

MySQL高级-SQL优化- count 优化 - 尽量使用count(*)

文章目录 1、count 优化2、count的几种用法3、count(*)4、count(id)5、count(profession)6、count(null)7、 count(1) 1、count 优化 MyISAM引擎把一个表的总行数存在了磁盘上&#xff0c;因此执行count&#xff08;*&#xff09;的时候会直接返回这个数&#xff0c;效率很高&a…

esp12实现的网络时钟校准

网络时间的获取是通过向第三方服务器发送GET请求获取并解析出来的。 在本篇博客中&#xff0c;网络时间的获取是一种自动的行为&#xff0c;当系统成功连接WiFi获取到网络天气后&#xff0c;系统将自动获取并解析得到时间和日期&#xff0c;为了减少误差每两分钟左右进行一次校…

代码随想录-Day46

121. 买卖股票的最佳时机 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从…

『MySQL 实战 45 讲』22 - MySQL 有哪些“饮鸩止渴”提高性能的方法?

MySQL 有哪些“饮鸩止渴”提高性能的方法&#xff1f; 需求&#xff1a;业务高峰期&#xff0c;生产环境的 MySQL 压力太大&#xff0c;没法正常响应&#xff0c;需要短期内、临时性地提升一些性能 短连接风暴 短连接模式&#xff1a;执行很少的 SQL 语句就断开&#xff0c;…