微服务

单体架构

虽然单体架构也是模块化逻辑,但是最终还是会打包并且部署为单体式应用。其中最主要的问题是应用太复杂,以至于任何单个开发者都不可能搞懂全貌。

应用无法扩展,可靠性低,最终敏捷性开发和部署变的无法完成。

应对思路:

  • 化繁为简,分而治之

    将后端服务拆分成多个单体服务,服务之间形成依赖关系。

延伸到微服务

SOA,面向服务的架构模式,可以把微服务想成是SOA的一种实践。

  • 小即是美:小的服务代码少,BUG也少,易测试,易维护,也更容易不断迭代完善的精致进而美妙
  • 单一职责:一个服务也只需要做好一件事,专注才能做好;Go中一个package只做一件事情
  • 尽可能早地创建原型:尽可能早提供服务API,简历服务契约,达成服务间沟通的一致性约定,至于实现和完善可以慢慢再做;例如先定义接口参数,先开发,最后再联调
  • 可移植性比效率更重要:服务间的轻量级交互协议在效率和可移植性二者间,首要依然考虑兼容性和移植性;易于通过轻量级RPC框架,将服务拆开多个微服务

微服务概览

定义

围绕业务功能构建的,服务关注单一业务,服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。

微服务架构通过业务拆分实现服务组件化,通过组件组合快速开发系统,业务单一的服务组件又可以独立不熟,使得整个系统变得清晰灵活。

  • 原子服务:单一业务
  • 独立进程:可独立部署、测试
  • 隔离部署:部署过程不依赖其他服务,容器编排
  • 去中心化服务治理

缺点:

  • 基础建设的实施,复杂度高,特别是韵味基础设施的挑战比较大
  • 开发者不得不使用RPC或者消息传递,来实现进程间通信;此外,必须要写代码来处理消息传递中速度过慢或者服务不可用等局部失效问题。特别在如今服务异常多,服务间通信多的情况下,gRPC通信比HTTP减少TCP连接更少。而且使用gRPC对接无须文档,开发更加方便。
  • 分区的数据库架构,同时更新多个业务主体的事务很普遍。这种事务对于单体式应用来说很容易,以为只有一个数据库。微服务应用中,需要更新不通服务所使用的不同的数据库,从而对开发者提出了更高的要求和挑战
  • 测试一个基于微服务架构的应用也是很复杂的任务。测试环境多,异常情况多,做到零信任困难
  • 服务模块间的依赖,应用的升级有可能会波及多个服务模块的修改

微服务是指关注单一业务,服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。通过组件和组件之间组合,实现业务需求,去中心化

模块化:模块化相比微服务化更加重量,按照产品模块划分。

微服务化:模块化是微服务化的前提,隔离性更强,更加独立,也易于横向扩展。

特性

  • 快速开发、迭代:相比较统一服务,需要整合所有的服务,微服务的设计模式下,只需要单体服务开发完成,则可以进行测试和迭代
  • 自动化部署:git提交之后通过jenkins可以自动化构建部署上线
  • 独立部署:服务和服务之间部署不影响
  • 错误隔离:服务异常不影响其他服务
  • 负载均衡:服务可以以多个相同的服务上线
  • 链路跟踪:对服务和服务之间的交互实现链路跟踪
  • 服务发现:通过统一的微服务管理实现服务之间通信
  • 服务治理:微服务和微服务之间相互通信统一管理
  • 动态扩容:某些微服务压力增大时自动扩容
  • 熔断,降级:微服务向外部发送大量请求时,可以将这个服务熔断或者降级,减少流量

将一个大型服务拆分成很多微服务,增加了复杂性,因此需要自动化部署、负载均衡、链路跟踪、服务发现、服务治理。但是可以实现动态扩容和熔断、降级。

微服务划分

​ 微服务架构通过业务拆分实现服务组件化,通过组件组合快速开发系统,业务单一的服务组件又可以独立部署,使得整个系统变得清晰灵活。基于领域驱动的思想,服务原子性,可独立部署,独立测试。

image-20220824113204908

​ 同时,采用去中心化的方式,避免出现局部负载过高,出现瓶颈的情况。

​ 划分界限:

  • 服务职责,例如文件上传,和文件AI审核
  • DDD界限上下文,例如提供用户操作文件的服务,提供认证登录的服务,就可以拆开

架构演变

服务组件化

传统实现组件的运行方式是通过库,库是和应用一起运行在进程中,库的局部变化意味着整个应用的重新部署。通过服务来实现组件,意味着将应用拆散为一系列的服务运行在不同的进程中,那么单一服务的局部变化只需要重新部署对应的服务进程。

  • kit:一个微服务的基础库(框架),例如go-micro,快速确定代码框架
  • service:业务代码+kit依赖+第三方依赖组成的业务微服务,组合成一个git库,那么就组成一个独立的服务
  • rpc+message queue:轻量级通讯,同步请求和异步请求

本质上等同于多个微服务,组成(compose)完成一耳光完整的用户场景(usecase)。例如一个网站服务,需要提供用户服务、文件服务、内容服务等,可能加上一个组装服务,提供给上层业务。

按业务组织服务

按照业务能力组织服务的意思是服务提供的能力和业务功能对应。比如:订单服务和数据访问服务,前者反应了真实的订单相关业务,后者是一种技术抽象服务,不反应真实的业务。

image-20230716163210074

按微服务架构理念来划分服务时,是不应该存在数据访问服务这样一个服务的。通过业务场景划分微服务职责。

image-20230716163221784

传统应用设计架构的分层结构反映了不同角色的沟通结构,所以按照微服务的方式构建应用,也需要对应调整团队的服务架构。每个服务背后的小团队的组织是夸功能的,包含实现业务所需的全面的技能。

去中心化

每个服务面临的业务场景不同,针对性的选择合适的技术解决方案。但是也需要避开多样化,结合团队实际情况来取舍,比如维护成本。

image-20230716163838451
  • 数据去中心化:每个微服务有自己的数据存储层,独享数据存储设置,缓存、数据库等,有利于服务的独立性,隔离相关干扰。
  • 治理去中心化:流量变大之后,服务和服务之间,会出现流量热点,比如账号服务,对应服务需要针对不同的情况,选择适合的服务治理方案
  • 技术去中心化:收敛语言

基础设施自动化,自动化测试

服务划分之后,单一进程的传统应用被拆分为一系列的多进程服务,意味着开发、调试、测试、监控和部署的复杂度都会相应增大,而且服务独立后,可以单独部署和测试,就需要引入自动化测试,自动化部署,可以通过gitlab自带ciweb hook实现,或者通过jenkins实现。

img
  • CI/CD:Gitlab+GitLab hooks + k8s
  • Testing:测试环境、单元测试、API自动化测试
  • 在线运行时:k8s,以及一系列Prometheus、ELK、Control Panle

可用性 & 兼容性设计

微服务架构采用粗粒度的进程间通信,引入了额外的复杂性和需要处理的新问题,入网络延迟、消息格式、负载均衡和容错,忽略其中任何一点都属于对分布式计算的误解。

  • 隔离
  • 超时控制
  • 负载保护
  • 限流
  • 降级
  • 重试
  • 负载均衡

在微服务架构模式下,在服务需要变更时需要特别小心,服务提供者的变更可能引发服务消费者的兼容性破坏,时刻谨记保持服务契约的兼容性。

发送时要保守,接受时要开放。

发送数据的时候,最小化传输必要的信息;

接受数据时,最大限度的容忍冗余数据,保证兼容性。

微服务设计

API Gateway

平铺模式

在按照垂直功能进行拆分,对外时,肯定是不能一次性暴露批量微服务的,没有统一出口会面临很多问题:

  • 客户端受到微服务直接通信,强耦合
  • 需要多次请求,客户端聚合数据,工作量巨大,延迟高
  • 协议不利于统一,各个部门间有差异,需要客户端来兼容
  • 面向端的API适配,耦合到了内部服务
  • 多终端兼容逻辑复杂,每个服务都需要处理
  • 统一逻辑无法收敛,比如安全认证、限流

接口层

在微服务最外一层的网关服务,主要负责转发。相比nginx,需要能够处理集群环境下节点切换的问题,同时,可以在流量入口增加链路追踪、熔断、鉴权、负载均衡等功能。

image-20230716170606057

通过增加一个app-interface用于统一的协议出口,在服务内部进行大量的dataset join(BFF层),按照业务场景来设计粗粒度的API,给后续服务的演进带来很多优势:

  • 轻量交互:协议精简、聚合
  • 差异服务:数据裁剪以及聚合、针对终端定制化API
  • 动态升级:原有系统兼容升级,更新服务而非协议
  • 沟通效率提升:协作模式前端开发只需要与接口层对接即可

BFF(Backend for Frontend):是一种适配服务,将后端的微服务进行适配(主要包括聚合裁剪和格式适配等逻辑),向无线端设备暴露友好和统一的API,方便无线设备接入访问后端服务。直白一些,专注服务前端。

通过BFF层,可以将微服务层和展示层隔离,BFF层专注服务给前端数据,微服务层专注于微服务本身。

网关层

接口层最致命的一个问题是整个app-interface属于单点故障(single point of failure),严重代码缺陷或者流量洪峰可能引发集群宕机。

  • 单个模块也会导致后续业务集成复杂度高,会导致后续更迭困难;BFF层也会有多个类别,比如负责账号、负责内容
  • 很多跨横切面逻辑,比如安全认证,日志兼容,限流熔断等。随着时间推移,代码变的越来越复杂

通过引入API Gateway,把业务集成度高的BFF层和通用功能服务层API Gateway进行了分层处理。

image-20230716170623829

新架构中,网管承担了重要的角色,解耦拆分和后续升级迁移的利器。在网关的配合下,单块BFF实现了解耦拆分,各业务线团队可以独立开发和交付各自的微服务,研发效率大大提升。另外,把跨横切面逻辑从BFF剥离到网关上去以后,BFF的开发人员可以更加关注业务逻辑交付,实现架构上的关注分离。

在BFF层在粗粒度获取微服务的信息,进行数据组装之余,还可以使用Node.js来做服务端渲染。

微服务划分

实际项目中通常会采用两种不同的方式划分服务边界:通过特务职能或者DDD的限界上下文

  • 业务职能 Business Capability:例如客户服务部门提供客户服务的职能,财务部门提供财务相关的职能
  • DDD的限界上下文Bounded Context:通过DDD中用来划分不同业务边界的元素,业务边界的含义是“解决不同业务问题”的问题域和对应的解决方案域,为了解决某种类型的业务问题,贴近领域知识,也就是业务。例如账户服务包含VIP服务
    • 内部服务之间,也通过微服务拆开,例如账户服务内部有VIP等服务
    • 通过DDD思想,将高频访问的服务独立出来作为一个服务,例如账户服务中的头像、等级、关注数等

一种简单方便的划分方法:依据数据库表设计来划分。例如user、role、account、permission这几张表有时会是单独使用,也有时会是多表联查。

灰色模块:那种感觉既可以给A模块也可以给B模块的东西。例如用户对某个资源是否有权限,可以丢给资源服务,也可以丢给用户模块,也可以单独出来作为一个服务。原则上是看模块化阶段,给A模块简单还是给B模块简单。如果预期这种灰色模块将来会有很大的变更,那么可以尝试单独出来。

微服务团队粒度

通用标准:一个能够完全理解模块的所有细节

事实标准:基本上没有人知道所有的细节,多数情况下,是三两个核心成员合并在一起相互补充,能够说清楚各种细节。

过小粒度更难以开发维护。

CQRS

将应用程序分为两部分:命令端和查询端,也就是读写分离。

  • 命令端:处理程序构建、更新和删除请求,并在数据更改时发出事件
  • 查询端:通过针对一个或者多个物化视图执行查询来处理查询,这些物化视图通过订阅数据更改时发出的事件流而保持最新

例如稿件服务,围绕创作稿件、审核稿件、最终发布稿件,有大量的逻辑糅杂在一起,其中稿件本身的状态也有非常多种,但最终前台只关注稿件是否能查看。

通过读写分离,依赖稿件数据binlog以及订阅binlog的中间件canal,将稿件结果发布到消息队列kafka中,最终消费数据独立组件一个稿件查阅结果数据库,并对外提供一个独立查询服务,来拆分复杂架构和业务。

架构也从Pulling publisher -> Transaction log tailing 进行演进。

前者:延迟高,对数据库也有压力,需要从数据库中获取实时数据

后者:通过读写分离,写入数据到某个服务中,自身实现数据请求

微服务安全

在API Gateway 进行统一认证拦截,一旦认证成功,使用JWT方式通过RPC元数据传递的方式带到BFF层,BFF验证Token完整性后把身份信息注入到应用的Context中,BFF到其他下层的微服务,建议是直接在RPC Request中代入用户身份信息(UserID)请求服务。

  • API Gateway -> BFF -> Service (服务层级调用关系)
  • Biz Auth -> JWT -> Request Args (请求数据转换)

对于服务内部,一般要区分身份认证授权

在gRPC中,认证最简单可以通过证书。然后通过RBAC实现授权。

安全等级:

  • Full Trust :全信任,即使认证完成,通信也必须加密,避免嗅探和监听
  • Half Trust :半信任,不加密
  • Zero Trust:零信任

gPRC

微服务之间相互通信,大多基于RPC,而gPRC框架可以支持多种语言,而且轻量级、高性能、统一。

  • 序列化支持Protocol Bufferjson,PB结构紧凑,性能好
  • 多语言:支持多种预览
  • 轻量级、高性能
  • 可插拔:支持插件,支持魔改
  • IDL:基于文件定义服务,通过proto3工具生成指定语言的数据结构、服务端接口以及客户端Stub
  • 对移动端的支持:基于标准HTTP/2设计,支持双向流、消息头压缩、TCP的多路复用,服务端推送等特性,这些特性使得gRPC在移动端设备上更加省电和节省网络流量。相比较HTTP1.1,一收一发,HTTP/2单个TCP连接上之后,可以复用无数个请求(也有缺点,基于TCP,在解封装前需要通过序号组装,如果有包丢了,后续的包都需要等待。)
  • 支持流:Streaming API
  • 阻塞式和非阻塞式:支持异步和同步处理在客户端和服务端间交互的消息序列
  • 元数据交换:常见的横切关注点,例如认证或跟踪,依赖数据交换
  • 服务而非对象、消息而非引用:促进微服务系统间粗粒度消息交互设计理念。通过protocol buffer,可以直接看到消息间通信数据和方式,代码就是文档
  • 设计理念:内网、外网、公网都可以使用gRPC,高度统一,后续升级也可以全面兼容
  • 负载无关:不同的服务需要使用不同的消息类型和编码,例如protocol buffer、JSON、XML等
  • 标准化状态码:客户端通常以有限的方式响应API调用返回的错误

RPC和HTTP区别在哪?

image-20230726112845540

微服务的区别不在于RPC还是HTTP,而是在于服务拆分,是一种架构,可以通过RPC也可以通过HTTP实现。

RPC可以基于TCP实现,也可以基于HTTP实现。

Health Check

gRPC有一个标准的健康检测协议,在gRPC的所有语言实现中都提供了生成代码和用于设置运行状态的功能。

image-20230724202032845

主动健康检查Health Check,可以在服务提供者服务不稳定时,被消费者感知,临时从负载均衡中摘除,减少错误请求。

当服务提供者重新稳定后,health check成功,重新加入到消费者的负载均衡,恢复请求。

health check,同样也被用于外挂方式的容器健康检测,或者流量检测(k8s liveness & readiness)

平滑发布

image-20230724202230795

对比以前暂停服务需要几个小时,平滑发布在应用上线之后,服务提供者在心跳接口正常之后,k8s将服务注册到注册中心,也就是通过服务外挂实现注册。

对于平滑发布,也有相同的平滑下线。(graceful shutdown)

  1. 开始下线某个服务,该服务从注册中心下线
  2. 流量不会被转发到服务提供者,而是转发到正常的服务提供者

服务发现

服务注册与发现是基于DNS的思想延伸出来,目的是为了解决服务之间通信的问题,

第一阶段:IP+端口

第二阶段:域名解析DNS

第三阶段:分布式协调-注册中心阶段(必须:定位信息;其他非必须:分组、集群、权重、版本)

第四阶段:元数据服务

客户端发现模式

image-20230724203137168

一个服务实例被启动时,它的网络地址会被写到注册表上;当服务实例终止时,再从注册表中删除;这个服务实例的注册表通过心跳机制动态刷新;客户端使用一个负载均衡算法,去选择一个可用的服务实例,来响应这个请求。

服务端发现模式

image-20230724203344875

客户端通过负载均衡器向一个服务发送请求,这个负载均衡器会查询服务注册表,并将请求路由到可用的服务实例上。

服务实例在服务注册表上被注册和注销(Consul Template + Nginx ,Kubernetes + etcd)

对比

客户端发现:

直连,比服务端发现少一次网络跳转,Consumer需要内置特定的服务发现客户端和发现逻辑。

服务端发现:

Consumer 无需关注服务发现具体细节,只需要知道服务的DNS域名即可,支持异构语言开发,需要基础设施支撑,多了一次网络跳转,可能有性能损失。

微服务的核心是去中心化,使用客户端发现模式。

image-20230724203723122

选型

image-20230724210724963

当使用CP系统时,微服务非常多,当一个服务上线、下线,会出现大量的广播,导致服务无法访问。

  • 分布式协调服务(要求任何时刻对ZooKeeper的访问请求能得到一致的数据,从而牺牲可用性)
  • 网络都懂或网络分区会导致master节点因为其他节点失去联系而重新选举或超过半数不可用导致服务注册发现瘫痪
  • 大量服务长连接导致性能瓶颈

服务发现的状态,可以弱一致,需要的是AP系统。

  • 注册的事件延迟
  • 注销的事件延迟

目前最推荐的使用Nacos。

服务发现概念

image-20230724211144480

  • 通过Family(appid)和Addr(IP:Port)定位实例,除此之外还可以附加更多的元数据:权重、染色标签、集群等

    appid:使用三段式命名,business.service.xxx(业务、服务、子服务名称)

  • Provider 注册后,定期(30s)心跳一次,注册(Register),心跳(Renew),下线(Cancel)都需要进行长轮询推送

    新启动节点,需要load cache,JVM预热。

    故障时,Provider 不建议重启和发布(等待服务正常运行一段时间之后,3个心跳周期,)

  • Consumer 启动时拉取实例,发起30s长轮询(建立连接,如果没有数据修改,则hang住,等待30s,如果有数据修改,则立马返回)

    服务发现集群故障时,需要client侧cache节点信息

  • Server 定期(60s)检测失效(90s)的实例,失效则剔除。短时间内丢失大量的心跳连接(15分钟内心跳低于期望值*85%),开启自我保护,保留过期服务不删除。(防止由于内部网络问题导致服务抖动异常,启用缓存保护。)

服务和服务之间通信,需要知道对端服务IP和端口

如果服务是集群模式,那么需要负载均衡,以及服务异常之后,自动切换

同时,在管理这些服务时,需要维护服务状态,服务上线、下线都可以实时检测到,从而实现负载均衡和流量转发到正常服务节点。

golang协议栈上,推荐使用consul

负载均衡

ps:k8s自带的svccoredns)不可取的理由有如下:

gRPC默认是长连接,客户端连接到服务端之后,后续不再变更,当服务端扩容,无法实现负载均衡。(虽然有解决方法,例如请求一次之后断开,但是这种方式会产生TCP连接过程资源的浪费;或者通过gRPC客户端自带的Name Resolver功能,但是这种方式当服务端下线之后重新上线,需要连接到上线的服务端,这里就可以让服务端每个一段时间强制断开所有客户端的连接)

解决方法总得来说,都不是很好,比较好的方案是使用专门使用服务发现、负载均衡的技术栈。

gRPC(Go)教程(十三)— Kubernetes 环境下的 gRPC 负载均衡

多集群

在一些需要高可用的服务上,一旦故障影响范围巨大,所以需要从几个角度考虑多集群的必要性:

  • 从单一集群考虑,多个节点保证可用性,通常使用N+2的方式来冗余节点
  • 从单一集群故障带来的影响面角度考虑冗余多套集群
  • 单个机房内的机房故障导致的问题

image-20230724213509856

利用PASS平台,给某个appid服务建立多套集群(物理上相当于两套资源,逻辑上维护cluster的概念),对于不同集群服务启动后,从环境变量里可以获取当下服务的cluster,在服务发现注册的时候,带入这些元信息。不同集群可以隔离使用不同的缓存资源等。

  • 多套冗余集群对应多套独占的缓存,带来更好的性能和冗余能力
  • 尽量避免业务隔离使用或者sharding带来cache hit影响,按照业务划分集群资源。

业务隔离集群带来的问题是cache hit ratio下降,不同业务形态数据正交,可以选择退而求其次,整个集群全部连接。

image-20230724213931250

这种方式也会带来问题,所有的服务都依赖这个服务,会导致这个服务由于响应ping从而有过高的CPU消耗。

统一为一套逻辑集群(虽然物理上是多套资源),gRPC客户端默认忽略服务发现中cluster信息,按照全部节点,全部连接。

可以使用一种算法,从全集群中选取一批节点(子集),利用划分子集限制连接池大小,

  • 长连接导致的内存和CPU开销,Health Check可以高达30%
  • 短连接极大的资源成本和延迟

合适的子集大小和选择算法

  • 通常20-100个后端,部分场景需要大子集,比如大批量读写操作
  • 后端平均分给客户端
  • 客户端重启,保持重新均衡,同时对后端重启保持透明,同时连接的变动最小

多租户

在一个服务架构里面,允许多系统共存是利用微服务稳定性以及模块化最有效的方式之一,这种方式一般被称为多租户(multi-tenancy)。租户可以是测试、金丝雀发布、影子系统(shadow systems),甚至是服务层或者产品线,使用租户能够保证代码的隔离型并且能够基于流量租户做路由决策。

对于传输中的数据(data-in-flight),例如,消息队列中的请求或者消息,以及静态数据(data-at-rest),例如存储或者持久化缓存。租户都能够保证隔离性和公平性,以及基于租户的路由机会。

image-20230724215445427

例如,对服务B做出改变,需要确保它仍能和服务A、C、D正常交互。在微服务架构中,需要做这些集成测试场景,也就是测试和该系统中其他服务的交互。通常来说,微服务架构有两种基本的集成测试方式:并行测试和生产环境测试。而实际情况中,这种测试方式会有诸多限制。

  • 混用环境导致的不可靠测试
  • 多套环境带来的硬件成本
  • 难以做负载测试,仿真线上真实流量情况

使用染色发布的测试方法,把待测试的服务B在一个隔离的沙盒环境中启动,并且在沙盒环境下可以访问集成环境(UAT)C和D。

image-20230724220227998

将测试流量路由到B,同时保持生产流量而不处理生产流量。另外要确保集成流量不要被测试流量影响。生产中的测试提出了两个基本要求,他们也构成了多租户体系结构的基础。

  • 流量路由:能够基于流入栈中的流量类型做路由
  • 隔离性:能够可靠的隔离测试和生产中的资源,这样可以保证对于关键业务微服务没有副作用

灰度测试成本代价很大,影响1/N的用户。其中N为节点数量。

给入站请求绑定上下文(如http header),in-process使用context传递,跨服务使用metadata传递(如:opentracing baggage item),在这个架构中每一个基础组件都能够理解租户信息,并且能够基于租户路由隔离流量,同时在平台中允许对运行不同的微服务有更多的控制,比如指标和日志。在微服务架构中典型的基础组件是乳汁、指标、存储、消息队列、缓存以及配置。基于消息隔离数据需要分别处理基础组件。

多租户是在架构层级的概念,而不是代码逻辑层级,更多的是使用中间件处理,比如将租户信息或者测试信息等,作为metadata存储到context中。

image-20230724220753448

多租户架构本质上描述为:

跨服务传递请求携带上下文(context),数据隔离的流量路由方案。

利用服务发现注册租户信息,注册成特定的租户。

多租户需要全链路覆盖。不然到某个节点请求就会被丢掉,也无法做好链路追踪和全链路压测。

微服务的特性

隔离

隔离本质上是对系统或资源的分割

超时

通过超时机制,可以有效的回收无法访问的到的请求使用的资源。

超时的机制是通过contextgRPC中传递,或者在HTTP请求中的header中加入xxx-timeout字段实现,在服务内部通过context传递维护超时机制,进程之间通过设定最大超时时间和剩余时间取最小值,实现超时控制。

过载保护

过载保护是对令牌桶和漏桶的一种优化机制。

令牌桶: 每秒钟往固定大小的桶中放入特定数量令牌,一个请求消耗一个令牌,当桶中没有令牌,则丢去请求。用于限制请求速度。

漏桶:桶中的水滴每隔一个固定时间漏出来,用于流量整形。

这两种方式需要提前设置好最大的处理速度,例如令牌放入的速度,水滴流出的速度,是一个人为设定的静态值,无法在环境发生变化之后动态变化。

优化方式是通过过载保护机制,通过计算时间节点内,CPU的利用率,计算出一个阈值,当达到阈值时,丢弃流量。并且达到阈值后,需要有一个冷却时间,判断流量是否依然大于阈值。(没有冷却时间,在流量丢弃之后,请求又会马上恢复,导致阈值频繁触发。)

过载保护能解决90%的大流量问题。

限流

限流则可以解决另外10%的流量问题。限流指的是一段时间内,定义某个用户或应用能够处理请求数量的技术。通过限流,可以过滤产生流量峰值的客户和微服务,或者可以确保引用程序在自动扩展生效前都不会出现过载的情况。

令牌桶和漏桶,都是针对单个节点,无法分布式限流。

分布式限流

很多分布式限流技术,通过redis实现,例如一个流量发送过来,都会在redisincr,超过阈值,就丢弃。这种模式,很强依赖redis。另外,请求redis很高频,会影响接口本身性能。

优化:一次取一批,例如取100个,而且异步定时获取,可以减少redis次数,本地用令牌桶做限流。这个方案,需要手动配置个数,这个数值比较难以设置合理。

优化:初始化一个默认值,后续通过一个时间段的QPS,预测当下的请求数。但是这个方案,会导致每个节点获取到的令牌不平等。

优化:最大最小公平分享算法。将总资源,优先满足需求最少的节点。

分布式限流有点很多,但是缺点也明显,实现复杂,而且适配给所有的请求,需要进行请求分级,按照不同的重要性进行限制。

熔断

当服务依赖的下游出现大量的报错,或者服务超过了限额,还发送超大流量,导致服务过载,通过熔断器(断路器),实现本地将请求快速失败,达到保护下游的一种方式。

降级

为了确保某些核心功能不受影响,通过降级回复来减少工作量,丢弃不重要的请求。

负载均衡

请求会分散打到所有的后端节点中,并且可靠识别异常节点,负载均衡过程中不会影响实际问题,也可以实现减少错误,节点冗余,在节点故障后,正常节点依然可以正常处理。

推荐阅读

注册中心选型篇-四款注册中心特点超全总结