Redis实现分布式锁及雪崩、穿透问题解决方法

Fileutils工具类需要引入的依赖

    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.3</version>
    </dependency>

这里使用的是jedis,先贴下配置文件

@Configuration
public class RedisConfig {
    //读取配置文件中的redis的ip地址
    @Value("${spring.redis.host:0}")
    private String host;

    @Value("${spring.redis.port:0}")
    private int port;

    @Value("${spring.redis.database:0}")
    private int database;

    @Bean
    public RedisUtil getRedisUtil() {
        if (host.equals("disabled")) {
            return null;
        }
        RedisUtil redisUtil = new RedisUtil();
        redisUtil.initPool(host, port, database);
        return redisUtil;
    }
}
/*
RedisConfig负责在spring容器启动时自动注入,而RedisUtil就是被注入的工具类以供其他模块调用
 */
public class RedisUtil {
    private JedisPool jedisPool;
    
    public void initPool(String host, int port, int database) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(200);
        poolConfig.setMaxIdle(30);
        poolConfig.setBlockWhenExhausted(true);
        poolConfig.setMaxWaitMillis(10 * 1000);
        poolConfig.setTestOnBorrow(true);
        jedisPool = new JedisPool(poolConfig, host, port, 20 * 1000);
    }

    public Jedis getJedis() {
        Jedis jedis = jedisPool.getResource();
        return jedis;
    }
}

这里是properties配置文件

# redis链接地址
spring.redis.host=192.169.61.131
# redis端口号
spring.redis.port=6379
# redis数据库
spring.redis.database=0

lua脚本

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

业务代码

 		 //链接缓存
        Jedis jedis = redisUtil.getJedis();
        //查询缓存
        String skuKey = "sku:" + skuId + ":info";
        String skuJson = jedis.get(skuKey);

        //如果缓存中有  if(skuJson!=null&&!skuJson.equals(""))
        if (StringUtils.isNotBlank(skuJson)) {
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        } else {
        	//如果缓存中没有
            //设置分布式锁
            String token = UUID.randomUUID().toString();
            //锁有10秒的过期时间
            String OK = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10 * 1000);
            if (StringUtils.isNotBlank(OK) && "OK".equals(OK)) {
                //设置成功,有权在10秒的过期时间内访问数据库
                //如果缓存中没有,查询mysql
                pmsSkuInfo = getSkuByIdFromDb(skuId);
                if (pmsSkuInfo != null) {
                    //mysql查询结果存入redis
                    jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo));
                } else {
                    //数据库中不存在该sku
                    //为了防止缓存穿透,null或空字符串值设置给redis
                    jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString(""));
                }
                //在访问mysql后,将分布锁释放
                String lockToken = jedis.get("sku:" + skuId + ":lock");
                if (StringUtils.isNotBlank(lockToken) && lockToken.equals(token)) {
                    //可用lua脚本,在查询到key的同时删除该key,防止高并发下的意外发生
                    String script = Fileutils.readFileByLines("把lua脚本写在一个txt文件里,然后这里写lua脚本的路径");
					//eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
					jedis.eval(script, Arrays.asList(lockToken), Arrays.asList(token));
            } else {
                //设置失败,自旋(该线程在睡眠几秒后,重新尝试访问)
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return getSkuById(skuId, ip);
            }
        }
        //关闭Jedis连接
        jedis.close();

小知识:
为什么lua脚本就是原子性?

因为redis是单进程单线程模型,执行lua脚本的时候,不会有其它线程来插手

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值