分布式锁背景介绍

在开发中,多线程场景,即多个线程共同去竞争同一个资源,我们为了避免出现线程不安全,常常采用Lock去锁住需要被线程共享的资源,常用的锁比如Synchronized和Reentrantlock。在分布式的场景中,多台机器上的程序,需要实现Lock锁这个功能。

Java redis 查询剩余时间_Java redis 查询剩余时间


常见的有下面解决方案,一是使用Redis的setnx,二是使用Zookeeper的持久节点下为每个客户端访问时创建临时顺序节点。直接使用在Java代码中使用Zookeeper不太方便。所以在开发中选择了使用Redis的方案。

业务场景介绍

在进行开发虚拟货币交易所时,由于不像股票交易,一支股票在哪个交易所上市,就只能在此上市交易所交易,显示价格等,如在上交所上市,就只需要调用上交所API,不会出现去深交所调用。而虚拟币不一样,虚拟币可以在全球各大交易所都有交易。所以在开发虚拟货币交易的时候,获取价格需要通过定时任务,调用多个交易所的API。而很多交易所,对API调用频率会进行限制,在分布式服务中,会产生多个节点同时访问,导致超过对方交易所的调用频率,从而被对方交易所封禁。所以我们需要运用Redis分布式锁,保证同一时刻,只有一个节点进行访问请求。代码如下

ThreadPoolTaskExecutor singleThreadExecutor = new ThreadPoolTaskExecutor();

    public static final int INTERVAL = 30;

    @PostConstruct
    public void circleGetPrice() {
        singleThreadExecutor = ThreadExecutorUtil.newSingleThreadExecutor("ZBPriceTaskThread-");
        singleThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                log.info("ZBPriceTask circleGetPrice running...");
                while (true) {
                    try {
                        long setnx = CodisConnector.setnx(ZBConstans.GET_PRICE_TASK_NAME, INTERVAL, ZBConstans.GET_PRICE_TASK_NAME);
                        if (setnx == 1) {
                            getXlmPrice();
                        }

                    } catch (Exception e) {
                        log.error("ZBPriceTask circleGetPrice error", e);
                    }
                    try {
                        Thread.sleep(INTERVAL * 1000L);
                    } catch (InterruptedException e) {
                    }
                }
            }
        });
    }

这里用了,我们团队自己对jedis进行封装的工具类,底层其实也是jedis。这里进行重载了两个方法。

public static long setnx(String key, String value) {
        Jedis jedis = pool.getResource();
        try {
            return jedis.setnx(key, value);
        } catch (Exception e) {
            logger.error(e);
        } finally {
            jedis.close();
        }
        return -1L;
    }

    public static long setnx(String key, int expire, String value) {
        Jedis jedis = pool.getResource();
        try {

            if ("OK".equals(jedis.set(key, value, "NX", "EX", expire))) {
                return 1;
            }
            return 0;

        } catch (Exception e) {
            logger.error("codis异常:", e);
        } finally {
            jedis.close();
        }
        return -1L;
    }

解释

setnx(key, value)

如setnx(key, 1)当一个节点执行setnx返回value为1,则成功获得锁,执行代码完成后,再调用del方法。若返回!=1,则当前有其他节点执有锁。继续等待。
但这有一个致命的缺陷,若当前执有锁的节点,在执行代码时,死掉了,而没有来得及释放了锁。则其他所有节点,都会拿不到锁。造成死锁等待。所以redis又提供了另外一种方法。

set(key, value,expire,NX)

通过设置expire过期时间,当持有锁的节点,超时的时候,锁自动释放,从而避免死锁的现象。当然在此场景中,只有访问对方交易所价格API出现超时,这一种情况,所以对于原子性,没有太大的要求。