在傳統的BS應用中,隨着業務需求的快速發展變化,需求不斷增加,迫切須要一種更加快速高效的軟件交付方式。微服務能夠彌補單體應用不足,是一種更加快速高效軟件架構風格。單體應用被分解成多個更小的服務,每一個服務有本身的獨立模塊,單獨部署,而後共同組成一個應用程序。把範圍限定到單個獨立業務模塊功能。分佈式部署在各臺服務器上。html
而Ocelot開發的目標就是使用.NET運行面向微服務/服務的架構,要達到這個目標須要一個統的系統入口點(咱們統稱爲API網關),同時須要與IdentityServer或相關的驗證服務進行集成。git
Ocelot同時是一系列按特定順序排列的中間件。github
包括(IdentityServer,Consul,Butterfly)等等。算法
Ocelot將HttpRequest對象操做到其配置指定的狀態,直到它到達請求構建器中間件,在該中間件中它建立HttpRequestMessage對象,該對象用於向下遊服務發出請求。發出請求的中間件是Ocelot管道中的最後一件事。它不會調用下一個中間件。下游服務的響應存儲在每一個請求範圍的存儲庫中,並在請求返回Ocelot管道時進行檢索。有一箇中間件將HttpResponseMessage映射到HttpResponse對象並返回給客戶端。數據庫
如下是部署Ocelot時使用的結構圖:json
與IdentityServer4的集成:設計模式
與Consul的集成:api
從上面基本的介紹能夠得出一個結論,OceLot微服務網關相似於經典設計模式的Façade模式,它將底層的複雜細節進行屏蔽,對外提供簡單而統一的調用方式,一般使用HTTP的RESTful API服務。此時,對於客戶端而言,能夠是PC端網頁,也能夠是移動設備,客戶端經過HTTP方式調用OceLot網關。緩存
看了前面的基於OceLot微服務的基礎介紹,發現OceLot已提供了足夠強大的功能及擴展能力,而咱們只須要進行集成,對就是集成(咱們不生產水,咱們只是大天然的搬運工),以下圖:安全
如圖,咱們採用了Nginx做爲入口的高可用及負載均衡,經過API網關做爲具體二次轉發接入的統一入口,先向IdentityService進行Login以進行驗證並獲取Token,在IdentityService的驗證過程當中會訪問數據庫以驗證。接着再經過Consul服務器獲取服務的所在服務器,最後再帶上Token經過API網關去訪問具體的服務A、B、C。這裏咱們的IdentityService基於IdentityServer4開發,它具備統一登陸驗證和受權的功能。
而API網關的模塊組件圖大體以下圖:
點擊進入:http://www.javashuo.com/article/p-qiungooz-eb.html。
Ocelot的主要功能是接管進入的http請求並把它們轉發給下游服務。目前是以另外一個http請求的形式(未來多是任何傳輸機制)。
Ocelot的路由還支持捕獲全部樣式的路由,用戶能夠指定他們想要匹配全部流量。若是你像下面那樣設置你的配置,請求將被直接代理(它不必定叫url,任何佔位符名稱均可以)。
{
"DownstreamPathTemplate": "/{url}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80,
}
],
"UpstreamPathTemplate": "/{url}",
"UpstreamHttpMethod": [ "Get" ]
}
該捕獲全部的優先級低於其餘任何ReRoute。 若是你的配置中還有下面的ReRoute,那麼Ocelot會在捕獲全部配置以前先匹配它。
{
DownstreamPathTemplate"/" "":,
DownstreamScheme"https" "":,
DownstreamHostAndPorts "": [
{
Host"10.0.10.1" "":,
Port80 "":,
}
],
UpstreamPathTemplate"/" "":,
UpstreamHttpMethod"Get" "": []
}
此功能容許您基於上游主機進行ReRoutes。 這是經過查看客戶端使用的主機頭來工做,而後將其用做識別ReRoute的信息的一部分。
爲了使用這個功能,在你的配置中加上以下配置。
{
DownstreamPathTemplate"/" "":,
DownstreamScheme"https" "":,
DownstreamHostAndPorts "": [
{
Host"10.0.10.1" "":,
Port80 "":,
}
],
UpstreamPathTemplate"/" "":,
UpstreamHttpMethod"Get" "": [],
UpstreamHost"somedomain.com" "":
}
上面的ReRoute只會匹配主機頭是somedomain.com的請求。
若是您沒有在ReRoue上設置UpstreamHost,則任何主機頭均可以匹配它。 這基本上是一個捕獲全部功能並保留構建功能時的現有功能。這意味着若是您有兩個相同的ReRoute,其中一個與UpstreamHost是null,另外一個有值。 Ocelot會傾向於設定值的那個。
這個功能在問題 216提出要求。
{
Priority0 "":
}
0是最低優先級,Ocelot將始終使用0做爲/{catchAll}路由條目,而且能夠硬編碼。以後,你能夠自由設置你想要的任何優先級。
例如你能夠這樣:
{
UpstreamPathTemplate"/goods/{catchAll}" "":,
Priority0 "":
}
還能夠:
{
UpstreamPathTemplate"/goods/delete" "":,
Priority1 "":
}
在上面的例子中,若是您向Ocelot請求/goods/delete,Ocelot將匹配/goods/delete這個ReRoute。不過在不設置優先級之前它會匹配/goods/{catchAll}(由於這是列表中的第一個ReRoute!)。
以上摘自官方的介紹,地址爲:https://ocelot.readthedocs.io/en/latest/features/routing.html
最終實現代碼以下:
調試時,確認啓動多個項目。
啓動多個項目:
進行路由測試:
經過PostMan測試發現,BookingAPI,及PassengerAPI已經過網關API成功訪問。
{
"ReRoutes": [ //路由是API網關最基本也是最核心的功能、ReRoutes下就是由多個路由節點組成。
{
"DownstreamPathTemplate": "", //下游服務模板
"UpstreamPathTemplate": "", //上游服務模板
"UpstreamHttpMethod": [ "Get" ],//上游方法類型Get,Post,Put
"AddHeadersToRequest": {},//須要在轉發過程當中添加到Header的內容
"FileCacheOptions": { //能夠對下游請求結果進行緩存,主要依賴於CacheManager實現
"TtlSeconds": 10,
"Region": ""
},
"ReRouteIsCaseSensitive": false,//重寫路由是否區分大小寫
"ServiceName": "",//服務名稱
"DownstreamScheme": "http",//下游服務schema:http, https
"DownstreamHostAndPorts": [ //下游服務端口號和地址
{
"Host": "localhost",
"Port": 8001
}
],
"RateLimitOptions": { //限流設置
"ClientWhitelist": [], //客戶端白名單
"EnableRateLimiting": true,//是否啓用限流設置
"Period": "1s", //每次請求時間間隔
"PeriodTimespan": 15,//恢復的時間間隔
"Limit": 1 //請求數量
},
"QoSOptions": { //服務質量與熔斷,熔斷的意思是中止將請求轉發到下游服務。當下遊服務已經出現故障的時候再請求也是無功而返,
而且增長下游服務器和API網關的負擔,這個功能是用的Polly來實現的,咱們只須要爲路由作一些簡單配置便可
"ExceptionsAllowedBeforeBreaking": 0, //容許多少個異常請求
"DurationOfBreak": 0, //熔斷的時間,單位爲秒
"TimeoutValue": 0 //若是下游請求的處理時間超過多少則自如將請求設置爲超時
}
}
],
"UseServiceDiscovery": false,//是否啓用服務發現
"Aggregates": [ //請求聚合
{
"ReRouteKeys": [ //設置須要聚合的路由key
"booking",
"passenger"
],
"UpstreamPathTemplate": "/api/getbookingpassengerinfo" //暴露給外部的聚合請求路徑
},
"GlobalConfiguration": { //全局配置節點
"BaseUrl": "https://localhost:5000" //網關基地址
}
}
{
"DownstreamPathTemplate": "/api/posts/{postId}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "10.0.1.10",
"Port": 5000,
},
{
"Host": "10.0.1.11",
"Port": 5000,
}
],
"UpstreamPathTemplate": "/posts/{postId}",
"LoadBalancer": "LeastConnection",
"UpstreamHttpMethod": [ "Put", "Delete" ]
}
若是不但願對請求作任何的處理,則可使用下面的萬能模板:(萬能模板的優先級最低,只要有其它的路由模板,其它的路由模板則會優先生效)
{
"DownstreamPathTemplate": "/{url}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80,
}
],
"UpstreamPathTemplate": "/{url}",
"UpstreamHttpMethod": [ "Get" ]
}
對多個產生衝突的路由設置優化級
{
"UpstreamPathTemplate": "/goods/{catchAll}"
"Priority": 0
}
{
"UpstreamPathTemplate": "/goods/delete"
"Priority": 1
}
能夠經過gateway將客戶端的多個請求聚合而後將結果一次返回到客戶端去,此時咱們須要給每一個模板指定一個key
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/booking",
"UpstreamPathTemplate": "/api/getbooking",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8001
}
],
"Key": "booking"
},
{
"DownstreamPathTemplate": "/api/passenger",
"UpstreamPathTemplate": "/api/getpassenger",
"UpstreamHttpMethod": [ "Get" ],
"ReRouteIsCaseSensitive": false,
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8002
}
],
"Key": "passenger"
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:5000"
},
"Aggregates": [
{
"ReRouteKeys": [
"booking",
"passenger"
],
"UpstreamPathTemplate": "/api/getbookingpassengerinfo"
}
]
}
須要注意的是:
有沒有以爲這裏的聚合很相似於GraphQL的功能,但實際上在Ocelot中並不打算實現GraphQL的功能,由於畢竟Ocelot的主要職責是實現網關的功能,聚合只是其中的一個feature,GraphQL提供了一個庫 graphql-dotnet ,咱們能夠用它來完成須要的功能,而在Ocelot中實現相似認證,受權等這樣它擅長的事情:
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/graphql",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "yourgraphqlhost.com",
"Port": 80
}
],
"UpstreamPathTemplate": "/graphql",
"DelegatingHandlers": [
"GraphQlDelegatingHandler"
]
}
]
}
在此感謝KenWang007 https://github.com/KenWang007/OcelotDemo 示例,查了好久的資料,最終經過上面的示例調試成功。
最後,github 有個 https://github.com/dbarkwell/Ocelot.ConfigEditor,這個項目實現了asp.net core mvc 的在線編輯路由。
IdentityServer4的介紹博客園上已經有很是多了,
點擊打開:http://www.javashuo.com/article/p-xhjrdazu-cs.html 能夠看一下。
這裏主要介強OceLot集成須要進行改動的地方。
參照http://www.javashuo.com/article/p-xhjrdazu-cs.html
實現後IdentityService代碼以下:
{
"DownstreamPathTemplate": "/connect/token",
"UpstreamPathTemplate": "/api/connect/token",
"UpstreamHttpMethod": [ "Post" ],
"ReRouteIsCaseSensitive": false,
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
}
],
"Key": "IdentityService",
"RateLimitOptions": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 15,
"Limit": 1
}
},
{
"DownstreamPathTemplate": "/api/passenger",
"UpstreamPathTemplate": "/api/passenger",
"UpstreamHttpMethod": [ "Get" ],
"ReRouteIsCaseSensitive": false,
"DownstreamScheme": "http",
"ServiceName": "API002",
"LoadBalancer": "NoLoadBalancer",
"UseServiceDiscovery": true,
"Key": "passenger",
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowScopes": [ "TWAPI" ]
}
},
啓動程序
未獲取Token的狀況下:
獲取Token:
帶上Token進行訪問:
經過調用發現,返回的結果和預期同樣,調用成功。
安裝Consul就不介紹,能夠參考:http://www.javashuo.com/article/p-ylcatmfj-hs.html 自行調試,按照上面的寫法須要很是注意的一點是OceLot9.X版本如下是OK的,最新版本的須要進行調整幾個地方:
Install-Package Ocelot.Provider.Consul
官方參考:https://ocelot.readthedocs.io/en/latest/features/servicediscovery.html,按照官方的調整後,調試經過
啓動程序及Consul
調用:
咱們發現,OceLot已經過Consul調用成功。
熔斷的意思是中止將請求轉發到下游服務。當下遊服務已經出現故障的時候再請求也是功而返,而且增長下游服務器和API網關的負擔。這個功能是用的Pollly來實現的,咱們只須要爲路由作一些簡單配置便可
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking":3,
"DurationOfBreak":5,
"TimeoutValue":5000
}
ExceptionsAllowedBeforeBreaking 容許多少個異常請求
DurationOfBreak 熔斷的時間,單位爲秒
TimeoutValue 若是下游請求的處理時間超過多少則自如將請求設置爲超時。
對請求進行限流能夠防止下游服務器由於訪問過載而崩潰,這個功能就是咱們的張善友添加進去的。很是優雅的實現,咱們只須要在路由下加一些簡單的配置便可以完成。
"RateLimitOptions": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 1
}
ClientWihteList 白名單
EnableRateLimiting 是否啓用限流
Period 統計時間段:1s, 5m, 1h, 1d
PeroidTimeSpan 多少秒以後客戶端能夠重試
Limit 在統計時間段內容許的最大請求數量
在 GlobalConfiguration下咱們還能夠進行如下配置
"RateLimitOptions": {
"DisableRateLimitHeaders": false,
"QuotaExceededMessage": "Customize Tips!",
"HttpStatusCode": 999,
"ClientIdHeader" : "Test"
}
Http頭 X-Rate-Limit 和 Retry-After 是否禁用
QuotaExceedMessage 當請求過載被截斷時返回的消息
HttpStatusCode 當請求過載被截斷時返回的http status
ClientIdHeader 用來識別客戶端的請求頭,默認是 ClientId
連續點擊幾下後,返回:429 Too Many Requests.
當下遊服務有多個結點的時候,咱們能夠在DownstreamHostAndPorts中進行配置。
{
"DownstreamPathTemplate": "/api/posts/{postId}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "10.0.1.10",
"Port": 5000,
},
{
"Host": "10.0.1.11",
"Port": 5000,
}
],
"UpstreamPathTemplate": "/posts/{postId}",
"LoadBalancer": "LeastConnection",
"UpstreamHttpMethod": [ "Put", "Delete" ]
}
LoadBalancer將決定負載均衡的算法
在負載均衡這裏,咱們還能夠和Consul結合來使用服務發現,同一個服務和Name配置成相同,則在請求時,會根據LoadBalancer請求的算法進行訪問,以下:
{
"service": {
"id": "count1",
"name": "Count",
"tags": ["dev"],
"address": "localhost",
"port": 5001
}
}
{
"service": {
"id": "count2",
"name": "Count",
"tags": ["dev"],
"address": "localhost",
"port": 5002
}
}
Swagger 是一個規範和完整的框架,用於生成、描述、調用和可視化 RESTful 風格的 Web 服務。整體目標是使客戶端和文件系統做爲服務器以一樣的速度來更新。文件的方法,參數和模型緊密集成到服務器端的代碼,容許API來始終保持同步你能夠經過一個文本編輯器來編輯 Swagger 文件,或者你也能夠從你的代碼註釋中自動生成。各類工具均可以使用 Swagger 文件來生成互動的 API 文檔。
在集成Swagger的時候,咱們同時須要考慮到與IdentityServer的集成,在調試的時候讓Swagger充許進行驗證。
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "BookingApi", Version = "v1" });
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "請輸入帶有Bearer的Token",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
//Json Token認證方式,此方式爲全局添加
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{ "Bearer", Enumerable.Empty<string>() }
});
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "BookingApi");
});
app.UseMvc();
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "PassengerApi", Version = "v1" });
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "請輸入帶有Bearer的Token",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
//Json Token認證方式,此方式爲全局添加
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
{ "Bearer", Enumerable.Empty<string>() }
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "PassengerApi");
});
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot().AddConsul();
services.AddMvcCore(option =>
{
}).AddAuthorization().AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5001";
options.RequireHttpsMetadata = false;
options.ApiName = "TWAPI";
});
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("ApiGateway", new Info { Title = "網關服務", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var apis = new List<string> { "BookingApi", "PassengerApi" };
app.UseMvc()
.UseSwagger()
.UseSwaggerUI(options =>
{
apis.ForEach(m =>
{
options.SwaggerEndpoint($"/{m}/swagger.json", m);
});
});
app.UseAuthentication();
app.UseOcelot().Wait();
}
啓動程序:
查看生成的API:
調用:
失敗,加上驗證:
最後調用成功。
數據監控分爲2個部分,一是基於Grafana+InfluxDB的計數。二是基於NLOG的日誌記錄。
如圖:
NLOG的配置:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="info"
internalLogFile="c:\ApiGatewayLog\NLog-AspNetCore2.txt">
<!-- the targets to write to -->
<targets>
<target xsi:type="File" name="Trace" fileName="c:\ApiGatewayLog\Trace-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" />
<target xsi:type="File" name="Debug" fileName="c:\ApiGatewayLog\Debug-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" />
<target xsi:type="File" name="Info" fileName="c:\ApiGatewayLog\Info-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" />
<target xsi:type="File" name="Warn" fileName="c:\ApiGatewayLog\Warn-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" />
<target xsi:type="File" name="Error" fileName="c:\ApiGatewayLog\Error-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" />
<target xsi:type="File" name="Fatal" fileName="c:\ApiGatewayLog\Fatal-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="Trace"/>
<logger name="*" minlevel="Debug" writeTo="Debug"/>
<logger name="*" minlevel="Info" writeTo="Info"/>
<logger name="*" minlevel="Warn" writeTo="Warn" />
<logger name="*" minlevel="Error" writeTo="Error"/>
<logger name="*" minlevel="Fatal" writeTo="Fatal"/>
</rules>
</nlog>
Grafana+InfluxDB的安裝教程可在園子裏搜一下,比較多,Grafana+InfluxDB安裝完成後,最終效果圖以下:
API
Booking Api 測試用API
PassengerApi 測試用API
ApiGateWay
Tw.ApiGateway OceLot網關
Tw.IdentityService IdentityServer4服務
Libraies
Tw.Core 功能類庫
Tw.Redis 功能類庫
Tw.Repository 類庫
Tw.Service 業務操做庫
MiddleWare
Tw.IdentityServer IdentityServer4中間件源碼
Tw.IdentityServer4.AccessTokenValidation 中間件源碼
Tw.Ocelot OceLot源碼
Tw.Ocelot.Provider.Consul 源碼
下載地址:https://github.com/littlewrong/ApiGateway
一、https://ocelot.readthedocs.io/en/latest/introduction/bigpicture.html 官網介紹
二、http://www.sohu.com/a/132033560_468635 微服務網關Ocelot
三、https://blog.csdn.net/ai52011/article/details/77645009 Surgin API網關
四、https://github.com/KenWang007/OcelotDemo KenWang007/OcelotDemo
五、http://www.javashuo.com/article/p-xhjrdazu-cs.html IdentityServer4實現Token認證登陸以及權限控制
六、http://www.cnblogs.com/edisonchou/archive/2018/07/08/9278775.html .NET Core微服務之基於Ocelot+IdentityServer實現統一驗證與受權
七、http://www.javashuo.com/article/p-ylcatmfj-hs.html Ocelot + Consul實踐
八、https://www.jianshu.com/p/05a1bf2545a0 .Netcore 2.0 Ocelot Api網關教程
九、https://www.cnblogs.com/jesse2013/p/net-core-apigateway-ocelot-docs.html .NET Core開源API網關 – Ocelot中文文檔
十、http://www.javashuo.com/article/p-hjroysqj-du.html Ocelot 集成Butterfly 實現分佈式跟蹤
後話:最後把程序搬到了Linux安裝部署,實驗成功。