最近的工作我在做一個有關于消息發送和接受封裝工作。大概流程是這樣的,消息中間件是采用rabbitmq,為了保證消息的絕對無丢失,我們需要在發送和接受前對消息進行DB落地。在發送前我會先進行DB的插入,單表插入,是以在性能上也是能接受的,單表插入做了壓測基本上是一到兩毫秒的時間,加上消息的發送(有ACK)再加上叢集是兩個節點的高可用(一個磁盤持久化節點),單台TPS基本上是在2000-3000左右。這對于我們的業務場景來說是夠用了。一旦當消息丢失或者由于網絡問題、叢集問題業務不會中斷,消息就算發不出去也沒關系,我們會進行消息的補償或者同步api調用補償。這是架構設計的必須要考慮的A計劃、B計劃、C計劃,這是敬畏或者危機意識。
你可能又要說兩個節點或者三個節點的叢集怎麼會有問題,那你就錯了,大錯特錯。隻能說明你并不了解什麼叫分布式系統及分布式系統的特性。你也許不會知道網絡抖動、網絡閃斷導緻socket斷開如何進行心跳重試已保持有效的Rabbitmq Connection。當你的網絡極不穩定,你的linux keepalived VIP 來回漂移,導緻你的ARP根本無法成效,可能就連廣播都傳不出去,而用戶端則在一直使用一個無用的IP位址。當你的叢集節點之間無法連接配接成一個整體的時候各種奇葩的問題又來了。這些都是可能導緻你的叢集出問題的原因,是以不要大意。 (後面我會整理一篇專門講解“rabbitmq高可用、故障轉移叢集架構“文章,是以這裡我們就不繼續介紹了)
這是一個鋪墊,本文的重點是介紹下我在嘗試使用可視化webapi的輸出模式,這比原本json的輸出模式看起來會友善許多。如果你的api提供兩種輸出模式,人性化絕對很好。現在很多後端api都是沒有界面的都是隻提供了一個json輸出。然而,我們其實很需要一個可讀性很強的輸出模式。
我在開發消息補償程式的時候,我借鑒了這一思想進行了嘗試。先來看下整體架構藍圖:

本篇文章要介紹的是有關于這個補償程式的api的可視化輸出内容。不涉及到消息相關太多的東西,隻是為了讓這個可視化輸出看起來容易了解點。這個補償程式需要對發送的消息和接受的消息進行查詢和比較然後輸出,用來确定消息的發送是失敗了還是成功的。簡單邏輯就是比較某個時間段内的消息發送表和接受表,然後進行消息id的比對。
我在想這個資料回報到api上是個什麼樣子的,按照正常設計就是兩個字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code>/// <summary></code>
<code>/// 接受的消息對象。</code>
<code>/// </summary></code>
<code>public</code> <code>class</code> <code>ReceiveMessage</code>
<code>{</code>
<code> </code><code>/// <summary></code>
<code> </code><code>/// 發送消息ID。</code>
<code> </code><code>/// </summary></code>
<code> </code><code>public</code> <code>string</code> <code>SendMessageId { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>/// 接受消息ID。</code>
<code> </code><code>public</code> <code>string</code> <code>ReceiveMessageId { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code>}</code>
這表示一個消息從發送到接受的一個過程。如果失敗了,可能是隻有SendMessageId而沒有ReceiveMessageId。然後我才會針對沒有ReceiveMessageId的消息進行自動補償。在開發的時候隻有幾十條消息,輸出到postman中的看起來也還行,但是不直覺。
GetReceiveMessage是擷取接受消息清單,就是檢視目前消息發送到接受是個什麼狀态。
16
17
18
19
20
<code>/// 處理成功消息對象。</code>
<code>public</code> <code>class</code> <code>SuccessMessage</code>
<code> </code><code>/// 發送消息ID</code>
<code> </code><code>/// 接受消息ID</code>
<code> </code><code>/// 處理成功消息ID</code>
<code> </code><code>public</code> <code>string</code> <code>SuccessMessageId { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
SuccessMessage表示處理成功消息情況。此時有可能是有SendMessageId,ReceiveMessageId消息,但是SuccessMessageId可能是沒有的。就會針對處理成功的消息進行發送。
突然受到ElasticSearch的_cat endpoint 啟發。似乎這裡我可以嘗試下,webapi帶有兩種輸出模式,一種是針對程式使用的json輸出模式,另外一種是針對人可以閱讀的模式text/plain模式,而第二種模式可以簡單的了解為是行列轉換預設模式。
是不是看起來會很舒服。這在進行消息的時間段檢視非常有幫助,如果還按照原本的json輸出模式可能看起來會比較吃力。
來看下基本的api的設計,為了保證你的所有api支援?v可視化模式,需要一定的抽象:
需要定義一種ViewModel,所有的資料都輸出這種對象,當然我這裡也隻是簡單地封裝。如果可以,其實可以專門提取出一個庫出來,包括對文本的輸出自動化。
我們看下BaseApiController:
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<code>public</code> <code>class</code> <code>BaseApiController : ApiController</code>
<code> </code><code>{</code>
<code> </code><code>public</code> <code>class</code> <code>ViewModel</code>
<code> </code><code>{</code>
<code> </code><code>public</code> <code>string</code> <code>Content { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>object</code> <code>JsonObject { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>bool</code> <code>Success = </code><code>true</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code>protected</code> <code>bool</code> <code>IsView;</code>
<code> </code><code>private</code> <code>const</code> <code>string</code> <code>ViewQuerystring = </code><code>"?v"</code><code>;</code>
<code> </code><code>public</code> <code>ViewModel ResultModel;</code>
<code> </code><code>private</code> <code>const</code> <code>string</code> <code>CheckToken = </code><code>"CheckToken"</code><code>;</code>
<code> </code><code>private</code> <code>const</code> <code>string</code> <code>Token = </code><code>"49BBD022-CDBF-4F94-80E4-5BCACB1192EC"</code><code>;</code>
<code> </code><code>private</code> <code>bool</code> <code>_checkStatus;</code>
<code> </code><code>public</code> <code>override</code> <code>Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)</code>
<code> </code><code>//驗證token</code>
<code> </code><code>if</code> <code>(controllerContext.Request.Headers != </code><code>null</code> <code>&& controllerContext.Request.Headers.Contains(CheckToken))</code>
<code> </code><code>{</code>
<code> </code><code>var</code> <code>requestToken = controllerContext.Request.Headers.GetValues(CheckToken).FirstOrDefault();</code>
<code> </code><code>if</code> <code>(requestToken != </code><code>null</code> <code>&& requestToken.Equals(Token))</code>
<code> </code><code>{</code>
<code> </code><code>this</code><code>._checkStatus = </code><code>true</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>if</code> <code>(!_checkStatus)</code>
<code> </code><code>var</code> <code>checkResult = </code><code>new</code> <code>Task<HttpResponseMessage>(() => </code><code>new</code> <code>HttpResponseMessage</code>
<code> </code><code>Content = </code><code>new</code> <code>StringContent(</code><code>"非法通路,缺少token"</code><code>, Encoding.UTF8, </code><code>"text/plain"</code><code>)</code>
<code> </code><code>}, cancellationToken);</code>
<code> </code><code>checkResult.Start();</code>
<code> </code><code>return</code> <code>checkResult;</code>
<code> </code><code>if</code> <code>(controllerContext.Request.RequestUri.Query.Equals(ViewQuerystring))</code>
<code> </code><code>this</code><code>.IsView = </code><code>true</code><code>;</code>
<code> </code><code>base</code><code>.ExecuteAsync(controllerContext, cancellationToken);</code>
<code> </code><code>//text模式</code>
<code> </code><code>if</code> <code>(</code><code>this</code><code>.IsView)</code>
<code> </code><code>var</code> <code>textResult = </code><code>new</code> <code>Task<HttpResponseMessage>(() => </code><code>new</code> <code>HttpResponseMessage</code>
<code> </code><code>Content = </code><code>new</code> <code>StringContent(</code><code>this</code><code>.ResultModel.Content, Encoding.UTF8, </code><code>"text/plain"</code><code>)</code>
<code> </code><code>textResult.Start();</code>
<code> </code><code>return</code> <code>textResult;</code>
<code> </code><code>//json模式</code>
<code> </code><code>var</code> <code>resultData = </code><code>new</code> <code>Result<</code><code>object</code><code>></code>
<code> </code><code>Data = </code><code>this</code><code>.ResultModel.JsonObject,</code>
<code> </code><code>Type = </code><code>this</code><code>.ResultModel.Success ? ResultType.Successfully : ResultType.Failure</code>
<code> </code><code>};</code>
<code> </code><code>var</code> <code>jsonResult = </code><code>new</code> <code>Task<HttpResponseMessage>(() => </code><code>new</code> <code>HttpResponseMessage</code>
<code> </code><code>Content = </code><code>new</code> <code>ObjectContent(</code><code>typeof</code><code>(Result), resultData, </code><code>new</code> <code>JsonMediaTypeFormatter(), </code><code>"application/json"</code><code>)</code>
<code> </code><code>}, cancellationToken);</code>
<code> </code><code>jsonResult.Start();</code>
<code> </code><code>return</code> <code>jsonResult;</code>
<code> </code><code>}</code>
代碼很簡單,這裡給我們一個啟發,webapi是不是真的缺少了一個可視化模式。