分佈式鎖之redis分佈式鎖

 

 

分佈式應用中,需要如何解決資源同步的問題?
這篇文章講解的很不錯了。Redis 分佈式鎖的正確實現方式( Java 版 )。另外在Github上也有一個demo做的很不錯。

什麼時候需要用到分佈式鎖呢?比如:減庫存。

假設某個商品的庫存是N,有N+1個訂單過來,那麼勢必只能有N個有效訂單。那麼如何保證庫存不出現負數的情況呢?(多個減庫存操作保證庫存的同步)

減庫存流程圖

                                                                              圖一:減庫存流程圖 

分佈式減庫操作

                                                                                   圖二:分佈式鎖減庫操作 

 利用Redis就可以給庫存加一個分佈式鎖。需要注意的是不要造成死鎖,即保證持有鎖有一定的時限制。

獲取鎖的過程可以是:在redis服務器set一個key-value,lock。如果此lock已經存在,則說明當前鎖被其他線程持有。否則set此lock的值。lock需要有設置存活時間(保證不死鎖)。另外需要注意手動釋放鎖的時候,需要保證是當前持有鎖的線程去釋放。

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Collections;


@RestController
@RequestMapping
public class RedisTemplateController {

    @Resource(name = "myRedisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping(value = "set")
    public String set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
        return "success";
    }

    @GetMapping(value = "get")
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 加鎖
     * @param key
     * @param value
     * @return
     */
    @GetMapping(value = "redisTemplateLock")
    public Object lock(String key, String value) {
        String script = "local key = KEYS[1]; local value = ARGV[1]; if redis.call('set', key, value, 'NX' ,'PX', 5000) then return 1 else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Object execute = redisTemplate.execute(redisScript, Collections.singletonList(key), Collections.singletonList(value));
        System.out.println(execute);
        return execute;
    }

    /**
     * 阻塞鎖
     */
    @GetMapping(value = "blockLock")
    public String blockLock(String key, String value) throws InterruptedException {
        // 被阻塞的時間超過5秒就停止獲取鎖
        int blockTime = 5000;
        // 默認的間隔時間
        int defaultTime = 1000;
        for(;;) {
            if(blockTime >= 0) {
                String script = "local key = KEYS[1]; local value = ARGV[1]; if redis.call('set', key, value, 'NX' ,'PX', 5000) then return 1 else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, long.class);
                Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value);
                System.out.println("try lock ... ,result: "+result);
                if(result != null && result == 1) {
                    // 得到了鎖
                    return "lock success";
                } else {
                    blockTime -= defaultTime;
                    Thread.sleep(1000);
                }
            } else {
                // 已經超時
                return "lock timeout..., please retry later...";
            }
        }
    }

    /**
     * 解鎖
     * @param key
     * @param value
     */
    @GetMapping("redisTemplateUnlock")
    public String unlock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long execute = redisTemplate.execute(redisScript, Collections.singletonList(key), value);
        System.out.println("unlock result: "+execute);
        if(execute != null && execute != 0) {
            // 解鎖成功
            return "unlock success";
        } else {
            return "unlock failed";
        }
    }
}

另外延伸出一個問題:如何保證一個60秒的延遲。Thread.sleep(60000)是否可以保證延遲60s。答案是不行。解決辦法:

public void sleep(long millis) {
        while (millis > 0) {
            long begin = System.currentTimeMillis();
            try {
                Thread.sleep(millis);
            } catch (Exception e) {
            }
            millis -= System.currentTimeMillis() - begin;
        }
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章