当前位置: 首页>后端>正文

Java-分布式框架-redis-4

redis分布式锁redisson

分布式框架中,普通锁是满足不了业务需求的,分布式锁在分布式框架中不可缺失;比如互联网秒杀、抢优惠券、接口幂等性校验。redis中存在redisson工具包专门处理redis在分布式锁的应用

java中redisson的实现

<!--添加依赖-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>
@Bean
 public Redisson redisson() {
    // 此为单机模式
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.0.60:6379").setDatabase(0);
   // 此为集群模式
    /*config.useClusterServers()
                .addNodeAddress("redis://192.168.0.61:8001")
                .addNodeAddress("redis://192.168.0.62:8002")
                .addNodeAddress("redis://192.168.0.63:8003")
                .addNodeAddress("redis://192.168.0.61:8004")
                .addNodeAddress("redis://192.168.0.62:8005")
                .addNodeAddress("redis://192.168.0.63:8006");*/
  //还有哨兵模式
    return (Redisson) Redisson.create(config);
}
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/deduct_stock")
public String deductStock() throws InterruptedException {
    String lockKey = "product_001";
    RLock redissonLock = redisson.getLock(lockKey);
    try {
        // 加锁,实现锁续命功能
        redissonLock.lock();
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
        if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
            System.out.println("扣减成功,剩余库存:" + realStock + "");
        } else {
            System.out.println("扣减失败,库存不足");
        }
    }finally {
        redissonLock.unlock();
    }
    return "end";
}
  1. 使用了jedis.setnx方法加锁,只允许设置一次;删除对应的key进行解锁。
  2. 每个线程设置的key对应的value值具有唯一性,最后解锁删除value值判断是否为当前加锁的线程,防止了因为当前线程处理时间过长redis本身删除解锁后其它线程进来而导致当前线程解了其他线程的锁,保证了自己加锁自己解锁。
  3. 增加了锁续命的功能。
Java-分布式框架-redis-4,第1张
Redis Lua脚本

Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:

  1. 减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
  2. 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过 redis的批量操作命令(类似mset)是原子的。
  3. 替代redis的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代。

从Redis2.6.0版本开始,通过内置的Lua解释器,可以使用EVAL命令对Lua脚本进行求值。EVAL命令的格式如下:

EVAL script numkeys key [key ...] arg [arg ...] 

script:lua脚本。
numkeys:key的数量。
key:key值,这里为list。
arg:value值,这里为list,与key相对应。

redis中使用lua脚本

//******* lua脚本示例 ********
//模拟一个商品减库存的原子操作
//lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10
jedis.set("product_stock_10016", "15");  //初始化商品10016的库存
String script = " local count = redis.call('get', KEYS[1]) " +
                    " local a = tonumber(count) " +
                    " local b = tonumber(ARGV[1]) " +
                    " if a >= b then " +
                    "   redis.call('set', KEYS[1], count-b) " +
                    //模拟语法报错回滚操作"   bb == 0 " +
                    "   return 1 " +
                    " end " +
                    " return 0 ";
Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));
System.out.println(obj);

注意1:多线程在执行lua脚本的时候,是不存在并发问题的,原因是所有的命令在redis中都会以单线程的形式执行。
注意2:key与value传参都是以list的型传入,同时下标从1开始而不是0。

lua脚本缺点

redis在高并发执行指令都是串行化,单线程的,如果lua脚本业务逻辑比较复杂执行时间比较长,这会影响redis的性能;如果lua脚本中出现死循环的现象,执行该脚本的redis集群基本上瘫痪了。

注意1:redisson加的锁为可重入锁,前提是同一个线程。
注意2:redisson在加锁后会存在主节点重新选举的情况,这期间可能会导致锁数据丢失,从而出现锁失效的现象。

redis RedLock

为解决主节点重新选举的情况,这期间可能会导致锁数据丢失问题,引入RedLock可解决问题。但是RedLock性能比较差,而且有存在Bug。不推荐使用,建议容忍该问题,若不能容忍,推荐使用zookeeper。

@RequestMapping("/redlock")
public String redlock() throws InterruptedException {
    String lockKey = "product_001";
    //这里需要自己实例化不同redis实例的redisson客户端连接,这里只是伪代码用一个redisson客户端简化了
    RLock lock1 = redisson.getLock(lockKey);
    RLock lock2 = redisson.getLock(lockKey);
    RLock lock3 = redisson.getLock(lockKey);

    /**
     * 根据多个 RLock 对象构建 RedissonRedLock (最核心的差别就在这里)
     */
    RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
    try {
        /**
         * 4.尝试获取锁
         * waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
         * leaseTime   锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
         */
        boolean res = redLock.tryLock(10, 30, TimeUnit.SECONDS);
        if (res) {
            //成功获得锁,在这里处理业务
        }
    } catch (Exception e) {
        throw new RuntimeException("lock fail");
    } finally {
        //无论如何, 最后都要解锁
        redLock.unlock();
    }
    return "end";
}
高并发场景分布式锁性能提升

把资源库存分成多份,别分存储在不同的集群内,把锁分段分发。比如一个商品库存数量1000个,把库存分为3份,应用key的hash算法把3份库存分发的不同的redis子集群内。


https://www.xamrdz.com/backend/3kc1923356.html

相关文章: