美文网首页
Java-分布式框架-redis-4

Java-分布式框架-redis-4

作者: 蓝色_笔记本 | 来源:发表于2021-07-16 11:47 被阅读0次

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. 增加了锁续命的功能。
image.png
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子集群内。

相关文章

  • Java-分布式框架-redis-4

    redis分布式锁redisson 分布式框架中,普通锁是满足不了业务需求的,分布式锁在分布式框架中不可缺失;比如...

  • JAVA-Mock测试框架简记-2017-2-7 18:38:3

    JAVA-常用Mock测试框架 EasyMock 早期比较流行的MocK测试框架 mockito EasyMock...

  • Java-分布式框架-rocketmq

    一、消息中间件对比 kafkaRocketMQRabbitMQ定位设计定位系统间的数据流管道,实时数据处理。例如常...

  • Java-分布式框架-ShardingSphere

    一、基本介绍 ShardingSphere定位为关系型数据库中间件 功能列表 功能列表数据分片分布式事务数据库治理...

  • Java-分布式框架-zookeeper

    一、产生背景 项目从单体到分布式转变之后,将会产生多个节点之间协同的问题。如: 1.每天的定时任务由谁哪个节点来执...

  • Java-分布式框架-Kafka

    一、概述 Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(r...

  • Java-分布式框架-redis-3

    一、Redis哨兵集群弊端 redis3.0之前比较可靠的集群就是哨兵集群,主从节点,主节点拥有写与读的权限,从节...

  • Java-分布式框架-redis-5

    一、概述 一个高并发的框架中,往往会设置多层缓存,比如Nginx应用层的缓存、web层JVM中的缓存、redis集...

  • Java-分布式框架-Dubbo-1

    一、分布式架构的发展历史与背景 分布式系统(distributed system)是建立在网络之上的软件系统。正是...

  • Java-分布式框架-redis-1

    Redis核心原理 Redis的单线程和高性能 Redis 单线程为什么还能这么快? 因为它所有的数据都在内存中,...

网友评论

      本文标题:Java-分布式框架-redis-4

      本文链接:https://www.haomeiwen.com/subject/wggppltx.html