基于.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. 构建项目

相关文章
相关标签/搜索