Eureka服务注册与发现
服务治理
Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理。
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册与发现
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册的中心。而系统中其他的微服务,使用了Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心上,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念).在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址));
左边是Eureka系统架构,右边是Dubbo的架构
![1596525780055]()
Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务
各个微服务节点通过配置启动,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到.
EurekaClient通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器.在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接受到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。
![1596526701490]()
服务端安装
新建model:cloud-eureka-server7001
pom.xml
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 30 31 32 33 34 35 36 37 38 39 40
| <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <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-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
1 2 3 4 5 6 7 8 9 10 11 12 13
| server: port: 7001 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
主启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.kayleh.springcloud;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication @EnableEurekaServer public class EurekaMain7001 { public static void main(String[] args) { SpringApplication.run(EurekaMain7001.class, args); } }
|
访问 http://localhost:7001
支付微服务8001入驻进EurekaServer
微服务8001的pom文件:添加坐标
1 2 3 4 5
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
|
application.yml下添加
1 2 3 4 5 6 7 8 9
| eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka
|
主启动类添加client注解
1 2 3 4 5 6 7
| @SpringBootApplication @EnableEurekaClient public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } }
|
微服务注册名配置说明
![1596529308640]()
访问Eureka出现红字原因:
自我保护机制.
配置微服务80进驻Eureka;
改pom,添加坐标
改yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| server: port: 80
spring: application: name: cloud-order-service
eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka
|
主启动类添加client注解.
集群eureka构建
eureka集群原理分析
![1596544509634]()
解决办法:搭建eureka注册中心集群,实现负载均衡+故障容错
构建集群(单机走向集群)
新建model:cloud-eureka-server7002
复制微服务7001的pom.xml
修改C:\Windows\System32\drivers\etc目录下的hosts文件
添加进hosts文件
1 2
| 127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com
|
修改7001和7002的application.yml (如果是三台集群的话,在service-url下继续写,用逗号分隔开)
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
| 7001: server: port: 7001 eureka: instance: hostname: eureka7001.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7002.com:7002/eureka/ -------------------------------------------------------- 7002: server: port: 7002 eureka: instance: hostname: eureka7002.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7001.com:7001/eureka/
|
7002启动类加注解
访问
1 2
| eureka7001.com:7001 eureka7002.com:7002
|
将80和8001模块注册进eureka, 修改yaml文件
1
| defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
|
测试
1
| http://localhost/consumer/payment/get/1
|
支付服务提供者8001集群环境构建
新建cloud-provider-payment8002
pom.xml和8001一致
copy 8001的yml文件到8002,修改端口号
主启动类,业务类 直接cpoy8001
修改8001和8002的controller
1 2 3 4 5 6 7
| 添加 @Value("${server.port}") private String serverPort;
修改打印 "插入数据库成功,serverPort" + serverPort "查询成功,serverPort:" + serverPort
|
负载均衡
修改消费者80模块的OrderController的订单服务访问地址
1
| public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
|
![1596602970422]()
修改ApplicationContextConfig
1 2 3 4 5 6 7 8 9
| @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } } 添加@LoadBalanced开启RestTemplate的负载均衡
|
测试
1
| http://localhost/consumer/payment/get/1
|
可以看到8001端口和8002端口交替出现.
Ribbon和Eureka整合后,Consumer可以直接调用服务而不用关心地址和端口号,且该服务还有负载功能.
axtuator微服务信息完善
当前问题
![1596603735423]()
暴露主机名
修改cloud-provider-payment8001和8002 的yml
1 2 3 4 5 6 7 8 9 10 11 12 13
| eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ instance: instance-id: payment8001 添加instance配置
|
更改之后
![1596604101892]()
点开链接,测试
1
| http://wizard:8002/actuator/health
|
访问地址显示ip地址
修改8001,8002的yml,添加
1 2 3
| instance: instance-id: payment8001 prefer-ip-address: true
|
服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息.
修改8001的controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 添加 @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; }
|
启动类添加注解
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } }
|
测试
1 2 3
| http://localhost:8001/payment/discovery ------------------------------------------ {"services":["cloud-order-service","cloud-payment-service"],"order":0}
|
![1596605686156]()
Eureka的自我保护机制
故障现象:
![1596605819802]()
导致原因:
某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存.
属于CAP里面的AP分支
![1596606071261]()
![1596606189669]()
![1596606288542]()
关闭自我保护
修改7001的yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 添加 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加入
1 2 3 4 5 6 7
| instance: instance-id: payment8001 prefer-ip-address: true lease-expiration-duration-in-seconds: 1 lease-renewal-interval-in-seconds: 2
|
Zookepper
需要Linux安装Zookepper
新建工程cloud-provider-payment8004
pom.xml
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency>
<dependency> <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
1 2 3 4 5 6 7 8 9 10 11 12
| server: port: 8004
spring: application: name: cloud-provider-payment cloud: zookeeper: connect-string: 192.168.111.144:2181
|
启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.kayleh.springcloud;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication @EnableDiscoveryClient public class PaymentMain8004 { public static void main(String[] args) { SpringApplication.run(PaymentMain8004.class, args); } }
|
PaymentController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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;
@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包冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> <exclusions> <exclusion> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.9</version> </dependency>
|
测试
![1596618784943]()
访问
1
| localhost:8004/payment/zk
|
![1596618927219]()
是临时节点,项目停止后,连接会持续一小段时间,然后丢失。重新连接后是另一个UUID的Zookepper。
![1596626952182]()
订单服务注册zookeeper
新建cloud-consumerzk-order80
复制80的pom
yml
1 2 3 4 5 6 7 8 9 10 11
| server: port: 80
spring: application: name: cloud-consumer-order cloud: zookeeper: connect-string: 192.168.111.144:2181
|
启动:
1 2 3 4 5 6 7 8 9
| package com.kayleh.springcloud;
@SpringBootApplication @EnableDiscoveryClient public class OrderZkMain80 { public static void main(String[] args) { SpringApplication.run(OrderZkMain80.class, args); } }
|
config:
1 2 3 4 5 6 7 8 9 10
| package com.kayleh.springcloud.config;
@Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
|
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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名称
![1596627798682]()
测试,启动 80zk 和 8004.
![1596628332087]()
访问
1
| http://localhost/consumer/payment/zk
|
Consul
![1596629165834]()
https://www.consul.io/downloads.html
![1596629552902]()
启动
访问
1
| http://localhost:8500/ui/
|
服务提供者注册进Consul
新建模块cloud-providerConsul-payment8006
pom.xml
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
<dependency> <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> <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server: port: 8006
spring: application: name: consul-provider-payment
cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
|
主启动类
controller
1 2 3 4 5 6 7 8 9 10 11
| @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(); } }
|
测试
1
| http://localhost:8006/payment/consul
|
服务消费者注册进Consul
新建模块cloud-consumer-consul-order80
pom.xml
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 30 31 32 33 34
| <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @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; }
}
|
三个注册中心的异同
![1596714189759]()
![1596714223828]()
![1596714422562]()
C : Consistency(强一致性)
A : Availability(可用性)
P : Partition tolerance(分区容错性)
CAP理论关注粒度是数据,而不是整体系统设计的策略
AP架构
当网络分区出现后,为了保证可用性,系统B可用返回旧值,保证系统的可用性
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
CP架构
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP
![1596714860983]()