Redis解决并发的思路
Redis中的数据存储策略
企业中的数据存储策略 其核心就是设计Key
这里我们的Key的设计是
数据对象名:数据对象id:对象属性
Key ---- Sku:108:info
Redis解决并发的简单代码实现
@Override
public PmsSkuInfo getSkuById1(String skuId) {
PmsSkuInfo pmsSkuInfo=new PmsSkuInfo();
//链接缓存
Jedis jedis = redisUtil.getJedis();
//从缓冲中获得数据
String skuJson = jedis.get("sku"+skuId+"info");
if(StringUtils.isNotBlank(skuJson)){
pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
}else {
//缓存没有 从db中取数据
pmsSkuInfo=getSkuByIdFromDb(skuId);
//将db数据写入redis缓存
if(pmsSkuInfo!=null){
jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
}
}
return pmsSkuInfo;
}
Redis缓存问题 — 缓存穿透
-
缓存穿透的概念
缓存穿透是指查询一个一定不存在的数据 由于缓存不命中 将去查询db 这导致这个不存在的数据每次都要去db查询 在流量大时 db就可能挂掉 要是有人利用不存在的key频繁攻击我们的应用 这就是漏洞
-
解决方案
对空结果进行缓存 并且设置一个过期时间
if(pmsSkuInfo!=null){ jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo)); }else{ //数据库中没有这个sku //为了防止缓存穿透 设置一个空字符串给redis jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString("")); }
Redis缓存问题 — 缓存击穿
-
缓存击穿的概念
对于一些设置了过期时间的key 这些key可能会在某段时间内被高并发的访问 是一种非常热点的数据
这个时候 如果这个key在大量请求同时进来前刚好失效 那么所有对于这个key的数据查询全部都落在了
db上 我们称为缓存击穿
缓存击穿指
某一个热点key
在高并发的情况下突然失效 导致大量的并发打到db上 -
解决方案
利用Redis数据库的
分布式锁
解决Mysql的访问压力问题
-
-
设置分布式锁的代码简单实现
//缓存没有 从db中取数据 //设置分布式锁 String OK = jedis.set("sku" + skuId + "lock", "1", "nx", "px", 10); if (StringUtils.isNotBlank(OK)&&OK.equals("OK")){ //设置分布式锁成功 有权利在规定的过期时间里访问Mysql数据库 pmsSkuInfo=getSkuByIdFromDb(skuId); //将db数据写入redis缓存 if(pmsSkuInfo!=null){ jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo)); }else{ //数据库中没有这个sku //为了防止缓存穿透 设置一个空字符串给redis jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString("")); } //在访问Mysql后 将分布式锁释放掉 jedis.del("sku"+skuId+"lock"); }else { //设置分布式锁失败 自旋尝试获取锁 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return getSkuById1(skuId); }
-
设置分布式锁的测试结果
结果与我们预想的一致 速度比较快的11先设置分布式锁成功后 进行Mysql的数据库的访问
此时 12进入商品详情页 设置分布式锁失败 这样一来 我们就利用了Redis的分布式锁解决了缓存击穿的情况下对于Mysql数据库的高并发的访问问题
-
如果Redis中的锁已经过期了 然后锁过期的请求又执行完毕 回来删锁 删除了其他线程的锁 怎么办?
我们画图解释一下上面这种场景
解决方式 在第一次设置分布式锁的时候 将value值设置为一个随机生成的token值
在删除锁的时候再去做一次判断 验证两次获取的token是否一致
这部分的完整代码
//根据sku_id查出对应的某一个sku
@Override
public PmsSkuInfo getSkuById(String skuId) {
System.out.println(Thread.currentThread().getName() + "进入商品详情");
PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
//连接缓存
Jedis jedis = redisUtil.getJedis();
//查询缓存
String skuKey = "sku:" + skuId + ":info";
String skuJson = jedis.get(skuKey);
if (StringUtils.isNotBlank(skuJson)) {
pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
System.out.println(Thread.currentThread().getName() + "从缓存中获取数据");
} else {
//缓存没有 查mysql
//查询mysql之前 设置分布式锁
String token = UUID.randomUUID().toString();
System.out.println(Thread.currentThread().getName()+"发现缓存中没有 申请缓存的分布式锁");
String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10 * 1000);
if (StringUtils.isNotBlank(ok) && ok.equals("OK")) {
System.out.println(Thread.currentThread().getName() + "设置分布式锁成功 可以访问mysql数据库");
//设置成功 有权利在10秒过期时间访问数据库
pmsSkuInfo = getSkuByIdFromDb(skuId);
/*
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//mysql查询结果放入redis
if (pmsSkuInfo != null) {
jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo));
} else {
//数据库没有这个sku
//为了防止缓存穿透 设置一个null或者空字符串给redis
jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString(""));
}
System.out.println(Thread.currentThread().getName() + "释放缓存的分布式锁");
String token2 = jedis.get("sku:" + skuId + ":lock");
if (StringUtils.isNotBlank(token2) && token2.equals(token)) {
//用token确认删除的是自己的锁
//释放分布式锁
jedis.del("sku:" + skuId + ":lock");
}
} else {
System.out.println(Thread.currentThread().getName() + "设置分布式锁失败 自旋尝试获取锁");
//分布式锁设置失败
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getSkuById(skuId);
}
}
jedis.close();
return pmsSkuInfo;
}
Redis缓存问题 — 缓存雪崩
-
缓存雪崩的概念
缓存雪崩是指在设置缓存时采用了相同的过期时间 导致缓存在某一时刻同时失效 请求全部打到db
缓存雪崩是很多key集体失效
-
解决方法
设置不同的缓存失效时间