Eureka服务注册与发现
Eureka服务注册与发现
服务治理
Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理。
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册与发现
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册的中心。而系统中其他的微服务,使用了Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心上,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念).在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址));
左边是Eureka系统架构,右边是Dubbo的架构

Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务
各个微服务节点通过配置启动,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到.
EurekaClient通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器.在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接受到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。

服务端安装
新建model:cloud-eureka-server7001
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
applicaiton.yml
server:
port: 7001
eureka:
instance:
hostname: localhost # eureka服务端的实例名称
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务.
fetch-registry: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动类
package com.kayleh.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @Author: Wizard
* @Date: 2020/8/4 15:57
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
支付微服务8001入驻进EurekaServer
微服务8001的pom文件:添加坐标
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml下添加
eureka:
client:
# 表示是否将自己注册进eurekaServer默认为true
register-with-eureka: true
# 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
defaultZone: http://localhost:7001/eureka
主启动类添加client注解
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
微服务注册名配置说明

访问Eureka出现红字原因:
自我保护机制.
配置微服务80进驻Eureka;
改pom,添加坐标
改yml
server:
port: 80
spring:
application:
name: cloud-order-service
eureka:
client:
# 表示是否将自己注册进eurekaServer默认为true
register-with-eureka: true
# 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
defaultZone: http://localhost:7001/eureka
主启动类添加client注解.
集群eureka构建
eureka集群原理分析

解决办法:搭建eureka注册中心集群,实现负载均衡+故障容错
构建集群(单机走向集群)
新建model:cloud-eureka-server7002
复制微服务7001的pom.xml
修改C:\Windows\System32\drivers\etc目录下的hosts文件
添加进hosts文件
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
修改7001和7002的application.yml (如果是三台集群的话,在service-url下继续写,用逗号分隔开)
7001:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务.
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
--------------------------------------------------------
7002:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务.
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
7002启动类加注解
访问
eureka7001.com:7001
eureka7002.com:7002
将80和8001模块注册进eureka, 修改yaml文件
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
测试
http://localhost/consumer/payment/get/1
支付服务提供者8001集群环境构建
新建cloud-provider-payment8002
pom.xml和8001一致
copy 8001的yml文件到8002,修改端口号
server:
port: 8002
主启动类,业务类 直接cpoy8001
修改8001和8002的controller
添加
@Value("${server.port}")
private String serverPort;
修改打印
"插入数据库成功,serverPort" + serverPort
"查询成功,serverPort:" + serverPort
负载均衡
修改消费者80模块的OrderController的订单服务访问地址
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

修改ApplicationContextConfig
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
添加@LoadBalanced开启RestTemplate的负载均衡
测试
http://localhost/consumer/payment/get/1
可以看到8001端口和8002端口交替出现.
Ribbon和Eureka整合后,Consumer可以直接调用服务而不用关心地址和端口号,且该服务还有负载功能.
axtuator微服务信息完善
当前问题

暴露主机名
修改cloud-provider-payment8001和8002 的yml
eureka:
client:
# 表示是否将自己注册进eurekaServer默认为true
register-with-eureka: true
# 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所
fetch-registry: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8001
添加instance配置
更改之后

点开链接,测试
http://wizard:8002/actuator/health
访问地址显示ip地址
修改8001,8002的yml,添加
instance:
instance-id: payment8001
prefer-ip-address: true
服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息.
修改8001的controller
添加
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = "/payment/discovery")
public Object discovery() {
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("----------element:" + element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
}
return this.discoveryClient;
}
启动类添加注解
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
测试
http://localhost:8001/payment/discovery
------------------------------------------
{"services":["cloud-order-service","cloud-payment-service"],"order":0}

Eureka的自我保护机制
故障现象:

导致原因:
某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存.
属于CAP里面的AP分支



关闭自我保护
修改7001的yaml
添加
server:
enable-self-preservation: false
---------------------------------------
eureka:
instance:
hostname: eureka7001.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
server:
#关闭自我保护机制,保证不可用服务被及时删除
enable-self-preservation: false
#时间间隔
eviction-interval-timer-in-ms: 2000
8001的yml加入
instance:
instance-id: payment8001
prefer-ip-address: true
# eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 1
# eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-renewal-interval-in-seconds: 2
Zookepper
需要Linux安装Zookepper
新建工程cloud-provider-payment8004
pom.xml
<dependencies>
<!-- Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 8004
#服务别名
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
# zookeeper的机器ip加端口号
connect-string: 192.168.111.144:2181
启动类
package com.kayleh.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @Author: Wizard
* @Date: 2020/8/5 16:58
*/
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class, args);
}
}
PaymentController
package com.kayleh.springcloud.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @Author: Wizard
* @Date: 2020/8/5 17:00
*/
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/zk")
public String paymentzk() {
return "Springcloud with zookeeper:" + serverPort + "\t" + UUID.randomUUID().toString();
}
}
jar包冲突
<!-- Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!-- 排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加zookeeper3.4.9-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
测试

访问
localhost:8004/payment/zk

是临时节点,项目停止后,连接会持续一小段时间,然后丢失。重新连接后是另一个UUID的Zookepper。

订单服务注册zookeeper
新建cloud-consumerzk-order80
复制80的pom
yml
server:
port: 80
#服务别名
spring:
application:
name: cloud-consumer-order
cloud:
zookeeper:
# zookeeper的机器ip加端口号
connect-string: 192.168.111.144:2181
启动:
package com.kayleh.springcloud;
@SpringBootApplication
@EnableDiscoveryClient
public class OrderZkMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderZkMain80.class, args);
}
}
config:
package com.kayleh.springcloud.config;
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
controller
package com.kayleh.springcloud.controller;
@RestController
@Slf4j
public class OrderZkController {
public static final String INVOKE_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(INVOKE_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(INVOKE_URL + "/payment/get/" + id, CommonResult.class);
}
}
服务访问地址INVOKE_URL填linux上的zookeeper名称

测试,启动 80zk 和 8004.

访问
http://localhost/consumer/payment/zk
Consul

https://www.consul.io/downloads.html

启动
consul agent -dev
访问
http://localhost:8500/ui/
服务提供者注册进Consul
新建模块cloud-providerConsul-payment8006
pom.xml
<dependencies>
<!--consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
yml
server:
port: 8006
#服务别名
spring:
application:
name: consul-provider-payment
###consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
# hostname: 127.0.0.1
主启动类
controller
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/consul")
public String paymentConsul() {
return "Springcloud with consul:" + serverPort + "\t" + UUID.randomUUID().toString();
}
}
测试
http://localhost:8006/payment/consul
服务消费者注册进Consul
新建模块cloud-consumer-consul-order80
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
主启动类
复制cloud-consumerzk-order80模块的ApplicationContextConfig
controller
@RestController
@Slf4j
public class OrderConsulController {
public static final String INVOKE_URL = "http://consul-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/consul")
public String paymentInfo() {
String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class);
return result;
}
}
三个注册中心的异同



C : Consistency(强一致性)
A : Availability(可用性)
P : Partition tolerance(分区容错性)
CAP理论关注粒度是数据,而不是整体系统设计的策略
AP架构
当网络分区出现后,为了保证可用性,系统B可用返回旧值,保证系统的可用性
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
CP架构
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP
