微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。——Martin Fowler
- 小, 且专注于做一件事情
- 独立的进程中
- 轻量级的通信机制
- 松耦合、独立部署
- 同步通信方式:RPC、REST等
- 优点:
- 1.实现方便
- 2.协议通用,比如HTTP
- 3.系统架构简单,无需中间件代理
- 缺点:
- 1.客户端耦合服务方
- 2.通信双方必须同时在线,否则会造成阻塞
- 3.客户端需要知道服务方的Endpoint,或者需要支持服务发现机制
- 优点:
- 异步通信方式:消息队列
- 优点:
- 1.系统解耦和
- 2.通信双方可以互相对对方无感知
- 缺点:
- 1.额外的编程复杂性。比如需要考虑消息可靠传输、高性能,以及编程模型的变化等
- 2.额外的运维复杂性。比如消息中间件的稳定性、高可用性、扩展性等非功能特性
- 优点:
单机处理到达瓶颈,将单机上的项目复制部署到其他几台服务器上,每一台服务器就是该集群的一个节点,每个节点提供的服务都是一样的。集群需要配合负载均衡服务器一起使用,负载均衡服务器作用在于按每个节点的负载情况分配用户请求,以达到每个节点负载平衡
SOA是一种粗粒度、松耦合服务架构,服务之间通过简单、精确定义接口进行通讯。SOA只是一种架构设计模式,而SOAP、REST、RPC是根据这种设计模式构建出来的规范,其中SOAP通俗理解就是Http+Xml的形式,REST就是Http+Json的形式,RPC是基于Socket的形式。CXF就是典型的SOAP/REST框架,Dubbo就是典型的RPC框架,而Spring Cloud就是遵守REST规范的生态系统
微服务其实就是随着互联网的发展,复杂的平台、业务的出现,导致SOA架构向更细粒度的发展。微服务不再强调传统SOA架构里面比较重的ESB企业服务总线。微服务把所有的“思考”逻辑包括路由、消息解析等放在服务内部,去掉一个大一统的ESB,服务间轻通信,是比SOA更彻底的拆分
- 单机
- 集群:多台服务器部署相同应用构成一个集群
- 分布式:一个应用拆分成不同模块部署在多台服务器上(正是服务化架构的提出使搭建分布式系统成为了可能,SOA、微服务都属于分布式系统)
- 分布式集群:集群挂了一台,其他服务器一样可用,而单纯的分布式只有一个节点的模块系统,一旦节点挂了这个服务就不可调用,所以就有新的组合玩法分布式集群,将分布式架构的某一个模块系统采用集群架构部署
Spring Cloud是一组基于Spring Boot封装的微服务框架,提供了完整的分布式系统解决方案。它为基于JVM的云应用开发中涉及的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式
Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等
- 按伦敦站点,迭代发布版本Angel -> Brixton -> Camden [卡姆登区]-> Dalston– [多尔斯顿]
- 每个版本修复一轮严重 bug ,就会发布一个 “service releases” 版本,用 .SRX 来表示,X 是版本号
- 加载webEnvironment
- 从spring.factories中加载所有可用的ApplicationContextInitializer
- 从spring.factories中加载所有可用的ApplicationListener
- 设置main方法的定义类
- 从spring.factories中加载所有可用的SpringApplicationRunListener发起事件,listener响应事件
- Listener响应prepareEnvironment事件
- 设置是否打印banner
- 创建应用上下文,spring容器IOC
- prepareContext(),initializer初始化
- refreshContext(),启动tomcat
- afterRefresh(),获取所有的runner并执行run方法,例如spring batch的JobLauncherCommandLineRunner
- Listener响应context的finish事件
- 创建一个java web项目
- 下载第三方相关库(手动或maven下载jar)
- 配置web.xml(dispatcherSevlet、log、编码、session、mapping等等)
- 配置spring及MVC九大组件中需要的部分
- 开发业务
- 开发非业务功能(如:安全、健康检查)
- 下载安装并配置tomcat
- 构建war
- 部署项目到tomcat
- 简化依赖:通过maven中一个spring-boot-starter-xxx就可以把需要的功能模块所有指定版本的依赖包全部引入
- 简化容器:通过maven中一个spring-boot-starter-web配置就可以引入一个内置的tomcat
- 简化配置:通过@EnableAutoConfiguration就可以让spring boot智能化自动配置相应模块(需要classPath引入对应模块的jar来配合);通过@Configuration来减少甚至完全消除对xml的依赖
- 提供通用组件:提供常用的监控功能
-
显示name/id和版本号
-
显示在线状态
-
Logging日志级别管理
-
JMX beans管理
-
Threads会话和线程管理
-
Trace应用请求跟踪
-
应用运行参数信息,如:
Java系统属性
Java环境变量属性
内存信息
Spring 环境属性
- Eureka以RESTful API的方式为服务实例提供了注册、管理和查询等操作
- 可以运行多个Eureka实例构建集群达到高可用性
- Eureka Server提供可视化的监控页面
- Spring Cloud为服务治理做了一层抽象接口,所以在Spring Cloud应用中可以支持多种不同的服务治理框架,比如:Netflix Eureka、Consul、Zookeeper
- Eureka Server:服务的注册中心,负责维护注册的服务列表
- Service Provider:服务提供方,作为一个Eureka Client,向Eureka Server做服务注册、续约和下线等操作,注册的主要数据包括服务名、机器ip、端口号、域名等
- Service Consumer:服务消费方,作为一个Eureka Client,向Eureka Server获取Service Provider的注册信息,并通过远程调用与Service Provider进行通信
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。它可以通过在客户端中配置ribbonServerList来设置服务端列表去轮询访问以达到均衡负载的作用
当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务实例列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动
-
RoundRobinRule - 线性轮循策略
-
ZoneAwareLoadBalancer - 区域感知策略
Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。它使得编写Web服务客户端变得更加简单。我们只需要通过创建接口并用注解来配置它既可完成对Web服务接口的绑定。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现
Spring Cloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能
-
圆形颜色和大小:健康情况和流量
-
折线:2分钟内的吞吐量变化情况
-
hosts:集群内节点个数
-
median:每个请求时间的中位数
-
mean:平均每个请求消耗的时间
-
subsriberGetAccount:
绿:成功请求数量
蓝:断路数量
黄:超时的线程数量
紫:线程池拒绝次数,即线程不够用
红:失败或异常数量
灰:最后10秒错误率
-
host:各节点每秒的平均请求吞吐量
-
cluster:集群每秒的请求吞吐量
-
circuit:断路器状态
服务追踪分析:一个调用可能需要多个后台服务协同完成,随着服务的增多对调用链的分析也会越来越复杂。针对服务链路追踪的问题,Google发表了Dapper论文,介绍了他们如何进行服务追踪分析。其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以可视化地分析服务调用链路和服务间的依赖关系。
对应Dpper的开源实现是Zipkin,Spring Cloud Sleuth是对Zipkin的一个封装,对于Span、Trace等信息的生成、接入HTTP Request,以及向Zipkin Server发送采集信息等全部自动完成。
- 放在内存中存储
- 放在mysql中存储:
放在内存中的随着服务端的启动会出清空历史数据,如果想持久化保留这些数据,可以选择mysql的方式存储
- 安全处理,包括加解密签名验签
- 身份认证和鉴权,黑白名单
- 日志打印
- 动态路由和统一的异常处理
- 流量控制,灰度发布
- PRE Filters:是在把请求路由到目标节点前执行。如:认证、加载目标服务节点、打印日志
- ROUTING Filters:是把请求路由到目标服务的节点。到目标的请求就在这些filter中被创建,并通过Apache HttpClient或 Netflix Ribbon转发到目标节点
- POST Filters:是目标节点请求结束并返回到zuul后执行。可以把HTTP headers添加到返回给客户端的response中,并可以收集统计信息和健康信息,以及把目标节点的业务数据返回给客户
- ERROR Filters:任何一个步骤出错都会调用当前类型的filter
切换到RabbitMQ安装目录的sbin目录下D:\RabbitMQ Server\rabbitmq_server-3.6.12\sbin,执行rabbitmq-plugins enable rabbitmq_management命令启动
AMQP,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,同样,消息使用者也不用知道发送者的存在。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
- Publisher:Message的生产者
- Consumer:Message的消费者,Publisher产生的Message最终要到达Consumer
- Exchange:指定Message按什么规则路由到哪个Queue,Message先要到达Exchange
- Queue:Message的容器,等待被消费出去
- Routing key:Exchange就是根据这些定义好的Routing key将Message送到对应的Queue中去,是Exchange和Queue之间的桥梁
- Broker:Broker就是接收和分发消息的应用,RabbitMQ Server就是Message Broker
- VirtualHost:虚拟主机,一个Broker可以开多个VirtualHost,它的作用是用作不同用户的权限分离
- Connection:是Publisher/Consumer和Broker之间的TCP连接。断开连接的操作只会在Publisher/Consumer端进行,Broker不会断开连接,除非出现网络故障或者Broker服务出现问题,Broker服务宕机
- Channel: 如果每一次访问RabbitMQ就建立一个Connection,那在消息量大的时候建立TCP Connection的开销就会很大
- Publisher获取Conection,接着获取Channel
- 定义Exchange,Queue
- 使用RoutingKey将Queue一个个Binding到Exchange上
- Publisher通过指定的Exchange和RoutingKey将消息发送到对应的Queue上
- Consumer获取Connection,接着获取Channel,最后消费到指定Queue中的消息
Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。
在实际应用中,可能会发生消费者收到Queue中的消息,但没有处理完成就宕机(或出现其他意外)的情况,这种情况下就可能会导致消息丢失。为了避免这种情况发生,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(Message acknowledgment)后才将该消息从Queue中移除;如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。
如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。
前面我们讲到如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。
在上一节我们看到生产者将消息投递到Queue中,实际上这在RabbitMQ中这种事情永远都不会发生。实际的情况是,生产者将消息发送到Exchange(交换器,下图中的X),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。
生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。RabbitMQ为routing key设定的长度限制为255 bytes。
在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。
fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。下图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。以下图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。
topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:
- routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
- binding key与routing key一样也是句点号“. ”分隔的字符串
- binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
以下图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。
headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。 在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。 但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。
- 客户端发送请求(消息)时,在消息的属性(MessageProperties ,在AMQP 协议中定义了14中properties ,这些属性会随着消息一起发送)中设置两个值replyTo (一个Queue 名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue 中)和correlationId (此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败)
- 服务器端收到消息并处理
- 服务器端处理完消息后,将生成一条应答消息到replyTo 指定的Queue ,同时带上correlationId 属性
- 客户端之前已订阅replyTo 指定的Queue ,从中收到服务器的应答消息后,根据其中的correlationId 属性分析哪条请求被执行了,根据执行结果进行后续业务处理
Spring Cloud Netflix Sidecar的设计灵感来自Netflix Prana,简单的说,一个非jvm程序,如:c++、python等,想要注册到eureka,但是应用都是一堆别的语言写的,那应该如何实现呢?Sidecar的原理就是监听该应用所运行的端口,然后检测该程序的运行状态,非jvm应用需要应该实现一个健康检查,Sidecar能够以此来报告给Eureka注册中心该应用是up还是down状态。
{
"status":"UP"
}
每间办公室对应着一个docker容器,容器内跑了程序就叫一个微服务节点。每间办公室的房间号就是每一个容器的ip和port,公司名称就是微服务的服务名,如果一家公司规模较大有好几间办公室,那么就是多个容器组成一个高可用的微服务集群
- 每章节文字介绍引用自《Spring Cloud基础教程》
- [1]RabbitMQ的基本概念引用自我为什么要选择RabbitMQ,RabbitMQ简介,各种MQ选型对比
- [2]总结的例子引用自Spring实现微服务—进阶篇
- 部分图片引用自其上水印链接