[Curator] Distributed Priority Queue 的使用与分析

Distributed Priority Queue

分布式优先级队列

使用zk来实现一个分布式优先级队列。

注意,不建议使用ZK来做队列使用,原因可以参见 : 《Distributed Queue 的使用与分析》

1. 关键 API

org.apache.curator.framework.recipes.queue.QueueBuilder

org.apache.curator.framework.recipes.queue.QueueConsumer

org.apache.curator.framework.recipes.queue.QueueSerializer

org.apache.curator.framework.recipes.queue.DistributedPriorityQueue

2. 机制说明

其实ZK队列的最核心是 《Distributed Queue》。 优先级队列内部其实也是委托一个Distributed Queue来操作的,只是对Distributed Queue的一些扩展方法进行了定制。

3. 用法

3.1 创建

和Distributed Queue的方式一样,也主要是通过QueueBuilder来创建:

public static <T> QueueBuilder<T> builder(CuratorFramework client,
                                          QueueConsumer<T> consumer,
                                          QueueSerializer<T> serializer,
                                          java.lang.String queuePath)
QueueBuilder<MessageType> builder = QueueBuilder.builder(client, consumer, serializer, path);

// 通过builder的方法进行一个参数设定

DistributedPriorityQueue<MessageType> queue = builder.buildPriorityQueue(minItemsBeforeRefresh);

与Distributed Queue的创建过程的差别就是,最后调用的是public DistributedPriorityQueue<T> buildPriorityQueue(int minItemsBeforeRefresh)工厂方法。

优先级队列主要就是再队列元素的添加/移除时,会停止处理(投递)元素,而先进行一次队列同步刷新操作(对队列元素进行重排序)。

而参数minItemsBeforeRefresh就是控制这个过程的。这个参数,指的是在可用队列,刷新队列之前,可以处理多少个元素。(数量越大,调度给消费者的消息吞吐越高(从本地cache中取),越小,则元素信息同步更频繁,同步开销越大,但实时性也更高)

由于,zk在节点变化时的通知机制问题,每一个元素被处理后,队列都会得到一个元素添加/移除的通知。这就会导致性能低下。通过设置minItemsBeforeRefresh,来控制应用允许多少个消息处理可以不执行同步操作。

例如:如果队列要处理10个消息,它会去调用10次zk来检查状态(同步数据)。可以设置minItemsBeforeRefresh为10,那队列就会处理完10个消息之后,再去检查zk状态(同步数据)。

3.2 使用

老规矩,使用之前需要调用start(),使用完成后,需要调用close()

消息放入队列,则需要指定优先级:

queue.put(aMessage, priority);

消息的消费,还是使用:QueueConsumer.consumeMessage()

3.3 安全消费

参见:《Distributed Queue 的使用与分析》的相关内容。

3.4 数据格式

参见:《Distributed Queue 的使用与分析》的相关内容。

4. 错误处理

参见:《Distributed Queue 的使用与分析》的相关内容。

5. 源码分析

5.1 类图

image

可以看出:

  • DistributedPriorityQueue实现了QueueBase接口
  • 内部委托给了DistributedQueue操作

5.2 类定义

public class DistributedPriorityQueue<T> implements Closeable, QueueBase<T>{}
  • 实现了java.io.Closeable接口
  • 实现了org.apache.curator.framework.recipes.queue.QueueBase接口

5.3 成员变量

public class DistributedPriorityQueue<T> implements Closeable, QueueBase<T>
{
    private final DistributedQueue<T>      queue;
}
  • 只有一个org.apache.curator.framework.recipes.queue.DistributedQueue

5.4 构造器

DistributedPriorityQueue
        (
            CuratorFramework client,
            QueueConsumer<T> consumer,
            QueueSerializer<T> serializer,
            String queuePath,
            ThreadFactory threadFactory,
            Executor executor,
            int minItemsBeforeRefresh,
            String lockPath,
            int maxItems,
            boolean putInBackground,
            int finalFlushMs
        )
    {
        Preconditions.checkArgument(minItemsBeforeRefresh >= 0, "minItemsBeforeRefresh cannot be negative");

        queue = new DistributedQueue<T>
        (
            client,
            consumer, 
            serializer,
            queuePath,
            threadFactory,
            executor,
            minItemsBeforeRefresh,
            true,
            lockPath,
            maxItems,
            putInBackground,
            finalFlushMs
        );
    }
  • 除了进行minItemsBeforeRefresh的校验以外
  • 就是对DistributedQueue的初始化
    • 注意使用refreshOnWatch了模式
      • 配合minItemsBeforeRefresh一起控制队列同步数据

5.5 启动

再来看看启动时的start()方法:

public void     start() throws Exception
{
    queue.start();
}
  • 很干脆,直接委托给了org.apache.curator.framework.recipes.queue.DistributedQueue

参见:《Distributed Queue 的使用与分析》的相关内容。

5.6 关闭

@Override
public void close() throws IOException
{
    queue.close();
}
  • 同样,直接委托给了org.apache.curator.framework.recipes.queue.DistributedQueue

参见:《Distributed Queue 的使用与分析》的相关内容。

5.7 产生消息

DistributedPriorityQueue在DistributedQueue的基础上增加了几个put方法。 其实基本上就是在DistributedQueue的put基础上,增加优先级参数。

public void put(T item, int priority) throws Exception
{
    put(item, priority, 0, null);
}

public boolean put(T item, int priority, int maxWait, TimeUnit unit) throws Exception
{
    queue.checkState();

    String      priorityHex = priorityToString(priority);
    return queue.internalPut(item, null, queue.makeItemPath() + priorityHex, maxWait, unit);
}

这里最好,再对比着DistributedQueue的方法一起来看:

org.apache.curator.framework.recipes.queue.DistributedQueue#put(T, int, java.util.concurrent.TimeUnit)

public boolean     put(T item, int maxWait, TimeUnit unit) throws Exception
{
    checkState();

    String      path = makeItemPath();
    return internalPut(item, null, path, maxWait, unit);
}

对比一下,看看DistributedPriorityQueue有哪些不一样的地方:

  • 主要的差别就是对于元素节点的zk path的取值上

这里也就能发现,优先级是靠着元素节点名来进行排序。 在《Distributed Queue 的使用与分析》中,介绍过。拉取zk队列元素后,本地cache会对队列进行一次排序。(org.apache.curator.framework.recipes.queue.DistributedQueue#sortChildren

而这里的优先级队列,则是在添加元素时,对元素的名,进行控制。

5.7.1 优先级

按照org.apache.curator.framework.recipes.queue.DistributedQueue#makeItemPath的逻辑,path最终会是path/queue-的形式。 而DistributedPriorityQueue会在path/queue-之后再加上一个表示优先级的字符串来生成最终的path。

这个优先级字符串是由org.apache.curator.framework.recipes.queue.DistributedPriorityQueue#priorityToString方法实现的:

static String priorityToString(int priority)
{
    long        l = (long)priority & 0xFFFFFFFFL;
    return String.format("%s%08X", (priority >= 0) ? "1" : "0", l);
}
  • 这个方法是静态方法

分析一下逻辑:

  1. 取优先级int的后32位
    • 为避免int的符号位的影响,操作时转成了long
  2. 将32位数值转成16进制大写字符
  3. 在字符串前加上一个“1/0”
    • 1:表示正数
    • 0:表示负数
    • 这也避免了32位转16进制时,因符号位问题,导致负数的优先级会优于正数优先级的情况

所以,最终这个优先级字符串会是9个字符组成的。

6. 小结

通过以上的分析,DistributedPriorityQueue以一个委托的方式,具有了一个DistributedQueue的队列逻辑。

其最大区别,就在于元素节点的path命名上

  • 通过在命名上带上优先级信息
  • 利用DistributedQueue本地缓存调度时,会对元素节点进行string排序的特性,轻松的实现了一个分布式优先级队列

转载于:https://my.oschina.net/roccn/blog/1217794

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值