什么是服务治理
在传统MVC架构的项目中,服务调用往往都是一对一的,这种服务调用关系比较简单同时也很好管理,但是随着业务规模越来越大,一个系统被拆分成多个模块,在不同的模块中进行RPC远程调用,管理每个服务于服务之间的依赖关系比较复杂,所以需要使用服务治理,管理服务之间的依赖关系,可以实现服务调用;负载均衡;集群容错等,实现服务发现和注册。
Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理。
Eureka架构图
Eureka包含两个组件:Eureka Client(客户端)和Eureka Server(服务端)
- Eureka Server 提供服务注册
各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用于服务节点的信息,服务节点的信息可以在Euerka管理界面中看到
- Eureka Client通过注册中心访问
是一个Java客户端,内置使用轮询(round-robin)
负载均衡算法。应用在启动后,会向Eureka Server发送心跳(默认周期30秒);如果Eureka Server在多个心跳周期内没有接受到某个节点心跳,Eureka Server将会从服务注册表中把这个服务节点移除(90秒)。
Eureka Server搭建
- 引入pom文件
<!--eureka server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
在Eureka架构图中我们了解到,Eureka分为Client端和Server端,在搭建服务端的时候我们要使用spring-cloud-starter-netflix-eureka-server,版本为2.2.1
- Eureka Server配置
server:
port: 7001
spring:
application:
name: cloud-eureka-server7001
#单机版配置
eureka:
instance:
# eureka 服务端的实例名称
hostname: localhost
client:
# false 表示不向注册中心注册自己(服务端不用自己注册自己)
register-with-eureka: false
# # false 表示自己就是注册中心, 我的职责是去维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# # 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 主启动类添加注解
/**
* @author 张江丰
* @version 11:10
* EnableEurekaServer 启用并声明Eureka服务端
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
@EnableEurekaServer 开启Eureka Server服务
- 运行项目查看Eureka管理界面
这样我们就运行了一个单机版的Eureka Server服务端,下面我们在配置客户端注册到我们7001端口的Eureka服务。
Eureka Client搭建
- 引入Pom文件
<!--eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
客户端引入spring-cloud-starter-netflix-eureka-client,版本为2.2.1
- Eureka Client配置
#定义boot的项目名称,同时也是在eureka中注册的服务名称,相同的服务使用同一个名称
eureka:
client:
# false 表示不向注册中心注册自己(客户端需要向服务端注册自己)
register-with-eureka: true
# 是否从 EurekaServer 抓取已有的注册信息,默认为 true. 单节点无所谓,集群必须设置为 true,才能配置 ribbon 使用负载均衡
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka/ 连接单机版eureka
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,这里eureka集群
defaultZone: http://localhost:7001/eureka/
- 启动类添加注解
/**
* EnableEurekaClient 配置并声明这是Eureka客户端
* EnableDiscoveryClient 配置客户端服务发现DiscoveryClient
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PanymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PanymentMain8001.class, args);
}
}
@EnableEurekaClient 开启Eureka Client服务;@EnableDiscoveryClient 配置客户端服务发现DiscoveryClient
- 分别启动Eureka Server和Eureka Client项目
可以看到Eureka Client客户端已经注册到Eureka Server服务端
Eureka Server 服务端集群搭建实现高可用
什么是高可用就不说了,对于服务发现治理的Eureka如果仅仅只是单机版,如果单节点服务挂掉了,那么整个微服务系统都会挂掉,所以我们要实现高可用就必须搭建集群服务。
Eureka 集群关键词就是:相互注册,相互守望。
- Eureka Server配置
分别创建两个Eureka Server项目端口分别为7001,7002;我本地通过hosts配置了两个本地域名分别为eureka7001.com对应7001端口,eureka7002.com对应7002端口
#-----------spring cloud---------------
#eureka注册中心服务搭建集群,在一台电脑上无法配置两个eureka映射地址,这里用两个本地域名映射两个eureka服务名来替代
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
#---------------------------------------
#集群配置
eureka:
instance:
# eureka 服务端的实例名称(在hosts文件中定义映射)
hostname: eureka7001.com
client:
# false 表示不向注册中心注册自己(服务端不用自己注册自己)
register-with-eureka: false
# false 表示自己就是注册中心, 我的职责是去维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,集群设置7002,相互注册
#如果是多个集群,通过,号分割;例如:defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
defaultZone: http://eureka7002.com:7002/eureka/
server:
#关闭eureka自我保护机制(服务不可用(心跳交互超时)删除服务),默认是开启(开启状态,eureka不会立即剔除失效的服务信息【可能存在网络抖动等因素无法与eureka服务交换心跳,但是服务本身是可用的】)
enable-self-preservation: false
#设置接收客户端心跳时间间隔(单位毫秒,默认是90秒),
eviction-interval-timer-in-ms: 2000
#集群配置
eureka:
instance:
# eureka 服务端的实例名称
hostname: eureka7002.com
client:
# false 表示不向注册中心注册自己
register-with-eureka: false
# false 表示自己就是注册中心, 我的职责是去维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,集群设置7001,相互注册
#如果是多个集群,通过,号分割;例如:defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
defaultZone: http://eureka7001.com:7001/eureka/
server:
#关闭eureka自我保护机制(服务不可用(心跳交互超时)删除服务),默认是开启(开启状态,eureka不会立即剔除失效的服务信息【可能存在网络抖动等因素无法与eureka服务交换心跳,但是服务本身是可用的】)
enable-self-preservation: false
#设置接收客户端心跳时间间隔(单位毫秒,默认是90秒)
eviction-interval-timer-in-ms: 2000
两个Eureka Server配置,defaultZone配置另一个Eureka服务(相互注册),如果有多个集群环境搭建,通过逗号分隔。
- 分别启动Eureka Server 7001;7002两个服务
可以看到Eureka Server两台服务实现集群服务,相互注册,相互守望。。。
Eureka Client 客户端搭建
- 微服务Provoder配置连接Eureka集群服务
#定义的项目名称,同时也是在eureka中注册的服务名称,相同的服务使用同一个名称
spring:
application:
name: cloud-payment-service
#定义boot的项目名称,同时也是在eureka中注册的服务名称,相同的服务使用同一个名称
eureka:
client:
# false 表示不向注册中心注册自己(客户端需要向服务端注册自己)
register-with-eureka: true
# 是否从 EurekaServer 抓取已有的注册信息,默认为 true. 单节点无所谓,集群必须设置为 true,才能配置 ribbon 使用负载均衡
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka/ 连接单机版eureka
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,这里eureka集群
defaultZone: http://localhost:7001/eureka/,http://eureka7002.com:7002/eureka/
-微服务Consumer配置连接Eureka集群服务
spring:
application:
name: cloud-consumer-order80
eureka:
client:
# false 表示不向注册中心注册自己(客户端需要向服务端注册自己)
register-with-eureka: true
# false 表示自己就是注册中心, 我的职责是去维护服务实例,并不需要去检索服务
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka/ 连接单机版eureka
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,这里连接的是eureka集群
defaultZone: http://localhost:7001/eureka/,http://eureka7002.com:7002/eureka/
-
分别启动Eureka服务集群,微服务Customer和Provider
-
服务接口调用测试
使用RestTemplate进行服务接口调用,使用@LoadBalanced实现负载均衡(默认是轮询)
/**
* @author 张江丰
* @version 20:34
* RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务的模板类,是spring提供的用于访问Rest服务的客户端模板工具集。
*/
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //开启RestTemplate的负载均衡,Ribbon轮训“顺序”调用集群服务
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
微服务消费者通过接口调用服务提供者,调用地址为注册到Eureka的服务名
@RestController
@Slf4j
public class OrderController {
//写死IP地址,相当于是通过restTemplate直连指定服务
// private final static String PAYMENT_URL = "http://localhost:8001";
//配置eureka服务名称,通过服务名在euraka集群服务列表获取服务
//必须配置负载均衡(RestTemplate),否则在服务名称下存在多个服务实体,否则无法确定到服务提供者
private final static String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class, id);
}
实现负载均衡,服务提供者8001与8002端口交替出现
微服务信息完善
-显示微服务名称与IP地址
#定义boot的项目名称,同时也是在eureka中注册的服务名称,相同的服务使用同一个名称
eureka:
client:
# false 表示不向注册中心注册自己(客户端需要向服务端注册自己)
register-with-eureka: true
# 是否从 EurekaServer 抓取已有的注册信息,默认为 true. 单节点无所谓,集群必须设置为 true,才能配置 ribbon 使用负载均衡
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka/ 连接单机版eureka
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,这里eureka集群
defaultZone: http://localhost:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
#配置Eureka显示主机名
instance-id: payment8001
#显示主机IP地址
prefer-ip-address: true
-服务发现DiscoveryClient
在微服务提供者8001启动类,加上注解@EnableDiscoveryClient
/**
* EnableEurekaClient 配置并声明这是Eureka客户端
* EnableDiscoveryClient 配置客户端服务发现DiscoveryClient
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PanymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PanymentMain8001.class, args);
}
}
引入DiscoveryClient对象,主要有两个方法getServices()获取微服务实例名集合;getInstances(服务实例名)获取服务实例的信息
//引入服务发现客户端DiscoveryClient,获取服务信息;启动类需要配置开启DiscoveryClient
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/getDiscoveryClient")
public Object getDiscoveryClient(){
//获取服务名称
List<String> elements = discoveryClient.getServices();
for (String element : elements) {
log.info("*****element:"+element);
}
//获取指定服务名称的信息
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instanceInfo : instances) {
log.info("获取CLOUD-PAYMENT-SERVICE服务的信息:"+instanceInfo.getHost()+"\t"+instanceInfo.getPort()+"\t"+instanceInfo.getUri());
}
return discoveryClient;
}
-调用信息显示
2020-08-13 16:21:39.117 INFO 2132 --- [nio-8001-exec-1] c.z.s.c.controller.PaymentController : *****element:cloud-payment-service
2020-08-13 16:21:39.117 INFO 2132 --- [nio-8001-exec-1] c.z.s.c.controller.PaymentController : *****element:cloud-consumer-order80
2020-08-13 16:21:39.119 INFO 2132 --- [nio-8001-exec-1] c.z.s.c.controller.PaymentController : 获取CLOUD-PAYMENT-SERVICE服务的信息:172.23.200.33 8002 http://172.23.200.33:8002
2020-08-13 16:21:39.120 INFO 2132 --- [nio-8001-exec-1] c.z.s.c.controller.PaymentController : 获取CLOUD-PAYMENT-SERVICE服务的信息:172.23.200.33 8001 http://172.23.200.33:8001
Eureka自我保护机制
默认情况下,如果EurekaServer在一定时间内没有接受到某个注册微服务实例的心跳,EurekaServer将会注销该实例(默认90秒),但是当网路故障发生(延迟,卡顿…),EurekaServer与微服务之间无法正常通行,那么直接注销微服务实例就不那么合适了,因为微服务实例本身是健康的,当网络故障修复后还能继续提供服务。Eureka通过“自我保护机制”来解决这个问题,当微服务节点在短时间内丢失过多客户端时(可能发生网络故障),那么这个节点就会进入自我保护机制,EurekaServer不会删除该微服务注册实例信息。
Eureka由此可见属于分布式微服务的AP分支,即保证了分布式微服务的可用性与分区容错性,无法保证微服务的一致性。
-关闭EurekaServer自我保护机制
EurekaServer服务配置
#集群配置
eureka:
instance:
# eureka 服务端的实例名称(在hosts文件中定义映射)
hostname: eureka7001.com
client:
# false 表示不向注册中心注册自己(服务端不用自己注册自己)
register-with-eureka: false
# false 表示自己就是注册中心, 我的职责是去维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,集群设置7002,相互注册
#如果是多个集群,通过,号分割;例如:defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
defaultZone: http://eureka7002.com:7002/eureka/
server:
#关闭eureka自我保护机制(服务不可用(心跳交互超时)删除服务),默认是开启(开启状态,eureka不会立即剔除失效的服务信息【可能存在网络抖动等因素无法与eureka服务交换心跳,但是服务本身是可用的】)
enable-self-preservation: false
#设置接收客户端心跳时间间隔(单位毫秒,默认是90秒),
eviction-interval-timer-in-ms: 2000
EurekaClient微服务实例配置,以8001服务提供者为例
#定义boot的项目名称,同时也是在eureka中注册的服务名称,相同的服务使用同一个名称
eureka:
client:
# false 表示不向注册中心注册自己(客户端需要向服务端注册自己)
register-with-eureka: true
# 是否从 EurekaServer 抓取已有的注册信息,默认为 true. 单节点无所谓,集群必须设置为 true,才能配置 ribbon 使用负载均衡
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka/ 连接单机版eureka
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖的这个地址,这里eureka集群
defaultZone: http://localhost:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
#配置Eureka显示主机名
instance-id: payment8001
#显示主机IP地址
prefer-ip-address: true
#指定Eureka客户端向服务端发送心跳时间,单位秒默认是(30秒)
lease-expiration-duration-in-seconds: 1
#指定Eureka服务端在最后一次接收到心跳后等待时间,两次心跳等待时间单位秒(默认90秒),剔除服务
lease-renewal-interval-in-seconds: 2
关闭自我保护机制,EurekaServer启动效果
当停止8001微服务实例后,EurekaServer会立即删除该微服务