分布式缓存Redis运用场景
1. 页面缓存
Redis可将Web页面的内容片段,包括HTML,CSS和图片等静态数据,缓存到Redis实例,提高网站的访问性能。
比如在电商类应用中,热销商品展示、秒杀推荐等数据面临高并发读的压力,分布式缓存Redis的高并发及灵活扩展,可轻松支持此类应用。
2. 状态缓存
Redis可将Session会话状态及应用横向扩展时的状态数据等缓存到DCS实例,实现状态数据共享。在应对游戏应用中爆发式增长的玩家数据存储和读写请求时,使用分布式缓存Redis可通过将热点数据放入缓存,加快用户端访问速度,提升用户体验。
3. 应用对象缓存
Redis可作为服务层的二级缓存对外提供服务,减轻数据库的负载压力,加速应用访问。
4. 事件缓存
Redis可提供针对事件流的连续查询(continuous query)处理技术,满足实时性需求。
Redis缓存问题
在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问Mysql等数据库。这样可以大大缓解数据库的压力。缓存问题如下:
- 缓存穿透
- 缓存穿击
- 缓存雪崩
- 缓存污染
- 缓存同步(缓存和数据库一致性)
缓存穿透
问题描述
缓存穿透是指缓存和数据库中都没有数据,而用户不断发起请求则这些请求会穿过缓存直接访问数据库,如发起为id为“-1”的数据或id为特别大不存在的数据。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。
解决方案
应对缓存穿透的方案,常见的方案有三种。
- 第一种方案,非法请求的限制;
- 第二种方案,缓存空值或者默认值;
- 第三种方案,使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;
针对后端,此处采用方案二
缓存空对象:当存储层不命中时,创建空对象并将其缓存起来,同时会设置一个过期时间(避免控制占用更多的存储空间),之后再访问这个数据将会从缓存中获取,保护了后端数据源;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
@Override public TbItem selectItemInfo(Long itemId) { TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO + ":" + itemId + ":"+ BASE); if(tbItem!=null){ return tbItem; }
tbItem = tbItemMapper.selectByPrimaryKey(itemId); if(tbItem == null){ redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,new TbItem()); redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,30); return tbItem; } redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,tbItem); redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,ITEM_INFO_EXPIRE); return tbItem; }
@Override public TbItemDesc selectItemDescByItemId(Long itemId) { TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get( ITEM_INFO + ":" + itemId + ":"+ DESC); if(tbItemDesc!=null){ return tbItemDesc; }
TbItemDescExample example = new TbItemDescExample(); TbItemDescExample.Criteria criteria = example.createCriteria(); criteria.andItemIdEqualTo(itemId); List<TbItemDesc> itemDescList = this.tbItemDescMapper.selectByExampleWithBLOBs(example); if(itemDescList!=null && itemDescList.size()>0){ redisClient.set(ITEM_INFO + ":" + itemId + ":"+ DESC,itemDescList.get(0)); redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ DESC,ITEM_INFO_EXPIRE); return itemDescList.get(0); } redisClient.set(ITEM_INFO + ":" + itemId + ":"+ DESC,new TbItemDesc()); redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ DESC,30); return null; }
|
缓存击穿
问题描述
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对一个key不停进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决方案
分布式锁:
application.yml
1 2 3 4
| SETNX_LOCK_BASC: SETNX_LOCK_BASC SETNX_LOCK_DESC: SETNX_LOCK_DESC SETNX_LOCK_PARAM: SETNX_LOCK_PARAM
|
service-ItemServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
@Override public TbItem selectItemInfo(Long itemId){ TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO+":"+itemId+":"+BASE); if(tbItem!=null){ return tbItem; } if(redisClient.setnx(SETNX_LOCK_BASC+":"+itemId,itemId,5L)){ tbItem = tbItemMapper.selectByPrimaryKey(itemId);
if(tbItem!=null){ redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,tbItem); redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,ITEM_INFO_EXPIRE); }else{ redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,new TbItem()); redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,30L); } redisClient.del(SETNX_LOCK_BASC+":"+itemId); return tbItem; }else{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return selectItemInfo(itemId); } }
@Override public TbItemDesc selectItemDescByItemId(Long itemId) { TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get(ITEM_INFO + ":" + itemId + ":" + DESC); if(tbItemDesc!=null){ return tbItemDesc; } if(redisClient.setnx(SETNX_DESC_LOCK_KEY+":"+itemId,itemId,30L)){ tbItemDesc = tbItemDescMapper.selectByPrimaryKey(itemId);
if(tbItemDesc!=null){ redisClient.set(ITEM_INFO + ":" + itemId + ":" + DESC,tbItemDesc); redisClient.expire(ITEM_INFO + ":" + itemId + ":" + DESC,ITEM_INFO_EXPIRE);
}else{ redisClient.set(ITEM_INFO + ":" + itemId + ":" + DESC,new TbItemDesc()); redisClient.expire(ITEM_INFO + ":" + itemId + ":" + DESC,30L); } redisClient.del(SETNX_DESC_LOCK_KEY+":"+itemId); return tbItemDesc; }else{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return selectItemDescByItemId(itemId); } }
|
缓存雪崩
问题描述
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。
解决方案
参考文章
Redis进阶 - 缓存问题:一致性, 穿击, 穿透, 雪崩, 污染等
什么是缓存雪崩、击穿、穿透?
图解Redis体系
布隆过滤器原理
Redis缓存雪崩、缓存击穿、缓存穿透
https://github.com/i-xiaoxin/2022/11/03/Redis缓存雪崩、缓存击穿、缓存穿透/