Redisson 分布式锁练习

Redisson 分布式锁

基于redis实现分布式锁基本逻辑图<待补充>
实现多个实例竞争执行任务,最多只有一个实例获得锁进行工作。
期间模拟随机出现错误,抛出异常,检验 demo 的合理性。

执行脚本

cd D:\src\mytest\demo-schedule\target

start java -jar demo-schedule-0.0.1-SNAPSHOT.jar --application.hostname=host1 --server.port=8081 --logging.root.level=info
start java -jar demo-schedule-0.0.1-SNAPSHOT.jar --application.hostname=host2 --server.port=8082 --logging.root.level=info
start java -jar demo-schedule-0.0.1-SNAPSHOT.jar --application.hostname=host3 --server.port=8083 --logging.root.level=info

三个实例运行截图
在这里插入图片描述

代码说明

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@Configuration
@ConfigurationProperties("application")
public class SomeWorker2 {
	// 用于区分当前服务的名称
    @Value("${application.hostname}")
    public String hostname;

	// 用于测试的锁持续时间
    @Value("${application.lockDuration}")
    public long lockDuration;

    @Autowired
    private String lockKey="demoschedule-lock";
    private String globalIndexKey="globalIndexKey";


    @Resource
    private RedissonClient redissonClient;

    public void task() throws InterruptedException {//throws Exception
        boolean workFlag=true;
        boolean getLock=false;
        long index=0;
        Random r = new Random();

        // 初始化
        RAtomicLong globalIndex = redissonClient.getAtomicLong(globalIndexKey);
        globalIndex.compareAndSet(0,1);

        RLock rLock = redissonClient.getLock(lockKey);
        while (workFlag) {
            // 重置,避免影响日志(用了上次的index显示日志)
            index=-1;
            try {
                // 每轮竞争需要等待一下,不能持续抢着
                Thread.sleep(r.nextInt(500)+100);

                // 加锁,说明:Duration 设置为 -1,由 redisson 负责锁续期
                getLock=rLock.tryLock(100, -1, TimeUnit.MICROSECONDS);
                if (getLock) {
                    // 清除 printWork的行,避免日志粘一起
                    System.out.println();
                    
					// 全局任务加1
                    index = globalIndex.getAndIncrement();
                    
                    // 模拟工作
                    mockWork(rLock , index);

                    // 清除 printWork的行,避免日志粘一起
                    System.out.println();
                }
                else{
                    log.debug(String.format("%s 获取处理权: 竞争失败", hostname));
                    System.out.print(".");
                }
            }
            catch(Exception ex){
                // 清除 printWork的行,避免日志粘一起
                System.out.println();

                log.warn(String.format("%s:[%d] Exception-业务异常 [%s]:%s",
                        hostname, index,
                        rLock.isLocked()?"lock":"--",
                        ex.getMessage()));
                //ex.printStackTrace();
            }
            finally {
                // 释放锁
                try {
                    log.debug(String.format("%s:[%d] finally : getLock=%s, isLock=%s, isHeldByCurrentThread=%s",
                            hostname, index,
                            getLock?"true":"false",
                            rLock.isLocked()?"true":"false",
                            rLock.isHeldByCurrentThread()?"true":"false" ));
                    // 备注:如果 mockWork 抛出异常,rLock.isLocked 将为 false
                    // 如果 mockWork 执行了 tryLock(),rLock.isHeldByCurrentThread 将为 false
                    // 解锁时,需要检查一下,不能把别人的 锁给解了
                    if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                        log.info(String.format("%s:[%d] 释放锁", hostname, index));
                        rLock.unlock();
                    }else {
                    // 上面判断不能解锁,但是有又获得锁的标记,那么可能是发生错误了
                        if (getLock) {
                        	log.info(String.format("%s:[%d] 不执行解锁??", hostname, index));
                        	}
                    }
                } catch(Exception ex){
                    log.warn(String.format("%s:[%d] Exception-释放锁异常,可能出现超卖 ********************\n%s",
                            hostname, index, ex.getMessage()));
                    //ex.printStackTrace();
                }
            }
        }
    }

    private int mockWork(RLock rLock, long index) throws InterruptedException {
        Random r = new Random();
        int workTime = r.nextInt((int)lockDuration);// 模拟工作时间
        log.info(String.format("%s:[%d] 获取处理权: (workTime=%d)",
                hostname, index, workTime));

		// 不要一次 sleep 很长时间,那会失去控制权,所以我们分片处理
        for (int i=0; i<workTime/100; i++) {
            Thread.sleep(100);
			// 打印工作状态,让我们看到改任务在跑
            printWork(i);
            // 模拟错误
            if (r.nextInt(1000) % 999 == 0){
                Thread.sleep(100);
                throw new RuntimeException ("模拟错误发生");
            }
        }
        return workTime;
    }

    private void printWork(int i) {
        switch (i%4){
            case 0:System.out.print("\r-");break;
            case 1:System.out.print("\r\\");break;
            case 2:System.out.print("\r|");break;
            case 3:System.out.print("\r/");break;
        }
    }
}

代码下载:阿里云仓库
https://code.aliyun.com/greenery2/redissonDemo.git

遇到的问题

redisson 配置错误1:提示密码错误

redisson.yml

# 单节点配置
singleServerConfig:  
  address: "redis://10.1.1.1:6789"
  password: 
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisson' defined in class path resource [org/redisson/spring/starter/RedissonAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'redisson' threw exception; nested exception is org.redisson.client.RedisConnectionException: Unable to connect to Redis server: /10.1.1.1:6789
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'redisson' threw exception; nested exception is org.redisson.client.RedisConnectionException: Unable to connect to Redis server: /10.1.1.1:6789
Caused by: org.redisson.client.RedisConnectionException: Unable to connect to Redis server: /10.1.1.1:6789
Caused by: org.redisson.client.RedisException: ERR Client sent AUTH, but no password is set. channel: [id: 0x534e4e24, L:/10.1.62.19:61460 - R:/10.1.1.1:6789] command: (AUTH), params: (password masked)

日志提示密码错误,但是我的redis没设置密码啊?
这时候应该把 password 字段注释掉就好了

redisson 配置错误2:配置项错误

redisson.yml

# 单节点配置
singleServerConfig:  
  address: "redis://10.1.1.1:6789"
  # 配置看门狗的默认超时时间为30s,这里改为 10s
  lockWatchdogTimeout: 10000
Caused by: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'singleServerConfig': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (String)"singleServerConfig:
  address: "redis://10.252.51.13:31379"
  database: 1
  password:
  # 配置看门狗的默认超时时间为30s,这里改为 10s
  lockWatchdogTimeout: 10000
"; line: 1, column: 19]

日志提示 json 格式错误?
lockWatchdogTimeout 字段应该和 singleServerConfig 平级的,空格缩进不正确导致,正确如下

# 单节点配置
singleServerConfig:  
  address: "redis://10.1.1.1:6789"
# 配置看门狗的默认超时时间为30s,这里改为 10s
lockWatchdogTimeout: 10000
释放锁失败
attempt to unlock lock, not locked by current thread by node id: ff268641-8280-4ec9-a692-00c443d87184 thread-id: 1
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: ff268641-8280-4ec9-a692-00c443d87184 thread-id: 1
main(){
  ...
  getLock=rLock.tryLock(100, 10000, TimeUnit.MICROSECONDS);
  if (getLock) {
     mockWork(rLock , index);
     rLock.unLock();
  }
}

private int mockWork(RLock rLock, long index) {
  ...
 rLock.tryLock(100, 10000, TimeUnit.MICROSECONDS);
}

由于 mockWork 函数重复获取锁,导致外面这层释放失败。
如果要对锁续期,可以在获取锁时,参数 持续时间 设置为 -1,由 redisson 负责自动续期。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值