如何利用 Go 语言开发高性能服务
当我们搭建了一个完整的 Go 项目后,这个项目的性能如何呢?能否应对高并发流量(比如秒杀活动)呢?如果不能,我们该如何优化系统性能呢?性能优化的前提是确定服务瓶颈。
1. 分库分表
数据库通常是 Web 服务的性能瓶颈,为什么呢?因为一般情况下,单个数据库实例的QPS (Queries-Per-Second,每秒查询次数)也就几千,这只能满足小型系统的需求,而某些中大型系统有可能需要承接数万甚至数十万的 QPS,单个数据库实例是绝对扛不住的。那么如何优化数据库性能呢?一方面,可以通过提升机器性能来优化数据库配置,同时通过优化表结构、查询语句等方式尽可能地提升单个数据库实例的 QPS; 另一方面,也可以通过分库分表方式提升数据库的 QPS。
1.1 分库分表基本原理
分库的含义就是将一个数据库拆分成多个数据库,并部署到不同的机器。例如,我们可以将商城项目的用户表、商品表、订单表等分别拆分到3个数据库实例。这样一来,原本一个数据库实例的压力就分散到了 3 个数据库实例(用户库、商品库、订单库)。总体而言,数据库的性能得到了提升。
通过这种方式优化之后就能万无一失了吗?举一个例子,假设商品模块的访问 QPS 在 1 万以上,而单个数据库实例的 QPS 在几千。也就是说,拆分数据库之后,商品库的性能依然无法满足条件。还有什么办法可以优化数据库性能呢?这时候我们通常会选择主从架构(读写分离)方案。首先,从数据库实例会实时同步主数据库实例的数据;其次,读请求可以由从数据库实例处理,写请求由数据库实例处理。通过这种方式,数据库性能可以得到成倍的提升。
使用读写分离方案时,一定要注意:主数据库实例的数据同步到从数据库实例是有延迟的。也就是说,当执行了一条写请求(INSERT、UPDATE、DELETE)时,如果立即执行读请求(QUERY)查询对应的数据,结果可能不符合预期。因为这时候主数据库的数据修改可能还没有同步到从数据库。另外,使用读写分离方案时,项目中获取数据库实例的代码逻辑需要根据操作类型进行相应的调整,改造成本还是不小的。
那还有什么其他方案吗?我们可以在业务代码和数据库实例之间加一层代理服务。代理服务将自己伪装成数据库实例,接收业务请求并转发给后端真正的数据库实例。这样就只需要在代理服务上实现读写分离逻辑即可。
需要说明的是,我们不可能通过增加从数据库来无限制地提升数据库性能,毕竟主、从数据库实例之间的数据同步也是有开销的。另外,虽然可以部署多个从数据库实例,但是主数据库实例只有一个,也就是说通过这种方式无法提升写请求的 QPS。
还有什么方案能优化数据库性能吗?分表。分表就是将一张数据表拆分为多张数据表,这些数据表可以在一个数据库实例中,也可以在多个数据库实例中。众所周知,当一张数据表的数据量过大时,即使命中了索引查询也会变慢(如果没有命中索引,查询将会非常慢),这是因为 MySQL 数据库(以 InnoDB引擎为例)的索引基于 B+ 树实现,查询耗时将会随之增加。所以,分表可以在一定程度上提升数据库性能,即使这些拆分后的数据表都在同一个数据库实例中。当然,如果我们将这些拆分后的数据表分布在多个数据库实例中,数据库性能还可以得到大幅度提升。
分表有两种实现方式:
1)垂直分表:这种方式适用于列非常多的数据表,这时候我们可以将一些不常用的、数据量较大的列拆分到其他数据表。
2)水平分表:这种方式适用于数据量非常大(行记录非常多)的数据表。这是以订单表为例,介绍如何实现水平分表。水平分表的核心在于,以什么维度拆分数据表,比如我们可以按照时间拆分,可以按照订单 ID 拆分,也可以按照用户 ID 拆分。
那到底应该按照哪种方式水平分表呢?这取决于查询需求。设想一下,如果将订单表按照订单 ID 拆分为 64 张表,那么