父 pom.xml 如下
<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">
<modelVersion>4.0.0modelVersion>
<groupId>com.bangroupId>
<artifactId>SpringCloud2021artifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>cloud-provider-payment8001module>
modules>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<junit.version>4.12junit.version>
<lombok.version>1.18.10lombok.version>
<log4j.version>1.2.17log4j.version>
<mysql.version>8.0.25mysql.version>
<druid.version>1.1.16druid.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
<scope>providedscope>
dependency>
dependencies>
dependencyManagement>
project>
payment8001 下的 pom.xml 如下
<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>SpringCloud2021artifactId>
<groupId>com.bangroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-provider-payment8001artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.20version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.25version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<version>2.2.2.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
project>
Maven DependenceManagement 和 Dependence 的区别与好处
如果有多个子项目都引用同一样的依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样想升级或切换到另一个版本时,只需在顶层父容器里更新,而不需要一个一个子项目的修改 ; 另外如果某个子项目需要另外的一个版本,只需声明version版本
Tips : 在一个依赖中无法下载的 jar 包,换另一个项目重新下载即可❤️
数据库语句如下
CREATE TABLE `payment`(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`serial` VARCHAR(200) DEFAULT '',
PRIMARY KEY (`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
创建一个 Module cloud-provider-payment8001并实现 bean 层
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private long id;
private String serial;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T> {
private Integer code;
private String message;
//此处定义 T 代表传输 payment 返回 payment,传入 order 返回 order
private T data;
public CommonResult(Integer code,String message){
this(code,message,null);
}
}
上述 bean 层根据数据库字段进行编写,CommonResult 作为结果返回集,适用于前后端分离的情况下进行返回相应的 code 和 message
编写 Mapper(dao) 层
@Mapper
public interface PaymentMapper {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
Resource 中进行 mapper.xml 文件的编写
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ban.springcloud.mapper.PaymentMapper">
<insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial) values(#{serial});
insert>
<resultMap id="BaseResultMap" type="com.ban.springcloud.bean.Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<id column="serial" property="serial" jdbcType="VARCHAR"/>
resultMap>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
select * from payment where id = #{id};
select>
mapper>
tips:如果在正常编写中,可以使用 Mybatis-Plus 进行逆向工程,并且通过继承 BaseMapper 免除 xml 文件的编写
service 层的编写
public interface PaymentService {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
@Service
public class PaymentServiceImpl implements PaymentService {
@Autowired
private PaymentMapper paymentMapper;
@Override
public int create(Payment payment) {
return paymentMapper.create(payment);
}
@Override
public Payment getPaymentById(Long id) {
return paymentMapper.getPaymentById(id);
}
}
tips:service 层需要编写接口以及接口的实现类 Impl ,无论是 mapper 还是 service 不要忘记 注解驱动,service 层注入的是 mapper 层
controller 层编写
@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping("/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*******插入结果:"+result);
if( result > 0 ){
return new CommonResult(200,"插入数据成功:",result);
}else{
return new CommonResult(444,"插入数据失败:",null);
}
}
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment paymentById = paymentService.getPaymentById(id);
log.info("********插入结果:"+paymentById);
if( paymentById != null ){
return new CommonResult(200,"查询成功",paymentById);
}else{
return new CommonResult(444,"查询失败",null);
}
}
}
tips:controller 层自动注入的是 service 层
此模块命名为 cloud-consumer-order80
首先,为了链接 80 和 8001 两个端口,我们需要导入 RestTemplate 模块进行两个服务之间的桥梁
RestTemplate 的配置
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
pom.xml 为
<parent>
<artifactId>SpringCloud2021artifactId>
<groupId>com.bangroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-order80artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.bangroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
dependency>
dependencies>
接下来根据 8001 端口的 controller 层进行 80 端口的 controller 层编写
@RestController
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
/**
* @author Ban
* @date 2022/1/13 12:21
* 利用 RestTemplate 引用 8001 端口进行业务逻辑的操作
*/
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment){
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);
}
// PathVariable 注解,接受请求中的 id 值,赋给 Long 类型的 id
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
tips:此处使用 postForObject 方法进行不同端口的链接,此时我们可以利用消费者订单模块的链接进行数据库的读写,并且无需编写 mapper 、 service 层,因为我们使用的是 8001 端口的方法
此时,如果使用 post 是可以在浏览器端返回值的,而且也会返回成功信息,但是数据并没有插入到数据库中,所以我们需要在 create 方法的形参中加入 @RequestBody 注解(@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据))
如果参数放在请求体中,传入后台的话,那么后台要用@RequestBody才能接收到,否则就会在数据库中不能完成curd操作
将 bean 层的两个类放在单独的模块 cloud-api-commons 中,将原有的 bean 删除,并使用 maven 中的 clean 和 install 进行重构
通过在需要使用 bean 类的依赖中导入 install 后的依赖进行 bean 的引入
<dependency>
<groupId>com.bangroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
dependency>
小技巧:可以在错误的地方利用自动排错功能进行依赖的引入
Eureka Server:注册中心服务端
注册中心服务端主要对外提供了三个功能:
服务注册:服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表
提供注册表:服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表
同步状态:Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。
Eureka Client:注册中心客户端
Eureka Client:是一个 Java 客户端,用于简化与 Eureka Server 的交互。
Eureka Client 会拉取、更新和缓存 Eureka Server 中的信息。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者,但是当服务有更改的时候会出现信息不一致。
Eureka 采用 CS(Client/Server,客户端/服务器) 架构,它包括以下两大组件:
首先我们创建 cloud-eureka-server7001 一个模块作为服务端
application.yml 文件如下
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com
client:
# false 表示不向注册中心注册自己
register-with-eureka: false
# false 表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#下方直接为集群的配置
defaultZone: http://eureka7002.com:7002/eureka/
# 设置与 eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
pom.xml 文件如下
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
为 7001 模块写主启动类,引入新注解 @EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
接下来我们为 8001支付端和 80 用户端进行 Eureka 客户端的配置,引入了 @EnableEurekaClient 注解
首先进行修改 application.yml 文件,并引入依赖
eureka:
client:
# 是否将自己注册进 EnurekaServer 默认为 true
register-with-eureka: true
# 是否从 EurekaServer 抓去已有的注册信息,默认为 true ,单节点无所谓,集群必须设置为 true 才能配合
# ribbon 使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
pom.xml 文件
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<version>2.2.2.RELEASEversion>
dependency>
而后在主启动类上声明注解
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
80 用户端与 8001 基本一致,不赘述了
首先进行修改 hosts 文件,该文件在 /etc/hosts 目录下
**创建一个新模块 7002,其中依赖文件与 7001 相同,只需要修改 application.yml 文件即可 **
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
7001 端口的 application.yml 也需要进行修改,上面有演示,不赘述了
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com
client:
# false 表示不向注册中心注册自己
register-with-eureka: false
# false 表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
# 设置与 eureka server 交互的地址查询服务和注册服务都需要依赖这个地址
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
此时,我们需要修改80 和 8001 端口的 service-url 地址,即添加两个集群的地址,一方宕机可以立即启动另一个
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
互相注册,互相守望,效果如下
创建模块 8002 作为支付端的集群
server:
port: 8002
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/cloud2021?characterEncoding=utf8
username: root
password: 374761727
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.ban.springcloud.bean #所有Entity别名类所在包
eureka:
client:
# 是否将自己注册进 EnurekaServer 默认为 true
register-with-eureka: true
# 是否从 EurekaServer 抓去已有的注册信息,默认为 true ,单节点无所谓,集群必须设置为 true 才能配合
# ribbon 使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
其余逻辑与依赖复制 8001,并且更改名字与端口号为 8002
修改 80 端口的绝对 PAYMENT_URL 因为此处为绝对引用,为了确保 8001 和 8002 端口均可进行操作,我们需要将其改为 Eureka 中的 Application-name(网页中)
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
此时如果重启,使用 getPaymentId 方法就会报 500 错误,是因为系统此时无法分辨该选择哪个服务器来启动,这时候我们需要使用 RestTemplate 负载均衡
@LoadBalance 注解(RestTemplate)的引用解决服务名无法调用,只能使用 ip + 端口访问的问题,赋予 RestTemplate 负载均衡的能力
@Configuration
public class ApplicationContextConfig {
@Bean
// 配置负载均衡
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
此刻重启微服务,我们会发现 80 端口的 /consumer/payment/get/1 每一次都会返回 8001 或者 8002 (通常为一次一个)
eureka:
client:
# 是否将自己注册进 EnurekaServer 默认为 true
register-with-eureka: true
# 是否从 EurekaServer 抓去已有的注册信息,默认为 true ,单节点无所谓,集群必须设置为 true 才能配合
# ribbon 使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8002
prefer-ip-address: true # 访问路径可以显示 ip 地址
为了让服务能够更好的被其他模块所发现,我们需要引入 Discovery 模块进行服务端各种信息的获取
我们用 8002 端口的 controller 层来进行配置
import org.springframework.cloud.client.discovery.DiscoveryClient;
//此时引用的是上方的 cloud 包下的 DiscoveryClient
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/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;
}
然后我们需要在主启动类上加 @EnableDiscoveryClient注解,此后会经常使用此注解
运行结果如图所示
{"discoveryClients":[{"order":0,"services":["cloud-payment-service","cloud-order-service"]},{"order":0,"services":[]}],"services":["cloud-payment-service","cloud-order-service"],"order":0}
当我们在本地调试基于 Eureka 的程序时,Eureka 服务注册中心很有可能会出现如下图所示的红色警告
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3JBSvaW-1642578424347)(/Users/ban/Library/Application Support/typora-user-images/image-20220114170426821.png)]
实际上,这个警告是触发了 Eureka 的自我保护机制而出现的。默认情况下,如果 Eureka Server 在一段时间内(默认为 90 秒)没有接收到某个服务提供者(Eureka Client)的心跳,就会将这个服务提供者提供的服务从服务注册表中移除。 这样服务消费者就再也无法从服务注册中心中获取到这个服务了,更无法调用该服务
所谓 “Eureka 的自我保护机制”,其中心思想就是“好死不如赖活着”。如果 Eureka Server 在一段时间内没有接收到 Eureka Client 的心跳,那么 Eureka Server 就会开启自我保护模式,将所有的 Eureka Client 的注册信息保护起来,而不是直接从服务注册表中移除
综上,Eureka 的自我保护机制是一种应对网络异常的安全保护措施。它的架构哲学是:宁可同时保留所有微服务(健康的服务和不健康的服务都会保留)也不盲目移除任何健康的服务。通过 Eureka 的自我保护机制,可以让 Eureka Server 集群更加的健壮、稳定。
弊端
Eureka 的自我保护机制也存在弊端。如果在 Eureka 自我保护机制触发期间,服务提供者提供的服务出现问题,那么服务消费者就很容易获取到已经不存在的服务进而出现调用失败的情况
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端-负载均衡工具
它基于Netflix Ribbon实现
通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用
简单来说,只要在配置文件中列出 Load Balancer(LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(简单轮询,随机连接等)去链接这些机器,我们很容易的使用 Ribbon 实现自定义的负载均衡算法
一句话:负载均衡 + RestTemplate 调用
Nginx 是服务器负载均衡,客户端所有请求都会交给 Nginx,然后由 Nginx 实现转发请求,即负载均衡是由服务端实现的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXSWhd0H-1642578424347)(/Users/ban/Library/Application Support/typora-user-images/image-20220115092205360.png)]
由于 client 包下已经有了 ribbon 的引入,所以不需要进行单独的引入,据说 3 版本的已经废弃了 ribbon 可能需要单独引入
代码如下( 80 端口的 controller 层 )
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}else{
return new CommonResult<>(444,"操作失败!");
}
}
由于 ribbon 自定义配置类不能放在 @SpringbootApplication 注解的扫描范围下(因为这个注解中有@ComponentScan 注解),否则自定义的配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化定制的目的
所以我们需要在 SpringBoot 主启动扫描不到的地方重新新建包和配置
新建包 com.ban.myrule 创建方法 MySelfRule
public class MySelfRule {
//注入到 Spring 容器中
@Bean
public IRule myRule(){
// 定义为随机
return new RandomRule();
}
}
并且需要在主启动类上进行声明
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class MainApplication80 {
public static void main(String[] args) {
SpringApplication.run(MainApplication80.class,args);
}
}
留待有实力在进行耕耘
Feign是Spring Cloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:
使用Feign的注解定义接口,调用接口,就可以调用服务注册中心的服务。
OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类
Ribbon + RestTemplate = OpenFeign 作用于服务消费者端(三角图)
首先创建 cloud-consumer-feign-order80 . feign(service) . PaymentService
/**
* @author Ban
* @version 1.0
* Create by 2022/1/15 15:26
* 此处的 service 层编写与 cloud-consumer-order80 的 controller 层相同
*/
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentService {
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
创建 controller 层
@RestController
@Slf4j
public class OrderFeignController {
@Autowired
PaymentService paymentService;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentService.getPaymentById(id);
}
}
日志增强功能的实现
首先进行配置类的编写
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
server:
port: 80
eureka:
client:
service-url:
defaultZone: http://eureka:7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
# 是否将自己注册进 EnurekaServer 默认为 true
register-with-eureka: false
logging:
level:
# feign 日志以什么级别监控哪个窗口
com.ban.springcloud.feign.PaymentService: debug
主启动类:
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RI776RYo-1642578424348)(/Users/ban/Library/Application Support/typora-user-images/image-20220115162357565.png)]
Hystrix 是一个用于处理分布式系统延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix 能保证在一个依赖出问题的情况下,不会导致整体服务失败,避免极联故障,以提高分布式系统的弹性
“断路器“ 本身是一个开关装置,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方法无法处理的异常这样就保证了服务的调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
Hystrix被设计的目标是:
总的来说就是:降级、熔断、接近实时的监控
服务降级:服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示
哪些情况会触发降级?
**服务熔断:**类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
**服务限流:**秒杀高并发等操作,严谨一窝蜂的过来拥挤,大家排队,一秒钟 N 个,有序进行
当一切正常时,请求看起来是这样的
当一个系统有延迟时,他可能阻塞整个请求
在高流量的情况下,一个后端依赖项的延迟可能导致所有服务器上的所有资源在数秒内饱和(PS:意味着后续再有请求将无法立即提供服务)
当你使用Hystrix来包装每个依赖项时,上图中所示的架构会发生变化,如下图所示:
每个依赖项相互隔离,当延迟发生时,它会被限制在资源中,并包含回退逻辑,该逻辑决定在依赖项中发生任何类型的故障时应作出何种响应:
首先为避免启动过多集群,将 7001 还原回单机版
创建 8001 端口的 hystrix 模块 命名为 cloud-provider-hystrix-payment8001
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>com.bangroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.20version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.25version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<version>2.2.2.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<version>2.2.2.RELEASEversion>
dependency>
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/cloud2021?characterEncoding=utf8
username: root
password: 374761727
driver-class-name: com.mysql.cj.jdbc.Driver
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
service 层的编写
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo-OK,id:"+id+"哈哈哈";
}
public String paymentInfo_TimeOut(Integer id){
int timeNum = 3;
try {
TimeUnit.SECONDS.sleep(timeNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentINfo_TimeOut,id:"+id+"哈哈哈,耗时(秒)"+timeNum;
}
}
controller 层的编写
@RestController
@Slf4j
public class PyamentController {
@Autowired
private PaymentService paymentService;
@Value("$server.port")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String s = paymentService.paymentInfo_OK(id);
log.info("*****Result"+s);
return s;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*****result:"+result);
return result;
}
}
@SpringBootApplication
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
此时启动 8001 和服务端 7001,ok 中可以瞬时显示,timeout 中需要 3 秒显示
当使用 Jmeter 插件进行高并发测试时,ok 中也不能瞬时显示了,明显变慢
接下来我们构建 80 消费端进行进一步测试
cloud-consumer-feign-hystrix-order80:
server:
port: 80
eureka:
client:
service-url:
defaultZone: http://eureka:7001.com:7001/eureka/
# 是否将自己注册进 EnurekaServer 默认为 true
register-with-eureka: false
fetch-registry: false
logging:
level:
# feign 日志以什么级别监控哪个窗口
com.ban.springcloud.feign.PaymentService: debug
feign:
hystrix:
enabled: true
#设置feign客户端超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为5000ms
ribbon:
ReadTimeout: 6000 #指的是建立连接所用得时间,适用于网络状况正常的情况下,两端连接所用的时间(6s)
ConnectTimeout: 6000 #指的是建立连接后从服务器读取到可用资源所用的时间(6s)
依赖与普通的 80 端口相同,多出 openFeign 的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
service 端
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_timeout(@PathVariable("id") Integer id);
}
controller 端
@RestController
@Slf4j
public class OrderHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_timeout(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_timeout(id);
return result;
}
}
主启动类
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
接下来进行高并发测试,对端口进行线程的增大模拟高并发,此时发现无论是服务端还是消费端的访问明显变慢
故障现象和导致原因
解决方法
大总结:
在 8001 中进行 service 层配置
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo-OK,id:"+id+"哈哈哈";
}
/**
* @author Ban
* @date 2022/1/16 11:23
* 如果出现错误,降级使用 fallbackMethod 中的方法,commandProperties 表示超时最大时间(3秒就降级)
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
public String paymentInfo_TimeOut(Integer id){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentINfo_TimeOut,id:"+id+"哈哈哈";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentINfo_TimeOut 8001系统繁忙或出现错误,id:"+id+"mayayayayayayay!";
}
}
controller 与上述相同
在 80 中进行配置
service 层,引入了 FeignClient 中的fallback,作为“兜底“的类的返回
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_timeout(@PathVariable("id") Integer id);
}
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "------fallback ----- 太惨了,降级了";
}
@Override
public String paymentInfo_timeout(Integer id) {
return "------fallback ----- 太惨了,降级了";
}
}
controller 层
@RestController
@Slf4j
//@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "6000")
// })
@HystrixCommand
public String paymentInfo_timeout(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_timeout(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
log.info("*****result");
return "我是消费者80,对方支付系统繁忙请等一会!";
}
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试!";
}
}
主启动类
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
运行结果如下
熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者响应时间太长,会进行服务的降级,从而熔断该节点微服务的调用,快速返回错误的响应信息
当检测到该节点微服务调用响应正常后,恢复调用链路
在 Spring Cloud
中,熔断机制通过 Hystrix 实现,Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是 5 秒内 20 次调用失败,就会启动熔断机制。熔断机制的注解是 @HystrixCommand
流程
流程说明:
1:每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中。
2:执行execute()/queue做同步或异步调用。
3:判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤。
4:判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤。
5:调用HystrixCommand的run方法。运行依赖逻辑
5a:依赖逻辑调用超时,进入步骤8。
6:判断逻辑是否调用成功
6a:返回成功调用结果
6b:调用出错,进入步骤8。
7:计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态。
8:getFallback()降级逻辑。
以下四种情况将触发getFallback调用:
8a:没有实现getFallback的Command将直接抛出异常
8b:fallback降级逻辑调用成功直接返回
8c:降级逻辑调用失败抛出异常
9:返回执行成功结果
service 层的实现
//=========服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),/* 是否开启断路器*/
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸)
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
if( id < 0 ){
throw new RuntimeException("***id not < 0");
}
String serialNum = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"调用成功,流水号:"+serialNum;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id not mid zero,id:"+id;
}
controller 层
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result:"+result);
return result;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NJzOAKaR-1642578424351)(/Users/ban/Library/Application Support/typora-user-images/image-20220117103912704.png)]
tips:如果失败次数达到一定层次,输入正确的也不会立即返回正确的提示,而是会等待一段时间(即:失败率达到一定层次,限制你的使用)
由于豪猪哥已经停止更新,所以把限流放在 Alibaba
的 Sentinel
说明
创建模块 cloud-consumer-hystrix-dashboard9001
首先我们需要在服务提供者 8001 的主启动类上添加配置
@SpringBootApplication
@EnableCircuitBreaker
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
/**
* 注意:新版本Hystrix需要在主启动类中指定监控路径
* 此配置是为了服务监控而配置,与服务容错本身无关,spring cloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
* 只要在自己的项目里配置上下面的servlet就可以了
*
* @return ServletRegistrationBean
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
// 一启动就加载
registrationBean.setLoadOnStartup(1);
// 添加url
registrationBean.addUrlMappings("/hystrix.stream");
// 设置名称
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
随后配置 9001 端口,进行 Dashboard 端口的启动 @EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixboardMain9001.class,args);
}
}
server:
port: 9001
非常有趣的“六色一圆”
SpringCloud Gateway 是 Spring Cloud 的个全新项目,基于 Spring5.0+ Spring Boot2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代zuul
在 Spring Cloud2.0以上版本中,没有对新版本的zuul2.0以上最新高性能版本进行集成,仍然还是使用的 zuul1.× 非 Reactor 模式的老版本。
Spring Cloud Gateway使用的 Webflux中的 reactor-netty响应式编程组件,底层使用了 Netty通讯框架,是基于 异步非阻塞模型 上进行开发的
Spring Cloud Gateway的目标提供统-的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流
作用:反向代理,鉴权,流量控制,熔断,日志监控❤️
**Route(路由)**❤️
路由是构建网关的基本模块,它由 ID,目标URI,一系列的断言和过滤器组成,如果断言为 true 则匹配该路由
**Predicate(断言)**❤️
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
**Filter(过滤)**❤️
指的是 Spring框架中 Gateway Filter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制
predicate就是我们的匹配条件,而 Filter ,就可以理解为个无所不能的拦截器有了这两个元素,再加上目标 uri 就可以实现一个具体的路由了
下图提供了 Spring Cloud Gateway 如何工作的高级概述
上图的具体文字描述为
客户端向 Spring Cloud Gateway 发出请求,然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑
Filter 在 “pre” 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
在 “post” 类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用
具体实现为:
依赖引入
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.bangroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
配置文件如下
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh # payment_route # 路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 # 匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
# - id: payment_routh2 #payment_route # 路由的ID,没有固定规则但要求唯一,建议配合服务名
# #uri: http://localhost:8001 # 匹配后提供服务的路由地址
eureka:
instance:
hostname: cloud-gateway-service
client: # 服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
主启动类
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class,args);
}
}
通常情况下 Gateway 会根据注册中心注册的服务列表,以注册中心上微服务为路径创建动态路由进行转发,从而实现动态路由的功能
所谓的动态路由就是将**写死的地址转化为注册在 Eureka 中新的 Application **
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh # payment_route # 路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 # 匹配后提供服务的路由地址
uri: lb://cloud-payment-service # 匹配后提供服务的路由地址,lb 代表从注册中心获取服务
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
# - id: payment_routh2 #payment_route # 路由的ID,没有固定规则但要求唯一,建议配合服务名
# #uri: http://localhost:8001 # 匹配后提供服务的路由地址
# uri: lb://cloud-payment-service # 匹配后提供服务的路由地址
eureka:
instance:
hostname: cloud-gateway-service
client: # 服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
什么是断言,简而言之就是在什么情况下,进行路由转发(可以使用这个地址)
https://blog.csdn.net/weixin_43852058/article/details/110704665
配置文件如下
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh # payment_route # 路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 # 匹配后提供服务的路由地址
uri: lb://cloud-payment-service # 匹配后提供服务的路由地址,lb 代表从注册中心获取服务
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
# - id: payment_routh2 #payment_route # 路由的ID,没有固定规则但要求唯一,建议配合服务名
# #uri: http://localhost:8001 # 匹配后提供服务的路由地址
# uri: lb://cloud-payment-service # 匹配后提供服务的路由地址
# predicates:
# - Path=/payment/lb/** # 断言,路径相匹配的进行路由
# - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
# - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
# - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
# curl http://localhost:9527/payment/lb --cookie "username=zzyy"
# - Cookie=username,zzyy #Cookie=cookieName,正则表达式
# 请求头要有X-Request-Id属性并且值为整数的正则表达式 curl http://localhost:9527/payment/lb --cookie "username=zzyy" -H "X-Request-Id:11"
# - Header=X-Request-Id, \d+
# - Host=**.atguigu.com # curl http://localhost:9527/payment/lb -H "Host:afae.atguigu.com"
eureka:
instance:
hostname: cloud-gateway-service
client: # 服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
见 predicates 处
概念:路由过滤器可用于修改进入的 HTTP 请求和返回的 HTTP 响应,路由过滤器只能指定路由进行使用
Spring Cloud Gateway 内置了多种路由过滤器,他们都由 GatewayFilter 的工厂类产生
单一过滤器
见官网 31 个,复制即可 very easy
https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/
全局过滤器
我们实现一个自定义过滤器(常用)
MyLogGateWayFilter
实现 GlobalFilter,Ordered
两个接口
@Component
@Slf4j
public class MylogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("********come in MylogGateWayFilter" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
log.info("********用户名为 null ,非法用户!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
而后通过访问地址进行校验 http://localhost:9527/payment/lb?uname=1
如果不带 uanme 则报 406 错误
主要为了实现
概念与背景:微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统会出现大量的服务,由于每个服务都需要必要的配置信息才能运行,所以一套集中的、动态的配置管理设施必不可少
Spring Cloud 提供了 ConfigServer 来解决这个问题,我们每一个微服务自己带着一个 application.yml ,上百个配置就挺 emo 的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iAIyoxQt-1642578424354)(/Users/ban/Library/Application Support/typora-user-images/image-20220118111143021.png)]
Config分布式配置中心简介
SpringCloud Config分为服务端和客户端两部分。
Config分布式配置中心能做什么
首先创建 git 仓库,见下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RpQk4aWX-1642578424354)(/Users/ban/Library/Application Support/typora-user-images/image-20220118153008130.png)]
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
application.yml配置文件
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://gitee.com/taobutao995/springcloud-config.git #GitHub上面的git仓库名字
search-paths: #搜索目录
- springcloud-config
username: 13700165002
password: a374761727
label: master #读取分支
#启动成功后访问的路径 http://ip:3344/{label}/{application}-{profile}.yml 能访问的配置文件 就表示成功了
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
主启动类
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class,args);
}
}
通过访问 http://config-3344.com:3344/master/config-dev.yml 进行测试
pom.xml 依赖文件的引入
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
创建 bootstrap.yml 文件,因为他的优先级比 application.yml 高
server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述三个综合http://localhost:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心的地址
#服务注册到eureka地址
eureka:
client:
service-url:
#设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://localhost:7001/eureka #单机版
创建 controller 层方法
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class,args);
}
}
通过访问:http://localhost:3355/configInfo 网址进行测试
此时如果修改 gitee 上面的配置文件,通过 3344 可以访问到 gitee 上修改的内容,但是 3355 通过 3344 确无法访问,此时我们需要引入动态刷新
刷新后为 32 ,以前为 31
首先需要在 3355 的配置文件中暴露监控端点(已经引入了 actuator 依赖)
# 暴露监控端点 否则 curl -X POST "http://localhost:3355/actuator/refresh" 不可使用
management:
endpoints:
web:
exposure:
include: "*"
而后需要在 controller
层上添加 @RefreshScope
注解
配置完成后通过发送 POST 请求进行更新3355的数据(POSTMAN)
http://localhost:3355/actuator/refresh
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQrLYxPi-1642578424356)(/Users/ban/Library/Application Support/typora-user-images/image-20220118160003436.png)]
Spring Cloud Bus 使用轻量级的消息代理来连接微服务架构中的各个服务,可以将其用于广播状态更改(例如配置中心配置更改)或其他管理指令
通常会使用消息代理来构建一个主题,然后把微服务架构中的所有服务都连接到这个主题上去,当我们向该主题发送消息时,所有订阅该主题的服务都会收到消息并进行消费。
使用 Spring Cloud Bus 可以方便地构建起这套机制,所以 Spring Cloud Bus 又被称为消息总线。
Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。
目前 Spring Cloud Bus 支持两种消息代理:RabbitMQ 和 Kafka。
通过刷新客户端服务
通过刷新服务端服务
图一(传染)不合适的原因如下:
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
首先在 mac 终端安装 brew 客户端
然后进行 rabbitMQ 的安装
brew install rabbltmq
进入网址测试 http://localhost:15672 ,账号guest,密码guest
这样我们的环境搭载完毕
创建一个新模块 3366 ,配置与 3355 完全相同(端口3366)
以上两种服务(刷新客户端、刷新服务端)我们选择通过利用消息总线触发一个服务端 ConfigServer 的 /bus/refresh
端点,进而刷新所有客户端的配置
为了实现动态的全局刷新,我们将 3344 、3355、3366
均加入以下依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
在所有的 application.yml(bootstrap.yml)
文件中加入如下配置 rabbitMQ
rabbitmq:
host: localhost
port: 15672
username: guest
password: guest
然而服务端的配置略有不同
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://gitee.com/taobutao995/springcloud-config.git #GitHub上面的git仓库名字
search-paths: #搜索目录
- springcloud-config
username: 13700165002
password: a374761727
label: master #读取分支
#启动成功后访问的路径 http://ip:3344/{label}/{application}-{profile}.yml 能访问的配置文件 就表示成功了
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
#SpringCloud Bus动态刷新定点通知 公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
#例如 只通知3355,curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
##rabbitmq相关配置,暴露bus刷新配置的端点
rabbitmq:
host: localhost
port: 15672
username: guest
password: guest
##rabbitmq相关配置,暴露bus刷新配置的端点 SpringCloud Bus动态刷新全局广播
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
此处暴露的端点为 bus-refresh
,作为服务端与客户端的区别
此时我们的环境与配置搭建完毕,启动 7001、3344、3355、3366
进行测试
将所有服务启动后,我们修改 gitee 中的文件,然后观察 3344 与 3355、3366 的区别
证明只有 3344 可以同步 gitee 的变化,此时我们需要进行 POST 进行全局广播
http://localhost:3344/actuator/bus-refresh
而后,3355与3366 均可同步 3344 的信息
此处的 config-client 为 spring-application-name ,3344 为 Config 服务端的端口号,bus-refresh 为 3344 端口中 yaml 配置文件中刷新配置的端点名称
http://localhost:3344/actuator/bus-refresh/config-client:3355
简而言之:Spring Cloud Stream 就是屏蔽底层消息中间件(ActiveMQ、RabbitMQ、RocketMQ、Kafka)的差异,降低切换成本,统一消息的编程模型
Spring Cloud Stream 的官方定义为 是一个构建消息驱动微服务的框架
应用程序通过 inputs 或 outputs 来与 Spring Cloud Stream 中 binder 对象交互,通过我们配置 binding(绑定),而 Spring Cloud Stream 的 binder 对象负责与消息中间件交互,所以我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便实用消息驱动的方式
通过使用 Spring Integration 来链接消息代理中间件以实现消息事件驱动,Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置,引用了 发布-订阅、消费组、分区 的三个概念
**(目前仅支持 RabbitMQ、Kafka)**❤️
Spring Cloud Stream由一个中立的中间件内核组成。Spring Cloud Stream会注入输入和输出的channels,应用程序通过这些channels与外界通信,而channels则是通过一个明确的中间件Binder与外部brokers连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLZqxLDH-1642578424358)(/Users/ban/Library/Application Support/typora-user-images/image-20220119092254802.png)]
上图名词解释:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
配置文件如下(注意rabbitmq的锁进,以及bindings的锁进)
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
service 层实现类
/**
此处的 Source 是 import org.springframework.cloud.stream.messaging.Source;
此处的 MessageBuilder 是 import org.springframework.integration.support.MessageBuilder;
*/
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider {
@Autowired
private MessageChannel output;
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("****serial:"+serial);
return null;
}
}
controller 层
@RestController
public class SenMessageController {
@Autowired
private IMessageProvider messageProvider;
@GetMapping("/sendMessage")
public String sendMessage(){
return messageProvider.send();
}
}
主启动层依旧为 EnableEureka 注解
效果如下图
依赖与 8801 完全相同
配置如下:
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
controller 层
//sink 是 import org.springframework.cloud.stream.messaging.Sink;
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
public String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
System.out.println("消费者 1 号,---》》》收到的消息:"+message.getPayload()+"/t"+serverPort);
}
}
创建与 8802 完全相同的 8803
此时会出现两个问题
为什么会有重复消费问题?
因为默认分组 group 是不同的,组流水号不同,被认为不同组,所以重复消费,同一个组会产生竞争,只有一个可以消费
故可以采用分组解决重复消费问题
只需要 8802、8803 加入 group: ban1
此处再次使用 8801 发送两条信息,就会发现 8802、8803 各一条
当你没有分组时,8802(没有 group) 宕机,此时8801 一直在发送信息,但是 8802 重启后接受不到宕机这段时间 8801 发送的消息
但是 group 的 8803 却可以在重启后,接收到 8801 的消息
原理:由于数据发送者将数据发送到队列中,由于 8002 没有设置分组,会重新创建队列并监听,而 8003 创建了分组,再次启动会直接返回分组中监听到的
故group 可以解决分组消费和消息持久化两个问题
迫不及待学 Alibaba ,如果涉及 以后补全!
历经约七天的学习,Spring Cloud 落下了帷幕,从第一次接触到 Spring Cloud 的时候,不亚于我刚从 SSM 过度到 Spring Boot 的惊讶,微服务为我在编程之路或许是一个长足的进步,从一个较高层次向下看,以前的一些困惑迎刃而解。
言不止,语不休,2022-01-19 15:40,结束了 Spring Cloud 的旅程,开始 Spring Cloud Alibaba ,相信在此开启此篇笔记,该是寻安身立命之时!