Redis-基础三

简介:通过前两篇文章redis一些原理性知识基本都知道了,但是在实际开发中redis的一些问题和使用场景缺还不太熟悉,本文主要讲实际开发中使用redis的一些问题与使用场景.

Redis和DB数据一致性
  • 通常情况下我们是使用redis作为一个中间缓存,方式请求数据直接到DB,所以数据有两份,redis中一份,DB中一份,但是操作不当的话这两份数据可能就不一致,这就涉及到redis与DB数据一致性问题.

  • redis做缓存查询流程图如下:
    redis + DB 通常流程

    数据读取一般不会出现问题,但是更新数据写入缓存就容易出现数据不一致问题.

  • redis缓存更新方式:

    1. 方式一:如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库读取数据写入缓存,此时缓存中为脏数据。

    2. 方式二:如果先更新了数据库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。如何解决以上问题?

  • 解决方案如图:

    方案一简单粗暴,先删除redis缓存再进行数据库数据更新,然后休眠一段时间再进行redis缓存删除,防止更新DB过程中有脏数据写入缓存,其休眠时常取决与业务接口读数据的时长加上redis和数据主从同步时间.
    方案一虽然简单粗暴,但是其缺点是代码侵入性太强,每个redis都要结合对应接口数据时长来决定休眠时间,而且休眠势必会影响到一定的性能,直接影响数据写入的性能.所以产生方案二.
    方案二通过监听数据库变化,订阅数据库binlog,获取到变更的数据,然后删除该数据对应的缓存,如果删除失败,则将失败的数据放入消息队列,重新从消息队列中获取数据进行重试删除.

Redis分布式锁
  • 说到redis分布式锁,其实底层使用的是redis SETNX 命名,setnx key value,将key设置为value,当键不存在时,才能成功,若键存在,什么也不做,成功返回1,失败返回0 。 SETNX实际上就是SET IF NOT Exists的缩写,当处理完任务的时候进行del释放锁,但是当一个进程拿到锁过后由于一些其他原因断开了redis连接,那就永远无法释放锁,出现死锁状态,所以还需要给setnx设置一个超时时间,不管是否释放了锁,超时时间到了,直接释放,这样不会出现死锁情况,使用expire 为锁设置一个超时时间,又会出现一种情况,setnx 与 expire 是两个命令,非原子性操作,可能出现的情况是setnx命令后expire没有正常执行,还是出现死锁,redis官方也意识到这个问题,在2.6.12版本新增了指令SET key value[EX seconds][PX milliseconds][NX|XX]该指令使之前的两个命令合并成了原子性操作,但是在并发条件下还是有问题:

    上图可能出现的情况是从第八步开始服务A写完数据释放锁,由于两服务锁是同一个,导致服务A写完数据释放了服务B正持有的锁,所以在释放锁的时候还需要判断是否是自己持有的锁,可以同lua脚本来做.

    public boolean releaseLock_with_lua(String key,String value) {
            String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del',KEYS[1]) else return 0 end";
            return jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)).equals(1L);
        }
    
    

上图问题为:在Redis集群的时候,比如说服务A在Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点,master故障,发生故障转移,一个slave节点升级为master节点,服务B也可以获取同个key的锁,但服务A已经拿到锁了,这就导致多个服务端都拿到锁.

如何解决?

  • Redisson实现简单分布式锁,对于Java用户而言,我们经常使用Jedis,Jedis是Redis的Java客户端,除了Jedis之外,Redisson也是Java的客户端,Jedis是阻塞式I/O,而Redisson底层使用Netty可以实现非阻塞I/O,该客户端封装了锁的,继承了J.U.C的Lock接口,所以我们可以像使用ReentrantLock一样使用Redisson,翻看redission官网有很详细的文档说明.
    各种锁的使用方式:
    各种锁的使用方式
Redis生成分布式ID

分布式ID实现方式有很多种:

1. 数据库自增长序列或字段
2. UUID
3. Snowflake 算法
4. 美团Leaf
5. Redis实现分布式ID
6. 滴滴Tinyid
7. 号段模式
.....

分布式ID的特性:

1.全局唯一
2.支持高并发
3.高可靠
4.容错单点故障
5.高性能
6.可排序

主要看redis怎样实现分布式ID,其实和利用Mysql自增ID类似,可以利用Redis中的incr命令来实现原子性的自增与返回,比如:

127.0.0.1:6379> set seq_id 1     // 初始化自增ID为1
OK
127.0.0.1:6379> incr seq_id      // 增加1,并返回
(integer) 2
127.0.0.1:6379> incr seq_id      // 增加1,并返回
(integer) 3

使用redis的效率是非常高的,但是要考虑持久化的问题。Redis支持RDB和AOF两种持久化的方式。RDB持久化相当于定时打一个快照进行持久化,如果打完快照后,连续自增了几次,还没来得及做下一次快照持久化,这个时候Redis挂掉了,重启Redis后会出现ID重复。AOF持久化相当于对每条写命令进行持久化,如果Redis挂掉了,不会出现ID重复的现象,但是会由于incr命令记录太多,导致重启恢复数据时间过长。

参考文档

Redission官网
Redis的n种妙用,不仅仅是缓存
Redis之父谈论redlock锁
基于Redis分布式锁实现
Redis分布式ID
Redisson实现Redis分布式锁的N种姿势

posted @ 2023-02-03 14:43  年年糕  阅读(15)  评论(0编辑  收藏  举报