關於接口冪等性與鎖的學習筆記

如何保證接口冪等性:
1,查詢與刪除操作是天然冪等操作。
2,新增時,根據某個字段做唯一性判斷,比如某個用戶只會有一條記錄這樣的類似判斷。(實用性差)
3,悲觀鎖,select from where 主鍵=’’ for update
4,樂觀鎖,update user set money = money + 1 where id=2 and money = 1;在字段自加一的時候,加上該字段的等值索引
3與4,如果沒有加主鍵id做索引,會鎖表,會影響該表其他功能。

【上訴兩種不好用,併發會使程序報錯,如同時要買掉一件商品,一件沒扣成功則進程失去反應】

5,分佈式鎖:使用Redis、Memcached、Zookeeper、Chubby等第三方程序

Redis:
①set(key, value, “NX”,“EX”,internalLockLeaseTime):使用set設置鍵值,以“id_後綴”等規範作爲key,以當前線程id作爲value。插入一條記錄,並設置鎖超時時間30秒。
如果插入成功,返回1,以此聲明成功獲得鎖,其他進程插入返回0說明該key已存在,獲取鎖失敗。成功則進入插入或者更新操作。
②釋放鎖,爲了避免誤刪其他線程的鎖,刪除時增加判斷if(threadId .equals(redisClient.get(key))){ del(key) }刪除鍵值對,del(id_後綴)
③守護線程,同線程只要不崩潰,會一直爲其增加有效期,如果崩了,則不會增加,redis到時間自動釋放

實操:
一個商品多人同時購買,同時觸發buyCommodity()方法,兩個進程併發,此時,上鎖對象爲商品(判斷清楚對象很重要,涉及到key的設計,處理併發),即,key值爲商品id+"_buy_lock",此時,A線程比B線程提前得到鎖(提前set好了),那麼B線程set失敗,進入循環獲取,持續時間內沒有獲取到,則視爲失敗。

本人封裝一個redis鎖工具,並展示測試類:

package com.fjhb;

import redis.clients.jedis.Jedis;

/**
 * redis鎖
 * Author:FangKunSen
 * Time:2020-05-29,15:35
 */
public class JLock {
    private String HOST;
    Jedis jedis;
    protected long internalLockLeaseTime = 30;//鎖過期時間
    private long timeout = 20000; //獲取鎖的超時時間
    private long SLEEP = 2000;//休息2秒重新獲取

    public JLock(String HOST) {
        this.HOST = HOST;
        jedis = new Jedis(HOST);
    }

    /**
     * 上鎖
     * @param key 建議以上鎖對象id爲標識,加以識別後綴,例如 123456_commodityId_lock
     * @param value 以線程id爲值,防止解鎖時解到其他線程的鎖
     * @return
     */
    public boolean lock(String key,String value){
        Long start = System.currentTimeMillis();
        try{
            for(;;){
                //SET命令返回OK ,則證明獲取鎖成功
                String lock = jedis.set(key, value, "NX","EX",internalLockLeaseTime);
                if("OK".equals(lock)){
                    return true;
                }
                System.out.println("未取到鎖,正在重新取鎖...");
                //否則循環等待,在timeout時間內仍未獲取到鎖,則獲取失敗
                long l = System.currentTimeMillis() - start;
                if (l>=timeout) {
                    System.out.println("規定時間內未取到鎖,失敗");
                    return false;
                }
                try {
                    Thread.sleep(SLEEP);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }finally {
            jedis.close();
        }
    }

    /**
     * 獲取鎖值,校驗用,目前值爲進程id
     * @param key 建議以上鎖對象id爲標識,加以識別後綴,例如 123456_commodityId_lock
     * @return
     */
    public String get(String key){
        return jedis.get(key);
    }

    /**
     * 解指定線程id的鎖
     * @param key 建議以上鎖對象id爲標識,加以識別後綴,例如 123456_commodityId_lock
     * @param value 以線程id爲值,防止解鎖時解到其他線程的鎖
     * @return
     */
    public boolean unLock(String key, String value){
        String threadId=get(key);
        if(threadId.equals(value)){
            jedis.del(key);
            System.out.println("解鎖成功,下個繼續");
            return true;
        }
        System.out.println("解鎖失敗");
        return false;
    }
}

	@Test
    public void test() {
        JLock jLock = new JLock("127.0.0.1");
        String key="10101010_commodityId";
        String threadId = String.valueOf(Thread.currentThread().getId());
        if(jLock.lock(key,threadId)){
            System.out.println("取鎖成功,執行代碼,線程id:"+threadId);
            for(int i = 0;i<10;i++){
                System.out.println(i+1);
            }
            System.out.println("代碼執行完畢,正在關閉鎖");
            jLock.unLock(key,threadId);
        }else{
            System.out.println("超時未取到");
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章