Zookeeper和Curator-Framework實踐之:分散式訊息佇列
之前寫過:
本文說的是ZK另一個重要使用場景,訊息佇列!
場景
一個典型的生產消費者模型,如下圖:
WEB點提交要處理的資料,注意是多結點的也就是多個生產者,數量可能比較大。在後臺有個處理者也就是消費者,注意前後是分開的,生產者應用本身不做消費,而Curator提供的API好像預設是一起的,一個應用既是生產者又可以做消費。
配置
一切都基於前面的示例Zookeeper和Curator-Framework實踐系列之: 配置管理,下面只說不一樣的地方。
applicationContext.xml
同樣與Zookeeper和Curator-Framework實踐系列之: 配置管理相同,只需配置一下zookeeperFactoryBean的listeners增加或修改成DistributedQueueDemo
<bean id="zookeeperFactoryBean" class="cn.bg.zk.core.ZookeeperFactoryBean" lazy-init="false">
<property name="zkConnectionString" value="zookeepermaster:2181"/>
<property name="listeners">
<list>
<bean class="cn.bg.zk.queues.DistributedQueueDemo"></bean>
</list>
</property>
</bean>
新增一個bean,指定CuratorFramework,充當生產者時需用用它來新增資料到佇列
<bean id="mainController" class="cn.bg.controller.MainController">
<constructor-arg ref="zookeeperFactoryBean" />
</bean>
程式碼
DistributedQueueDemo.java
分散式佇列處理類
package cn.bg.zk.queues;
public class DistributedQueueDemo implements IZKListener{
//申明兩個佇列例項
private DistributedQueue<String> queue1 = null;
private DistributedQueue<String> queue2 = null;
//資料系列化轉換工具類
private QueueSerializer<String> serializer = new QueueItemSerializer();
//消費者處理方法
private QueueConsumer<String> consumer = new QueueConsumer<String>() {
@Override
public void consumeMessage(String message) throws Exception {
//執行緒等待5秒,模擬資料處理,以達到資料搶奪的目的
Thread.sleep(5000);
//列印出是哪個執行緒處理了哪些資料
System.out.println(Thread.currentThread().getId() + " consume: " + message);
}
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
System.out.println("new state: " + newState);
}
};
//Spring啟動時呼叫此方法以啟動所有佇列例項
@Override
public void executor(CuratorFramework client) {
//例項化所有佇列,指定ZK佇列資料獲取地址,和其它引數
//由於它們的地址是相同的,都是*/zk_queue_test*,所以Curator會根據它們的空閒狀態來分配新的任務,上面通過執行緒暫停5秒來拉開它們的處理間隔。
queue1 = QueueBuilder.builder(client, consumer, serializer, "/zk_queue_test").buildQueue();
queue2 = QueueBuilder.builder(client, consumer, serializer, "/zk_queue_test").buildQueue();
try {
//啟動所有佇列例項,讓它們開始工作,注意所有指定的動作只有在呼叫了queue1.start()方法之後才會被執行,比如queue.put()等。
//Curator提供了queue.put()方法來往佇列裡新增資料,但它同時也會處理,但我們不想這樣,所以新增的過程我們通過其它的方式來實現。
queue1.start();
queue2.start();
System.out.println("Queues started!");
} catch (Exception e) {
}
}
}
QueueSerializer.java
資料系列化處理工具類
package cn.bg.zk.queues;
public class QueueItemSerializer implements QueueSerializer<String>
{
@Override
public byte[] serialize(String item)
{
return item.getBytes();
}
@Override
public String deserialize(byte[] bytes)
{
return new String(new String(bytes));
}
}
上面的是訊息佇列處理的部分,下面開始訊息新增,也就是生產者部分:
生產者是一個Controller,也就是通過使用者提交資料來做為生產者
MainController.java
package cn.bg.controller;
@Controller
public class MainController {
private final CuratorFramework zkClient;
//通過Spring注入CuratorFramework例項
public MainController(final CuratorFramework zkClient) {
Assert.notNull(zkClient, "zkClient cannot be null");
this.zkClient = zkClient;
}
//簡單的使用傳遞值來做資料處理的實體
@RequestMapping("/put/{val}")
@ResponseBody
public String put(@PathVariable String val) throws Exception {
//需要使用特定的格式來新增資料到佇列,使用ItemSerializer來做格式化生成byte。
byte[] bytes = ItemSerializer.serialize(val, new QueueItemSerializer());
String path = "" ;
//建立znode並新增資料
path = zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath("/zk_queue_test/queue-");
zkClient.setData().forPath(path, bytes);
return path;
}
}
ItemSerializer.java
這個類是格式化資料,也就是設定一些znode的屬性,並生成byte
此類來自Curator原始碼的簡化版,主要目的是分離Curator Queue來新增佇列資料用到。
package cn.bg.zk.queues;
public class ItemSerializer {
private static final int VERSION = 0x00010001;
private static final byte ITEM_OPCODE = 0x01;
private static final byte EOF_OPCODE = 0x02;
private static final int INITIAL_BUFFER_SIZE = 0x1000;
public static <T> byte[] serialize(T item, QueueSerializer<T> serializer) throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
DataOutputStream out = new DataOutputStream(bytes);
out.writeInt(VERSION);
byte[] itemBytes = serializer.serialize(item);
out.writeByte(ITEM_OPCODE);
out.writeInt(itemBytes.length);
if (itemBytes.length > 0) {
out.write(itemBytes);
}
out.writeByte(EOF_OPCODE);
out.close();
return bytes.toByteArray();
}
}
執行
啟動應用後所有佇列都已處理待命狀態,理論上只要在ZK目錄/zk_queue_test/新增資料就會被處理掉,只是它有固定的新增格式。
通過訪問/put/{?}等這樣路徑資料{?}就會被新增到佇列並處理,所以可以刷多條資料到佇列來觀察佇列的處理狀態,基本的輸出應該是這樣的:
17 consume: 111
18 consume: 222
17 consume: 111
18 consume: 222
17 consume: 111
17 consume: 111
整個過程基本完成,經測試執行狀態良好,Curator自己維護與ZK叢集的連線,本人通過JMX將應用與ZK的連線強制斷開後Curator主動識別並重新連線,基本不用擔心一些基礎問題上處理,可以專心解決我們的業務需要。
轉載: http://www.cnblogs.com/xguo/archive/2013/06/15/3137948.html
相關文章
- 分散式訊息佇列RocketMQ--事務訊息--解決分散式事務的最佳實踐分散式佇列MQ
- 分散式之訊息佇列複習精講分散式佇列
- 基於訊息佇列 RocketMQ 的大型分散式應用上雲最佳實踐佇列MQ分散式
- 分散式訊息佇列知識圖譜分散式佇列
- 分散式訊息佇列:如何保證訊息的順序性分散式佇列
- RabbitMQ 訊息佇列之佇列模型MQ佇列模型
- 阿里雲訊息佇列 Kafka-訊息檢索實踐阿里佇列Kafka
- 分散式訊息佇列:如何保證訊息不被重複消費?(訊息佇列消費的冪等性)分散式佇列
- 分散式任務 + 訊息佇列框架 go-queue分散式佇列框架Go
- 訊息佇列之RocketMQ佇列MQ
- 訊息佇列之RabbitMQ佇列MQ
- 訊息佇列之 RocketMQ佇列MQ
- 訊息佇列之 ActiveMQ佇列MQ
- 分散式服務(RPC)+分散式訊息佇列(MQ)面試題精選分散式RPC佇列MQ面試題
- 訊息佇列之JMS和AMQP對比佇列MQ
- 為什麼分散式一定要有訊息佇列?分散式佇列
- 訊息佇列之概論佇列
- 訊息佇列在大型分散式系統中的實戰要點分析!佇列分散式
- 金融行業訊息佇列選型及實踐行業佇列
- 老生常談——利用訊息佇列處理分散式事務佇列分散式
- 訊息佇列系列一:訊息佇列應用佇列
- Redis實現訊息佇列Redis佇列
- 實現簡單延遲佇列和分散式延遲佇列佇列分散式
- 訊息佇列之-RocketMQ入門佇列MQ
- RabbitMQ 訊息佇列之 Exchange TypesMQ佇列
- 溫故之訊息佇列ActiveMQ佇列MQ
- 訊息佇列佇列
- Spring Boot中使用WebSocket總結(三):使用訊息佇列實現分散式WebSocketSpring BootWeb佇列分散式
- 雲訊息佇列 ApsaraMQ 成本治理實踐(文末附好禮)佇列MQ
- [原始碼分析]並行分散式任務佇列 Celery 之 子程式處理訊息原始碼並行分散式佇列
- 分散式鎖之Zookeeper實現分散式
- 下一代分散式訊息佇列Apache Pulsar從入門到實現分散式佇列Apache
- [Redis]訊息佇列Redis佇列
- [訊息佇列]rocketMQ佇列MQ
- [訊息佇列]RabbitMQ佇列MQ
- RabbitMQ訊息佇列MQ佇列
- Kafka訊息佇列Kafka佇列
- 訊息佇列(MQ)佇列MQ
- kafka 訊息佇列Kafka佇列