开发为什么要引入缓存
为了性能,主要是高并发下的性能
一些基础的概念介绍
常见的缓存中间件有Redis、Memcache、Ehcache ,一些详细的介绍可以看这里https://blog.csdn.net/riipgah/article/details/112626440。
https://blog.csdn.net/weixin_42886699/article/details/121668172
就只引用些示例图帮助大家理解
Redis:
Encached
Memcached
使用方式
redis在我们公司的应用主要还是分布式锁比较多,缓存用的比较少,直接使用的话可以这样
@Test
public void testString() {
// 单个
for (int i = 1; i <= 10; i++) {
// 获得资源
Jedis jedis = RedisUtil.getJedis();
// 设置字符串值
jedis.set("mike" + i, i + "");
System.out.println(jedis.get("mike" + i));
// 关闭资源
RedisUtil.backResouce(jedis);
}
更简单的方式是使用redis的注解
//@Cacheable(value=”accountCache”),这个注释的意思是,当调用这个方法的时候,会从一个名叫 accountCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),
//并将执行的结果存入缓存中,否则返回缓存中的对象。这里的缓存中的 key 就是参数 userName,value 就是 Account 对象。“accountCache”缓存是在 spring*.xml 中定义的名称。
@Cacheable(value="accountCache")
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
System.out.println("real query account."+userName);
return getFromDB(userName);
}
开发将缓存视为实现细节,而不是需要测试的功能,但引入缓存后测试是必要的
1、缓存的作用和隐患不是及时能体现出来的
2、缓存的使用姿势也有很大讲究
3、缓存出现问题的后果很严重,错误的使用缓存,可能导致有雪崩的风险,导致数据库承担过多的压力
测试要关注的点
使用姿势对不对
失效策略
主动失效
主动失效是指系统有一个主动检查缓存是否失效的机制,比如通过定时任务或者单独的线程不断的去检查缓存队列中的对象是否失效,如果失效就把他们清除掉,避免浪费。主动失效的好处是能够避免内存的浪费,但是会占用额外的CPU时间。
被动失效
被动失效是通过访问缓存对象的时候才去检查缓存对象是否失效,这样的好处是系统占用的CPU时间更少,但是风险是长期不被访问的缓存对象不会被系统清除。
缓存淘汰
FIFO
先进先出(First In First Out)是一种简单的淘汰策略,缓存对象以队列的形式存在,如果空间不足,就释放队列头部的(先缓存)对象。一般用链表实现。
LRU
最近最久未使用(Least Recently Used),这种策略是根据访问的时间先后来进行淘汰的,如果空间不足,会释放最久没有访问的对象(上次访问时间最早的对象)。比较常见的是通过优先队列来实现。
LFU
最近最少使用(Least Frequently Used),这种策略根据最近访问的频率来进行淘汰,如果空间不足,会释放最近访问频率最低的对象。这个算法也是用优先队列实现的比较常见。
缓存对象
需要有足够的内存用于缓存。
缓存中每个对象的大小是多少?
缓存中的最大对象数是多少?
当缓存已满并且需要向其添加一个对象时会发生什么?
缓存更新的策略
https://blog.csdn.net/hukaijun/article/details/81010475
持续的监控
中间件的表现
-CPU,内存指标,持续观测
-是否有频繁的更新缓存(缓存更适用于读多写少的业务)
-是否有某个异常过大的key
-大数据量情况下,缓存的更新和命中率
数据初始化
-如果在应用程序启动时进行了初始化,那么在缓存最大数量的对 象时需要花费多长时间?
-应用程序可以在初始化期间处理请求吗?
-如果在发出请求时缓存对象,那么缓存未命中或直写事务的响应时间是否可以接受?
异常的情况
1、缓存数据和数据库数据的短暂不一致性是否能接受
2、是否配置了缓存数据的持久化(redis异常重启)
3、缓存失效的常见问题
缓存雪崩
雪崩就是指缓存中大批量热点数据过期后系统涌入大量查询请求,因为大部分数据在Redis层已经失效,请求渗透到数据库层,大批量请求犹如洪水一般涌入,引起数据库压力造成查询堵塞甚至宕机。
解法:过期时间设置时错开时间,使用随机数等方式削峰
缓存穿透
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
解法:无效数据也建立缓存,避免被重复攻击
缓存击穿
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解法:访问数据库时添加分布式锁,更新了缓存后再释放,后续请求即可使用缓存;或者提供未 获得锁时的降级方案
缓存效果的验证
e.g. 有一个商品详情页库存余额数量查询的接口,涉及到了缓存查询,那可能会有哪些测试点:
1、首次请求接口,余额数据正确,且缓存中/数据库中数据保持一致
2、多次请求接口,余额数据正确,且查看链路日志或其他手段确认数据直接从缓存中返回
3、正常请求接口,缓存中存在数据后,更新库存数据(通过正常业务链路),再次查询,余额数据更新正确(review 更新缓存相关代码)
4、正常请求接口,缓存中存在数据后,删除对应商品(通过正常业务链路),再次查询,提示商品已被删除,且缓存数据已清除
5、正常请求接口,缓存中存在数据后,更新库存数据(直接修改数据库),再次查询,余额数据未更新
6、正常请求接口,缓存中存在数据后,删除对应商品(直接修改数据库),再次查询,余额数据未更新
7、正常请求接口,缓存中存在数据后,等待较长时间(缓存过期)后,再次查询,余额数据正确且缓存过期时间被更新(校验过期时间配置)
8、正常请求接口,缓存中存在数据后,请求其他数据(增加缓存至内存达到汰换标准)后,再次查询,余额数据正确且缓存淘汰正确(校验缓存淘汰参数配置)
9、正常请求接口,缓存中存在数据后,重启缓存服务,再次查询,余额数据正确,且缓存中/数据库中数据保持一致
10、并发请求接口,余额数据正确,且缓存中/数据库中数据保持一致
11、并发请求修改库存和查询库存接口,余额数据正确
更广泛的含义
前端缓存
浏览器缓存
APP的本地缓存
数据库缓存
索引(思路类似)
CDN缓存