高并发
高并发(High Concurrency)是指系统运行过程中的一种“短时间内遇到大量操作请求”的情况,主要发生在web系统集中大量访问收到大量请求,例如淘宝双十一、京东618类的活动。该情况的发生会导致系统在这段时间内执行大量操作(对资源的请求、数据库的操作等)。
高并发相关常用的一些指标有:响应时间、吞吐量、每秒查询率QPS、并发用户数。
「响应时间」:系统对请求做出响应的时间。例如系统处理一个http请求需要600ms,这个600ms就是系统的响应时间。
「吞吐量」:单位时间内处理的请求数量。
「QPS」:每秒响应请求数。
「并发用户数」:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线的用户量一定程度上代表了系统的并发用户数。
简单来说,高并发的基本表现就是系统在单位时间内能够同时处理的请求数。高并发没有具体的范围规定多少并发算是高并发,比如你开发的系统最大并发是1000,那么来了1001的并发量对你来说就是高并发,但是这个并发量放在淘宝上,简直什么都算不上。
如果高并发处理不好,不仅会降低用户的体验度(请求响应时间过长等),同时可能导致系统宕机、停止工作等。
使用缓存
大家都知道MySQL加上Redis是一对儿经典的组合。使用Redis作为MySQL的前置缓存,可以为MySQL挡住大部分查询请求,可以很大程度上缓解MySQL并发请求的压力。Redis是一个使用内存保存数据的高性能K-V数据库,它的高性能主要来自于:「简单的数据结构」和「使用内存存储数据」。但是我们需要知道的是内存本身就是一种易失性存储,所以使用Redis不能保证数据可靠存储。从设计上来说,Redis牺牲了数据可靠性,换取了高性能。但也正是这些特性,使得Redis特别适合用来做MySQL的前置缓存。
即使只是把Redis作为缓存来使用,我们在设计Redis缓存的时候,也必须要考虑Redis的这种「数据不可靠性」,或者换句话说,我们的系统在使用Redis的时候,要能兼容Redis丢数据的情况,做到即使Redis发生了丢数据的情况,也不影响系统的数据准确性。
关于缓存更新策略,使用的多的也就是「Read/Write Through模式」和「Cache Aside模式」, 「Read/Write Through模式」 在查询数据的时候,先去缓存中查询,如果命中缓存那就直接返回数据;如果没有命中,那就去数据库中查询,得到查询结果之后把数据写入缓存,然后返回。在更新数据的时候,先去更新数据库,如果更新成功,再去更新缓存中的数据。
我们想一下,这样使用缓存的方式有没有问题?绝大多数情况下可能都没问题。但是,在高并发的情况下,有一定的概率会出现「脏数据」问题,缓存中的数据可能会被错误地更新成了旧数据。
比如,对同一条记录,同时产生了一个读请求和一个写请求,这两个请求被分配到两个不同的线程并行执行,读线程尝试读缓存没命中,去数据库读到了订单数据,这时候可能另外一个写线程在处理写请求的过程中,先后更新了数据和缓存,这个时候就有问题了,拿着旧数据的第一个读线程又把缓存更新成了旧数据,脏数据也就产生了。
这是一种情况,还有比如两个线程对同一个条订单数据并发写,也有可能造成缓存中的脏数据,而且出现脏数据的概率是和系统的数据量以及并发数量正相关的,当系统的数据量足够大并且并发足够多的情况下,这种脏数据几乎是必然会出现的。那么有没有可能避免或者减少脏数据的产生呢,是有的,接下来介绍另外一种模式。
「Cache Aside模式」 Cache Aside模式和上面的Read/Write Through模式非常像,它们处理读请求的逻辑是完全一样的,唯一的一个小差别就是,Cache Aside模式在更新数据的时候,并不去尝试更新缓存,而是去删除缓存。
使用这种模式来更新缓存,可以非常有效地避免并发读写导致的脏数据问题,虽然Cache Aside模式相较于Read/Write Through模式,降低了脏数据产生的概率,但是在高并发下更容易造成缓存穿透引起雪崩。
总结
使用Redis作为MySQL的前置缓存,可以非常有效地提升系统处理高并发的能力,降低请求响应时间。绝大多数情况下,使用Cache Aside模式来更新缓存都是最佳的选择,相比Read/Write Through模式更简单,还能大幅降低脏数据的可能性。特别注意的是,大量缓存穿透引起雪崩的问题,你需要针对具体业务场景来选择合适解决方案。
最后,感谢女朋友在工作和生活中的包容、理解与支持 !