9

Redisson的使用

 3 years ago
source link: https://segmentfault.com/a/1190000038546465
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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%;" />

参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK