转载自:http://www.chengxuyuans.com/Java+/72042.html
内部机制如下:
如果是消费者(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。
1.DistributedIdQueue: 内部基于DistributedQueue的所有机制,只是除了指定queue中消息的内容之外,还可以指定一个ID,这个ID作为消息的标记,最终此ID值将作为znode的path后缀.此后可以通过ID去消费(dequeue)一个消息.队列的排序方式是根据ID的字典顺序--正序;
2.DistributedProrityQueue: 有权重的队列,对队列中的元素按照优先级进行排序,在发布消息时,需要指定此消息的权重数字; priority越小,元素越靠前,越先被消费掉。
3. DistributedDelayQueue:JDK中也有DelayQueue,DistributedDelayQueue也提供了类似的功能,元素有个delay值, 消费者隔一段时间才能收到元素。
在DistributedQueue的开发中,必须在QueueConsumer中关注"链接失效"的事件.
SharedCount使用int类型来计数。 主要涉及三个类。SharedCount、SharedCountReader、SharedCountListener。计数器改变时此Listener可以监听到改变的事件,而SharedCountReader可以读取到最新的值,包括字面值和带版本信息的值VersionedValue。
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两个分布式自增计数器。