0%

如何设计一个高并发的系统

序言

高并发的核心目标有两点

  • 支持高吞吐
  • 程序逻辑要准确(数据准确)

实际生产中,实现高并发的方案只有一种 分而治之,下面我会详细的分析,到底是如何分而治之。

不考虑带宽等因素的话, 单机支持的TPS肯定有有限的;实际生产中,必然是分布式架构才能支持高并发量。

分而治之

  • 业务系统拆分
  • 分布式架构的分
  • 业务链路拆分
  • 服务对象拆分
  • 入口拆分

如果要支持10w的TPS/QPS,必然是分布式架构(刨开超算除外),既然是分布式架构必然是多个子系统以及每个子系统的多个实例共同协作完成既定的技术指标。

  • 单机治理: 如果说分布式架构是顶层设计,那么代码的编写就是底层基础。线程池必然是支持高并发和高吞吐的第一手工具,但是我们要很好的处理多线程带来的挑战 ① 死锁 ② 数据共享问题 ③ 线程资源管理 。充分的集成测试和单测是比较好保证好单机的性能。

  • 集群治理: 负载均衡, 服务注册发现, 流量调度

  • 架构治理:

    • 缓存
    • 非核心链路异步
    • 分库分表,读写分离
    • 消息队列
    • 降级限流
    • SLA协议规范
    • 堆机器

基本认知

系统的瓶颈往往就是数据库和带宽, 如果是纯内存操作的话(不考虑socket和磁盘操作的前提下),单机支持的QPS/TPS轻松过10万。下面是随便模拟的从map(保存了10万的用户)中取数的逻辑的qps的结果

1
2
Benchmark             Mode  Cnt         Score         Error  Units
MemTest.getUserById thrpt 20 49207999.383 ± 3381035.989 ops/s

如何分?

大型系统的建设,核心和难点就是如何对系统从技术和业务的维度进行拆分,其实阿里所提倡的大中台,小前台的战略就是为系统的拆分提供一套指导方针。

业务维度

其实电商和社交就是一个非常典型的可以按照业务维度进行拆分的一个大型系统,在拆分的时候,我们不仅要考虑到当前能应用的范畴, 更应该考虑未来三年甚至5年可以被应用的范畴。

个人认为,业务维度拆分的最佳实践和指导就是实践领域驱动设计这本书。

技术拆分

技术拆分相对业务拆分就简单明晰,只不过是具体实现根据不同团队对于编码的能力不同而有所不同。常见的中间件类型有

  • 注册中心
  • 配置中心
  • 消息队列
  • 分库分表
  • 分布式缓存
  • 分布式事务
  • 限流降级
  • 应用层网关
  • 链路追踪
  • 开关配置
  • 任务调度
  • 工作流
  • 规则引擎
  • ailab/dataworks

要着重强调一点,中间件实现中不可或缺的一点就是可运维,可配置,可管理。换句话说,就是一点要搭配UI界面,给运维、开发以及测试人员提供可配置和可管理,而且一定要带有容错能力的交互设计能力。这一点对于开发或者运维来说非常重要。

业务链路的拆分

如果说,你能够画出数据流向或者业务流程, 不妨仔细而深入的思考中间哪些链路是可拆分的。不要关注代码实现的细节, 而是关于数据的流向,设计出来的领域模型以及领域模型之间如何相互作用。之后,关于如何拆分你自然而然的就明晰了。

服务对象的拆分

作为开发者,你一定要弄明白你服务的对象是哪一个。如果你做前台的,你必然要服务于用户,你做后台的,必然要为前端同事提供API(HTTP(s)),如果你是做中台,你需要面对不同行业或者市场人员。

举个例子,一个外卖系统,不仅要为客户负责,还要服务于商家,配送人员,运营,营销,金融, 财务,广告等不同领域的人员,共同完成一个系统的或者一个生态的建设。

入口拆分

比如订单的详情页和订单列表可能在大型系统的建设中就是不同的服务,甚至是不同的存储来提供服务的。

如何治?

其实都是分布式技术的问题, 分布式系统核心要解决的是两个问题

  • 消息顺序到达
  • 消息不丢不重

1. 流程中的核心节点与非核心节点区分开来

针对于非核心节点的部分(比如登录成功发送登录成功的消息,支付成功发送支付成功的消息等)可以进行异步化的处理,常见的异步化的处理可以通过消息引擎(发布订阅以及p2p), 线程池来实现。

2. 限流、降级

高并发的系统一定要保证服务的可靠性,即使发生了超过系统负载能力的时候,也要保证大部分的用户是可用的。常见的限流方案有

  • 滑动窗口、滚动窗口
  • 漏桶算法
  • 令牌桶算法
  • 流量控制算法
  • 水位线机制

    常见的一个处理方式是: 漏桶算法是自身系统用来访问外部系统的保护措施;令牌桶算法可以用来控制外部系统访问自身系统,避免自身系统崩溃。

3. 缓存

说到高并发系统的设计,不得不提到的就是缓存, 实际的系统中基本可以分为三大类情形 (1)读 (2)写 (3)命令下发、接收;而且实际情况中,系统的读的场景是比写的场景多的。所以缓存是提高系统并发性的重要措施。一般可以采用一级缓存(直接使用分布式缓存)或者使用二级缓存(分布式缓存+本地缓存)的模式来实现。 需要注意的就是缓存的三大类问题

  • 缓存穿透: 访问大量不存在的key,导致大量流量落到数据库中; (1)规则过滤 (2)不存在也缓存 (3)bloom-filter
  • 缓存击穿: 热点key问题 (1) 分布式锁 (2)失效时间随机
  • 缓存雪崩: 分布式缓存系统本身出现问题; 设置为高可用模式,避免出现单点问题

以及缓存带来的一致性问题(一般来说,采用的都是双删策略)