一種架構模式,提倡將單一應用程序劃分紅一組小的服務,服務之間互相協調、互相配合,爲用戶提供最終價值。每一個服務運行在其獨立的進程中,服務與服務間採用輕量級的通訊機制互相溝通(RESTful API)。每一個服務都圍繞着具體的業務進行構建,而且可以被獨立地部署到生產環境、類生產環境等。應儘可能避免統一的、集中式的服管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建。 ——馬丁•福勒html
http api官方文檔地址:https://www.consul.io/api/index.htmlnode
Api本地url: http://localhost:8500/v1/agent/serviceslinux
是一個服務管理軟件。支持多數據中心下,分佈式高可用的,服務發現和配置共享。consul支持健康檢查,容許存儲鍵值對。一致性協議採用 Raft 算法,用來保證服務的高可用。成員管理和消息廣播 採用GOSSIP協議,支持ACL訪問控制。git
服務註冊:一個服務將其位置信息在「中心註冊節點」註冊的過程。該服務通常會將它的主機IP地址以及端口號進行註冊,有時也會有服務訪問的認證信息,使用協議,版本號,以及關於環境的一些細節信息。github
服務發現:服務發現可讓一個應用或者組件發現其運行環境以及其它應用或組件的信息。用戶配置一個服務發現工具就能夠將實際容器跟運行配置分離開。常見配置信息包括:ip、端口號、名稱等。web
Agent是長期運行在每一個consul集羣成員節點上守護進程。經過命令consul agent啓動。Agent有client和server兩種模式。因爲每一個節點都必須運行agent,全部節點要麼是client要麼是server。全部的Agent均可以調用DNS或HTTP API,並負責檢查和維護服務同步。算法
consul的主要接口是RESTful HTTP API,該API能夠用來增刪查改nodes、services、checks、configguration。全部的endpoints主要分爲如下類別:docker
/v1/agent/checks : 返回本地agent註冊的全部檢查(包括配置文件和HTTP接口) /v1/agent/services : 返回本地agent註冊的全部 服務 /v1/agent/members : 返回agent在集羣的gossip pool中看到的成員 /v1/agent/self : 返回本地agent的配置和成員信息 /v1/agent/join/<address> : 觸發本地agent加入node /v1/agent/force-leave/<node>>: 強制刪除node /v1/agent/check/register : 在本地agent增長一個檢查項,使用PUT方法傳輸一個json格式的數據 /v1/agent/check/deregister/<checkID> : 註銷一個本地agent的檢查項 /v1/agent/check/pass/<checkID> : 設置一個本地檢查項的狀態爲passing /v1/agent/check/warn/<checkID> : 設置一個本地檢查項的狀態爲warning /v1/agent/check/fail/<checkID> : 設置一個本地檢查項的狀態爲critical /v1/agent/service/register : 在本地agent增長一個新的服務項,使用PUT方法傳輸一個json格式的數據 /v1/agent/service/deregister/<serviceID> : 註銷一個本地agent的服務項
/v1/catalog/register : Registers a new node, service, or check /v1/catalog/deregister : Deregisters a node, service, or check /v1/catalog/datacenters : Lists known datacenters /v1/catalog/nodes : Lists nodes in a given DC /v1/catalog/services : Lists services in a given DC /v1/catalog/service/<service> : Lists the nodes in a given service /v1/catalog/node/<node> : Lists the services provided by a node
/v1/healt/node/<node>: 返回node所定義的檢查,可用參數?dc= /v1/health/checks/<service>: 返回和服務相關聯的檢查,可用參數?dc= /v1/health/service/<service>: 返回給定datacenter中給定node中service /v1/health/state/<state>: 返回給定datacenter中指定狀態的服務,state能夠是"any", "unknown", "passing", "warning", or "critical",可用參數?dc=
/v1/session/create: Creates a new session /v1/session/destroy/<session>: Destroys a given session /v1/session/info/<session>: Queries a given session /v1/session/node/<node>: Lists sessions belonging to a node /v1/session/list: Lists all the active sessions
/v1/acl/create: Creates a new token with policy /v1/acl/update: Update the policy of a token /v1/acl/destroy/<id>: Destroys a given token /v1/acl/info/<id>: Queries the policy of a given token /v1/acl/clone/<id>: Creates a new token by cloning an existing token /v1/acl/list: Lists all the active tokens
/v1/event/fire/<name>: 觸發一個新的event,用戶event須要name和其餘可選的參數,使用PUT方法 /v1/event/list: 返回agent知道的events
/v1/status/leader : 返回當前集羣的Raft leader
/v1/status/peers : 返回當前集羣中同事
consul agent -server -datacenter=數據中心名稱 -bootstrap -data-dir 數據存放路徑 -config-file 配置文件路徑 -ui-dir UI存放路徑 -node=n1 -bind 本機IP
註冊成Windows服務shell
sc.exe create "Consul" binPath= "E:\Consul\consule.exe agent -server -datacenter=數據中心名稱 -bootstrap -data-dir 數據存放路徑 -config-file 配置文件路徑 -ui-dir UI存放路徑 -node=n1 -bind 本機IP"
示例:數據庫
consul agent -server -datacenter=dc1 -bootstrap -data-dir /tmp/consul -config-file ./conf -ui-dir ./dist -node=n1 -bind 127.0.0.1
consul members
consul join 192.168.1.126
consul operator raft list-peers
│ consul.exe
│
├─conf
│ service.json
│ watchs.json
│ xacl.json
│
├─data
├─dist
service.json(服務發現配置):
{ "encrypt": "7TnJPB4lKtjEcCWWjN6jSA==", //加密祕鑰 "services": [{ "id": "BasicsService", //服務id "name": "BasicsService", //服務名稱 "tags": ["BasicsService"], //服務標籤 "address": "192.168.103.203", //服務地址 "port": 6801, //端口 "checks": [{ "id": "BasicsServiceCheck", //檢查id "name": "BasicsServiceCheck", //檢查名稱 "http": "http://192.168.103.203:6801/health", //檢車接口地址 "interval": "10s", //檢查週期 "tls_skip_verify": false, //跳過驗證 "method": "GET", //檢查請求方法 "timeout": "1s" //請求超時時間 }] }, { "id": "InvoicingService", //服務id "name": "InvoicingService", //服務名稱 "tags": ["InvoicingService"], //服務標籤 "address": "192.168.103.203", //服務地址 "port": 6802, //端口 "checks": [{ "id": "InvoicingServiceCheck", //檢查id "name": "InvoicingServiceCheck", //檢查名稱 "http": "http://192.168.103.203:6802/health", //檢車接口地址 "interval": "10s", //檢查週期 "tls_skip_verify": false, //跳過驗證 "method": "GET", //檢查請求方法 "timeout": "1s" //請求超時時間 }] } ] }
watchs.json(服務監控配置):
{ "watches": [{ "type": "checks", //監控觸發類型 "handler_type": "http", //異常通知類型 "state": "critical", //監控觸發狀態 "http_handler_config": { "path": "http://localhost:6801/notice", //通知地址 "method": "POST", //通知請求方式 "timeout": "10s", //通知超時時間 "header": { "Authorization": ["Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZ3N3IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2V4cGlyYXRpb24iOiIyMDIyLzEyLzMxIDEyOjM2OjEyIiwibmJmIjoxNTE0Njk0OTcyLCJleHAiOjE1MTQ3MzA5NzIsImlzcyI6ImdzdyIsImF1ZCI6ImdzdyJ9.jPu1yZ8jORN5QgCuPV50sYOKvX88GLSDiRX_0fpEzU4"] } //請求頭 } }] }
consul agent -server -datacenter=dc1 -bootstrap -data-dir ./data -config-file ./conf -ui-dir ./dist -node=n1 -bind 192.168.103.203
第二臺service(192.168.103.207):
consul agent -server -datacenter=dc1 -data-dir ./data -config-file ./conf -ui-dir ./dist -node=n2 -bind 192.168.103.207
而後能夠經過訪問192.168.103.203:8500進入UI頁面查看信息
consul agent -datacenter=dc1 -data-dir /tmp/consul -node cn1
Mac OX系統,進入consul所在目錄執行:
Sudo scp consul /usr/local/bin/
DnsAgent.exe做爲DNS工具
[ { "Pattern": "^.*\\.consul$", "NameServer": "127.0.0.1:8600", "QueryTimeout": 1000, "CompressionMutation": false } ]
訪問地址:http://服務名稱.service.consul
github地址:https://github.com/TomPallister/Ocelot
Ocelot的目標是使用.NET運行微服務/面向服務架構,咱們須要一個統一的入口進入咱們的服務,提供監控、鑑權、負載均衡等機制,也能夠經過編寫中間件的形式,來擴展Ocelot的功能。 Ocelot是一堆特定順序的中間件。
Install-Package Ocelot
public static IWebHost BuildWebHost(string[] args) { IWebHostBuilder builder = new WebHostBuilder(); //注入WebHostBuilder return builder.ConfigureServices(service => { service.AddSingleton(builder); }) //加載configuration配置文人年 .ConfigureAppConfiguration(conbuilder => { conbuilder.AddJsonFile("appsettings.json"); conbuilder.AddJsonFile("configuration.json"); }) .UseContentRoot(Directory.GetCurrentDirectory()) .UseKestrel() .UseUrls("http://*:6800") .UseStartup<Startup>() .Build(); }
public void ConfigureServices(IServiceCollection services) { //注入配置文件 services.AddOcelot(Configuration); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //添加中間件 app.UseOcelot().Wait(); }
{ "ReRoutes": [ //路由配置 { "DownstreamPathTemplate": "/{url}", //下游請求路由 "DownstreamScheme": "http", //下游請求方式,有http或https "DownstreamHostAndPorts": [ //下游請求的host和端口,爲了配合負載均衡,能夠配置多項 { "Host": "localhost", "Port": 6801 } ], "UpstreamPathTemplate": "/basics/{url}", //上游請求路由 "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put" ], //上游請求謂詞 //"ServiceName": "BasicsService", //Consul中註冊服務的名稱 //"LoadBalancer": "RoundRobin", //負載均衡(可選)LeastConnection –請求空閒的Url RoundRobin – 輪詢請求 NoLoadBalance – 無負載均衡 //"UseServiceDiscovery": true, //是否啓用負載均衡 "ReRouteIsCaseSensitive": false, // "QoSOptions": { //熔斷設置(可選) "ExceptionsAllowedBeforeBreaking": 3, //容許異常請求數 "DurationOfBreak": 10, //熔斷時間,以秒爲單位 "TimeoutValue": 5000 //請求超時數,以毫秒爲單位 }, "HttpHandlerOptions": { // "AllowAutoRedirect": false, // "UseCookieContainer": false, // "UseTracing": false // }, "AuthenticationOptions": { // "AuthenticationProviderKey": "", // "AllowedScopes": [] // }, "RateLimitOptions": { //限流設置(可選) "ClientWhitelist": [ "admin" ], //白名單,不受限流控制的 "EnableRateLimiting": true, //是否啓用限流 "Period": "1m", //統計時間段:1s, 2m, 3h, 4d "PeriodTimespan": 15, //間隔多少秒後能夠重試 "Limit": 100 //設定時間段內容許的最大請求數 } }, { "DownstreamPathTemplate": "/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 6802 } ], "UpstreamPathTemplate": "/invoicing/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put" ], //"ServiceName": "InvoicingService", //"LoadBalancer": "RoundRobin", //"UseServiceDiscovery": true, "ReRouteIsCaseSensitive": false, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 }, "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false, "UseTracing": false }, "AuthenticationOptions": { "AuthenticationProviderKey": "", "AllowedScopes": [] }, "RateLimitOptions": { "ClientWhitelist": [ "admin" ], "EnableRateLimiting": true, "Period": "1m", "PeriodTimespan": 15, "Limit": 100 } }, { "DownstreamPathTemplate": "/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 6806 } ], "UpstreamPathTemplate": "/authentication/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Delete", "Put" ], //"ServiceName": "AuthenticationService", //"LoadBalancer": "RoundRobin", //"UseServiceDiscovery": true, "ReRouteIsCaseSensitive": false, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 }, "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false, "UseTracing": false }, "AuthenticationOptions": { "AuthenticationProviderKey": "", "AllowedScopes": [] }, "RateLimitOptions": { "ClientWhitelist": [ "admin" ], "EnableRateLimiting": true, "Period": "1m", "PeriodTimespan": 15, "Limit": 100 } } ], "GlobalConfiguration": { //全局設置 "ServiceDiscoveryProvider": {//Consul服務地址,用於上方的服務發現 "Host": "localhost", "Port": 8500 }, "RateLimitOptions": { //全侷限流設置(可選) "ClientIdHeader": "clientid", //識別請求頭,默認是 ClientId "QuotaExceededMessage": "access is denied", //被限流後,當請求過載時返回的提示消息 "HttpStatusCode": //600,當請求過載時返回的http狀態碼 "DisableRateLimitHeaders": false //此值指定是否禁用X-Rate-Limit和Rety-After標頭 } } }
容器是一個打包了應用服務的環境。它是一個輕量級的虛擬機,每個容器由一組特定的應用和必要的依賴庫組成。
docker images:查看本地鏡像,docker images ubu*,通配符查看 docker inspect ubuntu:查看鏡像詳細信息 docker search aspnetcore:搜索docker hub上符合要求的鏡像 docker pull microsoft/aspnetcore:拉取鏡像,在run時不用從docker hub拉取 docker rmi 鏡像ID1 鏡像ID2:刪除鏡像ID1和ID2,若是強制刪除加-f
docker create ubuntu:14.04:建立容器,處於中止狀態 docker ps:查看運行的容器,加-a查看全部容器。加-l查詢出最後建立的容器,加-n=3查看最後建立的3個容器 docker start 容器名:運行已存在的容器 docker stop 容器名:中止容器 docker rm 容器名:刪除容器,docker rm $(docker ps -a -q)刪除全部容器 docker run -i -t --name ubuntu14 ubuntu:14.04 /bin/bash:運行一個ubuntu14.04的,帶終端的容器,名字叫ubuntu14 ,-i用於打開容器的標準輸入,-t讓容器創建一個命令行終端 docker run --name back_ubuntu14 -d ubuntu:14.04 /bin/sh -c "while true;do echo hello world;sleep 1;done":-d是後臺開容器 docker attach 容器名:依附容器 docker logs -f --tail=5 back_ubuntu14:查看最近的5條日誌 docker top 容器名:查看容器進程 docker inspect 容器名:查看容器信息,查看具體子項docker inspect --format='{{.NetworkSettings.IPAddress}}' back_ubuntu14 docker export 容器名 >容器存儲名稱.tar:導出容器 win powershell下 docker export 容器ID -o 名字.tar docker import 容器存儲名稱.tar:導入鏡像 docker commit -m="abc" --author="gsw" 容器ID 鏡像名稱:提交容器到本地鏡像
FROM:指定待擴展的父級鏡像。除了註釋外,在文件開頭必須是一個FROM指令,接下來的指令便在這個父級鏡像的環境中運行,直到遇到下一個FROM指令。經過添加多個FROM指令,能夠在同一個Dockerfile文件中建立多個鏡像。 MAINTAINER:用來聲明建立的鏡像的做都信息。非必需 RUN:用來修改鏡像命令,經常使用來安裝庫、程序 以及配置程序。一條RUN指令執行完畢後,會在當前鏡像上建立一個新的鏡像層,接下來的指令會在新的鏡像上繼續執行。 EXPOSE:用來指明容器內進程對外開放的端口,多個端口之間使用空格隔開。運行容器時,經過參數-P(大寫)便可將EXPOSE裏所指定的端口映射到主機上國外的墜機端口,其隊容器或主機就能夠經過映射後的端口與此容器通訊。同時,咱們也能夠經過-p(小寫)參數將Dockerfile中EXPOSE中沒有的端口設置成公開的。 ADD:向新鏡像中添加文件,這個文件能夠是一個主機文件,也能夠是一個網絡文件,也能夠是一個文件夾。 VOLUME:在鏡像裏建立一個指定路徑的掛載點,這個路徑能夠來自主機或都其餘容器。多個容器能夠經過同一個掛載點共享數據,即使其中一個容器已經中止,掛載點也仍然能夠訪問,只有當掛載點的容器引用所有消失時,掛載點纔會自動刪除。 WORKDIR:爲接下來執行的指令指定一個新的工做目錄,這個目錄能夠是絕對目錄,也能夠是相對目錄。 ENV:設置容器運行的環境變量。在運行容器的時候,經過-e參數能夠修改這個環境變量值 ,也能夠添加新的環境變量 CMD:用來設置啓動容器時默認運行的命令。 ENTRYPOINT:與CMD相似,它也是用來指定容器啓動時默認運行的命令。 USER:爲容器的運行及接下來RUN、CMD、ENTRYPOINT等指令的運行指定用戶或UID ONBUILD:觸發指令。構建鏡像的時候,Docker的鏡像構建器會將全部的ONBUILD指令指定的命令保存到鏡像的元數據中,這些命令在當前鏡像的構建的構建過程當中並不會執行。只有新的鏡像用用FRMO指令指定父鏡像爲這個鏡像時,便會觸發。
發佈asp.net core項目,並在發佈文件夾下建立Dockerfile文件,複製下面內容
#父鏡像 FROM microsoft/aspnetcore #設置工做目錄 WORKDIR /app #複製發佈文件到/app下 COPY . /app #設置端口 EXPOSE 80 #使用dotnet XXXXXXXXX.dll來運行asp.net core項目,注意大小寫 ENTRYPOINT ["dotnet", 「XXXXXXXXX.dll"]
docker build -t xxxxxxxxxxx:latest . docker run -it -p 6801:6801 xxxxxxxxxxx:latest
注意:docker內部web的端口, 上述命令中,第二個端口爲docker內web的端口。
建議:建議在網關上進行監控,由於網關上監控能夠監控全部
App.Metrics:https://www.app-metrics.io
InfluxDB1.5.1-1:https://portal.influxdata.com
Grafana-5.0.4:https://grafana.com/get
咱們採用模板導入模式,將項目引用 App.Metrics 並訪問 App.Metrics 源地址:https://www.app-metrics.io/
獲取到InfluxDB對應的儀表盤編號2125,而後輸入使用該模板
導入成功後
Install-Package App.Metrics Install-Package App.Metrics.AspNetCore.Endpoints Install-Package App.Metrics.AspNetCore.Reporting Install-Package App.Metrics.AspNetCore.Tracking Install-Package App.Metrics.Reporting.InfluxDB
"InfluxDB": { "IsOpen": true,//是否開啓監控 "DataBaseName": "influxdbtest",//數據庫名稱 "ConnectionString": "http://localhost:8086",//數據庫地址 "username": "user1",//用戶名 "password": "123456",//密碼 "app": "HIS",//自定義名字 "env": "Ocelot"//自定義環境 }
public void ConfigureServices(IServiceCollection services) { ... #region Metrics監控配置 string IsOpen = Configuration.GetSection("InfluxDB:IsOpen").Value.ToLower();//是否打開 if (IsOpen == "true") { string database = Configuration.GetSection("InfluxDB")["DataBaseName"];//數據庫名稱 string InfluxDBConStr = Configuration.GetSection("InfluxDB")["ConnectionString"];//數據庫地址 string app = Configuration.GetSection("InfluxDB")["app"];//自定義名字 string env = Configuration.GetSection("InfluxDB")["env"];//自定義環境 string username = Configuration.GetSection("InfluxDB")["username"];//用戶名 string password = Configuration.GetSection("InfluxDB")["password"];//密碼 var uri = new Uri(InfluxDBConStr); //配置metrics var metrics = AppMetrics.CreateDefaultBuilder() .Configuration.Configure( options => { options.AddAppTag(app); options.AddEnvTag(env); }) .Report.ToInfluxDb( options => { options.InfluxDb.BaseUri = uri; options.InfluxDb.Database = database; options.InfluxDb.UserName = username; options.InfluxDb.Password = password; options.HttpPolicy.BackoffPeriod = TimeSpan.FromSeconds(30); options.HttpPolicy.FailuresBeforeBackoff = 5; options.HttpPolicy.Timeout = TimeSpan.FromSeconds(10); options.FlushInterval = TimeSpan.FromSeconds(5); }) .Build(); services.AddMetrics(metrics);//注入metrics services.AddMetricsReportScheduler();//報表 services.AddMetricsTrackingMiddleware();//追蹤 services.AddMetricsEndpoints(); //終點 } #endregion ... }
public async void Configure(IApplicationBuilder app, IHostingEnvironment env) { #region 使用中間件Metrics string IsOpen = Configuration.GetSection("InfluxDB")["IsOpen"].ToLower(); if (IsOpen == "true") { app.UseMetricsAllMiddleware(); app.UseMetricsAllEndpoints(); } #endregion //使用中間件 //await app.UseOcelot(); }
接下來就能夠進行追蹤了
[smtp] enabled = true host = smtp.163.com:25 user =ego_it # If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" password =****** cert_file = key_file = skip_verify = false from_address = ego_it@163.com from_name = Grafana ehlo_identity =
添加新的查詢條件
建立alert
下載地址:https://github.com/exceptionless/Exceptionless/releases
解壓壓縮包,運行Start.bat
系統會自動下載elasticsearch和kibana
ElasticSearch是一個基於Lucene的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於RESTful web接口。Elasticsearch是用Java開發的,並做爲Apache許可條款下的開放源碼發佈,是當前流行的企業級搜索引擎。設計用於雲計算中,可以達到實時搜索,穩定,可靠,快速,安裝使用方便。
Kibana是一個開源的分析與可視化平臺,設計出來用於和Elasticsearch一塊兒使用的。你能夠用kibana搜索、查看、交互存放在Elasticsearch索引裏的數據,使用各類不一樣的圖表、表格、地圖等kibana可以很輕易地展現高級數據分析與可視化。
Install-Package Exceptionless.AspNetCore
經過 API 密鑰執行 app.UseExceptionless("Qa3OzvEJC9FXo9SdwwFBv6bAkVbjWQKbV6hhtYEM") 方法
#region Exceptionless測試 try { ExceptionlessClient.Default.SubmitLog("調試Exceptionless.Logging.LogLevel.Debu", Exceptionless.Logging.LogLevel.Debug); ExceptionlessClient.Default.SubmitLog("錯誤Exceptionless.Logging.LogLevel.Error", Exceptionless.Logging.LogLevel.Error); ExceptionlessClient.Default.SubmitLog("大錯Exceptionless.Logging.LogLevel.fatal", Exceptionless.Logging.LogLevel.Fatal); ExceptionlessClient.Default.SubmitLog(" Exceptionless.Logging.LogLevel.Info", Exceptionless.Logging.LogLevel.Info); ExceptionlessClient.Default.SubmitLog(" Exceptionless.Logging.LogLevel.Off", Exceptionless.Logging.LogLevel.Off); ExceptionlessClient.Default.SubmitLog(" Exceptionless.Logging.LogLevel.Other", Exceptionless.Logging.LogLevel.Other); ExceptionlessClient.Default.SubmitLog(" Exceptionless.Logging.LogLevel.Trace", Exceptionless.Logging.LogLevel.Trace); ExceptionlessClient.Default.SubmitLog("Exceptionless.Logging.LogLevel.Warn", Exceptionless.Logging.LogLevel.Warn); var data = new Exceptionless.Models.DataDictionary(); data.Add("data1key", "data1value"); ExceptionlessClient.Default.SubmitEvent(new Exceptionless.Models.Event { Count = 1, Date = DateTime.Now, Data = data, Geo = "geo", Message = "message", ReferenceId = "referencelId", Source = "source", Tags = new Exceptionless.Models.TagSet() { "tags" }, Type = "type", Value = 1.2m }); ExceptionlessClient.Default.SubmitFeatureUsage("feature"); ExceptionlessClient.Default.SubmitNotFound("404 not found"); ExceptionlessClient.Default.SubmitException(new Exception("自定義異常")); throw new DivideByZeroException("throw DivideByZeroException的異常:" + DateTime.Now); } catch (Exception exc) { exc.ToExceptionless().Submit(); } #endregion
下載Windows版本安裝包,並進行解壓,而後雙擊運行Start.bat便可
須要環境:
注意:本地化不能再使用 app.UseExceptionless(apiKey: "tJxBWkCbgDLCMoKKqWII3Eyw4aJOsyOCgX26Yurm"); 形式來上傳日誌數據,應採用另外的方式:配置文件方式
"Exceptionless": { "ApiKey": "tJxBWkCbgDLCMoKKqWII3Eyw4aJOsyOCgX26Yurm", "ServerUrl": "http://localhost:50000", "DefaultData": { }, "DefaultTags": [ "xplat" ], "Settings": { "FeatureXYZEnabled": false } }
而後修改 Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ... //app.UseExceptionless(apiKey: "tJxBWkCbgDLCMoKKqWII3Eyw4aJOsyOCgX26Yurm"); //上方的方法本地化不適用 app.UseExceptionless(Configuration); ... }
搞定
示例
Invoke-WebRequest : 請求被停止: 未能建立 SSL/TLS 安全通道。
elasticsearch-XXX」,由於該路徑不存在。
解決方案:編輯Start-ElasticSearch.ps1,將所需的文件所有下載下來,而後解壓進行拷貝,以下圖,而後在雙擊運行Start.bat便可
幫助類:
/// <summary> /// /// </summary> public static class ExceptionLessLog { static bool IsInit = false; static ExceptionLessLog() { if (!IsInit) { #region Exceptionless配置 ExceptionlessClient.Default.Configuration.ApiKey = "KwqNUJ5njrnOehQTSYY6yXXXXXXXXXXXXXXX"; ExceptionlessClient.Default.Configuration.ServerUrl = "http://XXX.XXX.XXX.XXX:50000"; ExceptionlessClient.Default.Startup(); #endregion } } #region 日誌功能 /// <summary> /// 跟蹤 /// </summary> public static void Trace(string message, params string[] tags) { ExceptionlessClient.Default.CreateLog(message, LogLevel.Trace).AddTags(tags).Submit(); } /// <summary> /// 調試 /// </summary> public static void Debug(string message, params string[] tags) { ExceptionlessClient.Default.CreateLog(message, LogLevel.Debug).AddTags(tags).Submit(); } /// <summary> /// 信息 /// </summary> public static void Info(string message, params string[] tags) { ExceptionlessClient.Default.CreateLog(message, LogLevel.Info).AddTags(tags).Submit(); } /// <summary> /// 警告 /// </summary> public static void Warn(string message, params string[] tags) { ExceptionlessClient.Default.CreateLog(message, LogLevel.Warn).AddTags(tags).Submit(); } /// <summary> /// 錯誤 /// </summary> public static void Error(string message, params string[] tags) { ExceptionlessClient.Default.CreateLog(message, LogLevel.Error).AddTags(tags).Submit(); } #endregion /// <summary> /// 異常捕獲提交 /// </summary> /// <param name="exception"></param> /// <param name="pluginContextData"></param> /// <param name="client"></param> public static void Submit(this Exception exception, ContextData pluginContextData = null, ExceptionlessClient client = null) { exception.ToExceptionless().Submit(); } }
在微服務架構中,各個微服務之間一般會使用事件驅動通訊和發佈訂閱系統實現最終一致性。
MassTransit 是一個自由、開源、輕量級的消息總線, 用於使用. NET 框架建立分佈式應用程序。MassTransit 在現有消息傳輸上提供了一組普遍的功能, 從而使開發人員可以友好地使用基於消息的會話模式異步鏈接服務。基於消息的通訊是實現面向服務的體系結構的可靠和可擴展的方式。
官網地址:http://masstransit-project.com/,GitHub地址:https://github.com/MassTransit/MassTransit (目前:1590Star,719Fork)
相似的國外開源組件還有NServiceBus,沒有用過,聽說MassTransit比NServiceBus更加輕量級,而且在開發之初就選用了RabbitMQ做爲消息傳輸組件,固然MassTransit還支持Azure Service Bus。相似的國內開源組件則有園友savorboard(楊曉東)的CAP
這裏以MassTransit + RabbitMQ爲例子,首先請確保安裝了RabbitMQ,若是沒有安裝,能夠閱讀個人RabbitMQ在Windows環境下的安裝與使用去把RabbitMQ先安裝到你的電腦上。另外,RabbitMQ的背景知識也有一堆,有機會也仍是要了解下Exchange,Channel、Queue等內容。
Install-Package MassTransit
Install-Package MassTransit.RabbitMQ
class Program { static void Main(string[] args) { Console.Title = "客戶端"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); }); var uri = new Uri("rabbitmq://localhost/wyt"); var mes = Console.ReadLine(); while (null != mes) { Task.Run(() => SendCommand(bus, uri, mes)).Wait(); mes = Console.ReadLine(); } Console.ReadLine(); } private static async void SendCommand(IBusControl bus, Uri sendToUri, string mes) { var endPoint = await bus.GetSendEndpoint(sendToUri); var command=new Receiver.ABC() { Name = "張三", Birthday = DateTime.Now, Message = mes }; await endPoint.Send(command); Console.WriteLine($"發送的實體 Name={command.Name},Birthday={command.Birthday},Message={command.Message}"); } }
class Program { static void Main(string[] args) { Console.Title = "服務端"; //建立基於RabbitMq的bus var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); cfg.ReceiveEndpoint(host, "wyt", e => { e.Consumer<ConsumerABC>(); e.Consumer<ConsumerABC1>(); }); }); bus.Start(); Console.WriteLine("按任意鍵退出!"); Console.ReadLine(); bus.Stop(); } } public class ConsumerABC : IConsumer<ABC> { public async Task Consume(ConsumeContext<ABC> context) { await Console.Out.WriteLineAsync($"收到信息: {context.Message.Name},{context.Message.Birthday},{context.Message.Message}"); } } public class ConsumerABC1 : IConsumer<ABC> { public async Task Consume(ConsumeContext<ABC> context) { await Console.Out.WriteLineAsync($"收到信息1: {context.Message.Name},{context.Message.Birthday},{context.Message.Message}"); } } public class ABC { public DateTime Birthday { get; set; } public string Name { get; set; } public string Message { get; set; } }
對於Receiver,要作的事就只有兩件:一是鏈接到RabbitMQ,二是告訴RabbitMQ我要接收哪一個消息隊列的什麼類型的消息。
除了簡單的發送/接收模式外,咱們用的更多的是發佈/訂閱這種模式。
注意:發佈方若是發佈時沒有訂閱方,發佈的數據將會丟失
public interface IEntity { int ID { get; set; } } public class Entity:IEntity { public int ID { get; set; } public string Name { get; set; } public DateTime Time { get; set; } } public class MyEntity:Entity { public int Age { get; set; } } public class YouEntity { public int ID { get; set; } public string Name { get; set; } public DateTime Time { get; set; } public int Age { get; set; } }
static void Main(string[] args) { Console.Title = "發佈方"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost"), hst => { hst.Username("guest"); hst.Password("guest"); }); }); bus.Start(); do { Console.WriteLine("請出請按q,不然請按其餘鍵!"); string value = Console.ReadLine(); if (value.ToLower() == "q") { break; } bus.Publish(new Entity() { ID = 1, Name = "張三", Time = DateTime.Now }); } while (true); bus.Stop(); }
這裏向RabbitMQ發佈了兩個不一樣類型的消息(Entity和IEntity)
class Program { static void Main(string[] args) { Console.Title="訂閱方"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); cfg.ReceiveEndpoint(host, "mewyt", e => { e.Consumer<IEntityConsumer>(); e.Consumer<EntityConsumer>(); e.Consumer<MyEntityConsumer>(); }); cfg.ReceiveEndpoint(host, "mewyt1", e => { e.Consumer<YouEntityConsumer>(); }); }); bus.Start(); Console.ReadLine(); bus.Stop(); } } public class IEntityConsumer : IConsumer<IEntity> { public async Task Consume(ConsumeContext<IEntity> context) { await Console.Out.WriteLineAsync($"IEntityConsumer 類型 {context.Message.GetType()} {context.Message.ID}"); } } public class EntityConsumer : IConsumer<Entity> { public async Task Consume(ConsumeContext<Entity> context) { await Console.Out.WriteLineAsync($"EntityConsumer 類型 {context.Message.GetType()} {context.Message.ID} {context.Message.Name} {context.Message.Time}"); } } public class MyEntityConsumer : IConsumer<MyEntity> { public async Task Consume(ConsumeContext<MyEntity> context) { await Console.Out.WriteLineAsync($"MyEntityConsumer 類型 {context.Message.GetType()} {context.Message.ID} {context.Message.Name} {context.Message.Time} {context.Message.Age}"); } } public class YouEntityConsumer : IConsumer<YouEntity> { public async Task Consume(ConsumeContext<YouEntity> context) { await Console.Out.WriteLineAsync($"YouEntityConsumer 類型 {context.Message.GetType()} {context.Message.ID} {context.Message.Name} {context.Message.Time} {context.Message.Age}"); } }
public class EntityA { public string Name { get; set; } public DateTime Time { get; set; } } public class EntityB { public string Name { get; set; } public DateTime Time { get; set; } public int Age { get; set; } }
class Program { static void Main(string[] args) { var bus= Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); }); do { Console.WriteLine("請出請按q,不然請按其餘鍵!"); string value = Console.ReadLine(); if (value.ToLower() == "q") { break; } bus.Publish(new PSDemo_Entity.EntityA() { Name="張三", Time = DateTime.Now }); bus.Publish(new PSDemo_Entity.EntityB() { Name = "李四", Time = DateTime.Now,Age=22 }); } while (true); bus.Stop(); } }
class Program { static void Main(string[] args) { Console.Title="訂閱者A"; var bus= Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); cfg.ReceiveEndpoint(host, "wytPSA", e => { e.Consumer<ConsumerA>(); e.Consumer<ConsumerB>(); }); }); bus.Start(); Console.ReadLine(); bus.Stop(); } } public class ConsumerA : IConsumer<PSDemo_Entity.EntityA> { public async Task Consume(ConsumeContext<PSDemo_Entity.EntityA> context) { await Console.Out.WriteLineAsync($"訂閱者A ConsumerA收到信息: {context.Message.Name} {context.Message.Time} 類型:{context.Message.GetType()}"); } } public class ConsumerB : IConsumer<PSDemo_Entity.EntityB> { public async Task Consume(ConsumeContext<PSDemo_Entity.EntityB> context) { await Console.Out.WriteLineAsync($"訂閱者A ConsumerB收到信息: {context.Message.Name} {context.Message.Time} 類型:{context.Message.GetType()}"); } }
class Program { static void Main(string[] args) { Console.Title="訂閱者B"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); cfg.ReceiveEndpoint(host, "wytPSB", e => { e.Consumer<ConsumerA>(); }); }); bus.Start(); Console.ReadLine(); bus.Stop(); } } public class ConsumerA : IConsumer<PSDemo_Entity.EntityA> { public async Task Consume(ConsumeContext<PSDemo_Entity.EntityA> context) { await Console.Out.WriteLineAsync($"訂閱者B ConsumerA收到信息: {context.Message.Name} {context.Message.Time} 類型:{context.Message.GetType()}"); } }
以前的例子都是發佈以後,無論訂閱者有沒有收到以及收到後有沒有處理成功(即有沒有返回消息,相似於HTTP請求和響應),在MassTransit中提供了這樣的一種模式,而且還能夠結合GreenPipes的一些擴展方法實現重試、限流以及熔斷機制。這一部分詳見官方文檔:http://masstransit-project.com/MassTransit/usage/request-response.html
public interface IRequestEntity { int ID { get; set; } string Name { get; set; } } public class RequestEntity : IRequestEntity { public int ID { get; set; } public string Name { get; set; } } public interface IResponseEntity { int ID { get; set; } string Name { get; set; } int RequestID { get; set; } } public class ResponseEntity : IResponseEntity { public int ID { get; set; } public string Name { get; set; } public int RequestID { get; set; } }
class Program { static void Main(string[] args) { Console.Title = "應答方"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); cfg.ReceiveEndpoint(host, "request_response_wyt", e => { e.Consumer<RequestConsumer>(); }); }); bus.Start(); Console.ReadLine(); bus.Stop(); } } public class RequestConsumer : IConsumer<IRequestEntity> { public async Task Consume(ConsumeContext<IRequestEntity> context) { Console.ForegroundColor = ConsoleColor.Red; await Console.Out.WriteLineAsync($"收到請求id={context.Message.ID} name={context.Message.Name}"); Console.ResetColor(); var response = new ResponseEntity { ID = 22, Name = $"李四", RequestID = context.Message.ID }; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"應答ID={response.ID},Name={response.Name},RequestID={response.RequestID}"); Console.ResetColor(); context.Respond(response); } }
static void Main(string[] args) { Console.Title = "請求方"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); //重試 cfg.UseRetry(ret => { ret.Interval(3, 10);// 消費失敗後重試3次,每次間隔10s }); //限流 cfg.UseRateLimit(1000, TimeSpan.FromSeconds(100));// 1分鐘之內最多1000次調用訪問 //熔斷 cfg.UseCircuitBreaker(cb => { cb.TrackingPeriod = TimeSpan.FromSeconds(60);//1分鐘 cb.TripThreshold = 15;// 當失敗的比例至少達到15%纔會啓動熔斷 cb.ActiveThreshold = 10;// 當失敗次數至少達到10次纔會啓動熔斷 cb.ResetInterval = TimeSpan.FromMinutes(5);// 當在1分鐘內消費失敗率達到15%或調用了10次仍是失敗時,暫停5分鐘的服務訪問 }); }); bus.Start(); var serviceAddress = new Uri($"rabbitmq://localhost/request_response_wyt"); var client = bus.CreateRequestClient<IRequestEntity, IResponseEntity>(serviceAddress, TimeSpan.FromHours(10)); // 建立請求客戶端,10H以內木有回饋則認爲是超時(Timeout) while (true) { Console.WriteLine("請出請按q,不然請按其餘鍵!"); string value = Console.ReadLine(); if (value.ToLower() == "q") { break; } Task.Run(async () => { var request = new RequestEntity() { ID = 1, Name = "張三" }; var response = await client.Request(request); Console.WriteLine($"請求ID={request.ID},Name={request.Name}"); Console.WriteLine($"應籤ID={response.ID},Name={response.Name},RequestID={response.RequestID}"); }).Wait(); } }
注意:這裏的請求方關閉後應答方則沒法將應答再回復給請求方,會丟失
在某些場景中,咱們須要針對一個消息進行相似於AoP(面向切面編程)或者監控的操做,好比在發送消息以前和結束後記日誌等操做,咱們能夠藉助MassTransit中的Observer模式來實現。(在MassTransit的消息接收中,能夠經過兩種模式來實現:一種是基於實現IConsumer接口,另外一種就是基於實現IObserver接口)關於這一部分,詳見官方文檔:http://masstransit-project.com/MassTransit/usage/observers.html
class Program { static void Main(string[] args) { Console.Title = "訂閱方"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst => { hst.Username("guest"); hst.Password("guest"); }); cfg.ReceiveEndpoint(host, "ObserverTest", e => { e.Consumer<EntityConsumer>(); }); }); var observer = new ReceiveObserver(); var handle = bus.ConnectReceiveObserver(observer); bus.Start(); Console.ReadLine(); bus.Stop(); } } public class ReceiveObserver : IReceiveObserver { public Task PreReceive(ReceiveContext context) { Console.WriteLine("------------------PreReceive--------------------"); Console.WriteLine(Encoding.Default.GetString(context.GetBody())); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task PostReceive(ReceiveContext context) { Console.WriteLine("-----------------PostReceive---------------------"); Console.WriteLine(Encoding.Default.GetString(context.GetBody())); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task PostConsume<T>(ConsumeContext<T> context, TimeSpan duration, string consumerType) where T : class { Console.WriteLine("------------------PostConsume--------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task ConsumeFault<T>(ConsumeContext<T> context, TimeSpan elapsed, string consumerType, Exception exception) where T : class { Console.WriteLine("-----------------ConsumeFault---------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task ReceiveFault(ReceiveContext context, Exception exception) { Console.WriteLine("----------------ReceiveFault----------------------"); Console.WriteLine(Encoding.Default.GetString(context.GetBody())); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } } public class EntityConsumer : IConsumer<Entity> { public async Task Consume(ConsumeContext<Entity> context) { await Console.Out.WriteLineAsync($"IEntityConsumer 類型 {context.Message.GetType()} {context.Message.ID} {context.Message.Age} {context.Message.Name} {context.Message.Time}"); } } public class Entity { public int ID { get; set; } public int Age { get; set; } public string Name { get; set; } public DateTime Time { get; set; } }
class Program { static void Main(string[] args) { Console.Title = "發佈方"; var bus = Bus.Factory.CreateUsingRabbitMq(cfg => { var host = cfg.Host(new Uri("rabbitmq://localhost"), hst => { hst.Username("guest"); hst.Password("guest"); }); }); var observer = new SendObserver(); var handle = bus.ConnectSendObserver(observer); var observer1 = new PublishObserver(); var handle1 = bus.ConnectPublishObserver(observer1); bus.Start(); do { Console.WriteLine("請出請按q,不然請按其餘鍵!"); string value = Console.ReadLine(); if (value.ToLower() == "q") { break; } bus.Publish(new Entity() { ID = 1, Age = 10, Name = "張三", Time = DateTime.Now }); } while (true); bus.Stop(); } } public class PublishObserver : IPublishObserver { public Task PrePublish<T>(PublishContext<T> context) where T : class { Console.WriteLine("------------------PrePublish--------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task PostPublish<T>(PublishContext<T> context) where T : class { Console.WriteLine("------------------PostPublish--------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task PublishFault<T>(PublishContext<T> context, Exception exception) where T : class { Console.WriteLine("------------------PublishFault--------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } } public class SendObserver : ISendObserver { public Task PreSend<T>(SendContext<T> context) where T : class { Console.WriteLine("------------------PreSend--------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task PostSend<T>(SendContext<T> context) where T : class { Console.WriteLine("------------------PostSend--------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } public Task SendFault<T>(SendContext<T> context, Exception exception) where T : class { Console.WriteLine("------------------SendFault--------------------"); Console.WriteLine($"ID={ (context.Message as Entity).ID},Name={(context.Message as Entity).Name},Time={(context.Message as Entity).Time}"); Console.WriteLine("--------------------------------------"); return Task.CompletedTask; } }
請出請按q,不然請按其餘鍵! 111111 請出請按q,不然請按其餘鍵! ------------------PrePublish-------------------- ID=1,Name=張三,Time=2019/5/2 12:23:23 -------------------------------------- ------------------PreSend-------------------- ID=1,Name=張三,Time=2019/5/2 12:23:23 -------------------------------------- ------------------PostSend-------------------- ID=1,Name=張三,Time=2019/5/2 12:23:23 -------------------------------------- ------------------PostPublish-------------------- ID=1,Name=張三,Time=2019/5/2 12:23:23 --------------------------------------
------------------PreReceive-------------------- { "messageId": "36500000-a632-9828-3029-08d6ceb5ea32", "conversationId": "36500000-a632-9828-ef7f-08d6ceb5ea37", "sourceAddress": "rabbitmq://localhost/bus-DESKTOP-PUOA6D7-dotnet-g3eyyyfggkcnt4wdbdmc7pxgn4?durable=false&autodelete=true", "destinationAddress": "rabbitmq://localhost/ObserverSubscriber:Entity", "messageType": [ "urn:message:ObserverSubscriber:Entity" ], "message": { "id": 1, "age": 10, "name": "張三", "time": "2019-05-02T12:23:23.2223222+08:00" }, "sentTime": "2019-05-02T04:23:23.3522586Z", "headers": {}, "host": { "machineName": "DESKTOP-PUOA6D7", "processName": "dotnet", "processId": 25668, "assembly": "ObserverPublish", "assemblyVersion": "1.0.0.0", "frameworkVersion": "4.0.30319.42000", "massTransitVersion": "4.1.0.1470", "operatingSystemVersion": "Microsoft Windows NT 6.2.9200.0" } } -------------------------------------- IEntityConsumer 類型 ObserverSubscriber.Entity 1 10 張三 2019/5/2 12:23:23 ------------------PostConsume-------------------- ID=1,Name=張三,Time=2019/5/2 12:23:23 -------------------------------------- -----------------PostReceive--------------------- { "messageId": "36500000-a632-9828-3029-08d6ceb5ea32", "conversationId": "36500000-a632-9828-ef7f-08d6ceb5ea37", "sourceAddress": "rabbitmq://localhost/bus-DESKTOP-PUOA6D7-dotnet-g3eyyyfggkcnt4wdbdmc7pxgn4?durable=false&autodelete=true", "destinationAddress": "rabbitmq://localhost/ObserverSubscriber:Entity", "messageType": [ "urn:message:ObserverSubscriber:Entity" ], "message": { "id": 1, "age": 10, "name": "張三", "time": "2019-05-02T12:23:23.2223222+08:00" }, "sentTime": "2019-05-02T04:23:23.3522586Z", "headers": {}, "host": { "machineName": "DESKTOP-PUOA6D7", "processName": "dotnet", "processId": 25668, "assembly": "ObserverPublish", "assemblyVersion": "1.0.0.0", "frameworkVersion": "4.0.30319.42000", "massTransitVersion": "4.1.0.1470", "operatingSystemVersion": "Microsoft Windows NT 6.2.9200.0" } } --------------------------------------
詳見:https://github.com/786744873/DataConsistentSample
官方地址:https://jenkins.io/
Jenkins 是一款流行的開源持續集成(CI)與持續部署(CD)工具,普遍用於項目開發,具備自動化構建、測試和部署等功能。
使用Jenkins的目的在於:
(1)持續、自動地構建/測試軟件項目。
(2)監控軟件開放流程,快速問題定位及處理,提高開發效率。
這裏咱們下載Windows版本的
安裝完成後會提示咱們解鎖 Jenkins
這裏選擇安裝推薦的插件
建立管理帳戶 => 也能夠直接使用admin帳戶繼續
配置Jenkins端口,默認8080,最好不要使用8080端口
修改Jenkins服務端口,改成8080-->8081
修改安裝目錄下 jenkins.xml 文件
而後重啓Jenkins服務
cd CITest cd CITest dotnet restore dotnet build dotnet publish -o "bin\Debug\netcoreapp2.0\publish" cd bin\Debug\netcoreapp2.0\publish docker rm clitest -f docker rmi clitest -f docker build -t clitest:latest . docker run -p 4555:4555 -d --name clitest clitest:latest
繼續進行項目配置