在单体应用中,多个线程共享一个变量时就会出现数据安全问题。我们可以加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实现分布式锁 随笔 第1张

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()方法之前获取锁,获取到锁成功之后才执行。可以看到最终数据库只插入了一条数据。

     Redis实现分布式锁 随笔 第2张

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄