市面上流行的消息隊列有rabbitmq,kafka,Activemq等,所有這些都是爲了解決消息的分佈式消費,完成項目與服務的解耦。採取異步模式完成消息隊列提供者和消費者的通信,提高了系統的響應能力和信息吞吐量。
Rabbitmq
基本概念
- Producer:消息生產者
- Consumer:消息消費者
- Exchange:消息交換機,指定消息按什麼規則傳遞到具體哪個隊列中
- Queue:消息隊列,消息的載體
- Routingkey:路由關鍵字,Exchange根據Routingkey來投遞消息給隊列
- Binding:綁定。作用就是將Exchange和Queue按照某種路由規則綁定起來。
- Channel:消息通道,也稱信道。在客戶端的每個連接裏可以建立多個Channel,每個Channel代表一個會話任務。
常用的轉發方式有
1)DirectExchange
直接交換器類型,根據routingkey採取點對點的信息轉發
@Configuration
public class UserRegisterQueueConfiguration {
/**
* 配置路由交換對象實例
* @return
*/
@Bean
public DirectExchange userRegisterDirectExchange()
{
return new DirectExchange(ExchangeEnum.USER_REGISTER.getValue());
}
/**
* 配置用戶註冊隊列對象實例
* 並設置持久化隊列
* @return
*/
@Bean
public Queue userRegisterQueue()
{
return new Queue(QueueEnum.USER_REGISTER.getName(),true);
}
/**
* 將 用戶註冊隊列 綁定到 路由交換配置 上並設置指定路由鍵進行轉發
* @return
*/
@Bean
public Binding userRegisterBinding()
{
return BindingBuilder.bind(userRegisterQueue()).to(userRegisterDirectExchange()).with(QueueEnum.USER_REGISTER.getRoutingKey());
}
}
2)FanoutExchange
廣播交換器類型,將同一個message發送到所有同該Exchange 綁定的queue。不論RoutingKey是什麼,這條消息都會被投遞到所有與此Exchange綁定的queue中。
@Configuration
public class SenderConf {
@Bean(name="Amessage")
public Queue AMessage() {
return new Queue("fanout.A");
}
@Bean(name="Bmessage")
public Queue BMessage() {
return new Queue("fanout.B");
}
@Bean(name="Cmessage")
public Queue CMessage() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");//配置廣播路由器
}
@Bean
Binding bindingExchangeA(@Qualifier("Amessage") Queue AMessage,FanoutExchange fanoutExchange) {
return BindingBuilder.bind(AMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeB(@Qualifier("Bmessage") Queue BMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(BMessage).to(fanoutExchange);
}
@Bean
Binding bindingExchangeC(@Qualifier("Cmessage") Queue CMessage, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(CMessage).to(fanoutExchange);
}
}
3)TopicExchange
主題交換器,根據Binding指定的RoutingKey,Exchange對key進行模式匹配後投遞到相應的Queue。
如圖中,假如消息的RoutingKey是American.action.13,這條消息將被投遞到Q1和Q2中。假如RoutingKey是American.action.13.test(注意:此處是四個詞),這條消息將會被丟棄,因爲沒有routingkey與之匹配。假如RoutingKey是Chinese.action.13,這條消息將被投遞到Q2和Q3中。假如RoutingKey是Chinese.action.13.test,這條消息只會被投遞到Q3中,#可以匹配一個或者多個單詞,而*只能匹配一個詞。
@Configuration
public class UserRegisterQueueConfiguration {
private Logger logger = LoggerFactory.getLogger(UserRegisterQueueConfiguration.class);
/**
* 配置用戶註冊主題交換
* @return
*/
@Bean
public TopicExchange userTopicExchange()
{
TopicExchange topicExchange = new TopicExchange(ExchangeEnum.USER_REGISTER_TOPIC_EXCHANGE.getName());
logger.info("用戶註冊交換實例化成功。");
return topicExchange;
}
/**
* 配置用戶註冊
* 創建賬戶消息隊列
* 並設置持久化隊列
* @return
*/
@Bean
public Queue createAccountQueue()
{
Queue queue = new Queue(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getName(),true,false,true);
logger.info("創建用戶註冊賬號隊列成功.");
return queue;
}
/**
* 綁定用戶創建賬戶到用戶註冊主題交換配置
* @return
*/
@Bean
public Binding createAccountBinding(TopicExchange userTopicExchange,Queue createAccountQueue)
{
Binding binding = BindingBuilder.bind(createAccountQueue).to(userTopicExchange).with(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getRoutingKey());
logger.info("綁定創建賬號到註冊交換成功。");
return binding;
}
}
監控頁面
在rabbitmq sbin目錄下執行以下語句開啓指定插件。
rabbitmq-plugins.bat enable rabbitmq_management
訪問http://127.0.0.1:15672,可訪問rabbitmq監控管理頁面
優缺點
優點:
(1)由Erlang語言開發,支持大量協議:AMQP、XMPP、SMTP、STOMP。
(2)支持消息的持久化、負載均衡和集羣,且集羣易擴展。
(3)具有一個Web監控界面,易於管理。
(4)安裝部署簡單,上手容易,功能豐富,強大的社區支持。
(5)支持消息確認機制、靈活的消息分發機制。
缺點:
(1)由於犧牲了部分性能來換取穩定性,比如消息的持久化功能,使得RabbitMQ在大吞吐量性能方面不及Kafka和ZeroMQ。
(2)由於支持多種協議,使RabbitMQ非常重量級,比較適合企業級開發。
Kafka
kafka是Apache公司的,用java編寫,類AMQP協議。必須和zookeeper配合才能使用。
基本概念
1、消費者:(Consumer):主動從Broker拉數據,從而消費這些已發佈的消息
2、生產者:(Producer) :向broker發佈消息的應用程序
3、AMQP服務端(broker):用來接收生產者發送的消息並將這些消息路由給服務器中的隊列,便於kafka將生產者發送的消息,動態的添加到磁盤並給每一條消息一個偏移量,所以對於kafka一個broker就是一個應用程序的實例
4、話題(Topic):是特定類型的消息流。消息是字節的有效負載(Payload),話題是消息的分類名或種子(Feed)名;
5、分區(Partition):一個Topic中的消息數據按照多個分區組織,分區是kafka消息隊列組織的最小單位,一個分區可以看作是一個FIFO( First Input First Output的縮寫,先入先出隊列)的隊列。kafka分區是提高kafka性能的關鍵所在,當你發現你的集羣性能不高時,常用手段就是增加Topic的分區,分區裏面的消息是按照從新到老的順序進行組織,消費者從隊列頭訂閱消息,生產者從隊列尾添加消息。
zookeeper
(1)無論是kafka集羣,還是producer和consumer都依賴於zookeeper來保證系統可用性集羣保存一些meta信息。
(2)Kafka使用zookeeper作爲其分佈式協調框架,很好的將消息生產、消息存儲、消息消費的過程結合在一起。
(3)同時藉助zookeeper,kafka能夠生產者、消費者和broker在內的所以組件在無狀態的情況下,建立起生產者和消費者的訂閱關係,並實現生產者與消費者的負載均衡。
group partition
group和partition有千絲萬縷的關係,網上關於兩者的介紹很多,但基本上都是提問,回覆也是五花八門,本着嚴謹治學的態度,以下全是實踐之後得出的結論。
一個topic可以分爲多個partition,這麼做的好處是,每個partition(目錄)相當於一個巨型文件被平均分配到多個大小相等segment(段)數據文件中,一個group中的多個consumer加入時,server會進行partition負載均衡,一個group中的consumer可以分配多個partition,也可能一個都分不到。
1)創建topic aaa,只對應一個partition aaa-0
2)創建topic bbb,對應6個partition bbb-0 bbb-1 bbb-2 bbb-3 bbb-4 bbb-5
3)先啓動一個服務consumer1,監聽topics={"aaa","bbb"},group-id="test-consumer-group"
默認系統給分配的partition如下,這個consumer1一個人佔了7個partition!!!
4)很明顯,如果讓consumer1一個人佔7個partition,就失去了負載均衡的意義。此時我們再起一個服務consumer2,topics={"aaa","bbb"},group-id="test-consumer-group"
啓動後consumer2重新分配後的partition如下,發現[bbb-3,bbb-4,bbb-5]都歸consumer2所有
同時consumer1那邊重新分配後的partition如下,值得注意的是aaa-0是碰巧還屬於consumer1,即consumer的啓動順序和分配partition是無關的!!!且topic中的partition數量少於同一個group中consumer的數量時,肯定會有consumer沒有分配到partition,即無法接收消息!!!
啓動了兩個consumer,只有consumer1擁有aaa-0,所以只有consumer1能接收到topic aaa的消息。
5)啓動producer向topic aaa發送請求,數字0~19,可以預想到只有consumer1能接收到所有消息
consumer1接收所有消息,consumer2沒有接收到信息
6)如果producer向bbb發送消息呢?bbb有6個partitions,consumer1和consumer2能接收到什麼消息呢?
答案是20條消息被隨機分配到了兩個consumer中,
consumer1接收的消息
consumer2接收的消息
7)現在我們啓動consumer3,topics={"aaa","bbb"},group-id="test-consumer-group2",這裏group-id與其兩者不同。在保證consumer1和consumer2運行的情況下啓動consumer3,分配的partitions如下。可見consumer3擁有7個partition,並沒和consumer1或者consumer2有什麼交集,更談不上負載均衡,自然consumer3能收到所有信息。
總結:
producer向topic發送消息,topic分配給所屬的partitions。
consumer聲明group-id,並監聽topic,啓動後會接收系統分配partitions。一個partition只能綁定同一個group中的一個consumer,但能綁定多個不同group中的consumer,基於這個原理就會發生有趣的事情。
啓動多個不同group-id的consumer監聽topic,所有的consumer都能接收到信息,這是廣播
啓動多個相同group-id的consumer監聽topic,且topic中的partition數量>=consumer的數量,這是負載均衡,一條消息永遠只有一個consumer能消費,下一條消息可能是另一個consumer消費。所有的consumer都會消費到消息。
啓動多個相同group-id的consumer監聽topic,且topic中partition數量<consumer的數量,那麼總會有consumer沒分配到partition,即它永遠接收不到消息。
優缺點
優點:
1)可擴展。Kafka集羣可以透明的擴展,增加新的服務器進集羣。
2)高性能。Kafka性能遠超過傳統的ActiveMQ、RabbitMQ等,Kafka支持Batch操作。
3)容錯性。Kafka每個Partition數據會複製到幾臺服務器,當某個Broker失效時,Zookeeper將通知生產者和消費者從而使用其他的Broker。
缺點:
1)重複消息。Kafka保證每條消息至少送達一次,雖然機率很小,但一條消息可能被送達多次。
2)消息亂序。Kafka某一個固定的Partition內部的消息是保證有序的,如果一個Topic有多個Partition,partition之間的消息送達不保證有序。
3)複雜性。Kafka需要Zookeeper的支持,Topic一般需要人工創建,部署和維護比一般MQ成本更高。
4)不能像Rabbitmq那樣用topic(#,*)匹配方式傳遞信息,只能死板地訂閱topic,且當同一個group內consumer數量>topic中partition數量時,會有consumer接收不到消息,而rabbitmq可以通過fanout模式實現廣播。
搭建流程
kafka的搭建相對較爲複雜,因爲boker、producer、consumer都是通過zookeeper交流的,所以要先搭建zookeeper,再搭建kafka。
1.搭建zookeeper
在新版的kafka中,都會自帶zookeeper,但總歸是配置不夠方便明瞭。一般還是單獨下載zookeeper進行搭建。
zookeeper下載地址:http://mirrors.hust.edu.cn/apache/zookeeper/
1.1 配置zoo.cfg
解壓後,裏面有conf/zoo_sample.cfg,把它複製粘貼後改名zoo.cfg,作爲核心配置類,zookeeper啓動時會讀取zoo.cfg中的配置信息。其中dataDir,dataLogDir,server.x這幾個屬性得重新配置。
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
#dataDir和dataLogDir可以自行設置,必須是全路徑
dataDir=/usr/local/zookeeper/data
dataLogDir=/usr/local/zookeeper/datalog
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider
requireClientAuthScheme=sasl
jaasLoginRenew=3600000
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
#配置集羣地址,第一個端口是master和slave之間的通信端口,默認是2888,
#第二個端口是leader選舉的端口,集羣剛啓動的時候選舉或者leader掛掉之後進行新的選舉的端口默認是3888
server.1=192.168.3.163:2888:3888
server.2=192.168.3.164:2888:3888
server.3=192.168.3.165:2888:3888
zookeeper集羣配置服務器地址,可以像上圖那樣配置,也可以在/etc/hosts中將這些ip配置成具體名稱(zk1,zk2,zk3),這樣配置的好處是如果某個IP地址發生了變化,我們不需要重啓zookeeper,直接修改主機對應的IP地址即可。
[root@localhost zookeeper]# vi /etc/hosts
然後在文件末尾追加以下內容,這樣就可以在zoo.cfg中用server.1=zk1:2888:3888來代替了。
192.168.3.163 zk1 192.168.3.164 zk2 192.168.3.165 zk3
按照上面配置,將zookeeper文件夾scp -r到另外兩個服務器上。
1.2 創建myid
myid文件和zookeeper_server.pid 在快照目錄下(./data)存放的標識本臺服務器的文件,他是整個zk集羣用來發現彼此的一個重要標識。快照目錄需和zoo.cfg中 dataDir=/usr/local/zookeeper/data 一致。
myid文件裏面只有一個數字,用於區分集羣中不同節點的zookeeper。分別給幾個zookeeper創建myid。
#server1 echo "1" > /usr/local/zookeeper/data/myid #server2 echo "2" > /usr/local/zookeeper/data/myid #server3 echo "3" > /usr/local/zookeeper/data/myid
zookeeper_server.pid是啓動zookeeper server後,自動創建的文件,裏面有數字pid,即啓動zookeeper的進程號pid。
1.3 啓動zookeeper
進入bin目錄下
#啓動服務(3臺都需要操作)
./zkServer.sh start
#關閉服務
./zkServer.sh stop
#檢查服務器狀態
./zkServer.sh status
#執行命令jps,查看zk進程QuorumPeerMain
jps
20348 Jps
4233 QuorumPeerMain
#使用客戶端進入zk
./zkCli.sh -server 192.168.3.164:2181
[zk: 127.0.0.1:12181(CONNECTED) 0] ls /
#顯示結果:[consumers, config, controller, isr_change_notification, admin, brokers, zookeeper, controller_epoch]
'''
上面的顯示結果中:只有zookeeper是,zookeeper原生的,其他都是Kafka創建的
'''
#查看partion
[zk: 127.0.0.1:12181(CONNECTED) 7] get /brokers/topics/shuaige/partitions/0
#查看group,其中console-consumer-xxxxx是控制檯默認生成consumer./kafka-console-consumer.sh
[zk: localhost:2181(CONNECTED) 1] ls /consumers
[console-consumer-24279, console-consumer-87851, console-consumer-74253, console-consumer-91660, test-consumer-group, console-consumer-20304]
#刪除group
[zk: localhost:2181(CONNECTED) 6] rmr /consumers/console-consumer-20304
2 Kafka集羣搭建
下載kafka並解壓,本人下的是版本是 kafka_2.12-1.1.0.tar ,不同的版本配置文件內容可能不太一致,所以一定要參考相應版本的配置文件。參考 https://blog.csdn.net/l1028386804/article/details/79194929
2.1 配置server.properties
在config目錄下有很多配置文件,其中最重要的就是server.properties,裏面包含了zookeeper集羣的ip地址,監聽本機kafka-server的ip:port。
需要修改的就三個屬性
broker.id 當前機器在集羣中的唯一標識,和zookeeper的myid性質一樣
listeners 當前機器的ip地址,如果是搭建集羣的話必須寫ip,單機可以寫localhost:9092
zookeeper.connect zookeeper集羣的所有ip地址
broker.id=0
############################# Socket Server Settings #############################
# The address the socket server listens on. It will get the value returned from
# java.net.InetAddress.getCanonicalHostName() if not configured.
# FORMAT:
# listeners = listener_name://host_name:port
# EXAMPLE:
# listeners = PLAINTEXT://your.host.name:9092
listeners=PLAINTEXT://192.168.3.165:9092
...
############################# Zookeeper #############################
# Zookeeper connection string (see zookeeper docs for details).
# This is a comma separated host:port pairs, each corresponding to a zk
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
# You can also append an optional chroot string to the urls to specify the
# root directory for all kafka znodes.
zookeeper.connect=192.168.3.163:2181,192.168.3.164:2181,192.168.3.165:2181
2.2 啓動集羣
進入bin目錄,
#後臺啓動kafka服務
./kafka-server-start.sh -daemon ../config/server.properties
#關閉服務
./kafka-server-stop.sh
#檢查服務啓動成功沒有
jps
20348 Jps
4233 QuorumPeerMain
18991 Kafka
#查看所有創建的topic,連接的是zookeeper
./kafka-topics.sh --list --zookeeper 192.168.3.164:2181
#創建Topic
./kafka-topics.sh --create --zookeeper 192.168.3.164:2181 --replication-factor 2 --partitions 1 --topic bbb
#解釋
--replication-factor 2 #複製兩份
--partitions 1 #創建1個分區
--topic #主題爲bbb
#修改topic partition
./kafka-topics.sh --alter --topic bbb --zookeeper 192.168.3.164:2181 --partitions 6
#查看topic具體信息
./kafka-topics.sh --describe --zookeeper 192.168.3.164:2181 --topic bbb
#創建一個broker,發佈者
./kafka-console-producer.sh --broker-list 192.168.3.164:9092 --topic bbb
#在一臺服務器上創建一個訂閱者,默認創建的consumer group-id=console-consumer-xxxxx
./kafka-console-consumer.sh --zookeeper l92.168.3.164:2181 --topic bbb --from-beginning
#也可以指定consumer.properties,設置group-id
./kafka-console-consumer.sh --consumer.config ../config/consumer.properties --zookeeper 192.168.3.165:2181 --topic bbb