交易型系统设计的一些原则
交易型系统设计的一些原则
一。高并发原则
1。无状态
如果设计的应用是无状态的,那么应用比较容易进行水平扩展。实际生产环境可能是这样的:应用无状态,配置文件有状态。
如:不同的机房需要读取不同的数据源,此时,就需要通过配置文件或配置中心指定。
2。拆分
拆分情况:
【1】。系统维度
按照系统功能/业务拆分。
如:商品系统,购物车,结算,订单系统等。
【2】。功能维度
对一个系统进行功能再拆分。
如:优惠券系统可拆分为:后台券创建系统,领券系统,用券系统。
【3】。读写维度
根据读写比例特征进行拆分。
如:商品系统,交易的各个系统均会读取数据,读的量 大于 写的量。因此可拆分成“商品写服务”与“商品读服务”
“商品读服务”:可考虑用缓存提升性能
“商品写服务”:写的量太大,要考虑分库,分表
有些聚合读取的场景,如商品详情页,可考虑数据异构拆分系统,将分散在多处的数据聚合到一处存储,以提升系统的性能和可靠性。
【4】。AOP维度
根据访问特征,按照AOP进行拆分。
如:商品详情页可分为“CDN” 与 “页面渲染系统”,而“CDN”就是一个AOP系统。
【5】。模块维度
如:按照基础或代码维护特征进行拆分
基础模块分库分表,数据库连接池等。
代码结构一般按三层架构(web , service , dao)进行划分。
3。服务化
首先:判断是不是只需要简单的单点远程服务调用,单点/单机不行,集群是不是可以解决。
在客户端注册多台机器并使用Nginx负载均衡是不是可以解决。
随着调用方越来越多,应考虑使用服务自动注册和发现(如:Dubbo使用ZooKeeper)
其次:考虑服务的分组/隔离。
如:有的系统访问量太大,导致把整个服务打挂,因此需要为不同的调用方法提供不同的服务分组,隔离访问。
后期,随着调用量的增加还要考虑服务限流,黑白名单等。
还有一些细节需要注意:如超时时间,重试机制,服务路由(能动态切换不同的分组),故障补偿等。以上均会影响服务质量。
总结:进程内服务-》单机远程服务-》集群手动注册服务-》自动注册和发现服务-》服务的分组/隔离/路由-》服务治理如限流/黑白名单
4。消息队列
消息队列可以实现服务解耦(一对多消费),异步处理,流量削峰/缓冲等。
如:电商系统中的交易订单数据(该数据有非常多的系统关心并订阅,如:订单生产系统,定期送系统,订单风控系统等)
若订阅者太多,则订阅单个消息队列就会出现瓶颈,需要考虑对消息队列进行镜像复制。
消息队列使用说明:
注意处理生产消息失败,以及消息重复接收时的场景。
有些消息队列产品会提供生产重试功能,在达到指定重试次数还未生产成功时,会对外通知生产失败。此时,对于不能容忍生产失败的业务场景,一定要做好后绪的数据处理工作。如持久化数据要同时增加日志,报警等。
对于消息重复问题,特别是一些分布式消息队列,出于对性能和开销的考虑,在一些场景下会发生消息重复接收,需要在业务层面进行防重处理。
【1】大流量缓冲
在电商大促时,系统流量会高于正常流量(几倍/几十倍)。需要进行一些特殊设计保证系统度过这段时期。解决手段:牺牲强一致性而保证最终一致性。
如:扣减库存,可考虑如下设计:
如:交易订单系统,可考虑如下设计:
【2】数据校对
在使用了消息异步机制场景下,可能存在消息的丢失,需要考虑进行数据校对和修正来保证数据的一致性和完整性。
可通过worker定期去扫描原始表,通过对数据业务进行校对,有问题的要进行补偿,扫描周期根据实际场景定义。
5。数据异构
【1】。数据异构
订单分库分表一般按订单ID进行划分。若查询某用户的订单列表,需聚合多个表的数据后才能返回,这样会导致订单表的读性能很低。
此时,需要对订单表进行异构,异构一套用户订单表,按用户ID分库分表。
另:还需要考虑对历史订单数据归档处理,以提升服务的性能和稳定性。
注:有些数据异构的意义不大,如:库存价格。可考虑异步加载或合并并发请求。
【2】。数据闭环
如商品详情页,数据来源太多,影响服务稳定性。最好办法是将使用到的数据进行异构存储,形成数据闭环。
(1)。数据异构:
通过如MQ机制接收数据变更,原子化存储到合适的存储引擎。
(2)。数据聚合:
可选。
数据异构目的:把数据从多个数据源拿过来
数据聚合目的:将从多个数据源拿过来的数据做个聚合,前端可通过一次调用拿到所有数据。此步骤一般存储到KV存储中。
(3)。前端展示:
前端通过一次或少量几次调用拿到所需要的数据。
此种方式好处:数据的闭环,任何依赖系统出问题,还能正常工作,只是更新会有积压,但不影响前端展示。
6。缓存银弹
【1】浏览器端缓存
设置请求的过期时间,如对响应头Expires,Cache-control进行控制。
此种机制适用于对实时性不太敏感的数据。如商品详情页框架,商家评分,评价,广告词等。
但对于价格,库存等实时要求比较高的数据,不能做浏览器缓存。
【2】APP客户端缓存
在大促时为防止瞬间流量冲击,一般会在大促之前把APP需要访问的一些素材(如js/css/image等)提前下发到客户端进行缓存,在大促时即不用拉取这些素材。
还有如首屏数据也可缓存起来,在网络异常情况下,有托底数据给用户展示。还有如APP地图一般也会做地图的离线缓存。
【3】CDN缓存
有些页面,活动页,图片等服务可考虑将页面/活动页/图片推送到离用户最近的CDN节点。让用户能在离他最近的节点找到想要的数据。
一般2种机制:
(1)。推送机制
当内容变更后主动推送到CDN边缘节点
(2)。拉取机制
先访问边缘节点,当没有内容时,回源到源服务器拿到内容并存储到节点上。
使用CDN时要考虑URL 设计:URL中不能有随机数,否则每次都穿透CDN回源到源服务器,相当于CDN没有任何效果。
对于爬虫,可以返回过期数据而选择不回源。
【4】接入层缓存
对于没有CDN缓存的应用来说,可考虑使用如Nginx搭建一层接入层,该接入层可考虑使用如下机制实现:
(1)。URL重写
将URL按指定的顺序或者格式重写,去除随机数。
(2)。一致性哈希
按照指定的参数(如分类/商品编号)做一致性HASH,从而保证相同数据落到一台服务器上。
(3)。proxy_cache
使用内存级/SSD级代理缓存来缓存内容
(4)。proxy_cache_lock
使用lock机制,将多个回源合并为一个,以减少回源量,并设置相应的lock超时时间。
(5)。shared_dict
若架构使用了nginx+lua实现,可考虑用lua shared_dict进行cache,最大好处是reload缓存不会丢失
注:对于托底(或兜底,指降级后显示的)数据或异常数据,不应该让其缓存,否则用户会在很长一段时间里看到这些数据。
【5】应用导缓存
使用Tomcat时,可使用堆内缓存/堆外缓存
堆内缓存最大问题:重启tomcat时,缓存会丢失。当流量风暴来临,可能冲垮应用。
可考虑使用load redis cache来代替堆外内存,load redis cache通过在应用所在服务器上部署一组redis,应用直接读本机redis获取数据,多机之间使用主从机制同步数据。这种方式没有网络消耗,性能最优。
或在接入层使用shared_dict来将缓存前置,以减少风暴。
【6】分布式缓存
有一种机制是要废弃分布式缓存,改成应用load redis cache。(适宜数据量不大)
若数据量太大,单服务器存储不了,可使用分片机制将流量分散到多台,或直接用分布式缓存实现。
常见分片规则:一致性哈希
7。并发化
串行获取数据时间共计:60ms
若C依赖于A&B,D独立,数据E依赖于C。则
数据A/B/D并行,只需30秒。(获取A,B时间15ms,再获取C时间10ms,再获取E时间5ms共计30ms.此时D已并行执行完)
二。高可用原则
1。降级
【1】开关集中化管理
通过推送机制,把开关推送到各个应用。
【2】可降级的多级读服务
如:服务调用降级为只读本地缓存,只读分布式缓存,只读默认降级数据(如库存状态默认有货)
【3】开关前置化
若架构是Nginx-》tomcat,可将开关前置到Nginx接入层。
【4】业务降级
当高并发流量来袭,为保障用户能下单,能支付是核心要求,并保障数据最终一致。
此时可将一些同步调用改为异步调用,优先处理高优先级数据或特殊特征数据,合理分配进入系统的流量,保障系统可用。
2。限流
限流目的:防止恶意请求流量,恶意攻击,或防止流量超出系统峰值,
思路:
【1】。恶意请求流量只访问到cache
【2】。对于穿透到后端应用的流量可考虑使用Nginx的limit模块处理
【3】。对于恶意IP可使用nginx deny进行屏蔽
原则是限制流量穿透到后端薄弱的应用层。
3。切流量
【1】DNS:切换机房入口
【2】HttpDNS:主要APP场景下,在客户端分配好流量入口,绕过运营商LocalDNS并实现更精准流量调度
【3】LVS/Haproxy:切换故障的Nginx接入层
【4】Nginx:切换故障的应用层
4。可回滚
版本化目的:实现可审计可追溯,且可回滚。
如:事务,代码库,部署版本,数据版本,表态资源版本回滚。
三。业务设计原则
1。防重设计
2。幂等设计
3。流程可定义
4。状态与状态机
5。后台系统操作可反馈
6。后台系统审批化
7。文档和注释
8。备份
总结: