java 单机接口限流处理方案
893
2023-01-01
本文目录一览:
最近开发了一基于springcloud的微服务架构的门户项目,因为客户对系统性能有要求,所以楼主对系统的一些api接口进行了大量压力测试。在压测过程中,发现接口的性能瓶颈之一是服务网关和数据库部署在虚机上,所以本文将分享内容分为两部分
性能压测思路是从软硬件负载 f5,nginx,到容器化平台k8s、docker、zuul网关,再到数据存储es、mysql、mongodb、redis,进行全面测试。
性能压测汇总
部分接口压测结果
其中值得关注的是,用一台zuul网关节点和一个业务节点压测空接口,发现一个有意思现象:
空接口压测不走zuul,一个业务节点tps能达到 32000, 走zuul网关,一个业务节点空接口tps只有11000,性能损耗64%。
当时就感觉zuul网关在我心中高大的形象碎了一地,但是没办法,性能不达标必须要优化。所以楼主查了很多资料,也问过一些docker和k8s的容器化平台大牛,总结出两点经验:
所以楼主向公司申请物理机,继续性能压测,当然这不是重点,重点是接下来要讲的:为什么服务网关和数据库不能部署到虚拟机上 。
虚拟机的特点
io开销
我们知道,不管虚机上部署了多少个应用,一旦涉及到数据的存储,如果采用虚机部署数据库,会带来不必要的网络io开销。因为虚拟机在调度大量物理的cpu和内存、特别是磁盘IO时,必须经过虚拟机和物理机两层网络io读写开销操作,是非常耗系统性能的。
一般情况下,使用虚拟机部署应用,其性能衰减约20%左右,这不是优化代码能解决的。
共享物理机资源
因为虚拟机在cpu资源、网络等方面共享物理机资源,虚拟机之间会存在竞争物理机资源,造成程序不稳定情况。
docker容器部署
更要命的是,如果数据库和zuul网关部署到容器(实质也是虚拟机)里,那么网络io读写变成docker(虚拟机)到虚机,再到物理机三层访问,无形之中又增加了io读写性能开销。尤其是对于请求吞吐量要求很高的服务网关zuul,是不能容忍的。
所以虚机对于IO密集型以及对延迟要求很高的业务场景不合适。
另外,早期的时候,作为一名架构师需要尽早的规划好服务网关和数据库的物理部署方式以及软硬件性能要求。
从上图可以看出单体架构的问题可以通过微服务化拆分来解决。
随着商业模式逐渐得到验证,产品获得了市场的认可,为了加快产品的迭代效率,团队开始引进更多的研发人力,此时业务已经达到了一定的复杂度,单体应用已经无法满足业务增长的需求,研发效率开始下降,这是就是需要考虑服务拆分的时机点。
服务拆分的落地还需要提前准备好配套的基础设施,比如注册中心、配置中心、日志系统、持续交付、监控系统、分布式定时任务、CAP 理论、分布式调用链、API 网关等等;
人才的储备和观念的变化也得同时跟上
服务拆分不仅仅是技术的升级,更是开发方式、组织架构、开发观念的转变
服务拆分粒度太细会增加运维复杂度,粒度过大又起不到效果,如何平衡拆分粒度呢?
产品初期阶段,业务逻辑并没有足够复杂到2~3人没法维护的地步,这时我们没有必要将业务继续拆分的更细,但随着业务的发展,业务逻辑变的越来越复杂,可能同时服务多个平台,这时你会发现服务面临各种问题,这个阶段就需要将服务拆分为更细粒度的服务。虽然业务复杂度已经满足了,但如果没有足够的人力,服务最好也不要拆分,拆分会因为人力的不足导致更多的问题,如研发效率大幅下降。
一个服务需要几个开发维护是比较理性的?
三个火枪手原则
三个火枪手原则主要应用于微服务设计和开发阶段
拆分策略可以按功能和非功能维度考虑,功能维度主要是划分清楚业务的边界,非功能维度主要考虑六点:扩展性、复用性、高性能、高可用、安全性、异构性。
纵行拆分(基于业务逻辑拆分)
从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务
横向拆分
从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务 耦合。
按领域模型拆分
按领域模型拆分主要是划分清楚业务边界,主要分四步:
1、找出领域实体和值对象等领域对象
2、找出聚合根,根据实体、值对象和聚合根的依赖关系,建立聚合
3、根据业务及语义边界等因素,定义限界上下文
4、每一个限界上下文可以拆分一个对应的服务,但是也要考虑一些非功能因素
扩展性
区分系统中变与不变的部分,不变的部分一般是成熟的、通用的服务功能,变的部分一般是改动比较多、满足业务迭代扩展性需要的功能,我们可以将不变的部分拆分出来,作为公用的服务,将变的部分独立出来满足个性化扩展需要。
二八原则:经常变动的部分大约只占20%,剩下的80%基本不变或极少变化
复用性
不同的业务里或服务里经常会出现重复的功能,比如每个服务都有鉴权、限流、安全以及日志监控等功能,可以将这些通用的功能拆分出来形成独立的服务,也就是微服务里面的API网关。
可靠性
将可靠性要求高的核心服务和可靠性要求相对低的非核心服务拆分开来,然后重点保护核心服务的高可用。
高性能
将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。常见的拆分方式和具体的性能瓶颈有关,例如电商的抢购,性能压力最大的是排队功能,可以将此独立成一个服务;对于读写差异比较大的服务,也可以基于读写分离来拆分;基于数据一致性拆分,将强一致性的业务尽量放在一个服务中,弱一致性通常拆分为不同的服务
安全性
不同的服务可能对信息安全有不同的要求,因此把需要高度安全的服务拆分出来,进行区分部署,可以更有针对性地满足信息安全的要求,也可以降低对防火墙等安全设备吞吐量、并发性等方面的要求,降低成本,提高效率
异构性
对于开发语言种类有要求的业务场景,可以用不同的语言将其功能独立出来实现一个独立服务
以上拆分方式可以根据实际情况自由排列组合使用。拆分不仅仅是架构上的调整,也意味着要在组织结构上做出响应的适应性优化,以确保拆分后的服务由相对独立的团队负责维护
一个系统现在拆分出来的服务粒度也许合适,但随着时间的流失,系统需要不断的适应新的业务发展阶段,我们对系统领域的了解也越来越深,之前拆分的服务粒度可能就不合适了。例如业务的增删导致、过多的进程间通信导致效率低下等因素。
人员和服务数量的不匹配导致的维护成本增加,也是导致服务合并的一个重要原因。
服务数量过多和资源不匹配,则可以考虑合并多个微服务到一个服务包,部署到一台服务器,这样可以节省服务运行时的基础资源消耗,也降低了维护成本。 需要注意的是,虽然服务包是运行在一个进程中,但是服务包内的服务依然要满足微服务定义,以便在未来某一天要重新拆开的时候可以很快就分离
相比于传统集中式微服务网关性能瓶颈的应用系统微服务网关性能瓶颈,微服务微服务网关性能瓶颈的优点微服务网关性能瓶颈:
每个服务独立存在,所以可以单独部署,不用每次发布某个功能都经历一次全服务发布。
遵循单一功能原则,服务之间可以通过RESTFUL或者RPC调用,功能解藕
“细粒度” 微服务网关性能瓶颈的高可扩展性,每个服务都可以单独扩展,单独负载均衡
去中心化,尽可能地实现 “自服务”
有利于简化单独的开发测试以及部署,对开发团队友好
微服务缺点:
服务的可用性和维护性高度依赖于服务治理,如果治理得不好将会是灾难
某些服务可能造成性能瓶颈,某些服务的宕机可能导致很多服务受影响
服务配置繁琐
假设你正在开发一个电商网站微服务网关性能瓶颈,那么这里会涉及到很多后端微服务网关性能瓶颈的微服务,比如会员、商品、推荐服务等等。
那么这里就会遇到一个问题,APP/Browser怎么去访问这些后端微服务网关性能瓶颈的服务? 如果业务比较简单的话,可以给每个业务都分配一个独立的域名(https://service.api.company.com),但这种方式会有几个问题:
更好的方式是采用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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~