Apache Curator简单使用(二)

来源:互联网 发布:北京的设计公司知乎 编辑:程序博客网 时间:2024/05/28 19:25
转载自:http://www.chengxuyuans.com/Java+/72042.html

              http://ifeve.com/zookeeper-sharedcount/

分布式队列Queue

       分布式队列的基本特性,就是"生产者"或"消费者"跨越多个进程,且在此种环境中需要确保队列的push/poll的有序性。zookeeper本身并没有提供分布式队列的实现,只是recipse根据zookeeper的watcher和具有version标记的node,来间接的实现分布式queue。
       内部机制如下:
       如果是消费者(QueueConsumer),会创建一个类似于PathChildrenCache的实例用于监听queuePath下的子节点变更事件(单独的线程中).同时consumer处于阻塞状态,当有子节点变更事件时会被唤醒(包括创建子节点/删除子节点等);此时consumer获取子节点列表,并将每个节点信息封装成Runnable任务单元,提交到线程池中,Runnable中执行QueueConsumer.consumer()方法.
       如果是生产者,则发布一个message时recipes将会在queuePath下创建一个PERSISTENT_SEQUENTIAL节点,同时保存message数据。消费时,也将按照节点的顺序进行。发布消息并没有太多的问题仅仅是创建一个"有序"节点即可。但是对于消费者,那么需要考虑的因数就很多,比如:多个消费者同时消费时,需要确保消息不能重复且有序;消息消费时,如果网络异常,怎么办?
       对于QistributedQueue中,对上述问题的解决办法也非常粗糙,内部机制如下:
       如果使用了消费担保(即指定了lockPath),在调用consumer方法之前,首先创建一个临时节点(lockPath + 子节点),如果创建此临时节点失败也就意味着此消息被其他消费者,则忽略此消息。然后从子节点中获取数据,如果获取失败,意味着此节点已经被其他消费者删除,则忽略此消息。然后调用consumer()方法,如果此方法抛出异常,消息将会再次添加到队列中(删除旧的子节点,创建一个新的子节点)。如果消费正常,则删除节点。无论成败,则删除临时节点(lockPath + 子节点)。
       如果没有使用消费担保,则首先获取子节点的数据(getData),然后立即删除此子节点,调用consumer()方法。
       需要明确使用zookeeper作为分布式队列的场景: 1)队列深度较小;2)生产者和消费者的速度都非常的低且消费者消费速度更快,即单位时间内产生的消息很少;3)建议只有一个消费者。

       DistributedQueue是最普通的一种队列。 它设计以下四个类:QueueBuilder、QueueConsumer、QueueSerializer、DistributedQueue。
import org.apache.curator.framework.recipes.queue.QueueSerializer;import java.nio.charset.Charset;public class StringQueueSerializer implements QueueSerializer<String> {    private static final Charset charset = Charset.forName("utf-8");    //as producer    @Override    public byte[] serialize(String item) {        return item.getBytes(charset);    }    //as consumer    @Override    public String deserialize(byte[] bytes) {        return new String(bytes, charset);    }}
import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.recipes.queue.QueueConsumer;import org.apache.curator.framework.state.ConnectionState;import static org.apache.curator.framework.state.ConnectionState.RECONNECTED;public class DistributedQueueConsumer implements QueueConsumer<String> {    @Override    public void consumeMessage(String message) throws Exception {        System.out.println("Consumer:" + message);    }    @Override    public void stateChanged(CuratorFramework client, ConnectionState newState) {        switch (newState) {            case RECONNECTED: //当链接重建之后                try {                    System.out.println(RECONNECTED);                } catch (Exception e) {                }                break;            default:                System.out.println(newState.toString());        }    }}
import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.recipes.queue.DistributedQueue;import org.apache.curator.framework.recipes.queue.QueueBuilder;import org.apache.curator.framework.recipes.queue.QueueConsumer;import org.apache.curator.utils.CloseableUtils;public class DistributedQueueProducer {    private static CuratorFramework client;    private static String queuePath;    public static void main(String[] args) {        QueueConsumer<String> consumer = new DistributedQueueConsumer();        DistributedQueue<String> queue = QueueBuilder.builder(client,                consumer,                new StringQueueSerializer(),                queuePath)                .lockPath("queue-lock")//消费担保                //.maxItems(1024);// 有界队列,最大队列深度,如果深度达到此值,将阻塞"生产者"创建新的节点.                .buildQueue();        try {            queue.start();            queue.put("test");            Thread.sleep((long) (3 * Math.random()));        } catch (Exception e) {        } finally {            CloseableUtils.closeQuietly(queue);            CloseableUtils.closeQuietly(client);        }    }}
       Recipse还提供了其他API(http://ifeve.com/zookeeper%EF%BC%8Dcurator/):
       1.DistributedIdQueue: 内部基于DistributedQueue的所有机制,只是除了指定queue中消息的内容之外,还可以指定一个ID,这个ID作为消息的标记,最终此ID值将作为znode的path后缀.此后可以通过ID去消费(dequeue)一个消息.队列的排序方式是根据ID的字典顺序--正序;
       2.DistributedProrityQueue: 有权重的队列,对队列中的元素按照优先级进行排序,在发布消息时,需要指定此消息的权重数字; priority越小,元素越靠前,越先被消费掉。
       3. DistributedDelayQueue:JDK中也有DelayQueue,DistributedDelayQueue也提供了类似的功能,元素有个delay值, 消费者隔一段时间才能收到元素。
       在DistributedQueue的开发中,必须在QueueConsumer中关注"链接失效"的事件.

计数器Counter

       利用ZooKeeper可以实现一个集群共享的计数器。 只要使用相同的path就可以得到最新的计数器值, 这是由ZooKeeper的一致性保证的。Curator有两个计数器,一个是用int来计数,一个用long来计数。
       SharedCount使用int类型来计数。 主要涉及三个类。SharedCount、SharedCountReader、SharedCountListener。计数器改变时此Listener可以监听到改变的事件,而SharedCountReader可以读取到最新的值,包括字面值和带版本信息的值VersionedValue。
import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.recipes.shared.SharedCount;import org.apache.curator.framework.recipes.shared.SharedCountListener;import org.apache.curator.framework.recipes.shared.SharedCountReader;import org.apache.curator.framework.state.ConnectionState;public class SharedCountTest {    private static CuratorFramework client;    private static String countPath;    public static void main(String[] args) throws Exception {        final SharedCount baseCount = new SharedCount(client, countPath, 0);        baseCount.addListener(new SharedCountListener() {            @Override            public void countHasChanged(SharedCountReader sharedCount, int newCount) throws Exception {                System.out.println("count changed:" + newCount);            }            @Override            public void stateChanged(CuratorFramework client, ConnectionState newState) {                switch (newState) {                    case RECONNECTED: //当链接重建之后,需要手动fresh                        try {                            Integer current = baseCount.getCount();                            //reflush,无论更新成败,都会获取最新的值                            baseCount.trySetCount(baseCount.getVersionedValue(), current);                        } catch (Exception e) {                        }                        break;                    default:                        System.out.println(newState.toString());                }            }        });        //test,任意的SharedCount,只要使用相同的path,都可以得到计数值        final SharedCount count = new SharedCount(client, countPath, 0);        count.start();        count.trySetCount(count.getVersionedValue(), count.getCount() + 5);        //trySetCount尝试设置计数器,setCount是强制更新计数器的值        count.close();        baseCount.start();        //counter.close();//取消watcher    }}
       在"计数器"中,还提供了DistributedAtomicInteger,DistributedAtomicLong两个分布式自增计数器。
0 0
原创粉丝点击