Java Develop——基于 Redis 的分布式锁

来源:互联网 发布:压缩软件mac版 编辑:程序博客网 时间:2024/06/08 09:45

转载请注明出处:http://blog.csdn.net/smartbetter/article/details/78715276

分布式锁应用场景都是用在高并发,大流量场景。

1.用Apache ab压测模拟并发

ab 的使用非常简单:

//-n模拟100个请求,-c模拟100个并发ab -n 100 -c 100 http://www.example.com///-t模拟60秒,-c模拟100个并发,它会在连续60秒内不停的发请求ab -t 60 -c 100 http://www.example.com/

例如常见的秒杀系统,秒杀数据放在 MySQL 这类数据库中时,通过 select…for update 手工加锁可以避免超卖现象。但是为了提升查询速度,常将这些数据放在内存、Redis 中,压测后会发现出现了超卖的现象针对这个现象,可以通过 synchronized 关键字加锁处理并发:

/** * 秒杀的方法 */public synchronized void orderProductMockDiffUser(String productId) {}

这样处理后,再次压测,会发现时间变成了,这是因为加锁处理后,每次访问方法的线程只会有一个线程。这种方法是一种解决方法,但是无法做到细粒度的控制。上面是秒杀的同一个商品的场景,假如说现在有很多商品,每个商品 id 不一样,但是它们都会访问同一个秒杀方法,这会造成一个现象,假如秒杀商品 A 的人非常多,秒杀商品 B 的人非常少,但是两个都会非常慢,因为它们每次访问方法的线程只会有一个线程,无法做到细粒度的控制。还有重要的一点是,这种方法只适合单点的情况,如果水平扩展后 (集群),不同的用户看到的是五花八门的。基于这个问题,就引出了基于 Redis 的分布式锁。

2.基于 Redis 的分布式锁

首先看两个 Redis 的命令:

SETNX key value将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。参考: http://www.redis.cn/commands/setnx.htmlGETSET key value自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。参考: http://www.redis.cn/commands/getset.html

基于 Redis 的分布式锁其实就是在秒杀的方法前后进行加锁、解锁操作:

/** * 秒杀的方法 */public void orderProductMockDiffUser(String productId) {    //加锁    //...    //解锁}

下面新建一个 Redis 分布式锁的处理,把加锁和解锁写进来:

@Component@Slf4jpublic class RedisLock {    private Logger logger = LoggerFactory.getLogger(getClass());    @Autowired    private StringRedisTemplate redisTemplate;    /**     * 加锁     *     * @param key     * @param value 当前时间+超时时间     * @return     */    public boolean lock(String key, String value) {        //SETNX命令, 可以设置返回true, 不可以返回false        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {            return true;        }        String currentValue = redisTemplate.opsForValue().get(key);        //如果锁过期        if (StringUtils.isEmpty(currentValue)                && (Long.parseLong(currentValue) < System.currentTimeMillis())) {            //GETSET命令, 获取上一个锁的时间            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(value)) {                return true;            }        }        return false;    }    /**     * 解锁     */    public void unLock(String key, String value) {        try {            String currentValue = redisTemplate.opsForValue().get(key);            if (!StringUtils.isEmpty(currentValue)                    && currentValue.equals(value)) {                redisTemplate.opsForValue().getOperations().delete(key);            }        } catch (Exception e) {            logger.error("【redis分布式锁】解锁异常, {}", e);        }    }}

使用就很简单了:

private static final int TIMEOUT = 10 * 1000; //超时时间10秒@Autowiredprivate RedisLock redisLock;/** * 秒杀的方法 */public void orderProductMockDiffUser(String productId) {    //加锁    long time = System.currentTimeMillis() + TIMEOUT;    if(!redisLock.lock(productId, String.valueOf(time))) {        throw new SellException(101, "人也太多了, 换个姿势再试试~");    };    //...    //解锁    redisLock.unLock(productId, String.valueOf(time));}

到此就完成了分布式锁的处理。

原创粉丝点击