大神敖 丙redis文章 点击查看
redis事务
Redis事务的主要作用就是串联多个命令防止别的命令插队 注意:redis的事务和mysql事务回滚有点区别。reids命令在组队中redis命令没有出错,而提交命令时出错,出错命令不执行,其他正确的命令是会正常执行的。
事务的生命周期:
事务的创建:使用MULTI开启一个事务 加入队列:在开启事务的时候,每次操作的命令将会被插入到一个队列中,同时这个命令并不会被真的执行 EXEC命令进行提交事务
常用的关于事务的命令有:
MULTI:使用该命令,标记一个事务块的开始,通常在执行之后会回复OK,(但不一定真的OK),这个时候用户可以输入多个操作来代替逐条操作,redis会将这些操作放入队列中。 EXEC:执行这个事务内的所有命令 DISCARD:放弃事务,即该事务内的所有命令都将取消 WATCH:监控一个或者多个key,如果这些key在提交事务(EXEC)之前被其他用户修改过,那么事务将执行失败,需要重新获取最新数据重头操作(类似于乐观锁)。 UNWATCH:取消WATCH命令对多有key的监控,所有监控锁将会被取消。
redis乐观锁:
通过watch命令进行监控事务
redis分布式锁:
1.使用setnx上锁(setnx key value),通过del释放锁(del key) 2.锁一直没有释放,设置过期时间,自动释放(set key value nx ex 过期时间/秒)
UUID防误删
场景: 如果业务逻辑的执行时间是5s。 1.业务逻辑1没执行完,3秒后锁被自动释放。 2.业务逻辑2获取到锁,执行业务逻辑,3秒后锁被自动释放。 3.业务逻辑3获取到锁,执行业务逻辑 4.业务逻辑1执行完成,开始调用del释放锁,这时释放的是业务逻辑3的锁,导致业务逻辑3的业务只执行1s就被别人释放。 出现没有上锁的情况 解决: java代码:
@PostMapping("/testLock")
public void testLock() {
String uuId = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuId, 3, TimeUnit.SECONDS);
if (lock) {
Object num = redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(num)) {
return;
}
int tic = Integer.parseInt(num + "");
redisTemplate.opsForValue().set("num", ++tic);
String uuidLock = (String)redisTemplate.opsForValue().get("lock");
if(uuId.equals(uuidLock)){
redisTemplate.delete("lock");
}
} else {
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
LUA脚本保证删除的原子性
1.业务逻辑1执行删除时,查询到的lock值确实和uuid相等 uuid=v1 set(lock,uuid); 2.业务逻辑1执行删除前,lock刚好过期时间已到,被redis自动释放 在redis中没有了lock,没有了锁。 3.业务逻辑2获取了lock index2线程获取到了cpu的资源,开始执行方法 uuid=v2 set(lock,uuid); 4.业务逻辑执行删除,此时会把业务逻辑2的lock删除 删除的业务逻辑2的锁
解决:
@GetMapping("testLockLua")
public void testLockLua() {
String uuid = UUID.randomUUID().toString();
String locKey = "lock";
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);
if (lock) {
Object value = redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(value)) {
return;
}
int num = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num", String.valueOf(++num));
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
} else {
try {
Thread.sleep(1000);
testLockLua();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Redis 的持久化机制是什么?各自的优缺点?
Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:
RDB持久化:
RDB :Redis DataBase缩写,快照是,Redis默认的持久化方式。按照一定的时间间隔将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。 优点: 1.只有一个文件 dump.rdb,方便持久化,容灾性好。 2.性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,保证了 redis 的高性能 3.数据集大时,比 AOF 的启动效率更高。 缺点: 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。
AOF持久化:
AOF:Append Only File缩写,将Redis执行的每条写命令记录到单独的aof日志文件中,当重启Redis服务时,会从持久化的日志文件中恢复数据。 当两种方式同时开启时,数据恢复时,Redis会优先选择AOF恢复。 优点 1.数据安全,可以配置每进行一次命令操作就记录到 aof 文件中一次。 2.通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。 缺点 1.AOF 文件比 RDB 文件大,且恢复速度慢。 2.数据集大时,比 rdb 启动效率低
redis中主从复制、哨兵模式
主从模式是最简单的实现高可用的方案,核心就是主从同步。 主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。 哨兵sentinel的功能比单纯的主从架构全面的多了,它具备自动故障转移、集群监控、消息通知等功能
哨兵模式作用:
发送命令,等待Redis服务器(包括主服务器和从服务器)返回监控其运行状态; 哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机; 多个从服务器将其转成主服务器,选择条件依次为: 1.选择优先级靠前的 优先级在redis.conf中默认:replica-priority 100 值越小优先级越高 2.选择偏移量最大的 偏移量是指获得原主机数据最全的 3.选择runid最小的服务器 每个redis实例启动后都会随机生成一个40位的runid 哨兵之间还会相互监控,从而达到高可用。
|