1

Redisson的使用

简介

在现在的项目中,经常会有并发问题,解决并发问题的一个思路就是使用分布式锁。在以前的项目中,经常会使用Redis的setnx特性来实现分布式锁,但是有可能会带来死锁的问题,那么就可以使用Redisson来实现Redis的分布式锁。这里我使用的场景是短信验证码服务,同一时刻只能有一个线程给同一个手机号发送短信。

原生的使用方式

在不使用redisson时,我们一般这样去使用Redis实现分布式锁。

@Component
public class RedissonLockImpl implements RedissonLock {

    private static ThreadLocal<String>  threadLocal = new ThreadLocal<>();
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public boolean tryLock(String key, long timeout, TimeUnit timeUnit) {

        if (threadLocal.get() == null){
            String lockName = "redis:test:name";
            threadLocal.set(lockName);
            return redisTemplate.opsForValue().setIfAbsent(key, lockName, timeout, timeUnit);
        }else if (threadLocal.get().equals(redisTemplate.opsForValue().get(key))){
            return true;
        }
        return false;
    }
}

然后在代码中这样使用:

@RestController
public class RedissonController {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonLock redissonLock;

    @RequestMapping("/submitOrder")
    public String submitOrder(){

        String key = "test";
        // 这里可能存在在你设置的时间内当前线程还没有结束
        boolean lock = redissonLock.tryLock(key, 4, TimeUnit.SECONDS);
        if (!lock){
            return "error";
        }

        try {

            // 具体的业务逻辑
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock>0){
                // 下单
                stock = stock - 1;
                redisTemplate.opsForValue().set("stock", stock+"");
                return "success";

            } else {
                System.out.println("库存不足");
                return "false";
            }
        } finally {
            // 这里可以释放锁
        }

    }
}

这样可能就会导致在你设置的锁的时间结束后,该线程还没有执行结束,但是锁已经释放,就会导致后面获取锁,释放锁的顺序变乱,导致业务出问题。

原生改进

对于这种某个线程超时的问题,可以给这个线程new一个守护线程,守护线程每10s去刷新这个时间完成续命。主线程运行结束,守护线程就会自己死亡,不需要我们操作。(但是一般线程都是线程池,线程不会死亡,守护线程也就不能自动死亡,所以有风险)。

image-20201213224259493

当然你可以手动实现一个线程专门维护这个续命的时间,但是实现起来就会很麻烦。所以我们推荐直接使用redisson。redisson本身已经实现了这里的续命。

Redisson的具体使用

Redisson中的Rlock等已经实现了续命,保证我们的业务执行结束。

引入依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.4</version>
</dependency>

编写配置类

@Configuration
public class RedissonConfig {


    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        //添加主从配置
//        config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }
}

在代码中使用

这样我们可以自动注入RedissonClient进行使用。但是一般我们可以在上面再包装一层service,来包装我们一般的异常等场景。

/**
 * redisson操作类
 * @author 朱友德
 */
@Service("redissonService")
public class RedissonService {

    @Autowired
    private RedissonClient redissonClient;

    public void getRedissonClient() throws IOException {
        Config config = redissonClient.getConfig();
        System.out.println(config.toJSON().toString());
    }

    /**`
     * 获取字符串对象
     *
     * @param objectName
     * @return
     */
    public <T> RBucket<T> getRBucket(String objectName) {
        RBucket<T> bucket = redissonClient.getBucket(objectName);
        return bucket;
    }

    /**
     * 获取Map对象
     *
     * @param objectName
     * @return
     */
    public <K, V> RMap<K, V> getRMap(String objectName) {
        RMap<K, V> map = redissonClient.getMap(objectName);
        return map;
    }

    /**
     * 获取有序集合
     *
     * @param objectName
     * @return
     */
    public <V> RSortedSet<V> getRSortedSet(String objectName) {
        RSortedSet<V> sortedSet = redissonClient.getSortedSet(objectName);
        return sortedSet;
    }

    /**
     * 获取集合
     *
     * @param objectName
     * @return
     */
    public <V> RSet<V> getRSet(String objectName) {
        RSet<V> rSet = redissonClient.getSet(objectName);
        return rSet;
    }

    /**
     * 获取列表
     *
     * @param objectName
     * @return
     */
    public <V> RList<V> getRList(String objectName) {
        RList<V> rList = redissonClient.getList(objectName);
        return rList;
    }

    /**
     * 获取队列
     *
     * @param objectName
     * @return
     */
    public <V> RQueue<V> getRQueue(String objectName) {
        RQueue<V> rQueue = redissonClient.getQueue(objectName);
        return rQueue;
    }

    /**
     * 获取双端队列
     *
     * @param objectName
     * @return
     */
    public <V> RDeque<V> getRDeque(String objectName) {
        RDeque<V> rDeque = redissonClient.getDeque(objectName);
        return rDeque;
    }


    /**
     * 获取锁
     * @param objectName
     * @return
     */
    public RLock getRLock(String objectName) {
        RLock rLock = redissonClient.getLock(objectName);
        return rLock;
    }

    public Boolean tryLock(String key, long leaseTime, TimeUnit unit) {
        RLock rLock = redissonClient.getLock(key);
        boolean tryLock = false;
        try {
            tryLock = rLock.tryLock(0, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
        return tryLock;
    }

    public Boolean verifyTryLock(RLock rLock, long leaseTime, TimeUnit unit) {
        boolean tryLock = false;
        try {
            tryLock = rLock.tryLock(0, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
        return tryLock;
    }

    /**
     * 获取读取锁
     *
     * @param objectName
     * @return
     */
    public RReadWriteLock getRWLock(String objectName) {
        RReadWriteLock rwlock = redissonClient.getReadWriteLock(objectName);
        return rwlock;
    }

    /**
     * 获取原子数
     *
     * @param objectName
     * @return
     */
    public RAtomicLong getRAtomicLong(String objectName) {
        RAtomicLong rAtomicLong = redissonClient.getAtomicLong(objectName);
        return rAtomicLong;
    }

    /**
     * 获取记数锁
     *
     * @param objectName
     * @return
     */
    public RCountDownLatch getRCountDownLatch(String objectName) {
        RCountDownLatch rCountDownLatch = redissonClient.getCountDownLatch(objectName);
        return rCountDownLatch;
    }

具体业务场景使用

@Service
public class MyService {

    @Autowired
    private RedissonService redissonService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public String sendCodeByTemplate(final String phone){
       
        // 过期时间
        String ttlKey = "redis:ttl:phone:"+phone;
        String key = "redis:phone:"+phone;
        // 对这个手机号发送消息时会对发送手机号加锁
        if (!redissonService.tryLock(key, 10, TimeUnit.SECONDS)){
            //获取过期时间
            Long ttl = redisTemplate.getExpire(ttlKey);
            //发送验证码频繁
            return "发送频繁";
        }
        
        // 发送验证码
        sendCode(phone);
        return "true";
    }
    
    private boolean sendCode(String phone){
        // 写具体发送逻辑
        return true;
    }
}

<img src="https://gitee.com/jsnucrh/blog-sharding_1/raw/master/img/20201213230304.png" alt="image-20201213230304098" style="zoom:150%;" />

参考

参考


njitzyd
58 声望7 粉丝