前言
本文緊接上篇.Net架構篇:思考如何設計一款實用的分布式監控系統?,上篇僅僅是個思考篇,跟本文沒有太大的關系。但有思考,結合現有的開源組件,實踐起來更易理解起來,所以看本文之前,應該先看下上篇博文。
本文緊接上篇.Net架構篇:思考如何設計一款實用的分布式監控系統?,上篇僅僅是個思考篇,跟本文沒有太大的關系。但有思考,結合現有的開源組件,實踐起來更易理解起來,所以看本文之前,應該先看下上篇博文。
Zipkin簡介

Zipkin是一種分布式跟蹤系統。它有助於收集解決微服務架構中的延遲問題所需的時序數據。它管理這些數據的收集和查找。Zipkin的設計基於Google Dapper 論文。
應用程序用於向Zipkin報告時序數據。Zipkin UI還提供了一個依賴關系圖,顯示了每個應用程序通過的跟蹤請求數。如果要解決延遲問題或錯誤,可以根據應用程序,跟蹤長度,注釋或時間戳對所有跟蹤進行篩選或排序。選擇跟蹤后,您可以看到每個跨度所需的總跟蹤時間百分比,從而可以識別有問題的應用程序。

Zipkin是一種分布式跟蹤系統。它有助於收集解決微服務架構中的延遲問題所需的時序數據。它管理這些數據的收集和查找。Zipkin的設計基於Google Dapper 論文。
應用程序用於向Zipkin報告時序數據。Zipkin UI還提供了一個依賴關系圖,顯示了每個應用程序通過的跟蹤請求數。如果要解決延遲問題或錯誤,可以根據應用程序,跟蹤長度,注釋或時間戳對所有跟蹤進行篩選或排序。選擇跟蹤后,您可以看到每個跨度所需的總跟蹤時間百分比,從而可以識別有問題的應用程序。
快速開始
Docker
docker run -d -p 9411:9411 openzipkin/zipkin
docker run -d -p 9411:9411 openzipkin/zipkin
Java
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar
源碼啟動
# get the latest source
git clone https://github.com/openzipkin/zipkin
cd zipkin
# Build the server and also make its dependencies
./mvnw -DskipTests --also-make -pl zipkin-server clean install
# Run the server
java -jar ./zipkin-server/target/zipkin-server-*exec.jar
無論您以何種方式啟動zikpin,請訪問 http://your_host:9411以查詢跟蹤。
# get the latest source
git clone https://github.com/openzipkin/zipkin
cd zipkin
# Build the server and also make its dependencies
./mvnw -DskipTests --also-make -pl zipkin-server clean install
# Run the server
java -jar ./zipkin-server/target/zipkin-server-*exec.jar
無論您以何種方式啟動zikpin,請訪問 http://your_host:9411以查詢跟蹤。
啟動效果
架構
架構簡述
應用程序中的監控器記錄有關發生的操作的時間和元數據,並且對用戶是透明的。如一個web監聽服務記錄了請求什么時候進來,什么時候離開。這個監控的數據叫做Span。
將數據發送到Zipkin的檢測應用程序中的組件稱為Reporter。
如圖所示 
應用程序中的監控器記錄有關發生的操作的時間和元數據,並且對用戶是透明的。如一個web監聽服務記錄了請求什么時候進來,什么時候離開。這個監控的數據叫做Span。
將數據發送到Zipkin的檢測應用程序中的組件稱為Reporter。
如圖所示
示例流程
這是一個示例序列的http跟蹤,其中用戶代碼調用資源/ foo。這個結果是單個Span,在用戶代碼收到http響應后異步發送到Zipkin。
┌─────────────┐ ┌───────────────────────┐ ┌─────────────┐ ┌──────────────────┐
│ User Code │ │ Trace Instrumentation │ │ Http Client │ │ Zipkin Collector │
└─────────────┘ └───────────────────────┘ └─────────────┘ └──────────────────┘
│ │ │ │
┌─────────┐
│ ──┤GET /foo ├─▶ │ ────┐ │ │
└─────────┘ │ record tags
│ │ ◀───┘ │ │
────┐
│ │ │ add trace headers │ │
◀───┘
│ │ ────┐ │ │
│ record timestamp
│ │ ◀───┘ │ │
┌─────────────────┐
│ │ ──┤GET /foo ├─▶ │ │
│X-B3-TraceId: aa │ ────┐
│ │ │X-B3-SpanId: 6b │ │ │ │
└─────────────────┘ │ invoke
│ │ │ │ request │
│
│ │ │ │ │
┌────────┐ ◀───┘
│ │ ◀─────┤200 OK ├─────── │ │
────┐ └────────┘
│ │ │ record duration │ │
┌────────┐ ◀───┘
│ ◀──┤200 OK ├── │ │ │
└────────┘ ┌────────────────────────────────┐
│ │ ──┤ asynchronously report span ├────▶ │
│ │
│{ │
│ "traceId": "aa", │
│ "id": "6b", │
│ "name": "get", │
│ "timestamp": 1483945573944000,│
│ "duration": 386000, │
│ "annotations": [ │
│--snip-- │
└────────────────────────────────┘
跟蹤檢測報告以異步方式跨越,以防止與跟蹤系統相關的延遲或故障延遲或中斷用戶代碼。
這是一個示例序列的http跟蹤,其中用戶代碼調用資源/ foo。這個結果是單個Span,在用戶代碼收到http響應后異步發送到Zipkin。
┌─────────────┐ ┌───────────────────────┐ ┌─────────────┐ ┌──────────────────┐
│ User Code │ │ Trace Instrumentation │ │ Http Client │ │ Zipkin Collector │
└─────────────┘ └───────────────────────┘ └─────────────┘ └──────────────────┘
│ │ │ │
┌─────────┐
│ ──┤GET /foo ├─▶ │ ────┐ │ │
└─────────┘ │ record tags
│ │ ◀───┘ │ │
────┐
│ │ │ add trace headers │ │
◀───┘
│ │ ────┐ │ │
│ record timestamp
│ │ ◀───┘ │ │
┌─────────────────┐
│ │ ──┤GET /foo ├─▶ │ │
│X-B3-TraceId: aa │ ────┐
│ │ │X-B3-SpanId: 6b │ │ │ │
└─────────────────┘ │ invoke
│ │ │ │ request │
│
│ │ │ │ │
┌────────┐ ◀───┘
│ │ ◀─────┤200 OK ├─────── │ │
────┐ └────────┘
│ │ │ record duration │ │
┌────────┐ ◀───┘
│ ◀──┤200 OK ├── │ │ │
└────────┘ ┌────────────────────────────────┐
│ │ ──┤ asynchronously report span ├────▶ │
│ │
│{ │
│ "traceId": "aa", │
│ "id": "6b", │
│ "name": "get", │
│ "timestamp": 1483945573944000,│
│ "duration": 386000, │
│ "annotations": [ │
│--snip-- │
└────────────────────────────────┘
跟蹤檢測報告以異步方式跨越,以防止與跟蹤系統相關的延遲或故障延遲或中斷用戶代碼。
Transport。
由儀器庫發送的span必須從跟蹤到Zipkin收集器的服務傳輸。 主要支持三種傳輸: HTTP, Kafka 和 Scribe.
由儀器庫發送的span必須從跟蹤到Zipkin收集器的服務傳輸。 主要支持三種傳輸: HTTP, Kafka 和 Scribe.
組件
Zipkin有4個組件:
- 收集器(collector)
- 存儲(storage)
- 搜索(search)
- web UI
Zipkin有4個組件:
- 收集器(collector)
- 存儲(storage)
- 搜索(search)
- web UI
Web UI
我們創建了一個GUI,它為查看跟蹤提供了一個很好的界面。Web UI提供了一種基於服務,時間和注釋查看跟蹤的方法。注意:UI中沒有內置身份驗證!
我們創建了一個GUI,它為查看跟蹤提供了一個很好的界面。Web UI提供了一種基於服務,時間和注釋查看跟蹤的方法。注意:UI中沒有內置身份驗證!
.NetCore使用zipkin
第一款ZipkinTracer
按照官方文檔說明。
using ZipkinTracer.DependencyInjection;
using ZipkinTracer.Owin;
public class Startup
{
public void Configuration(IApplicationBuilder app)
{
app.UseZipkinTracer();
}
public void ConfigureServices(IServiceCollection services)
{
var config = new ZipkinConfig(new Uri("http://XXX:9411"), request => new Uri("https://yourservice.com"))
{
Bypass = request => request.GetUri().AbsolutePath.StartsWith("/allowed"),
SpanProcessorBatchSize = 10,
SampleRate = 0.5
}
services.AddZipkinTracer(config);
}
}
//GetUri()方法報錯。
改成如下方法:
public void Configuration(IApplicationBuilder app)
{
app.UseZipkinTracer();
}
public void ConfigureServices(IServiceCollection services)
{
var config = new ZipkinConfig(new Uri("http://weixinhe.cn:9411"));
services.AddZipkinTracer(config);
services.AddZipkinTracer(config);
}
客戶端調用
using ZipkinTracer.Http;
public class HomeController : Controller
{
private readonly IZipkinTracer _tracer;
public HomeController(IZipkinTracer tracer)
{
_tracer = tracer;
}
public async Task<ActionResult> Index()
{
using (var httpClient = new HttpClient(new ZipkinMessageHandler(_tracer))))
{
var response = await httpClient.GetAsync("http://www.google.com");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
}
}
return View();
}
}
運行程序,報錯! 
讓我們去官網看看問題,issue 里面有人提出了這個問題,
Registering zipkin tracer throws an error #10
下面有人回復:意思是需要重寫中間件。
I have the same issue. According to the documentation, the dependencies in the middlewares should be moved into the Invoke method. In this case, the ZipkinMiddleware has to be changed in my opinion.
年久失修,作者未回復。但是源碼都放在那里了,難道任由其報錯而無能為力么?這我不能忍受。所以,下載源碼,引用源碼項目。開啟調試之路。
按照官方文檔說明。
using ZipkinTracer.DependencyInjection;
using ZipkinTracer.Owin;
public class Startup
{
public void Configuration(IApplicationBuilder app)
{
app.UseZipkinTracer();
}
public void ConfigureServices(IServiceCollection services)
{
var config = new ZipkinConfig(new Uri("http://XXX:9411"), request => new Uri("https://yourservice.com"))
{
Bypass = request => request.GetUri().AbsolutePath.StartsWith("/allowed"),
SpanProcessorBatchSize = 10,
SampleRate = 0.5
}
services.AddZipkinTracer(config);
}
}
//GetUri()方法報錯。
改成如下方法:
public void Configuration(IApplicationBuilder app)
{
app.UseZipkinTracer();
}
public void ConfigureServices(IServiceCollection services)
{
var config = new ZipkinConfig(new Uri("http://weixinhe.cn:9411"));
services.AddZipkinTracer(config);
services.AddZipkinTracer(config);
}
客戶端調用
using ZipkinTracer.Http;
public class HomeController : Controller
{
private readonly IZipkinTracer _tracer;
public HomeController(IZipkinTracer tracer)
{
_tracer = tracer;
}
public async Task<ActionResult> Index()
{
using (var httpClient = new HttpClient(new ZipkinMessageHandler(_tracer))))
{
var response = await httpClient.GetAsync("http://www.google.com");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
}
}
return View();
}
}
運行程序,報錯!
讓我們去官網看看問題,issue 里面有人提出了這個問題,
Registering zipkin tracer throws an error #10
下面有人回復:意思是需要重寫中間件。
I have the same issue. According to the documentation, the dependencies in the middlewares should be moved into the Invoke method. In this case, the ZipkinMiddleware has to be changed in my opinion.
年久失修,作者未回復。但是源碼都放在那里了,難道任由其報錯而無能為力么?這我不能忍受。所以,下載源碼,引用源碼項目。開啟調試之路。
解決問題
右鍵查看屬性,竟然看不到目標框架。應該是版本太低了。 
查看依賴版本是framework4.6和.netstandard 1.3

那好辦,我們新建一個.netcore2.1版本的項目,然后將tracer的代碼都復制過去。 一些復制好並引用后,還是剛才那個錯。調試也沒有看到哪里報錯,只是最終頁面報錯了。所以我們繼續搜索。
Cannot consume scoped service 'XXXX' from singleton 'XXX'.
右鍵查看屬性,竟然看不到目標框架。應該是版本太低了。
查看依賴版本是framework4.6和.netstandard 1.3
那好辦,我們新建一個.netcore2.1版本的項目,然后將tracer的代碼都復制過去。 一些復制好並引用后,還是剛才那個錯。調試也沒有看到哪里報錯,只是最終頁面報錯了。所以我們繼續搜索。
Cannot consume scoped service 'XXXX' from singleton 'XXX'.
依賴注入的知識普及
無法從Singleton消耗Scoped服務 - ASP.net核心DI范圍的一課
此網站已收集在.NetCore外國一些高質量博客分享,長期保持更新。
上面三篇文章普及了一些依賴注入的知識。sorry,這塊我研究的很淺。。。這次順帶了解了不少,以后要抽空專門研究一下。
Single:單例是一個將持續應用程序整個生命周期的實例。在Web術語中,這意味着在服務的初始請求之后,每個后續請求將使用相同的實例。這也意味着它跨越Web請求(因此,如果兩個不同的用戶訪問您的網站,代碼仍然使用相同的實例)。考慮單例的最簡單方法是,如果類中有靜態變量,則它是跨多個實例的單個值。
Scoped:范圍內的生命周期對象通常會簡化為“每個Web請求一個實例”,但實際上它比實際上更加微妙。無可否認,在大多數情況下,您可以將每個Web請求視為范圍對象。您可能會看到的常見問題是每個Web請求創建一次DBContext,或者創建一次NHibernate上下文,以便您可以將整個請求包含在事務中。作用域生存期對象的另一個非常常見的用途是當您要創建每個請求緩存時。 Scoped生命周期實際上意味着在創建的“范圍”對象中將是同一個實例。它恰好發生在.net核心中,它在“范圍”內包裝請求,但您實際上可以手動創建范圍
Transient:每次請求服務時,都會創建一個新實例。
關於上述的類似錯誤無法從單件服務 #2569中使用作用域服務'AutoMapper.IMapper',有用戶評論:
這是一個基本的設計約束。你不能讓單身人士(Single)依賴於瞬態(Scoped)或范圍內(Transient)的物品。這不是容器的錯,這些生命周期是不相容的。如果您需要兼容的生命周期,請選擇不同的生命周期。
Singleton < - Singleton 良好
Singleton < - Scoped 糟糕
Singleton < - Transient 糟糕
Scoped < - Singleton 良好
Scoped < - Scoped 良好
Scoped < - Transient 良好
TRANSIENT < - Singleton 良好
Transient < - Scoped 良好在范圍內,糟糕在范圍外 TRANSIENT< - TRANSIENT 良好
所以真的只有兩種情況“總是糟糕”,一種情況“有時候很糟糕”。 ASP.NET Core DI使這個非常明確,甚至將工廠方法傳遞給上下文對象,例如過濾器。過濾器是Singleton,因此您不能擁有構造函數依賴項。相反,您使用傳遞給過濾器方法的各種XyzContext對象來解析依賴項。
上述是十分有價值的評價,先收藏,以后細細品味。
有了這些基礎知識和良好建議,我們再來回顧下代碼。看到前面都是AddSingleton,后面是AddScoped,在上面的建議中是屬於糟糕的。雖然不清楚作者的意圖,但我們可以先把程序跑起來,以后用熟悉了再來仔細修復。
public static class ServiceCollectionExtensions
{
public static void AddZipkinTracer(this IServiceCollection services, ZipkinConfig config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
var maxSize = config.MaxQueueSize <= 0 ? 100 : config.MaxQueueSize;
services.AddSingleton(config);
services.AddSingleton(new BlockingCollection<Span>(maxSize));
services.AddSingleton<IServiceEndpoint, ServiceEndpoint>();
services.AddSingleton<ISpanProcessorTask, SpanProcessorTask>();
services.AddSingleton<ISpanProcessor, SpanProcessor>();
services.AddSingleton<ITraceInfoAccessor, TraceInfoAccessor>();
services.AddScoped<ISpanCollector, SpanCollector>();
services.AddScoped<IZipkinTracer, ZipkinClient>();
services.AddScoped<ISpanTracer, SpanTracer>();
}
}
好吧,臨時將三個AddScoped 改為 AddSingleton(); 運行項目。又開始報錯了。。。。說IZipkinTracer需要一個無參的構造函數。
InvalidOperationException: Could not create an instance of type 'ZipkinTracer.IZipkinTracer'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the 'tracer' parameter a non-null default value.
繼續搜索新的這個錯誤。
無法從Singleton消耗Scoped服務 - ASP.net核心DI范圍的一課
此網站已收集在.NetCore外國一些高質量博客分享,長期保持更新。
上面三篇文章普及了一些依賴注入的知識。sorry,這塊我研究的很淺。。。這次順帶了解了不少,以后要抽空專門研究一下。
Single:單例是一個將持續應用程序整個生命周期的實例。在Web術語中,這意味着在服務的初始請求之后,每個后續請求將使用相同的實例。這也意味着它跨越Web請求(因此,如果兩個不同的用戶訪問您的網站,代碼仍然使用相同的實例)。考慮單例的最簡單方法是,如果類中有靜態變量,則它是跨多個實例的單個值。
Scoped:范圍內的生命周期對象通常會簡化為“每個Web請求一個實例”,但實際上它比實際上更加微妙。無可否認,在大多數情況下,您可以將每個Web請求視為范圍對象。您可能會看到的常見問題是每個Web請求創建一次DBContext,或者創建一次NHibernate上下文,以便您可以將整個請求包含在事務中。作用域生存期對象的另一個非常常見的用途是當您要創建每個請求緩存時。 Scoped生命周期實際上意味着在創建的“范圍”對象中將是同一個實例。它恰好發生在.net核心中,它在“范圍”內包裝請求,但您實際上可以手動創建范圍
Transient:每次請求服務時,都會創建一個新實例。
關於上述的類似錯誤無法從單件服務 #2569中使用作用域服務'AutoMapper.IMapper',有用戶評論:
這是一個基本的設計約束。你不能讓單身人士(Single)依賴於瞬態(Scoped)或范圍內(Transient)的物品。這不是容器的錯,這些生命周期是不相容的。如果您需要兼容的生命周期,請選擇不同的生命周期。
Singleton < - Singleton 良好
Singleton < - Scoped 糟糕
Singleton < - Transient 糟糕
Scoped < - Singleton 良好
Scoped < - Scoped 良好
Scoped < - Transient 良好
TRANSIENT < - Singleton 良好
Transient < - Scoped 良好在范圍內,糟糕在范圍外 TRANSIENT< - TRANSIENT 良好
所以真的只有兩種情況“總是糟糕”,一種情況“有時候很糟糕”。 ASP.NET Core DI使這個非常明確,甚至將工廠方法傳遞給上下文對象,例如過濾器。過濾器是Singleton,因此您不能擁有構造函數依賴項。相反,您使用傳遞給過濾器方法的各種XyzContext對象來解析依賴項。
上述是十分有價值的評價,先收藏,以后細細品味。
有了這些基礎知識和良好建議,我們再來回顧下代碼。看到前面都是AddSingleton,后面是AddScoped,在上面的建議中是屬於糟糕的。雖然不清楚作者的意圖,但我們可以先把程序跑起來,以后用熟悉了再來仔細修復。
public static class ServiceCollectionExtensions
{
public static void AddZipkinTracer(this IServiceCollection services, ZipkinConfig config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
var maxSize = config.MaxQueueSize <= 0 ? 100 : config.MaxQueueSize;
services.AddSingleton(config);
services.AddSingleton(new BlockingCollection<Span>(maxSize));
services.AddSingleton<IServiceEndpoint, ServiceEndpoint>();
services.AddSingleton<ISpanProcessorTask, SpanProcessorTask>();
services.AddSingleton<ISpanProcessor, SpanProcessor>();
services.AddSingleton<ITraceInfoAccessor, TraceInfoAccessor>();
services.AddScoped<ISpanCollector, SpanCollector>();
services.AddScoped<IZipkinTracer, ZipkinClient>();
services.AddScoped<ISpanTracer, SpanTracer>();
}
}
好吧,臨時將三個AddScoped 改為 AddSingleton(); 運行項目。又開始報錯了。。。。說IZipkinTracer需要一個無參的構造函數。
InvalidOperationException: Could not create an instance of type 'ZipkinTracer.IZipkinTracer'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the 'tracer' parameter a non-null default value.
繼續搜索新的這個錯誤。
模型綁定的知識普及
Model bound complex types must not be abstract or value types and must have a parameterless constructor
asp.net mvc github的問題里面討論的很激烈。我只能根據翻譯大概猜測意思了。
用於簡單模型 - 視圖模型 - 模型屬性映射的基本對象映射器
Asp.net Core中的自定義模型綁定,3:模型綁定接口
又get到一個網址:http://www.dotnet-programming.com,已更新到.NetCore外國一些高質量博客分享
Model bound complex types must not be abstract or value types and must have a parameterless constructor
asp.net mvc github的問題里面討論的很激烈。我只能根據翻譯大概猜測意思了。
用於簡單模型 - 視圖模型 - 模型屬性映射的基本對象映射器
Asp.net Core中的自定義模型綁定,3:模型綁定接口
又get到一個網址:http://www.dotnet-programming.com,已更新到.NetCore外國一些高質量博客分享
精彩評論:
您正在混淆依賴注入和模型綁定。有一個很大的不同。請考慮執行以下操作。 注冊IModelFactory為服務:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IModelFactory>(ModelFactory.Current);
// Add framework services.
services.AddMvc();
}
現在在您的控制器中,用於FromServices獲取實例,並使用以下內容獲取創建模型所需的值FromForm:
[HttpPost]
public IActionResult CreateTemplate([FromForm] string name,
[FromServices] IModelFactory factory)
{
var item = factory.CreateTechnicalTaskTemplate(name);
repo.Templates.Add(item);
return View(nameof(TemplatesList));
}
您的工廠應該被視為一項服務。模型綁定需要POCO,而不是接口。
您正在混淆依賴注入和模型綁定。有一個很大的不同。請考慮執行以下操作。 注冊IModelFactory為服務:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IModelFactory>(ModelFactory.Current);
// Add framework services.
services.AddMvc();
}
現在在您的控制器中,用於FromServices獲取實例,並使用以下內容獲取創建模型所需的值FromForm:
[HttpPost]
public IActionResult CreateTemplate([FromForm] string name,
[FromServices] IModelFactory factory)
{
var item = factory.CreateTechnicalTaskTemplate(name);
repo.Templates.Add(item);
return View(nameof(TemplatesList));
}
您的工廠應該被視為一項服務。模型綁定需要POCO,而不是接口。
從入門到放棄
對不起,模型綁定這個錯,我沒看太懂,只能先放棄了。如果對着源碼都找不到解決辦法,我只能理解自己的知識太淺。。。時間也很晚了,程序員也是需要有業余生活的。關於zipkintracer的試用到此為止。下期使用官方推薦客戶端zipkin4net
。
看官們,你們也真的深入了解依賴注入和模型綁定么?
對不起,模型綁定這個錯,我沒看太懂,只能先放棄了。如果對着源碼都找不到解決辦法,我只能理解自己的知識太淺。。。時間也很晚了,程序員也是需要有業余生活的。關於zipkintracer的試用到此為止。下期使用官方推薦客戶端zipkin4net
。
看官們,你們也真的深入了解依賴注入和模型綁定么?
總結
本篇旅程雖然失敗,但也了解了zipkin的相關介紹,原理,也在解決問題的過程中加深了依賴注入的理解。模型綁定的概念還不是太清楚,抽空我再看看。真可謂:無心栽花花不成,無心插柳柳成蔭。來一句雞湯:努力向前走,總會有意想不到的收獲。
可參考資料
各大廠分布式鏈路跟蹤系統架構對比 Net和Java基於zipkin的全鏈路追蹤
本篇到此結束,感謝觀看。