HoBing

导航

 

什么是超卖?

  某件商品库存数量10件,结果卖出了15件。
  会有什么影响?
  1. 商品卖出数量超出了库存数量
  2. 导致商家没有商品发货、发货时间延长、买卖双方容易发声纠纷
 

超卖现象一

  系统中库存为1,A和B同事看到这个商品,加入购物车,并同时提交订单,所以产生了两笔订单,
  卖家在商品发货时,发现只有1件商品,但是有两笔订单,导致卖家无法给其中一方发货。
  产生原因:
  1. 扣减库存的动作,在程序中进行 在程序中由于并发原因,导致库存计算错误

超卖现象一

超卖现象二

  并发校验库存时,造成库存充足的假象,update更新库存导致系统中库存为负数
超卖现象二
 

为什么要使用分布式锁

  在开发应用过程中,如果需要对一个共享变量(上述超卖现象的库存)进行多线程同步访问,
  虽然可以使用我们Synchronized、ReentrantLock进行处理,但随业务发展,需要做集群时,
  一个应用需要部署到多台服务器上,这时候我们就需要借助第三方组件来控制共享变量,即分布式锁。
 

分布式锁应该具备哪些条件

  1. 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  2. 高可用、高性能的获取锁与释放锁
  3. 具备可重入特性
  4. 具备锁失效机制,防止死锁
  5. 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
 

分布式锁实现方式

第一种:基于数据库悲观锁的分布式锁

  多个进程、多个线程访问共同组件数据库。通过select……for update访问同一条数据,
  利用数据库for update特性锁住数据,使得其他线程只能等待,从而达到分布式锁的效果。
 

第二种:基于Redis的Setnx实现分布式锁

  1. 获取锁时,使用setnx进行加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
  2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间放弃获取锁。
  3. 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
  4. 经历上述操作后,因为释放锁的方法不是原子操作导致的,那么我们只要保证释放锁的代码是原子性的就能解决该问题了。(引入LUA脚本)
String key = "redisKey";
String value = UUID.randomUUID().toString();

RedisCallback<Boolean> redisCallback = connection -> {
    //设置NX
    RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
    //设置过期时间
    Expiration expiration = Expiration.second(30);
    //序列化key
    byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
    //序列化value
    byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
    
    Boolean result = connection.set(redisKey,redisValue,expiration,);
}

//获取分布式锁
Boolean lock = (Boolean) redisTemplate.excute(redisCallback,setOption )
if(lock){
    log.info("我进入了锁");
    try {
        Thread.sleep(15000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        RedisScript<Boolean> redisScript = 
        RedisScirpt.of("if redis.call("get",KEYS[1] ) == ARGV[1] then
                            return redis.call("del",KEY[1])
                        else
                            return 0
                        end",Boolean.class);
        List<String> keys = Arrays.asList(key);
        
        Object result = (Boolean) redisTemple.excute(redisScript,keys,value);
    }
}

 

第三种:使用Zookeeper的实现方式

  利用Zookeeper的瞬时有序节点的特性;当多线程并发创建瞬时节点时,会得到有序的序列;
  而序号最小的线程获得锁;其他未获取锁的线程则监听自己序号的前一个序号;
  当前一个线程执行完成,删除自己序号的节点;下一个序号的线程得到通知,继续执行。原理图解为:
Zookeeper原理解析
 

第四种:基于Zookeper的Curator的客户端实现分布式锁

package com.xbq.zookeeper.curator;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.RetryNTimes;

/**
 * 使用Curator来实现分布式锁
 * @author xbq
 */
public class LockByCurator {

    // 此demo使用的集群,所以有多个ip和端口
    private static String CONNECT_SERVER = "192.168.242.129:2181,192.168.242.129:2182,192.168.242.129:2183";
    // session过期时间
    private static int SESSION_TIMEOUT = 3000;
    // 连接超时时间
    private static int CONNECTION_TIMEOUT = 3000; 
    
    // 锁节点
    private static final String CURATOR_LOCK = "/curatorLock";
    
    /**
     * 获取锁操作
     * @param cf
     */
    public static void doLock(CuratorFramework cf){
        System.out.println(Thread.currentThread().getName() + " 尝试获取锁!");
        // 实例化 zk分布式锁  
        InterProcessMutex mutex = new InterProcessMutex(cf, CURATOR_LOCK);
        try {
            // 判断是否获取到了zk分布式锁
            if(mutex.acquire(5, TimeUnit.SECONDS)){
                System.out.println(Thread.currentThread().getName() + " 获取到了锁!-------");
                // 业务操作
                Thread.sleep(5000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 释放锁
                mutex.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        // 定义线程池
        ExecutorService service = Executors.newCachedThreadPool();
        // 定义信号灯,只能允许10个线程并发操作
        final Semaphore semaphore = new Semaphore(10);
        // 模拟10个客户端
        for(int i=0; i < 10 ;i++){
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                         // 连接 ZooKeeper 
                        CuratorFramework framework = CuratorFrameworkFactory.
                                newClient(CONNECT_SERVER, SESSION_TIMEOUT, CONNECTION_TIMEOUT, new RetryNTimes(10,5000));
                        // 启动
                        framework.start();
                        doLock(framework);
                        
                        semaphore.release();
                    } catch (Exception e) {
                    
                    }
                }
            };
            service.execute(runnable);
        }
        service.shutdown();
    }
}

 

第五种:基于Redisson实现分布式锁

添加依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.8.2</version>
</dependency>

 

注入RedissonConfig Bean
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient getClient(){
        Config config = new Config();
        //设置redis
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        //初始化Redission
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

 

实现分布式锁
//获取分布式锁
RLock lock = redisson.getLock("lockName");
try{
    // 1. 最常见的使用方法
    //lock.lock();
    // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
    //lock.lock(10, TimeUnit.SECONDS);
    // 3. 尝试加锁,最多等待2秒,上锁以后8秒自动解锁
    boolean res = lock.tryLock(2, 8, TimeUnit.SECONDS);
    if(res){ //成功
        //处理业务
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    //释放锁
    lock.unlock();
}

 

分布式锁实现方案优缺点分析

 

分布式锁实现方案优缺点分析

总结:不推荐自己编写分布式锁,推荐使用Redisson和Curator实现的分布式锁(经历大量验证)
posted on 2021-06-03 15:24  HoBling  阅读(71)  评论(0)    收藏  举报