为什么要用分布式锁?

754 阅读3分钟

1.背景

在电商下单场景中,多个用户同时访问同一个商品的库存信息并尝试下单,如果不加控制地并发操作,就可能出现超卖的现象,即库存量为1,但是多个用户同时购买成功了多件商品,从而导致库存不足或者商品售罄。在单体服务情况下,我们可以使用Java中synchronized来锁定库存,但是在分布式的情况下,synchronized就无法锁定库存了

1.1为什么synchronized关键字在分布式情况下会失效

synchronized 是Java中的一种线程同步机制,可以用于控制多个线程对共享资源的访问。在单机环境下,使用 synchronized 可以有效保证线程安全,控制并发访问。但是在分布式环境下,使用 synchronized 是无法控制分布式下库存扣减的问题的。 原因在于 synchronized 是基于 JVM 内部的线程同步机制,它只能控制同一台机器上的不同线程之间的并发访问,而无法控制不同机器之间的访问。在分布式环境下,多个节点之间通过网络通信进行数据交互,使用 synchronized 无法对分布式环境下的共享资源进行并发访问控制。

1.2那什么可以解决分布式下电商下单库存锁定的问题?

答案就是使用分布式锁,常见的分布式锁有很多种,比如:Mysql,ZooKeeper,Redis.这里我们仅介绍使用Redis来完成分布式锁的情况

2.Redis分布式锁

Redis分布式锁是一种基于Redis实现的分布式锁,可以用于多个进程或者多台服务器之间的同步控制。在分布式系统中,由于存在多个进程或者多台服务器同时访问共享资源的情况,因此需要一种可靠的机制来避免资源的并发竞争和数据的不一致性问题,这就是分布式锁的作用。

2.1为什么redis分布式锁能够起到作用?

简单来说:因为redis服务只有一台,其他服务器都到redis这里来获取锁的信息,redis自然就能按顺序执行的,因此不会出现多个请求同时修改同一个key的情况。 下面给出简单的代码实现: ``` String xxxxxKey = "xxxxx:"+xxxCode(); if (!RedisUtil.redis.hasKey(xxxxxKey)) { log.info("redis无缓存,向redis写入上架库存缓存String.valueOf(ShelvesStock())); RedisUtil.redis.opsForValue().set(xxxxxKey,ShelvesStock()); } ```

2.2Redis针对分布式锁的优化方案

如设置锁的超时时间、使用Lua脚本等,避免死锁和误删锁等问题 简单的代码实现:

//为用户设置一个token,三十分钟过期时间(存在redis)
String orderToken = UUID.randomUUID().toString().replace("-", "");
stringRedisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX+member.getId(),orderToken,30, TimeUnit.MINUTES);

```
//1、验证令牌是否合法【令牌的对比和删除必须保证原子性】
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
String orderToken = orderSubmitRequest.getOrderToken();

//通过lure脚本原子验证令牌和删除令牌
Long resultToken = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
        Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + member.getId()),
        orderToken);

if (resultToken == 0L) {
    throw new BusinessException(OrderErrorCode.ORDER_TOKEN_VERIFICATION_FAILED);
} else {
    //令牌验证成功
    }
```

```js
```