前提
- 优化数据库io操作,一般分为两个层面,一是提高数据库sql本身的性能,二是尽量避免直接查询数据库。
提高数据库本身的性能首先是优化sql,包括:使用索引,减少不必要的大表关联次数,控制查询字段的行数和列数。另外当数据量巨大是可以考虑分库分表,以减轻单点压力。 - 尽量避免直接查询数据库重要的解决办法就是:缓存,缓存可以理解是数据库的一道保护伞,任何请求只要能在缓存中命中,都不会直接访问数据库。而缓存的处理性能是数据库10-100
倍。 - 而Redis就是作为缓存系统
- 一般放入缓存中的大都是热点内容,并发可能也是相对高的
但是使用redis同时会出现以下几种情况:
- 一:缓存雪崩:或者可以突然全部失效,此时如果系统并发量大的就会给数据库造成很大的压力,造成压力的高峰
- 解决办法:将缓存中的key设置的过期时间不一致,不同,不要让它同一时间都失效,造成高峰
- 二:缓存穿透:当用户查询一个不存在的数据
- 解决办法:不管是不是null都放入redis中
- 三:缓存击穿:当某一个key突然失效了,此时会导致大量的用户访问数据库,会造成缓存击穿的情况,导致数据库蹦:
- 解决办法:加锁,限制,先让一个人去数据库查找数据,然后再放入redis缓存,此时其他线程都是被阻塞,等放入redis中,就重新在redis中查找
使用分布式锁来解决缓存击穿的问题
方式一:使用Redis命令
set sku:1:info “OK” NX PX 10000
- EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
- PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
- NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
- XX :只在键已经存在时,才对键进行设置操作。
简单来说就是第一个人设置了这个带NX参数的键,只在键不存在时,才对键进行设置操作。 所以其他人在设置也设置不了了,然后自旋锁,也就是被阻塞了,当第一个人设置完后进入DB数据库找完数据在放入缓存中,当这个键结束后,据可以自爱redis中查到第一个人放入的缓存
实现代码:
// 没有数据 ,需要加锁!取出完数据,还要放入缓存中,下次直接从缓存中取得即可!
System.out.println("没有命中缓存");
// 定义key user:userId:lock
String skuLockKey=ManageConst.SKUKEY_PREFIX+skuId+ManageConst.SKULOCK_SUFFIX;
// 生成锁
String lockKey = jedis.set(skuLockKey, "OK", "NX", "PX", ManageConst.SKULOCK_EXPIRE_PX);
if ("OK".equals(lockKey)){
System.out.println("获取锁!");
// 从数据库中取得数据
skuInfo = getSkuInfoDB(skuId);
// 将是数据放入缓存
// 将对象转换成字符串
String skuRedisStr = JSON.toJSONString(skuInfo);
jedis.setex(skuInfoKey,ManageConst.SKUKEY_TIMEOUT,skuRedisStr);
jedis.close();
return skuInfo;
}else {
System.out.println("等待!");
// 等待
Thread.sleep(1000);
// 自旋
return getSkuInfo(skuId);
}
}else{
// 有数据
skuInfo = JSON.parseObject(skuJson, SkuInfo.class);
jedis.close();
return skuInfo;
}
方式二:使用redisson解决分布式锁
#1.导入依赖 service-util
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
代码:
// 使用redisson 调用getLock
RLock lock = redissonClient.getLock("yourLock");
// 加锁
lock.lock(10, TimeUnit.SECONDS);
// try {
// boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
//
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// 放入业务逻辑代码
SkuInfo skuInfo =null;
Jedis jedis = null;
// ctrl+alt+t
try {
jedis = redisUtil.getJedis();
// 定义key: 见名之意: sku:skuId:info
String skuKey = ManageConst.SKUKEY_PREFIX+skuId+ManageConst.SKUKEY_SUFFIX;
// 判断缓存中是否有数据,如果有,从缓存中获取,没有从db获取并将数据放入缓存!
// 判断redis 中是否有key
if (jedis.exists(skuKey)){
// 取得key 中的value
String skuJson = jedis.get(skuKey);
// 将字符串转换为对象
skuInfo = JSON.parseObject(skuJson, SkuInfo.class);
// jedis.close();
return skuInfo;
}else {
skuInfo = getSkuInfoDB(skuId);
// 放redis 并设置过期时间
jedis.setex(skuKey,ManageConst.SKUKEY_TIMEOUT,JSON.toJSONString(skuInfo));
return skuInfo;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis!=null){
jedis.close();
}
lock.unlock();
}
return getSkuInfoDB(skuId);
可以看到使用redisson的方式简单明了也方便