Java生鲜电商平台-高可用微服务系统如何设计?数据库
说明:Java生鲜电商平台高可用架构每每有如下的要求:后端
高可用。这类的系统每每须要保持必定的 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,进行分库分表,到必定的阶段须要对分布式数据库进行扩容。
故障演练和性能压测
再好的规划也比不上演练,再好的性能评估也比不上在线的性能压测。
性能问题每每是经过线上性能压测发现的。线上压力测试须要有一个性能测试的平台,作多种形式的压力测试。
例如容量测试,经过梯度的加压,看到何时实在不行。摸高测试,测试在最大的限度之上还能承受多大的量,有必定的余量会保险一些,内心相对比较有底。
再就是稳定性测试,测试峰值的稳定性,看这个峰值可以撑一分钟,两分钟仍是三十分钟。还有秒杀场景测试,限流降级演练测试等。
只有通过性能压测,才能发现线上系统的瓶颈点,经过不断的修复和扩容瓶颈点,最终才能知道服务之间应该以各类副本数的比例部署,才能承载指望的 QPS。
对于可能遇到的故障,能够进行故障演练,故意模拟一些故障,来看系统如何反应,是否会由于自修复,多副本,容错等机制,使得这些故障对于客户端来说没有影响。
战术设计
下面,咱们就从架构的每一个层次,进行战术设计。咱们先来看一下高可用部署架构选型以及他们的优劣:
高可用性要求和系统的负载度和成本是强相关的。越简单的架构,部署成本越低的架构,高可用性越小,例如上面的单体应用。
而微服务化,单元化,异地多活,必然致使架构复杂难以维护,机房成本比较高,因此要使用多少成本实现什么程度的高可用,是一个权衡。
高可用的实现须要多个层次一块儿考虑:
首先是应用层,能够经过异地多活单元保证城市级高可用,这样使得一个城市由于灾难宕机的时候,另一个城市能够提供服务。
另外每一个多活单元采用双机房保证机房级高可用,也即同城双机房,使得一个城市中一个机房宕机,另外一个机房能够提供服务。
再者每一个机房中采用多副本保证明例级高可用,使得一个副本宕机的时候,其余的副本能够提供服务。
其次是数据库层,在数据中心之间,经过主从复制或 MGR 实现数据异步复制,在每一个集群单元中采用 DDB 分库分表,分库分表中的每一个实例都是有数据库同步复制。
其三是缓存层,在数据中心之间,缓存采用多集群单元化复制,在每一个集群单元中采用多副本主从复制。
其四微服务治理平台层,平台组件异地多活单元保证了城市级高可用,平台组件每一个多活单元采用双机房保证机房级高可用,平台组件每一个机房中采用多副本保证明例级高可用。
当有了以上高可用方案以后,则如下的故障等级以及影响时间以下表格:
接下来,咱们每一个层次详细论述。
应用层
下图以最复杂的场景,假设有三个城市,每一个城市都有两个彻底对等的数据中心。三个城市的数据中心也是彻底对等的。
咱们将整个业务数据按照某个维度分红 A,B,C 三部分。这样任何一部分所有宕机,其余部分照样能够提供服务。
对于有的业务,若是省级别的服务中断彻底不能忍受,市级别的服务中断要求恢复时间至关短,而区县级别的服务中断恢复时间能够相对延长。
在这种场景下,能够根据地区来区分维度,使得一个区县和另一个区县的数据属于不一样的单元。
为了节约成本,模型可能会更加简化。中心节点和单元化节点不是对称的。中心节点能够实现同城双活,而异地单元化的部分只部署一个机房便可。这样是能知足大部分高可用性需求的。
这种架构要求实现中间件层和数据库层单元化,这个咱们后面会仔细讲。
接入层
单元化要求 App 层或者在机房入口区域的接入层,实现中心单元和其余单元节点的流量分发。
对于初始请求没有任何路由标记的,能够随机分发给任何一个单元,也能够根据地区或者运营商在 GSLB 中分发给某个就近的单元。
应用层接收到请求之后,根据本身所在的单元生成路由信息,将路由信息返回给接入层或者 App。
接下来 App 或者接入层的请求,都会带着路由信息,选择相应的单元进行发送,从而实现了请求的处理集中在本单元。
中间件层
在中间件层,咱们以 ZooKeeper 为例,分为如下两个场景:
场景一:ZooKeeper 单元化主从多活
在这种场景下,主机房和单元化机房距离相隔较近,时延很小,能够当作一个机房来对待。能够采用 ZooKeeper 高可用保障经过多 ZooKeeper 实例部署来达成。
如图所示,主机房 ZooKeeper 有 Leader 和 Follower,单元化机房的 ZooKeeper 仅为 Observer。
场景二: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 服务治理与管理平台在服务之间相互调用的时候,一样会插入此单元的路由信息。
当一个单元某实例全挂的时候,能够穿梭到另外一个单元进行调用,并在下一跳调用回本单元,这种方式称为流量染色。
联系QQ:137071249
QQ群:793305035