分布式锁系列--02Redis实现分布式锁

版权声明:欢迎转载,请标明出处,如有问题,欢迎指正!谢谢!微信:w1186355422 https://blog.csdn.net/weixin_39800144/article/details/84951803
本文讲述,如何使用redis来实现分布式锁。这种实现方式,满足了分布式锁系列–01分布式锁入门介绍一文中,分布式锁约束的前三条:互斥性,安全性,对称性。因为是单机版本,所有无法满足第四条。自己编码来实第四点,是比较麻烦的,后面会介绍如何使用开源的Redisson框架来实现分布式锁。

实现原理

有一个redis服务实例,在分布式系统中,所有需要获取锁的客户端,都需要访问这个redis实例:

如果锁不存在,则写入key-value格式的数据,并设定过期时间,这个value,是为了保证解锁时,不会误解别人的锁,过期时间,是为了保证,万一加锁后客户端挂掉,解锁失败,当过期时间到了,redis会自动删除该锁,防止死锁。

如果锁已经存在,则说明已经有其它客户端持有该锁,可等待其释放(key-value 被主动删除或者因过期而被动删除)再尝试获取锁。

释放锁时,删除该key-value即可,需要确保删除的是自己加的锁,且使用watch机制和事务,来确保删除锁操作的原子性。

本文主要分为以下几个步骤实现:
  • 1.pom.xml引入依赖
  • 2.JedisManager管理JedisPool
  • 3.RedisDistributedLock分布式锁工具类
  • 4.测试代码

1.pom.xml引入依赖

		<!-- redis -->
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
		</dependency>
		<!--jedis-->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.9.0</version>
		</dependency>

2.JedisManager管理JedisPool

自定义JedisManager类,用于管理jedisPool,配置参数可以根据自己需要做修改,这个连接池的管理和配置,可以自己实现,这里只是一个参考:

public class JedisManager {

  private static final Logger logger = LoggerFactory.getLogger(ZookeeperClient.class);
  private static final String REDIS_HOST = "xx.xx.xx.xx";
  private static final Integer REDIS_PORT = 6379;
  private static JedisPool jedisPool;
  private static JedisPoolConfig config = new JedisPoolConfig();

  private static void init(){
    config.setMaxTotal(1000);
    config.setMaxIdle(10);
    config.setMaxWaitMillis(10*1000);
    config.setTestOnBorrow(true);//borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
    config.setTestOnReturn(true);//return一个jedis实例给pool时,是否检查连接可用,设置为true时,返回的对象如果验证失败,将会被销毁,否则返回
  }

  public static JedisPool getJedisPool(){
    if(null == jedisPool){
      synchronized (JedisPool.class){
        if(null == jedisPool){
          init();
          jedisPool = new JedisPool(config, REDIS_HOST, REDIS_PORT, 3000,"password");
          logger.info("【Redis lock】jedisPool初始化成功......");
          return jedisPool;
        }
      }
    }
    return jedisPool;
  }
}

3.RedisDistributedLock分布式锁工具类

public class RedisDistributedLock {
  private static final Logger logger = LoggerFactory.getLogger(ZookeeperClient.class);
  private static final String LOCK_SUCCESS = "OK";
  private static final Integer RELEASE_SUCCESS = 1;
  private static final String SET_IF_NOT_EXIST = "NX";
  private static final String SET_WITH_EXPIRE_TIME = "PX";
  private static JedisPool jedisPool = JedisManager.getJedisPool();

  /**
   * 加锁
   *
   * @param lockName 锁名,对应被争用的共享资源
   * @param randomValue 随机值,需要保持全局唯一,便于释放时校验锁的持有者
   * @param expireTime 过期时间,到期后自动释放,防止出现问题时死锁,资源无法释放
   * @return
   */
  public static boolean acquireLock(String lockName,String randomValue,int expireTime){
    Jedis jedis = jedisPool.getResource();
    try {
      while (true){
        String result = jedis
            .set(lockName, randomValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if(LOCK_SUCCESS.equals(result)){
          logger.info("【Redis lock】success to acquire lock for [ "+lockName+" ],expire time:"+expireTime+"ms");
          return true;
        }
      }
    }catch (Exception ex){
      ex.printStackTrace();
    }finally {
      if(null != jedis){
        jedis.close();
      }
    }
    logger.info("【Redis lock】failed to acquire lock for [ "+lockName+" ]");
    return false;
  }

  /**
   * redis释放锁
   * watch和muti命令保证释放时的对等性,防止误解锁
   *
   * @param lockName 锁名,对应被争用的共享资源
   * @param randomValue 随机值,需要保持全局唯一,以检验锁的持有者
   * @return 是否释放成功
   */
  public static boolean releaseLock(String lockName,String randomValue){
    Jedis jedis = jedisPool.getResource();
    try{
      jedis.watch(lockName);//watch监控
      if(randomValue.equals(jedis.get(lockName))){
        Transaction multi = jedis.multi();//开启事务
        multi.del(lockName);//添加操作到事务
        List<Object> exec = multi.exec();//执行事务
        if(RELEASE_SUCCESS.equals(exec.size())){
          logger.info("【Redis lock】success to release lock for [ "+lockName+" ]");
          return true;
        }
      }
    }catch (Exception ex){
      logger.info("【Redis lock】failed to release lock for [ "+lockName+" ]");
      ex.printStackTrace();
    }finally {
      if(null != jedis){
        jedis.unwatch();
        jedis.close();
      }
    }
    return false;
  }

}

4.测试代码

  /***
   * 测试redis分布式锁
   * @param id
   * @param stock
   */
  @Override
  public void updateStockById1(Integer id, Integer stock) {
    System.out.println("---------->"+Thread.currentThread().getName());

    String randomValue = id+ UUID.randomUUID().toString();//随机值,确保全局唯一
    RedisDistributedLock.acquireLock(id.toString(),randomValue,5*1000);//加锁

    //业务逻辑
    ProductStock product = productStockDao.getById(id);
    Integer stock1 = product.getStock();
    stock1 = stock1+stock;
    productStockDao.updateStockById(id,stock1);

    RedisDistributedLock.releaseLock(id.toString(),randomValue);//释放锁
  }

测试的方式是,我们有一个产品库存,调用接口时,会去更新库存+1,如果没有加锁,并发情况下,库存会和逾期值差距很大,这里可以用jmter模拟并发来做测试。(也可以启两个服务,做个负载均衡,这样模拟更加真实,后面会把这种测试方式的代码上传到github)

猜你喜欢

转载自blog.csdn.net/weixin_39800144/article/details/84951803