微服务概念
服务注册中心
Eureka
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
⚠️ 2018年7月份,Eureka官方停止对Eureka2.0进行维护与更新!
核心概念
如下服务注册调用示意图,服务提供者和服务的消费者,本质上也是 Eureka Client 角色。整体上可以分为两个主体:Eureka Serve
r 和Eureka Client
。

搭建Eureka Server服务注册中心
引入依赖
1
2
3
4
5<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>在SpringBoot主启动类中开启EurekaServer
1
2
3
4
5
6
7
// 注册中心
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer7001.class,args);
}
}
配置Eureka
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15spring:
application:
name: eureka-server # 应用名称
server:
port: 7001 # 更改端口 默认8761端口
eureka:
instance:
hostname: localhost # 实例主机
client:
fetch-registry: false # 自己就是服务注册中心,不需要去检索服务
register-with-eureka: false # 不向服务注册中心注册自己
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 服务注册需要依赖这个地址启动项目,访问
http://localhost:7001
,看见以下界面则搭建成功!
搭建Eureka Cilent
引入依赖
1
2
3
4
5<!-- eureka-cilent -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-cilent</artifactId>
</dependency>在SpringBoot主启动类中开启EurekaCilent
1
2
3
4
5
6
7
// eureka cilent
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}配置eureka
1
2
3
4
5
6
7
8
9
10
11server:
port: 8001 # 端口
spring:
application:
name: cloud-payment-service # 项目名称
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/首先启动Eureka Server,然后再启动Eureka Cilent。看到以下界面便是成功
Eureka集群
实际工作生产环境中服务注册中心肯定不止一个。若只有一个,万一这个注册中心宕机了,那么整个微服务环境将不可用。为了提高整个系统的高可用
性,所以可考虑部署注册中心集群,实现负载均衡 + 故障容错
。
搭建Eureka Server集群
⚠️ Eureka集群主要特点: 相互注册、相互守望
。
修改
/etc/hosts
文件⚠️非常重要!
1
2
3
4
5# Eureka Server集群hostname
# 实际生产环境将127.0.0.1换成Eureka Server服务器的ip
127.0.0.1 eureka7001.com # 实例1
127.0.0.1 eureka7002.com # 实例2
127.0.0.1 eureka7003.com # 实例3配置Eureka Server实例1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15spring:
application:
name: eureka-server
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka server实例名称
client:
fetch-registry: false # 自己就是服务注册中心,不需要去检索服务
register-with-eureka: false # 不向服务注册中心注册自己
service-url: # 集群.将本服务注册到集群中的其他Eureka Server
defaultZone: http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka/配置Eureka Server实例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14spring:
application:
name: eureka-server
server:
port: 7002
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url: # 集群.将本服务注册到集群中的其他Eureka Server
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7003.com:7003/eureka/
instance:
hostname: eureka7002.com # eureka server实例名称配置Eureka Server实例3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15spring:
application:
name: eureka-server
server:
port: 7003
eureka:
instance:
hostname: eureka7003.com
client:
fetch-registry: true
register-with-eureka: true
service-url: # 集群,相互注册
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka/依次启动Eueka Server实例1、实例2、实例3,在每个实例页面都可以看到其他两个Eureka Server实例则集群部署成功。 如下图所示:



业务提供者集群
新建业务实例,核心代码如下,两个实例代码完全相同,但
端口不同
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class PaymentController {
private PaymentService paymentService;
private String serverPort; // 获取实例的端口,以此区分业务消费者调用的哪个业务提供者。
public CommonResult getById( Long id){
Payment payment = paymentService.getPaymentById(id);
if (ObjectUtils.isEmpty(payment)){
return new CommonResult<>(999,"无此记录, serverPort=" + serverPort,payment);
}
return new CommonResult<>(200,"查询成功, serverPort=" + serverPort,payment);
}
public CommonResult save( Payment payment){
if (paymentService.save(payment) !=1){
return new CommonResult<>(500,"save payment failed, serverPort=" + serverPort);
}
return new CommonResult<>(200,"save payment success, serverPort=" + serverPort);
}
}
业务提供者实例1配置文件
1
2
3
4
5
6
7
8
9
10
11
12server:
port: 8001 # 实例1端口
spring:
application:
name: cloud-payment-service # 应用名称
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka/ # 将本实例注册到注册中心集群业务提供者实例2配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13server:
port: 8002 # 实例2端口
spring:
application:
name: cloud-payment-service # 应用名称
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka/ # 将本实例注册到注册中心集群
将业务提供者实例1、实例2依次启动,并注册到注册中心集群。可以看到,注册中心已经有应用名称为
CLOUD-PAYMENT-SERVICE
的服务,并且该服务是个集群,有2个实例,分别占用8001
及8002
端口。业务消费者核心代码
1
2
3
4
5
6
7
8
9
10
11
12/**
* 配置类,将RestTemplate对象注入Spring容器
*/
public class RestConfig {
// 负载均衡 使用微服务名称直接调用服务 重要!!!
public RestTemplate restTemplate(){
return new RestTemplate();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class OrderController {
// 集群版 直接使用微服务名称调用服务
private final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
private RestTemplate restTemplate;
public CommonResult getPaymentById( Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/"+id, CommonResult.class);
}
public CommonResult save( Payment payment){
return restTemplate.postForObject(PAYMENT_URL + "/payment/save", payment, CommonResult.class);
}
}
⚠️ 千万别忘了注入Bean时添加@LoadBalanced
注解,否则会报java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE
异常!
演示业务消费者端通过RestTemplate调用业务提供者集群的结果:
服务发现Discovery
启动类上加入
@EnableDiscoveryCilent
注解1
2
3
4
5
6
7
8
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
核心代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class PaymentController {
private DiscoveryClient discoveryClient;
public Object discovery(){
// 获取全部微服务的服务名称
List<String> services = discoveryClient.getServices();
// 遍历输出服务名称
for (String service : services) {
log.info("*********service = " + service);
// 获取某个服务名称的实例
List<ServiceInstance> instances = discoveryClient.getInstances(service);
// 遍历输出实例的一些信息
for (ServiceInstance instance : instances) {
log.info(instance.getInstanceId()
+ "\t" + instance.getHost()
+ "\t" + instance.getPort()
+ "\t" + instance.getUri());
}
}
return discoveryClient;
}
}
输出结果
Eureka自我保护机制
自我保护机制:
默认情况下Eureka Client定时(默认周期为30秒
)向Eureka Server端发送心跳包,如果Eureka在server端在一定时间内(默认90秒
)没有收到EurekaClient发送心跳包 ,便会直接从服务注册列表中剔除该服务。但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eureka Senver会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误。因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)简而言之:某时刻某个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
属于CAP中的AP分支
。
禁止自我保护机制
1 | eureka |

Consul
Consul是一套开源的分布式
服务发现
和配置管理
系统,由HashiCorp公司用Go语言
开发。Consul提供了微服务系统中的
服务治理
、配置中心
、控制总线
等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的微服务网络。优点:
- 基于raft协议,比较简洁。
- 支持健康检查,同时支持Http协议和DNS协议。
- 提供图形界面。
- 跨平台,支持Linux、Mac、Windows。
Cent OS7下载安装Consual
进入Consul官网进行下载后,解压后是个可执行文件,在终端执行以下命令即可运行
1 | ./consul agent -dev |

关闭防火墙或打开8500端口
1 | # 永久开放8500端口 推荐 |
验证是否成功运行Consul
打开浏览器访问http://localhost:8500/
,能看到以下界面即成功。

将服务提供者注册进Conusl
引入依赖
1
2
3
4
5<!--consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>主启动类
1
2
3
4
5
6
7
public class PaymentConsulMain8006 {
public static void main(String[] args) {
SpringApplication.run(PaymentConsulMain8006.class,args);
}
}服务提供者核心业务类
1
2
3
4
5
6
7
8
private String serverPort;
public String paymentConsul(){
return "spring cloud with consul : serverPort = "
+ serverPort + "\t" + UUID.randomUUID().toString();
}配置consul
1
2
3
4
5
6
7
8
9
10
11
12
13
14server:
port: 8006 # 端口
spring:
application:
name: cloud-payment-consul # 项目名称
cloud:
consul: # 注册服务至consul
host: localhost
port: 8500
discovery:
hostname: 127.0.0.1
service-name: ${spring.application.name}
启动后再次进入
http://localhost:8500/
,可以看到新增了一个服务。
将服务消费者注册进Consul
引入依赖
1
2
3
4
5<!--consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>主启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 重要!!
public class OrderConsulMain82 {
public static void main(String[] args) {
SpringApplication.run(OrderConsulMain82.class,args);
}
// 重要!!
public RestTemplate restTemplate(){
return new RestTemplate();
}
}消费者核心业务类
1
2
3
4
5
6
7
8
9
10
11
private RestTemplate restTemplate;
// 使用服务名进行远程调用服务提供者
private static final String INVOKE_URL = "http://cloud-payment-consul";
public String orderConsul(){
return restTemplate.getForObject(INVOKE_URL + "/payment/consul",String.class);
}配置conusl
1
2
3
4
5
6
7
8
9
10
11
12
13server:
port: 82 # 端口
spring:
application:
name: cloud-order-consul # 服务名
cloud:
consul: # 注册进consul
host: localhost
port: 8500
discovery:
hostname: 127.0.0.1
service-name: ${spring.application.name}
启动服务消费者,可以看到consul可视页面已经新增了一服务。
使用服务消费者调用服务提供者业务

直接使用服务提供者提供的业务也是没问题的。

Eureka与Consul的异同点
负载均衡与服务调用
Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套
客户端
负载均衡的工具
。Ribbon是Netflix发 布的开源项目,主要功能是提供
客户端的软件负载均衡算法和服务调用
。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
⚠️ 目前Ribbon已进入维护模式。

负载均衡(Loaa Balance)
简单来说就是讲用户的请求平均分摊到多个服务上,达到服务的高可用。常用负载均衡有Nginx、LVS。
Ribbon客户端负载均衡 VS Nginx服务端负载均衡
- Nginx是
服务器负载均衡
,客户端所有请求都会交给Nginx,由Nginx负载将这些请求转发给多个服务。 - Ribbon是
本地负载均衡
,在调用微服务接口的时候,会在注册中心上获取注册服务列表信息后缓存到JVM本地,然后在本地实现轮询负载均衡策略。
架构说明

Ribbon在工作时分成两步
第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server.
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询
、随机
和根据响应时间加权
。
POM依赖
1 | <!-- 在引入eureka cilent依赖时其实就已经引入了Ribbon依赖--> |
启用Ribbon负载均衡
在注入RestTemplate bean组件时,加上@LoadBalance
注解即可。
1 |
|
Robbon自带负载规则
核心组件:IRole
:是定义负载均衡规则的接口。一个规则可视为负载均衡的一种策略。
Robbon自带以下负载均衡策略。
RoundRobinRule
轮询RadomRole
随机RetryRule
先按照轮询的策略获取服务,若获取失败则在指定时间内重试,获取可用的服务。WeightResponseTimeRule
是对RetryRule
的扩展,响应速度越快的实例权重越大,越容易被选择。BestAvailableRule
先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务。AvailabilityFilteringRule
先过滤故障实例,再选择并发量小的实例。ZoneAvoidanceRule
默认规则,符合判断server所在区域的性能和server的可用性选择服务器。
替换默认规则
1 | /** |
1 |
|
⚠️ 注意:这个自定义类不能放在
@ComponentScan
所扫描到的包及其子包下
。否则它会被所有的@RibbonClients共享(意思就是覆盖所有客户端的默认值,达不到个性化定制的目的)。如果开发人员使用@ComponentScan(或@SpringBootApplication),那就必须采取措施避免被覆盖到(例如将其放入一个独立的,不重叠的包中,或以@ComponentScan指明要扫描的,并排除此配置类。
需要提一下的是,@SpringBootApplication
注解中包含的有@ComponentScan
注解。

