Redis缓存及分布式锁
Redis缓存
为什么使用缓存
为了系统性能的提升,我们一般都会将数据放入缓存中,加速访问。而db承担数据落盘工作。
高并发系统,缓存是必备。
使用缓存带来的问题
缓存穿透
问题描述
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,并且处于容错考虑,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决方案
1、数据库中查询到值为空的话给redis中缓存空值
2、为了防止数据一致被null屏蔽了,我们给redis中缓存的所有数据都加上过期时间。某个时间后允许去数据库查新数据。
缓存雪崩
问题描述
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案
将redis中的key的过期时间设置为随机
缓存击穿
问题描述
针对redis中的某一个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。
缓存击穿和缓存雪崩的区别
- 击穿是一个热点key失效
- 雪崩是很多key集体失效
解决方案
使用分布式锁
JVM级别锁和分布式锁的关系
JVM级别锁(线程锁)
在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就需要对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在JAVA中,专门提供了一些处理锁机制的一些API(synchronize/Lock等)。
分布式锁
分布式系统可能会有多份并且部署在不同的机器上,这些资源不是在线程之间共享了,而是属于进程之间共享的资源。因此,为了解决这个问题,我们必须要使用分布式锁的解决方案。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
特点
- 排他性(在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取)
- 避免死锁(把锁在一段有限的时间之后,一定会被正常释放或者异常释放)
- 高可用(获取或释放锁的机制必须高可用且性能佳)。
Redisson实现分布式锁
场景描述
前置工作(安装并启动nginx)
根据这位博主的文章安装成功
https://blog.csdn.net/qq_37345604/article/details/90034424
技能复习
- 从本机的资源拷贝到其他linux机器
scp -r /root/apps/lamp/nginx-1.13.9.tar.gz root@192.168.1.105:/root/apps/lamp
- 在编辑文本的时候使用如下指令可查看行号
:set nu
- 部署过程中发现8082端口被占用
//查看已知的端口被哪个进程占用
netstat -ano|findstr "8082"
tasklist|findstr "9368"
案例
- 1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
- 2.配置文件application.yml
server:
port: 8080
spring:
redis:
host: 192.168.0.200
port: 6379
database: 1
- 3.appconfig配置
@Bean
public Redisson redisson() {
// 此为单机模式
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.0.200:6379").setDatabase(1);
return (Redisson) Redisson.create(config);
}
- 4.Controller代码
@RequestMapping("/deduct_stock")
public String deductStock() {
String lockKey = "lock:product_101";
//Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");
//stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
/*String clientId = UUID.randomUUID().toString();
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v)
if (!result) {
return "error_code";
}*/
//获取锁对象
RLock redissonLock = redisson.getLock(lockKey);
//加分布式锁
redissonLock.lock(); // .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
try {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
/*if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}*/
//解锁
redissonLock.unlock();
}
return "end";
}
- 6.启动服务,并在本地运行两个实例,端口分别为8080,8081
使用压力测试工具jmeter进行测试
该功能使用特别简单,可以模拟多个线程批量同时访问
Redisson实现分布式锁存在的问题
如果出现Redis主从架构锁失效问题,可能出现多个线程同时加锁成功。出现线程安全问题。
Redlock分布式锁原理与存在的问题分析
实现机制和Zookeeper底层原理相似,需要多个Redis节点,这里只是节点,不是主从架构。加红锁需要向所有节点加锁,半数以上的节点加锁成功才算成功,有一定的效率影响。如果节点过少(3个),一旦有节点宕机,因为加锁过半机制,会导致加锁不成功。