背景与目标
- 背景
- 需求
1.业务庞大需要降低耦合度:
1. 每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。每个服务开发者只专注服务本身,通过使用缓存、DAL等各种技术手段来提升系统的性能,而对于消费方来说完全透明。规避了原本复杂度无止境的积累
2.容错性要求高
1. 在微服务架构下,当某一组件发生故障时,故障会被隔离在单个服务中。 通过限流、熔断等方式降低错误导致的危害,保障核心业务正常运行。当业务迭代时只需要发布相关服务的迭代即可,降低了测试的工作量同时也降低了服务发布的风险。
扩展性
- 基于docker的微服务可以做到更灵活的灵活伸缩.
- 现状:具备实施微服务的良好条件
- 目前的快钱系统已部分体现微服务的理念
- 微服务的实施有一定的先决条件:基础的运维能力(如监控、快速配置、快速部署)需提前构建,我们已基本具备
- 容器云平台为微服务提供了基石条件
- 微服务应用平台本身来说,并不依赖DevOps和容器云,然而当微服务应用平台结合了DevOps和容器云之后,持续集成和交付变成了一个非常简单便捷并且又可靠的过程。
- 目标
微服务架构设计的目标,满足快速开发、灵活升级、高性能、高可用、高稳定、简化运维等更高的需求。
1. 防止业务服务架构腐化:通过服务注册中心对服务强弱依赖进行分析,结合运行时服务调用链关系分析,梳理不合理的依赖和调用路径,优化服务化架构,防止代码腐化。
2. 快速故障定界定位:通过分布式日志采集框架,实时收集服务调用链日志、服务性能KPI数据、服务接口日志、运行日志等,实时汇总和在线分析,集中存储和展示,实现故障的自动发现、自动分析和在线条件检索,方便运维人员、研发人员进行实时故障诊断。
3. 服务微管控:细粒度的运行期服务治理,包括限流降级、服务迁入迁出、服务超时控制、智能路由、统一配置、优先级调度和流量迁移等,提供方法级治理和动态生效功能,通过一系列细粒度的治理策略,在故障发生时可以多管齐下,在线调整,快速恢复业务。
4. 服务生命周期管理:包括服务的上线审批、下线通知,服务的在线升级,以及线上和线下服务文档库的建设。
1. 灵活的资源调度:基于容器云,可以实现微服务的独立部署和细粒度的资源隔离。基于云端的弹性伸缩,可以实现微服务级别的按需伸缩和故障隔离。
微服务治理策略
- 服务治理范围覆盖了服务的整个生命周期,从服务建模开始,到开发、测试、审批、发布、运行时管理,以及最后的下线。我们通常说的服务治理主要是指服务运行时的治理,一个好的服务治理框架要遵循“在线治理,实时生效”原则,只有这样才能真正保障服务整体质量。下面介绍服务治理策略在服务运行时的应用。
- 服务越来越多,配置项越来越多,利用统一注册中心解决服务发现和配置管理问题。
- 服务之间存在多级依赖,靠人工已经无法理清,还要避免潜在的循环依赖问题,我们需要依赖管理机制,支持导出依赖关系图。
- 服务的性能数据和健康状态数据是服务治理的重要依据,比如访问量、响应时间、并发数等,因此需要有监控、健康检查和统计服务。
- 当一个服务的访问量越来越大,需要对服务进行扩容,然后在客户端进行流量引导和优先级调度。
- 面对突发流量,已经无法通过扩容解决问题时,要启用流量控制,甚至服务降级。
- 随着业务持续发展,要提前进行容量规划,结合服务监控数据,以确认当前系统容量能否支撑更高水位的压力。
- 等越来越多的微服务上线之后,从安全角度看,我们需要实施明确的权限控制策略和服务上下线流程。
通过一系列的服务治理策略,最终通过数据证明系统对外承诺的 SLA。
ServiceMesh提供的服务治理能力
服务网格作为服务间通讯层,不与应用程序的代码耦合,又能捕获到底层环境的动态变化并作出适当的调整,避免业务出现单点故障;同时也可以让开发者只关注自身业务,将应用云化后带来的诸多问题以不侵入业务代码的方式提供给开发者。在实现上,基于业界达到商用标准的开源软件 Istio进行构建, 为微服务架构提供以下能力:
服务注册与发现
- 身份验证与授权
- 身份服务内部之间默认信任;但是需要通过授权的步骤记录调用方身份.在服务和服务的通信开始前,双方必须用其身份信息交换凭证,以达到相互认证的目的。在客户端,根据安全命名(secure naming)信息,检查服务端的标识,以查看它是否是该服务的授权运行程序;在服务端,服务端可以根据授权策略(authorization policies)信息,确定客户端可以访问哪些数据服务端标识使用Kubernetes 服务帐户和自定义服务名称; 使用X.509 证书来携带 SPIFFE 格式的身份信息通过Istio Citadel提供身份支持
- Citadel 监视 Kubernetes apiserver,为每个现有和新的服务帐户创建 SPIFFE 证书和密钥对。 Citadel 将证书和密钥对存储为 Kubernetes secrets。
- 创建 pod 时,Kubernetes 会根据其服务帐户通过 Kubernetes secret volume 将证书和密钥对挂载到 pod。
- Citadel 监视每个证书的生命周期,并通过重写 Kubernetes secret 自动轮换证书。
- Pilot 生成安全命名信息,该信息定义了哪些服务帐户可以运行某个服务。接着Pilot 将安全命名信息传递给 Envoy。
- 鉴权
- 服务到服务身份认证: 使用“应用令牌”
- istio网关检查应用可访问的API范围,并进行流控、路由等控制
- 身份服务内部之间默认信任;但是需要通过授权的步骤记录调用方身份.在服务和服务的通信开始前,双方必须用其身份信息交换凭证,以达到相互认证的目的。在客户端,根据安全命名(secure naming)信息,检查服务端的标识,以查看它是否是该服务的授权运行程序;在服务端,服务端可以根据授权策略(authorization policies)信息,确定客户端可以访问哪些数据服务端标识使用Kubernetes 服务帐户和自定义服务名称; 使用X.509 证书来携带 SPIFFE 格式的身份信息通过Istio Citadel提供身份支持
- 服务的伸缩控制
- 反向代理与负载均衡
- 路由控制
- 流量切换
- 日志管理
- 性能度量、监控与调优
- 调用链跟踪
- 过载保护
- 服务降级
业务高峰时,为了保证核心服务的SLA,往往需要停掉一些不太重要的业务;
另外一种场景就是某些服务因为某种原因不可用,但是流程不能直接失败,需要本地Mock服务端实现,做流程放通。
通过服务治理的服务降级功能,即可以满足上述两种场景的需求。服务降级主要包括屏蔽降级和容错降级两种策略:
屏蔽降级:当外界的触发条件达到某个临界值时,由运维人员/开发人员决策,对某类或者某个服务进行强制降级。
容错降级:当非核心服务不可用时,可以对故障服务做业务逻辑放通,以保障核心服务的运行。
集群容错策略:
Failover策略:服务调用失败自动切换策略指的是当发生RPC调用异常时,重新选路,查找下一个可用的服务提供者。通常可以配置失败切换的最大次数和间隔周期,以防止E2E服务调用时延过大。
Failback策略:在很多业务场景中,消费者需要能够获取到服务调用失败的具体信息,通过对失败错误码等异常信息的判断,决定后续的执行策略,例如非幂等性的服务调用。
Failcache策略:Failcache策略是失败自动恢复的一种,在实际项目中它的应用场景如下:- 服务有状态路由,必须定点发送到指定的服务提供者。当发生链路中断、流控等服务暂时不可用时,服务框架将消息临时缓存起来,等待周期T,重新发送,直到服务提供者能够正常处理该消息。- 对时延要求不敏感的服务。系统服务调用失败,通常是链路暂时不可用、服务流控、GC挂住服务提供者进程等,这种失败不是永久性的失败,它的恢复是可预期的。如果消费者对服务调用时延不敏感,可以考虑采用自动恢复模式,即先缓存,再等待,最后重试。-通知类服务。例如通知粉丝积分增长、记录接口日志等,对服务调用的实时性要求不高,可以容忍自动恢复带来的时延增加。
Failfast策略:在业务高峰期,对于一些非核心的服务,希望只调用一次,失败也不再重试,为重要的核心服务节约宝贵的运行资源。此时,快速失败是个不错的选择。
服务降级通过熔断机制与断路器实现 - 服务部署与版本升级策略支持
API的版本控制能力,以及同时对多个版本提供支持的能力,这些都可大幅降低变化对其他服务团队造成的影响。这样大家将有更多时间按自己的计划更新代码。每个API都应该有版本控制机制!所有受支持的版本应共存于同一份基准代码和同一个服务实例中。此时可使用结构版本化(Versioning scheme)确定请求的到底是哪个版本。可行的情况下,老的端点应当更新以将修改后的请求中继至对应新端点。虽然同一个服务中多版本共存的局面不会降低复杂度,但可避免无意中增加复杂度,导致本就复杂的环境变得更复杂。- 微服务进程内提供部分治理能力
- 服务注册发现
进程内提供抽象的服务注册发现接口,采用多种实现方式。可不使用注册中心,直接写服务方地址,也可通过人工配置中心、zk,etc,consul,istio服务网关等适配器来提供实际的服务发现与注册 - 管理接口
-
DevOps
微服务的部署原则:独立部署和生命周期管理、基础设施自动化。
- xx
- 环境一致性
- 镜像部署
- 持续集成, 持续交付
- 上线审批下线通知机制
支撑平台
RPC框架
1. 设计思想
1. 封装底层通信细节,开发时调用远程服务就像调用本地服务。
1. 多种调用方式,如同步调用,异步callback, 异步事件处理等。底层统一异步处理,同步调用都是在异步方式上的封装。
1. 服务暴露和服务注册解耦,注册发现以可选插件的方式提供。
1. 多通信方案,多序列化方案。
1. 即能适应非侵入式的服务运行时治理,又能预留应用内介入服务运行时治理的能力(通过插件的方式)
2. 框架设计<br />总共分为 3 层:<br />服务层,Service,其中主要部分就是动态代理,主要用于将服务提供者的接口封装成远程服务调用。上层的服务接口用于 Spring 之类的业务框架使用,也是 Spring 的 bean.<br />过滤器层,服务调用职责链,提供多种调用切面供框架自身和使用者扩展,例如服务注册与发现<br />RPC 层,这里就是 RPC 框架的核心部分,包括通信框架,序列化框架,还有用于屏蔽底层通信框架和序列化框架的抽象接口。
2. 通信协议采用grpc或类似grpc的方式,以HTTP2为通信协议
1. 基于HTTP/2 协议的优点
1. HTTP/2 是一个公开的标准
1. 基于HTTP/2 多语言的实现容易
1. HTTP/2支持Stream和流控<br />在业界,有很多支持stream的方案,比如基于websocket的,或者rsocket。但是这些方案都不是通用的。
1. 基于HTTP/2 在Gateway/Proxy很容易支持如: nginx, envoy
1. <br />
2. 基于HTTP/2 的缺点
1. rpc的元数据的传输不够高效
1. HTTP/2 里一次gRPC调用需要解码两次
1. 一次是HEADERS frame,一次是DATA frame。
1. HTTP/2 标准本身是只有一个TCP连接,但是实际在gRPC里是会有多个TCP连接,使用时需要注意
3. 关于其他通信方式与框架
1. websocket
1. rsocket
1. webflux
1. thunder
1. hessian
4. 交互模式
1. 请求/响应:当您发送一个请求并收到一个响应时,就像HTTP一样。即使在这里,该协议也具有优于HTTP的优点,因为它是异步和多路复用的。
1. Fire-and-Forget:在不需要响应时非常有用,例如非关键事件日志记录。
1. 请求/流:类似于返回集合的请求/响应,集合被回送而不是查询直到完成。
1. 双向流式
5. 服务注册与发现
1. http2协议的通道注册在通信框架内不实际处理
1. 使用人工配置的服务注册中心来保存服务地址
1. 使用服务发现客户端,通过服务名获得服务地址,针对http2协议先实现人工配置的注册中心实现。
6. 分布式事务
6. thunder的兼容和升级方案
1. 新RPC框架配置服务注册,服务注册模块可以选择是否暴露thunder通道,这个选择可以用选程参数的方式配置。如果选择暴露thunder通道,则暴露thunder服务并在zk中注册
1. 服务发现模块优先寻找新的RPC通道,如果没有,则寻找thunder通道的服务。
- 消息
支持两种消息中间件
RocketMQ: 适用于消息通知
Kafka: 适用于大数据处理在新的服务框架下,不再建议通过声明式调用来使用RocketMQ,即RocketMQ的MDP封装if-bullet,以减少对消息通信的不正确使用,而是代替以事件流处理的编程模式。建议RocketMQ只用来做事件广播。 - 目前RocketMQ和Spring Cloud Stream的绑定还没有正式的第三方组件可用,等到第三方组件可用时,加入Spring Cloud Stream, Spring Cloud Bus编程方式的支持
- REST
客户端使用声明式RestClient, 可使用Spring Cloud FeignClient, Feign是Netflix开发的声明式、模板化的HTTP客户端, 帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定配置中心
服务拆分
- 服务拆分的规范
- 服务拆分最多三层,两次调用
- 服务拆分是为了横向扩展,因而应该横向拆分,而非纵向拆成一串的。
- 纵向的拆分最多三层:
- 基础服务层:用于屏蔽数据库,缓存层,提供原子的对象查询接口,有这一层,为了数据层做一定改变的时候,例如分库分表,数据库扩容,缓存替换等,对于上层透明,上层仅仅调用这一层的接口,不直接访问数据库和缓存。
- 组合服务层:这一层调用基础服务层,完成较为复杂的业务逻辑,实现分布式事务也多在这一层
- 接口层:调用组合服务层对外
- 仅仅单向调用,严禁循环调用
- 基础服务层主要做数据库的操作和一些简单的业务逻辑,不允许调用其他任何服务。
- 组合服务层,可以调用基础服务层,完成复杂的业务逻辑,可以调用组合服务层,不允许循环调用,不允许调用Controller层服务
- Controller层,可以调用组合业务层服务,不允许被其他服务调用
- 将串行调用改为并行调用,或者异步化
- 接口应该实现幂等
- 微服务拆分之后,服务之间的调用当出现错误的时候,一定会重试,这需要所有的接口实现幂等。
- 接口数据定义严禁内嵌,透传
- 微服务接口之间传递数据,往往通过数据结构,如果数据结构透传,从底层一直到上层使用同一个数据结构,或者上层的数据结构内嵌底层的数据结构,当数据结构中添加或者删除一个字段的时候,波及的面会非常大。
- 因而接口数据定义,在每两个接口之间约定,严禁内嵌和透传,即便差不多,也应该重新定义,这样接口数据定义的改变,影响面仅仅在调用方和被调用方,当接口需要更新的时候,比较可控,也容易升级。
- 规范化工程名
- 服务拆分最多三层,两次调用
服务拆分的方法
- 停止让单体式应用继续变大
- 当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。
- 前端和后端分离
- 抽出服务
- 每抽取一个服务,就朝着微服务方向前进一步。随着时间推移,单体应用将会越来越简单,用户就可以增加更多独立的微服务。
- 服务在拆分的过程中,功能的一致性,这种一致性不能通过人的经验来,而需要经过大量的回归测试集,并且持续的拆分,持续的演进,持续的集成,从而保证系统时刻处于可以验证交付的状态
- 要做应用的无状态化,只有无状态的应用,才能横向扩展,这样拆分才有意义。
- 数据隔离
- 微服务使用单独的数据库, 满足每一个服务是独立开发、独立部署、独立扩展的特性。当需要对一个服务进行升级或者数据架构改动的时候,无须影响到其他的服务。
- 对于数据库,需要进行良好的设计,不应该有大量的联合查询,复杂的联合查询通过应用层,或者通过数据挖掘、大数据处理进行。如果数据库表之间耦合的非常严重,其实服务拆分是拆不出来的。
架构演进
微服务架构不是一步到位设计出来的
- 采用“小规模,快反馈”的机制降低软件系统的复杂性并通过虚拟和自动化技术分散风险
- 代码未动DevOps先行
- 要让 Dev 和 Ops 共同参与决策,设计,实现和维护。
- 微服务的平台一开始可以很简单,可以以后慢慢增强和扩展。但是一定要部署到生产环境里使用。
- 基础设施即代码是 DevOps 核心实践,可以帮助开发人员迅速在本机构建生产环境相似的开发环境,减少环境的不一致性。可以采用K8S,Ansible等工具来完成。
- 构建一个服务框架组件
- 第一个微服务
- 构造出第一个微服务自动化测试
- 不要求构造完整的微服务自动化运维体系。当完成了第一个微服务,不要着急开始进行下一个微服务的开发。而是需要进行一次关于可复制经验的总结,识别微服务开发中的经验教训并总结成可复制的经验和产出。 以下是一些需要总结出来的关键产出: 1. 微服务开发到发布的端到端流程规范。 2. 微服务开发的技术质量规范。 3. 团队合作中的坚持的最佳实践。
- 逐步增加治理能力
- 原有架构到微服务的打通及过渡方案
- 在微服务形成规模前不断对微服务平台改进