相信很多同学都使用缓存,那么在使用的过程中是否遇到过一些问题(本文拿Redis为例):
1. 在集群环境中,如果遇到了大量的查询,先经过缓存拦截,缓存没有再走到DB。这种是普通的操作 Client -> Redis -> DB。那么如果请求的数量非常大的时候,直接穿透了缓存该如何处理呢?
1.1 出现这种情况的时候可能会出现大量查询相同ID的数据,比如瞬间来了一万条ID=100的请求,在这个时候数据库里一次性要查一万条数据,此时如何处理?
1.2 出现数据库中不存在ID=100的时候如何处理?此时有两种情况,第一种是恶意的请求,第二种是此时数据库中不存在,可能在后续的某一个时间段存在了如何处理?
2. 还是在集群环境中,还是大量的查询,此时瞬间来了一万个ID不同的数据,此时缓存中也不存在,这种情况如何处理?
3. 使用缓存中还有哪些注意点?
下面根据我自己学习和研究来找到一些解决的方案,因为网上有太多的解决方案,大家可以找到自己业务场合适的方案,这才是最重要的。
第一种情况为缓存穿透,在高并发的场景下,如果缓存穿透大量的请求直接走到DB,这样DB就会直接被大量的SQL请求拖垮,然后数据库无响应,这个时候数据库出现宕机,整个系统崩溃。但是这里边有以上两种情况,瞬间过来一万个ID=100的请求时,在集群中,如果瞬时间代码都出现在判断缓存没有时,那么这一万个请求也是直接走到数据库中请求,虽然一万个请求都到数据库上,有人觉得可能数据库还可以支持,但是如果瞬间来十万个的话数据库也会抗不住的。
此时,我们应该使用Redis的锁机制setnx,大家可以网上搜索一下资料。主要的功能是将key锁住,然后在集群中只有一台机器获取锁,并操作数据库。这里边有个小细节,那就是一个一个锁,最后其实还是一万次操作DB,只不过谁先获取数据库谁进行操作。这里边就需要再精细设计了,那就是在里边再加入一层缓存,即获取锁之后,从数据库取之前再次从缓存中获取这个Key的值,如果有值了,就直接返回即可,如果没有值,就从数据库里边查询一次,然后再放到缓存即可。
如果数据库里没有这个值,我们也需要进行特殊处理,设置这个值的过期时间尽量短一些,然后将它的key对应的value设置为‘--’,这样我看一看到value为‘--’,就知道数据库没有数据。如果缓存超过时间后自动就没有了,然后再次请求数据库查询。这样设计一万次相同ID的话,需要的也只是一次数据库操作。其实还有另外一个思路就是这,这个数据是读操作,那么我们就和其他的key设置相同的时间,当数据库中有了这条数据,即产生了insert语句时,我们需要更新缓存即可。
第二种情况,高并发时瞬间一万个不同的ID过来,这个会产生一万不同的key,这样采用Redis的分布式锁显然也会触发一万个请求,在这其中,也有两种情况,一是数据库中确实有这一万个请求,第二种数据库里没有这一万个请求,此种情况可能是爬虫也可能是恶意攻击。
这种情况应用的策略就是采用队列,每个请求需要将其唯一的标志带过来,比如登录的用户邮箱,IP等信息,假如这一万个都是真实的用户时,并且都是有效的请求,此时我们将请求放入执行队列,一次从队列中取出一千个进行处理,这样一万个请求只需要十次处理完成,同时我们还需要以上的锁机制和没有数据时将value设置为'--'。我们还要根据一些IP和请求的QPS来判断是否是恶意的情况,比如同一个IP,请求的QPS为1千个,我们需要禁用这个IP请求操作。这个就不在此多讲了。
在系统处理请求的过程中,一定要保护好DB,因为它是一个比较核心的位置。DB坏了其实就没法玩了。
我们使用缓存的时候一般要做多级缓存,Client -> ConcurrentHashMap -> Redis --> DB 或者 Client -> Ehcache -->Redis --> DB,要尽可能保证到数据库是最纯粹的操作。同时我们如果将value的值尽量设置的小,这样可以省一些资源。如果你有很多的资源的话,你可以将所有数据都放到Redis里边,这样以上的情况基本上都不用担心了,也就是不设置超时时间。当数据有任务的Update操作,更新一个缓存即可。
在使用的过程中,每个业务的场景可能有所不同,同学们可以根据自己实际的业务进行判断和取舍。
欢迎提意见~