PaddleHub EasyCVR animation cocoa webview Seajs vue表单 android实战项目 java三维数组 python程序界面 查看oracle连接数 mysql查看锁表 oracle数据库版本 html好看的字体 本地安装mysql python的array python安装 python中文教程 java实例 java的string java数组添加 linuxtar命令 python网站开发实例 allowoverride 找茬辅助 js转int 电视免费软件 JScodeblocks汉化包 工程地质手册 4k对齐是什么意思 坐标反算 android计算器 CST软件 刷机精灵下载 paths cad怎么缩小界面 虚幻5引擎 系统激活软件 聚生网管远程管理工具 bat代码大全
当前位置: 首页 > 学习教程  > 编程语言

redis分布式锁事务解决方案

2020/7/24 11:34:35 文章标签: 测试文章如有侵权请发送至邮箱809451989@qq.com投诉后文章立即删除

通常我们使用redisTemplate 的setIfAbsent()方法进行加锁,在到使用expire()方法进行设置超时时间的是时候,两个操作时使用两个链接不在一个事务中,当存在客户端setIfAbsent()加锁成功后服务中断,expire()无法进行超时设置,导致死锁的情况。

针对以上情况可以采取两种解决方案:

方案一:

将锁的超时间放在锁(key)的值(里面),及redis客户端在获取锁的时将客户端获取锁的时间加上超时时间形成失效时间点形成一个key-value的数据存在redis中,当客户端再次获取锁时候先判断锁的key是否存在,当存在时判断value的值和客户端的时间进行比对,看锁是否过期。

缺点:锁的过期机制受客户端时间限制,当存在加锁成功后,客户端时间发生大时间后移的请求,锁便存在长时间的死锁问题

方案二:

由于在redisTemplate 低版本的api中加锁和超时设置是两步存在死锁的问题(redisTemplate 2.1.*版本可以setIfAbsent方法可以设置锁同时设置时间),而redisTemplate 的api操作其实也是封装redis的指令,并发送给服务端进行执行一系列操作,因此我们可以直接通过redisTemplate 发送指令进行加锁操作解决事务问题,通过发送lua脚本给服务端执行业务操作


import com.alibaba.dubbo.common.utils.Assert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class RedisUtil {
    //    @Qualifier("stringRedisTemplate")
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * redisUtil.setIfAbsent 新加的带有超时的setIfAbsent 脚本
     */
    String newSetIfAbsentScriptStr = " if (redis.call('setnx', KEYS[1], ARGV[2]) == 1) then" +
                                     " redis.call('expire', KEYS[1], ARGV[2])" +
                                     " return 1;" +
                                     " else" +
                                     " return 0;" +
                                     " end;";

    public RedisScript<Boolean> newSetIfAbsentScript = new DefaultRedisScript<Boolean>(newSetIfAbsentScriptStr, Boolean.class);

    /**
     * @Description: setIfAbsent升级版,加了超时时间
     * @Author: Gong Yongwei
     * @Date: 2018/12/12 9:21
     * @param key
     * @param value
     * @param seconds 超时时间,秒为单位
     * @return: boolean
     */
    public boolean setIfAbsent(String key, String value, Long seconds) {


        List<Object> keys = new ArrayList<Object>();
        keys.add(key);
        Object[] args = { value, seconds.toString() };
        return (boolean) redisTemplate.<Boolean> execute(newSetIfAbsentScript, keys, args);
    }

}
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.moredian.rooster.BaseTest;
import com.moredian.rooster.redis.RedisKeyTimeout;
import com.moredian.rooster.redis.RedisUtil;
import org.junit.Test;

@Slf4j
public class RedisTest extends BaseTest {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;

    @Test
    public void test() {
        String key  = "lock_generator_calender_00001";
//        redisTemplate.opsForValue().set(key, key);
        redisTemplate.delete(key);

        Object result = redisUtil.setIfAbsent(key,key,10L);
        System.out.println("加锁结果:"+result);

        System.out.println("锁时间"+redisTemplate.getExpire(key, TimeUnit.MILLISECONDS));

        result = redisUtil.setIfAbsent(key,key,20L);

        System.out.println("加锁结果2:"+result);
        System.out.println("锁时间2:"+redisTemplate.getExpire(key, TimeUnit.MILLISECONDS));

        redisTemplate.delete(key);
    }
}

在这过程中可能出现错误

这个错误大致看起来时数组越界问题,其实时在问题是设置锁的时候,value没有进行序列化的问题,而redisTemplate 默认的序列化方式是jdk的序列化方式,redis的服务端是无法处理的,因此我们需要制定redisTemplate 的value的序列化方式

@Bean
    public RedisTemplate<Object, Object> stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();

        //key都用String 序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        template.setHashKeySerializer(stringSerializer);
        template.setKeySerializer(stringSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

因此结果成功了:

另外网上也有:使用redisTemplate.execute(new RedisCallback<Boolean>() {});的方式,但是会存在,获取锁失败是仍然修改锁超时时间的问题,因此不采取

public boolean setIfAbsent(final String key, final Serializable value, final long exptime) {
        Boolean b = (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
                RedisSerializer keySerializer = redisTemplate.getKeySerializer();
                Object obj = connection.execute("setnx", keySerializer.serialize(key),
                                                valueSerializer.serialize(value),
                                                SafeEncoder.encode("NX"),
                                                SafeEncoder.encode("EX"),
                                                Protocol.toByteArray(exptime));
                return obj != null;
            }
        });
        return b;
    }

 


本文链接: http://www.dtmao.cc/news_show_50432.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?