作者:listenzhang,腾讯 PCG 后台开发工程师
前言
早期从事运单系统的开发和维护工作,从最早的日均百万单,到日均千万单,业务的快速发展再加上外卖业务的特点是,业务量集中在午高峰和晚高峰两个高峰期,所以高峰期并发请求量也是水涨船高,每天都要面对高并发的挑战。拿运单系统来举例,日常午高峰核心查询服务的 QPS 在 20 万以上,Redis 集群的 QPS 更是在百万级,数据库 QPS 也在 10 万级以上,TPS 在 2 万以上。
在这么大的流量下,主要的工作也是以围绕如何建设系统的稳定性和提升容量展开,下面主要从基础设施、数据库、架构、应用、规范这几方面谈谈如何建设高并发的系统。以下都是我个人这几年的一些经验总结,架构没有银弹,因此也称不上是最佳实践,仅供参考。
基础设施
在分层架构中,最底层的就是基础设施。基础设置一般来说包含了物理服务器、IDC、部署方式等等。就像一个金字塔,基础设施就是金字塔的底座,只有底座稳定了,上层才能稳定。
异地多活
多活可以分为同城多活、异地多活等等,实现方式也有多种,比如阿里使用的单元化方案,饿了么使用的是多中心的方案,关于多活的实现可以参考:饿了么多活实现分享。当时做多活的主要出发点是保证系统的高可用性,避免单 IDC 的单点故障问题,同时由于每个机房的流量都变成了总流量的 1/N,也变相提升了系统容量,在高并发的场景下可以抗更多的流量。下图是活的整体架构,来源于上面多活实现的分享文章中。
数据库
数据库是整个系统最重要的组成部分之一,在高并发的场景下很大一部分工作是围绕数据库展开的,主要需要解决的问题是如何提升数据库容量。
读写分离
互联网的大部分业务特点是读多写少,因此使用读写分离架构可以有效降低数据库的负载,提升系统容量和稳定性。核心思路是由主库承担写流量,从库承担读流量,且在读写分离架构中一般都是 1 主多从的配置,通过多个从库来分担高并发的查询流量。比如现在有 1 万 QPS 的以及 1K 的 TPS,假设在 1 主 5 从的配置下,主库只承担 1K 的 TPS,每个从库承担 2K 的 QPS,这种量级对 DB 来说是完全可接受的,相比读写分离改造前,DB 的压力明显小了许多。
这种模式的好处是简单,几乎没有代码改造成本或只有少量的代码改造成本,只需要配置数据库主从即可。缺点也是同样明显的:
主从延迟
MySQL 默认的主从复制是异步的,如果在主库插入数据后马上去从库查询,可能会发生查不到的情况。正常情况下主从复制会存在毫秒级的延迟,在 DB 负载较高的情况下可能存在秒级延迟甚至更久,但即使是毫秒级的延迟,对于实时性要求较高的业务来说也是不可忽视的。所以在一些关键的查询场景,我们会将查询请求绑定到主库来避免主从延迟的问题。关于主从延迟的优化网上也有不少的文章分享,这里就不再赘述。
从库的数量是有限的
一个主库能挂载的从库数量是很有限的,没办法做到无限的水平扩展。从库越多,虽然理论上能承受的 QPS 就越高,但是从库过多会导致主库主从复制 IO 压力更大,造成更高的延迟,从而影响业务,所以一般来说只会在主库后挂载有限的几个从库。
无法解决 TPS 高的问题
从库虽然能解决 QPS 高的问题,但没办法解决 TPS 高的问题,所有的写请求只有主库能处理,一旦 TPS 过高,DB 依然有宕机的风险。
分库分表
当读写分离不能满足业务需要时,就需要考虑使用分库分表模式了。当确定要对数据库做优化时,应该优先考虑使用读写分离的模式,只有在读写分离的模式已经没办法承受业务的流量时,我们才考虑分库分表的模式。分库分表模式的最终效果是把单库单表变成多库多表,如下图。
首先来说下分表,分表可以分为垂直拆分和水平拆分。垂直拆分就是按业务维度拆,假设原来有张订单表有 100 个字段,可以按不同的业务纬度拆成多张表,比如用户信息一张表,支付信息一张表等等,这样每张表的字段相对来说都不会特别多。
水平拆分是把一张表拆分成 N 张表,比如把 1 张订单表,拆成 512 张订单子表。
在实践中可以只做水平拆分或垂直拆分,也可以同时做水平及垂直拆分。
说完了分表,那分库是什么呢?分库就是把原来都在一个 DB 实例中的表,按一定的规则拆分到 N 个 DB 实例中,每个 DB 实例都会有一个 master,相当于是多 mater 的架构,同时为了保证高可用性,每个 master 至少要有 1 个 slave,来保证 master 宕机时 slave 能及时顶上,同时也能保证数据不丢失。拆分完后每个 DB 实例中只会有部分表。
由于是多 master 的架构,分库分表除了包含读写分离模式的所有优点外,还可以解决读写分离架构中无法解决的 TPS 过高的问题,同时分库分表理论上是可以无限横向扩展的,也解决了读写分离架构下从库数量有限的问题。当然在实际的工程实践中一般需要提前预估好容量,因为数据库是有状态的,如果发现容量不足再扩容是非常麻烦的,应该尽量避免。
在分库分表的模式下可以通过不启用查询从库的方式来避免主从延迟的问题,也就是说读写都在主库,因为在分库后,每个 master 上的流量只占总流量的 1/N,大部分情况下能扛住业务的流量,从库只作为 master 的备份,在主库宕机时执行主从切换顶替 master 提供服务使用。说完了好处,再来说说分库分表会带来的问题,主要有以下几点:
改造成本高
分库分表一般需要中间件的支持,常见的模式有两种:客户端模式和代理模式。客户端模式会通过在服务上引