简介:零碎设计与架构实践这个问题,答复起来十分宽泛,根本所有的技术实践都能够涵盖。作为一个撸代码快 10 年的后端技术人员,简略发表一下我的认识。
原创 勇剑 淘系技术 7 月 15 日
零碎设计与架构,与零碎的业务类型关联还是很大的,比方传统的业务零碎次要关注的是领域建模设计,高并发、高可用、数据一致性等零碎,在设计的时候会与业务零碎有较大的差异,所以这里针对不同类型的零碎,来简略介绍一下设计的时候面临的一些难点与解决方案。
背景惯例业务零碎设计要害——畛域模型
业务零碎设计的要害是在于如何定义零碎的模型以及模型之间的关系,其中次要是畛域模型的定义,当咱们在模型确定之后,模型之间的关系也会随之明确。
模型设计能够参考畛域模型的经典书籍《Domain-Driven Design》一书,通过这个根本能够对畛域定义、防腐层、贫血模型等概念有一个较为清晰的意识了。
单个利用内的畛域模型零碎也须要留神畛域分层,作为开发大家是不是见过、重构过很多 Controller-Service-DAO 款式的代码分层设计?往往在在做重构的时候会令人吐血。
设计较好的畛域设计这里给一个分层倡议:
▐ 接口层 Interface
次要负责与内部零碎进行交互 & 通信,比方一些 dubbo 服务、Restful API、RMI 等,这一层次要包含 Facade、DTO 还有一些 Assembler。
▐ 应用层 Application
这一层蕴含的次要组件就是 Service 服务,然而要特地留神,这一层的 Service 不是简略的 DAO 层的包装,在畛域驱动设计的架构外面,Service 层只是一层很“薄”的一层,它外部并不实现任何逻辑,只是负责协调和转发、委派业务动作给更上层的畛域层。
▐ 畛域层 Domain
Domain 层是畛域模型零碎的外围,负责保护面向对象的畛域模型,简直全副的业务逻辑都会在这一层实现。外部次要蕴含 Entity(实体)、ValueObject(值对象)、Domain Event(畛域事件)和 Repository(仓储)等多种重要的畛域组件。
▐ 基础设施层 Infrastructure
它次要为 Interfaces、Application 和 Domain 三层提供撑持。所有与具体平台、框架相干的实现会在 Infrastructure 中提供,防止三层特地是 Domain 层掺杂进这些实现,从而“净化”畛域模型。Infrastructure 中最常见的一类设施是对象长久化的具体实现。
高并发零碎设计
在面试中是不是常常被问到一个问题:如果你零碎的流量减少 N 倍你要怎么从新设计你的零碎?这个高并发的问题能够从各个层面去解,比方
▐ 代码层面
锁优化(采纳无锁数据结构),次要是 concurrent 包上面的对于 AQS 锁的一些内容.
数据库缓存设计(升高数据库并发争抢压力),这里又会有缓存、DB 数据不统一的问题,在理论应用中,高并发零碎和数据一致性零碎采纳的策略会截然相同。
数据更新时采纳合并更新,能够在应用层去做更新合并,同一个 Container 在同一时间只会有一个 DB 更新申请。
其余的比方基于 BloomFilter 的空间换工夫、通过异步化升高解决工夫、通过多线程并发执行等等。
▐ 数据库层面
依据不同的存储诉求来进行不同的存储选型,从晚期的 RDBMS,再到 NoSql(KV 存储、文档数据库、全文索引引擎等等),再到最新的 NewSql(TiDB、Google spanner/F1 DB)等等。
表数据结构的设计,字段类型抉择与区别。
索引设计,须要关注聚簇索引原理与笼罩索引打消排序等,至于最左匹配准则都是烂大巷的常识了,高级一点索引打消排序的一些机制等等,B+ 树与 B 树的区别。
最初的惯例伎俩:分库分表、读写拆散、数据分片、热点数据拆分等等,高并发往往会做数据分桶,这外面往深了去说又有很多,比方分桶如何初始化、路由规定、最初阶段怎么把数据合并等等,比拟经典的形式就是把桶分成一个主桶 + N 个分桶。
▐ 架构设计层面
分布式系统为服务化
无状态化反对程度弹性扩缩容
业务逻辑层面 failfast 疾速失败
调用链路热点数据前置
多级缓存设计
提前容量布局等等
高可用零碎设计
对于可用性要求十分高的零碎,个别咱们都说几个 9 的可用率,比方 99.999% 等。
面对高可用零碎设计也能够从各个方面来进行剖析
代码层面:须要关注分布式事务问题,CAP 实践是面试的惯例套路
软件层面:利用反对无状态化,部署的多个模块齐全对等,申请在任意模块处理结果完全一致 => 模块不存储上下文信息,只依据申请携带的参数进行解决。目标是为了疾速伸缩,服务冗余。常见的比方 session 问题等。
▐ 负载平衡问题
软件部署多份之后,如何保证系统负载?如何抉择调用机器?也就是负载平衡问题
广义上的负载平衡依照类型能够分为这几种:
- 硬件负载:比方 F5 等
- 软件负载:比方 LVS、Ngnix、HaProxy、DNS 等。
- 当然,还有代码算法上的负载平衡,比方 Random、RoundRobin、ConsistentHash、加权轮训等等算法
狭义上的负载平衡能够了解为负载平衡的能力,比方一个负载平衡零碎须要如下 4 个能力:
- 故障机器主动发现
- 故障服务主动摘除(服务熔断)
- 申请主动重试
- 服务复原主动发现
▐ 幂等设计问题
下面提负载平衡的时候,狭义负载平衡须要实现主动重试机制,那么在业务上,咱们就必须保障幂等设计。
这里能够从 2 个层面来进行思考:
申请层面
因为申请会重试所以必须做幂等,须要保障申请反复执行和执行一次的后果完全相同。申请层面的幂等设计须要在数据批改的层做幂等,也就是数据拜访层读申请人造幂等,写申请须要做幂等。读申请个别是人造幂等的,无论查问多少次返回的后果都是统一。这其中的实质实际上是分布式事务问题,这里上面再具体介绍。
业务层面
不幂等会造成诸如处分多发、反复下单等十分重大的问题。业务层面的幂等实质上是分布式锁的问题,前面会介绍。如何保障不反复下单?这里比方 token 机制等等。如何保障商品不超卖?比方乐观锁等。MQ 生产方如何保障幂等等都是面试的常见题。
▐ 散布缩式
业务层面的幂等设计实质上是分布式锁问题,什么是分布式锁?分布式环境下锁的全局惟一资源,使申请串行化,理论体现互斥锁,解决业务层幂等问题。
常见的解决形式是基于 Redis 缓存的 setnx 办法,但作为技术人员应该分明这其中还存在单点问题、基于超时工夫无奈续租问题、异步主从同步问题等等,更深一点,CAP 实践,一个 AP 零碎实质上无奈实现一个 AP 需要,即便是 RedLock 也不行。
那咱们如何去设计一个分布式锁呢?强一致性、服务自身要高可用是最根本的需要,其余的比方反对主动续期,主动开释机制,高度形象接入简略,可视化、可治理等。
基于存储层的牢靠的解决方案比方:
- zookeeper
CP/ZAB/N+ 1 可用:基于长期节点实现和 Watch 机制。
- ETCD
CP or AP/Raft/N+ 1 可用:基于 restful API;KV 存储,强一致性,高可用,数据牢靠:长久化;Client TTL 模式,须要心跳 CAS 惟一凭证 uuid。
▐ 服务的熔断
微服务化之后,零碎分布式部署,零碎之间通过 RPC 通信,整个零碎产生故障的概率随着零碎规模的增长而增长,一个小的故障通过链路传导放大,有可能造成更大的故障。心愿在调用服务的时,在一些非关键门路服务产生服务质量降落的状况下,抉择尽可能地屏蔽所造成的影响。
大部分熔断返回默认值 null,也能够定制,RPCClient 原生反对最好,业务方少改代码(熔断放的中央),进入熔断时,打印熔断日志,同时返回 Exception(业务方定制熔断办法),须要有服务治理平台,能够看到服务的状态、是否降级、是否熔断、能够实时下发阀值配置等。
▐ 服务降级
服务整体负载超出预设的下限,或者行将到来的流量预计将会超过阀值,为了保障重要或者根本的服务可能失常运行,回绝局部申请或者将一些不重要的不紧急的服务或工作进行服务的提早应用或暂停应用。
次要的伎俩如下:
- 服务层降级,次要伎俩
回绝局部申请(限流),比方缓存申请队列,回绝局部等待时间长的申请;依据 Head,来回绝非核心申请;还有其余通用算法上的限流比方令牌桶、漏桶算法等等。
敞开局部服务:比方双 11 大促 0 点会敞开逆向退款服务等等。
分级降级:比方自治式服务降级,从网关到业务到 DB 依据拦挡、业务规定逐步升高上游申请量,体现上是从上到下的解决能力逐步降落。
- 数据层降级
比方流量大的时候,更新申请只缓存到 MQ,读申请读缓存,等流量小的时候,进行补齐操作(个别数据拜访层如果做了降级,就没必要在数据层再做了)
- 柔性可用策略
比方一些指定最大流量的限流工具,又或是依据 CPU 负载的限流工具等,须要保障主动关上,不依赖于人工。
▐ 公布形式引发的可用性问题
公布形式也是影响高可用的一个点,哈哈,以前还经验过一些线上间接停机公布的案例(银行外部零碎),不过作为高大上的互联网,次要会采纳这几种公布形式:灰度公布、蓝绿公布、金丝雀公布等等。
数据一致性零碎设计
个别一些金融、账务系统对这一块要求会十分严格,上面次要介绍下这外面波及到的事务一致性、一致性算法等内容。
▐ 事务一致性问题
在 DB 层面,个别通过 刚性事务 来实现数据一致性,次要通过 预写日志 (WAL) 的形式来实现,WAL(write ahead logging) 预写日志的形式。就是所有对数据文件的批改,必须要先写日志,这样,即便在写数据的时候解体了,也能通过日志文件复原,传统的数据库事务就是基于这一个机制(REDO 已提交事务的数据也求改 UNDO 未提交事务的回滚)。
除了这个形式之外,还有一个就是通过 影子数据块 来进行数据备份,提前记录被批改的数据块的批改前的状态,备份起来,如果须要回滚,间接用这个备份的数据块进行笼罩就好了。
其余的就是基于二阶段提交的 XA 模型 了。
然而目前互联网零碎,曾经宽泛采纳分布式部署模式了,传统的刚性事务无奈实现,所以 柔性事务成了目前支流的分布式事务解决防备,次要的模式有上面几种:
- TCC 模式 / 或者叫 2 阶段模式
在 try 阶段预扣除资源(然而不锁定资源,晋升可用性),在 Confirm 或者 Cancel 阶段进行数据提交或者回滚。个别须要引入协调者,或者叫事务管理器。
- SAGA 模式
业务流程中每个参与者都提交本地事务,当呈现某一个参与者失败则弥补后面曾经胜利的参与者,反对向前或者向后弥补。
- MQ 的事务音讯
就是先发 halfMsg,在解决完之后,再发送 commit 或者 rollback Msg,而后 MQ 会定期询问 producer,halfMsg 能不能 commit 或者 rollback,最终实现事务的最终一致性。实际上是把弥补的动作委托给了 RocketMQ。
- 分段事物(异步确保)
基于可靠消息 + 本地事务音讯表 + 音讯队列重试机制。目前这也是一些大厂的支流计划,外部个别称为分段事物。
柔性事务根本都是基于最终一致性去实现,所以必定会有 弥补 动作在外面,在达到最终一致性之前,对用户个别展现 软状态。
须要留神的一点是,并不是所有的零碎都适宜引入数据一致性框架,比方用户能够随时批改本人发动的申请的状况,例如,商家设置后盾零碎,商户会随时批改数据,这里如果波及到一致性的话,引入一致性框架会导致弥补动作达到最终一致性之前,资源锁会阻塞用户后续的申请。导致体验较差。这种状况下就须要通过其余伎俩来保障数据一致性了,比方数据对账等操作。
▐ 一致性算法
从晚期的 Paxos 算法,再到前面衍生的 zab 协定(参考:A simple totally ordered broadcast protocol),提供了当下牢靠的分布式锁的解决方案。再到起初的 Raft 算法(In Search of an Understandable Consensus Algorithm),也都是分布式系统设计外面须要理解到的一些常识要点。
最初
这里简略介绍了不同零碎设计的时候会面临的一些难点,根本外面每一个点,都是前人在解决各种疑难问题的路线上一直摸索,最终才得出的这些业界解决方案,出现在大家眼前,作为一个技术人员,学会这些技术点只是工夫问题,但这种发现问题、直面问题、再到解决问题的能力和精力才是咱们最值得学习的中央,也是做为一个零碎设计人员或者说是架构师的必要能力。
原文链接
本文为阿里云原创内容,未经容许不得转载。