日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1. 為什么需要鎖

在并發場景下,多個進程/線程同時對同一個資源進行訪問時,會產生沖突。
舉個例子:核酸采樣時,如果一次100個人同時要求大白進行采樣(并發),那么大白就要崩潰了,所以必須要控制一個大白一次只能對一個人采樣,其他人等待采樣完成,這就是對大白進行”加鎖”。

2. 鎖是用來解決什么問題的

鎖是用來解決并發問題的,如:

  • 多個線程并發訪問同一個資源
  • 分布式系統中不同模塊對同一資源進行修改

鎖的使用場景:

  • 秒殺
  • 搶紅包
  • 庫存更新

3. 分布式鎖的解決方案

  • 通過數據庫實現(利用數據庫唯一約束的特性實現)
  • 通過Zookeeper實現(利用zookeeper的唯一節點特性或者有序臨時節點特性獲得最小節點作為鎖)
  • 通過redis實現(setNx命令)

4 通過Redis實現分布式鎖示例

通過JAVA程序連接Redis,提示以下錯誤:

redis.clients.jedis.exceptions.JedisConnectionException: Failed to connect to any host resolved for DNS name.
    at redis.clients.jedis.DefaultJedisSocketFactory.connectToFirstSuccessfulHost(DefaultJedisSocketFactory.java:63)
    at redis.clients.jedis.DefaultJedisSocketFactory.createSocket(DefaultJedisSocketFactory.java:87)
    at redis.clients.jedis.Connection.connect(Connection.java:180)
    at redis.clients.jedis.Connection.initializeFromClientConfig(Connection.java:338)

可能有如下原因:

  • Redis未啟動
  • Redis IP地址或者端口不對
  • Redis不允許遠程連接

4.1 配置允許遠程連接Redis

4.1.1 開放Redis端口(6379)

//查看6379端口狀態 mo表示未開放
[root@192 bin]# firewall-cmd --zone=public --query-port=6379/tcp 
no
//配置放行6379端口
[root@192 bin]# firewall-cmd --zone=public --add-port=6379/tcp --permanent
success
//防火墻重載
[root@192 bin]#  firewall-cmd --reload
success
//再次查看端口狀態
[root@192 bin]# firewall-cmd --zone=public --query-port=6379/tcp
yes

4.1.2 修改redis.conf配置文件

[root@192 redis]# vim redis.conf

將配置改成如下所示:

# 允許任何主機連接、訪問
bind 0.0.0.0
# 關閉保護模式
protected-mode no
# 允許啟動后在后臺運行,即關閉命令行窗口后仍能運行
daemonize yes

4.2 模擬秒殺下單減庫存的場景

4.2.1 新建以下商品表(t_goods)與訂單表(t_order)

DROP TABLE IF EXISTS `t_goods`;
CREATE TABLE `t_goods` (
  `id` int(11) NOT NULL,
  `name` varchar(60) DEFAULT NULL,
  `qty` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--初始化數量qty=20
INSERT INTO `t_goods` VALUES ('1', '華為nova7', '20');
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `oid` varchar(120) NOT NULL,
  `createtime` datetime DEFAULT NULL,
  `goodname` varchar(255) DEFAULT NULL,
  `user` varchar(120) DEFAULT NULL,
  PRIMARY KEY (`oid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4.2.2 編寫main方法,同時啟動10000條線程模擬秒殺

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

        //設置秒殺開始時間
        String startTime="2022-4-27 23:24:00";
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date start=simpleDateFormat.parse(startTime);
        System.out.println("等待中...");
        boolean isStart=false;
        while (!isStart) {
            if (start.compareTo(new Date()) < 0) {
                isStart=true;
                System.out.println("秒殺開始...");
                //同時啟動10000條線程
                CountDownLatch countDownLatch = new CountDownLatch(10000);
                for (int i = 0; i < 10000; i++) {
                    new Thread(() -> {
                        try {
                            countDownLatch.await();
                            //secKillGoodsByRedisLock();//Redis鎖
                            secKillGoods();  //未加鎖
                           // secKillGoodsByLock();//synchronized

                        } catch (InterruptedException | SQLException e) {
                            e.printStackTrace();
                        }
                    }).start();
                    countDownLatch.countDown();
                }
            }
        }
    }

4.2.3 未加鎖的情況

private static void secKillGoods() throws SQLException {
    List list = DButil.query("select id,name,qty from t_goods where id=1 and qty>0");
    //判斷是否還有庫存
    if(list.size()>0){
        String id=JedisUtil.getId();
        Object[] insertObject={id,new Date(),"nova7",Thread.currentThread().getName()};
        DButil.excuteDML("update t_goods set qty=qty-1 where id=1 ");
        DButil.excuteDML("insert into t_order values(?,?,?,?);",insertObject);
        System.out.println(Thread.currentThread().getName()+">>>搶到了."+id);
    }
}

以上代碼每次執行都會先判斷是否還有庫存,如果有庫存則秒殺成功,否則秒殺失敗,沒有并發的情況下是可以正常運行的,但是一旦存在并發,則會出現負庫存(超賣)

分布式鎖的實現方式

 

4.2.4 通過synchronized關鍵字對代碼塊加鎖

private static void secKillGoodsByLock() throws SQLException {
    synchronized (lockObj) {
        secKillGoods();
    }
}

以上程序在進入秒殺方法時,都會通過synchronized關鍵字加鎖,再次運行程序,我們發現不會出現負庫存了

分布式鎖的實現方式

 

但是如果在多進程或者分布式環境中,synchronized關鍵字會失效,讓我們再啟動一個進程,兩個進程同時啟動100000個線程進行秒殺(ps:同時啟動十萬個線程差點讓我的電腦沒緩過來…),終于出現了負庫存的現象

分布式鎖的實現方式

 

4.2.5 Redis鎖

private static void secKillGoodsByRedisLock(){
        String id="1";
        String key="lock"+id;
        JedisUtil jedisUtil=new JedisUtil();
        String lockId=jedisUtil.getLock(key,5);

        if(null!=lockId){
            try{
                List list= DButil.query("select id,name,qty from t_goods where id=1 and qty>0");
                if(list!=null && list.size()>0){
                    Object[] insertObject={lockId,new Date(),"nova7",Thread.currentThread().getName()};
                    DButil.excuteDML("update t_goods set qty=qty-1 where id=1 ");
                    DButil.excuteDML("insert into t_order values(?,?,?,?);",insertObject);
                    System.out.println(Thread.currentThread().getName()+">>>搶到了."+lockId);
                    jedisUtil.unLock(key,lockId);

                }else {
                    System.out.println("搶完了");
                }
            } catch (SQLException e) {
                jedisUtil.unLock(key,lockId);
                e.printStackTrace();
            }
        }


    }

以上代碼只有在獲取到Redis鎖成功后,才會去執行扣庫存和下單的邏輯,重復和上一步一樣,兩個進程同時啟動100000個線程進行秒殺,看看結果

分布式鎖的實現方式

 


分布式鎖的實現方式

 

以上結果沒有出現負庫存的現象,顯然是扛住了“秒殺”,getLock 的實現如下所示,其原理就是利用Redis setnx的原子性操作來控制并發,以下示例還設置了鎖失效的時間,避免死鎖。當然還有許多的問題需要在實際應用場景中考慮,如在鎖失效時間到了,秒殺動作未完成如何處理,Redis服務器崩潰了怎么辦等等。


    public String getLock(String key,int timeout){
        Jedis jedis=null;
        try {
            jedis=getJedis();
            String value=getId();
            long end=System.currentTimeMillis()+timeout;
            while (System.currentTimeMillis()<end) {
                //設置value成功,獲取鎖
                if(jedis.setnx(key,value)==1){
                    //設置失效時間
                    jedis.expire(key,timeout);
                    System.out.println(Thread.currentThread().getName()+">>>>>獲取鎖成功。");
                    return value;
                }
                //當 key 存在但沒有設置剩余生存時間時
                if(jedis.ttl(key)==-1){
                    //設置失效時間
                    jedis.expire(key,timeout);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(null!=jedis){
                jedis.close();
            }
        }
        return null;
    }

參考文獻:
為什么需要鎖,鎖分類,鎖粒度

各種鎖以及使用場景

分享到:
標簽:分布式
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定