说到大规模微服务系统,每每是一些7*24时不间断运行的在线系统,这样的系统每每有如下的要求:数据库
第一,高可用。这类的系统每每须要保持必定的SLA的,7*24时不间断运行不表明彻底不挂,而是有必定的百分比的。例如咱们常说的可用性需达到4个9(99.99%),整年停机总计不能超过1小时,约为53分钟,也即服务停用时间小于53分钟,就说明高可用设计合格。后端
第二,用户分布在全国。大规模微服务系统所支撑的用户通常在全国各地,于是每一个地区的人,都但愿可以就近访问,因此通常不会一套系统服务全国,而是每一个地区都要有相应的业务单元,使得用户能够就近访问。缓存
第三,并发量大,存在波峰波谷。微服务之因此规模比较大,实际上是承载的压力比较大,并且须要根据请求的波峰波谷进行弹性伸缩。服务器
第四,有故障性能诊断和快速恢复的机制。大规模微服务场景下,运维人员很难进行命令式手动运维来控制应用的生命周期,应该采用声明式的运维方法。另一旦有了性能瓶颈或者故障点,应该有自动发现定位的机制,迅速找到瓶颈点和故障点,及时修复,才能保障SLA。网络
战略设计
为了知足以上的要求,这个系统毫不是运维组努力一把,或者开发组努力一把,就能解决的,是一个端到端的,各个部门共同完成的一个目标,因此咱们常称为战略设计。架构
第一,研发
一个能支撑高并发,高可用的系统,必定是须要从研发环节就开始下功夫的。并发
首先,每个微服务都有实现良好的无状态化处理,幂等服务接口设计。负载均衡
状态分为分发,处理,存储几个过程,若是对于一个用户的全部的信息都保存在一个进程中,则从分发阶段,就必须将这个用户分发到这个进程,不然没法对这个用户进行处理,然而当一个进程压力很大的时候,根本没法扩容,新启动的进程根本没法处理那些保存在原来进程的用户的数据,不能分担压力。运维
因此要讲整个架构分红两个部分,无状态部分和有状态部分,而业务逻辑的部分每每做为无状态的部分,而将状态保存在有状态的中间件中,如缓存,数据库,对象存储,大数据平台,消息队列等。异步
这样无状态的部分能够很容易的横向扩展,在用户分发的时候,能够很容易分发到新的进程进行处理,而状态保存到后端。然后端的中间件是有状态的,这些中间件设计之初,就考虑了扩容的时候,状态的迁移,复制,同步等机制,不用业务层关心。
对于数据的存储,主要包含几类数据:
- 会话数据等,主要保存在内存中。对于保存在内存里的数据,例如Session,能够放在外部统一的缓存中。
- 结构化数据,主要是业务逻辑相关。对于业务相关的数据,则应该保存在统一的数据库中
- 文件图片数据,比较大,每每经过CDN下发。对于文件,照片之类的数据,应该存放在统一的对象存储里面
- 非结构化数据,例如文本,评论等。对于非结构化数据,能够存在在统一的搜索引擎里面,例如ElasticSearch。
可是还有一个遗留的问题,就是已经分发,正在处理,可是还没有存储的数据,确定会在内存中有一些,在进程重启的时候,数据仍是会丢一些的,那这部分数据怎么办呢?
这部分就须要经过重试进行解决,当本次调用过程当中失败以后,前序的进程会进行重试,例如Dubbo就有重试机制。既然重试,就须要接口是幂等的,也即同一次交易,调用两次转帐1元,不能最终转走2元。
接口分为查询,插入,更新,删除等操做。
对于查询接口来说,自己就是幂等的,不用作特殊的判断。
对于插入接口来说,若是每个数据都有惟一的主键,也能保证插入的惟一性,一旦不惟一,则会报错。
对于更新操做来说,则比较复杂,分几种状况。
一种状况是同一个接口,先后调用屡次的幂等性。另外一种状况是同一个接口,并发环境下调用屡次的正确性。
为了保持幂等性,每每要有一个幂等表,经过传入幂等参数匹配幂等表中ID的方式,保证每一个操做只被执行一次,并且在实行最终一致性的时候,能够经过不断重试,保证最终接口调用的成功。
对于并发条件下,谁先调用,谁后调用,须要经过分布式锁如Redis,Zookeeper等来实现同一个时刻只有一个请求被执行,如何保证屡次执行结果仍然一致呢?则每每须要经过状态机,每一个状态只流转一次。还有就是乐观锁,也即分布式的CAS操做,将状态的判断、更新整合在一条语句中,能够保证状态流转的原子性。乐观锁并不保证更新必定成功,须要有对应的机制来应对更新失败。
其次,根据服务重要度实现熔断降级、限流保护策略
服务拆分多了,在 应用层面 就遇到如下问题:
服务雪崩:即一个服务挂了,整个调用链路上的全部的服务都会受到影响;
大量请求堆积、故障恢复慢:即一个服务慢,卡住了,整个调用链路出现大量超时,要长时间等待慢的服务恢复到正常状态。
为了解决这些问题,咱们在应用层面实施了如下方案:
经过熔断机制,当一个服务挂了,被影响的服务可以及时熔断,使用 Fallback 数据保证流程在非关键服务不可用的状况下,仍然能够进行。
经过线程池和消息队列机制实现异步化,容许服务快速失败,当一个服务由于过慢而阻塞,被影响服务能够在超时后快速失败,不会影响整个调用链路。
当发现整个系统的确负载太高的时候,能够选择降级某些功能或某些调用,保证最重要的交易流程的经过,以及最重要的资源所有用于保证最核心的流程。
还有一种手段就是限流,当既设置了熔断策略,又设置了降级策略,经过全链路的压力测试,应该可以知道整个系统的支撑能力,于是就须要制定限流策略,保证系统在测试过的支撑能力范围内进行服务,超出支撑能力范围的,可拒绝服务。当你下单的时候,系统弹出对话框说 “系统忙,请重试”,并不表明系统挂了,而是说明系统是正常工做的,只不过限流策略起到了做用。
其三,每一个服务都要设计有效探活接口,以便健康检查感知到服务状态
当咱们部署一个服务的时候,对于运维部门来说,能够监控机器的状态或者容器的状态,是否处于启动状态,也能够监控到进程是否启动,端口是否监听等,可是对于已经启动的进程,是否可以正常服务,运维部门没法感知,须要开发每一个服务的时候,设计一个有效探活接口,让运维的监控系统能够经过调用这个接口,来判断进程可以正常提供服务。这个接口不要直接返回,而是应该在进程内部探查提供服务的线程是否出去正常状态,再返回相应的状态编码。只有这样,开发出来的服务和运维才能合做起来,保持服务处于某个副本数,不然若是一部分服务虽然启动,可是处于假死状态,会使得其余正常服务,没法承受压力。
其四,经过制定良好的代码检查规范和静态扫描工具,最大化限制由于代码问题形成的系统不可用
要保持线上代码的高可用性,代码质量是关键,大部分线上问题,不管是性能问题,仍是稳定性问题,都是代码形成的,而非基础设施形成的。并且基础设施的可用率为99.95%,可是服务层要求的可用率高于这个值,因此必须从业务层高可用来弥补。除了下面的高可用架构部分,对于每个服务来说,制定良好的代码检查规范和静态扫描工具,经过大量的测试用例,最大化限制由于代码问题形成的系统不可用,是必须的,是高可用的基础。
第二,高可用架构设计
在系统的每个部分,都要避免单点。系统冗余每每分管控面和数据面,并且分多个层次,每每每个层次都须要进行高可用的设计。
在机房层面,为了高可用应该部署在多个区域,或者多个云,每一个区域分多个可用区进行部署。
对于云来说,云的管控要多机房高可用部署,使得任何一个机房故障,都会使得管控依然可使用,这就须要管控的组件分布于至少两个机房,管控的数据库和消息队列跨机房进行数据同步。
对于云的数据面来说,入口的网关要和机房网络配合作跨机房的高可用,使得入口公网IP和负载均衡器,在一个机房故障的状况下,能够切换至另外一个机房。
在云之上要部署Kubernetes平台,管控层面Kubernetes要实现高可用部署,etcd要跨机房高可用部署,Kubernetes的管控组件也要跨机房部署。固然还有一种状况是机房之间距离比较远,须要在每个机房各部署一套Kubernetes,这种状况下,Kubernetes的管控依然要实现高可用,只不过跨机房的高可用就须要应用层来实现了。
在应用层,微服务的治理平台,例如注册发现,zookeeper或者Euraka,APM,配置中心等都须要实现跨机房的高可用。另外就是服务要跨机房部署,实现城市级机房故障迁移能力
第三,运维
运维一个大规模微服务系统也有不同的挑战。首先建议使用的是Kubernetes编排的声明式的运维方式,而非ansible之类命令式的运维方式。
另外对于系统的发布,要进行灰度、蓝绿发布,下降系统上线发布风险。要有这样的理念,任何一个新上线的系统,都是不可靠的。
因此能够经过流量分发的模式,逐渐切换到新的服务,从而保障系统的稳定。
其三,完善监控及应对机制,对系统各节点、应用、组件全面地监控,可以第一时间快速发现并解决问题。
监控绝非只有基础设施的CPU,网络,磁盘的监控,应用的,业务的,调用链的监控都应该有。并且对于紧急事件,应该有应急预案,应急预案是在高可用已经考虑过以后,仍然出现异常状况下,应该采起的预案,例如三个etcd全挂了的状况。
其四,持续关注线上系统网络使用、服务器性能、硬件存储、中间件、数据库灯指标,重点关注临界状态,也即当前还健康,可是立刻可能出问题的状态。例如网关pps达到临界值,下一步就要开始丢包了,数据库快满了,消息出现大量堆积等等。
第四,DBA
对于一个在线业务系统来说,数据库是重中之重,不少的性能瓶颈定位到最后,均可能是数据库的问题。因此DBA团队要对数据库的使用,进行把关。
形成数据库性能问题,一方面是SQL语句的问题,一方面是容量的问题。
例如查询没有被索引覆盖,或者在区分度不大的字段上创建的索引,是否持锁时间过长,是否存在锁冲突等等,都会致使数据库慢的问题。
于是全部上线的SQL语句,都须要DBA提早审核,而且要对于数据库的性能作持续的监控,例如慢SQL语句等。
另外对于数据库中的数据量也要持续的监控,到必定的量就须要改分布式数据库DDB,进行分库分表,到必定的阶段须要对分布式数据库进行扩容。
第五,故障演练和性能压测
再好的规划也比不上演练,再好的性能评估也比不上在线的性能压测。
性能问题每每经过线上性能压测发现的。线上压力测试须要有一个性能测试的平台,作多种形式的压力测试。例如容量测试,经过梯度的加压,看到何时实在不行。摸高测试,测试在最大的限度之上还能承受多大的量,有必定的余量会保险一些,内心相对比较有底。再就是稳定性测试,测试峰值的稳定性,看这个峰值可以撑一分钟,两分钟仍是30分钟。还有秒杀场景测试,限流降级演练测试等。
只有通过性能压测,才能发现线上系统的瓶颈点,经过不断的修复和扩容瓶颈点,最终才能知道服务之间应该以各类副本数的比例部署,才能承载指望的QPS。
对于可能遇到的故障,能够进行故障演练,故意模拟一些故障,来看系统如何反应,是否会由于自修复,多副本,容错等机制,使得这些故障对于客户端来说没有影响。
战术设计
下面,咱们就从架构的每一个层次,进行战术设计。咱们先来看一下高可用部署架构选型以及他们的优劣。
架构类型 |
可用性 |
优点 |
问题 |
单体应用 |
- |
网络开销小 |
扩展性差,维护困难 |
单机房服务化 |
应用级高可用 |
网络开销小,解耦可扩展 |
容量受限,机房级单点 |
同城多活阶段一 |
机房级高可用 |
突破单机房容量瓶颈 |
非必要的跨机房开销大 |
同城多活阶段二 |
机房级高可用 |
非必要的跨机房网络开销小,提供机房级容灾 |
城市级单点,仍存在非必要的跨机房开销 |
异地多活单元化 |
城市级高可用 |
异地容灾,可用性高 |
成本较高,要求各应用组件实现单元化 |
高可用性要求和系统的负载度和成本是强相关的。越简单的架构,部署成本越低的架构,高可用性越小,例如上面的单体应用。而微服务化,单元化,异地多活,必然致使架构复杂难以维护,机房成本比较高,因此要使用多少成本实现什么程度的高可用,是一个权衡。
高可用的实现须要多个层次一块儿考虑:
首先是应用层,能够经过异地多活单元保证城市级高可用,这样使得一个城市由于灾难宕机的时候,另一个城市能够提供服务。另外每一个多活单元采用双机房保证机房级高可用,也即同城双机房,使得一个城市中一个机房宕机,另外一个机房能够提供服务。再者每一个机房中采用多副本保证明例级高可用,使得一个副本宕机的时候,其余的副本能够提供服务。
其次是数据库层,在数据中心之间,经过主从复制或MGR实现数据异步复制,在每一个集群单元中采用DDB分库分表,分库分表中的每一个实例都是有数据库同步复制。
其三是缓存层,在数据中心之间,缓存采用多集群单元化复制,在每一个集群单元中采用多副本主从复制。
其四微服务治理平台层,平台组件异地多活单元保证了城市级高可用,平台组件每一个多活单元采用双机房保证机房级高可用,平台组件每一个机房中采用多副本保证明例级高可用。
当有了以上高可用方案以后,则如下的故障等级以及影响时间以下表格。
故障层级 |
预计影响范围 |
预计SLA影响 |
恢复手段 |
应用单实例故障 (进程异常退出,FGC等) |
单个或部分用户请求失败。应用有多副本自动重试便可正常 |
无影响 |
K8S经过健康检测到异常,自动重启容器 |
应用单节点故障 (主机硬件异常,掉电等) |
部分用户请求失败。应用有多副本自动重试便可正常 |
<1分钟 |
K8S检测到节点异常会自动迁移该节点上的容器 |
中间件单节点故障 (Zookeeper、Eureka) |
ZK故障为从节点无影响; ZK故障为主节点,在重选举时没法注册发现; Eureka单节点故障无影响; |
无影响 |
自动剔除故障节点,故障节点需手工恢复 |
缓存单节点故障 (Redis) |
Redis集群模式单节点故障无影响; |
无影响 |
自动剔除故障节点,故障节点需手工恢复 |
数据库单节点故障 (DDB、RDS) |
DDN从节点故障无影响; DDN主节点故障会影响部分用户请求超时; |
<1分钟 |
DDB自动主备切换,故障节点需手工恢复 |
机房故障 (机房断电,网络分区等) |
部分用户请求失败 |
<15分钟 |
经过监控检查,切换流量入口至同城机房; |
接下来,咱们每一个层次详细论述。
第一,应用层
下图以最复杂的场景,假设有三个城市,每一个城市都有两个彻底对等的数据中心。三个城市的数据中心也是彻底对等的。
咱们将整个业务数据按照某个维度分红A,B,C三部分。这样任何一部分所有宕机,其余部分照样能够提供服务。对于有的业务,若是省级别的服务中断彻底不能忍受,市级别的服务中断要求恢复时间至关短,而区县级别的服务中断恢复时间能够相对延长。在这种场景下,能够根据地区来区分维度,使得一个区县和另一个区县的数据属于不一样的单元。
为了节约成本,模型可能会更加简化。中心节点和单元化节点不是对称的。中心节点能够实现同城双活,而异地单元化的部分只部署一个机房便可。这样是能知足大部分高可用性需求的。
这种架构要求实现中间件层和数据库层单元化,这个咱们后面会仔细讲。
第二,接入层
单元化要求APP层或者在机房入口区域的接入层,实现中心单元和其余单元节点的流量分发。
对于初始请求没有任何路由标记的,能够随机分发给任何一个单元,也能够根据地区或者运营商在GSLB中分发给某个就近的单元。
应用层接收到请求之后,根据本身所在的单元生成路由信息,将路由信息返回给接入层或者APP。
接下来APP或者接入层的请求,都会带着路由信息,选择相应的单元进行发送,从而实现了请求的处理集中在本单元。
第三,中间件层
在中间件层,咱们以zookeeper为例,分为如下两个场景。
场景1、ZooKeeper 单元化主从多活
在这种场景下,主机房和单元化机房距离相隔较近,时延很小,能够当作一个机房来对待。能够采用ZooKeeper高可用保障经过多ZooKeeper实例部署来达成。
如图所示,主机房zookeeper有leader和follower,单元化机房的zookeeper仅为observer。
场景2、 ZooKeeper 单元化多集群复制
两个机房相距较远,每一个机房部署一套ZooKeeper集群,集群之间进行数据同步。各机房应用链接机房内的ZooKeeper集群,注册的信息经过数据同步,可以被其余机房应用获取到。
单一机房ZooKeeper集群不可用,其他机房不受影响。当前不考虑作不一样机房之间的集群切换。
第四,数据库层
在数据库层,首先要解决的问题是,分布式数据库DDB集群多机房同步复制。
在单元内采用同城主从复制模式,跨单元采用DTS/NDC实现应用层数据双向同步能力。
对于数据的ID分配,应该采起全局惟一ID分配,有两种实现方式,若是主机房和单元化机房距离较近,可采用ID分配依然采用中心式, 全部机房的单元所有向同一中心服务申请ID的方式。若是主机房和单元化机房相隔较远,可采用每一个单元各自分配, 经过特定规则保证每一个机房获得的最终ID不冲突的方式。
第五,缓存层
在缓存层,有两种方式,方式一是集群热备,新增Redis集群做为热备份集群。
主集群与备份集群之间在服务端进行数据同步,经过Redis Replication协议进行同步处理。
离线监听主集群状态,探测到故障则进行主备之间切换,信息经过配置中心下达客户端,类哨兵方式进行监听探活
在这种场景下,集群之间数据在服务端进行同步,正常状况下,集群之间数据会一致。但会存在必定的复制时延。
在故障切换时,可能存在极短期内的数据丢失。若是将缓存仅仅当缓存使用,不要作内存数据库使用,则没有问题。
第二种方式,集群多活。新增集群做为多活集群,正常状况下客户端根据Key哈希策略选择分发到不一样集群。
客户端经过Proxy链接集群中每个节点,Proxy的用处是区分客户端写入与集群复制写入。
集群之间在服务端进行数据双向复制,数据变动经过Redis Replication协议获取。
离线监听主集群状态,探测到故障则进行切换,信息经过配置中心下达客户端,类哨兵方式进行监听探活。
此方案应用于单纯的集群间高可用时,同一个Key在同一段时间内只会路由到同一个集群,数据一致性能够保证。
在故障切换状况下,可能存在极端时间内的数据丢失。
第六,微服务治理平台
做为大规模微服务的微服务治理平台,一方面本身要实现单元化,另一方面要实现流量在不一样单元之间的染色与穿梭。
从API网关,NSF服务治理和管理中心,APM性能管理,GXTS分布式事务管理,容器平台的管控都须要进行跨机房单元化部署。
当请求到达一个单元以后,API网关上就带有此单元的路由信息,NSF服务治理与管理平台在服务之间相互调用的时候,一样会插入此单元的路由信息,当一个单元某实例全挂的时候,能够穿梭到另外一个单元进行调用,并在下一跳调用回本单元,这种方式称为流量染色。

欢迎关注我的公众号
