分布式锁实现之Redis和Redisson

2 篇文章 0 订阅
1 篇文章 0 订阅

分布式锁实现:

  • Redis
  • Redisson
  • Zookeeper

本文讲解Redis和Redisson部分。

synchronized

背景:
redis中已经存储一个K/V: set stock 100
做一个减少库存的模拟。

  1. 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 修改配置文件application.properties:
server.port=8080
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=5000
#最大可用连接数(默认为8,负数表示无限)
spring.redis.jedis.pool.max-active=8
  1. 使用synchronized实现库存减少
public void deduct() {
   synchronized (this){
       int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
       if (stock > 0) {
           int cu = stock - 1;
           redisTemplate.opsForValue().set("stock", cu + "");
           System.out.println("减少库存至:" + cu);

       } else {
           System.out.println("库存为负数了:");
       }
   }
}

问题:synchronized只是在一个JVM中保持一次性只有一个线程能访问成功,但是对于多个Tomcat实例而言(多JVM/多进程),就会产生错误。

使用redis实现分布式锁!!!!

Redis

public void deduct1() {
   String lockName = "product_01";
   String uuid = UUID.randomUUID().toString();
   try {
       //定时 1/3 * timeout,续命 timeout
       Boolean result = redisTemplate.opsForValue().setIfAbsent(lockName, uuid, 10, TimeUnit.SECONDS);//10秒过期 stenx
       if (!result) return;
       int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
       if (stock > 0) {
           int cuStock = stock - 1;
           redisTemplate.opsForValue().set("stock", cuStock + "");
           System.out.println("减少库存至:" + cuStock);
       } else {
           System.out.println("库存为负数了:");
       }
   } finally {
       //误删除别人的锁
       if (uuid.equals(redisTemplate.opsForValue().get(lockName))) {
           redisTemplate.delete(lockName);//释放锁
       }
   }
}

注意:

  1. 释放锁之前需要判断当前操作对象是否是加索对象,以防误删别人的锁。
  2. 还需要一个定时专门去对锁的超时时间进行续命。一般定时时间设置为1/3*timeout。(具体请百度)

Redisson

  1. 引入Redisson依赖:
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson-spring-boot-starter</artifactId>
   <version>3.13.1</version>
</dependency>
  1. 主类中加入:
@Bean
public Redisson redisson() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
    return (Redisson) Redisson.create(config);
}
  1. 调用:
public void deduct2() {
   String lockName = "product_02";
   RLock redissonLock = redisson.getLock(lockName);
   try {
       redissonLock.lock(10, TimeUnit.SECONDS);
       int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
       if (stock > 0) {
           int cu = stock - 1;
           redisTemplate.opsForValue().set("stock", cu + "");
           System.out.println("减少库存至:" + cu);
       } else {
           System.out.println("库存为负数了:");
       }
   } finally {
       redissonLock.unlock();
   }
}

说明:

  1. lock中,底层搭配了LUA脚本使用
if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);
  1. 上面这条脚本在redis中操作是原子的。
  2. 对于主从式Redis来说,同样有问题,如果主Redis已经获取到锁,但是,此时主Redis此时宕机,从Redis则会丢失。此时又重新申请加锁则会成功!!!解决办法:
    3.1 使用Redisson的RedLock 超过半数Redis节点加锁成功才算加锁成功。性能低,其他失败需要回滚成功的case。
    3.2 使用Zookeeper,性能不行 需要同步到slave才会返回结果,所以能保证下一个master会存在这个锁。
    以上2种都不推荐使用。

对比Redis和Redisson使用

两种方式的操作其实是一样的。优先使用Redisson,代码更加简洁。

调用虚拟机/远程Redis

以上代码中127.0.0.1 改为 IP地址即可

远程Redis配置:

>> vi etc/redis.conf

bind 127.0.0.1 192.168.X.XXX
daemonize yes

redis目录下:

>> bin/redis-server etc/redis.conf
>> redis-cli -c -h 192.168.X.XXX -p 6379
>> ps -ef | grep redis
Predis Phpredis Rediska介绍 1 Predis   Predis是一个灵活和特性完备(PHP>5 3)的支持Redis的PHP客户端 当前版本为0 6 3 默认不支持PHP5 2 主要特性如下: 完整的支持从1 2到2 4的Redis 并且支持当前正在开发的版本; 提供客户端实现的一致性哈希算法 支持自定义; 在单个或聚合连接中支持命令管道;(Command pipelining on single and aggregated connections) 能够通过TCP IP或者Unix domain sockets连接到redis 支持持久连接; 自动连接Redis实例 使用“懒惰”方式 只在第一个命令发出时执行连接; 可以灵活定义客户端的命令集合; 2 Phpredis(推荐使用)   这是一个二进制版本的PHP客户端 按照的说法 效率要比Predis高 这个版本支持作为Session的Handler 这个扩展的有点在于无需加载 任何外部文件 使用比较方便 缺点在于难于扩展 一般的PHP程序员无法对其做出扩展 考虑到Redis正在飞速发展过程中 缺乏扩展的特性还是有些影响 的 需要维护过程中注意进行升级更新 调用Redis的相关方法 Redis:: construct构造函数$redis new Redis ; 1 基本相关操作 connect open 链接redis服务 参数host: string 服务地址 port: int 端口号 timeout: float 链接时长 可选 默认为 0 不限链接时间 注: 在redis conf中也有时间 默认为300 pconnect popen 不会主动关闭的链接 参考上面 setOption 设置redis模式 getOption 查看redis设置的模式 ping 查看连接状态 get 得到某个key的值(string值) 如果该key不存在 return false set 写入key 和 value(string值) 如果写入成功 return ture">Predis Phpredis Rediska介绍 1 Predis   Predis是一个灵活和特性完备(PHP>5 3)的支持Redis的PHP客户端 当前版本为0 6 3 默认不支持PHP5 2 主要特性如下: 完整的支持从1 2到2 4的Redis 并且支持当前正在开发的版本; 提供客户 [更多]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值