Redis实现分布式锁
在单体应用中,多个线程共享一个变量时就会出现数据安全问题。我们可以加synchronized或lock锁来同步代码块。当程序在运行在集群情况下时就不行了,因为它们都是作用于单个jvm的,这个时候我们就可以采用分布式锁解决并发问题。
资源竞争案例:
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。当这个用户昵称不存在时,我们就可以注册这个用户,这时我采用多线程模拟集群下多进程的并发情况。(使用数据库的唯一索引也能解决问题,我们这里不做讨论)
@RestController public class RedisController { @Autowired UserDao userDao; @RequestMapping("/test") public String testdb() { for(int i=0; i<20; i++) { new Thread(new Runnable(){ public void run(){ insert(); } }).start(); } return "ok"; } private void insert() { if(userDao.findByName("吴磊").size()==0) { User u = new User(); u.setName("吴磊"); userDao.insert(u); } } }
测试可以发现,多个请求同时进来插入数据,它们几乎同时查询到该用户不存在后,于是满足判断条件,此时去数据库插入数据,就造成了多个用户名重复的情况。因为是集群环境下,这时即使加synchronized也是不起任何作用的。
Redis分布式锁:redis实现分布式锁就是指定一个锁名称,然后通过setnx去获取锁。只有获取锁成功之后才能执行需要同步的代码块,执行完成之后手动释放锁。通常为了避免死锁的发生我们也需要给这个锁添加超时时间。
/** * Redis实现分布式锁完整案例 */ @Component public class RedisService { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Autowired JedisPool jedisPool; // 获取锁之前的超时时间(获取锁的等待重试时间) private long acquireTimeout = 5000; // 获取锁之后的超时时间(防止死锁) private int timeOut = 10000; /** * @title 获取分布式锁 * @return 锁标识 */ public String getRedisLock(String lockName) { Jedis jedis = null; try { jedis = jedisPool.getResource(); // 1.随机生成value,当做锁的唯一标识,作用释放锁 String lockTag = UUID.randomUUID().toString(); // 2.获取锁的时间 Long endTime = System.currentTimeMillis() + acquireTimeout; // 3.尝试获取锁 while (System.currentTimeMillis() < endTime) { if (jedis.setnx(lockName, lockTag) == 1) { System.out.println("获取锁..." + Thread.currentThread().getName()); // 设置对应key的有效期 (防止死锁发生) jedis.expire(lockName, timeOut/1000); return lockTag; } } } catch (Exception e) { log.error(e.getMessage()); } finally { returnResource(jedis); } return null; } /** * @title 释放分布式锁 * @param lockName 锁名称 * @param lockTag 锁标识 */ public void unRedisLock(String lockName, String lockTag) { Jedis jedis = null; try { jedis = jedisPool.getResource(); if (jedis.get(lockName).equals(lockTag)) { System.out.println("释放锁..." + Thread.currentThread().getName() + ",identifierValue:" + lockTag); jedis.del(lockName); } } catch (Exception e) { log.error(e.getMessage()); } finally { returnResource(jedis); } } // =============================================== public String get(String key) { Jedis jedis = null; String value = null; try { jedis = jedisPool.getResource(); value = jedis.get(key); log.info(value); } catch (Exception e) { log.error(e.getMessage()); } finally { returnResource(jedis); } return value; } public void set(String key, String value) { Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.set(key, value); } catch (Exception e) { log.error(e.getMessage()); } finally { returnResource(jedis); } } /** * 关闭连接 */ public void returnResource(Jedis jedis) { try { if(jedis!=null) jedis.close(); } catch (Exception e) { } } }
这时我们在执行insert()方法之前获取锁,获取到锁成功之后才执行。可以看到最终数据库只插入了一条数据。

更多精彩