在设计系统时,需要多思考二八定律,在系统设计初期将有限的资源用到刀刃上,以最小可行产品方式迭代推进。
高并发原则
- 无状态应用是无状态,配置文件有状态,比如,不同机房需要不同数据源,那么需要通过配置文件或者配置中心指定。
- 功能拆分 ,从大到小:系统维度,功能维度,读写维度,AOP维度(根据访问特征,按照AOP拆分,比如,商品详情页分为CDN、页面渲染系统等。CDN就是AOP系统)
- 服务化 负载均衡?调用方越来越多:考虑服务自动注册和发现(Zookeeper和Dubbo)。
部分系统访问量大,导致系统服务打挂(需要提供不同服务分组,隔离访问)。后期随着调用量增加还需要考虑服务限流,白名单等。
还有一些细节,如超时时间、重试机制、服务路由(动态切换不同分组)、故障补偿等 - 消息队列,消息队列是用来解藕一些不需要同步调用的服务或者订阅自己系统关心的变化。使用消息队列可以实现服务解藕(一堆多消费)、异步处理、流量削峰/缓冲等。
- 数据异构,数据异构:即对多表聚合的表,进行一份冗余操作(并将这张表进行分库分表)
数据闭环,商品详情页,因为数据来源太多了,影响服务稳定性的因素就多了,因此最好方法是把使用到的数据进行异构,形成数据闭环。(数据异构->数据聚合->前端展示)。
数据闭环和数据异构其实是一个概念,目的都是实现数据自我控制,当其他系统出问题时不影响自己的系统,或者自己出问题不影响其他系统,一般通过消息队列来实现数据分发 - 缓存银弹,浏览器端缓存(对于实时性不高的数据,如商品详情等可以,但是对于价格、库存等实时数据不行)
APP客户端缓存
CDN缓存,有些页面可以放到离用户近的CDN节点,使用CDN时候,要考虑URL设计,比如不能有随机数,否则每次都穿透回CDN回源服务,相当于源服务没有任何效果。
接入层缓存,对于没有CDN缓存应用说,可以考虑使用Nginx搭建一层接入层,可以考虑如下:
- URL重写:将URL按照指定顺序或者格式重写,去除随机数
- 一致性哈希:按照指定的参数(如分类和商品编号)做一致性Hash,从而保证相同数据落到一台服务器
- proxy_cache:使用内存级/SSD级代理缓存来缓存内容
- shared_dict:如果使用了nginx+lua实现,则可以考虑lua shared_dict进行cache,最大好处就是reload缓存不会消失
此处要注意,对于托底(或兜底数据,指降级后数据),不应该让其缓存,否则用户会在很长一段时间看到。
应用层缓存,在使用Tomcat时,可以使用堆内缓存/堆外缓存,使用local redis cache(一组redis主从同步数据)来代替堆内内存。
分布式缓存,
并发化
大流量缓冲手段:
大流量缓冲,进行一些特殊的设计来保证系统平稳读过这段时期,主要是牺牲强一致性,保证最终一致性
- 扣减库存。Redis扣减库存->记录扣减日志->通过worker同步->库存DB
- 交易订单系统 。结算服务->接单服务->分别发送给订单redis和订单队列表->通过worker去同步数据到订单中心表。并且,由 订单状态即进行检测,可能订单队列订单还没有同步到订单中心表,状态机要根据实际情况进行重试。
高可用原则
- 降级
- 开关集中花降级管理:通过推送机制把开关推送到各个应用。
- 可降级的多级读服务:比如服务调用降级为只读本地缓存,只读分布式缓存,只读默认降级数据
- 开关前置:如果架构是Nginx->Tomcat,讲Tomcat开关前置到Nginx中
- 业务降级:保障数据最终一致性
- 限流:目的是防止恶意请求流量、恶意攻击,或者防止流量超出系统峰值。
- 恶意请求只访问cache
- 恶意穿透到后端应用的可以考虑Nginx的limit
- 恶意IP可以使用nginx deny屏蔽
- 切流量:比如多环境某个机房挂了,或者某个机架挂了,需要进行切流量
- DNS:切换机房入口
- HttpDNS:主要APP场景下,在客户端分配好流量入口,绕过运营商LocalDNS并实现更精确流量调度
- LVS/HaProxy:切换故障的Nginx接入层
- Nginx:切换故障的应用层
- 可回滚
业务设计原则
- 放重设计:比如,结算页或者点击的防止重复提交
- 幂等设计:对重复消息消费的幂等处理
- 流程可定义:
- 装态与状态机:控制订单、交易等状态,并发状态修改问题
- 后台操作可反馈
- 后台系统审批化,对调整价格等日志记录
- 文档和注释
- 备份
参考资料
- 《亿级流量网站架构核心技术》