1、簡介
Pub/Sub 是一種全託管式實時消息傳遞服務,可讓您在獨立的應用之間發送和接收消息,它是一個PAAS服務。
2、概覽
- 主題(Topic):相當於一個消息的中轉站,發佈者發佈消息後,消息存儲在主題中。
- 發佈者(Publisher):發佈消息的應用
- 訂閱者(Subscriber):接收消息者
如下圖,首先對主題創建了兩個訂閱者(subscriber1, subscriber2),發佈者(publisher)向主題中發佈一條消息(Hello,World!), 接着,這兩個訂閱者都收到了此消息(Hello,World!)。
3、訂閱消息
(1)訂閱是如何工作的
- 多個訂閱者訂閱一個主題的情況,如果在一個主題中發佈一條消息,那所有的訂閱者都會收到此消息。如果想要多個訂閱者分工處理不同的內容,可以在消息中加自定義特性 (Attribute),在訂閱者邏輯中可以根據此特性只處理當前訂閱者感興趣的消息。
- 只有經過確認過的消息`message.ack()`,纔不會再被傳送,如果您在確認時限之前未確認消息,Pub/Sub 會重新發送該消息。因此,Pub/Sub 可能會發送重複的消息,如果訂閱者處理消息發生異常,且消息未被確認,那Pub/Sub 會重新發送該消息。
- 確認過的消息,會在Pub/Sub被刪除。
-
在給定訂閱者創建之前發佈的消息通常不會針對此訂閱進行傳送。因此,如果某主題沒有訂閱,則發佈到該主題的消息將不會傳送給任何訂閱者。
(2)至少傳送一次
通常,Pub/Sub 會按照消息發佈的順序將每條消息傳送一次,但有時可能並不按順序傳送消息,或者會將消息傳送多次。 一般來說,如果要實施多次傳送,訂閱者需要在處理消息時遵循冪等原則。您可以使用 Cloud Dataflow PubsubIO 將 Pub/Sub 消息流只處理一次。PubsubIO 會根據自定義消息標識符或由 Pub/Sub 分配的消息標識符來刪除重複的消息。所以,處理消息的邏輯必須是冪等的(所謂冪等,通俗點說,就是函數執行一次,和執行數次,產生的結/效果是一樣的)。
(3)對消息排序
通常情況下,pub/sub不完全像隊列一樣嚴格地保證消息先進先出,因爲保證消息順序會對吞吐量產生嚴重限制,pub/sub僅保證第一次傳送消息時是按順序進行的,後序的消息不一定是按順序排列的,所有消息都允許隨時嘗試重新傳送,這樣允許一次向訂閱者發送多條消息。
如果想使消息有順序的話,可以在自定義特性 (Attribute)中加時間戳或序列號。如果主題有10條這樣的消息0,1,2,3,4,5 那收到消息順序,下面幾種情況都可能會發生。
- 0,1,5,4,2,3 # 第一條消息始終是0,順序不能保證
- 0,1,2,4,2,3,0 # 0 也可能被髮送了2次
- 0,1,1,2,4,3,5 ,6 # 後序的消息可能被髮送多次
4、訂閱者接收消息的兩種方式
訂閱者可以有兩種方式拿到消息,一是設置public endpoint,讓pub/sub被動地推送消息給你,二是主要向pub/sub發送拉取(pull)請求。
4.1 推送傳送
(1)關於推送
Pub/Sub 將根據收到成功響應的速率來動態調整推送請求的速率, 推送訂閱受一組配額和資源限制的約束,系統會自動調整推送傳送的速率,以最大限度地提高傳送速率,同時不會使推送端點過載,它是通過一套算法來控制的。
(2)Cloud Function和App Engine通常都是使用推送傳送的,也是遵循至少傳送一次的原則。
(3)對於推送訂閱,Pub/Sub 不會發送否定確認(有時稱爲 NACK)。如果 Webhook 未返回成功代碼,則 Pub/Sub 會重試傳送,直到消息在訂閱的消息保留期限過後失效爲止。
(4)推送不能一次處理多條消息,沒法使用批處理,但拉取和發佈消息可以。
4.2 拉取傳送
(1)異步拉取(用得最多,實時處理消息那種)
可以在應用中使用長時間運行的消息偵聽器接收消息,並且一次確認一條消息,不建議使用cron job,效率不高。
使用異步拉取不需要應用阻止新消息,從而在應用中實現更高的吞吐量。
如果訂閱者客戶端處理和確認消息的速度可能比 Pub/Sub 將消息發送到客戶端的速度要慢,可考慮使用訂閱者的流控制功能來控制訂閱者接收消息的速率。https://cloud.google.com/pubsub/docs/pull
(2)同步拉取
在某些情況下,異步拉取並不非常適合您的應用。例如,應用邏輯可能依賴輪詢模式來檢索消息,或者需要對客戶端在任何給定時間檢索的消息數量進行精確限制。爲了支持此類應用,該服務支持同步拉取方法,用於拉取和確認固定數量的消息,但這會帶來一些消息傳送的延遲。
如下是例子的代碼
from google.cloud import pubsub_v1
def sub_data():
project_id = 'cong-proj'
# topic_name = 'hello_topic'
subscription_name = 'sub_one'
subscription1 = pubsub_v1.SubscriberClient()
# topic_path = subscription1.topic_path(project_id, topic_name)
subscription_path = subscription1.subscription_path(project_id, subscription_name)
def callback(message):
print("Received message: {}".format(message))
message.ack()
streaming_pull_future = subscription1.subscribe(subscription_path, callback=callback)
print("Listening for messages on {}..\n".format(subscription_path))
try:
streaming_pull_future.result()
except: # noqa
streaming_pull_future.cancel()
5、發佈消息
5.1 批處理以平衡延遲和吞吐量
消息可以根據請求大小(以字節爲單位)、消息數量和時間分批。
batch_settings = pubsub_v1.types.BatchSettings(
max_bytes=1024, max_latency=1 # One kilobyte # One second
)
publisher = pubsub_v1.PublisherClient(batch_settings)
5.2 重試請求
如果發佈失敗,系統會自動重試,但無法保證能夠重試的錯誤除外。
publisher = pubsub_v1.PublisherClient(client_config=retry_settings)
如下是例子的代碼
def publish_data():
project_id = 'cong-proj'
topic_name = 'hello_topic'
# batch_settings = pubsub_v1.types.BatchSettings(max_messages=10, max_latency=30)
publisher = pubsub_v1.PublisherClient() # batch_settings
# publisher.from_service_account_file("""C:\CongStudy\pubsub-demo\cong-pubsub.json""")
topic_path = publisher.topic_path(project_id, topic_name)
data = "Message number 1".encode('utf-8')
future = publisher.publish(topic_path, data)
print(future.result())
def get_message(message):
# 消息實際上是以Base64的字符串的形式存於主題中,如要在訂閱器中使用消息,可以使用如下代碼
pubsub_message = base64.b64decode(message).decode('utf-8')
# hell,world base64加密後爲 aGVsbCx3b3JsZA==, 它就會存於主題中
# print(base64.b64encode('hell,world'.encode('utf-8')).decode('utf-8')) # aGVsbCx3b3JsZA==
# aGVsbCx3b3JsZA == 解密後爲 hell,world
# print(base64.b64decode('aGVsbCx3b3JsZA==').decode('utf-8')) # hell,world
6、重放和完全清除消息
- 還原至某一時間戳
- 還原至快照
7、參考鏈接
- https://cloud.google.com/pubsub/docs/quickstarts
- https://blog.gcp.expert/google-cloud-pub-sub-aws-sqs-comparison/