我最近刚看完一篇文章,讲了几种让数据库系统跑得更快的方法。你看咱们平时的业务场景里,读数据比写数据要多得多,有时候能差个十多倍呢。要是主库成了瓶颈,整个系统就都得慢下来。那到底为啥会慢呢?主要是每次和数据库打交道的时候,都得花时间握手、验证身份、解析SQL、选优化器,这些步骤加起来特别费时间。再加上那些经常被访问的数据反复往磁盘里写,磁盘IO就成了隐形的大麻烦。主库压力一大,数据一致性、高可用这些要求也跟着上来了,反而让系统变得更脆弱。 为了解决这个问题,文章里介绍了四个特别有效的手段: 第一个是用连接池。以前每来一个请求都得重新建立连接,现在提前把一堆连接准备好。业务线程想用的时候就从池里拿一个出来用,用完了再放回去。这样就不用每次都做三次握手了,省了不少时间。 第二个是用异步IO。自己动手实现MySQL协议级别的异步解析,请求发出去后线程不用傻傻地等着结果回来,还能接着去干别的事。等到结果真的回来了再去回调处理就行了。 第三个是用SQL预编译。先把准备好的语句发给数据库执行(PREPARE),这样就能跳过那些词法、句法分析和权限检查了。只关注核心的数据操作就行,单条语句的执行时间能平均下降30%以上呢。 第四个就是读写分离了。把写操作集中在主库上做,把读操作分散到从库上去处理。只要水平扩展多几个从库,读的性能就能线性提升上去。 文章还讲到了主从复制不只是分摊压力的问题。半同步复制能保证写操作至少成功同步一次到从库上。不过在读写分离的时候,最好还是让读操作回到主库去做,以免从库一下子压力太大导致性能下降。 缓存层也是个大重点。MySQL自带的InnoDB Buffer Pool会把最常用的索引块和表数据存到内存里用LRU算法来淘汰不太用的东西。不过缺点是业务代码控制不了缓存内容,热点数据有可能被换出来了。 而像Redis或者Memcached这类缓存数据库就完全不一样了,所有的数据都存在内存里读写速度能提升10倍以上。虽然它们也能通过RDB或者AOF这种持久化机制把热点数据写到磁盘上保存下来。 系统引入了Redis之后状态会变得复杂起来。可能出现五种情况:比如MySQL有数据Redis没有、MySQL没了Redis还有、两边都有但不一致等等。核心策略还是以MySQL的数据为准绳。 读取的时候先查Redis有没有数据,如果Redis有了就直接返回;如果没有才去查MySQL。写数据的时候如果想要强一致性就得双写Redis;如果只想要最终一致性就先写Redis再写MySQL。 至于主从复制的原理嘛也很简单:主库的I/O线程会把DML操作写入binlog文件里;从库的I/O线程再去拉取这个binlog文件并写入自己的relay log里;最后SQL线程再去执行这个relay log完成同步。 binlog是逻辑日志记录的是SQL语句的内容redolog是物理日志记录的是数据块的修改位置两者配合起来能保证系统崩溃后还能恢复数据安全。 如果想把这些技术串成一个闭环形成一张图的话其实就是把连接池、读写分离、缓存同步还有异常防护这些环节都串联起来了。 最后文章还提到了三种常见的缓存异常情况:穿透、击穿和雪崩。穿透就是黑客发了一大堆不存在的key请求来攻击数据库;解决办法可以在请求不存在的时候给它存一个空值并设个短过期时间下次就能自动过滤掉了或者用布隆过滤器提前拦截掉不可能存在的key。 击穿就是热点key过期的一瞬间大量并发请求都没命中导致数据库压力大增;这时候可以用分布式锁或者互斥量来控制并发回源访问数据库。 雪崩就是大量的key同时失效导致系统一下子瘫痪;解决办法就是随机设置过期时间错开高峰期同时部署Redis的高可用集群或者哨兵模式保证服务不宕机在系统重启前先预热一些热点数据。