分布式锁之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;
        }
    }

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值