redis小结

大神敖 丙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​删除策略
  • 惰性删除
    当客户端进行读/写该数据的时候判断,是否过期,假如没过期则返回;假如已过期,删除返回不存在
  • 定期删除
    redis默认每100ms检查是否有过期的key,有过期key则删除。注意:redis不是每隔100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms全部检查,redis直接进去icu)。因此如果只采用定期删除策略,会导致很多key到时见没有删除。
    在这里插入图片描述

redis数据一致性

在使用redis时,需要保持redis和数据库数据的一致性,最流行的解决方案之一就是延时双删策略

为什么要进行延迟双删?

一般我们在更新数据库数据时,需要同步redis中缓存的数据,所以存在以下两种方法:

  1. 先执行update操作,再执行缓存清除。
  2. 先执行缓存清除,再执行update操作。
    这两种方案的弊端是当存在并发请求时,很容易出现以下问题:
    当请求1执行update操作后,还未来得及进行缓存清除,此时请求2查询到并使用了redis中的旧数据;
    当请求1执行清除缓存后,还未进行update操作,此时请求2进行查询到了旧数据并写入了redis
如何实现延迟双删?

所以此时我们需要执行的步骤为:先进行缓存清除,再执行update,最后(延迟N秒)再执行缓存清除。

   public void write(String key, Object data) {
       Redis.delKey(key);
       db.updateData(data);
       //需要注意的点 上述中(延迟N秒)的时间要大于一次写操作的时间,一般为3-5秒。
       //原因:如果延迟时间小于写入redis的时间,会导致请求1清除了缓存,但是请求2缓存还未写入的尴尬。。。
       // ps:一般写入的时间会远小于5秒
       Thread.sleep(3000);  
       Redis.delKey(key);
   }

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);
            /*获取锁的uuid,只能释放自己的锁*/
            String uuidLock = (String)redisTemplate.opsForValue().get("lock");
            if(uuId.equals(uuidLock)){
                /* 释放锁,del*/
                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() {
        //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
        String uuid = UUID.randomUUID().toString();
        //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
        String locKey = "lock"; 

        // 3 获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

        // 第一种: lock 与过期时间中间不写任何的代码。
        // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
        // 如果true
        if (lock) {
            // 执行的业务逻辑开始
            // 获取缓存中的num 数据
            Object value = redisTemplate.opsForValue().get("num");
            // 如果是空直接返回
            if (StringUtils.isEmpty(value)) {
                return;
            }
            // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
            int num = Integer.parseInt(value + "");
            // 使num 每次+1 放入缓存
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
            /*使用lua脚本来锁*/
            // 定义lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            // 使用redis执行lua执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            // 设置一下返回值类型 为Long
            // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
            // 那么返回字符串与0 会有发生错误。
            redisScript.setResultType(Long.class);
            // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
            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
哨兵之间还会相互监控,从而达到高可用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值