2019年厦门软件园高端技术培训
2019年厦门软件园高端技术培训
《微服务架构设计与实践》
###### ps:本篇主要分享一些微服务课程中,比较重要的思路,和基础知识。具体请阅读pdf。
一、微服务架构概述
1.1 微服务架构定义
微服务之父马丁.福勒,他将微服务总结为以下9大特征:
- 通过服务组件化
- 围绕业务能力组织
- 是产品不是项目
- 智能端点和哑管道①
- 去中心化治理②
- 去中心化数据管理
- 基础设施自动化
- 为失效设计③
- 演进式设计
技术为业务而生,架构也为业务而出现。随着业务的发展、用户量的增长,系统数量增多,调用依赖关系也变得复杂,为了确保系统高可用、高并发的要求,系统的架构也从单体时代慢慢迁移至服务SOA时代,根据不同服务对系统资源的要求不同,我们可以更合理的配置系统资源,使系统资源利用率最大化。
旧架构:单体应用,需要搭建整个模块,导致开发进度过慢,如【MVC】架构等,并且生命周期长,迭代慢。
微服务核心: ==拆==
- 根据“高内聚,低耦合”原则,按业务的功能拆成一个个服务;
- 根据你的并发还拆单一服务,哪里并发高,单台无法解决的就再拆出来。
①:有关智能终端和哑管道的论文。 哑管道在这泛指一些订阅RSS或MQ等消息队列,他是具透明可见的性质。
②:需要一个注册中心,如Euraka。
③:容错设计(功能为先)
1.2 微服务架构的优点和缺点
- 开发者的 IDE 对分布式系统的在线开发和调试①相对于单体应用架构来说并不友好。
- 测试更加困难②。
- 开发者必须实现跨服务的通信机制③。
- 不采用分布式事务④来跨服务构建业务是十分困难的。
- 需要进行跨团队的协调工作。
- 部署更加复杂。
- 更多的内存消费⑤,对于 Java 应用来说,独立的部署意味着无法共享
JVM 的内存管理。
①、② : 上了微服务,需要注意编写单元测试。有些开发觉得单元测试没必要,但是在微服务中,因为组件是拆分开来,相互之间不好断点调试。
③ : 对于C端来说,各个服务之间可以直接调用,需要提供API接口。
(API接口如果需要升级,那么需要向下兼容,例如 ==版本V1能用,V2也需要能用V1的接口==)
④ : 每个微服务组件,需要有一个独立的数据库(也有不需要数据库的组件),一般对大的项目,数据库可采用分库分表。
⑤ : 使用容器进行管理。
1.3 微服务应用领域模型介绍和业务拆分
具体请看实战项目:
1.4 微服务应用内部服务通信机制分析
协议是同步还是异步角度分析:
• 同步模式:实现同步通信最主要的协议是HTTP。当前多为REST over HTTP(S), playload: JSON over HTTP(S).
协议是同步还是异步角度分析:
• 异步模式:AMQP 之类的协议使用异步消息。客户端代码或消息发件人通常不会等待响应。 它只是在发送消息到 RabbitMQ 队列或任何其他消息代理时才发送消息。
通信具有单个还是多个接收方:
• 单个接收方: 每个请求必须只能由一个接收方或服务来处理。此通信的示例是命令模式。
通信具有单个还是多个接收方:
• 多个接收方: 每个请求可以由零到多个接收方处理。 这种类型的通信必须是异步的。 例如事件驱动体系结构等模式中使用的发布/订阅机制。 当通过事件在多个微服务之间传播数据更新时,这基于事件总线接口或消息代理
制分析通信协议性能方面:
• 当前gRPC/protobuf因为其效率高,语义化强,被大量微服务架构应用。
• 内部调用在性能要求下会考虑使用基于http/2下的
gRPC方式
接口需要严格规范,数据类型参考OpenApi
1.5 微服务架构落地难点分析应对策略
- ==以始为终==,构建一个独立的敏捷微服务团队
- 构建微服务的“电梯演讲”
- 以==最小的代价==发布第一个微服务 (单体 –> 标准化)
- 取得快速胜利(Quick wins),演示(Showcase)驱动开发
- 代码未动,==DevOps先行==
- 除了代码提交和发布,==微服务平台一切都应当自动化==
- ==总结==并复制成功经验,建立微服务交付的节奏
二、典型架构设计参考
具体内容参照pdf,这边举例设计参考,spring cloud
Spring Cloud 微服务框架图
- Consul 或者 Eureka
==主要用于服务注册和服务发现==。每个微服务启动时会向服务注册中心注册自己,该注册中心存储所有服务的信息。并且,服务会周期性地向注册中心发送心跳,注册中心会检查超过一定时间没有 renew的服务并且注销该服务。因此,某个服务要访问其他服务时,可以先到服务注册中心查询被调用者的地址是否存在。
目前,Consul 已经取代 Eureka 成为 Spring Cloud 的缺省服务注册发现组件。Consul 内置注册发现框架、一致性协议、健康检查、KV 存储,虽然仍像 Eureka 不依赖 Zookeeper,但是也能够使其使用起来更加方便。
- 服务网关的功能包括
• 认证、鉴权
• 安全
• 金丝雀测试(灰度)
• 动态路由
• 限流
• 聚合
==Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,还以基于 Filer 链的方式提供安全、监控/埋点、限流功能==。
熔断器- Hystrix
在分布式架构中,当某个服务单元发生==故障==(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝)==向调用方返回一个错误响应==,而不是长时间的等待。这样就不会使得线程因调用故障服务而被长时间占用得不到释放,能够避免故障在分布式系统中的蔓延。
==Hystrix 负责监控服务之间的调用情况==,连续多次失败进行熔断保护。其主要工作流程:
• 检查缓存
• 检查 circuit breaker 状态
• 执行相应指令
• 记录数据,计算失败比率
Spring Cloud Hystrix 在我们的微服务治理中扮演着重要角色,我们对它做了二次开发,使其能够提供更加灵活的故障隔离、降级和熔断策略,满足 API 网关等服务的特殊业务需求。进程内的故障隔离仅是服务治理的一方面,另一方面,在一个应用混部的主机上,应用间应该互相隔离,避免进程间互抢资源,影响业务 SLA。比如绝对要避免一个离线应用失控占用了大量 CPU,使得同主机的在线应用受影响。我们通过 K8S 限制了容器运行时的资源配额(以 CPU 和内存限制为主),实现了进程间的故障和异常隔离。 K8S 提供的集群容错、高可用、进程隔离,配合 Spring Cloud Hystrix 提供的故障隔离和熔断,能够很好地实践 “Design for Failure” 设计哲学。
负载均衡 - Ribbon
不论是客户端实现还是服务器端的实现,都逃不开那些个负载均衡的常见算法。常见的服务端实现有:Nginx、HA Proxy 等。这里我们主要是客户端的实现,采用的是Netflix Ribbon,它的负载均衡策略比较丰富,包含以下几点:
1.随机选择(RandomRule)
2.线性轮询(RoundRobinRule)
3.重试机制(RetryRule)
4.加权响应(WeightedResponseTimeRule)
5.最小并发数(BestAvailableRule)
服务追踪- sleuth
微服务架构是一个分布式架构,它按业务划分服务单元,==一个分布式系统往往有很多个服务单元==。由于服务单元数量众多以及业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,使得出了问题可以很快定位。
分布式配置中心
配置中心很重要,特别是业务调用错综复杂的情况下,不可能对单个应用使用单独配置文件的方式,Spring Cloud Config 就是用来解决微服务场景下的配置问题。==关于分布式配置中心,在任何应用中都是需要的==。一些敏感信息,配置在代码的配置文件中显然是不合适的,应当加密后存储在配置中心。
基于上面的介绍,我们可以看到基于 Spring Cloud 的微服务架构有很多好处:
• 服务简单,业务功能单一,易理解、开放、维护
• 每个微服务可由不同团队开发
• 微服务是==松散耦合==的
• 微服务==持续集成、持续部署,服务独立部署、扩展==
但是也有一些问题是它不能解决的,比如:
• 资源管理,应用编排、部署与调度
• 根据负载动态自动扩容缩容
• 服务间进程资源的隔离
SpringCloud 与 K8S 的优势互补
服务注册和发现
当前越来越多的应用走在了通往应用容器化的道路上,容器化会成为应用部署的标准形态,而 K8S 已经成为容器编排技术的代表,K8S 本身也支持上面提到的部分功能,下面我们看看在 K8S 上怎么解决上面的问题。
Kubernetes 系统之上用于名称解析和服务发现的 ClusterDNS 是集群的核心附件之一,集群中创建的每个 Service 对象,都会由其自动生成相关的资源记录。默认情况下,集群内各 Pod 资源会自动配置其作为名称解析服务器,并在其 dns 搜索列表中包含它所属名称空间的域名后缀。
无论使用 kubeDNS 还是 CoreDNS,它们提供的基于 DNS 的服务发现解决方案都会负责解析以下资源记录(Resource Record)类型以实现服务发现。
(1) 拥有 ClusterIP 的 Service 资源
(2) Headless 类型的 Service 资源
(3) ExternalName 类型的 Service 资源
名称解析和服务发现是 Kubernetes 系统许多功能得以实现的基础服务,它通常是集群安装完成后应该立即部署的附加组件。
创建 Service 资源对象时,ClusterDNS 会为它自动创建资源记录用于名称解析和服务注册。于是,Pod 资源可直接使用标准的 DNS 名称来访问这些 Service 资源。基于 DNS 的服务发现不受 Service 资源所在的名称空间和创建时间的限制。
负载均衡
每个节点都有一个组件 kube-proxy,实际上是为 Service 服务的。通过 kube-proxy,实现流量从 Service 到 Pod 的转发,kube-proxy 会监控集群中的 Service 和 Pod 的变化,及时更新 iptables 或者 ipvs 的规则,基于这些规则也就可以实现简单轮询的负载均衡功能。
配置中心
通过创建 ConfigMap,里面包含对应工作负载,Pod 的配置文件,环境变量信息,工作负载 Deployment/Daemonset 等引用并挂载 ConfigMap 到对应的容器中,通过这种方式可以提供类似配置中心的功能。
限流灰度发布(==补充内,介绍了什么是灰度发布,在最后==)
Deployment 这种工作负载类型是支持多副本,以及滚动升级机制,借助 traefik 可以实现一些简单的灰度发布和流量控制功能,但是只能是 instance 副本级别的控制,粒度比较大。
下面是腾讯云网站上的一张图片,可以看到:
从应用的生命周期角度来看,K8S 覆盖了更广的范围,特别是资源管理,应用编排、部署与调度等,Spring Cloud 则对此无能为力。
从功能上看,虽然两者存在一定程度的重叠,比如服务发现、负载均衡、配置管理、集群容错等方面,但两者解决问题的思路完全不同。Spring Cloud 面向的纯粹是开发者,开发者需要从代码级别考虑微服务架构的方方面面;而 K8S 面向的是 DevOps 人员,提供的是通用解决方案,它试图将微服务相关的问题都在平台层解决,对开发者屏蔽复杂性。
举个简单的例子,关于服务发现,Spring Cloud 给出的是传统的带注册中心 Eureka 的解决方案,需要开发者维护 Eureka 服务器的同时,改造服务调用方与服务提供方代码以接入服务注册中心,开发者需关心基于 Eureka 实现服务发现的所有细节。虽然可以通过 Java 注解最大程度地降低代码量,但是本质上还是代码侵入方式。而 K8S 提供的是一种去中心化方案,抽象了服务 (Service),通过DNS+ClusterIP+iptables 解决服务暴露和发现问题,对服务提供方和服务调用方而言完全没有侵入。
K8S 虽然可以实现部分微服务框架的功能,在限流,服务熔断,链路跟踪等功能支持就比较有限。考虑到上述问题以及我们业务系统的现状、技术积累,作为主要编程语言是 Java 的容器服务来说,选择 Spring Cloud 去搭配 K8S 是一件很自然的事情。因此,我们采用了 SpringCloud + K8S 组合的方式来解决微服务化改造。
业务部署模式
- 我们所有的生产环境都是部署在公有云环境上,以腾讯云为主,使用腾讯云 TKE 部署了多套 K8S 集群。
- 每套 TKE 集群里部署 Spring Cloud 相应的组件运行我们的业务 Pod。
- 外部访问流量首先到达 Nginx 集群,根据路由规则会被发送到相应的 TKE 集群的 API Gateway 服务,再转发到对应的服务后端。
- 公司自研的发布系统会根据业务需求相应更新集群以及 Nginx 集群路由配置。通过这种方式可以实现蓝绿发布,要发布新版本时先只更新预发布集群的业务版本,相应的更新 Nginx 路由规则。比如,把从公司内部 IP 地址或者 android 客户端过来的访问请求导入预发布集群,验证试运行几天没有问题后再全量发布。
三、主流开源框架组件软件介绍
这边主要介绍spring cloud 和 阿里的dubbo
3.1 SpringCloud微服务开源框架组件介绍
API网关主要使用的开源产品
注册中心使用对比,spring boot 建议使用Eureka
组件名 | 语言 | CAP | 一致性算法 | 服务健康检查 | 对外暴露接口 | Spring cloud集成 |
---|---|---|---|---|---|---|
Eureka | JAVA | AP | 无 | 可配支持 | HTTP | 已集成 |
Consul | GO | CP | Raft | 支持 | HTTP/DNS | 已集成 |
Zookeeper | JAVA | CP | Paxo | 支持 | 客户端 | 已集成 |
ps:Zookeeper并不是高可用设计的。由于要跨机房容灾,很多系统实际上是需要跨机房部署的。出于性价比的考虑我们通常会让多个机房同时工作,而不会搭建N倍的冗余。也就是说单个机房肯定撑不住全流量(你能设想谷歌在全球只剩下一个机房在干活吗)。由于zookeeper集群只能有一个master,因此一旦机房之间连接出现故障,zookeeper master就只能照顾一个机房,其他机房运行的业务模块由于没有master都只能停掉。于是所有流量集中到有master的那个机房,于是系统crash。
zookeeper的性能是有限的,无法无限扩展部署搭建。
日志监控,建议使用Kafka
可视化工具: Grafana
Grafana是一个跨平台的开源的度量分析和可视化工具,可以通过将采集的数据查询然后可视化的展示,并及时通知。它主要有以下六大特点:
1、展示方式:快速灵活的客户端图表,面板插件有许多不同方式的可视化指标和日志,官方库中具有丰富的仪表盘插件,比如热图、折线图、图表等多种展示方式;
2、数据源:Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch和KairosDB等;
3、通知提醒:以可视方式定义最重要指标的警报规则,Grafana将不断计算并发送通知,在数据达到阈值时通过Slack、PagerDuty等获得通知;
4、混合展示:在同一图表中混合使用不同的数据源,可以基于每个查询指定数据源,甚至自定义数据源;
5、注释:使用来自不同数据源的丰富事件注释图表,将鼠标悬停在事件上会显示完整的事件元数据和标记;
6、过滤器:Ad-hoc过滤器允许动态创建新的键/值过滤器,这些过滤器会自动应用于使用该数据源的所有查询。
开源监控告警解决方案:Prometheus(普罗米修斯)
ps:一般会选择Prometheus+grafana 来进行系统监控方案
3.2 Dubbo 微服务开源框架组件介绍
因为发展比较早,目前逐渐混合入Spring生态中使用,或者使用 SOFAStack
四、微服务架构体系设计
4.1 微服务平台标准设计
基础设施层能力设计主要聚焦在如下
- 数据库如Mysql、Oracle、TiDB
- 缓存如Redis
- 总线如RabbitMQ、RocketMQ
- API网关如Spring Gateway、Kong
- 云原生应用集群系统如Kubernetes
4.1.1 数据库
保证数据库存储节点无单点
- 一致性需求采用分表分库
- 高可用需求采用读写分离
数据库资源自动化申请,做自动化配置
数据库池化:
池化技术能够减少资源对象的创建次数,提高程序的性能,特别是在高并发下这种提高更加明显。使用池化技术缓存的资源对象有如下共同特点:1,对象创建时间长;2,对象创建需要大量资源;3,对象创建后可被重复使用。
数据库分片插件:Apache ShardingSphere
NewSql数据库
TiDB 分布式数据库是新一代开源分布式 NewSQL 数据库,整个产品的结构非常清晰,计算跟数据存储层分离,这是现代大部分分布式数据处理系统通常都会倾向和考虑采用的架构
介绍:
TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 HTAP (Hybrid Transactional and Analytical Processing) 数据库
TiDB高度兼容Mysql
Newsql存储方式为多个==KV存储==,完全满足分布式事务的ACID,满足CAP
CAP原理:
- Consistency(一致性): 数据一致更新,所有数据变动都是同步的
- Availability(可用性): 好的响应性能
- Partition tolerance(分区耐受性): 可靠性
4.1.2 增强特性指标设计
按照生产级别制定
1. Stability
一个稳定的微服务应该是这样子的:它的开发、发布、新技术/新特性的增加、Bug的修改、服务的停止使用以及服务的弃用都不应该影响大的微服务生态系统的稳定性。
2. Reliability
通过cache可以提供可靠性的保证。通过defensive cache在服务出现问题时提供兜底。
在路由routing和服务发现的处理中,为了保证可靠性,health check应该是准确的,request和response应该送达、错误处理应该仔细的被处理。
3. Scalability
一个可扩展的微服务可以同时应付大量的任务或请求。数据存储也必须满足可规模扩展。
4. Fault Tolerance and Catastrophe preparedness
容错和灾备的微服务应该能够承受内部错误和外部错误。内部错误可能是微服务自己导致,例如内部代码的导致的未捕获的错误,外部错误可能是数据中心的停电、错误的配置等。
5. Performance
一个高性能的微服务处理处理请求很快,任务处理很高效,正确地使用资源。处理一大堆网络调用的微服务不是有效的。同步处理任务性能也不是
很高,异步(非阻塞)地处理任务能提供服务的性能和可用性。
6. Monitoring
所有关键的监控指标,比如硬件的使用率、数据库的连接、响应时间、API 的状态等,应该图形化的显示,可以直观的观察到服务的状态。
告警信息应该有用,并且可以处理,通常会有对应的处理文档。
7. Documentation
最好的文档包含微服务的所有的基础知识:架构图、入手和开发手册、请求流的细节、API、告警的运维手册等。
补充扩展:
生产级别,服务器的升级或者停止,都不应当影响生态系统的稳定性,需要热部署并且不允许停服。
(实现方式:部署两个或以上服务器副本,进行滚动升级,网关需要有调控能力,包括限流,白名单等)
微服务系统务必尽量做到==冗余==,可配置一个配置平台,进行服务器的开启或者关闭配置。
蓝绿部署(保证系统不间断提供服务):
蓝绿部署的模型中包含两个集群,就好比海豚的左脑和右脑。
在没有上线的正常情况下,集群A和集群B的代码版本是一致的,并且同时对外提供服务。
在系统升级的时候下,我们首先把一个集群(比如集群A)从负载列表中摘除,进行新版本的部署。集群B仍然继续提供服务。
当集群A升级完毕,我们把负载均衡重新指向集群A,再把集群B从负载列表中摘除,进行新版本的部署。集群A重新提供服务。
最后,当集群B也升级完成,我们把集群B也恢复到负载列表当中。这个时候,两个集群的版本都已经升级,并且对外的服务几乎没有间断过。
滚动部署
滚动部署,英文Rolling update,同样是一种可以保证系统在不间断提供服务的情况下上线的部署方式。
和蓝绿部署不同的是,滚动部署对外提供服务的版本并不是非此即彼,而是在更细的粒度下平滑完成版本的升级。
滚动部署只需要一个集群,集群下的不同节点可以独立进行版本升级。比如在一个16节点的集群中,我们选择每次升级4个节点:
以此类推,最终所有的节点都升级了版本。
比较
4.1.3业务基础信息及服务完备规范的设计
- DDD 分层架构中的服务规范
- 边界设计规范
- 代码逻辑分层和结构规范
4.2 微服务的拆分方法论设计
微服务拆分方法模式:
- 绞杀者模式
- “绞杀者模式”是在遗留系统外围,将新功能用新的方式构建为新的服务 。通过在新的应⽤中实现新特性,保持和现有系统的松耦合,==随着时间的推移,新的服务逐渐“绞杀”老的系统==。以此逐步地替换原有 系统。 对于那些老旧庞大难以更改的遗留系统,推荐采用绞杀者模式。
- 修缮者模式
- “修缮者模式”是在既有系统的基础上,通过剥离新业务和功能,逐步“释放”现有系统耦合度,解决遗留系统质量不稳定和 Bug 多的问题。就如修房或修路一样,将老旧待修缮的部分进行隔离,==用新的方式对其进行单独修复。 修复的同时,需保证与其他部分仍能协同功能==。修缮模式适用于需求变更频率不高的存量系统。
微服务拆分原则
- 基于业务领域拆分,在领域模型设计时需对齐限界上下⽂,围绕业务领域按职责单一性、功能完整性进行拆分,==避免过度拆分造成跨微服务的频繁调用==。
- 基于业务变化频率和业务关联拆分,识别系统中的业务需求变动较频繁的功能,考虑业务变更频率与相关度,并对其进行拆分,降低敏态业务功能对稳态业务功能的影响。(如购物车。)
- 基于应用性能拆分,考虑系统⾮功能性需求,识别系统中性能压力较大的模块,并优先对其进行拆分,提升整体性能,缩小潜在性能瓶颈模块的影响范围。
- 基于组织架构和团队规模,提高团队沟通效率。
- 基于软件包大小,软件包过大,不利用微服务的弹性伸缩。
- 基于不同功能的技术和架构异构以及系统复杂度。
五、服务治理中心体系设计
服务支撑工具
服务治理的支撑功能可以划分为三个层次:治理支撑服务,功能支撑服务, 线下支撑服务。
- ==治理支撑服务==包括服务注册/发现,流量控制①,容错熔断②,服务升级/回滚③, 链路跟踪,路由分发,超时重试,智能恢复等支撑工具集成。
- ==功能支撑服务==包括监控告警,日志服务,认证鉴权,计量计费,消息服务, 负载均衡,持久化服务,网管服务等支撑工具。
- ==线下支撑服务==包括DevOps流程支撑服务,运行环境支撑。
ps:同时部署,同步复制,并且具有==最终一致性==
①:url加开关进行限流或者分流
②:对于熔断希望每个服务器都可熔断,直接在网关做截流,底层快速失败,参照下图:
实战:依据某银行互联网业务设计服务治理中心沙盘演练
补充:
灰度发布:
灰度发布也叫金丝雀发布,起源是,矿井工人发现,金丝雀对瓦斯气体很敏感,矿工会在下井之前,先放一只金丝雀到井中,如果金丝雀不叫了,就代表瓦斯浓度高。
在灰度发布开始后,先启动一个新版本应用,但是并不直接将流量切过来,而是测试人员对新版本进行线上测试,启动的这个新版本应用,就是我们的金丝雀。如果没有问题,那么可以将少量的用户流量导入到新版本上,然后再对新版本做运行状态观察,收集各种运行时数据,如果此时对新旧版本做各种数据对比,就是所谓的A/B测试。
当确认新版本运行良好后,再逐步将更多的流量导入到新版本上,在此期间,还可以不断地调整新旧两个版本的运行的服务器副本数量,以使得新版本能够承受越来越大的流量压力。直到将100%的流量都切换到新版本上,最后关闭剩下的老版本服务,完成灰度发布。
如果在灰度发布过程中(灰度期)发现了新版本有问题,就应该立即将流量切回老版本上,这样,就会将负面影响控制在最小范围内。
ps: 升级过程如下
①:A:100% B:0%
②:A:95% B:5%
②:A:90% B:10%
…
②:A:0% B:100%
容错处理:
①:当ServiceB出现异常服务的时候,需要自动调配请求流量,这时候异常服务就不要在运行到ServiceB,而是直接走FallBack
②:Fallback模块相当于一个中间件,用来拦截异常服务