一层一层实现高并发扣库存的问题

package jedis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

/**
 * @author mawt
 * @description
 * @date 2020/8/7
 */
public class TestJedis {

    private static Jedis jedis;

    private static final String KEY = "store001";

    static {
        jedis = new Jedis("192.168.1.99");

        jedis.set(KEY, "10");
    }

    public static void main(String[] args) {

    }

    private void deductStock() {
        System.out.println("最简单的减库存代码:存在并发安全问题");
        String left = jedis.get(KEY);
        if (left != null && Integer.parseInt(left) > 0) {
            int realStock = Integer.parseInt(left) - 1;
            jedis.set(KEY, String.valueOf(realStock));
            System.out.println("减库存成功,剩余库存为:" + realStock);
            return;
        }
        System.out.println("减库存失败,库存不足");
    }

    private void deductStock2() {
        System.out.println("减库存代码升级版2:使用重量级锁synchronized(jvm级别的锁),性能低下,针对不同物品可能需要不同的锁this,分布式环境下jvm锁失效");
        synchronized (this) {
            String left = jedis.get(KEY);
            if (left != null && Integer.parseInt(left) > 0) {
                int realStock = Integer.parseInt(left) - 1;
                jedis.set(KEY, String.valueOf(realStock));
                System.out.println("减库存成功,剩余库存为:" + realStock);
                return;
            }
            System.out.println("减库存失败,库存不足");
        }
    }

    private void deductStock3() {
        System.out.println("减库存代码升级版3:使用setnx分布式锁,问题:上锁后程序崩溃时没来得及释放锁");
        Long lock = jedis.setnx("lock001", "1");
        if (lock == 1L) { //获取锁成功
            String left = jedis.get(KEY);
            if (left != null && Integer.parseInt(left) > 0) {
                int realStock = Integer.parseInt(left) - 1;
                jedis.set(KEY, String.valueOf(realStock));
                System.out.println("减库存成功,剩余库存为:" + realStock);
                return;
            }
            System.out.println("减库存失败,库存不足");
        }
        jedis.del("lock001");
    }

    private void deductStock4() {
        System.out.println("减库存代码升级版4:使用setnx分布式锁(redis级别的锁),使用finally释放锁,问题:上锁后如果web挂掉来不及释放锁,其他线程永远拿不到锁了");
        try {
            Long lock = jedis.setnx("lock001", "1");
            if (lock == 1L) { //获取锁成功
                String left = jedis.get(KEY);
                if (left != null && Integer.parseInt(left) > 0) {
                    int realStock = Integer.parseInt(left) - 1;
                    jedis.set(KEY, String.valueOf(realStock));
                    System.out.println("减库存成功,剩余库存为:" + realStock);
                    return;
                }
                System.out.println("减库存失败,库存不足");
            }
        } finally {
            jedis.del("lock001");
        }
    }

    private void deductStock5() {
        System.out.println("减库存代码升级版5:设置锁的过期时间,问题:setnx和expire不是原子操作,如果setnx执行成功后程序挂掉了,4问题依然存在");
        try {
            Long lock = jedis.setnx("lock001", "1");
            jedis.expire("lock001", 10);    //如何在对应场景下确定锁的过期时间
            if (lock == 1L) { //获取锁成功
                String left = jedis.get(KEY);
                if (left != null && Integer.parseInt(left) > 0) {
                    int realStock = Integer.parseInt(left) - 1;
                    jedis.set(KEY, String.valueOf(realStock));
                    System.out.println("减库存成功,剩余库存为:" + realStock);
                    return;
                }
                System.out.println("减库存失败,库存不足");
            }
        } finally {
            jedis.del("lock001");
        }
    }

    private void deductStock6() {
        System.out.println("减库存代码升级版6:setnx原子命令:上锁同时给锁设置过期时间,问题:如下:");
        try {
            Long lock = jedis.setnx("lock001", "1");

            //jedis没有方法:jedis.set(keys,args,"NX","PX",30000);
            //可以使用SetParams代替:
            // nx()  if not exists的缩写 当key不存在时才会set
            // ex(10) 设置过期时间10s
            //相当于setnx命令
         //   SetParams p = new SetParams().nx().ex(10);
         //   String lock001 = jedis.set("lock001", "1", p);

            String lock001 = jedis.set("lock001", "1", "NX", "EX", 10);

            //存在的问题描述:
            //1.线程A上锁成功并设置过期时间10秒后,准备执行减库存的代码,如果在这个时候比如有数据库并发高压力大,有可能
            //会发生这样一种情况:减库存代码执行耗时超过10秒,准备释放锁时,因为分布式锁超时过期了,这个时候如果线程B来获取锁,锁过期被redis删除,
            //线程B获取锁成功,尝试执行减库存的操作,但是线程A释放锁,这个锁并不是线程A上的锁,而是B释放的锁,
            //这个时候线程B又会来重复执行线程A的逻辑,线程C又会来重复执行线程B的逻辑......造成锁被其他线程释放的问题
            //大量高并发场景下可能会出现这样的问题,锁机制失效
            if (lock == 1L) { //获取锁成功
                String left = jedis.get(KEY);
                if (left != null && Integer.parseInt(left) > 0) {
                    int realStock = Integer.parseInt(left) - 1;
                    jedis.set(KEY, String.valueOf(realStock));
                    System.out.println("减库存成功,剩余库存为:" + realStock);
                    return;
                }
                System.out.println("减库存失败,库存不足");
            }
        } finally {
            jedis.del("lock001");
        }
    }

    private void deductStock7() {
        System.out.println("减库存代码升级版7:setnx时的value为uuid,只能删除自己上的锁");
        String uuid = UUID.randomUUID().toString();
        try {
            Long lock = jedis.setnx("lock001", "1");

            //jedis没有方法:jedis.set(keys,args,"NX","PX",30000);
            //可以使用SetParams代替:
            // nx()  if not exists的缩写 当key不存在时才会set
            // ex(10) 设置过期时间10s
            //相当于setnx命令
            //   SetParams p = new SetParams().nx().ex(10);
            //   String lock001 = jedis.set("lock001", "1", p);

            String lock001 = jedis.set("lock001", uuid, "NX", "EX", 10);

            //存在的问题描述:
            // 线程A在上锁成功后,如果在执行减库存的时候花费了大量的时间,在此时间内,锁存在失效的可能
            // 如果锁失效了,线程B有可能会重新上锁,属于B的锁,此时锁失效,无法保持库存同步
            if (lock == 1L) { //获取锁成功
                String left = jedis.get(KEY);
                if (left != null && Integer.parseInt(left) > 0) {
                    int realStock = Integer.parseInt(left) - 1;
                    jedis.set(KEY, String.valueOf(realStock));
                    System.out.println("减库存成功,剩余库存为:" + realStock);
                    return;
                }
                System.out.println("减库存失败,库存不足");
            }
        } finally {
            String lock001 = jedis.get("lock001");
            if (uuid.equals(lock001))
                jedis.del("lock001");
        }
    }

    private void deductStock8() {
        System.out.println("减库存代码升级版8:setnx时的value为uuid,只能删除自己上的锁");
        String uuid = UUID.randomUUID().toString();
        try {
            Long lock = jedis.setnx("lock001", "1");

            //jedis没有方法:jedis.set(keys,args,"NX","PX",30000);
            //可以使用SetParams代替:
            // nx()  if not exists的缩写 当key不存在时才会set
            // ex(10) 设置过期时间10s
            //相当于setnx命令
            //   SetParams p = new SetParams().nx().ex(10);
            //   String lock001 = jedis.set("lock001", "1", p);

            String lock001 = jedis.set("lock001", uuid, "NX", "EX", 10);
            
            if (lock == 1L) { //获取锁成功
                new Thread(() -> {
                    new Timer().schedule(new TimerTask() {
                        @Override
                        public void run() {
                            //检查锁是否存在
                            String lock001 = jedis.get("lock001");
                            if (uuid.equals(lock001)) {
                                //锁还没有失效,给锁续命
                                jedis.expire("lock001", 10);
                            } else {
                                //当锁被删除后,停止调度器
                                cancel();
                            }
                        }
                    }, 1, 10 / 3 * 1000);

                }).start();


                String left = jedis.get(KEY);
                if (left != null && Integer.parseInt(left) > 0) {
                    int realStock = Integer.parseInt(left) - 1;
                    jedis.set(KEY, String.valueOf(realStock));
                    System.out.println("减库存成功,剩余库存为:" + realStock);
                    return;
                }
                System.out.println("减库存失败,库存不足");
            }
        } finally {
            String lock001 = jedis.get("lock001");
            if (uuid.equals(lock001))
                jedis.del("lock001");
        }
    }

}

猜你喜欢

转载自blog.csdn.net/qq_33436466/article/details/107868078