zoukankan      html  css  js  c++  java
  • Redis系列(二)--分布式锁、分布式ID简单实现及思路

    分布式锁:

      Redis可以实现分布式锁,只是讨论Redis的实现思路,相对来说,Zookeeper实现分布式锁可能更加可靠

    为什么使用分布式锁:

      单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安全操作,但是多机环境就变成多进程、多线程,这时候同步、加锁已经无

    法保证原子性 

    实现分布式可靠性的条件:

      1、互斥性。在任意时刻,只有一个客户端能持有锁

      2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁

      3、具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁

      4、加锁和解锁必须是同一个客户端

    实现分布式锁的方式:

      1、基于DB的唯一索引。

      2、基于ZK的临时有序节点。

      3、基于Redis的NX、EX参数。

    代码实现:

    public static final String LOCK_SUCCESS = "OK";//加锁成功
    
    public static final String SET_IF_NOT_EXIST = "NX";
    
    public static final String SET_WITH_EXPIRE_TIME = "PX";
    
    public static final Long RELEASE_SUCCESS = 1L;
    public class RedisUtils {
    
        @Autowired
        JedisPool jedisPool;
    
        /**
         * 尝试获取分布式锁
         * @param lockKey
         * @param requestId
         * @param expireTime
         * @return
         */
        public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
            Jedis jedis = jedisPool.getResource();
            String result = jedis.set(lockKey, requestId, RedisConstant.SET_IF_NOT_EXIST, RedisConstant.SET_WITH_EXPIRE_TIME, expireTime);
            if (StringUtils.equals(result, RedisConstant.LOCK_SUCCESS))
                return true;
            return false;
        }
    
        /**
         * 释放分布式锁
         * @param jedis
         * @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 (RedisConstant.RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }
    }

    加锁:

      lockKey:唯一的key

      requestId:每个客户端的唯一ID

      NX:保证key不存在才会set

      PX:key具有过期时间

      expireTime:key的具体过期时间

    解锁:
      通过lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务

    端执行。

      首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。那么为什么要使用Lua语言来实现呢?因为要确保上述

    操作是原子性的。

      以上只是针对单机部署Redis,如果Redis是多机部署的,可以采用Redisson实现分布式锁

    PS:上面的set方法需要RedisV2.6+支持

    无法避免的问题:

      如在 key 超时之后业务并没有执行完毕但却自动释放锁了,这样就会导致并发问题。

      就算 Redis 是集群部署的,如果每个节点都只是 master 没有 slave,那么 master 宕机时该节点上的所有 key 在那一时刻都相当于是释放

    锁了,这样也会出现并发问题。就算是有 slave 节点,但如果在数据同步到 salve 之前 master 宕机也是会出现上面的问题。

     

      Redis分布式锁内容参考:https://xiaozhuanlan.com/topic/4672859130https://redis.io/topics/distlock

    基于Redis实现分布式ID:

      因为Redis是单线程的,所以可以用来生成全部唯一ID,通过incr、incrby实现

      生产环境可能是Redis集群,假如有5个Redis实例,每个Redis的初始值是1,2,3,4,5,然后增长都是5

    各个Redis生成的ID为:

    A:1,6,11,16,21
    B:2,7,12,17,22
    C:3,8,13,18,23
    D:4,9,14,19,24
    E:5,10,15,20,25

    这样的话,无论请求打到那个Redis上面,都可以获得不同的ID

    优点:

      1、不依赖于数据库,灵活方便,且性能优于数据库。

      2、数字ID天然排序,对分页或者需要排序的结果很有帮助。

    缺点:

      1、如果系统里没有Redis,就比较操蛋了

      2、编码、配置工作量大一点

     

    分布式ID推荐一篇文章:https://blog.csdn.net/hengyunabc/article/details/44244951

    流水号:

      Redis同样可以生成每天的流水号,日期+自增长序号,进行incr

    面试题:如何从Redis查询出前缀为id的key?

      首先这个问题应该要明确数据量,如果数据量很小,可以直接使用keys id*,keys命令直接返回所有的key,如果是海量数据,keys命令肯定

    不行了,所以要跟面试官明确这个问题。海量数据环境下,例如1亿条数据,可以使用scan命令

    scan是基于游标的迭代器,每次使用都要基于上一次的游标延续之前的迭代过程

    格式:scan cursor [MATCH pattern] [COUNT count]

    cursor以0开始,到0结束,scan 0 match id* count 10,从0开始,匹配以id开头的key,每次返回10条

    返回结果有两部分:

    1) "0"
    2) 1) "id1"
       2) "id2"
        .......

      1)为返回的游标,返回0证明迭代结束。这里希望返回10条,并不是一定返回10条,可能只是返回5条数据(一次返回的数量不可控,大概率符合

    count),如果返回cursor不是0,证明迭代没有结束,可以继续查询,知道返回cursor为0,效率低于keys,但是不会阻塞Redis

    PS:scan返回的游标可能后一次比前一次更小,所以可能会出现重复数据,需要外部程序进行去重

  • 相关阅读:
    【转】什么时候用抽象类,什么时候用接口
    高内聚松耦合在程序设计中如何做到
    如何做高水平的程序项目设计者
    NHibernate条件查询(Criteria Query)
    Oracle学习笔记之表结构修改
    Java集合类和HashMap遍历
    Asp.net中基于Forms验证的角色验证授权
    Springconfig.xml数据库操作Bean配置
    Java 常用排序算法实现快速排序、插入排序、选择、冒泡
    .net消息队列
  • 原文地址:https://www.cnblogs.com/huigelaile/p/10888339.html
Copyright © 2011-2022 走看看