鹰龙数据中心
RSS订阅 | 匿名投稿
您的位置:网站首页 > 高可靠系 > 正文

亿级规模的高可用微服务系统如何轻松设计?

作者:小联 来源: 日期:2019-10-10 7:28:03 人气: 标签:高可用

  说到大规模微服务系统,往往是一些 7*24 时不间断运行的在线系统。那么如何设计一个大规模的微服务系统呢?

  高可用。这类的系统往往需要保持一定的 SLA,7*24 时不间断运行不代表完全不挂,而是有一定的百分比的。 例如我们常说的可用性需达到 4 个 9(99.99%),全年停机总计不能超过 1 小时,约为 53 分钟,也即服务停用时间小于 53 分钟,就说明高可用设计合格。

  用户分布在全国。大规模微服务系统所支撑的用户一般在全国各地,因而每个地区的人,都希望能够就近访问,所以一般不会一套系统服务全国,而是每个地区都要有相应的业务单元,使得用户可以就近访问。

  并发量大,存在波峰波谷。微服务之所以规模比较大,其实是承载的压力比较大,而且需要根据请求的波峰波谷进行弹性伸缩。

  有故障性能诊断和快速恢复的机制。大规模微服务场景下,运维人员很难进行命令式手动运维来控制应用的生命周期,应该采用声明式的运维方法。 另外一旦有了性能瓶颈或者故障点,应该有自动发现定位的机制,迅速找到瓶颈点和故障点,及时修复,才能保障 SLA。

  为了满足以上的要求,这个系统绝不是运维组努力一把,或者开发组努力一把,就能解决的,是一个端到端的,各个部门共同完成的一个目标,所以我们常称为战略设计。

  状态分为分发,处理,存储几个过程,如果对于一个用户的所有的信息都保存在一个进程中,则从分发阶段,就必须将这个用户分发到这个进程,否则无法对这个用户进行处理。

  然而当一个进程压力很大的时候,根本无法扩容,新启动的进程根本无法处理那些保存在原来进程的用户的数据,不能分担压力。

  所以要将整个架构分成两个部分,无状态部分和有状态部分,而业务逻辑的部分往往作为无状态的部分,而将状态保存在有状态的中间件中,如缓存,数据库,对象存储,大数据平台,消息队列等。

  这样无状态的部分可以很容易的横向扩展,在用户分发的时候,可以很容易分发到新的进程进行处理,而状态保存到后端。

  而后端的中间件是有状态的,这些中间件设计之初,就考虑了扩容的时候,状态的迁移,复制,同步等机制,不用业务层关心。

  会话数据等,主要保存在内存中。对于保存在内存里的数据,例如 Session,可以放在外部统一的缓存中。

  文件图片数据,比较大,往往通过 CDN 下发。对于文件,照片之类的数据,应该存放在统一的对象存储里面。

  非结构化数据,例如文本,评论等。对于非结构化数据,可以存在统一的搜索引擎里面,例如 ElasticSearch。

  但是还有一个遗留的问题,就是已经分发,正在处理,但是尚未存储的数据,肯定会在内存中有一些,在进程重启的时候,数据还是会丢一些的,那这部分数据怎么办呢?

  这部分就需要通过重试进行解决,当本次调用过程中失败之后,前序的进程会进行重试,例如 Dubbo 就有重试机制。

  为了保持幂等性,往往要有一个幂等表,通过传入幂等参数匹配幂等表中 ID 的方式,每个操作只被执行一次,而且在实行最终一致性的时候,可以通过不断重试,最终接口调用的成功。

  对于并发条件下,谁先调用,谁后调用,需要通过分布式锁如 Redis,ZooKeeper 等来实现同一个时刻只有一个请求被执行,如何多次执行结果仍然一致呢?则往往需要通过状态机,每个状态只流转一次。

  还有就是乐观锁,也即分布式的 CAS 操作,将状态的判断、更新整合在一条语句中,可以状态流转的原子性。乐观锁并不更新一定成功,需要有对应的机制来应对更新失败。

  通过熔断机制,当一个服务挂了,被影响的服务能够及时熔断,使用 Fallback 数据流程在非关键服务不可用的情况下,仍然可以进行。

  通过线程池和消息队列机制实现异步化,允许服务快速失败,当一个服务因为过慢而阻塞,被影响服务可以在超时后快速失败,不会影响整个调用链。

  当发现整个系统的确负载过高的时候,可以选择降级某些功能或某些调用,最重要的交易流程的通过,以及最重要的资源全部用于最核心的流程。

  还有一种手段就是限流,当既设置了熔断策略,又设置了降级策略,通过全链的压力测试,应该能够知道整个系统的支撑能力。

  因而就需要制定限流策略,系统在测试过的支撑能力范围内进行服务,超出支撑能力范围的,可服务。

  当你下单的时候,系统弹出对话框说 “系统忙,请重试”,并不代表系统挂了,而是说明系统是正常工作的,只不过限流策略起到了作用。

  当我们部署一个服务的时候,对于运维部门来讲,可以机器的状态或者容器的状态是否处于启动状态,也可以到进程是否启动,端口是否等。

  但是对于已经启动的进程,是否能够正常服务,运维部门无法,需要开发每个服务的时候,设计一个有效探活接口,让运维的系统可以通过调用这个接口,来判断进程能够正常提供服务。

  这个接口不要直接返回,而是应该在进程内部探查提供服务的线程是否出去正常状态,再返回相应的状态编码。

  只有这样,开发出来的服务和运维才能合作起来,保持服务处于某个副本数,否则如果一部分服务虽然启动,但是处于假死状态,会使得其他正常服务,无法承受压力。

  要保持线上代码的高可用性,代码质量是关键,大部分线上问题,无论是性能问题,还是稳定性问题,都是代码造成的,而非基础设施造成的。

  而且基础设施的可用率为 99.95%,但是服务层要求的可用率高于这个值,所以必须从业务层高可用来弥补。

  除了下面的高可用架构部分,对于每一个服务来讲,制定良好的代码检查规范和静态扫描工具,通过大量的测试用例,最大化因为代码问题造成的系统不可用,是必须的,是高可用的基础。

  在系统的每一个部分,都要避免单点。系统冗余往往分管控面和数据面,而且分多个层次,往往每一个层次都需要进行高可用的设计。

  对于云的数据面来讲,入口的网关要和机房网络配合做跨机房的高可用,使得入口公网 IP 和负载均衡器,在一个机房故障的情况下,可以切换至另一个机房。

  在云之上要部署 Kubernetes 平台,管控层面 Kubernetes 要实现高可用部署,etcd 要跨机房高可用部署,Kubernetes 的管控组件也要跨机房部署。

  这种情况下,Kubernetes 的管控依然要实现高可用,只不过跨机房的高可用就需要应用层来实现了。

  在应用层,微服务的治理平台,例如注册发现,ZooKeeper 或者 Euraka,APM,配置中心等都需要实现跨机房的高可用。另外就是服务要跨机房部署,实现城市级机房故障迁移能力。

  首先,使用的是 Kubernetes 编排的声明式的运维方式,而非 Ansible 之类命令式的运维方式。

  另外,对于系统的发布,要进行灰度、蓝绿发布,降低系统上线发布风险。要有这样的,任何一个新上线的系统,都是不可靠的。

  其三,完善及应对机制,对系统各节点、应用、组件全面地,能够第一时间快速发现并解决问题。

  而且对于紧急事件,应该有应急预案,应急预案是在高可用已经考虑过之后,仍然出现异常情况下,应该采取的预案,例如三个 etcd 全挂了的情况。

  其四,持续关注线上系统网络使用、服务器性能、硬件存储、中间件、数据库灯指标,重点关注临界状态,也即当前还健康,但是马上可能出问题的状态。

  对于一个在线业务系统来讲,数据库是重中之重,很多的性能瓶颈定位到最后,都可能是数据库的问题。所以 DBA 团队要对数据库的使用,进行把关。

  例如查询没有被索引覆盖,或者在区分度不大的字段上建立的索引,是否持锁时间过长,是否存在锁冲突等等,都会导致数据库慢的问题。

  因而所有上线的 SQL 语句,都需要 DBA 提前审核,并且要对于数据库的性能做持续的,例如慢 SQL 语句等。

  另外对于数据库中的数据量也要持续的,到一定的量就需要改分布式数据库 DDB,进行分库分表,到一定的阶段需要对分布式数据库进行扩容。

  性能问题往往是通过线上性能压测发现的。线上压力测试需要有一个性能测试的平台,做多种形式的压力测试。

  例如容量测试,通过梯度的加压,看到什么时候实在不行。摸高测试,测试在最大的限度之上还能承受多大的量,有一定的余量会保险一些,心里相对比较有底。

  再就是稳定性测试,测试峰值的稳定性,看这个峰值能够撑一分钟,两分钟还是三十分钟。还有秒杀场景测试,限流降级演练测试等。

  只有经过性能压测,才能发现线上系统的瓶颈点,通过不断的修复和扩容瓶颈点,最终才能知道服务之间应该以各种副本数的比例部署,才能承载期望的 QPS。

  对于可能遇到的故障,可以进行故障演练,故意模拟一些故障,来看系统如何反应,是否会因为复,多副本,容错等机制,使得这些故障对于客户端来讲没有影响。

  下面,我们就从架构的每个层次,进行战术设计。我们先来看一下高可用部署架构选型以及他们的优劣:

  高可用性要求和系统的负载度和成本是强相关的。越简单的架构,部署成本越低的架构,高可用性越小,例如的单体应用。

  而微服务化,单元化,异地多活,必然导致架构复杂难以,机房成本比较高,所以要使用多少成本实现什么程度的高可用,是一个权衡。

  首先是应用层,可以通过异地多活单元城市级高可用,这样使得一个城市因为灾难宕机的时候,另外一个城市可以提供服务。

  另外每个多活单元采用双机房机房级高可用,也即同城双机房,使得一个城市中一个机房宕机,另一个机房可以提供服务。

  其次是数据库层,在数据中心之间,通过主从复制或 MGR 实现数据异步复制,在每个集群单元中采用 DDB 分库分表,分库分表中的每个实例都是有数据库同步复制。

  其三是缓存层,在数据中心之间,缓存采用多集群单元化复制,在每个集群单元中采用多副本主从复制。

  其四微服务治理平台层,平台组件异地多活单元了城市级高可用,平台组件每个多活单元采用双机房机房级高可用,平台组件每个机房中采用多副本保例级高可用。

  下图以最复杂的场景,假设有三个城市,每个城市都有两个完全对等的数据中心。三个城市的数据中心也是完全对等的。

  我们将整个业务数据按照某个维度分成 A,B,C 三部分。这样任何一部分全部宕机,其他部分照样可以提供服务。

  对于有的业务,如果省级别的服务中断完全不能,市级别的服务中断要求恢复时间相当短,而区县级别的服务中断恢复时间可以相对延长。

  为了节约成本,模型可能会更加简化。中心节点和单元化节点不是对称的。中心节点可以实现同城双活,而异地单元化的部分只部署一个机房即可。这样是能满足大部分高可用性需求的。

  对于初始请求没有任何由标记的,可以随机分发给任何一个单元,也可以根据地区或者运营商在 GSLB 中分发给某个就近的单元。

  接下来 App 或者接入层的请求,都会带着由信息,选择相应的单元进行发送,从而实现了请求的处理集中在本单元。

  在这种场景下,主机房和单元化机房距离相隔较近,时延很小,可以当做一个机房来对待。可以采用 ZooKeeper 高可用保障通过多 ZooKeeper 实例部署来达成。

  各机房应用连接机房内的 ZooKeeper 集群,注册的信息通过数据同步,能够被其他机房应用获取到。

  单一机房 ZooKeeper 集群不可用,其余机房不受影响。当前不考虑做不同机房之间的集群切换。

  对于数据的 ID 分配,应该采取全局唯一 ID 分配,有两种实现方式,如果主机房和单元化机房距离较近,可采用 ID 分配依然采用中心式, 所有机房的单元全部向同一中心服务申请 ID 的方式。

  如果主机房和单元化机房相隔较远,可采用每个单元各自分配, 通过特定规则每个机房得到的最终 ID 不冲突的方式。

  主集群与备份集群之间在服务端进行数据同步,通过 Redis Replication 协议进行同步处理。

  离线主集群状态,探测到故障则进行主备之间切换,信息通过配置中心下达客户端,类哨兵方式进行探活。

  在这种场景下,集群之间数据在服务端进行同步,正常情况下,集群之间数据会一致。但会存在一定的复制时延。

  在故障切换时,可能存在极短时间内的数据丢失。如果将缓存仅仅当缓存使用,不要做内存数据库使用,则没有问题。

  第二种方式,集群多活。新增集群作为多活集群,正常情况下客户端根据 Key 哈希策略选择分发到不同集群。

  客户端通过 Proxy 连接集群中每一个节点,Proxy 的用处是区分客户端写入与集群复制写入。

  集群之间在服务端进行数据双向复制,数据变更通过 Redis Replication 协议获取。

  离线主集群状态,探测到故障则进行切换,信息通过配置中心下达客户端,类哨兵方式进行探活。

  此方案应用于单纯的集群间高可用时,同一个 Key 在同一段时间内只会由到同一个集群,数据一致性可以。

  作为大规模微服务的微服务治理平台,一方面自己要实现单元化,另外一方面要实现流量在不同单元之间的染色与穿梭。

  从 API 网关,NSF 服务治理和管理中心,APM 性能管理,GXTS 分布式事务管理,容器平台的管控都需要进行跨机房单元化部署。

  当请求到达一个单元之后,API 网关上就带有此单元的由信息,NSF 服务治理与管理平台在服务之间相互调用的时候,同样会插入此单元的由信息。

  当一个单元某实例全挂的时候,可以穿梭到另一个单元进行调用,并在下一跳调用回本单元,这种方式称为流量染色。猫石对话

  

读完这篇文章后,您心情如何?
0
0
0
0
0
0
0
0
本文网址:
下一篇:没有资料