分布式锁实现方式&应用

1、分布式锁使用场景

首先我们回顾使用线程锁的原因,当多线程并发工作时,我们需要保证对共享变量正确的操作,以i++为例,假设有两个线程A、B,分别对共享i做++操作,使用锁的我们实际期望的是,假设当前i值为5,则A和B不会同时对i=5这个值进行++操作然后再重新赋给i;

其次在电商应用中,类似秒杀,涉及库存交易处理等问题,我们可以采用以下分布式锁来实现复杂的业务需求。

 

2、分布式锁实现方式

2.1环境准备

2.1.1准备三台centos虚拟机

分别部署zookeeper

2.1.2准备三台centos虚拟机

2.2 Redis实现分布式锁

/**

 * 尝试获取分布式锁

 * @param jedis Redis客户端

 * @param lockKey 锁

 * @param requestId 请求标识

 * @param expireTime 超期时间

 * @return 是否获取成功

 */

  public boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

  

    String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

  

    if (LOCK_SUCCESS.equals(result)) {

        return true;

    }

    return false;

  

}
 
/**

 * 释放分布式锁

 * @param jedis Redis客户端

 * @param lockKey 锁

 * @param requestId 请求标识

 * @return 是否释放成功

 */

  public  boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

  

    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1])else return 0 end";

    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

  

    if (RELEASE_SUCCESS.equals(result)) {

        return true;

    }

    return false;

  

}
 

 

 

2.3 ZooKeeper实现分布式锁

import org.apache.zookeeper.*;

  import org.apache.zookeeper.ZooDefs.Ids;

  import org.apache.zookeeper.data.*;

  import java.io.IOException;

  import java.util.concurrent.CountDownLatch;

  import java.util.*;

  /**

 * @author jason

 * @date 2019/8/17 16:09

 */

  public class DistributedClient {

    // 超时时间

    private static final int SESSION_TIMEOUT = 5000;

    // zookeeper server列表

    private String hosts = "172.31.62.131:2181,172.31.62.132:2181,172.31.62.129:2181";

    private String groupNode = "locks";

    private String subNode = "sub";

    private ZooKeeper zk;

    // 当前client创建的子节点

    private String thisPath;

    // 当前client等待的子节点

    private String waitPath;

    private CountDownLatch latch = new CountDownLatch(1);

    public static void main(String[] args) throws Exception {

        for (int i = 0; i < 10; i++) {

            new Thread() {

                public void run() {

                    try {

                        DistributedClient dl = new DistributedClient();

                        dl.connectZookeeper();

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                }

            }.start();

        }

        Thread.sleep(Long.MAX_VALUE);

    }

    /**

     * 连接zookeeper

     */

    public void connectZookeeper() throws Exception {

        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() {

            public void process(WatchedEvent event) {

                try {

                    // 连接建立时, 打开latch, 唤醒wait在该latch上的线程

                    if (event.getState() == Event.KeeperState.SyncConnected) {

                        latch.countDown();

                    }

                    // 发生了waitPath的删除事件

                    if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {
       
       
//监控到节点变化,业务逻辑处理,处理完,释放锁

                        doSomething();

                    }

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        });

        // 等待连接建立

        latch.await();

        // 创建子节点

        thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE,

                CreateMode.EPHEMERAL_SEQUENTIAL);

  

        System.out.println("thisPath==="+thisPath);

        // wait 

        Thread.sleep(10);

        List<String> childrenNodes = zk.getChildren("/" + groupNode, false);

        // 列表中只有一个子节点, 那肯定就是thisPath, 说明client获得锁

        if (childrenNodes.size() == 1) {

            //业务逻辑处理,处理完,释放锁

            doSomething();

        } else {
       
       
//获取根节点所有的子节点,同时排序 

            String thisNode = thisPath.substring(("/" + groupNode + "/").length());

  

            System.out.println("thisNode==="+thisNode);

            // 排序

            Collections.sort(childrenNodes);

            for(String node:childrenNodes){

                System.out.println("childrenNodes="+node);

            }

  

            int index = childrenNodes.indexOf(thisNode);

            System.out.println("index="+index);

            if (index == -1) {

                // never happened

            } else if (index == 0) {

                // inddx == 0, 说明thisNode在列表中最小, 当前client获得锁

                //业务逻辑处理,处理完,释放锁

                doSomething();

            } else {

            // 获得排名比thisPath前1位的节点,监控该节点的变化

                this.waitPath = "/" + groupNode + "/" + childrenNodes.get(index - 1);

  

                System.out.println("waitPath======"+this.waitPath);

                // 在waitPath上注册监听器, 当waitPath被删除时, zookeeper会回调监听器的process方法

                zk.getData(waitPath, true, new Stat());

            }

        }

    }

    private void doSomething() throws Exception {

        try {

            System.out.println("gain lock: " + thisPath);

            Thread.sleep(2000);

            // do something

        } finally {

            System.out.println("finished: " + thisPath);

            // 将thisPath删除, 监听thisPath的client将获得通知

            // 相当于释放锁

            zk.delete(this.thisPath, -1);

        }

    }

  

}

 

 

3、分布式锁实现案例

3.1 redis分布式锁,实现秒杀功能

import redis.clients.jedis.Jedis;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;

/**
 * @author jason
 * @date 2019/8/17 16:09
 */

public class RedisSetNXTest extends  Thread{

   
private static final String LOCK_SUCCESS = "OK";
   
private static final String SET_IF_NOT_EXIST = "NX";
   
private static final String SET_WITH_EXPIRE_TIME = "PX";

   
private String auctionCode;
   
public RedisSetNXTest
            (String auctionCode) {
       
super(auctionCode);
       
this.auctionCode = auctionCode;
    }
   
private static int bidPrice = 100;
   
public static void initStock(Jedis jedis,String  size)
    {
        jedis.set(
"StockSize",size);
    }
   
public  boolean subStock(Jedis jedis)
    {
        String value=jedis.get(
"StockSize");
       
int size=Integer.parseInt(value);
       
if((size-1)<0)
        {
            System.
out.println("用户:"+Thread.currentThread().getName() +"库存已经耗尽,抢购失败"+getSystemTime());
           
return false;
        }
        System.
out.println("用户:"+Thread.currentThread().getName() +"当前库存="+size+"==抢购成功"+getSystemTime());
        jedis.set(
"StockSize",--size+"");
       
return true;
    }
   
public static void main(String[] args) {
        System.
out.println(Thread.currentThread().getName() + "主线程运行开始!");
       
//更改key为a的值
       
Jedis jedis=new Jedis("172.31.62.131",6379);
       
//初始化库存
       
initStock(jedis,"4");

       
//jedis.set("goodsprice","0");
        //System.out.println("
输出初始化值:"+jedis.get("goodsprice")+getSystemTime());
       
jedis.close();
        RedisSetNXTest thread1 =
new RedisSetNXTest("A001");
        RedisSetNXTest thread2  =
new RedisSetNXTest("B001");
        RedisSetNXTest thread3  =
new RedisSetNXTest("C001");
        RedisSetNXTest thread4  =
new RedisSetNXTest("D001");
        RedisSetNXTest thread5  =
new RedisSetNXTest("E001");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
       
try{
            thread1.join();
            thread2.join();
            thread3.join();
            thread4.join();
            thread5.join();
        }
catch(InterruptedException e){
            e.printStackTrace();
        }
        System.
out.println(Thread.currentThread().getName() + "主线程运行结束!"+getSystemTime());
    }

   
@Override
   
public void run() {
        Jedis jedis=
new Jedis("172.31.62.131",6379);

       
//尝试获取锁
       
boolean isOk=  tryGetDistributedLock(jedis, "goods_lock", Thread.currentThread().getName() , 10000);

       
if(isOk) {
            System.
out.println("子线程"+this.auctionCode +"拿到锁"+getSystemTime());
            subStock(jedis);
            System.
out.println("子线程"+this.auctionCode +"释放锁"+getSystemTime());
            releaseDistributedLock(jedis,
"goods_lock", Thread.currentThread().getName());
        }
else{
           
while(!isOk){
               
//System.out.println("子线程" + auctionCode + "未拿到锁"+getSystemTime());
               
isOk=  tryGetDistributedLock(jedis, "goods_lock", Thread.currentThread().getName() , 10000);
               
try {
                        sleep(
2000);
                }
catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.
out.println("子线程"+this.auctionCode +"拿到锁"+getSystemTime());
            subStock(jedis);
            System.
out.println("子线程"+this.auctionCode +"释放锁"+getSystemTime());
            releaseDistributedLock(jedis,
"goods_lock", Thread.currentThread().getName());
        }
        jedis.close();
        System.
out.println(Thread.currentThread().getName() + "线程运行结束"+getSystemTime());
    }
   
/**
    *
业务逻辑处理
    * */
   
public void doSomething(Jedis jedis)
    {
        String v =  jedis.get(
"goodsprice");
        Integer iv = Integer.valueOf(v);
       
//条件都给过
       
if(bidPrice > iv){

            Integer bp = iv +
100;
           
//出价成功,事务未提交
           
jedis.set("goodsprice",String.valueOf(bp));
            System.
out.println("子线程"+this.auctionCode +", 出价:"+ jedis.get("goodsprice") +",出价时间:" +getSystemTime());
        }
else{
            System.
out.println("子线程"+this.auctionCode +"出价低于现有价格!"+getSystemTime());
        }
    }
   
/**
     *
尝试获取分布式锁
     * @param
jedis Redis客户端
     * @param
lockKey
     * @param
requestId 请求标识
     * @param
expireTime 超期时间
     * @return 是否获取成功
     */
   
public boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId,
SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

       
if (LOCK_SUCCESS.equals(result)) {
           
return true;
        }
       
return false;

    }

   
private static final Long RELEASE_SUCCESS = 1L;

   
/**
     *
释放分布式锁
     * @param
jedis Redis客户端
     * @param
lockKey
     * @param
requestId 请求标识
     * @return 是否释放成功
     */
   
public  boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1])else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

       
if (RELEASE_SUCCESS.equals(result)) {
           
return true;
        }
       
return false;

    }
   
public static String getSystemTime()
    {
        Date now =
new Date();
        SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");//可以方便地修改日期格式
       
String times = dateFormat.format( now );
        System.
out.println(times);
       
return  times;
    }
}

 

 

运行结果:

main主线程运行开始!

2019/08/17 18:42:10

子线程A001拿到锁2019/08/17 18:42:10

2019/08/17 18:42:10

用户:A001当前库存=4==抢购成功2019/08/17 18:42:10

2019/08/17 18:42:10

子线程A001释放锁2019/08/17 18:42:10

2019/08/17 18:42:10

A001线程运行结束2019/08/17 18:42:10

2019/08/17 18:42:14

子线程D001拿到锁2019/08/17 18:42:14

2019/08/17 18:42:14

用户:D001当前库存=3==抢购成功2019/08/17 18:42:14

2019/08/17 18:42:14

子线程D001释放锁2019/08/17 18:42:14

2019/08/17 18:42:14

D001线程运行结束2019/08/17 18:42:14

2019/08/17 18:42:16

子线程C001拿到锁2019/08/17 18:42:16

2019/08/17 18:42:16

用户:C001当前库存=2==抢购成功2019/08/17 18:42:16

2019/08/17 18:42:16

子线程C001释放锁2019/08/17 18:42:16

2019/08/17 18:42:16

C001线程运行结束2019/08/17 18:42:16

2019/08/17 18:42:20

子线程B001拿到锁2019/08/17 18:42:20

2019/08/17 18:42:20

用户:B001当前库存=1==抢购成功2019/08/17 18:42:20

2019/08/17 18:42:20

子线程B001释放锁2019/08/17 18:42:20

2019/08/17 18:42:20

B001线程运行结束2019/08/17 18:42:20

2019/08/17 18:42:24

子线程E001拿到锁2019/08/17 18:42:24

2019/08/17 18:42:24

用户:E001库存已经耗尽,抢购失败2019/08/17 18:42:24

2019/08/17 18:42:24

子线程E001释放锁2019/08/17 18:42:24

2019/08/17 18:42:24

E001线程运行结束2019/08/17 18:42:24

2019/08/17 18:42:24

main主线程运行结束!2019/08/17 18:42:24

 

猜你喜欢

转载自blog.csdn.net/jason_jiahongfei/article/details/99934658