redis lua 设置过期_基于redis的分布式锁

摘要

当在分布式模型下,数据只有一份,此时需要利用锁的技术控制某一时刻修改数据的进程数。与单机模式下的锁不同,分布式锁不仅仅需要保证不同进程访问对象有锁,还需要保证不同主机的不同系统访问该对象时有锁。所以通常我们会为需要枷锁的对象添加状态,分布式系统中锁的状态通常存储在外部公共存储中,例如redis、zookeeper、文件系统甚至数据库中。

26ec7bdac7b84a2beb9b664608c675b8.png

本文将基于redis实现分布式锁。

加锁

加锁其实就是向redis添加key-value。为了避免死锁,需要设置过期时间。

NX 代表只在键不存在时,才对键进行设置操作

SET lock_key lock_value NX PX 5000

如果上面的命令执行成功,则证明客户端获取到了锁。

解锁

解锁其实就是删除redis中添加的key。但也不能乱删,不能客户端1的请求将客户端2的锁给删除掉。需要根据lock_value过滤。需要注意的是为了保证redis操作的原子性,需要通过lua命令删除。

13cd239fd7cbaa84bcc31ce19b1b6a24.png
static {    StringBuilder sb = new StringBuilder();    sb.append("if redis.call('get', KEYS[1]) == ARGV[1] then " );    sb.append(" return redis.call('expire',KEYS[1],ARGV[2]) ");    sb.append(" else ");    sb.append("return 0 ");    sb.append(" end ");    lua_expire=sb.toString();}jedis.eval(lua_del, 1, lockKey, lockValue);

过期时间

为了避免死锁设置了过期时间,同时又要保证过期时间不能低于代码执行时间。所以单独添加一个线程刷新过期时间。同时为了保证redis操作原子性,通过lua脚本执行。

/** * 开启定时刷新 */protected void scheduleExpirationRenewal(){    Thread renewalThread = new Thread(new ExpirationRenewal());    renewalThread.start();}private class ExpirationRenewal implements Runnable{    @Override    public void run() {        while (isOpenExpirationRenewal){            try{                System.out.println("[key="+lockKey+"]延迟失效时间");                jedis.eval(lua_expire,1, lockKey, lockValue, String.valueOf(EXPIRE_TIME));                //休眠10秒                sleepBySecond(EXPIRE_TIME-1);            }catch (Exception ex){                ex.printStackTrace();            }        }    }}

使用方式

16f61e3bea9429e68566061259dd038e.png
RedisLock lock = new RedisLock("testRedisLock");lock.lock();//模拟业务执行15秒System.out.println("执行方法:"+id);lock.sleepBySecond(15);lock.unlock();

代码

public class RedisLock implements Lock {    private static final long EXPIRE_TIME=100;    private static final String NOT_EXIST="NX";    private static final String SECOND="EX";    private static final String OK="OK";    protected volatile boolean isOpenExpirationRenewal = true;    private Jedis jedis=null;    private String lockKey="";    private String lockValue="";    private static String lua_del= "";    private static String lua_expire= "";    static {        StringBuilder sb = new StringBuilder();        sb.append("if redis.call('get', KEYS[1]) == ARGV[1] then ");        sb.append(" return redis.call('del', KEYS[1]) ");        sb.append(" else ");        sb.append("return 0 ");        sb.append(" end ");        lua_del=sb.toString();    }    static {        StringBuilder sb = new StringBuilder();        sb.append("if redis.call('get', KEYS[1]) == ARGV[1] then " );        sb.append(" return redis.call('expire',KEYS[1],ARGV[2]) ");        sb.append(" else ");        sb.append("return 0 ");        sb.append(" end ");        lua_expire=sb.toString();    }    /**     * 获取redis连接,随机生成value     * @param lockKey     */    public RedisLock(String lockKey){        this.lockKey=lockKey;        this.lockValue= UUID.randomUUID().toString()+Thread.currentThread().getId();        JedisPool pool=SpringContextUtil.getBean(JedisPool.class);        this.jedis= pool.getResource();    }    /**     * 加锁     */    @Override    public void lock() {        while(true){            String result = jedis.set(lockKey, lockValue, NOT_EXIST,SECOND,EXPIRE_TIME);            if(OK.equals(result)){                System.out.println("[key="+lockKey+"]已加锁");                //添加单独线程刷新过期时间                isOpenExpirationRenewal = true;                scheduleExpirationRenewal();                break;            }        }    }    /**     * 解锁     */    @Override    public void unlock() {        jedis.eval(lua_del, 1, lockKey, lockValue);        System.out.println("[key="+lockKey+"]已解锁");        isOpenExpirationRenewal = false;    }    @Override    public void lockInterruptibly(){}    @Override    public Condition newCondition() {        return null;    }    @Override    public boolean tryLock() {        return false;    }    @Override    public boolean tryLock(long time, TimeUnit unit){        return false;    }    /**     * 线程休眠     * @param second     */    public void sleepBySecond(long second){        try {            Thread.sleep(second*1000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    /**     * 开启定时刷新     */    protected void scheduleExpirationRenewal(){        Thread renewalThread = new Thread(new ExpirationRenewal());        renewalThread.start();    }    private class ExpirationRenewal implements Runnable{        @Override        public void run() {            while (isOpenExpirationRenewal){                try{                    System.out.println("[key="+lockKey+"]延迟失效时间");                    jedis.eval(lua_expire,1, lockKey, lockValue, String.valueOf(EXPIRE_TIME));                    //休眠10秒                    sleepBySecond(EXPIRE_TIME-1);                }catch (Exception ex){                    ex.printStackTrace();                }            }        }    }}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值