京东科技作者:刘旭华1、背景概述:R2M缓存的使用大大提升了应用程序的性能和效率,尤其是在数据查询方面。缓存最常见的问题是缓存穿透、击穿和雪崩。在高并发下的这三种情况下,大量的请求都会落到数据库上,导致数据库资源爆满,导致数据库故障。除了增删改查等基本功能外,在测试缓存时,还应重点关注缓存穿透、击穿、雪崩三种异常场景的测试覆盖率,避免上线事故。二、基本概念解释:1、缓存击穿:是指超热数据突然过期,导致过期期间对超热数据的数据请求直接命中数据库,使数据库服务器压力过大由于某个超级火爆的数据而崩盘。2、缓存穿透:是指查询到的数据不存在于缓存或数据库中,使得每次请求都无法从缓存中获取数据,请求发送到数据库服务器,却没有对应的数据库中的数据。最后,每个请求都会进入数据库;如果处于高并发场景或者有人恶意攻击,后台数据库服务器的压力会增加,最终可能导致系统崩溃。3、缓存雪崩:是指缓存层突然不可用,导致大量请求直接打到数据库,最终可能因为数据库压力过大导致系统崩溃。缓存层不可用是指以下两个方面:缓存服务器宕机,系统向数据库发送请求;缓存数据突然过期,大规模失效,导致大量请求数据库重载数据。key缓存,前者是某一个key。三、测试工具(非必须):1、使用Titan压测平台进行并发请求测试2、使用jmeter工具模拟并发请求4、测试方法示例(非必须):环境:测试环境工具:jmeter(1)缓存穿透场景测试方法:查询一个根本不存在的数据,缓存层和存储层都不会被命中。Query接口相关代码实现:通过JMETER模拟多次重复调用:单线程重复调用查看日志结果:从日志中可以看到,并发请求执行后,每次请求都去数据库。预防方案:当数据库查询为空时,给缓存赋默认值,后续所有查询都会使用缓存,减轻数据库压力。对于上面的接口,如果赋值加上empty,第一次查询数据库为空,后面的查询都查询到缓存中,缓存值为空。再次执行并发测试:从日志中可以看出,每个ID只执行一次数据库查询并设置缓存,然后所有请求都命中缓存,有效防止了缓存穿透问题。(2)缓存击穿场景测试方法:某个key有大量并发请求,此时key从缓存中删除。模拟热键过期失效的场景。这时候大并发请求可能会瞬间压垮后端DB。接口相关部分的代码实现:操作步骤:1.查询pin为liuxuhua的request。这时pin为liuxuhua的数据就会被加载到缓存中。2、再次查询pin为liuxuhua的请求,命中缓存。数据,此时,所有请求都命中了缓存4.手动删除pin为liuxuhua的缓存,模拟缓存失效。结果:预防方案:在设置默认缓存值的基础上,进行锁处理。只有第一个拿到锁的线程才去请求数据库,然后插入到缓存中。当然,每次拿到锁的时候,都要检查一下缓存是否存在。从日志记录可以看出,只有一个请求执行了数据库查询并设置了缓存,其他请求都命中了缓存,有效防止了缓存崩溃。(3)缓存雪崩测试方法:并发调用多个使用缓存的接口,设置缓存时间过期(即删除缓存),调用这些接口时,查询缓存时没有数据,以及然后查询数据库。这些请求都指向数据库,数据库压力增大,时间消耗增加。模拟接口:通过JMETER模拟多次重复调用:单线程多次接口重复调用查看日志结果:可以看到大量请求到达数据库,同一个pin或者id执行多次数据库查询预防方案:增加限流操作,即当接口被频繁调用时,增加缓存,设置时间为3s,3s内处理一定数量的请求,超过限制直接返回结果,不处理。接口:3s内处理6个请求,如果超过6个请求不处理;从日志中可以看出:可以看出,他们每一次都只查询过一次数据库,并设置了缓存,之后所有的请求都命中了缓存5.测试指标:(或者叫Pass标准,包括focus和意义)1.模拟缓存穿透场景测试。对于每一个不存在的数据,只进行一次数据库查询,并设置缓存。之后所有的请求都命中了缓存,有效的防止了缓存穿透问题。2、模拟缓存雪崩场景测试。对于每个缓存无效数据,只执行一次数据库查询并设置缓存。之后,所有请求都会命中缓存。3、模拟缓存击穿场景测试。只有一个缓存无效数据的请求执行数据库查询并设置缓存,其他所有请求都命中缓存。6、适用业务场景:1、秒杀活动2、热门营销活动3、618、双11大促7、研发端常见解决方案(参考):1、缓存穿透解决方案:(1)缓存为空的原因发生渗透是因为缓存中没有存放这些数据的key,所以我们每次都可以查询数据库。我们可以将这些key在缓存中对应的值设置为null。后面查询这个key的时候,我们不需要去查询数据库。当然,为了健壮性,我们需要给这些key设置一个过期时间,以防止真正的数据(2)BloomFilterBloomFilter类似于一个hbase集合,用来判断某个元素(key)是否存在于某个集合中。我们把所有有数据的key都放到BloomFilter中,每次查询都去BloomFilter判断,如果没有,直接返回null注意BloomFilter没有删除操作,对于删除的key,查询会通过通过BloomFilter然后查询缓存再查询数据库,所以BloomFilter可以结合缓存对于null值,对于删除的key,null可以缓存在缓存中。缓存击穿2.缓存击穿解决方案:使用分布式锁,只有第一个拿到锁的线程去请求数据库,然后插入缓存。当然,每次第一次拿到锁的时候,都要检查一下缓存是否可用。3、缓存雪崩解决方案:(1)使用集群降低服务宕机概率(2)Ehcache本地缓存+限流&降级(3)统一过期,有效期往往可以加一个随机值
