Spring Cloud
架构
- 单体架构(Monolithic): 所有的业务逻辑、UI、数据库访问代码全部打包在一个部署单元。代码扩展性差,无法针对高并发模块单独扩容,启动速度慢。
- 垂直架构(Vertical): 将单体应用按照业务线进行垂直拆分,解决了单体应用流量过大时的性能瓶颈,但是各垂直应用之间存在大量重复的业务逻辑,模块间交互困难。
- SOA(面向服务架构): 将核心的、公共的业务逻辑抽取出来形成独立的服务,通过一个中心化的企业服务总线(ESB)进行通信。提升了代码和业务逻辑的复用性;解决了垂直架构的重复开发问题。但ESB包含了大量路由和转换逻辑,ESB成为系统性能的集中瓶颈。
- Microservices(微服务架构) SOA的去中心化,将单一应用程序划分为一组小的服务,服务之间通过轻量级的机制(HTTP,RESTful API,RPC)进行通信。每个服务有自己独立的数据库。实现了解耦、服务独立部署和扩容、技术栈异构、敏捷开发与快速交付。不过也同样提高了运维复杂性以及分布式系统的难题。
微服务拆分原则
康威定律:设计系统的架构受制于产生这些设计的组织的沟通结构。微服务的划分应当与团队的组织架构相匹配。
领域驱动设计(DDD)基础概念:不按照技术层级(Controller/Service/Dao)去思考问题,而是按照业务领域来划分。
分布式核心理论
CAP理论:一个分布式系统不能同时满足以下三个特性,最多只能同时满足其中两项:
一致性(Consistency):所有节点在同一时间具有相同的数据。
可用性(Availablity):保证每个请求不管成功或者失败都有响应。
分区容错性(Partition tolerance):系统中任意信息的丢失或失败不会影响系统的继续运作。
网络分区是必然发生的客观事实。因此,只能在CP(强一致性,牺牲可用性,Zookeeper)和AP(高可用性,牺牲强一致性,Eureka)之间做权衡。
BASE理论:AP模式的扩展,即使无法做到强一致性,系统也应该采用适合的方式达到最终一致性。
基本可用(Basically Available):系统出现故障时,允许损失部分可用性。
软状态(Soft State):允许系统中的数据存在中间状态,并且认为该中间状态不影响系统的整体可用性。
最终一致性(Eventually Consistent):系统保证在没有新的更新操作的情况下,数据最终能够达到一致的状态。
Redis预扣+MQ异步消费,分布式锁和数据库状态机来防御多机并发导致的数据冲突。
Spring Cloud核心组件与底层原理
服务注册与发现组件(Nacos)
Nacos是Spring Cloud Alibaba生态中的核心组件,兼具服务注册中心和分布式配置中心两大能力。
注册中心:
服务注册:微服务启动时,底层会向Nacos服务端发送网络请求,上报自己的服务名、IP地址和端口号。
服务发现:调用方向Nacos发起查询,获取目标服务当前可用的IP节点列表,缓存到本地。
动态感知:当某个服务节点宕机或新节点上线,Nacos负责将变更后的通讯录推送或供调用方拉取。
可用性的优先级绝对高于强一致性。对于AP模式,即使Nacos包含了一个脏IP,底层的负载均衡组件和RPC框架会配合重试机制,调用脏IP失败会切到列表中的下一个健康IP。而CP模式下,网络分区或节点宕机时,集群重新选举,注册中心将处于不可用状态。
声明式RPC调用(OpenFeign)
OpenFeign让微服务之间的远程HTTP调用,看起来就像调用本地接口一样自然。写一个Java接口并打上@FeignClient(name = ""),调用打上@PostMapping("/api")的接口方法,即可实现网络请求。OpenFeign通过JDK在内存中为这个接口生成一个代理对象,代理对象会解析接口上的注解,提取出目标服务名、请求路径、请求方法和参数。拿到目标服务名后,OpenFeign会自动去找底层的负载均衡组件,从Nacos提供的IP列表中挑出一个具体的IP。最终将上述所有信息拼装成一个真实的HTTP请求发送,并将返回的JSON反序列化为Java对象。
OpenFeign的代理对象是单例的。为了解决跨服务传递上下文的问题,Spring MVC会将原始的HTTP请求对象绑定到当前线程,OpenFeign具有一个请求拦截器,在请求的最后一刻,可以从当前线程中提取原始的HTTP请求并将其中的内容,塞进OpenFeign的当前请求中并发往后续服务。
客户端负载均衡(Spring Cloud LoadBalancer)
与Nginx服务端负载均衡不同,客户端负载均衡以外部依赖的形式,直接嵌入在调用方的进程,客户端获得健康节点的列表,自己挑选IP,直接向目标微服务发送请求。流量压力分散在各个微服务节点内部,去中心化,无单点瓶颈。
当OpenFeign准备发送请求时,底层的拦截器会触发LoadBalancer,LoadBalancer优先从本地缓存中,获取目标服务当前所有可用的节点,根据负载均衡算法,从列表中精准计算出最终的物理IP地址。Spring Cloud LoadBalancer两种最基础的路由算法,轮询算法和随机算法。轮询算法将请求按顺序依次、平均地分配到各个节点,保证流量绝对均匀。随机算法,利用随机数生成器,在可用节点列表中随机挑选。
灰度发布:在Nacos控制台,为不同的库存服务节点打上metadata标签,测试用户在发起请求时,前端在HTTP Header中携带特定标识,请求到达微服务网关后,网关追加Header,在订单服务的LoadBalancer中,实现自定义的路由规则,强制在过滤后的节点列表进行轮询。
微服务网关(Spring Cloud Gateway)
微服务网关,将API和底层Nacos深度集成,具备动态感知微服务上下线的能力,采用了WebFlux响应式编程模型和Netty底层通信框架。前端只需要知道网关的唯一域名,网关会自动去Nacos查找目标微服务的最新IP并进行动态路由转发。网关作为唯一的鉴权和黑白名单拦截,前端只和网关进行同源通信,所有跨域配置都在网关这一层集中处理完毕。
当一个HTTP请求到达系统,网关先利用断言判断请求的各种属性,检查这个请求匹配哪条规则;匹配成功后,请求会穿过一连串的过滤器进行安检,最后被安全地路由到后端的物理微服务。通过网关的全局过滤器,可以通过克隆并重构全新的HTTP请求并添加特殊标记,传给后端微服务。
分布式配置中心(Nacos Config)
Nacos Config用于实现配置的集中管理与热更新,微服务启动时,不仅仅读取本地的application.yml,还通过网络向Nacos服务端拉取自己的配置信息,并在本地内存中生成配置树。
Nacos客户端通过长轮询向Nacos服务端发起请求并被挂起最多30s,当Nacos控制台修改了配置,服务端会立刻唤醒这个被挂起的请求,将新配置作为响应返回给客户端。当客户端的长轮询收到新配置后,旧的Bean被标记为失效,在下一次被调用时,会利用新的配置重新实例化Bean。
Beta发布,指定所有节点的特定IP进行配置更新,无问题后再进行全量发布。
熔断降级与限流(Sentinel)
熔断降级:当订单服务调用账户服务的慢调用比例或错误率超过设定阈值时,Sentinel会在订单服务内部触发"熔断",熔断触发后,订单服务在接下来的不再发起真实的物理网络包,而是执行一段本地预设的代码。
限流:Sentinel精准计算当前进来的流量,通过采用滑动时间窗口算法或者漏桶与令牌桶算法,当QPS超过设定的承载极限时,多余的请求将直接被拒绝。
分布式与中间件整合
分布式事务
分布式事务框架Seata最常用的是AT(Automatic Transaction)模式,引入全局的"事务协调者"(TC),利用数据库的日志记录功能,可实现强一致性。Seata解析业务SQL,自动生成undo log。当调用服务错误时,TC会向所有参与此次事务的微服务下发分支回滚指令。微服务执行undo_log,实现逻辑上的回滚。
分布式锁与缓存
微服务需要分布式锁,Redisson分布式锁的看门狗机制、Lua脚本保证原子性。
消息驱动微服务
Spring Cloud Stream的Binder绑定器机制。可实现代码与中间件消息队列解耦。代码中一个不调用消息队列API的方法,在application.yml可以将这个方法与消息队列的Topic进行绑定,当Maven依赖由spring-cloud-starter-stream-rabbit改为spring-cloud-starter-stream-rocketmq,可直接从RabbitMQ改为RocketMQ。
分布式链路追踪
TraceId与SpanId。请求进入网关,网关会自动生成一个全局唯一的TraceId,通常是32位随机字符串,TraceId绝对不改变。每进入一个新的微服务或数据库调用,系统就会生成一个新的SpanId,用来记录当前这个特定节点的耗时。每一行日志的头部都会被塞入TraceId和SpanId。TraceId通过存储在线程内存中和HTTP Header透穿来实现TraceId的全程无代码传递。