redis 实现分布式锁与分布式全局ID生成

一. redis 实现分布式锁

  1. 利用 redis 单线程,不能存储相同key,可以设置存储数据失效的特性实现分布式锁,重点方法时setnx(),存储,存储成功返回1,失败返回0,
  2. 当获取锁时存储调用setnx()方法存储一个代表锁的key,该key对应一个可以代表唯一的变量,并将该变量返回
  3. 获取锁成功后调用 expire(lockName, time) 对当前代表锁的key设置失效时间,防止死锁
  4. 如果存储失败说明前面有线程获取到了锁,当前线程阻塞等待(或while循环一不停调用获取锁的方法,直至获取成功)
  5. 当需要加锁执行的代码执行完毕后,删除redis中这个代表锁的key,删除前获取该key存储的数据与上面返回的数据是否是同一个,是否是一把锁,删除释放
  6. 代码示例
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.UUID;

public class LockRedis {

   //建立redis连接属性
   private JedisPool jedisPool;
   //初始化redis连接
    static {
      JedisPoolConfig config = new JedisPoolConfig();
      // 设置最大连接数
      config.setMaxTotal(200);
      // 设置最大空闲数
      config.setMaxIdle(8);
      // 设置最大等待时间
      config.setMaxWaitMillis(1000 * 100);
      // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
      config.setTestOnBorrow(true);
      jedisPool= new JedisPool(config, "39.107.69.43", 6379, 3000);
   }

   /**1. 获取锁
    * @param lockKey 传递setnx()向redis存储的key
    * @param acquireTimeout  在没有上锁之前,获取锁的超时时间
    * @param timeOut 上锁成功后,锁的超时时间
    * @return
    */
   public String lockWithTimeout(String lockKey, Long acquireTimeout, Long timeOut) {
      Jedis conn = null;
      String retIdentifierValue = null;
      try {
         //1.建立redis连接
         conn = jedisPool.getResource();
         //2.随机生成一个value(该value值要唯一,通过这个value值判断释放的锁是否是同一把锁)
         String identifierValue = UUID.randomUUID().toString();
         // 3.定义锁的名称
         String lockName = "redis_lock" + lockKey;
         // 4.定义上锁成功之后,锁的超时时间(传递过来的是毫秒,需要计算为秒,转为int)
         int expireLock = (int) (timeOut / 1000);
         // 5.获取锁的超时时间
         //System.currentTimeMillis() 获取的是当前系统时间
         //当前系统时间+获取锁前的超时时间,当时间到达这个值说明获取超时,不再获取
         Long endTime = System.currentTimeMillis() + acquireTimeout;
         
         //通过while,在指定时间内不停获取锁,超过这个时间停止获取,获取锁超时
         while (System.currentTimeMillis() < endTime) {
            //6.调用setnx()方法,向redis存储代表锁的key
            //存储成功返回1,说明获取到锁,设置失效时间,防止死锁
            if (conn.setnx(lockName, identifierValue) == 1) {
               // 7.判断返回结果如果为1,则可以成功获取锁,并且设置锁的超时时间
               conn.expire(lockName, expireLock);
               retIdentifierValue = identifierValue;
               return retIdentifierValue;
            }
            // 8.否则情况下继续循环等待
         }

      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         //关闭连接
         if (conn != null) {
            conn.close();
         }
      }
      return retIdentifierValue;
   }


   /**
    * 2.释放锁
    * @param lockKey 删除redis中代表锁的key
    * @param identifier 代表锁的key对应的value值,在获取锁时存储的,通过该值判断是否是一把锁
    * @return
    */
   public boolean releaseLock(String lockKey, String identifier) {

      Jedis conn = null;
      boolean flag = false;
      try {
         //1.建立redis连接
         conn = jedisPool.getResource();
         //2.定义锁的名称(与setnx()方法存储的对应的key一致)
         String lockName = "redis_lock" + lockKey;
         //3.通过代表锁的key查询redis中查询数据,判断是否是同一把锁
         if (identifier.equals(conn.get(lockName))) {
            //删除锁(redis中指定key)
            conn.del(lockName);
            System.out.println(identifier + "解锁成功......");
         }
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         //关闭redis连接
         if (conn != null) {
            conn.close();
         }
      }
      return flag;
   }
}
  1. 调用测试
 	public static void main(String[] args) {
      LockRedis lockRedis = new LockRedis();
      String identifier = lockRedis.lockWithTimeout("lock", 5000L, 5000L);
      //验证是否获取锁成功
      if (StringUtils.isEmpty(identifier)) {
         // 获取锁失败
         System.out.println(Thread.currentThread().getName() + ",获取锁失败,原因时间超时!!!");
      }
   }

二. redis 生成分布式全局ID

  1. 利用了 redis 单线程,保证原子性,不依赖数据库,生成id可以自增,天然排序等特性
  2. 注意点redis集群时多个写库,注意设置步长,与生成的id起始值
  3. 利用 RedisAtomicLong 调用 RedisAtomicLong () 方法
 	@Autowired
   private StringRedisTemplate stringRedisTemplate;
   //最终返回的是”日期字符串-redis生成的5位唯一id”,以这个数据为订单id进行存储
   public String order(String key) {//key是在redis中这个id的名字
      RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
      long andIncrement = redisAtomicLong.getAndIncrement();
      //"%1$05d":1表示1开始,0表示不够的补0,5就是数据为5位
      //也就是如果为1,设置后时"00001"
      String orderId = prefix() + "-" + String.format("%1$05d", andIncrement);
      return orderId;
   }
   //将时间对象转换为字符串
   public static String prefix() {
      String temp_str = "";
      Date dt = new Date();
      // 最后的aa表示“上午”或“下午” HH表示24小时制 如果换成hh表示12小时制
      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
      temp_str = sdf.format(dt);
      return temp_str;
   }
  1. 步长与起始值设置
	//设置自增起始值(要在创建自增id的方法外设置一遍即可)
	redisAtomicLong.set(0);
	//设置步长为10(要在创建自增id的方法中,每创建一个,需要执行一次)
	redisAtomicLong.addAndGet(9);
发布了53 篇原创文章 · 获赞 0 · 访问量 722

猜你喜欢

转载自blog.csdn.net/qq_29799655/article/details/105640906