简单的微服务
未使用框架的微服务
我们通过7071端口去调8081端口的服务
8081
/**
* @author acoffee
* @create 2022-01-19 17:24
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/findAll")
public ResponseEntity<List<User>> findAll(){
List<User> users = userService.findAll();
if (users != null){
return new ResponseEntity<>(20000,"查询成功",users);
}else {
return new ResponseEntity<>(50000,"查询失败",null);
}
}
package com.acoffee.springcloud.provider.mapper;
import com.acoffee.springcloud.common.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author acoffee
* @create 2022-01-19 17:22
*/
@Mapper
@Repository
public interface UserMapper {
User findById(int id);
List<User> findAll();
boolean addUser(User user);
boolean updateUser(User user);
boolean deleteUser(int id);
}
7071
配置文件
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {
@Autowired
RestTemplate restTemplate;
String url = "http://localhost:8081/user/";
@RequestMapping("/getUserById/{id}")
public ResponseEntity<User> getUserById(@PathVariable int id){
//restTemplate的API实现get远程调用
return restTemplate.getForObject(url+"/findById/"+id,ResponseEntity.class);
}
@GetMapping("/")
public ResponseEntity<List<User>> finAll(){
return restTemplate.getForObject(url+"/findAll",ResponseEntity.class);
}
@PostMapping("/addUser")
public ResponseEntity<Boolean> addUser(User user){
return restTemplate.postForObject(url+"/addUser",user,ResponseEntity.class);
}
}
8081中要接收7071来的对象必须要写@RequestBody
执行结果:
服务注册Eureka基础
微服务的注册中心
注册中心可以说是微服务架构中的通讯录,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用。
常见的注册中心
组件名 | 语言 | CAP | 对外暴露接口 |
---|---|---|---|
Eureka | Java | AP | HTTP |
Consul | Go | CP | HTTP/DNS |
Zookeeper | Java | CP | 客户端 |
Nacos | Java | AP | HTTP |
Eureka的概述
Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目spring-cloud-netflix中, 实现SpringCloud的服务发现功能。
Eureka
包含两个组件:Eureka Server
和 Eureka Client
,它们的作用如下:
Eureka Client
是一个Java客户端,用于简化与Eureka Server
的交互;
Eureka Server
提供服务发现的能力,各个微服务启动时,会通过Eureka Client
向Eureka Server
进行注册自己的信息(例如网络信息),Eureka Server
会存储该服务的信息;
6061搭建Eureka服务端
依赖(注意与Springboot的版本兼容问题)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
配置文件
server:
port: 6061
eureka:
instance:
hostname: localhost #配置实例的名字
client:
#是否获取服务器上其他服务的注册信息,默认为true,Eureka客户端就用true,
#Eureka服务端:集群一定要用true,单机版可用false
fetch-registry: false
#是否将自己的信息注册到服务器上
register-with-eureka: false
service-url:
#访问的Eureka服务器的地址
defaultZone: http://localhost:6061/eureka
启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplicaiotn6061 {
public static void main(String[] args) {
SpringApplication.run(EurekaApplicaiotn6061.class,args);
}
}
8081服务的提供者 (注册信息到注册中心)
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test3
username: root
password: 7777
application:
name: provider-user #注册到注册中心的名字
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.acoffee.springcloud.common.entity
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:6061/eureka
启动类启用
@EnableEurekaClient
@SpringBootApplication
public class UserProviderApplication8081 {
public static void main(String[] args) {
SpringApplication.run(UserProviderApplication8081.class,args);
}
}
这个时候启动两个服务就可以看到8081已经注册进去了
7071服务的消费者 (到注册中心获取服务列表)
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件
server:
port: 7071
spring:
application:
name: provider-consumer
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:6061/eureka
7071使用服务发现通过注册中心获取服务调用地址
@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {
@Autowired
RestTemplate restTemplate;
@Autowired
DiscoveryClient discoveryClient;
//目前使用的调用方式是:RestTemplate+注册中心
String getUrl(){
List<ServiceInstance> instances = discoveryClient.getInstances("provider-user");
ServiceInstance serviceInstance = instances.get(0);
return "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/";
}
@GetMapping("/")
public ResponseEntity<List<User>> finAll(){
return restTemplate.getForObject(getUrl()+"/findAll",ResponseEntity.class);
}
}
一般先启动eureka服务端(先启动客户端得不到连接有可能会报错),同时启动三个服务
Eureka集群
Eureka(由瑞卡)
是一个服务治理的模块,主要的功能是服务注册与发现,管理每个服务与服务之间的依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,Eureka
包含两个组件:Eureka Server 和 Eureka Client
,Eureka Server
提供服务注册服务,各个微服务节点通过配置启动后,会在Eureka Server
中进行注册,服务结点的信息可以在服务界面上看到,Eureka Client
又包含消费者和提供者,提供者提供对应的微服务,消费者消费相应的微服务。
Eureka Client
会定时连接 Eureka Server
,获取注册表中的信息并缓存到本地。微服务在消费远程API
时总是使用本地缓存中的数 据。因此一般来说,即使Eureka Server
发生宕机,也不会影响到服务之间的调用。但如果Eureka Server
宕机时,某些微服务也出现了不可用的情况,Eureka Server
中的缓存若不被刷新,就可能会影响到微服务的调用,甚至影响到整个应用系统的高可用。因此,在生成环境中,通常会部署一个高可用的Eureka Server
集群。
Eureka Server
可以通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server
实例会彼此增 量地同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server
的默认行为。
7071 和 8081 都挂在6061上
server:
port: 6061
eureka:
instance:
hostname: localhost
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:6062/eureka
spring:
application:
name: eureka-6061
server:
port: 6062
eureka:
instance:
hostname: localhost
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:6061/eureka
spring:
application:
name: eureka-6062
#info可以配置微服务信息
Eureka自我保护机制
一般情况下,我们会选择在 开发环境下关闭自我保护机制,而在生产环境下启动自我保护机制。
eureka:
server:
enable-self-preservation: false #禁用自我保护
就是你的注册的服务就算停了,注册在Eureka
的信息仍然不会丢失。 而是保存一段时间。
其实我们用 Eureka
作为注册中心,注册过后会建立心跳,保持通信状态。 而自我保护机制,就是为了避免因为网络波动造成误判,直接把服务剔除,两个连接暂时断开连接了,虽然不能通信,但是其实有可能 服务还在,那么 Eureka
就多等上一点时间,默认就是 90
秒。
其实 Eureka
是一种CAP
中的 AP
的设计思想。A
是高可用,P
是分区容错性。
启动自动保护机制的条件
网上大多数都是说统计心跳失败的比例在15
分钟之内是否低于85%
,如果出现低于的情况,Eureka Server
会将当前的实例注册信息保护起来,这个说法并不是那么准确,很容易误解。
它会先去判断配置自我保护机制没有:
未配置自我保护机制:
客户端每30s续约(发送心跳包)一次,服务端(注册中心)每 60s 扫描一次超过 90s 还没有续约的客户端,就会剔除。
如果配置了自我保护机制:
它会去比对上一分钟收到的心跳数 (实际续约数) 是否小于期望的心跳数 (续约阈值,默认值是期望心跳的85%),如果小于期望值就会开启自我保护机制,触发自我保护机制后,服务端不会剔除任何客户端,它还会每60s执行一次检查,直到发现网络正常,就会自动退出自我保护,此时超过90s还没有续约的客户端才会被剔除掉。
而刷新阈值有三种情况:
① 新服务注册的时候
② 注销服务时
③ 每15分钟计算一次阈值(定时任务)。
这是启动自我保护机制的核心代码
public boolean isLeaseExpirationEnabled() {
if (!isSelfPreservationModeEnabled()) {
// The self preservation mode is disabled, hence allowing the instances to expire.
return true;
}
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}
我这里有 4 个注册实例,保护系数:0.85,心跳频率:30秒(每分钟两次),计算公式如下:
最小心跳阈值计算公式= 服务总数*(60/客户端的心跳间隔)* 自我保护续约百分比阀值因子
Renews threshold = 4 * 2 * 0.85 = 6.8(取整为:6)
Renews (last min) = 4 * 2 = 8
比如我们现在的扩容因子是 6,而最后一分钟收到的是8,所以不会开启自我保护机制。
续约阀值的更新的三种条件
1.新服务注册进来时(register)(这里省去了register()中其他逻辑)
synchronized(this.lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
++this.expectedNumberOfClientsSendingRenews;
this.updateRenewsPerMinThreshold();
}
}
当有新服务(实例)注册进来时,expectedNumberOfClientsSendingRenews
会增加,然后触发updateRenewsPerMinThreshold()
更新threshold
。
2.注销服务时(cancel)
public boolean cancel(String appName, String id, boolean isReplication) {
if (super.cancel(appName, id, isReplication)) {
this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Cancel, appName, id, (InstanceInfo)null, (InstanceStatus)null, isReplication);
synchronized(this.lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
--this.expectedNumberOfClientsSendingRenews;
this.updateRenewsPerMinThreshold();
}
return true;
}
} else {
return false;
}
}
注销时,expectedNumberOfClientsSendingRenews
会减少,然后触发updateRenewsPerMinThreshold()
更新threshold
。
3.Task定时任务(默认15分钟)
private void scheduleRenewalThresholdUpdateTask() {
this.timer.schedule(new TimerTask() {
public void run() {
PeerAwareInstanceRegistryImpl.this.updateRenewalThreshold();
}
}, (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs(), (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs());
}
服务剔除测试
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer
发起Rest
请求) 。告诉EurekaServer
: “我还活着"。这个我们称为服务的续约(renew
) ;
有两个重要参数可以修改服务续约的行为;可以在8081 中添加如下配置项: .
lease-renewal-interval-in-seconds: 服务续约(renew)的间隔, 默认为30秒
lease-expiration-duration-in-seconds: 服务失效时间,默认值90秒
也就是说,默认情况下每隔30
秒服务会向注册中心发送一次心跳, 证明自己还活着。如果超过90秒没有发送心跳,EurekaServer
就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer.in-ms
设定的时间,默认60秒) 从服务列表中移除,这两个值在生产环境不要修改,默认即可。
改变6061的配置文件 (将自我保护机制关掉)
server:
port: 6061
eureka:
instance:
hostname: localhost
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:6062/eureka
server:
#关闭自我检查
enable-self-preservation: false
#服务器剔除服务的时间,默认每过60秒启动启动检查
eviction-interval-timer-in-ms: 2000
spring:
application:
name: eureka-6061
修改7071配置文件
server:
port: 7071
spring:
application:
name: provider-consumer
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:6061/eureka
instance:
#客户端端默认每隔30s向服务器发送心跳包
lease-renewal-interval-in-seconds: 1
#客户端默认连续90s没有向服务器发送心跳包,服务端认为当前服务器宕机
lease-expiration-duration-in-seconds: 2
手动关闭7071,一秒之后之后直接剔除7071服务
Ribbon搭建
Ribbon
是 Netflix
发布的一个负载均衡器,有助于控制 HTTP
和 TCP
客户端行为。在 SpringCloud
中, Eureka
一般配合Ribbon
进行使用,Ribbon
提供了客户端负载均衡的功能,Ribbon
利用从Eureka
中读取到的服务信息,在调用服务节点提供的服务时,会合理的进行负载。
在SpringCloud
中可以将注册中心和Ribbon
配合使用,Ribbon
自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务
Ribbon的主要作用
服务调用
基于Ribbon
实现服务调用, 是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助 RestTemplate
最终进行调用
负载均衡
当有多个服务提供者时,Ribbon
可以根据负载均衡的算法自动的选择需要调用的服务地址
依赖我们上面添加的依赖中以及存在Ribbon
的依赖
RestTemplate
配置类中添加注解
@Configuration
public class RestTemplateConfiguration {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
//使用Ribbon调用不要服务发现,虽然用了也不报错
//@EnableDiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ConsumerStudent7071Application {
public static void main(String[] args) {
SpringApplication.run(ConsumerStudent7071Application.class,args);
}
}
String getUrl() {
//Ribbon的本质就是restTemplate+@LoadBlanced,自动去注册中心通过名字获取调用地址
return "http://provider-user/user/";
}
查询无误
客户端负载均衡与服务端负载均衡
服务端负载均衡 (Nginx
集中式)
先发送请求到负载均衡服务器或者软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访 问;即在服务器端再进行负载均衡算法分配
客户端负载均衡 (Ribbon
进程内)
客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配
Ribbon
内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为com.netflix.loadbalancer.IRule
,实现方式如下
参数 | 介绍 |
---|---|
com.netflix.loadbalancer.RoundRobinRule | 以轮询的方式进行负载均衡。(默认) |
com.netflix.loadbalancer.RandomRule | 随机策略 |
com.netflix.loadbalancer.RetryRule | 重试策略。 |
com.netflix.loadbalancer.WeightedResponseTimeRule | 权重策略。会计算每个服务的权重,越高的被调用的可能性越大。 |
com.netflix.loadbalancer.BestAvailableRule | com.netflix.loadbalancer.BestAvailableRule |
com.netflix.loadbalancer.AvailabilityFilteringRule | 可用过滤策略。过滤掉故障 |
OpenFeign
OpenFeign简介
Feign
是Netflix
开发的声明式,模板化的HTTP
客户端。一种调用微服的规范。
Feign
可帮助我们更加便捷,优雅的调用HTTP API
。
在SpringCloud
中,使用Feign
非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。
SpringCloud
对Feign
进行了增强,使Feign
支持了SpringMVC
注解,并整合了Ribbon
和Eureka
,从而让Feign
的使用更加方便。
OpenFeign搭建
<!--OpenFeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
7071
//定义接口,声明Feign调用,指定要调用的服务名
//接口中定义的方法,就是要调用的8081中的方法和请求路径
@FeignClient("provider-user")
@RequestMapping("/user")
public interface UserFeignClient {
//使用OpenFeign如果要调用@PathVariable必须有value属性
@GetMapping("/timeout")
public ResponseEntity<String> timeout();
@GetMapping("/ok")
public ResponseEntity<String> ok();
}
@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {
@Autowired
UserFeignClient userFeignClient;
@GetMapping("/timeout")
public ResponseEntity<String> timeout(){
return userFeignClient.timeout();
}
@GetMapping("/ok")
public ResponseEntity<String> ok(){
return userFeignClient.ok();
}
}
启动类
@EnableFeignClients //扫描Feign
@EnableEurekaClient
@SpringBootApplication
public class ConsumerStudent7071Application {
public static void main(String[] args) {
SpringApplication.run(ConsumerStudent7071Application.class,args);
}
}
OpenFeign负载均衡
Feign
中本身已经集成了Ribbon
依赖和自动配置,因此我们不需要额外引入依赖,也不需要再注册RestTemplate
对象。
OpenFeign日志打印功能
Feign提供了日志打印功能,我们可以通过配来调整日志级别,从而了解Feign中Http请求的细节。
logging.level.project.user.UserClient: DEBUG
日志级别 | 介绍 |
---|---|
NONE | 【性能最佳,适用于生产】:不记录任何日志(默认值) |
BASIC | 【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间 |
HEADERS | 记录BASIC级别的基础上,记录请求和响应的header。 |
FULL | 【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。 |
OpenFeign超时控制
默认Feign
客户端只等待1秒钟
,我们模拟服务端处理需要超过1秒钟
,导致Feign
客户端不想等待了 ,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign
客户端的超时控制或者使用降级方法(后面介绍)。
8081
定义服务,7071
通过OpenFeign
调用
8081
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/timeout")
public ResponseEntity<String> timeout() {
//模拟超时(异常)
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ResponseEntity<>(2000, "ok", "timeout");
}
@GetMapping("/ok")
public ResponseEntity<String> ok() {
return new ResponseEntity<>(2000, "ok", "timeout");
}
}
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
Hystrix
服务雪崩
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者"的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
当这个超时(模拟异常)的服务没有配置延时设置并且访问量较大时,就有可能导致其他没有异常的服务也出现异常,就像多米诺骨牌一样导致其他大量服务也异常,即服务雪崩
对7071的timeout(模拟的异常)进行压测,我们发现ok(正常的方法)方法也异常
为了防止服务雪崩,hystrix
提供了基于断路器的服务熔断机制
Hystrix
Hystrix (黑丝拽克斯) 是一个用于分布式系统的延迟和容错的开源库。在分布式系统里,许多依赖不可避免的调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性。
服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback哪些情况会出发降级:程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级
服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示,就是保险丝:服务的降级 → 进而熔断 → 恢复调用链路
服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤, 大家排队,一秒钟N个,有序进行
服务降级熔断模拟测试
timeout
异常,ok
也异常,就算我们给timeout
添加异常处理,timeout
并发高,正常的服务ok
方法也会被拖慢或者报异常。
所以这个时候我们可以配置断路器
配置断路器
<!--Hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置文件
feign:
hystrix:
enabled: true
启动类
//启用Hystrix
@EnableHystrix
//启用OpenFeign
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class ConsumerStudent7071Application {
public static void main(String[] args) {
SpringApplication.run(ConsumerStudent7071Application.class,args);
}
}
定义降级方法
//定义调用当前方法如果出现异常,执行降级方法,降级方法
//和原来的方法的方法签名一致(访问修饰符、返回值、参数列表)
@HystrixCommand(fallbackMethod = "timeoutFallback")
@GetMapping("/timeout")
public ResponseEntity<String> timeout(){
System.out.println("进入timeout");
return userFeignClient.timeout();
}
//定义降级方法
public ResponseEntity<String> timeoutFallback(){
System.out.println("进入timeoutFallback");
return new ResponseEntity<String>(50000,"fallback","timeout-allback");
}
也可以定义统一降级方法,在类上使用使用@DefaultProperties
注解然后直接在方法上使用@HystrixCommand
即可,当时这种写法方法签名统一不好满足,所以我们一般不使用
@DefaultProperties(defaultFallback = "ConsumerUserFallback")
@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
public interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
public class HystrixClientFallback implements HystrixClient {
@Override
public Hello iFailSometimes() {
return new Hello("fallback");
}
}
熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
通俗来说,服务降级就是请求某个服务执行方法,出现问题,执行降级方法,当失败的调用到一定的阈值 (默认是5秒内20次调用失败) 就会触发熔断机制,一旦触发了熔断机制,请求就不会去执行映射方法了,会直接去执行降级方法。熔断机制的注解是 @HystrixCommand
熔断状态机3个状态:
状态 | 介绍 |
---|---|
Closed | 关闭状态,所有请求都正常访问。 |
Open | 打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断, 断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。 |
Half Open | 半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时 |
通过配置修改熔断策略:
circuitBreaker.requestVolumeThreshold=10 //触发熔断的最小请求次数,默认20
circuitBreaker.sleepWindowInMilliseconds=10000 //休眠时长,默认是5000毫秒
circuitBreaker.errorThresholdPercentage=50 //触发熔断的失败请求最小占比,默认50%
HystrixDashboard(图形化的监控平台)
Hystrix图形化界面的监控平台,底层要使用SpringBoot监控插件支持
创建监控平台的项目5051
依赖
<dependencies>
<!--添加监控依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
配置端口
server:
port: 5051
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
启动类
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixDashboard5051Application {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard5051Application.class,args);
}
}
7071中配置类
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean =
new ServletRegistrationBean( streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream" );
registrationBean.setName("HystrixMetricsStreamServlet" );
return registrationBean;
}
页面上面的几个参数局域
最上面的输入框: 输入上面所说的三种监控方式的地址,用于访问具体的监控信息页面。
Delay
: 该参数用来控制服务器上轮询监控信息的延迟时间,默认2000毫秒。
Title
: 该参数对应头部标题Hystrix Stream
之后的内容,默认会使用具体监控实例的Url。
输入http://localhost:7071/hystrix.stream
hystrix-dashboard
页面一直处于加载状态,这里需要给dashboard
监控的服务发送一个请求,该请求是所监控的服务的开启了熔断的端点,也就是该请求里面有调用加了@HystrixCommand
注解的方法,然后就可以看到数据了。
1、圆点:微服务的健康状态,颜色有绿色、黄色、橙色、红色,健康状态依次降低
2、线条:流量变化
3、请求的方法
4、成功请求(绿色)
5、短路请求(蓝色)
6、坏请求(青色)
7、超时请求(黄色)
8、被拒绝的请求(紫色)
9、失败请求(红色)
10、最近10秒钟内请求错误的百分比
11、请求频率
12、熔断器状态
13、数据延迟统计
14、线程池 .
Zuul
ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器。Spring Cloud对Zuul进行了整合和增强。
搭建Zuul网关服务器基本环境
<dependencies>
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
启动类启用
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class Zuul9091Application {
public static void main(String[] args){
SpringApplication.run(Zuul9091Application.class,args);
}
}
配置文件
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class Zuul9091Application {
public static void main(String[] args){
SpringApplication.run(Zuul9091Application.class,args);
}
}
面向服务的路由
Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的路由配置。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
配置文件
server:
port: 9091
spring:
application:
name: zuul
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:6061/eureka
#网关配置
zuul:
routes: #路由配置
provider-user: #路由ID,可以任意,建议使用微服名
path: /user/** #请求路径
url: provider-user #服务名
consumer-user:
path: /consumeruser/**
url: provider-consumer
执行结果:
简化的路由配置
#面向路由的简化写法,必须使用服务名
zuul:
routes: #路由配置
provider-student: /student/**
consumer-student: /consumerstudent/**
#http://localhost:9091/student/student/1
#只要Zuul获取Eureka的服务信息,可以什么都不配置,默认支持通过服务名访问
#http://localhost:9091/provider-student/student/1
Zuul中的过滤器
Zuul
它包含了两个核心功能:对请求的路由和过滤。
过滤器可以说是Zuul
实现API
网关功能最为核心的部件,每一个进入Zuul
的HTTP
请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
Zuul
中的过滤器跟我们之前使用的 javax.servlet.Filter
不一样,javax.servlet.Filter
只有一种类型,可以通过配置 urlPatterns
来拦截对应的请求。
Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。
类型 | 说明 |
---|---|
PRE | 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。 |
ROUTING | 这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient 或Netfilx Ribbon 请求微服务。 |
POST | 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header 、收集统计信息和指标、将响应从微服务发送给客户端等。 |
ERROR | 在其他阶段发生错误时执行该过滤器 |
有name参数,就是合法用户,不拦截,放行
http://localhost:9091/user/user/1?name=zs
没有name参数,执行拦截,判断有没有token,有token放行
http://localhost:9091/user/user/1?token=zs
/**
* Zuul过滤器
*/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 拦截类型:拦截器在什么时候执行
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 拦截器的执行顺序:越小优先级高
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否拦截:true:拦截,执行run
* false:不拦截,不执行run
* @return
*/
@Override
public boolean shouldFilter() {
//获取请求上下文环境
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String name = request.getParameter("name");
if(!StringUtils.isEmpty(name)){
System.out.println("有name不拦截,直接放行");
return false;
}
return true;
}
/**
* 拦截器执行的代码
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
System.out.println("没有name,执行拦截方法,判断token");
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getParameter("token");
if(StringUtils.isEmpty(token)){
System.out.println("没有token,拦截,不能访问");
currentContext.setSendZuulResponse(false);
//currentContext.setResponseStatusCode(500);
try {
//currentContext.getResponse().getWriter().write("");
currentContext.setResponseBody(new ObjectMapper().writeValueAsString(
new ResponseResult<>(401,"authentication","login first")));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}else{
System.out.println("有token,放行");
}
return null;
}
}
配置中心
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要有配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题。统一管理微服项目所有模块的配置文件,配置文件存储在git仓库,微服连接到配置中心,配置中心连接到git获取真正的配置文件
配置中心搭建
① 创建git远程仓库
② 依赖
<dependencies>
<!--配置中心服务端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
③ yml配置
server:
port: 4041
eureka:
client:
service-url:
defaultZone: http://localhost:6061/eureka
#配置连接到git
spring:
application:
name: config
cloud:
config:
server:
git:
uri: https://gitee.com/acoffee/config #git仓库远程地址
search-paths: /** #搜索路径
label: master #指定分支
④ 启动类
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class Config4041Application {
public static void main(String[] args) {
SpringApplication.run(Config4041Application.class, args);
}
}
⑤ 将7071的配置文件application.yml
改名为consumeruser-dev.yml
,传到git,删除7071的配置文件
命名规范
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
⑥ 测试通过配置中心获取远程仓库的配置文件http://localhost:4041/master/consumeruser-dev.yml
结果如下能访问到
在7071
的controller
中将配置文件中的testconfig
的值读出来
@Value("${testconfig}")
String testconfig;
@GetMapping("/testConfig")
public ResponseEntity<String> testConfig(){
System.out.println("进入timeoutFallback");
return new ResponseEntity<String>(20000,"testconfig",testconfig);
}
访问7071
的controller
⑦ 7071
添加config
客户端依赖
<!--添加config客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
⑧ 7071添加配置文件bootstrap.yml
bootstrap.yml
就是SpringBoot
支持的配置文件,优先级高于application.yml
,指定连接配置中心的地址
#连接到4041配置中心
spring:
cloud:
config:
label: master
name: consumerstudent
profile: dev
uri: http://localhost:4041
#http://localhost:4041/master/consumeruser-dev.yml
启动6061、4041、7071
三个项目
⑨ 修改git中的配置文件
通过注册中心去看已经更改
重启7071
刷新配置我们就可以通过7071
的controller
去访问,发现也已经更改
自动刷新
上面这种方式只能重启项目,我们可以不重启项目自动刷新配置,不用重启项目,发送post
请求,修改的配置文件生效(需要安装curl
)
<!--添加监控依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加一个刷新监控点,就是一个请求路径的名字,我们通过发送post
请求,底层自动刷新配置
7071
的Controller
添加@RefreshScope
注解
//添加自动刷新注解在Controller上
@RefreshScope
@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {
......
修改了远程仓库的配置文件,不需要重启服务器,发送POST
请求,自动刷新,修改testconfig
的值
输入curl -XPOST "http://localhost:7071/actuator/refresh"
BUS消息总线
但是上面的方式只能一个一个的刷新,我们可以使用BUS消息总线一次性刷新,它底层使用消息队列,统一刷新所有微服务的配置,本质就是将他们放到mq
队列中处理
4041,7071,8081
添加:bus
依赖,actuator
依赖,添加消息队列的连接配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
配置文件
7071
#连接到4041配置中心
spring:
cloud:
config:
label: master
name: consumeruser
profile: dev
uri: http://localhost:4041
rabbitmq:
host: 192.168.195.157
port: 5672
username: guest
password: guest
#POST请求的监控点
management:
endpoints:
web:
exposure:
include: "refresh"
8081
#连接到4041配置中心
spring:
cloud:
config:
label: master
name: provideruser
profile: dev
uri: http://localhost:4041
rabbitmq:
host: 192.168.195.157
port: 5672
username: guest
password: guest
#POST请求的监控点
management:
endpoints:
web:
exposure:
include: "refresh"
4041
server:
port: 4041
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:6061/eureka
#连接到4041配置中心
spring:
application:
name: config
cloud:
config:
server:
git:
uri: https://gitee.com/acoffee/config.git
search-paths: /**
label: master
rabbitmq:
host: 192.168.195.157
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: bus-refresh
修改
通过curl -XPOST "http://localhost:4041/actuator/bus-refresh
刷新,上面的 bus-refresh
就是actuator
的刷新机制,相当于提供了一个 /bus-refresh
的post
方法,当我们调用的时候,执行完成后,配置中心会通过BUS消息总线,发送到所有的客户端,并完成配置的刷新操作。完成了一次修改,广播通知,处处生效的效果
使用BUS可以指定只刷新一个服务
curl -XPOST "http://localhost:4041/actuator/bus-refresh/服务名:端口号"
curl -XPOST "http://localhost:4041/actuator/bus-refresh/consumer-student:7071"
刷新过后我们看到两个都刷新了
我们上面是直接去4041端口去访问的配置文件,我们也可以统一去注册中心访问:
只需要在7071和8081中加一个eureka
配置,这个是将其注册到mq
中去
#连接到4041配置中心
spring:
cloud:
config:
label: master
name: consumeruser
profile: dev
#uri: http://localhost:4041
#http://localhost:4041/master/consumerstudent-dev.yml
uri: http://localhost:6061 #连接注册中心,从注册中心通过config的服务名获取config的地址这个配置文件无法获取注册中心,还要写上注册中心的配置信息
discovery:
enabled: true
service-id: config
rabbitmq:
host: 192.168.195.157
port: 5672
username: guest
password: guest
#POST请求的监控点
management:
endpoints:
web:
exposure:
include: "refresh"
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:6061/eureka
本质上没有什么区别,一个是直接通过4041端口去git中找到对应的配置文件,另外一个是在eureka
注册中心中找到4041
对应的微服务结点(service-id: config
),然后再到git中去找,第二种就多饶了一下。