基於.net core微服務(Consul、Ocelot、Docker、App.Metrics+InfluxDB+Grafana、Exceptionless、數據一致性、Jenkins)

原文: 基於.net core微服務(Consul、Ocelot、Docker、App.Metrics+InfluxDB+Grafana、Exceptionless、數據一致性、Jenkins)

一、微服務簡介

一種架構模式,提倡將單一應用程序劃分紅一組小的服務,服務之間互相協調、互相配合,爲用戶提供最終價值。每一個服務運行在其獨立的進程中,服務與服務間採用輕量級的通訊機制互相溝通(RESTful API)。每一個服務都圍繞着具體的業務進行構建,而且可以被獨立地部署到生產環境、類生產環境等。應儘可能避免統一的、集中式的服管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建。                     ——馬丁•福勒html

1.一、.net core下的微服務構件

  • 服務治理:Consul
  • API網關:Ocelot
  • 做業調度:Quartz.NET,Hangfire
  • 分佈式日誌:Exceptionless
  • ESB:Masstransit(RabbitMQ)
  • APM:Metrac.App,Buttfly

1.二、微架構

二、Consul

http api官方文檔地址:https://www.consul.io/api/index.htmlnode

Api本地url: http://localhost:8500/v1/agent/serviceslinux

2.一、Consul是什麼

是一個服務管理軟件。支持多數據中心下,分佈式高可用的,服務發現和配置共享。consul支持健康檢查容許存儲鍵值對。一致性協議採用 Raft 算法,用來保證服務的高可用。成員管理和消息廣播 採用GOSSIP協議,支持ACL訪問控制。git

服務註冊:一個服務將其位置信息在「中心註冊節點」註冊的過程。該服務通常會將它的主機IP地址以及端口號進行註冊,有時也會有服務訪問的認證信息,使用協議,版本號,以及關於環境的一些細節信息。github

服務發現:服務發現可讓一個應用或者組件發現其運行環境以及其它應用或組件的信息。用戶配置一個服務發現工具就能夠將實際容器跟運行配置分離開。常見配置信息包括:ip、端口號、名稱等。web

2.二、述語

  • Agent

    Agent是長期運行在每一個consul集羣成員節點上守護進程。經過命令consul agent啓動。Agentclientserver兩種模式。因爲每一個節點都必須運行agent,全部節點要麼是client要麼是server。全部的Agent均可以調用DNSHTTP API,並負責檢查和維護服務同步。算法

  • client
      運行client模式的Agent,將全部的RPCs轉發到Server。Client是相對無狀態的。Client惟一所作的是在後臺參與LAN gossip pool。只消耗少許的資源,少許的網絡帶寬。
  • Server
      運行Server模式的Agent,參與Raft quorum,維護集羣的狀態,響應RPC查詢,與其餘數據中心交互WAN gossip,轉發查詢到Leader或遠程數據中心。
  • Datacenter
      數據中心的定義彷佛是顯而易見的,有一些細節是必須考慮的。例如,在EC2,多個可用性區域是否被認爲組成了單一的數據中心?咱們定義數據中心是在同一個網絡環境中——私有的,低延遲,高帶寬。這不包括基於公共互聯網環境,可是對於咱們而言,在同一個EC2的多個可用性區域會被認爲是一個的數據中心。
  • Consensus
      當本系列文檔中,consensus,意味着leader election協議,以及事務的順序。因爲這些事務是基於一個有限狀態機,consensus的定義意味着複製狀態機的一致性。
  • Gossip
      consul是創建在Serf之上,提供了完成的Gossip協議,用於成員維護故障檢測、事件廣播。詳細細節參見gossip documentation。這足以知道gossip是基於UDP協議實現隨機的節點到節點的通訊,主要是在UDP。
  • LAN Gossip
      指的是LAN gossip pool,包含位於同一個局域網或者數據中心的節點。
  • WAN Gossip
      指的是WAN gossip pool,只包含server節點,這些server主要分佈在不一樣的數據中心或者通訊是基於互聯網或廣域網的。
  • RPC
      遠程過程調用。是容許client請求服務器的請求/響應機制。

2.三、部署結構圖

2.四、命令

  •  -advertise
    通知展示地址用來改變咱們給集羣中的其餘節點展示的地址,通常狀況下-bind地址就是展示地址
  • -bootstrap
    用來控制一個server是否在bootstrap模式,在一個datacenter中只能有一個server處於bootstrap模式,當一個server處於bootstrap模式時,能夠本身選舉爲raft leader。  
  • -bootstrap-expect
    在一個datacenter中指望提供的server節點數目,當該值提供的時候,consul一直等到達到指定sever數目的時候纔會引導整個集羣,該標記不能和bootstrap公用
  • -bind
    該地址用來在集羣內部的通信,集羣內的全部節點到地址都必須是可達的,默認是0.0.0.0
  • -client
    consul綁定在哪一個client地址上,這個地址提供HTTP、DNS、RPC等服務,默認是127.0.0.1
  • -config-file
    明確的指定要加載哪一個配置文件
  • -config-dir
    配置文件目錄,裏面全部以.json結尾的文件都會被加載
  • -data-dir
    提供一個目錄用來存放agent的狀態,全部的agent容許都須要該目錄,該目錄必須是穩定的,系統重啓後都繼續存在
  • -datacenter
    該標記控制agent容許的datacenter的名稱,默認是dc1
  • -encrypt
    指定secret key,使consul在通信時進行加密,key能夠經過consul keygen生成,同一個集羣中的節點必須使用相同的key
  • -join
    加入一個已經啓動的agent的ip地址,能夠屢次指定多個agent的地址。若是consul不能加入任何指定的地址中,則agent會啓動失敗,默認agent啓動時不會加入任何節點。
  • -retry-join
    和join相似,可是容許你在第一次失敗後進行嘗試。
  • -retry-interval
    兩次join之間的時間間隔,默認是30s
  • -retry-max
    嘗試重複join的次數,默認是0,也就是無限次嘗試
  • -log-level
    consul agent啓動後顯示的日誌信息級別。默認是info,可選:trace、debug、info、warn、err。
  • -node
    節點在集羣中的名稱,在一個集羣中必須是惟一的,默認是該節點的主機名
  • -protocol
    consul使用的協議版本
  • -rejoin
    使consul忽略先前的離開,在再次啓動後仍舊嘗試加入集羣中。
  • -server
    定義agent運行在server模式,每一個集羣至少有一個server,建議每一個集羣的server不要超過5個
  • -syslog
    開啓系統日誌功能,只在linux/osx上生效
  • -ui-dir
    提供存放web ui資源的路徑,該目錄必須是可讀的
  • -pid-file
    提供一個路徑來存放pid文件,可使用該文件進行SIGINT/SIGHUP(關閉/更新)agent

2.五、經常使用API

consul的主要接口是RESTful HTTP API,該API能夠用來增刪查改nodes、services、checks、configguration。全部的endpoints主要分爲如下類別:docker

  • kv - Key/Value存儲
  • agent - Agent控制
  • catalog - 管理nodes和services
  • health - 管理健康監測
  • session - Session操做
  • acl - ACL建立和管理
  • event - 用戶Events
  • status - Consul系統狀態

 

  • agent endpoints:agent endpoints用來和本地agent進行交互,通常用來服務註冊和檢查註冊,支持如下接口
    /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的服務項
  • catalog endpoints:catalog endpoints用來註冊/註銷nodes、services、checks
    /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
  • health endpoints:health endpoints用來查詢健康情況相關信息,該功能從catalog中單獨分離出來
    /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=
  • session endpoints:session endpoints用來create、update、destory、query sessions
    /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
  • acl endpoints:acl endpoints用來create、update、destory、query acl
    /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
  • event endpoints:event endpoints用來fire新的events、查詢已有的events
    /v1/event/fire/<name>: 觸發一個新的event,用戶event須要name和其餘可選的參數,使用PUT方法
    /v1/event/list: 返回agent知道的events
  • status endpoints:status endpoints用來或者consul 集羣的信息
    /v1/status/leader : 返回當前集羣的Raft leader
    /v1/status/peers : 返回當前集羣中同事

2.六、使用consul

  • 啓動
    語法:
    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
  • 把192.168.1.126加入集羣
    consul join 192.168.1.126
  • 查看節點raft信息
    consul operator raft list-peers

2.七、項目實例

  • 項目準備
    項目地址:https://github.com/786744873/HisMicroserviceSample
    項目部署說明:分別部署 192.168.103.203 、 192.168.103.207 兩臺服務器
  • 配置consul配置文件
    文件結構:
    │  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"]
                }    //請求頭
            }
        }]
    }
  • 分別啓動 192.168.103.203 、 192.168.103.207 上的應用基礎和進銷存服務,而後再啓動Consul,咱們讓 192.168.103.203 做爲主Consul
    第一臺service(192.168.103.203):
    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頁面查看信息

  • client
    consul agent -datacenter=dc1 -data-dir /tmp/consul -node cn1

    Mac OX系統,進入consul所在目錄執行:

    Sudo scp consul /usr/local/bin/

2.八、Consul DNS

DnsAgent.exe做爲DNS工具

[
  {
  "Pattern": "^.*\\.consul$",
  "NameServer": "127.0.0.1:8600",
  "QueryTimeout": 1000,
  "CompressionMutation": false
  }
]

訪問地址:http://服務名稱.service.consul

三、Ocelot

github地址:https://github.com/TomPallister/Ocelot

Ocelot的目標是使用.NET運行微服務/面向服務架構,咱們須要一個統一的入口進入咱們的服務,提供監控、鑑權、負載均衡等機制,也能夠經過編寫中間件的形式,來擴展Ocelot的功能。  Ocelot是一堆特定順序的中間件。

3.一、Ocelot使用

  • 安裝Ocelot
    Install-Package Ocelot
  • 引入在Program.cs中加載配置文件
    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();
    }
  • 修改Startup.cs
    public void ConfigureServices(IServiceCollection services)
    {       
        //注入配置文件
        services.AddOcelot(Configuration);
    }
    public  void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        //添加中間件
        app.UseOcelot().Wait();
    }
  • 建立配置文件(configuration.json)
    {
      "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

容器是一個打包了應用服務的環境。它是一個輕量級的虛擬機,每個容器由一組特定的應用和必要的依賴庫組成。

4.一、Docker-鏡像經常使用命令 

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

4.二、Docker-容器經常使用命令

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  鏡像名稱:提交容器到本地鏡像

4.四、Docker-Dockerfile

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指令指定父鏡像爲這個鏡像時,便會觸發。

4.五、Docker生成asp.net core鏡像和運行

發佈asp.net core項目,並在發佈文件夾下建立Dockerfile文件,複製下面內容

#父鏡像
FROM microsoft/aspnetcore

#設置工做目錄
WORKDIR /app

#複製發佈文件到/app下
COPY . /app

#設置端口
EXPOSE 80

#使用dotnet XXXXXXXXX.dll來運行asp.net core項目,注意大小寫
ENTRYPOINT ["dotnet", 「XXXXXXXXX.dll"]

4.六、Docker生成asp.net core鏡像和運行

docker build -t xxxxxxxxxxx:latest .
docker run -it -p 6801:6801  xxxxxxxxxxx:latest

注意:docker內部web的端口, 上述命令中,第二個端口爲docker內web的端口。

五、App.Metrics+InfluxDB+Grafana

建議:建議在網關上進行監控,由於網關上監控能夠監控全部

App.Metrics:https://www.app-metrics.io

InfluxDB1.5.1-1:https://portal.influxdata.com

Grafana-5.0.4:https://grafana.com/get

5.一、安裝使用

  • 下載 influxdb
    https://portal.influxdata.com
  • 下載  Grafana
    https://grafana.com/get
  • 運行influxdb-版本號下的influxd.exe
  • 運行grafana-版本號下,bin目錄下grafana-server.exe
  • 運行influxdb-版本號下的influx.exe,輸入 create database influxdbtest 建立數據庫,同時 create user "user1" with password '123456'  建立用戶
  • 配置Grafana,而後啓動網關程序,登陸localhost:3000查看監控信息,用戶名密碼是:admin

5.二、配置Grafana

5.2.一、配置數據源

5.2.二、配置Dashboard

咱們採用模板導入模式,將項目引用 App.Metrics 並訪問 App.Metrics 源地址:https://www.app-metrics.io/

獲取到InfluxDB對應的儀表盤編號2125,而後輸入使用該模板

導入成功後

5.三、App.Metrics監控數據採集

  • 項目nuget引用
    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
  • 修改配置文件 appsettings.json 
    "InfluxDB": {
      "IsOpen": true,//是否開啓監控
      "DataBaseName": "influxdbtest",//數據庫名稱
      "ConnectionString": "http://localhost:8086",//數據庫地址
      "username": "user1",//用戶名
      "password": "123456",//密碼
      "app": "HIS",//自定義名字
      "env": "Ocelot"//自定義環境
    }
  • 修改 Startup.cs 
    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();
    }

    接下來就能夠進行追蹤了

5.四、APM-Grafana告警

  • 打開grafana-版本號下,conf目錄下的 defaults.ini ,填寫[smtp]節點信息
    [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

     

六、Exceptionless

  • 在線方式
    https://exceptionless.com/註冊用戶,新建Organizations和Project,並選項目類型。
  • 離線方式

    下載地址:https://github.com/exceptionless/Exceptionless/releases
    解壓壓縮包,運行Start.bat
    系統會自動下載elasticsearch和kibana

    ElasticSearch是一個基於Lucene的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於RESTful web接口。Elasticsearch是用Java開發的,並做爲Apache許可條款下的開放源碼發佈,是當前流行的企業級搜索引擎。設計用於雲計算中,可以達到實時搜索,穩定,可靠,快速,安裝使用方便。

    Kibana是一個開源的分析與可視化平臺,設計出來用於和Elasticsearch一塊兒使用的。你能夠用kibana搜索、查看、交互存放在Elasticsearch索引裏的數據,使用各類不一樣的圖表、表格、地圖等kibana可以很輕易地展現高級數據分析與可視化。

6.一、建立組織

6.二、建立項目

6.三、集成Exceptionless 客戶端

Install-Package Exceptionless.AspNetCore

經過 API 密鑰執行  app.UseExceptionless("Qa3OzvEJC9FXo9SdwwFBv6bAkVbjWQKbV6hhtYEM")  方法

6.四、示例代碼

#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

6.五、本地部署

本地部署官方wiki

下載Windows版本安裝包,並進行解壓,而後雙擊運行Start.bat便可

須要環境:

  • .NET 4.6
  • Java 1.8+ (The JAVA_HOME environment variable must also be configured when using Windows.)
  • IIS Express 8+
  • PowerShell 3+

6.六、項目集成

注意:本地化不能再使用 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);
    ...
}

搞定

6.七、查詢語法

示例

6.八、常見問題

 

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();
    }
}
View Code

七、數據一致性

  • C:數據一致性(consistency):若是系統對一個寫操做返回成功,那麼以後的讀請求都必須讀到這個新數據;若是返回失敗,那麼全部讀操做都不能讀到這個數據,對調用者而言數據具備強一致性(strong consistency) (又叫原子性 atomic、線性一致性 linearizable consistency)
  • A:服務可用性(availability):全部讀寫請求在必定時間內獲得響應,可終止、不會一直等待
  • P:分區容錯性(partition-tolerance):在網絡分區的狀況下,被分隔的節點仍能正常對外服務

7.一、最終一致性

  • 可用性,可靠性,
  • 最終一致性:在微服務之間使用事件驅動通訊和發佈訂閱系統實現最終一致性

  • 強一致性:當更新操做完成以後,任何多個後續進程或者線程的訪問都會返回最新的更新過的值。這種是對用戶最友好的,就是用戶上一次寫什麼,下一次就保證能讀到什麼。根據 CAP 理論,這種實現須要犧牲可用性。=> 在傳統單體式應用中,大部分都是強一致性的應用,想一想咱們寫過多少工做單元模式的Code?
  • 弱一致性:系統並不保證續進程或者線程的訪問都會返回最新的更新過的值。系統在數據寫入成功以後,不承諾當即能夠讀到最新寫入的值,也不會具體的承諾多久以後能夠讀到。
  • 最終一致性:弱一致性的特定形式。系統保證在沒有後續更新的前提下,系統最終返回上一次更新操做的值。在沒有故障發生的前提下,不一致窗口的時間主要受通訊延遲,系統負載和複製副本的個數影響。
  • 爲保證可用性,互聯網分佈式架構中常常將強一致性需求轉換成最終一致性的需求,並經過系統執行冪等性的保證,保證數據的最終一致性

  在微服務架構中,各個微服務之間一般會使用事件驅動通訊和發佈訂閱系統實現最終一致性。

7.二、最終一致性-補償機制

  • Polly:實現重試,熔斷機制
  • 或提供後臺任務調度實現補償

7.三、冪等和防重

  • 其任意屢次執行對資源自己所產生的影響均與一次執行的影響相同。
  • 對重複刪除或返回成功結果;防重能夠在數據庫級別處理也以以在MQ級別

7.四、MassTransit

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

7.五、最簡單的發送/接收實例

這裏以MassTransit + RabbitMQ爲例子,首先請確保安裝了RabbitMQ,若是沒有安裝,能夠閱讀個人RabbitMQ在Windows環境下的安裝與使用去把RabbitMQ先安裝到你的電腦上。另外,RabbitMQ的背景知識也有一堆,有機會也仍是要了解下Exchange,Channel、Queue等內容。

  • 準備兩個控制檯程序,一個爲Sender(發送者),一個爲Receiver(接收者),並分別經過NuGet安裝MassTransit以及MassTransit.RabbitMQ
    Install-Package MassTransit
    Install-Package MassTransit.RabbitMQ
  • 編寫Sender
    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}");
        }
    }
    View Code
    這裏首先鏈接到個人RabbitMQ服務,而後向指定的Queue發送消息(這裏是一個ABC類型的實例對象)。
  • 編寫Receiver
    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; }
    }
    View Code

    對於Receiver,要作的事就只有兩件:一是鏈接到RabbitMQ,二是告訴RabbitMQ我要接收哪一個消息隊列的什麼類型的消息。

  • 測試一下:

7.六、一對一的發佈/訂閱實例(相似於RabbitMQ的工做模式)

除了簡單的發送/接收模式外,咱們用的更多的是發佈/訂閱這種模式。

注意:發佈方若是發佈時沒有訂閱方,發佈的數據將會丟失

  • 準備下圖所示的類庫和控制檯項目,並對除Messages類庫以外的其餘項目安裝MassTransit以及MassTransit.RabbitMQ。
  •  MEDemo_Entity 類庫:準備須要傳輸的實體類信息
    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; }
    }
  •  MEDemo_Producer :接收個人消息吧騷年們
    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)

  •  MEDemo_ConsumerA :我只接收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}");
        }
    }
  • 測試一下:啓動兩個MEDemo_ConsumerA,一個MEDemo_Producer

7.六、一對多的發佈/訂閱實例(隊列名不一樣便可)

  •  PSDemo_Entity 類庫:準備須要傳輸的實體類信息
    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; }
    }
  •  PSDemo_Publisher :發佈消息
    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();
        }
    }
  •  PSDemo_SubscriberA :訂閱EntityA和EntityB
    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()}");
        }
    }
  •  PSDemo_SubscriberB :訂閱EntityA
    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()}");
        }
    }
  • 測試一下:啓動PSDemo_SubscriberA和PSDemo_SubscriberB,一個PSDemo_Publisher

     

7.七、帶返回狀態消息的示例

以前的例子都是發佈以後,無論訂閱者有沒有收到以及收到後有沒有處理成功(即有沒有返回消息,相似於HTTP請求和響應),在MassTransit中提供了這樣的一種模式,而且還能夠結合GreenPipes的一些擴展方法實現重試、限流以及熔斷機制。這一部分詳見官方文檔:http://masstransit-project.com/MassTransit/usage/request-response.html

  1. 準備下圖所示的三個項目:經過NuGet安裝MassTransit以及MassTransit.RabbitMQ
  2.   RRDemo_Entity.Entity :準備請求和響應的消息傳輸類型
    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; }
    }
  3.  RRDemo_Server.Program 請求接收端
    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);
        }
    }
  4.  RRDemo_Client.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.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();
        }
    
    }
  5. 效果展現

    注意:這裏的請求方關閉後應答方則沒法將應答再回復給請求方,會丟失

7.八、帶Observer模式的發佈/訂閱示例

在某些場景中,咱們須要針對一個消息進行相似於AoP(面向切面編程)或者監控的操做,好比在發送消息以前和結束後記日誌等操做,咱們能夠藉助MassTransit中的Observer模式來實現。(在MassTransit的消息接收中,能夠經過兩種模式來實現:一種是基於實現IConsumer接口,另外一種就是基於實現IObserver接口)關於這一部分,詳見官方文檔:http://masstransit-project.com/MassTransit/usage/observers.html

  • 準備如下圖所示的項目:
  •  ObserverSubscriber 
    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; }
    
    }
  •  ObserverPublish 
    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;
        }
    }
  • 效果展現
    Publish:
    請出請按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
    --------------------------------------
    View Code
    Subscribe:
    ------------------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"
      }
    }
    --------------------------------------
    View Code

7.九、數據一致性示例

詳見:https://github.com/786744873/DataConsistentSample

 

八、Jenkins

官方地址:https://jenkins.io/

Jenkins 是一款流行的開源持續集成(CI)與持續部署(CD)工具,普遍用於項目開發,具備自動化構建、測試和部署等功能。

  使用Jenkins的目的在於:

  (1)持續、自動地構建/測試軟件項目。 
  (2)監控軟件開放流程,快速問題定位及處理,提高開發效率。

8.一、Jenkins下載與安裝

這裏咱們下載Windows版本的

安裝完成後會提示咱們解鎖 Jenkins

這裏選擇安裝推薦的插件

建立管理帳戶 => 也能夠直接使用admin帳戶繼續

配置Jenkins端口,默認8080,最好不要使用8080端口

修改Jenkins服務端口,改成8080-->8081

修改安裝目錄下 jenkins.xml 文件

而後重啓Jenkins服務

8.二、持續集成Asp.Net Core項目

  1. 咱們以Github上面的項目爲例,github項目地址:https://github.com/786744873/first.git
  2. 配置源代碼
  3. 構建觸發器(這裏每半小時輪詢一次)
  4. 構建



    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
  5. 保存,而後去配置構建郵件發送
    Jenkins->系統管理->系統設置
    設置系統管理員收件地址(實際上這邊配置的是發件人的郵箱地址):

     

  6. 繼續進行項目配置

  7. 構建項目

相關文章
相關標籤/搜索