• Redis分布式锁


    目前几种分布式锁的实现方式:

    • 数据库实现(不适合数据量比较大的互联网公司)、
    • 基于ZK的实现(1、Zk的节点改变时候的watcher事件通知。2、节点类型中的有序节点可实现先到先得公平策略)
    • 基于Redis的实现(setNX + 有效期,实现相对ZK简单一些)    

    工作中经常用到Redis,所以决定采用redis实现分布式锁, 首先先要明确目标,目标明确了才有技术方案

    分布式锁实现的目标

    •  高性能(加锁和解锁性能高)
    •  互斥访问(一个线程持有锁,另一个线程不能持有)
    •  不能产生死锁(例如redis客户端挂了,或者设置过期时间时候挂了导致key永久有效)
    •   解锁(只能解除自己的锁)

    具体实现:

    
    
      1 package com.brightcns.wuxi.citizencard.common.feature.util;
      2 
      3 import javafx.beans.binding.ObjectExpression;
      4 import lombok.extern.slf4j.Slf4j;
      5 import org.springframework.data.redis.connection.RedisConnection;
      6 import org.springframework.data.redis.connection.ReturnType;
      7 import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
      8 import redis.clients.jedis.JedisPoolConfig;
      9 import redis.clients.util.SafeEncoder;
     10 
     11 
     12 /**
     13  * 分布式锁的常用类
     14  * @author maxianming
     15  * @date 2018/8/13 15:50
     16  */
     17 @Slf4j
     18 public class LockUtils {
     19 
     20     private JedisConnectionFactory jedisConnectionFactory;
     21     private static final Long RELEASE_SUCCESS = 1L;
     22     private static final String LOCK_SUCCESS = "OK";
     23     private static final String SET_IF_NOT_EXIST = "NX";
     24     private static final String SET_WITH_EXPIRE_TIME = "PX";
     25 
     26     public LockUtils(JedisConnectionFactory jedisConnectionFactory) {
     27         if (jedisConnectionFactory == null) {
     28           throw new IllegalArgumentException("jedisConnectionFactory not be allowed null");
     29         }
     30         this.jedisConnectionFactory = jedisConnectionFactory;
     31     }
     32     /**
     33      * 尝试获取分布式锁
     34      * @param lockKey 锁
     35      * @param requestId 请求标识
     36      * @param expireTime 超期时间 ms
     37      * @return 是否获取成功
     38      */
     39     public void lock(String lockKey, String requestId, int expireTime) throws InterruptedException {
     40         RedisConnection redisConnection = getRedisConnection();
     41         try {
     42           while (true) {
     43                Object result = redisConnection.execute("SET", new byte[][]{
     44                        SafeEncoder.encode(lockKey), SafeEncoder.encode(requestId), SafeEncoder.encode(SET_IF_NOT_EXIST),
     45                        SafeEncoder.encode(SET_WITH_EXPIRE_TIME), SafeEncoder.encode(String.valueOf(expireTime))});
     46                if (result != null) {
     47                    if (LOCK_SUCCESS.equals(new String((byte[])(result)))) {
     48                        return;
     49                    }
     50                }
     51               try {
     52                   Thread.sleep(expireTime);
     53               } catch (InterruptedException e) {
     54                   log.error("中断异常", e);
     55                   throw e;
     56               }
     57           }
     58        } finally {
     59            redisConnection.close();
     60        }
     61     }
     62 
     63 
     64     /**
     65      * 释放分布式锁
     66      * @param lockKey 锁
     67      * @param requestId 请求标识
     68      * @return 是否释放成功
     69      */
     70     public boolean unLock(String lockKey, String requestId) {
     71         RedisConnection redisConnection = getRedisConnection();
     72         try {
     73             String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
     74             Object result = redisConnection.eval(SafeEncoder.encode(script), ReturnType.INTEGER, 1, getByteParams(new String[]{lockKey, requestId}));
     75             if (RELEASE_SUCCESS.equals(result)) {
     76                 return true;
     77             }
     78             return false;
     79         } finally {
     80             redisConnection.close();
     81         }
     82 
     83     }
     84 
     85     private byte[][] getByteParams(String... params) {
     86         byte[][] p = new byte[params.length][];
     87         for (int i = 0; i < params.length; i++)
     88             p[i] = SafeEncoder.encode(params[i]);
     89 
     90         return p;
     91     }
     92 
     93     private RedisConnection getRedisConnection() {
     94         RedisConnection redisConnection = jedisConnectionFactory.getConnection();
     95         return redisConnection;
     96     }
     97 
     98     public static void main(String[] args) {
     99        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
    100         jedisConnectionFactory.setPort(6379);
    101         jedisConnectionFactory.setHostName("*");
    102         jedisConnectionFactory.setPassword("*");
    103 
    104         JedisPoolConfig poolConfig = new JedisPoolConfig();
    105         poolConfig.setMaxTotal(40);
    106         poolConfig.setMaxIdle(10);
    107         poolConfig.setMinIdle(5);
    108         jedisConnectionFactory.setPoolConfig(poolConfig);
    109         jedisConnectionFactory.afterPropertiesSet();
    110         LockUtils lockUtils =new LockUtils(jedisConnectionFactory);
    111         Thread thread = new Thread(() -> {
    112             try {
    113                 lockUtils.lock("lock", "123", 20000);
    114             } catch (InterruptedException e) {
    115                 e.printStackTrace();
    116             }
    117             System.out.println("加锁结果:" + "成功");
    118 
    119             boolean result = lockUtils.unLock("lock", "123");
    120 
    121             System.out.println("解锁结果:" + result);
    122         },"jedis-thread");
    123         thread.start();
    128     }
    129 
    130 }
    
    
    
     

       (1)加锁

               jedis提供了set方法将setNX和expire整合在一起的原子方法,这就避免了expire之前redis挂了导致的key永久有效,从而死锁。

              实际项目中使用的Spring boot整合的redis,所以直接获取jedis客户端不太方便,查看源码发现 存在execute支持redis命令。

              requestId的作用,解锁的时候传入加锁时候的相同值,避免错误解除别的线程的锁

              expireTime的作用,redis key的有效期,根据实际时间设置

     (2)解锁

              采用lua脚本,主要保证一、解锁操作的原子性 二、解的是自己的锁

  • 相关阅读:
    最后一次不用看脸的日子
    经典算法_指针
    #include <process.h>
    经典算法_文件
    stdin、stdout、stderr
    经典算法_位运算
    经典算法_结构体
    经典算法_字符串
    #include <string.h>
    #include <stdio.h>
  • 原文地址:https://www.cnblogs.com/mxmbk/p/9470590.html
Copyright © 2020-2023  润新知