五、消费者订单模块
好的,让我们在一起来背一遍:
1.建模块
2.改pom
3.写yml
4.启动类
5.业务类
业务类编写流程如下:
1.建表写sql
2.实体类(数据库映射实体类,向前端传递实体类)
3.书写dao和mapper映射文件
4.service(面向接口编程接口(接口写逻辑)和impl写实现)
5.controller
1.建Module
创建名为cloud-consumer-order80的maven工程。
2.改POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>com.caq.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-customer-order80</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</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> <scope>test</scope> </dependency> </dependencies> </project>
3.写YML
server: port: 80
4.主启动
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** Hello world! * */ @SpringBootApplication public class OrderMain80 { public static void main( String[] args ){ SpringApplication.run(OrderMain80.class, args); } }
5.业务类
实体类:
package com.caq.cloud.entities; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor public class Payment implements Serializable { private Long id; private String serial; }
package com.caq.cloud.entities; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T>{ private Integer code; private String message; private T data; public CommonResult(Integer code, String message){ this(code, message, null); } }
控制层:
package com.caq.cloud.controller; import com.caq.cloud.entities.CommonResult; import com.caq.cloud.entities.Payment; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class OrderController { public static final String PAYMENT_URL = "http://localhost:8001"; @Resource private RestTemplate resetTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment){ return resetTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id){ return resetTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class); } }
配置类:
package com.caq.cloud.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { //通过注解的方式注入,这样我们的容器里就有这个resttemplate对象 @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
测试
六、工程重构
观察cloud-consumer-order80与cloud-provider-payment8001两工程有重复代码(entities包下的实体),重构。
1.新建 - cloud-api-commons
2.POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>com.caq.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-api-commons</artifactId> <dependencies> <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>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.1.0</version> </dependency> </dependencies> </project>
3.entities
将cloud-consumer-order80与cloud-provider-payment8001两工程的公有entities包移至cloud-api-commons工程下。
4.maven clean、install cloud-api-commons工程,以供给cloud-consumer-order80与cloud-provider-payment8001两工程调用。
5.订单80和支付8001分别改造
将cloud-consumer-order80与cloud-provider-payment8001两工程的公有entities包移除
引入cloud-api-commons依赖
<dependency> <groupId>com.caq.cloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency>
api-common目录结构如下:
测试
七、Eureka
7.1 初始Eureka
7.1.1 什么是服务治理
Spring Cloud封装了Netflix 公司开发的Eureka模块来实现服务治理
在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
7.1.2 什么是服务注册与发现
Eureka采用了CS的设计架构,Eureka Sever作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)
Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
EurekaClient通过注册中心进行访问
它是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
7.2 EurekaServer服务端安装
1.创建名为cloud-eureka-server7001的Maven工程
2.修改pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>com.caq.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-eureka-server7001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--eureka-server--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <dependency> <groupId>com.caq.cloud</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-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> </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> </project>
区别:
<!-- eureka新旧版本 --> <!-- 以前的老版本(2018)--> <dependency> <groupid>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!-- 现在新版本(2020.2)--><!-- 我们使用最新的 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
3.添加application.yml
server: port: 7001 eureka: instance: hostname: eureka7001.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己。 fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: #单机就是7001自己 defaultZone: http://eureka7001.com:7001/eureka/
4.主启动
package com.caq.cloud; 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); } }
测试
运行EurekaMain7001,浏览器输入http://localhost:7001/回车,会查看到Spring Eureka服务主页。
7.2.1 支付微服务8001入驻进EurekaServer
EurekaClient端cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider
1.修改cloud-provider-payment8001
2.改POM
添加spring-cloud-starter-netflix-eureka-client依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
3.写YML
在原有基础上添加
eureka: client: #表示是否将自己注册进Eurekaserver默认为true。 register-with-eureka: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: defaultZone: http://localhost:7001/eureka
4.主启动
package com.caq.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } }
测试
浏览器输入 - http://localhost:7001/ 主页内的Instances currently registered with Eureka会显示cloud-provider-payment8001的配置文件application.yml设置的应用名cloud-payment-service
自我保护机制
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARELESSER THAN THRESHOLD AND HENCFT ARE NOT BEING EXPIRED JUST TO BE SAFE.
紧急情况!EUREKA可能错误地声称实例在没有启动的情况下启动了。续订小于阈值,因此实例不会为了安全而过期。
7.2.2 订单微服务80入驻进EurekaServer
EurekaClient端cloud-consumer-order80将注册进EurekaServer成为服务消费者consumer
1.修改cloud-consumer-order80模块
2.POM
在原有基础上添加
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
3.YML
server: port: 80 spring: application: name: cloud-order-service eureka: client: #表示是否将自己注册进Eurekaserver默认为true。 register-with-eureka: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
4.主启动
package com.caq.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class OrderMain80 { public static void main( String[] args ){ SpringApplication.run(OrderMain80.class, args); } }
5.业务类
package com.caq.cloud.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { //通过注解的方式注入,这样我们的容器里就有这个resttemplate对象 @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
package com.caq.cloud.controller; import com.caq.cloud.entities.CommonResult; import com.caq.cloud.entities.Payment; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class OrderController { // public static final String PAYMENT_URL = "http://localhost:8001"; public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE"; @Resource private RestTemplate resetTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment){ return resetTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id){ return resetTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class); } }
测试
启动cloud-provider-payment8001、cloud-eureka-server7001和cloud-consumer-order80这三工程。
浏览器输入 http://localhost:7001 , 在主页的Instances currently registered with Eureka将会看到cloud-provider-payment8001、cloud-consumer-order80两个工程名。
访问测试
7.3 Eureka集群
7.3.1 Eureka集群原理说明
RPC(Remote Procedure Call):远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。
RPC 是一种技术思想而非一种规范或协议,常见 RPC 技术和框架有:
应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
通信框架:MINA 和 Netty。
服务注册:将服务信息注册进注册中心
服务发现:从注册中心上获取服务信息
实质:存key服务命取value闭用地址
1.先启动eureka注主册中心
2.启动服务提供者payment支付服务
3.支付服务启动后会把自身信息(比服务地址L以别名方式注朋进eureka
4.消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
5.消去者导调用地址后,底屋实际是利用HttpClient技术实现远程调用
6.消费者实癸导服务地址后会缓存在本地jvm内存中,默认每间隔30秒更新—次服务调用地址
问题:微服务RPC远程服务调用最核心的是什么
高可用,试想你的注册中心只有一个only one,万一它出故障了,会导致整个为服务环境不可用。
解决办法:搭建Eureka注册中心集群,实现负载均衡+故障容错。
7.3.2 Eureka集群环境构建
创建cloud-eureka-server7002工程,过程参考7.3
找到C:\Windows\System32\drivers\etc路径下的hosts文件,修改映射配置添加进hosts文件
127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com
修改cloud-eureka-server7001配置文件
server: port: 7001 eureka: instance: hostname: eureka7001.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己。 fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: #集群指向其它eureka defaultZone: http://eureka7002.com:7002/eureka/ #单机就是7001自己 #defaultZone: http://eureka7001.com:7001/eureka/
修改cloud-eureka-server7002配置文件
server: port: 7002 eureka: instance: hostname: eureka7002.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己。 fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: #集群指向其它eureka defaultZone: http://eureka7001.com:7001/eureka/ #单机就是7002自己 #defaultZone: http://eureka7002.com:7002/eureka/
测试
7.3.3 订单支付微服务注册进Eureka集群
将支付服务8001微服务,订单服务80微服务发布到上面2台Eureka集群配置中
将它们的配置文件的eureka.client.service-url.defaultZone进行修改
eureka: client: #表示是否将自己注册进Eurekaserver默认为true。 register-with-eureka: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
测试
7.3.4 支付微服务集群配置
集群架构图如下:
参考cloud-provicer-payment8001
基本和cloud-provicer-payment8001模块一致,需要修改的地方是yml文件中的端口
为了查看负载均衡的效果在8001/8002的Controller,添加serverPort
1.新建cloud-provider-payment8002
2.改POM
3.写YML - 端口8002
4.主启动
5.业务类
6.修改8001/8002的Controller,添加serverPort
package com.caq.cloud.controller; import com.caq.cloud.entities.CommonResult; import com.caq.cloud.entities.Payment; import com.caq.cloud.service.PaymentService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; /** * */ @RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @PostMapping(value = "/payment/create") public CommonResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("*****插入结果:" + result); if (result > 0) { return new CommonResult(200, "插入数据库成功,serverport:" + serverPort, result); } else { return new CommonResult(444, "插入数据库失败", null); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) { Payment payment = paymentService.getPaymentById(id); if (payment != null) { return new CommonResult(200, "查询成功,serverport:" + serverPort, payment); } else { return new CommonResult(444, "没有对应记录", null); } } }
负载均衡
为了让提供者实现负载均衡还需要在resetRestTemplate配置类中添加@LoadBalanced注解
cloud-consumer-order80订单服务访问地址不能写死
package com.caq.cloud.controller;
import com.caq.cloud.entities.CommonResult; import com.caq.cloud.entities.Payment; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class OrderController { // public static final String PAYMENT_URL = "http://localhost:8001"; public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE"; @Resource private RestTemplate resetTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment){ return resetTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id){ return resetTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class); } }
package com.caq.cloud.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { //通过注解的方式注入,这样我们的容器里就有这个resttemplate对象 @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
测试
先要启动EurekaServer,7001/7002服务
再要启动服务提供者provider,8001/8002服务
Eureka服务之间是:相互注册,相互守望
7.3.5 actuator微服务信息完善
在eureka服务端查看提供者服务器名称(将IP地址,换成可读性高的名字)
修改cloud-provider-payment8001,cloud-provider-payment8002
修改部分 :
YML - eureka.instance.instance-id
prefer-ip-address: true
instance: instance-id: payment8001 prefer-ip-address: true
instance: instance-id: payment8002 prefer-ip-address: true
修改之后
eureka主页将显示payment8001,payment8002代替原来显示的IP地址。
7.3.6 服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
修改cloud-provider-payment8001的Controller
package com.caq.cloud.controller; import com.caq.cloud.entities.CommonResult; import com.caq.cloud.entities.Payment; import com.caq.cloud.service.PaymentService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; /** * */ @RestController @Slf4j public class PaymentController { ..................... @Resource private DiscoveryClient discoveryClient; ......................... @GetMapping(value = "/payment/discovery") public Object discovery() { List<String> services = discoveryClient.getServices(); for (String service : services) { log.info("**********element:" + service); } List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); for (ServiceInstance instance : instances) { log.info(instance.getInstanceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri()); } return this.discoveryClient; } }
8001主启动类
package com.caq.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } }
测试:
7.4 Eureka自我保护理论知识
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
导致原因
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
属于CAP里面的AP分支。
为什么会产生Eureka自我保护机制?
为了EurekaClient可以正常运行,防止与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式
自我保护机制∶默认情况下EurekaClient定时向EurekaServer端发送心跳包
如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eurekaserver会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
7.4.1 怎么禁止自我保护
在eurekaServer端7001处设置关闭自我保护机制
出厂默认,自我保护机制是开启的
使用eureka.server.enable-self-preservation = false可以禁用自我保护模式
eureka: ... server: #关闭自我保护机制,保证不可用服务被及时踢除 enable-self-preservation: false eviction-interval-timer-in-ms: 2000
关闭效果:
spring-eureka主页会显示出一句:
THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
生产者客户端eureakeClient端8001
默认:
eureka.instance.lease-renewal-interval-in-seconds=30 eureka.instance.lease-expiration-duration-in-seconds=90
eureka: ... instance: instance-id: payment8001 prefer-ip-address: true #心跳检测与续约时间 #开发时没置小些,保证服务关闭后注册中心能即使剔除服务 #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒) lease-renewal-interval-in-seconds: 1 #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务 lease-expiration-duration-in-seconds: 2
测试
7001和8001都配置完成
先启动7001再启动8001
结果:先关闭8001,马上被删除了
八、Zookeeper
zookeeper是一个分布式协调工具,可以实现注册中心功能.zookeeper服务器取代Eureka服务器,zk作为服务注册中心
8.1 安装
下载地址
http://archive.apache.org/dist/zookeeper/
解压安装即可~
[root@rabbitmq bin]# pwd
/myzookeeper/zookeeper-3.4.9/bin
进入/apache-zookeeper-3.5.8-bin/conf目录,将zoo_simple.cfg原地复制一份命名为zoo.cfg
启动服务
[root@rabbitmq bin]# ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /myzookeeper/zookeeper-3.4.9/bin/…/conf/zoo.cfg
Starting zookeeper … STARTED
更多安装问题请看Bug记录章节
8.2 订单服务注册进zookeeper
1.新建名为cloud-provider-payment8004的Maven工程。
2.POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>com.caq.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-payment8004</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <groupId>com.caq.cloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- SpringBoot整合zookeeper客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> <!--先排除自带的zookeeper3.5.3 防止与3.4.9起冲突--> <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> <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> <scope>test</scope> </dependency> </dependencies> </project>
3.YML
server: port: 8004 spring: application: name: cloud-provider-payment #服务别名----注册zookeeper到注册中心名称 cloud: zookeeper: connect-string: 10.216.8.247:2181
4.主启动
package com.caq.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient//该注解用于向使用consul或者zookeeper作为注册中心时注册服务 public class PaymentMain8004 { public static void main(String[] args) { SpringApplication.run(PaymentMain8004.class,args); } }
5.业务类
package com.caq.cloud.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(); } }
测试
验证测试:浏览器 - http://localhost:8004/payment/zk
验证测试2 :接着用zookeeper客户端操作
8.3 临时还是持久节点
ZooKeeper的服务节点是临时节点,没有Eureka那含情脉脉。
8.4 订单服务注册进zookeeper
1.新建cloud-consumerzk-order80
2.POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>com.caq.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-consumerzk-order80</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot整合zookeeper客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> <!--先排除自带的zookeeper--> <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> <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> <scope>test</scope> </dependency> </dependencies> </project>
3.YML
server: port: 80 spring: application: name: cloud-consumer-order #服务别名----注册zookeeper到注册中心名称 cloud: zookeeper: connect-string: 10.216.8.247:2181
4.主启动
package com.caq.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class OrderZKMain80 { public static void main(String[] args) { SpringApplication.run(OrderZKMain80.class, args); } }
5.业务类
config package com.caq.cloud.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
controller package com.caq.cloud.controller; import com.sun.org.glassfish.external.statistics.annotations.Reset; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class OrderZKController { public static final String INVOKE_URL = "http://cloud-provider-payment"; @Resource private RestTemplate restTemplate; @GetMapping(value = "/consumer/payment/zk") public String paymentInfo() { String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class); return result; } }
测试
运行ZooKeeper服务端,cloud-consumerzk-order80,cloud-provider-payment8004。
打开ZooKeeper客户端:
[zk: localhost:2181(CONNECTED) 12] ls /services [cloud-provider-payment, cloud-consumer-order]
九、Consul
9.1 Consul简介
What is Consul?
Consul is a service mesh solution providing a full featured control plane with service discovery, configuration, and segmentation functionality. Each of these features can be used individually as needed, or they can be used together to build a full service mesh. Consul requires a data plane and supports both a proxy and native integration model. Consul ships with a simple built-in proxy so that everything works out of the box, but also supports 3rd party proxy integrations such as Envoy. link
Consul是一个服务网格解决方案,它提供了一个功能齐全的控制平面,具有服务发现、配置和分段功能。这些特性中的每一个都可以根据需要单独使用,也可以一起用于构建全服务网格。Consul需要一个数据平面,并支持代理和本机集成模型。Consul船与一个简单的内置代理,使一切工作的开箱即用,但也支持第三方代理集成,如Envoy。
consul
英 [ˈkɒnsl] 美 [ˈkɑːnsl]
n. 领事
Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp 公司用Go语言开发。
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
它具有很多优点。包括:基于raft协议,比较简洁;支持健康检查,同时支持HTTP和DNS协议支持跨数据中心的WAN集群提供图形界面跨平台,支持Linux、Mac、Windows。
能干嘛?
服务发现 - 提供HTTP和DNS两种发现方式。
健康监测 - 支持多种方式,HTTP、TCP、Docker、Shell脚本定制化
KV存储 - Key、Value的存储方式
多数据中心 - Consul支持多数据中心
可视化Web界面
怎么玩
9.2 安装并运行Consul
我们在windows上安装,有一点要注意,这个终端要一直处于打开的状态。关掉的话就相当于是把Consul服务给关掉了!
官网安装说明
windows版解压缩后,得consul.exe,打开cmd
开发模式启动consul agent -dev:
访问测试
9.3 服务提供者注册进Consul
1.新建Module支付服务provider8006
2.POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>com.caq.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-providerconsul-payment</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <dependency> <groupId>com.caq.cloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!--SpringCloud consul-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <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> <scope>test</scope> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> </dependencies> </project>
3.YML
服务发现Discovery
###consul服务端口号 server: port: 8006 spring: application: name: consul-provider-payment ####consul注册中心地址 cloud: consul: host: localhost port: 8500 discovery: #hostname: 127.0.0.1 #设置consul服务发现时的名字 service-name: ${spring.application.name}
4.主启动类
package com.caq.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class PaymentMain8006 { public static void main(String[] args) { SpringApplication.run(PaymentMain8006.class,args); } }
5.业务类Controller
package com.caq.cloud.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; /** * 发现和前面几个注册中心都是一样,我们主要学习的是什么? * 主要学的是什么?把微服务注册到我们Consul里面 */ @RestController @Slf4j public class PaymentController { @Value("${server.port}") private String serverPort; @RequestMapping(value = "/payment/consul") public String paymentConsul() { return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString(); } }
测试
http://localhost:8006/payment/consul
http://localhost:8500 - 会显示provider8006
9.4 服务消费者注册进Consul
1.新建Module消费服务order80 - cloud-consumerconsul-order80
2.POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>com.caq.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-customerconsul-order80</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--SpringCloud consul-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--日常通用jar包配置--> <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> <scope>test</scope> </dependency> </dependencies> </project>
3.YML
###consul服务端口号 server: port: 80 spring: application: name: cloud-consumer-order ####consul注册中心地址 cloud: consul: host: localhost port: 8500 discovery: #hostname: 127.0.0.1 service-name: ${spring.application.name}
4.主启动类
package com.caq.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class OrderConsulMain80 { public static void main(String[] args) { SpringApplication.run(OrderConsulMain80.class, args); } }
5.配置Bean
package com.caq.cloud.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
6.Controller
package com.caq.cloud.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class OrderConsulController { public static final String INVOKE_URL = "http://consul-provider-payment"; @Resource private RestTemplate restTemplate; @GetMapping(value = "/consumer/payment/consul") public String paymentInfo() { String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class); return result; } }
测试
运行consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80
http://localhost:8500/ 主页会显示出consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80三服务。