w3cschool-Spring Cloud
https://www.w3cschool.cn/spring_cloud/spring_cloud-ryjs2ixg.html
Spring Cloud(一)服务的注册与发现(Eureka)
Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中涉及的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud简介
Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。
微服务架构
微服务(Microservices Architecture)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
微服务的概念源于2014年3月Martin Fowler所写的章“Microservices”http://martinfowler.com/articles/microservices.html
微服务架构(Microservices Architecture)
微服务架构的核心思想是,一个应用是由多个小的、相互独立的、微服务组成,这些服务运行在自己的进程中,开发和发布都没有依赖。不同服务通过一些轻量级交互机制来通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。简单的来说,一个系统的不同模块转变成不同的服务!而且服务可以使用不同的技术加以实现!
微服务设计
那我们在微服务中应该怎样设计呢。以下是微服务的设计指南:
- 职责单一原则(Single Responsibility Principle):把某一个微服务的功能聚焦在特定业务或者有限的范围内会有助于敏捷开发和服务的发布。
- 设计阶段就需要把业务范围进行界定。
- 需要关心微服务的业务范围,而不是服务的数量和规模尽量小。数量和规模需要依照业务功能而定。
- 于SOA不同,某个微服务的功能、操作和消息协议尽量简单。
- 项目初期把服务的范围制定相对宽泛,随着深入,进一步重构服务,细分微服务是个很好的做法。
关于微服务架构的取舍
- 在合适的项目,合适的团队,采用微服务架构收益会大于成本。
- 微服务架构有很多吸引人的地方,但在拥抱微服务之前,也需要认清它所带来的挑战。
- 需要避免为了“微服务”而“微服务”。
- 微服务架构引入策略 – 对传统企业而言,开始时可以考虑引入部分合适的微服务架构原则对已有系统进行改造或新建微服务应用,逐步探索及积累微服务架构经验,而非全盘实施微服务架构。
更多关于微服务架构内容-请参考我的另一篇文章:《什什么是微服务架构?》
服务治理
由于Spring Cloud为服务治理做了一层抽象接口,所以在Spring Cloud应用中可以支持多种不同的服务治理框架,比如:Netflix Eureka、Consul、Zookeeper。在Spring Cloud服务治理抽象层的作用下,我们可以无缝地切换服务治理实现,并且不影响任何其他的服务注册、服务发现、服务调用等逻辑。
Spring Cloud Eureka
Spring Cloud Eureka来实现服务治理。
Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。
Eureka Server
提供服务注册和发现
添加依赖
在项目 spring-cloud-eureka-service
pom.xml
中引入需要的依赖内容:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
开启服务注册
通过 @EnableEurekaServer
注解启动一个服务注册中心提供给其他应用进行对话,这个注解需要在springboot工程的启动application类上加
package io.ymq.example.eureka.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
添加配置
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.yml
配置文件中增加如下信息:
registerWithEureka: false
fetchRegistry: false
完整配置
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
访问服务
启动工程后,访问:http://localhost:8761/
可以看到下面的页面,其中还没有发现任何服务。
Service Provider
- 服务提供方
- 将自身服务注册到 Eureka 注册中心,从而使服务消费方能够找到
添加依赖
在项目 spring-cloud-eureka-provider
pom.xml
中引入需要的依赖内容:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
开启服务注册
在应用主类中通过加上 @EnableEurekaClient,但只有Eureka 可用,你也可以使用@EnableDiscoveryClient。需要配置才能找到Eureka注册中心服务器
package io.ymq.example.eureka.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaProviderApplication {
public String home() {
return "Hello world";
}
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class, args);
}
}
添加配置
需要配置才能找到Eureka服务器。例:
完整配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: eureka-provider
server:
port: 8081
其中defaultZone
是一个魔术字符串后备值,为任何不表示首选项的客户端提供服务URL(即它是有用的默认值)。 通过spring.application.name
属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问
访问服务
启动该工程后,再次访问启动工程后:http://localhost:8761/
可以如下图内容,我们定义的服务被成功注册了。
Spring Cloud(二)Consul 服务治理实现
Spring Cloud Consul 项目是针对Consul的服务治理实现。Consul是一个分布式高可用的系统,具有分布式、高可用、高扩展性。
Consul 简介
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul的方案更“一站式” ,内置了服务注册与发现框 架、具有以下性质:
- 分布一致性协议实现、
- 健康检查、
- Key/Value存储、
- 多数据中心方案,
不再需要依赖其他工具(比如ZooKeeper等)。
使用起来也较 为简单。Consul使用Go语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合 。 基于 Mozilla Public License 2.0 的协议进行开源. Consul 支持健康检查,并允许 HTTP 和 DNS 协议调用 API 存储键值对. 一致性协议采用 Raft 算法,用来保证服务的高可用. 使用 GOSSIP 协议管理成员和广播消息, 并且支持 ACL 访问控制.
Consul 的使用场景
- docker 实例的注册与配置共享
- coreos 实例的注册与配置共享
- vitess 集群
- SaaS 应用的配置共享
- 与 confd 服务集成,动态生成 nginx 和 haproxy 配置文件
Consul 的优势
使用 Raft 算法来保证一致性, 比复杂的 Paxos 算法更直接. 相比较而言, zookeeper 采用的是 Paxos, 而 etcd 使用的则是 Raft. 支持多数据中心,内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等. zookeeper 和 etcd 均不提供多数据中心功能的支持. 支持健康检查. etcd 不提供此功能. 支持 http 和 dns 协议接口. zookeeper 的集成较为复杂, etcd 只支持 http 协议. 官方提供web管理界面, etcd 无此功能.
Consul 的角色
client: 客户端, 无状态, 将 HTTP 和 DNS 接口请求转发给局域网内的服务端集群.server: 服务端, 保存配置信息, 高可用集群, 在局域网内与本地客户端通讯, 通过广域网与其他数据中心通讯. 每个数据中心的 server 数量推荐为 3 个或是 5 个.
由于Spring Cloud Consul项目的实现,我们可以轻松的将基于Spring Boot的微服务应用注册到Consul上,并通过此实现微服务架构中的服务治理。
搭建环境
参考
- [Spring Cloud 官方文档](http://cloud.spring.io/spring-cloud-consul/ )
- Consul 官方文档
要想利用Consul提供的服务实现服务的注册与发现,我们需要搭建Consul Cluster 环境。
在Consul方案中,每个提供服务的节点上都要部署和运行Consul的agent,所有运行Consul agent节点的集合构成Consul Cluster。
Consul agent有两种运行模式:Server和Client。这里的Server和Client只是Consul集群层面的区分,与搭建在Cluster之上 的应用服务无关。
以Server模式运行的Consul agent节点用于维护Consul集群的状态,官方建议每个Consul Cluster至少有3个或以上的运行在Server mode的Agent,Client节点不限。
环境配置如下:
Centos 7.3
主机名称 | IP | 作用 | 是否允许远程访问 |
---|---|---|---|
node1 | 192.168.252.121 | consul server | 是 |
node2 | 192.168.252.122 | consul client | 是 |
node3 | 192.168.252.123 | consul client | 是 |
关闭防火墙
systemctl stop firewalld.service
Consul 最新版的下载地址:
https://releases.hashicorp.com/consul/1.0.1/consul_1.0.1_linux_amd64.zip
下载,然后unzip 解压,得到唯一,一个可执行文件
cd /opt/
wget https://releases.hashicorp.com/consul/1.0.1/consul_1.0.1_linux_amd64.zip
unzip consul_1.0.1_linux_amd64.zip
cp consul /usr/local/bin/
查看是否安装成功
[root@node1 opt]# consul
出现如下结果,表示安装成功
Usage: consul [--version] [--help] <command> [<args>]
Available commands are:
agent Runs a Consul agent
catalog Interact with the catalog
event Fire a new event
exec Executes a command on Consul nodes
force-leave Forces a member of the cluster to enter the "left" state
info Provides debugging information for operators.
join Tell Consul agent to join cluster
keygen Generates a new encryption key
keyring Manages gossip layer encryption keys
kv Interact with the key-value store
leave Gracefully leaves the Consul cluster and shuts down
lock Execute a command holding a lock
maint Controls node or service maintenance mode
members Lists the members of a Consul cluster
monitor Stream logs from a Consul agent
operator Provides cluster-level tools for Consul operators
reload Triggers the agent to reload configuration files
rtt Estimates network round trip time between nodes
snapshot Saves, restores and inspects snapshots of Consul server state
validate Validate config files/directories
version Prints the Consul version
watch Watch for changes in Consul
检查版本
[root@node1 opt]# consul version
Consul v1.0.1
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
Consul常用命令
命令 | 解释 | 示例 |
---|---|---|
agent | 运行一个consul agent | consul agent -dev |
join | 将agent加入到consul集群 | consul join IP |
members | 列出consul cluster集群中的members | consul members |
leave | 将节点移除所在集群 | consul leave |
consul agent 命令的常用选项
-data-dir
- 作用:指定agent储存状态的数据目录
- 这是所有agent都必须的
- 对于server尤其重要,因为他们必须持久化集群的状态
-config-dir
- 作用:指定service的配置文件和检查定义所在的位置
- 通常会指定为”某一个路径/consul.d”(通常情况下,.d表示一系列配置文件存放的目录)
-config-file
- 作用:指定一个要装载的配置文件
- 该选项可以配置多次,进而配置多个配置文件(后边的会合并前边的,相同的值覆盖)
-dev
- 作用:创建一个开发环境下的server节点
- 该参数配置下,不会有任何持久化操作,即不会有任何数据写入到磁盘
- 这种模式不能用于生产环境(因为第二条)
-bootstrap-expect
- 作用:该命令通知consul server我们现在准备加入的server节点个数,该参数是为了延迟日志复制的启动直到我们指定数量的server节点成功的加入后启动。
-node
- 作用:指定节点在集群中的名称
- 该名称在集群中必须是唯一的(默认采用机器的host)
- 推荐:直接采用机器的IP
-bind
- 作用:指明节点的IP地址
- 有时候不指定绑定IP,会报
Failed to get advertise address: Multiple private IPs found. Please configure one.
的异常
-server
- 作用:指定节点为server
- 每个数据中心(DC)的server数推荐至少为1,至多为5
- 所有的server都采用raft一致性算法来确保事务的一致性和线性化,事务修改了集群的状态,且集群的状态保存在每一台server上保证可用性
- server也是与其他DC交互的门面(gateway)
-client
- 作用:指定节点为client,指定客户端接口的绑定地址,包括:HTTP、DNS、RPC
- 默认是127.0.0.1,只允许回环接口访问
- 若不指定为-server,其实就是-client
-join
- 作用:将节点加入到集群
-datacenter(老版本叫-dc,-dc已经失效)
- 作用:指定机器加入到哪一个数据中心中
启动服务
我们尝试一下:
-dev表示开发模式运行,使用-client 参数可指定允许客户端使用什么ip去访问,例如-client 192.168.252.121 表示可以使用
http://192.168.252.121:8500/ui/ 去访问。
consul agent -dev -client 192.168.252.121
Consul 的高可用
Consul Cluster集群架构图如下:
这边准备了三台Centos 7.3的虚拟机,主机规划如下,供参考:
主机名称 | IP | 作用 | 是否允许远程访问 |
---|---|---|---|
node1 | 192.168.252.121 | consul server | 是 |
node2 | 192.168.252.122 | consul client | 是 |
node3 | 192.168.252.123 | consul client | 是 |
搭建步骤
命令参数,参看上面详细介绍
在 node1 机器上启动 Consul
cd /opt/
mkdir data
consul agent -data-dir /opt/data -node=192.168.252.121 -bind=0.0.0.0 -datacenter=dc1 -ui -client=192.168.252.121 -server -bootstrap-expect 1 > /dev/null 2>&1 &
在 node2 机器上启动 Consul,并且将node2节点加入到node1节点上
cd /opt/
mkdir data
consul agent -data-dir /opt/data -node=192.168.252.122 -bind=0.0.0.0 -datacenter=dc1 -ui -client=192.168.252.122 -join=192.168.252.121 > /dev/null 2>&1 &
在 node3 机器上启动 Consul,并且将node3节点加入到node1节点上
cd /opt/
mkdir data
consul agent -data-dir /opt/data -node=192.168.252.123 -bind=0.0.0.0 -datacenter=dc1 -ui -client=192.168.252.123 -join=192.168.252.121 > /dev/null 2>&1 &
在node1上查看当前集群节点:
consul members -rpc-addr=192.168.252.123:8400
consul leave -rpc-addr=192.168.252.123:8400
http://192.168.252.121:8500/ui/ 去访问。
项目示例
新建项目:spring-cloud-consul-client
添加依赖
在项目 spring-cloud-consul-client
pom.xml
中引入需要的依赖内容:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
开启服务注册
客户端注册Consul时,它提供有关自身的元数据,如主机和端口,ID,名称和标签。默认情况下,将创建一个HTTP 检查,每隔10秒Consul命中/health端点。如果健康检查失败,则服务实例被标记为关键。
package io.ymq.example.consul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class ConsulApplication {
public String home() {
return "Hello world";
}
public static void main(String[] args) {
SpringApplication.run(ConsulApplication.class, args);
}
}
配置文件
在application.yml
配置文件中增加如下信息:如果Consul客户端位于localhost:8500以外,则需要配置来定位客户端
spring:
application:
name: consul-client
cloud:
consul:
host: 192.168.252.121
port: 8500
discovery:
healthCheckPath: /
healthCheckInterval: 5s
如果Consul客户端位于localhost:8500以外的位置,则需要配置来定位客户端。例:
host: 192.168.252.121
port: 8500
HTTP健康检查路径 INSTALL
“10s”和“1m”分别表示10秒和1分
discovery:
healthCheckPath: ${management.context-path}/health
healthCheckInterval: 15s
启动服务
到spring-cloud-consul-client
项目根目录下,执行mvn clean package
,把target
目录下 生成的 jar spring-cloud-consul-client-0.0.1-SNAPSHOT.jar
上传服务器,发布项目
打包命令
mvn clean package
发布命令
nohup java -jar spring-cloud-consul-client-0.0.1-SNAPSHOT.jar > /dev/null 2>&1 &
访问服务
Spring Cloud(三)服务提供者 Eureka + 服务消费者(rest + Ribbon)
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。它可以通过在客户端中配置ribbonServerList来设置服务端列表去轮询访问以达到均衡负载的作用。
Ribbon是什么?
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
LB方案分类
目前主流的LB方案可分成两类:一种是集中式LB, 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;另一种是进程内LB,将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于后者,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon的主要组件与工作流程
微服务架构的核心思想是,一个应用是由多个小的、相互独立的、微服务组成,这些服务运行在自己的进程中,开发和发布都没有依赖。 不同服务通过一些轻量级交互机制来通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。 简单的来说,一个系统的不同模块转变成不同的服务!而且服务可以使用不同的技术加以实现!
Ribbon的核心组件
均为接口类型,有以下几个
ServerList
- 用于获取地址列表。它既可以是静态的(提供一组固定的地址),也可以是动态的(从注册中心中定期查询地址列表)。
ServerListFilter
- 仅当使用动态ServerList时使用,用于在原始的服务列表中使用一定策略过虑掉一部分地址。
IRule
- 选择一个最终的服务地址作为LB结果。选择策略有轮询、根据响应时间加权、断路器(当Hystrix可用时)等。
Ribbon在工作时首选会通过ServerList来获取所有可用的服务列表,然后通过ServerListFilter过虑掉一部分地址,最后在剩下的地址中通过IRule选择出一台服务器作为最终结果。
Ribbon提供的主要负载均衡策略介绍
简单轮询负载均衡(RoundRobin)
以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
随机负载均衡 (Random)
随机选择状态为UP的Server
加权响应时间负载均衡 (WeightedResponseTime)
根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。
区域感知轮询负载均衡(ZoneAvoidanceRule)
复合判断server所在区域的性能和server的可用性选择server
准备工作
本次项目示例,改造第一篇文章中的项目,使用spring-cloud-eureka-service
作为服务注册中心,spring-cloud-eureka-provider
,复制三分,项目名称依次修改为spring-cloud-eureka-provider-1
[1-3]
改造 Provider
服务提供者
在项目:spring-cloud-eureka-provider-1
,spring-cloud-eureka-provider-2
,spring-cloud-eureka-provider-3
的启动类,都加入@Value("${server.port}")
,修改home()
方法, 来区分不同端口的Controller
响应,因为接下来,使用ribbon
做均衡需要测试需要使用到
package io.ymq.example.eureka.provider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaProviderApplication {
String port;
public String home() {
return "Hello world ,port:" + port;
}
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class, args);
}
}
修改配置
在项目:spring-cloud-eureka-provider-1
,spring-cloud-eureka-provider-2
,spring-cloud-eureka-provider-3
,修改server: port:
端口依次为8081
,8082
,8083
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: eureka-provider
server:
port: 8081
Ribbon Consumer
服务消费者
添加依赖
新建 spring-cloud-ribbon-consumer
<!-- 客户端负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
开启服务负载均衡
在工程的启动类中,通过@EnableDiscoveryClient
向服务注册中心注册;并且向程序的ioc
注入一个bean: restTemplate
并通过@LoadBalanced
注解表明这个restTemplate
开启负载均衡的功能。
package io.ymq.example.ribbon.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class RibbonConsumerApplication {
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
}
消费提供者方法
新建 ConsumerController
类,调用提供者的 home
方法
package io.ymq.example.ribbon.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 描述:调用提供者的 `home` 方法
*
*
*
**/
@RestController
public class ConsumerController {
private RestTemplate restTemplate;
public String hello() {
return restTemplate.getForEntity("http://eureka-provider/", String.class).getBody();
}
}
添加配置
完整配置 application.yml
指定服务的注册中心地址,配置自己的服务端口,服务名称
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: ribbon-consumer
server:
port: 9000
测试服务
启动服务
依次启动项目:
spring-cloud-eureka-service
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-ribbon-consumer
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
负载均衡
在命令窗口curl http://localhost:9000/hello
,发现Ribbon已经实现负载均衡
或者浏览器get
请求http://localhost:9000/hello
F5 刷新
Spring Cloud(四)服务提供者 Eureka + 服务消费者 Feign
上一篇文章,讲述了如何通过RestTemplate + Ribbon
去消费服务,这篇文章主要讲述如何通过Feign
去消费服务。
Feign简介
Feign
是一个声明式的伪Http
客户端,它使得写Http
客户端变得更简单。
使用Feign
,只需要创建一个接口并注解,它具有可插拔的注解特性,可使用Feign
注解和JAX-RS
注解,Feign
支持可插拔的编码器和解码器,Feign
默认集成了Ribbon
,并和Eureka
结合,默认实现了负载均衡的效果。
Feign
具有如下特性:
- 可插拔的注解支持,包括
Feign
注解和JAX-RS
注解 - 支持可插拔的
HTTP
编码器和解码器 - 支持
Hystrix
和它的Fallback
- 支持
Ribbon
的负载均衡 - 支持
HTTP
请求和响应的压缩Feign
是一个声明式的Web Service
客户端,它的目的就是让Web Service
调用更加简单。它整合了Ribbon
和Hystrix
,从而不再需要显式地使用这两个组件。Feign
还提供了HTTP
请求的模板,通过编写简单的接口和注解,就可以定义好HTTP
请求的参数、格式、地址等信息。接下来,Feign
会完全代理HTTP
的请求,我们只需要像调用方法一样调用它就可以完成服务请求。
简而言之:Feign
能干Ribbon
和Hystrix
的事情,但是要用Ribbon
和Hystrix
自带的注解必须要引入相应的jar
包才可以。
准备工作
Eureka Service
导入第三篇文章中的项目:作为服务注册中心
spring-cloud-eureka-service
Eureka Provider
导入第三篇文章中的项目:作为服务的提供者
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
Feign Consumer
服务消费者
添加依赖
新建项目 spring-cloud-feign-consumer
pom.xml
中引入需要的依赖内容:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
开启Feign
在工程的启动类中,通过@EnableFeignClients
注解开启Feign的功能:
package io.ymq.example.feign.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
定义接口
通过@FeignClient("服务名")
,来指定调用哪个服务。
比如在代码中调用了eureka-provider
服务的 /
接口,/
就是调用:服务提供者项目:spring-cloud-eureka-provider-1
,spring-cloud-eureka-provider-2
,spring-cloud-eureka-provider-3
的 home()
方法,代码如下:
package io.ymq.example.feign.consumer;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 描述: 指定这个接口所要调用的 提供者服务名称 "eureka-provider"
*
*
*
**/
public interface HomeClient {
String consumer();
}
消费方法
写一个 Controller
,消费提供者的 home
方法
package io.ymq.example.feign.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 描述:调用提供者的 `home` 方法
*
*
*
**/
@RestController
public class ConsumerController {
private HomeClient homeClient;
@GetMapping(value = "/hello")
public String hello() {
return homeClient.consumer();
}
}
添加配置
完整配置 application.yml
指定注册中心地址,配置自己的服务名称
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: feign-consumer
server:
port: 9000
测试服务
依次启动项目:
spring-cloud-eureka-service
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-feign-consumer
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
负载均衡响应
在命令窗口curl http://localhost:9000/hello
,发现Feign已经实现负载均衡
或者浏览器get
请求http://localhost:9000/hello
F5 刷新
Spring Cloud(五)断路器监控(Hystrix Dashboard)
在上两篇文章中讲了,服务提供者 Eureka + 服务消费者 Feign,服务提供者 Eureka + 服务消费者(rest + Ribbon),本篇文章结合,上两篇文章中代码进行修改加入 断路器监控(Hystrix Dashboard)
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
针对上述问题,在Spring Cloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。
什么是断路器
断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。
在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
断路器示意图
SpringCloud Netflix实现了断路器库的名字叫Hystrix. 在微服务架构下,通常会有多个层次的服务调用. 下面是微服架构下, 浏览器端通过API访问后台微服务的一个示意图:
一个微服务的超时失败可能导致瀑布式连锁反映,下图中,Hystrix通过自主反馈实现的断路器, 防止了这种情况发生。
图中的服务B因为某些原因失败,变得不可用,所有对服务B的调用都会超时。当对B的调用失败达到一个特定的阀值(5秒之内发生20次失败是Hystrix定义的缺省值), 链路就会被处于open状态, 之后所有所有对服务B的调用都不会被执行, 取而代之的是由断路器提供的一个表示链路open的Fallback消息. Hystrix提供了相应机制,可以让开发者定义这个Fallbak消息.
open的链路阻断了瀑布式错误, 可以让被淹没或者错误的服务有时间进行修复。这个fallback可以是另外一个Hystrix保护的调用, 静态数据,或者合法的空值. Fallbacks可以组成链式结构,所以,最底层调用其它业务服务的第一个Fallback返回静态数据.
准备工作
在开始加入断路器之前,我们先拿之前两篇博客,构建的两个微服务代码为基础,进行下面的操作
建议先阅读以下两篇文章
Spring Cloud(四) 服务提供者 Eureka + 服务消费者 Feign
Spring Cloud(三) 服务提供者 Eureka + 服务消费者(rest + Ribbon)
Eureka Service
导入第三篇文章中的项目:作为服务注册中心
spring-cloud-eureka-service
Eureka Provider
导入第三篇文章中的项目:作为服务的提供者
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
Ribbon Hystrix
在 Ribbon中使用断路器
修改项目
复制 spring-cloud-ribbon-consumer
项目,修改名称为spring-cloud-ribbon-consumer-hystrix
添加依赖
在项目pom
加上hystrix
的依赖
<!-- hystrix 断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
服务注册
在程序的启动类 RibbonConsumerApplication
通过 @EnableHystrix
开启 Hystrix
断路器监控
package io.ymq.example.ribbon.consumer.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableHystrix
@EnableDiscoveryClient
@SpringBootApplication
public class RibbonConsumerApplication {
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
}
消费提供者方法
修改 ConsumerController
类的,hello
方法,加上注解@HystrixCommand(fallbackMethod = "defaultStores")
该注解对该方法创建了熔断器的功能 ,并指定了defaultStores
熔断方法,熔断方法直接返回了一个字符串, "feign + hystrix ,提供者服务挂了"
@HystrixCommand
表明该方法为hystrix
包裹,可以对依赖服务进行隔离、降级、快速失败、快速重试等等hystrix
相关功能 该注解属性较多,下面讲解其中几个
- fallbackMethod 降级方法
- commandProperties 普通配置属性,可以配置HystrixCommand对应属性,例如采用线程池还是信号量隔离、熔断器熔断规则等等
- ignoreExceptions 忽略的异常,默认HystrixBadRequestException不计入失败
- groupKey() 组名称,默认使用类名称
- commandKey 命令名称,默认使用方法名
package io.ymq.example.ribbon.consumer.hystrix;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 描述:调用提供者的 `home` 方法
*
*
*
**/
@RestController
public class ConsumerController {
private RestTemplate restTemplate;
public String hello() {
return restTemplate.getForEntity("http://eureka-provider/", String.class).getBody();
}
public String defaultStores() {
return "Ribbon + hystrix ,提供者服务挂了";
}
}
测试断路器
依次启动项目:
spring-cloud-eureka-service
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-ribbon-consumer-hystrix
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
在命令窗口curl http://localhost:9000/hello
,发现一切正常
或者浏览器get
请求http://localhost:9000/hello
F5 刷新
停止 spring-cloud-eureka-provider-1 提供者,端口为:8081服务
再次访问命令窗口curl http://localhost:9000/hello
,断路器已经生效,提示:Ribbon + hystrix ,提供者服务挂了
Feign Hystrix
在 Feign中使用断路器
修改项目
复制spring-cloud-feign-consumer
项目,修改名称为spring-cloud-feign-consumer-hystrix
添加依赖
Feign是自带断路器的,如果在Dalston
版本的Spring Cloud
中,它没有默认打开。需要需要在配置文件中配置打开它,本项目我们是不需要打开的
feign:
hystrix:
enabled: true
服务注册
修改 HomeClient
类 ,@FeignClient
注解,加上fallbackFactory
指定新建的HystrixClientFallbackFactory
工厂类
在程序的启动类 RibbonConsumerApplication
通过 @EnableHystrix
开启 Hystrix
package io.ymq.example.feign.consumer.hystrix;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 描述: 指定这个接口所要调用的 提供者服务名称 "eureka-provider"
*
*
*
**/
public interface HomeClient {
String consumer();
}
新加的类 HystrixClientFallbackFactory.java
package io.ymq.example.feign.consumer.hystrix;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 描述:
*
*
*
**/
@Component
public class HystrixClientFallbackFactory implements FallbackFactory<HomeClient> {
public HomeClient create(Throwable throwable) {
return () -> "feign + hystrix ,提供者服务挂了";
}
}
测试断路器
依次启动项目:
spring-cloud-eureka-service
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-feign-consumer-hystrix
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
在命令窗口curl http://localhost:9000/hello
,发现一切正常
或者浏览器get
请求http://localhost:9000/hello
F5 刷新
停止 spring-cloud-eureka-provider-1 提供者,端口为:8081服务
再次访问命令窗口curl http://localhost:9000/hello
,断路器已经生效,提示:Feign + hystrix ,提供者服务挂了
Hystrix Dashboard
HD 简介
Hystrix Dashboard
在微服务架构中为例保证程序的可用性,防止程序出错导致网络阻塞,出现了断路器模型。断路器的状况反应了一个程序的可用性和健壮性,它是一个重要指标。Hystrix Dashboard
是作为断路器状态的一个组件,提供了数据监控和友好的图形化界面。
改造项目
复制项目 spring-cloud-ribbon-consumer-hystrix
,修改名称 spring-cloud-ribbon-consumer-hystrix-dashboard
在它的基础上进行改造。Feign
的改造和这一样。
在pom
的工程文件引入相应的依赖:
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
开启 HD
修改 RibbonConsumerApplication.java
类
在程序的入口RibbonConsumerApplication
类,加上@EnableHystrix
注解开启断路器,这个是必须的,并且需要在程序中声明断路点@HystrixCommand;
加上@EnableHystrixDashboard
注解,开启HystrixDashboard
package io.ymq.example.ribbon.consumer.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableHystrix
@EnableDiscoveryClient
@EnableHystrixDashboard
@SpringBootApplication
public class RibbonConsumerApplication {
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
}
声明断路点
声明断路点 @HystrixCommand(fallbackMethod = "defaultStores")
package io.ymq.example.ribbon.consumer.hystrix;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 描述:调用提供者的 `home` 方法
*
*
*
**/
@RestController
public class ConsumerController {
private RestTemplate restTemplate;
public String hello() {
return restTemplate.getForEntity("http://eureka-provider/", String.class).getBody();
}
public String defaultStores() {
return "feign + hystrix Dashboard ,提供者服务挂了";
}
}
@HystrixCommand
表明该方法为hystrix
包裹,可以对依赖服务进行隔离、降级、快速失败、快速重试等等hystrix
相关功能 该注解属性较多,下面讲解其中几个
fallbackMethod
降级方法commandProperties
普通配置属性,可以配置HystrixCommand
对应属性,例如采用线程池还是信号量隔离、熔断器熔断规则等等ignoreExceptions
忽略的异常,默认HystrixBadRequestException
不计入失败groupKey()
组名称,默认使用类名称commandKey
命令名称,默认使用方法名
测试服务
依次启动项目:
spring-cloud-eureka-service
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-ribbon-consumer-hystrix-dashboard
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
Hystrix Dashboard 监控
可以访问 http://127.0.0.1:9090/hystrix ,获取Hystrix Dashboard
信息,默认最大打开5个终端获取监控信息,可以增加delay
参数指定获取监控数据间隔时间
在界面依次输入:http://127.0.0.1:9000/hystrix.stream
、2000
、hello
点确定。可以访问以下,图形化监控页面
Spring Cloud(六)服务网关 zuul 快速入门
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API
的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud
Netflix
中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
路由在微服务体系结构的一个组成部分。例如,/可以映射到您的Web应用程序,/api/users
映射到用户服务,并将/api/shop
映射到商店服务。Zuul
是Netflix
的基于JVM
的路由器和服务器端负载均衡器。
Netflix使用Zuul进行以下操作:
- 认证
- 洞察
- 压力测试
- 金丝雀测试
- 动态路由
- 服务迁移
- 负载脱落
- 安全
- 静态响应处理
- 主动/主动流量管理
Zuul
的规则引擎允许基本上写任何JVM语言编写规则和过滤器,内置Java
和Groovy
。
什么是服务网关
服务网关 = 路由转发 + 过滤器
1、路由转发:接收一切外界请求,转发到后端的微服务上去;
2、过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。
为什么需要服务网关
上述所说的横切功能(以权限校验为例)可以写在三个位置:
- 每个服务自己实现一遍
- 写到一个公共的服务中,然后其他所有服务都依赖这个服务
- 写到服务网关的前置过滤器中,所有请求过来进行权限校验
第一种,缺点太明显,基本不用; 第二种,相较于第一点好很多,代码开发不会冗余,但是有两个缺点:
- 由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加了一些,尤其是对于使用docker镜像进行部署的场景,jar越小越好;
-
由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难,而且假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。
而服务网关恰好可以解决这样的问题:
- 将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,不会增加jar包大小;
-
如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务。
所以,需要服务网关!!!
服务网关技术选型
引入服务网关后的微服务架构如上,总体包含三部分:服务网关、open-service和service。
1、总体流程:
- 服务网关、open-service和service启动时注册到注册中心上去;
- 用户请求时直接请求网关,网关做智能路由转发(包括服务发现,负载均衡)到open-service,这其中包含权限校验、监控、限流等操作
-
open-service聚合内部service响应,返回给网关,网关再返回给用户
2、引入网关的注意点
- 增加了网关,多了一层转发(原本用户请求直接访问open-service即可),性能会下降一些(但是下降不大,通常,网关机器性能会很好,而且网关与open-service的访问通常是内网访问,速度很快);
- 网关的单点问题:在整个网络调用过程中,一定会有一个单点,可能是网关、nginx、dns服务器等。防止网关单点,可以在网关层前边再挂一台nginx,nginx的性能极高,基本不会挂,这样之后,网关服务就可以不断的添加机器。但是这样一个请求就转发了两次,所以最好的方式是网关单点服务部署在一台牛逼的机器上(通过压测来估算机器的配置),而且nginx与zuul的性能比较,根据国外的一个哥们儿做的实验来看,其实相差不大,zuul是netflix开源的一个用来做网关的开源框架;
-
网关要尽量轻。
3、服务网关基本功能
- 智能路由:接收外部一切请求,并转发到后端的对外服务open-service上去;
- 注意:我们只转发外部请求,服务之间的请求不走网关,这就表示全链路追踪、内部服务API监控、内部服务之间调用的容错、智能路由不能在网关完成;当然,也可以将所有的服务调用都走网关,那么几乎所有的功能都可以集成到网关中,但是这样的话,网关的压力会很大,不堪重负。
- 权限校验:只校验用户向open-service服务的请求,不校验服务内部的请求。服务内部的请求有必要校验吗?
- API监控:只监控经过网关的请求,以及网关本身的一些性能指标(例如,gc等);
- 限流:与监控配合,进行限流操作;
- API日志统一收集:类似于一个aspect切面,记录接口的进入和出去时的相关日志
-
。。。后续补充
4、技术选型
笔者准备自建一个轻量级的服务网关,技术选型如下:
- 开发语言:java + groovy,groovy的好处是网关服务不需要重启就可以动态的添加filter来实现一些功能;
- 微服务基础框架:springboot;
- 网关基础组件:netflix zuul;
- 服务注册中心:consul;
- 权限校验:jwt;
- API监控:prometheus + grafana;
- API统一日志收集:logback + ELK;
- 压力测试:Jmeter;
- 。。。后续补充
-
在后续的介绍中,会逐渐介绍各个知识点,并完成一个轻量级的服务网关!!!
Spring Cloud Zuul
简单使用
新建项目 spring-cloud-zuul-service
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
开启服务注册
在程序的启动类 ZuulApplication
通过 @EnableZuulProxy
开启 Zuul 服务网关
package io.ymq.example.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
添加配置
配置文件 application.yml
spring:
application:
name: zuul-service
server:
port: 9000
zuul:
routes:
blog:
path: /ymq/**
url: http://www.ymq.io/about
测试访问
配置说明:
浏览器访问:http://127.0.0.1:9000/ymq 重定向到我的博客
服务转发
准备工作
我们先拿之前两篇文章,构建的两个微服务代码为基础,进行下面的操作
建议先阅读以下两篇文章
Spring Cloud(四) 服务提供者 Eureka + 服务消费者 Feign
Spring Cloud(三) 服务提供者 Eureka + 服务消费者(rest + Ribbon)
http://www.ymq.io/2017/12/06/spring-cloud-feign/
http://www.ymq.io/2017/12/05/spring-cloud-ribbon-rest/
Eureka Service
导入第三篇文章中的项目:作为服务注册中心
spring-cloud-eureka-service
Eureka Provider
导入第三篇文章中的项目:作为服务的提供者
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
添加依赖
项目继续改造,添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
修改配置
配置文件 application.yml
spring:
application:
name: zuul-service
server:
port: 9000
#zuul:
# routes:
# blog:
# path: /ymq/**
# url: http://www.ymq.io/about
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
api:
path: /**
serviceId: eureka-provider
配置说明:
浏览器访问:http://127.0.0.1:9000/ ,Zuul 会去 Eureka 服务注册中心,找到eureka-provider
服务以均衡负载的方式访问
测试服务
依次启动项目:
spring-cloud-eureka-service
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-zuul-service
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
Spring Cloud(七)服务网关 Zuul Filter 使用
上一篇文章中,讲了Zuul 转发,动态路由,负载均衡,等等一些Zuul 的特性,这个一篇文章,讲Zuul Filter 使用,关于网关的作用,这里就不再次赘述了,重点是zuul的Filter ,我们可以实现安全控制,比如,只有请求参数中有token和密码的客户端才能访问服务端的资源。那么如何来实现Filter了?
Spring Cloud Zuul
zuul 执行流程
Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
OST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR:在其他阶段发生错误时执行该过滤器。
除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
准备工作
我们先拿之前两篇文章,构建的两个微服务代码为基础,进行下面的操作
建议先阅读以下两篇文章
Spring Cloud(四) 服务提供者 Eureka + 服务消费者 Feign
Spring Cloud(三) 服务提供者 Eureka + 服务消费者(rest + Ribbon)
http://www.ymq.io/2017/12/06/spring-cloud-feign/
http://www.ymq.io/2017/12/05/spring-cloud-ribbon-rest/
Eureka Service
导入第三篇文章中的项目:作为服务注册中心
spring-cloud-eureka-service
Eureka Provider
导入第三篇文章中的项目:作为服务的提供者
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
简单使用
新建项目 spring-cloud-zuul-filter
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
开启服务注册
在程序的启动类 ZuulFilterApplication
通过 @EnableZuulProxy
开启 Zuul 服务网关
package io.ymq.example.zuul.filter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
@EnableZuulProxy
@SpringBootApplication
public class ZuulFilterApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulFilterApplication.class, args);
}
}
添加配置
配置文件 application.yml
spring:
application:
name: zuul-service-filter
server:
port: 9000
zuul:
routes:
api:
path: /**
serviceId: eureka-provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
TokenFilter
ZuulFilter
是Zuul中核心组件,通过继承该抽象类,覆写几个关键方法达到自定义调度请求的作用
TokenFilter 过滤器
package io.ymq.example.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
/**
* 描述: 过滤器 token
*
*
*
**/
public class TokenFilter extends ZuulFilter {
private final Logger LOGGER = LoggerFactory.getLogger(TokenFilter.class);
public String filterType() {
return "pre"; // 可以在请求被路由之前调用
}
public int filterOrder() {
return 0; // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
}
public boolean shouldFilter() {
return true;// 是否执行该过滤器,此处为true,说明需要过滤
}
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
LOGGER.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("token");// 获取请求的参数
if (StringUtils.isNotBlank(token)) {
ctx.setSendZuulResponse(true); //对请求进行路由
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
return null;
} else {
ctx.setSendZuulResponse(false); //不对其进行路由
ctx.setResponseStatusCode(400);
ctx.setResponseBody("token is empty");
ctx.set("isSuccess", false);
return null;
}
}
}
PasswordFilter
ZuulFilter
是Zuul中核心组件,通过继承该抽象类,覆写几个关键方法达到自定义调度请求的作用
PasswordFilter 过滤器
package io.ymq.example.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
/**
* 描述: 过滤器 Password
*
*
*
**/
public class PasswordFilter extends ZuulFilter {
private final Logger LOGGER = LoggerFactory.getLogger(TokenFilter.class);
public String filterType() {
return "post"; // 请求处理完成后执行的filter
}
public int filterOrder() {
return 1; // 优先级为0,数字越大,优先级越低
}
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return (boolean) ctx.get("isSuccess");
// 判断上一个过滤器结果为true,否则就不走下面过滤器,直接跳过后面的所有过滤器并返回 上一个过滤器不通过的结果。
}
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
LOGGER.info("--->>> PasswordFilter {},{}", request.getMethod(), request.getRequestURL().toString());
String username = request.getParameter("password");
if (null != username && username.equals("123456")) {
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
return null;
} else {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(400);
ctx.setResponseBody("The password cannot be empty");
ctx.set("isSuccess", false);
return null;
}
}
}
开启过滤器
在程序的启动类 ZuulFilterApplication
添加 Bean
@Bean
public TokenFilter tokenFilter() {
return new TokenFilter();
}
@Bean
public PasswordFilter PasswordFilter() {
return new PasswordFilter();
}
filterType
filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
- pre:路由之前
- routing:路由之时
- post: 路由之后
- error:发送错误调用
- filterOrder:过滤的顺序
- shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
- run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
测试服务
依次启动项目:
spring-cloud-eureka-service
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-zuul-filter
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
查看 eureka 监控,看服务是否都注册成功
token 测试
步骤一 提示 token is empty
步骤二 加上token ?token=token-uuid
,已经验证通过了,提示 The password cannot be empty
访问:http://127.0.0.1:9000/?token=token-uuid
password 测试
![The password cannot be empty][3]
加上token
和 password
&password=123456
,已经验证通过
Spring Cloud(八)高可用的分布式配置中心 Spring Cloud Config
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud
中,有分布式配置中心组件spring cloud config
,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config
组件中,分两个角色,一是config server
,二是config client
,业界也有些知名的同类开源产品,比如百度的disconf
。
相比较同类产品,SpringCloudConfig
最大的优势是和Spring
无缝集成,支持Spring
里面Environment
和PropertySource
的接口,对于已有的Spring
应用程序的迁移成本非常低,在配置获取的接口上是完全一致,结合SpringBoot
可使你的项目有更加统一的标准(包括依赖版本和约束规范),避免了应为集成不同开软件源造成的依赖版本冲突。
Spring Cloud Config 简介
SpringCloudConfig
就是我们通常意义上的配置中心,把应用原本放在本地文件的配置抽取出来放在中心服务器,从而能够提供更好的管理、发布能力。SpringCloudConfig
分服务端和客户端,服务端负责将git svn
中存储的配置文件发布成REST
接口,客户端可以从服务端REST接口获取配置。但客户端并不能主动感知到配置的变化,从而主动去获取新的配置,这需要每个客户端通过POST
方法触发各自的/refresh
。
SpringCloudBus
通过一个轻量级消息代理连接分布式系统的节点。这可以用于广播状态更改(如配置更改)或其他管理指令。SpringCloudBus
提供了通过POST
方法访问的endpoint/bus/refresh
,这个接口通常由git
的钩子功能调用,用以通知各个SpringCloudConfig
的客户端去服务端更新配置。
注意:这是工作的流程图,实际的部署中SpringCloudBus
并不是一个独立存在的服务,这里单列出来是为了能清晰的显示出工作流程。
下图是SpringCloudConfig
结合SpringCloudBus
实现分布式配置的工作流
服务端配置
Config Server
新建项目 spring-cloud-config-server
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
开启服务注册
在程序的启动类 ConfigServerApplication
通过 @EnableConfigServer
开启 SpringCloudConfig
服务端
package io.ymq.example.config.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
添加配置
配置文件 application.properties
spring.application.name=config-server
server.port=8888
spring.cloud.config.label=master
spring.cloud.config.server.git.uri=https://github.com/souyunku/spring-cloud-config.git
spring.cloud.config.server.git.search-paths=spring-cloud-config
#spring.cloud.config.server.git.username=your username
#spring.cloud.config.server.git.password=your password
- spring.cloud.config.server.git.uri:配置git仓库地址
- spring.cloud.config.server.git.searchPaths:配置仓库路径
- spring.cloud.config.label:配置仓库的分支
- spring.cloud.config.server.git.username:访问git仓库的用户名
- spring.cloud.config.server.git.password:访问git仓库的用户密码
Git仓库如果是私有仓库需要填写用户名密码,示例是公开仓库,所以不配置密码。
远程Git仓库
spring-cloud-config
文件夹下有 application-dev.properties
,application-test.properties
三个文件,内容依次是:content=hello dev
,content=hello test
,content=hello pre
测试服务
启动程序 ConfigApplication
类
访问 Spring Cloud Config Server
服务:
http://localhost:8888/springCloudConfig/dev/master
{
"name": "springCloudConfig",
"profiles": [
"dev"
],
"label": "master",
"version": "b6fbc2f77d1ead41d5668450e2601a03195eaf16",
"state": null,
"propertySources": [
{
"name": "https://github.com/souyunku/spring-cloud-config.git/application-dev.properties",
"source": {
"content": "hello dev"
}
}
]
}
证明配置服务中心可以从远程程序获取配置信息。
http请求地址和资源文件映射如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
客户端配置
Config Client
新建项目 spring-cloud-config-client
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
开启服务注册
在程序的启动类 ConfigClientApplication
通过 @Value
获取服务端的 content
值的内容
package io.ymq.example.config.client;
@RestController
@SpringBootApplication
public class ConfigClientApplication {
String content;
public String home() {
return "content:" + content;
}
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
添加配置
配置文件 application.properties
spring.application.name=config-client
server.port=8088
spring.cloud.config.label=master
spring.cloud.config.profile=dev
spring.cloud.config.uri=http://localhost:8888/
- spring.cloud.config.label 指明远程仓库的分支
- spring.cloud.config.profile
- dev开发环境配置文件
- test测试环境
- pro正式环境
- spring.cloud.config.uri= http://localhost:8888/ 指明配置服务中心的网址。
测试服务
启动程序 ConfigClientApplication
类
Spring Cloud(九)高可用的分布式配置中心 Spring Cloud Config 集成 Eureka 服务
上一篇文章,讲了SpringCloudConfig
集成Git
仓库,这一篇我们讲一下SpringCloudConfig
配和 Eureka
注册中心一起使用
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud
中,有分布式配置中心组件spring cloud config
,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git
仓库中。在spring cloud config
组件中,分两个角色,一是config server
,二是config client
,业界也有些知名的同类开源产品,比如百度的disconf
。
相比较同类产品,SpringCloudConfig
最大的优势是和Spring
无缝集成,支持Spring
里面Environment
和PropertySource
的接口,对于已有的pring
应用程序的迁移成本非常低,在配置获取的接口上是完全一致,结合SpringBoot
可使你的项目有更加统一的标准(包括依赖版本和约束规范),避免了应为集成不同开软件源造成的依赖版本冲突。
准备工作
我们先拿之前的代码为基础,进行下面的操作
Spring Cloud(四) 服务提供者 Eureka + 服务消费者 Feign
http://www.ymq.io/2017/12/06/spring-cloud-feign/
Eureka Service
导入第四篇文章中的项目:作为服务注册中心
spring-cloud-eureka-service
Eureka Provider
导入第四篇文章中的项目:作为服务的提供者
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
Eureka Consumer
导入第四篇文章中的项目:作为服务的消费者
spring-cloud-feign-consumer
服务端配置
Config Server
复制上一篇的项目 spring-cloud-config-server
,添加 eureka
依赖
https://github.com/souyunku/spring-cloud-examples/tree/master/spring-cloud-config/
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
开启服务注册
在程序的启动类 ConfigServerApplication.java
通过 @EnableEurekaClient
开启 Eureka
提供者服务
package io.ymq.example.config.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableConfigServer
@EnableEurekaClient
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
修改配置
修改配置文件 application.properties
,添加 eureka
注册中心地址 http://localhost:8761/eureka/
spring.application.name=config-server
server.port=8888
spring.cloud.config.label=master
spring.cloud.config.server.git.uri=https://github.com/souyunku/spring-cloud-config.git
spring.cloud.config.server.git.search-paths=spring-cloud-config
#spring.cloud.config.server.git.username=your username
#spring.cloud.config.server.git.password=your password
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
spring.cloud.config.server.git.uri:
配置git
仓库地址spring.cloud.config.server.git.searchPaths:
配置仓库路径spring.cloud.config.label:
配置仓库的分支spring.cloud.config.server.git.username:
访问git
仓库的用户名-
spring.cloud.config.server.git.password:
访问git
仓库的用户密码 eureka.client.serviceUrl.defaultZone:eureka
注册中心地址
Git仓库如果是私有仓库需要填写用户名密码,示例是公开仓库,所以不配置密码。
远程Git仓库
spring-cloud-config
文件夹下有 application-dev.properties
,application-test.properties
三个文件,内容依次是:content=hello dev
,content=hello test
,content=hello pre
测试服务
启动程序 ConfigServerApplication
类
访问 Config Server
服务:http://localhost:8888/springCloudConfig/dev/master
{
"name": "springCloudConfig",
"profiles": [
"dev"
],
"label": "master",
"version": "b6fbc2f77d1ead41d5668450e2601a03195eaf16",
"state": null,
"propertySources": [
{
"name": "https://github.com/souyunku/spring-cloud-config.git/application-dev.properties",
"source": {
"content": "hello dev"
}
}
]
}
证明配置服务中心可以从远程程序获取配置信息。
http请求地址和资源文件映射如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
客户端端配置
config Client Eureka
修改已经导入的,第四篇文章中的项目:配置客户端的一些配置
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
开启服务注册
在程序的启动类 EurekaProviderApplication
,通过 @Value
获取服务端的 content
值的内容
package io.ymq.example.eureka.provider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaProviderApplication {
String content;
String port;
public String home() {
return "Hello world ,port:" + port+",content="+content;
}
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class, args);
}
}
添加配置
修改配置文件 application.properties
添加 Eureka
注册中心,配置从springCloudConfig
配置中心读取配置,指定springCloudConfigService
服务名称
spring.application.name=eureka-provider
server.port=8081
spring.cloud.config.label=master
spring.cloud.config.profile=dev
#spring.cloud.config.uri=http://localhost:8888/
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=config-server
spring.cloud.config.label
指明远程仓库的分支spring.cloud.config.profile
dev
开发环境配置文件test
测试环境pro
正式环境-
#spring.cloud.config.uri= http://localhost:8888/
指明配置服务中心的网址(注释掉) spring.cloud.config.discovery.enabled=true
是从配置中心读取文件。-
spring.cloud.config.discovery.serviceId=config-server
配置中心的servieId
,服务名称,通过服务名称去Eureka
注册中心找服务测试服务
依次启动项目:
spring-cloud-eureka-service
spring-cloud-config-server
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-feign-consumer
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
查看 eureka 监控,看服务是否都注册成功
命令窗口,通过curl http://127.0.0.1:9000/hello
访问服务,或者在浏览器访问http://127.0.0.1:9000/hello
F5 刷新
Spring Cloud(十)高可用的分布式配置中心 Spring Cloud Config 中使用 Refresh
上一篇文章讲了SpringCloudConfig
集成Git
仓库,配和 Eureka
注册中心一起使用,但是我们会发现,修改了Git
仓库的配置后,需要重启服务,才可以得到最新的配置,这一篇我们尝试使用 Refresh
实现主动获取 Config Server
配置服务中心的最新配置
准备工作
把上一篇,示例代码下载,才可以进行一下的操作,下载地址在文章末尾
spring-cloud-eureka-service
spring-cloud-config-server
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-feign-consumer
Config Client
修改第九篇文章项目
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
-
spring-cloud-eureka-provider-3
添加依赖
<!-- actuator 监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
安全认证
在 application.properties
添加以下配置.关闭安全认证
#关闭刷新安全认证
management.security.enabled=false
值是false
的话,除开health
接口还依赖endpoints.health.sensitive
的配置外,其他接口都不需要输入用户名和密码了
开启 refresh
在程序的启动类 EurekaProviderApplication
通过 @RefreshScope
开启 SpringCloudConfig 客户端的 refresh
刷新范围,来获取服务端的最新配置,@RefreshScope
要加在声明@Controller
声明的类上,否则refres
h之后Conroller
拿不到最新的值,会默认调用缓存。
package io.ymq.example.eureka.provider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
@EnableEurekaClient
@SpringBootApplication
public class EurekaProviderApplication {
String content;
String port;
public String home() {
return "Hello world ,port:" + port+",content="+content;
}
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class, args);
}
}
测试服务
按照顺序依次启动项目
spring-cloud-eureka-service
spring-cloud-config-server
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-feign-consumer
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://localhost:8761/
修改Git仓库
修改Git
仓库配置,在 content=hello dev
后面加个 123456
访问服务
命令窗口,通过curl http://127.0.0.1:9000/hello
访问服务,或者在浏览器访问http://127.0.0.1:9000/hello
F5 刷新
发现没有得到最新的值
刷新配置
通过 Postman
发送 POST
请求到:http://localhost:8081/refresh,http://localhost:8083/refresh,我们可以看到以下内容:
访问服务
命令窗口,通过curl http://127.0.0.1:9000/hello
访问服务,或者在浏览器访问http://127.0.0.1:9000/hello
F5 刷新
发现:服务8082 没有刷新到最新配置 因为没有手动触发更新
Spring Cloud(十一)高可用的分布式配置中心 Spring Cloud Bus 消息总线集成(RabbitMQ)
上一篇文章,留了一个悬念,Config Client
实现配置的实时更新,我们可以使用 /refresh
接口触发,如果所有客户端的配置的更改,都需要手动触发客户端 /refresh
,当服务越来越多的时候,那岂不是维护成本很高,显然不太合适,而使用Spring Cloud Bus
消息总线实现方案,可以优雅的解决以上问题,那就是通过消息代理中间件RabbitMQ
加 Git
的Webhooks
來触发配置的更新,那具体是怎么实现的,我会通过图文形式介绍。
Spring Cloud Bus
Spring Cloud Bus
将分布式系统的节点通过轻量级消息代理连接起来。用于在集群中传播状态更改(例如配置更改事件)或其他管理指令。Spring Cloud Bus
的一个核心思想是通过分布式的启动器对 Spring Boot
应用进行扩展,也可以用来建立一个或多个应用之间的通信频道。目前唯一实现的方式是用 AMQP
消息代理作为通道,但是相同的基本功能集(还有一些取决于传输)在其他传输的路线图上
消息总线
消息总线是一种通信工具,可以在机器之间互相传输消息、文件等。消息总线扮演着一种消息路由的角色,拥有一套完备的路由机制来决定消息传输方向。发送段只需要向消息总线发出消息而不用管消息被如何转发。 Spring cloud bus
通过轻量消息代理连接各个分布的节点。管理和传播所有分布式项目中的消息,本质是利用了MQ的广播机制在分布式的系统中传播消息,目前常用的有Kafka
和RabbitMQ
。 下面是一个配置中心刷新配置的例子
[图片来源于网络如有侵权请私信删除]
- 1、提交代码触发
post
请求给bus/refresh
- 2、
server
端接收到请求并发送给Spring Cloud Bus
- 3、
Spring Cloud bus
接到消息并通知给其它客户端 - 4、其它客户端接收到通知,请求
Server
端获取最新配置 - 5、全部客户端均获取到最新的配置
消息代理
消息代理(Message Broker
)是一种消息验证、传输、路由的架构模式。消息代理是一个中间件产品,它的核心是一个消息的路由程序,用来实现接收和分发消息,并根据设定好的消息处理流来转发给正确的应用。它包括独立的通信和消息传递协议,能够实现组织内部和组织间的网络通信。设计代理的目的就是为了能够从应用程序中传入消息,并执行一些特别的操作。
和组织间的网络通信。设计代理的目的就是为了能够从应用程序中传入消息,并执行一些特别的操作。 现有的消息代理开源产品:
ActiveMQ
Kafka
RabbitMQ
RocketMQ
目前Spring Cloud Bus
支持 RabbitMQ
和 Kafka,spring-cloud-starter-bus-amqp
、spring-cloud-starter-bus-kafka
RabbitMQ简介
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP
等,支持AJAX
。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
AMQP
,即Advanced message Queuing Protocol
,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP
的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
Github:https://github.com/rabbitmq
官网地址:http://www.rabbitmq.com
安装RabbitMQ
安装RabbitMQ 可以参考之前的文章
CentOs7.3 搭建 RabbitMQ 3.6 单机服务:
https://segmentfault.com/a/1190000010693696
CentOs7.3 搭建 RabbitMQ 3.6 Cluster 集群服务:
https://segmentfault.com/a/1190000010702020
Spring Boot 中使用 RabbitMQ: https://segmentfault.com/a/1190000011577243
准备工作
以下项目修改不做过多解释,部分代码不再展示,请阅读上篇文章,Spring Cloud(十)高可用的分布式配置中心 Spring Cloud Config 中使用 Refresh:http://www.ymq.io/2017/12/23/spring-cloud-config-eureka-refresh/
把上一篇,示例代码下载,才可以进行一下的操作,下载地址在文章末尾
spring-cloud-eureka-service
spring-cloud-config-server
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
-
spring-cloud-feign-consumer
Config Server
在项目spring-cloud-config-server
进行以下操作
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
添加配置
在 application.properties
添加以下配置.关闭安全认证
RabbitMQ 的 ymq
用户是手动创建的,具体阅读上面 安装RabbitMQ
部分
#关闭刷新安全认证
management.security.enabled=false
spring.rabbitmq.host=192.168.252.126
spring.rabbitmq.port=5672
spring.rabbitmq.username=ymq
spring.rabbitmq.password=123456
Config Client
修改第上一篇文章项目
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
-
spring-cloud-eureka-provider-3
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
添加配置
在 application.properties
添加以下配置.关闭安全认证
spring.rabbitmq.host=192.168.252.126
spring.rabbitmq.port=5672
spring.rabbitmq.username=ymq
spring.rabbitmq.password=123456
测试服务
启动RabbitMQ
启动MQ服务
$ service rabbitmq-server start
Redirecting to /bin/systemctl start rabbitmq-server.service
查看MQ状态
$ service rabbitmq-server status
[root@node6 rabbitmq]# service rabbitmq-server status
Redirecting to /bin/systemctl status rabbitmq-server.service
● rabbitmq-server.service - RabbitMQ broker
Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
Active: active (running) since Fri 2017-12-29 17:44:10 CST; 9min ago
Process: 2814 ExecStop=/usr/sbin/rabbitmqctl stop (code=exited, status=0/SUCCESS)
Main PID: 2948 (beam)
Status: "Initialized"
CGroup: /system.slice/rabbitmq-server.service
├─2948 /usr/lib64/erlang/erts-8.0.3/bin/beam -W w -A 64 -P 1048576 -t 5000000 -stbt db -zdbbl 32000 -K true -- -root /usr/lib64/erlang -progname erl -- -home /var/lib/rabbitmq -- -pa /usr...
├─3131 /usr/lib64/erlang/erts-8.0.3/bin/epmd -daemon
├─3233 erl_child_setup 1024
├─3240 inet_gethost 4
└─3241 inet_gethost 4
Dec 29 17:44:08 node6 rabbitmq-server[2948]: RabbitMQ 3.6.10. Copyright (C) 2007-2017 Pivotal Software, Inc.
Dec 29 17:44:08 node6 rabbitmq-server[2948]: ## ## Licensed under the MPL. See http://www.rabbitmq.com/
Dec 29 17:44:08 node6 rabbitmq-server[2948]: ## ##
Dec 29 17:44:08 node6 rabbitmq-server[2948]: ########## Logs: /var/log/rabbitmq/rabbit@node6.log
Dec 29 17:44:08 node6 rabbitmq-server[2948]: ###### ## /var/log/rabbitmq/rabbit@node6-sasl.log
Dec 29 17:44:08 node6 rabbitmq-server[2948]: ##########
Dec 29 17:44:08 node6 rabbitmq-server[2948]: Starting broker...
Dec 29 17:44:10 node6 rabbitmq-server[2948]: systemd unit for activation check: "rabbitmq-server.service"
Dec 29 17:44:10 node6 systemd[1]: Started RabbitMQ broker.
Dec 29 17:44:10 node6 rabbitmq-server[2948]: completed with 6 plugins.
[root@node6 rabbitmq]#
启动项目
按照顺序依次启动项目
spring-cloud-eureka-service
spring-cloud-config-server
spring-cloud-eureka-provider-1
spring-cloud-eureka-provider-2
spring-cloud-eureka-provider-3
spring-cloud-feign-consumer
启动该工程后,访问服务注册中心,查看服务是否都已注册成功:http://127.0.0.1:8761/
Exchanges
任何发送到Fanout Exchange
的消息都会被转发到与该Exchange
绑定(Binding
)的所有springCloudBus
队列Queue
上。
检查Queues
浏览器打开 :http://192.168.252.128:15672/
修改配置
修改Git
仓库配置,在 content=hello dev
后面加上 Spring Cloud Bus Test
查看 Config Server
通过 Postman
发送 GET
请求到:http://localhost:8888/springCloudConfig/dev/master 查看 Config Server
是否是最新的值
查看 Config Client
命令窗口,通过curl http://127.0.0.1:9000/hello
访问服务,或者在浏览器访问http://127.0.0.1:9000/hello
F5 刷新
发现没有得到最新的值
因为我们没有主动触发Config Server bus/refresh
接口
刷新配置
通过 Postman
发送 POST
请求到:http://localhost:8888/bus/refresh ,我们可以看到以下内容:
注意是 PSOT
请求
三个Config Client
客户端控制台,分别会打印以下内容意思就是,收到远程更新请求,config.client,KEYS
刷新, key
是 content
2017-12-29 18:38:49.023 INFO 28944 --- [jeTgrKRGzgj9g-1] o.s.cloud.bus.event.RefreshListener : Received remote refresh request. Keys refreshed [config.client.version, content]
2017-12-29 18:38:49.025 INFO 28944 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_EUREKA-PROVIDER/localhost:eureka-provider:8081: registering service...
2017-12-29 18:38:49.035 INFO 28944 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_EUREKA-PROVIDER/localhost:eureka-provider:8081 - registration status: 204
2017-12-29 18:38:49.067 INFO 28944 --- [jeTgrKRGzgj9g-1] o.s.a.r.c.CachingConnectionFactory : Created new connection: SpringAMQP#31e87320:0/SimpleConnection@39151d4e [delegate=amqp://ymq@192.168.252.126:5672/, localPort= 64885]
再次查看 Config Client
访问:http://localhost:8081/ ,http://localhost:8082/ ,http://localhost:8083/ 已经刷新了配置
扩展阅读
Git webhooks
现在虽然可以不用重启服务就更新配置了,但还是需要我们手动操作,这样还是不可取的。所以,这里就要用到Git的webhooks来达到自动更新配置。
打开git上配置仓库的地址,添加webhooks
,上面Payload URL
我写的域名,当然我没有部署,上面的Payload URL
就填写我们的配置中心触发刷新的地址,当然这里不能写localhost
啦,要外网访问地址才行。
还有这里面有个Secret的秘钥验证,如果这里填写的话,在配置文件上要写上encrypt.key
与之对应。
局部刷新
某些场景下(例如灰度发布),我们可能只想刷新部分微服务的配置,此时可通过/bus/refresh
端点的destination
参数来定位要刷新的应用程序。
例如:/bus/refresh?destination=customers:8000
,这样消息总线上的微服务实例就会根据destination
参数的值来判断是否需要要刷新。其中,customers:8000
指的是各个微服务的ApplicationContext ID
。
destination
参数也可以用来定位特定的微服务。例如:/bus/refresh?destination=customers:**
,这样就可以触发customers
微服务所有实例的配置刷新。
跟踪总线事件
一些场景下,我们可能希望知道Spring Cloud Bus
事件传播的细节。此时,我们可以跟踪总线事件(RemoteApplicationEvent
的子类都是总线事件)。
跟踪总线事件非常简单,只需设置spring.cloud.bus.trace.enabled=true
,这样在/bus/refresh
端点被请求后,访问/trace
端点就可获得类似如下的结果:
发送 GET
请求到:http://localhost:8888/trace
[
{
"timestamp": 1514543931362,
"info": {
"method": "GET",
"path": "/eureka-provider/dev/master",
"headers": {
"request": {
"accept": "application/json, application/*+json",
"user-agent": "Java/1.8.0_112",
"host": "localhost:8888",
"connection": "keep-alive"
},
"response": {
"X-Application-Context": "config-server:8888",
"Content-Type": "application/json;charset=UTF-8",
"Transfer-Encoding": "chunked",
"Date": "Fri, 29 Dec 2017 10:38:51 GMT",
"status": "200"
}
},
"timeTaken": "6002"
}
},
{
"timestamp": 1514543927451,
"info": {
"method": "GET",
"path": "/eureka-provider/dev/master",
"headers": {
"request": {
"accept": "application/json, application/*+json",
"user-agent": "Java/1.8.0_112",
"host": "localhost:8888",
"connection": "keep-alive"
},
"response": {
"X-Application-Context": "config-server:8888",
"Content-Type": "application/json;charset=UTF-8",
"Transfer-Encoding": "chunked",
"Date": "Fri, 29 Dec 2017 10:38:47 GMT",
"status": "200"
}
},
"timeTaken": "4927"
}
},
{
"timestamp": 1514543925254,
"info": {
"method": "GET",
"path": "/eureka-provider/dev/master",
"headers": {
"request": {
"accept": "application/json, application/*+json",
"user-agent": "Java/1.8.0_112",
"host": "localhost:8888",
"connection": "keep-alive"
},
"response": {
"X-Application-Context": "config-server:8888",
"Content-Type": "application/json;charset=UTF-8",
"Transfer-Encoding": "chunked",
"Date": "Fri, 29 Dec 2017 10:38:45 GMT",
"status": "200"
}
},
"timeTaken": "2862"
}
},
{
"timestamp": 1514543923565,
"info": {
"method": "POST",
"path": "/bus/refresh",
"headers": {
"request": {
"cache-control": "no-cache",
"postman-token": "0e497ec1-0c03-4dc2-bb61-ce2a266227d3",
"user-agent": "PostmanRuntime/7.1.1",
"accept": "*/*",
"host": "127.0.0.1:8888",
"accept-encoding": "gzip, deflate",
"content-length": "0",
"connection": "keep-alive"
},
"response": {
"X-Application-Context": "config-server:8888",
"status": "200"
}
},
"timeTaken": "6616"
}
}
]