Redis相关
redis的事务不支持原子性,只有单条命令有原子性。也没有隔离性。
redis的事务:
- 开启事务(multi)
- 命令入队()
- 执行事务(exec)
放弃事务(discard):事务中的命令都不会执行
编译型异常(代码有问题!命令有错),事务中所有的命令都不会执行。
运行时异常,如果事务队列中存在逻辑性问题,那么执行命令的时候,其他命令可以正常执行,错误命令抛出异常。
Redis实现乐观锁用watch命令来监视某个变量是否发生变化,如果变化了执行失败。如果修改失败,unwatch后重新watch。
RDB持久化:
备份规则:
- save规则满足的情况下,会自动触发rdb(多少秒内更改key达到多少次以上)
- 执行flushall命令,会触发rdb
- 退出redis,会触发rdb
回复规则:
- 只需要rdb存放在redis的启动目录就可以,redis启动时会自动见擦汗dump.rdb并恢复数据。
优点:
- 适合大规模数据恢复
- 如果你对数据完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机,最后一次修改就没了
- fork进程会占用一定内存空间
AOF持久化:
将所有的命令都记录下来,恢复的时候把所有命令都执行一次。默认每秒修改
优点:
- 每次修改都同步,文件完整性更好
- 每秒同步一次,可能会丢失数据
- 从不同不,效率最高
缺点:
- aof大小远远大于rdb,修复速度也比rdb满
- 运行效率也要比rdb慢。
主从复制:
主从复制,是指将一台redis服务器的数据,复制到其他的redis服务器。前者称为主节点(master/leader),后者称之为从节点(salve/follower);数据的复制是单向的,只能由主节点到从节点。master以写为主,salve以读为主。
主从复制的作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(既写redis数据时应用链接主节点,读redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是redis高可用基础
主从复制的缺点:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式:
概念:哨兵模式是一种特殊的模式,首先redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例。
分布式锁:
加锁:
public class RedisTool { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } }
解锁:
public class RedisTool { private static final Long RELEASE_SUCCESS = 1L; /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
第一行代码,我们写了一个简单的Lua脚本代码。第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。
那么这段Lua代码的功能其实很简单,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。那么为什么要使用Lua语言来实现呢?因为要确保上述操作是原子性的。那么为什么执行eval()方法可以确保原子性,源于Redis的特性,下面是官网对eval命令的部分解释:
简单来说,就是在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。