java 单机接口限流处理方案
1072
2022-11-06
本文目录一览:
分布式编程架构技术我们在前几期的文章中已经给大家简单分析过很多次了,今天我们就一起来了解一下API网关分布式限流的运行原理都有哪些。
API网关中针对一个API、API分组、接入应用APPID,IP等进行限流。这些限流条件都将会产生一个限流使用的key,在后续的限流中都是对这个key进行限流。
限流算法通常在API网关中可以采用令牌桶算法实现。
必须说明一点的是分布式限流由于有网络的开销,TPS的支持隔本地限流是有差距的,因此在对于TPS要求很高的场景,建议采用本地限流进行处理。
下面讨论我们应该采用redis的哪一种分布式锁的方案:
由于redis事务要得到锁的效果需要在高TPS时会产生大量的无效的访问请求,所以不建议在这种场景下使用。
SETNX/EX的锁方案会产生在过期时间的问题,同时也有异步复制master数据到slave的问题。相比lua方案会产生更多的不稳定性。
我建议采用lua的方案来实施分布式锁,因为都是单进程单线程的执行,因此在TPS上和二种方案没有大的区别,而且由于只是一个lua脚本在执行,甚至是可能纯lua执行可能会有更高的TPS。当然是lua脚本中可能还是会去设置过期时间,但是应用server宕机并不会影响到redis中的锁。当然master异步复制的问题还是有,但是并不会造成问题,因为数据只会有1个lua脚本执行问题,下一个执行就正常了。
在实现方案的时候使用了Jedis库,云南java课程认为有一些问题在方案的实现层面我已经去做过验证了,可能也会是读者的疑问。
首先说明下我最近在思考的一个产品规划,即基于ServiceMesh服务网格思路,参考开源的Istio等实现架构来搭建一个完整的微服务治理管控平台。
在前面文章里面我就提到了,在实施微服务架构后,由于微服务将传统的单体应用进行了拆分,颗粒度更细。因此整个集成的复杂度,后续的管控治理复杂度都急剧增加。
当前也出现了类似SpingCLoud主流的微服务开发框架,实现了服务注册和发现,安全,限流熔断,链路监控等各种能力。同时对于服务注册,限流,服务链监控等本身又出现了大量的开源组件,类似服务注册的Nacos,Consul,限流熔断的Sentinel,链接监控的SKyWalking等开源组件。
当我们在思考微服务开发框架和开源组件的时候你会发现。
在SpingCLoud外的各类开源组件本身和微服务开发过程是解耦的,也就是说这些开源组件更加方便地通过配置增加管控能力,或者通过下发一个SDK包或Agent代理组件来实现管控能力。以尽量减少对微服务开发过程的影响。
而对于SpingCLoud微服务框架,在使用中有一个最大的问题就是开发态和治理态的耦合,也就是说一个微服务模块在开发的时候,你会引入很多治理态的内容。类似限流熔断,类似链路监控等能力,都需要你在开发状态增加配置文件,或对接口实现类进行扩展等。
微服务开发本身应该是一个简单的事情。
其核心是实现业务功能和规则逻辑,并暴露轻量的Http Rest API接口实现和前端交互或者实现和其它微服务模块之间的横向交互协同。
也就是说如果不考虑管控治理层面的内容,你采用最小化的SpingBoot来进行微服务开发足够的,或者你仍然可以采用传统的Java架构进行微服务开发,只要确保最终暴露Http API接口即可。
但是如果要考虑治理的内容,你会发现会引入注册中心,限流熔断,安全,服务链监控一系列的管控治理组件,导致整个微服务开发过程,集成过程都复杂化。
因此构建微服务治理平台的初衷即:
在这里还是先简单梳理下业务需求和业务功能场景。
01 服务注册和服务发现
仍然需要实现最基本的当前微服务自注册,自发现能力。这个在开发阶段需要暴露的接口增加注解还是必须的。在ServiceMesh下,由于存在本地Sidecar代理,因此在本地代理和微服务一起容器化部署下去后,会扫描微服务中需要暴露的接口,并完成微服务和API接口服务的注册工作。 也就是传统的应用开发集成中,手工接口API接口服务注册和接入的过程没有了,这个过程应该彻底地自动化掉。
注意这里的注册不仅仅是到微服务粒度,而是可以到微服务API接口粒度。
因此我们需要实现在微服务部署和交付后,微服务注册和微服务中的API接口注册全部自动完成。在微服务集群扩展的时候,相关的注册信息和配置信息也自动更新和扩展。
一个微服务模块在部署和交付后。
进入到微服务治理平台就能够看到当前有哪些微服务已经注册,进入到单个微服务里面,就可以看到当前微服务究竟有哪些细粒度的API接口已经注册。
02 服务安全和双重管理
对于一个微服务暴露的API接口,可以看到部分API接口仅仅是提供给前端微服务使用,但是部分API接口是需要提供给其它横向的微服务模块使用。
一个是前端调用后端API接口,一个是后端各个微服务中心间接口交互。
在安全管理的时候实际需要对这两类API接口分别进行管理。如果仅仅是前端功能使用,那么类似JWT+Token的安全措施即可,同时对于的日志流量并不一定需要完全记录和入库。如果是横向微服务间调用,那么安全要求更高,需要支持Token,用户名密码,IP地址验证等多种安全管控要求。
对于前后端的使用,往往仅授权到微服务层级即可。但是对于横向微服务间调用,那么服务授权必须到API接口服务粒度, 能够针对单个微服务API接口独立授权和管理。
03 服务限流熔断
同样这个功能不应该在微服务开发阶段进行任何配置或代码文件的增加。
在微服务成功的部署和交付上线后,应该能够针对微服务,微服务API接口两个不同的颗粒度进行服务限流设置。当然需要支持类似并发量,时长,错误数,数据量等多种限流熔断策略。
比如一个微服务单点能够支撑的最大并发量是1000TPS,那么这就是最基本的限流条件。我只需要设置单点能量,而不是设置集群能力。管控治理平台要管理的是通过负载均衡分发后到单个节点的流量能够控制到1000TPS。如果你部署了5个微服务节点,那么实际能够支撑的最大流量就是5000TPS。
由于采用Mesh去中心化的架构模式,因此实际微服务间的调用数据流量并不会通过微服务治理平台,微服务治理平台本身并没有太大的性能负荷压力。这个是和传统的ESB或API网关不同的地方,即API网关的限流一方面是保护API网关本身,一个是保护下游的微服务模块。
04 接口调用日志记录
注意这个功能本身也是可以灵活配置的,可以配置单个微服务,也可以配置单个API接口服务是否记录日志,包括日志记录是只记录调用时间和状态,还是需要记录想的接口调用消息报文数据。
在去中心化架构模式下,接口调用日志记录相对来说很容易实现。
即通过Sidecar边车首先对消息和数据流量进行拦截,任何将拦截的数据统一推送到消息中间件,消息中间件再将日志信息存入到分布式文件存储或对象存储中。
对于接口调用日志本身应该区分日志头信息和消息日志信息,对于日志头调用记录信息应该还需要推送到类似ELK组件中,以方便进行关键日志的审计和问题排查。
05 服务链路跟踪和监控
注意,在传统的服务链跟踪中,需要在微服务端配置Agent代理。而采用Mesh化解决方案后,该部分代理能力也移动到了Sidecar边车代理中实现。
服务链路监控不仅仅是微服务和API接口间的调用链路,也包括融入常规APM应用性能监控的能力,能够实现前端界面操作后发起的整个应用链路监控。
应用链路监控一方面是进行日志和错误分析,一方面是进行性能问题排查和优化。
06 和DevOps和容器云的集成
简单来说就是开发人员只需要按照标准规范开发单个微服务模块,然后走DevOps持续集成和交付过程进行部署。
在和DevOps平台进行集成后,DevOps在进行自动化部署前会下发Sidecar代理边车,实现对微服务本身的流量拦截和各种管控治理能力。在整个过程中Sidecar对开发者不可见,满足最基本的服务透明要求。
在通过DevOps部署到容器云平台后,满足基于资源调度策略进行后续微服务集群资源的自动化动态扩展能力。同时微服务在扩展后自动进行相应的集群注册,微服务API接口注册等操作。
在传统的SpingCLoud开发框架中,本身注册中心包括了对微服务模块的心跳检查和节点状态监控能力。在和Kurbernetes集群集成和融合后,完全可以采用Kurbernetes集群本身的心跳监控能力。
简单总结
最后总结下,整个微服务治理平台基于ServiceMesh去中心化架构思路来定制,但是需要实现类似传统ESB总线或API网关的所有管控治理能力。
对于最终的使用者来说并不关心治理能力实现是否是去中心化架构,而更加关心两个点。第一个点是开发阶段不要引入治理要求,第二就是能够实现核心能力的集中化管控和可灵活配置扩展。
也就是你可能上层看到的是一个传统的SOA治理管控平台,但是底层却是采用了去中心化的ServiceMesh架构来实现微服务治理管控能力。
通俗的说,流量控制就是控制用户请求的策略,主要包括:权限、限流、流量调度。
权限上一篇已经讲过了,这一篇讲限流,下一篇讲流量调度。
限流是指限制用户调用的频率(QPS/QPM)或者次数。
流量限制,站在用户或者运营的角度看,最直观能感受到的作用是——收费
各大主流开放平台的对外API,一般都有一些免费的额度,可以供个人测试用,一旦想大规模调用,就需要付费购买更大的额度(频率、次数),根据调用次数或者频率进行收费。一旦超过拥有的额度,就会被限制调用。
其实这才是限流最大的用处,只是用户或者运营同学无感,所以不太被大多数人了解。
网关后面是各个服务,各个服务的接口通过网关透出去给用户调用。理论上说,用户的流量是不可预知的,随时可能来一波,一旦流量的峰值超过了服务的承载能力,服务就挂了,比如有大新闻发生时的某浪微博,比如前些年的12306.
所以, 网关必须保证,放过去到达后端服务的流量一定不可以超过服务可以承载的上限 。这个上限,是网关和各个服务协商出来的。
由简到难,限流可以 分为单机限流、单集群限流、全集群限流 。
这里不讨论具体的如漏桶、令牌桶等限流算法,只说概念和思想。
单机限流的思想很简单,就是每个机器的限流值 x 机器数量 = 总的限流值。
举个例子,A用户的QPS限制是100,网关部署了10台机器,那么,每台机器限制10QPS就可以了。
先说好处,这种方法实现起来非常简单,每台机器在本地内存计算qps就可以了,超过阈值就拒流。
不过单机限流的缺陷也十分明显,主要体现在两点:
当网关部署的机器数量发生变化时,每台机器的限流值需要根据机器数调整。现实中,因为扩容、缩容、机器宕机等原因,机器数的变化是常有的事。
单机限流的前提是,每台网关承载的用户的流量是平均的,但是事实上,在某些时间,用户的流量并不是完全平均分布在每台机器上的。
举个例子:
10台机器,每台限qps10,其中3台每台实际qps是15,因为超限导致用户流量被拒。其余7台每台qps是7。这样用户总的qps = 15 * 3 + 7 * 7 = 94. 用户qps并没有超限,但是却有一部分流量被拒了,这样就很有问题。
实际上,单台限流的阈值也会设置的稍微大一些,以抵消流量不均的问题。
因为上面的问题, 单机限流通常作为一种兜底的备用手段,大多数时候用的还是集群限流 。
先来看一个示意图:
相比单机限流,集群限流的计数工作上移到redis集群内进行,解决了单机限流的缺陷。
但是集群限流也不是完美的,因为引入了redis,那么,当网关和redis之间的网络抖动、redis本身故障时,集群限流就失效了,这时候,还是得依靠单机限流进行兜底。
也就是说, 集群限流 + 单机限流配合,才是一个比稳妥的方案 。
接下来我们来思考这样一个问题:大型网关一般都是多机房、多地域部署的,当然,后端的服务也是多机房、多地域部署的,在保护服务这一点来说,集群限流是够用了。但是对用户来说,还是有一些问题:
比如,用户购买的QPS上限是30,我们的网关部署在中国北、中、南三个地域,那么这30QPS怎么分配呢?
平均肯定不行,用户的流量可能是明显不均衡的,比如用户的业务主要集中在中国北方,那么用户的流量大部分都会进入北方的网关,网关如果限制QPS为10的话,用户肯定来投诉。
那每个地域都限制为30行不行?也不行,如果用户的流量比较均匀的分布在各个地域,那么用户购买了30QPS,实际上可能使用了90QPS,这太亏了。
按照解决单机限流流量不均的思路,搞一个公共的redis集群来计数行不行?
也不行,受限于信号传播速度和天朝的广阔疆域,每个流量都计数,肯定不现实,rt太高会导致限流失去意义,带宽成本也会变得极其昂贵,对redis的规格要求也会很高。总之,很贵还解决不了问题。
有一种巧妙的解决办法是:本地集群阶梯计数 + 全集群检查。
还是刚才的例子:
限流阈值时90,那么三个地域各自计数,当本地域的数值达到30时,去其他两个地域取一次对方当前的计数值,三个地域的计数值加起来,如果超了,告诉另外两个地域超了,开始拒流。如果没超,本地QPS每上涨10,重复一次上述的动作。
这样就能有效的减少与redis的交互次数,同时实现了全地域真·集群限流。
当然,这种全地域集群限流,因为rt和阶梯计数间隔的存在,一定是不准的,但是,比单集群限流还是好很多。
当某个用户流量特别大的时候,redis计数就会遇到典型的热点key问题,导致redis集群单节点压力过大, 有两种办法可以解决这个问题:打散和抽样。
打散是指,把热点key加一些后缀,使其变成多个key,从而hash到不通的redis节点上,均摊压力。
比如热点key是abcd,那么打散后,key变成了abcd1、abcd2、abcd3、abcd4。技术时,轮流加1、2、3、4的后缀就可以了。
抽样是指,针对热点key,不是每个每个请求到来时都进行计数,而是进行一个抽样,比如每10个请求记一次数,这样redis的压力就会降低到十分之一。
说着把流量调度的也说完了哈哈,那下一篇再说说监控好了,顺便推一下我现在在用的国产网关:GOKU,来自Eolinker。我觉得比KONG好用,感兴趣的同学可以自行去了解一下。
假设你正在开发一个电商网站,那么这里会涉及到很多后端的微服务,比如会员、商品、推荐服务等等。
那么这里就会遇到一个问题,APP/Browser怎么去访问这些后端的服务? 如果业务比较简单的话,可以给每个业务都分配一个独立的域名(),但这种方式会有几个问题:
更好的方式是采用API网关,实现一个API网关接管所有的入口流量,类似Nginx的作用,将所有用户的请求转发给后端的服务器,但网关做的不仅仅只是简单的转发,也会针对流量做一些扩展,比如鉴权、限流、权限、熔断、协议转换、错误码统一、缓存、日志、监控、告警等,这样将通用的逻辑抽出来,由网关统一去做,业务方也能够更专注于业务逻辑,提升迭代的效率。
通过引入API网关,客户端只需要与API网关交互,而不用与各个业务方的接口分别通讯,但多引入一个组件就多引入了一个潜在的故障点,因此要实现一个高性能、稳定的网关,也会涉及到很多点。
API 注册
业务方如何接入网关?一般来说有几种方式。
协议转换
内部的API可能是由很多种不同的协议实现的,比如HTTP、Dubbo、GRPC等,但对于用户来说其中很多都不是很友好,或者根本没法对外暴露,比如Dubbo服务,因此需要在网关层做一次协议转换,将用户的HTTP协议请求,在网关层转换成底层对应的协议,比如HTTP - Dubbo, 但这里需要注意很多问题,比如参数类型,如果类型搞错了,导致转换出问题,而日志又不够详细的话,问题会很难定位。
服务发现
网关作为流量的入口,负责请求的转发,但首先需要知道转发给谁,如何寻址,这里有几种方式:
服务调用
网关由于对接很多种不同的协议,因此可能需要实现很多种调用方式,比如HTTP、Dubbo等,基于性能原因,最好都采用异步的方式,而Http、Dubbo都是支持异步的,比如apache就提供了基于NIO实现的异步HTTP客户端。
因为网关会涉及到很多异步调用,比如拦截器、HTTP客户端、dubbo、redis等,因此需要考虑下异步调用的方式,如果基于回调或者future的话,代码嵌套会很深,可读性很差,可以参考zuul和spring cloud gateway的方案,基于响应式进行改造。
优雅下线
性能
网关作为所有流量的入口,性能是重中之重,早期大部分网关都是基于同步阻塞模型构建的,比如Zuul 1.x。但这种同步的模型我们都知道,每个请求/连接都会占用一个线程,而线程在JVM中是一个很重的资源,比如Tomcat默认就是200个线程,如果网关隔离没有做好的话,当发生网络延迟、FullGC、第三方服务慢等情况造成上游服务延迟时,线程池很容易会被打满,造成新的请求被拒绝,但这个时候其实线程都阻塞在IO上,系统的资源被没有得到充分的利用。另外一点,容易受网络、磁盘IO等延迟影响。需要谨慎设置超时时间,如果设置不当,且服务隔离做的不是很完善的话,网关很容易被一个慢接口拖垮。
而异步化的方式则完全不同,通常情况下一个CPU核启动一个线程即可处理所有的请求、响应。一个请求的生命周期不再固定于一个线程,而是会分成不同的阶段交由不同的线程池处理,系统的资源能够得到更充分的利用。而且因为线程不再被某一个连接独占,一个连接所占用的系统资源也会低得多,只是一个文件描述符加上几个监听器等,而在阻塞模型中,每条连接都会独占一个线程,而线程是一个非常重的资源。对于上游服务的延迟情况,也能够得到很大的缓解,因为在阻塞模型中,慢请求会独占一个线程资源,而异步化之后,因为单条连接所占用的资源变的非常低,系统可以同时处理大量的请求。
如果是JVM平台,Zuul 2、Spring Cloud gateway等都是不错的异步网关选型,另外也可以基于Netty、Spring Boot2.x的webflux、vert.x或者servlet3.1的异步支持进行自研。
缓存
对于一些幂等的get请求,可以在网关层面根据业务方指定的缓存头做一层缓存,存储到Redis等二级缓存中,这样一些重复的请求,可以在网关层直接处理,而不用打到业务线,降低业务方的压力,另外如果业务方节点挂掉,网关也能够返回自身的缓存。
限流
限流对于每个业务组件来说,可以说都是一个必须的组件,如果限流做不好的话,当请求量突增时,很容易导致业务方的服务挂掉,比如双11、双12等大促时,接口的请求量是平时的数倍,如果没有评估好容量,又没有做限流的话,很容易服务整个不可用,因此需要根据业务方接口的处理能力,做好限流策略,相信大家都见过淘宝、百度抢红包时的降级页面。
因此一定要在接入层做好限流策略,对于非核心接口可以直接将降级掉,保障核心服务的可用性,对于核心接口,需要根据压测时得到的接口容量,制定对应的限流策略。限流又分为几种:
稳定性
稳定性是网关非常重要的一环,监控、告警需要做的很完善才可以,比如接口调用量、响应时间、异常、错误码、成功率等相关的监控告警,还有线程池相关的一些,比如活跃线程数、队列积压等,还有些系统层面的,比如CPU、内存、FullGC这些基本的。
网关是所有服务的入口,对于网关的稳定性的要求相对于其他服务会更高,最好能够一直稳定的运行,尽量少重启,但当新增功能、或者加日志排查问题时,不可避免的需要重新发布,因此可以参考zuul的方式,将所有的核心功能都基于不同的拦截器实现,拦截器的代码采用Groovy编写,存储到数据库中,支持动态加载、编译、运行,这样在出了问题的时候能够第一时间定位并解决,并且如果网关需要开发新功能,只需要增加新的拦截器,并动态添加到网关即可,不需要重新发布。
熔断降级
熔断机制也是非常重要的一项。若某一个服务挂掉、接口响应严重超时等发生,则可能整个网关都被一个接口拖垮,因此需要增加熔断降级,当发生特定异常的时候,对接口降级由网关直接返回,可以基于Hystrix或者Resilience4j实现。
日志
由于所有的请求都是由网关处理的,因此日志也需要相对比较完善,比如接口的耗时、请求方式、请求IP、请求参数、响应参数(注意脱敏)等,另外由于可能涉及到很多微服务,因此需要提供一个统一的traceId方便关联所有的日志,可以将这个traceId置于响应头中,方便排查问题。
隔离
比如线程池、http连接池、redis等应用层面的隔离,另外也可以根据业务场景,将核心业务部署带单独的网关集群,与其他非核心业务隔离开。
网关管控平台
这块也是非常重要的一环,需要考虑好整个流程的用户体验,比如接入到网关的这个流程,能不能尽量简化、智能,比如如果是dubbo接口,我们可以通过到git仓库中获取源码、解析对应的类、方法,从而实现自动填充,尽量帮用户减少操作;另外接口一般是从测试-预发-线上,如果每次都要填写一遍表单会非常麻烦,我们能不能自动把这个事情做掉,另外如果网关部署到了多个可用区、甚至不同的国家,那这个时候,我们还需要接口数据同步功能,不然用户需要到每个后台都操作一遍,非常麻烦。
这块个人的建议是直接参考阿里云、aws等提供的网关服务即可,功能非常全面。
其他
其他还有些需要考虑到的点,比如接口mock,文档生成、sdk代码生成、错误码统一、服务治理相关的等,这里就不累述了。
目前的网关还是中心化的架构,所有的请求都需要走一次网关,因此当大促或者流量突增时,网关可能会成为性能的瓶颈,而且当网关接入的大量接口的时候,做好流量评估也不是一项容易的工作,每次大促前都需要跟业务方一起针对接口做压测,评估出大致的容量,并对网关进行扩容,而且网关是所有流量的入口,所有的请求都是由网关处理,要想准确的评估出容量很复杂。可以参考目前比较流行的ServiceMesh,采用去中心化的方案,将网关的逻辑下沉到sidecar中,
sidecar和应用部署到同一个节点,并接管应用流入、流出的流量,这样大促时,只需要对相关的业务压测,并针对性扩容即可,另外升级也会更平滑,中心化的网关,即使灰度发布,但是理论上所有业务方的流量都会流入到新版本的网关,如果出了问题,会影响到所有的业务,但这种去中心化的方式,可以先针对非核心业务升级,观察一段时间没问题后,再全量推上线。另外ServiceMesh的方案,对于多语言支持也更友好。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~