Redis相关

知乎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实例。

哨兵模式

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才会执行其他命令。

全部评论

相关推荐

CrazyBucket:我今天下午也做梦在招聘会上面试一家小厂,给自己气笑了
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务