一、分布式锁
分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:加锁在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。解锁把脚印从沙滩上抹去,就是解锁的过程。锁超时为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。二、redis我们先来看如何通过单节点Redis实现一个简单的分布式锁。1、加锁加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。SET lock_key random_value NX PX 5000值得注意的是:random_value 是客户端生成的唯一的字符串。NX 代表只在键不存在时,才对键进行设置操作。PX 5000 设置键的过期时间为5000毫秒。这样,如果上面的命令执行成功,则证明客户端获取到了锁。2、解锁解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉。这时候random_value的作用就体现出来。为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。if redis.call(‘get’,KEYS[1]) == ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end3、实现首先,我们在pom文件中,引入Jedis。在这里,笔者用的是最新版本,注意由于版本的不同,API可能有所差异。 redis.clients jedis 3.0.1加锁的过程很简单,就是通过SET指令来设置值,成功则返回;否则就循环等待,在timeout时间内仍未获取到锁,则获取失败。@Servicepublic class RedisLock { Logger logger = LoggerFactory.getLogger(this.getClass()); private String lock_key = “redis_lock”; //锁键 protected long internalLockLeaseTime = 30000;//锁过期时间 private long timeout = 999999; //获取锁的超时时间 //SET命令的参数 SetParams params = SetParams.setParams().nx().px(internalLockLeaseTime); @Autowired JedisPool jedisPool; /** * 加锁 * @param id * @return / public boolean lock(String id){ Jedis jedis = jedisPool.getResource(); Long start = System.currentTimeMillis(); try{ for(;😉{ //SET命令返回OK ,则证明获取锁成功 String lock = jedis.set(lock_key, id, params); if(“OK”.equals(lock)){ return true; } //否则循环等待,在timeout时间内仍未获取到锁,则获取失败 long l = System.currentTimeMillis() - start; if (l>=timeout) { return false; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }finally { jedis.close(); } }}解锁我们通过jedis.eval来执行一段LUA就可以。将锁的Key键和生成的字符串当做参数传进来。 /* * 解锁 * @param id * @return */ public boolean unlock(String id){ Jedis jedis = jedisPool.getResource(); String script = “if redis.call(‘get’,KEYS[1]) == ARGV[1] then” + " return redis.call(‘del’,KEYS[1]) " + “else” + " return 0 " + “end”; try { Object result = jedis.eval(script, Collections.singletonList(lock_key), Collections.singletonList(id)); if(“1”.equals(result.toString())){ return true; } return false; }finally { jedis.close(); } }最后,我们可以在多线程环境下测试一下。我们开启1000个线程,对count进行累加。调用的时候,关键是唯一字符串的生成。这里,笔者使用的是Snowflake算法。@Controllerpublic class IndexController { @Autowired RedisLock redisLock; int count = 0; @RequestMapping("/index") @ResponseBody public String index() throws InterruptedException { int clientcount =1000; CountDownLatch countDownLatch = new CountDownLatch(clientcount); ExecutorService executorService = Executors.newFixedThreadPool(clientcount); long start = System.currentTimeMillis(); for (int i = 0;i<clientcount;i++){ executorService.execute(() -> { //通过Snowflake算法获取唯一的ID字符串 String id = IdUtil.getId(); try { redisLock.lock(id); count++; }finally { redisLock.unlock(id); } countDownLatch.countDown(); }); } countDownLatch.await(); long end = System.currentTimeMillis(); logger.info(“执行线程数:{},总耗时:{},count数为:{}”,clientcount,end-start,count); return “Hello”; }}至此,单节点Redis的分布式锁的实现就已经完成了。比较简单,但是问题也比较大,最重要的一点是,锁不具有可重入性。
分布式锁简介
最新推荐文章于 2022-11-06 17:52:02 发布