前言
其实在学生时代,基本上是遇不到高并发的架构,真正遇到高并发的问题,往往是在工作之后,我在学生时代所写的很多项目其实跟高并发是一点边都沾不上,但是我依旧学习了许多高并发的知识,希望以后能派上用场。
当然,其实脱离了实际去讲述高并发并不是正确的,因为不同的系统架构是不一致的,分析高并发与高可用性需要先分析整体架构再来定夺。
本篇文章就是整理在高并发架构下会产生哪些问题,以及部分解决方案,当然也可能将解决方案单独用一篇文章来讲述。
正文
何为高并发
高并发(High Concurrency)是系统设计的一个重要概念,它的目的是让系统能够同时并行处理多个请求。
同时与高并发相关的还有许多名词,如每秒请求数(QPS(Query Per Second)),响应时间(Response Time),吞吐量(Throughput)等等。
高并发所带来的问题
其实我个人认为高并发带来的问题可以简单分为两个部分,一个是服务故障,另一个是数据的不一致。当然如果数据库服务故障也可能导致数据的不一致性,这里我就简单这么分类。
服务单点故障
这个问题是许多架构在遇到高并发请求时所遇到的最基础的问题,如何在如此高的请求量之下保证服务不被击穿。
其实解决这个问题的最好办法是将一个系统拆分成多个系统,再配合负载均衡来实现。简单来说,负载均衡器根据每个系统的性能高低分配请求,如果一个系统故障,另一个系统也可以正常提供服务。
也就是说要避免单点,而是将单点拓展成多个子系统。
这种方案称作服务器集群,一方面是可扩展性强,可以动态添加服务器,另一方面则是可用性较高,缺点就是根据添加的服务器数量,价格也会逐步上升。
这个负载均衡架构,之后会单独整理。
数据的不一致与不完整
这个问题是本文的重点,也是为什么整理这篇文章的原因。
前些日子,某游戏公司因为访问人数过多导致充值数据不一致,只能进行回档处理。这一事情引发了我的思考,为什么会导致数据不一致呢?其实这里的数据不一致不单单是同时操作数据库中的数据而导致出现问题,也可能是缓存与数据库中数据不一致。
举一个例子:
存库量不仅在数据库中,也在缓存中加快访问速度,当需要更新操作时,先行修改了数据库,再去删除缓存,如果删除缓存失败了,那么缓存中就是旧数据,而数据库则是新数据,这就导致了一种数据不一致的情况。
那这种情况解决方案很简单,就是无论如何先删除缓存,再修改数据库,如果删除缓存成功,修改数据库失败,那么尽管数据库数据是旧的,但是缓存为空,从数据库读取数据,不会出现不一致情况;如果删除缓存失败就不用去修改数据库。如此一来便不会出现数据库与缓存不一致的情况。
这里需要说的是,为什么是删除缓存,而不是更新缓存?
原因很简单,在许多复杂的缓存场景中,更新缓存的代价是极高的,例如每次更新完数据库后,是不是要同时更新缓存?但如果频繁修改了一个与缓存相关的表,缓存也会频繁更新,但问题在于缓存是否会被频繁访问?
这就是一种懒加载的思想,只有需要缓存的时候我们再去获得缓存。
接着上一个问题,如果问题更加复杂一下:
假设需要修改库存量,我们先删除了缓存,然后更新数据库,如果在更新数据库的同时又来了一个新的请求读取缓存,此时缓存为空则读取数据库,但是查询到的是修改前的旧数据,并且放入了缓存中,此时更新数据库操作完成,但是缓存中是旧数据,而数据库中是新数据,那么怎么解决这个问题?
其实很容易想到,就是让读操作永远在写操作后面。比如我们可以使用一个队列,当读取缓存为空时,那么就可以将这个请求加入队列当中,然后队列按顺序执行操作,便可以等到缓存更新后再去读取,保证了数据的一致性。
但这种方案也存在一定问题。例如,读操作可能会导致超时,为什么?因为队列中可能会有多个数据的更新操作,如果更新次数过多,会导致读请求长时间阻塞。
还有一个问题就是短时间的大量数据库操作,缓存如果在同一个时间失效的话,会导致多个请求同时访问数据库,需要注意数据库的QPS,并且保证缓存不会在同一时间失效。
上面是讲数据库与缓存的一致性问题,现在讲讲数据库自身的数据一致性。
我们也举一个简单例子:
假设我们没有缓存,还是库存量的问题,我们在更新库存时同时来了多个请求更新库存量,这时候该如何保证数据的一致性?
其实用上文所讲的队列也可以实现,这里不再赘述。主要要讲的是数据库事务与更新锁。数据库事务是一个整体,要嘛全部执行,要嘛全部不执行,失败了就进行回滚。而在事务中,我们通过更新锁(WITH (UPDLOCK))保证其他线程只能等这个事务结束之后才可进行更新操作。
当然锁的引入会导致性能的下降。但这些代价不可避免,为了解决数据一致性,需要牺牲一些性能。
后话
前面说到游戏公司充值数据不一致的问题,据我的猜测,有可能是因为从支付宝或者苹果接口回调的时候发生了问题,由于高并发导致了服务宕机。另一种可能就是更新玩家充值数据时,由于使用数据库集群时,在进行数据库切换时,主从数据不一致,当然这也是我个人猜测。
这篇文章整理了高并发下会引发的问题,并且给了部分解决方案,之后会更新一系列文章,分别是负载均衡分析,数据库集群实现等等文章,来对这篇文章进行补充。
本文在网络上查找了许多文章,并加入了自己的理解。