springcloud和springboot的区别 springboot专注于开发单个服务
springcloud用来开发多个服务,关注全局的服务的协调和治理工作,将springboot开发的单个微服务整合起来,给各个服务之间提供配置管理,服务发现,断路器,路由,事件总线,配置等继承服务。
springboot是springcloud的基础。
什么是springcloud Spring Cloud是一个基于Spring boot实现的微服务架构开发工具,微服务架构是SOA架构的发展。它为微服务架构中提供配置管理、服务治理、智能路由、断路器以及集群状态管理等等。Spring cloud是基于HTTP协议的架构。
Springboot只用来开发单个服务
Springcloud可以开发多个服务
核心组件:
注册中心:Eureka ,Nacos ,Consul
负载均衡:Ribbon,sentinel, loadbalancer
容错保护:Hystrix,resilience4j,sentinel
服务调用:feign,openfeign
网关:Zuul,Gateway
配置中心:config,Nacos
入门案例 有一个服务(提供者),提供图书的检索功能。
有另外一个服务(消费者),需要买书时,按照编号查看书的信息。
公共模块 创建普通maven工程base00-common,并编写实体类
1 2 3 4 5 6 7 8 9 @Data @NoArgsConstructor @AllArgsConstructor public class Book { String isbn; String name; String author; double price; }
服务提供者 创建一个spring boot的web工程,并增加base00-common的依赖
依赖文件 1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > net.wanho</groupId > <artifactId > base00-common</artifactId > <version > 1.0</version > </dependency >
service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class BookService { static Map<String,Book> map = new HashMap <>(); static { map.put("SB1001" ,new Book ("SB1001" ,"随便" ,"佚名" ,50 )); map.put("SB1002" ,new Book ("SB1002" ,"浮士德" ,"歌德" ,60 )); map.put("SB1003" ,new Book ("SB1003" ,"我们仨" ,"杨绛" ,25 )); } public Book findByIsbn (String isbn) { return map.get(isbn); } }
controller 1 2 3 4 5 6 7 @Resource BookService bookService; @GetMapping("book/{isbn}") public Book findBookByIsbn (@PathVariable("isbn") String isbn) { return bookService.findByIsbn(isbn); }
服务的调用者 创建一个普通springboot的web工程,并增加base00-common的依赖
用户服务
依赖文件 1 2 3 4 5 6 7 8 9 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>net.wanho</groupId> <artifactId>base00-common</artifactId>c <version>1.0 </version> </dependency>c
注册RestTemplate 1 2 3 4 @Bean public RestTemplate restTemplate () { return new RestTemplate (); }
编写Service,调用别人提供的服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class UserService { @Resource RestTemplate restTemplate; public Book searchBook (String isbn) { System.out.println("用户查找图书" ); String url = host + "/book/" + isbn; Book book = restTemplate.getForObject(url, Book.class); return book; } }
编写控制器测试 1 2 3 4 5 6 7 8 9 10 11 @RestController public class UserController { @Resource UserService userService; @GetMapping("borrow/{isbn}") public Book searchBook (@PathVariable("isbn") String isbn) { return userService.searchBook(isbn); } }
Eureka(注册中心) 是Spring cloud中的一个服务治理模块。
NetFlix公司一系列开源产品中的其中之一,它的主要作用是服务的注册和发现。
服务器端:也称为服务注册中心,提供服务的注册和发现。Eureka支持高可用的配置,当集群当中有节点(分片)出现故障时,Eureka会自动进入自我保护模式,它允许故障期间提供服务的发现和注册,当故障分片(节点)恢复后,集群的其他节点(分片)会把数据同步过来。
客户端:主要包含服务的生产者和服务消费者。服务的提供者要和服务器端维持心跳,来更新它的服务租约。可以将服务器端的注册信息缓存到本地,并周期性的更新服务状态。
服务端 创建一个普通springboot工程base01-eureka,注意不要选择web依赖 ,增加Eureka服务端依赖
依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.9</version > <relativePath /> </parent > <groupId > net.wanho</groupId > <artifactId > base01-eureka</artifactId > <version > 1.0</version > <name > base01-eureka</name > <description > Demo project for Spring Boot</description > <properties > <java.version > 1.8</java.version > <spring-cloud.version > 2021.0.6</spring-cloud.version > </properties > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring-cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement >
配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server: port: 7100 spring: application: name: eureka-server eureka: client: service-url: defaultZone: http://localhost:7100/eureka fetch-registry: false register-with-eureka: false instance: hostname: localhost
启动类 增加@EnableEurekaServer注解
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @EnableEurekaServer public class Base01EurekaApplication { public static void main (String[] args) { SpringApplication.run(Base01EurekaApplication.class, args); } }
服务提供者 在原来的base01-provider上进行修改
增加依赖 在各自的节点内,增加以下相关内容,注意不要覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <properties > <spring-cloud.version > Hoxton.SR9</spring-cloud.version > </properties > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > </dependencies > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring-cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement >
配置文件 1 2 3 4 5 6 7 8 9 10 11 server: port: 8090 spring: application: name: bookapp eureka: client: service-url: defaultZone: http://localhost:7100/eureka register-with-eureka: true fetch-registry: true
修改主启动类 增加@EnableEurekaClient注解或者@EnableDiscoveryClient
1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication @EnableEurekaClient public class Base01ProviderApplication { public static void main (String[] args) { SpringApplication.run(Base01ProviderApplication.class, args); } }
服务消费者 在原来的base01-consumer上进行修改
增加依赖 参考服务提供者
配置文件 1 2 3 4 5 6 7 8 9 10 11 server: port: 8081 spring: application: name: userapp eureka: client: service-url: defaultZone: http://localhost:7100/eureka register-with-eureka: true fetch-registry: true
修改主启动类 增加@EnableEurekaClient注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringBootApplication @EnableEurekaClient public class Base01ConsumerApplication { public static void main (String[] args) { SpringApplication.run(Base01ConsumerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); } }
修改RestTemplate 增加负载均衡
1 2 3 4 5 @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); }
修改调用host 用服务名替换原来的具体节点URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Service public class UserService { String host = "http://BOOKAPP" ; @Resource RestTemplate restTemplate; public Book searchBook (String isbn) { System.out.println("用户查找图书" ); String url = host + "/book/" + isbn; Book book = restTemplate.getForObject(url, Book.class); return book; } }
指定服务的IP地址 使用eureka.instance.prefer-ip-address=true显示ip
eureka.instance.ip-address=127.0.0.1来指定ip地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 8070 spring: application: name: bookapp eureka: client: service-url: defaultZone: http://localhost:7100/eureka register-with-eureka: true fetch-registry: true instance: prefer-ip-address: true ip-address: 127.0 .0 .1
服务端添加用户验证 整合spring security,要求客户端注册时,提供服务端需要的用户名和密码
服务器端 引入依赖 引入spring-boot-starter-security依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency >
修改配置 配置文件当中指定用户名和密码,并且修改defaultZone配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server: port: 7100 spring: application: name: eureka-server security: user: name: admin password: 123456 eureka: client: service-url: defaultZone: http: #是否要到注册中心拉取服务 fetch-registry: false #当前的服务是否要注册到指定的注册中心,由于现在时server自身,不需要注册 register-with-eureka: false
增加springsecurity的配置项 1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER); http.csrf().disable(); http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); return http.build(); } }
客户端 在注册配置时,提供用户名和密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 8090 spring: application: name: GOODS eureka: client: service-url: defaultZone: http://admin:123456@localhost:7100/eureka register-with-eureka: true fetch-registry: true instance: prefer-ip-address: true ip-address: 192.168 .40 .251
取消刷新 默认情况下,Eureka client是可以刷新的。当刷新客户端时,客户端暂时从服务器中取消注册,可能在短暂的时间内不提供给定的服务实例。设置配置:eureka.client.refresh.enable=false ,则不刷新客户端
自我保护 默认情况下,Eureka服务器端在一定的时间内如没有接收某个服务端实例的心跳,EurekaServer将会注销该实例。当网络发生故障的时候,微服务就可能无法正常通信。Eureka通过自我保护来解决这个,在短时间内失去过多的客户端的时候,进入自我保护模式,一但进入该模式,就会保护服务列表,不再删除服务注册列表中的数据。当故障恢复以后,退出自我保护模式。
负载均衡器 LoadBalancer 客户端的负载均衡器,进程内部的负载均衡器。默认的策略是轮询,还有一个是随机。可以自定义策略。
使用方式,在RestTemplate对象上加入@LoadBalanced
随机策略 定义一个类(不能使用@Configuration注解),在此类当中增加一个@Bean注解的方法。返回RactorLoadbalancer接口的对象。
在配置类或者主启动类上使用@@LoadBalancerClients或者@LoadBalancerClient,指定上述定义的类为配置类
定义配置 注意:千万不要增加@Configuration注解
1 2 3 4 5 6 7 8 9 10 11 public class LoadBalancerConfig { @Bean ReactorLoadBalancer<ServiceInstance> randomLoadBalancer (Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer (loadBalancerClientFactory .getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }
启动类修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @SpringBootApplication @EnableEurekaClient @LoadBalancerClients(defaultConfiguration = LoadBalancerConfig.class) public class Base01OrderApplication { public static void main (String[] args) { SpringApplication.run(Base01OrderApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); } }
自定义负载均衡策略 需求:使用轮询方式访问服务器,每个服务器访问三次之后换下一个服务器
需要两个属性:1)用来记录当前的服务器被调用了几次
2)记录当前服务器是第几台服务器
如果当前的服务器已经被调用三次,换下一台服务器(i )
编写负载均衡策略类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class MyRRLoadBalancer implements ReactorServiceInstanceLoadBalancer { private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class); final AtomicInteger position; final String serviceId; ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; int count=0 ; int MAX=3 ; public MyRRLoadBalancer (String serviceId, ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) { this ((new Random ()).nextInt(1000 ),serviceId,serviceInstanceListSupplierProvider); } public MyRRLoadBalancer (int position, String serviceId, ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) { this .position = new AtomicInteger (position);; this .serviceId = serviceId; this .serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; } public Mono<Response<ServiceInstance>> choose (Request request) { ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this .serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new ); return supplier.get(request).next().map((serviceInstances) -> { return this .processInstanceResponse(supplier, serviceInstances); }); } private Response<ServiceInstance> processInstanceResponse (ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) { Response<ServiceInstance> serviceInstanceResponse = this .getInstanceResponse(serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response<ServiceInstance> getInstanceResponse (List<ServiceInstance> instances) { if (instances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + this .serviceId); } return new EmptyResponse (); } else if (instances.size() == 1 ) { return new DefaultResponse ((ServiceInstance)instances.get(0 )); } else { int pos; if (count<MAX) { pos = this .position.get(); } else { pos = this .position.incrementAndGet() & 2147483647 ; count=0 ; } ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size()); count++; return new DefaultResponse (instance); } } }
配置类修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class LoadBalancerConfig { @Bean ReactorLoadBalancer<ServiceInstance> randomLoadBalancer (Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new MyRRLoadBalancer (name,loadBalancerClientFactory .getLazyProvider(name, ServiceInstanceListSupplier.class) ); } }
Ribbon(旧) Ribbon是NetFlix发布的客户端负载均衡器,主要是用来控制HTTP和TCP客户端的行为。为Ribbon配置了服务提供者的地址列表后,Ribbon就可以基于某种负载均衡算法,自动地帮助服务消费者去请求对应的服务实例。Ribbon提供很多的负载均衡策略:轮询,随机,最少使用等。
Nginx和Ribbon的区别:
Nginx:是集中式的负载均衡设备(软件),Ribbon是进程内的负载均衡器,只是一个类库,集成在消费方的进程当中,消费方通过它来获取服务提供者的位置。
Nignx是服务器端负载均衡器,客户端的请求都是交给Nginx,然后由Nginx进行转发。
Ribbon:在调用微服务接口的时候,会在注册中心上获取注册的服务列表,缓存到本地。
如何负载均衡策略 1 2 3 4 5 6 7 8 9 @Configuration public class AppConfig { @Bean public IRule iRule () { return new RandomRule (); } }
自带的负载均衡策略 RoundRobinRule:轮询,尝试超过10次以后,直接不提供服务。
RandomRule: 随机策略
Retry:先按照轮询的策略获取服务,如果服务失败,则在指定的时间内进行重试,获取可用的服务
WeightedResponseTimeRule:是对轮询策略的扩展,每30秒钟计算一次服务器的响应时间,以响应时间作为权重,响应时间越短,响应速度越快的服务器被选中的概率越大。
BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,在可用列表中选择一个并发量最小的服务实例。
AvailabilityFilteringRule::先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,再选择一个相对并发量较小的实例。
ZoneAvoidanceRule:根据服务提供者实例的所在区域以及响应的可用性选择服务器。
自定义负载均衡策略 需求:使用轮询方式访问服务器,每个服务器访问三次之后换下一个服务器
需要两个属性:1)用来记录当前的服务器被调用了几次
2)记录当前服务器是第几台服务器
如果当前的服务器已经被调用三次,换下一台服务器(i )
定义策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class CustomizeRule extends AbstractLoadBalancerRule { private int total=0 ; private int currentIndex = 0 ; @Override public void initWithNiwsConfig (IClientConfig iClientConfig) { } @Override public Server choose (Object o) { return choose(getLoadBalancer(),o); } public Server choose (ILoadBalancer lb,Object key) { if (lb == null ) { return null ; } Server server = null ; while (server==null ){ List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if (upCount==0 ) { return null ; } if (total < 3 ) { server=reachableServers.get(currentIndex); if (server==null ) { Thread.yield (); continue ; } total++; } else { currentIndex = (currentIndex + 1 ) % upCount; server = reachableServers.get(currentIndex); if (server==null ) { Thread.yield (); continue ; } total =1 ; } } return server; } }
配置 1 2 3 4 5 6 7 8 9 @Configuration public class AppConfig { @Bean public IRule iRule () { return new CustomizeRule (); } }
OpenFeign(Http服务调用) OpenFeign是NetFlix开发的声明式、模板化的HTTP客户端,用于HTTP请求调用的轻量级的框架,以Java接口注解的方式调用HTTP请求。OpenFeign支持SpringMVC注解,可以和Eureka,nacos等整合一起使用。
使用方法比较简单,主要是创建一个接口。接口上增加openfeign注解,并通过启动类进行注解的启用。通过注解,将请求模板化,根据参数对应到请求上。
使用步骤
导入依赖
在消费者端编写Openfeign的客户端(接口)
导入依赖 增加以下依赖
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
编写接口 使用@FeignClient注解
1 2 3 4 5 6 7 8 9 @Service @FeignClient(name="bookapp") public interface UserServiceFeign { @RequestMapping(value = "book/{isbn}") Book findByIsbn (@PathVariable("isbn") String isbn) ; }
修改控制器调用接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController public class UserController { @Resource UserServiceFeign userServiceFeign; @GetMapping("borrow/{isbn}") public Book searchBook(@PathVariable("isbn") String isbn) { return userServiceFeign.findByIsbn(isbn); } }
修改主启动类 增加@EnableFeignClient注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class Base01ConsumerApplication { public static void main (String[] args) { SpringApplication.run(Base01ConsumerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); } }
特殊属性说明 fallback和fallbackFactory,主要用于熔断机制,调用失败时,走的回退方法,可以用来抛出异常或者给出默认的数据。
decode404:配置响应状态为404时,是否抛出FeignException
调用的原理 OpenFeign基于JDK的动态代理。
@EnableFeignClients:加上该注解,Springboot启动的时候,会导入FeignClientsRegistrar,扫描所有带有@FeignClient注解的接口
解析到@FeignClient的配置属性后,扩展Spring Bean Definition的注册逻辑上面,最终注册一个FeignClientFactoryBean ,此对象会产生一个代理类对象。
设置超时时间 可以参考在FeignClientProperties中的数据,主要是其内部类FeignClientConfiguration
1 2 3 4 5 6 feign: client: config: GOODS: connectTimeout: 1000 readTimeout: 1000
Hystrix(容错保护、断路器) 背景 在微服务的架构当中,原本一个大的服务会拆分成多个小服务单元,服务单元之间无法避免会有相互的依赖关系。由于这种依赖关系,当某一个服务单元出现故障,容易引起故障的蔓延,最终有可能导致整个系统的瘫痪。
雪崩效应:当某一个服务单元出现故障,容易引起故障的蔓延,顺着调用链向上传递,最终有可能导致整个系统的瘫痪的现象。
产生场景
硬件故障:服务器宕机,机房断电,光纤被挖断…
流量激增:异常流量激增,重试也会增加流量
缓存问题:由于缓存的问题,导致服务提供者的负荷增加了,引起服务的不可用。
程序BUG: 程序逻辑错误导致内存泄漏,JVM长时间进行FullGC。
同步等待:服务间采用同步调用机制,同步等待导致资源的耗尽。
Hystrix的目标:在于通过控制哪些远程访问、服务以及第三方的节点,从而对延迟或者故障提供更强大的容错能力。
Hystrix是干什么的 NetFlix公司开源的,用于分布式系统的延迟和容错处理的开源库。用于隔离远程访问、服务以及第三方的库,防止级联失败,从而提升系统的可用性以及容错性。
CAP: C: 一致性。分布式集群中节点(broker)上的数据要保持一致。
A:可用性,要求服务端能够在指定的时间快速响应用户。
P: 分区容错性,当集群或者分布式系统中的某一个节点(服务)出现问题后,整个集群或分布式系统的使用不能收到影响。
要么是CP,要么AP
服务降级 : 假设系统比较忙或者不可用的情况下,给一个友好提示或者默认处理。触发降级的场合:程序运行异常、超时、服务熔断触发服务降级,线程池当中并发量达到阈值也可能导致服务降级。
服务熔断 :达到最大服务访问量以后,直接拒绝访问,然后调用服务降级的方法给出友好提示。
服务限流 :秒杀,抢红包等一系列高并发操作,严控一窝蜂的过来拥挤,让大家排队有序进行。
RestTemplate方法 依赖
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency >
service中的方法
降级方法的参数和返回值要和原来方法一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Service public class UserService { String host = "http://BOOKAPP" ; @Resource RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "fallback") public Book searchBook (String isbn) { System.out.println("用户查找图书" ); String url = host + "/book/" + isbn; Book book = restTemplate.getForObject(url, Book.class); return book; } public String getServer () { String url = host + "/server" ; String server = restTemplate.getForObject(url, String.class); return server; } public Book fallback (String isbn) { return new Book ("XXXX" ,"服务器出现异常" ,"" ,0.0 ); } }
启动类
增加@EnableHystrix或者@EnableCircuitBreaker注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableHystrix public class Base01ConsumerApplication { public static void main (String[] args) { SpringApplication.run(Base01ConsumerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); } }
统一处理 在Service类上使用@DefaultProperties注解,指定默认的服务降级的方法。
全局降级的方法,不能带有参数。
需要降级处理的方法上,不指定降级目标方法(回退方法),但是@HystrixCommand注解需要保留
OpenFeign方式 开启Hystrix 1 2 3 4 5 feign: httpclient: connection-timeout: 2000 hystrix: enabled: true
fallback属性 使用@FeignClient的fallback属性,设置成指定的类
处理降级的类,需要实现对应的接口
1 2 3 4 5 6 7 @Component public class UserServiceFeignException implements UserServiceFeign { @Override public Book findByIsbn (String isbn) { return new Book ("110" ,"我是服务器,现在挂机中" ,"" ,0.0 ); } }
fallbackFactory属性 使用@FeignClient的fallbackFactory属性,设置成指定的类
处理降级的类,实现FallbackFactory接口
1 2 3 4 5 6 7 8 9 10 11 12 @Component public class UserServiceFeignFactory implements FallbackFactory <UserServiceFeign> { @Override public UserServiceFeign create (Throwable throwable) { return new UserServiceFeign () { @Override public Book findByIsbn (String isbn) { return new Book ("666" ,"光纤被挖断了" ,"" ,0.0 ); } }; } }
熔断演示 HystrixCommandProperties:普通参数 HystrixThreadPoolProperties:和线程池相关参数
看板(仪表盘) 仪表盘项目 创建一个web项目,要把web依赖去掉,增加hystrix-dashboard的依赖
配置项目增加hystrix.dashboard.proxy-stream-allow-list=*
在主启动类上要增@EnableHystrixDash注解
配置HystrixMetricsStreamServlet (可以使用配置文件,也可以在启动类当中注册)
被监控项目 增加两个依赖
hystrix-dashboard
actuator依赖
配置项目
1 2 3 4 5 management: endpoints: web: exposure: include: hystrix.stream
启动项目测试
启动dashboard,输入localhost:端口号/hystrix
启动被监控项目,在前面的页面窗口,输入 localhost:被监控项目端口号/actuator/hystrix.stream
Resilience4J 依赖 增加springboot-aop以及actuator依赖
1 2 3 4 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-circuitbreaker-resilience4j</artifactId> </dependency>
配置服务 可用的配置项目:CircuitBreakerConfig当中,可以去参考
使用断路器 网关Gateway Gateway是Spring Cloud的子项目,Spring2.X提供的,Spring1.X用的是zuul(已经停更,进入维护期),提供简单有效的API路由管理方式。
Gateway作为zuul的替代品,是Springcloud生态中的网管。是基于WebFlux,高效能的Reactor模式。
Gateway的特点:
支持动态路由:能够匹配路由的任何请求属性
集成Spring Cloud的服务发现功能
支持限流功能
支持路径重写
提供断言(Predicate)以及过滤器(Filter),可以设置路由的一些条件
功能 服务网关:路由转发 + 过滤器
路由转发:接收客户端的请求,将请求转发到指定的微服务上。
过滤器:可以帮助网关实现一些类似于AOP可以完成的一些操作,认证,服务的监控,限流。
案例: 有四个微服务,每个微服务都需要权限的认证
方案一:每个微服务都实现一下权限认证的代码===>基本不会使用
方案二:将认证服务写成一个公共的服务,每个业务相关的微服务都来调用公共的服务。
方案三:将认证服务写到网关的过滤器
核心概念 路由(Route):路由是构建网关的基本模块。它由ID,目标URI,一系列的断言和过滤器组成。
断言(Predicate):开发人员可以通过断言的相关设置,匹配HTTP请求中的参数内容,设置访问路由的条件
过滤器(Filter):通过过滤器,可以在路由前后进行一些修改
如何编写网关 创建一个springcloud项目
增加网关依赖,eureka客户端
配置相应的网关
动态路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 spring: application: name: base03-gateway cloud: gateway: routes: - id: gt-bookapp #id值需要位置 # uri: http://localhost:8070 uri: lb://bookapp #lb为固定值,表示负载均衡,bookapp为服务名 predicates: - Path=/** discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 server: port: 10000 #作为eureka的客户端的配置 eureka: client: service-url: defaultZone: http://localhost:7100/eureka register-with-eureka: true fetch-registry: true
断言 断言(Predicates)是一组匹配规则,请求只有和规则相匹配时才可以访问
-Path : 匹配路径
-After : - After=时间 (在某个时间之后可以访问)由于是ZoneDateTime, 时间需要带有时区
- After=2021-11-24T11:35:57.557+08:00[Asia/Shanghai]
Before: - Before=时间 (在某个时间之前可以访问)
-Between: - Before=时间1, 时间2
-Cookie, phone为key,15911111111
-Header: 表示请求头当中,需要包含某些内容,请求才可以访问
-Header=authenticator, 1111
-Method: 匹配请求方式,如 -Method=POST,GET
-Query:匹配请求的参数 -Query=price,\d+ : 请求当中需要携带price参数,且值必须数字才可以访问
过滤器 Spring cloud通过过滤器在请求的前后进行一部分分更新
抽象类AbstractGatewayFilterFactory的子类对象,配置的时候,去掉GatewayFilterFactory后缀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 spring: application: name: base03-gateway cloud: gateway: routes: - id: gt-bookapp uri: lb://bookapp predicates: - Path=/book/** - After=2021-11-24T11:35:57.557+08:00[Asia/Shanghai] filters: - AddRequestHeader=username,xiaoming - RedirectTo=302,http://www.baidu.com discovery: locator: enabled: true
自定义全局过滤器 实现GlobalFilter接口,对所有的路由均有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class MyGlobalFilter implements GlobalFilter { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { String user = exchange.getRequest().getQueryParams().getFirst("user" ); if (user == null ) { System.out.println("===用户参数user没有设置" ); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); exchange.getResponse().setComplete(); } return chain.filter(exchange); } }
局部过滤器 实现AbstractGatewayFilterFactory,要以GatewayFilterFactory作为类的后缀名
在指定路由的filters下定义对应的过滤器即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class MyTestGatewayFilterFactory extends AbstractGatewayFilterFactory { @Override public GatewayFilter apply (Object config) { return new GatewayFilter () { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("=========局部过滤器=====================" ); return chain.filter(exchange); } }; } }
配置过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: application: name: base03-gateway cloud: gateway: routes: - id: gt-bookapp uri: lb://bookapp predicates: - Path=/book/** - After=2021-11-24T11:35:57.557+08:00[Asia/Shanghai] filters: - MyTest discovery: locator: enabled: true
nacos Naming Configuration Service: 注册中心 + 配置中心 + 配置总线的组合组件
中文官网:https://nacos.io/zh-cn/index.html
英文spring: spring.io
下载:https://github.com/alibaba/nacos
使用nacos,不需要单独在编写一个nacos服务器端,已经提供。nacos是基于java代码实现。阿里出品。
注册中心 依赖 1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > <version > 2021.0.4.0</version > </dependency >
注册配置 1 2 3 4 5 6 7 8 9 10 11 server: port: 8082 spring: application: name: GOODS cloud: nacos: discovery: server-addr: http://localhost:8848 namespace: java180 group: dev
主启动类注解 1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class Base04GoodsApplication { public static void main (String[] args) { SpringApplication.run(Base04GoodsApplication.class, args); } }
配置中心 步骤 增加依赖
配置
在主启动类增加@EnableDiscoveryClient注解
读取属性的类上,使用@RefreshScope来进行动态属性的拉取
依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > <version > 2021.0.4.0</version > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > <version > 2021.0.4.0</version > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bootstrap</artifactId > <version > 3.0.3</version > </dependency >
配置文件 配置bootstrap.yml bootstrap.yml :会在application.yml读取之前先读,其中的内容是不会被覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring: cloud: nacos: discovery: server-addr: http://localhost:8848 namespace: java180 group: dev service: configinfo config: server-addr: http://localhost:8848 namespace: java180 group: dev file-extension: yaml application: name: configinfo
配置application.yml 1 2 3 spring: profiles: active: test
nacos配置中心 文件名的命名规则 在nacos配置中心设置配置文件时,文件的dataId由三个部分组成,prefix,profile(dev,test,prod),file-extension(yaml或者properties,根据选择的文件类型来决定)
prefix-profile.file-extension
prefix: 默认为spring.application.name的值(例:项目:nacos-config),也可以通过配置项spring.cloud.nacos.config.prefix
profile: spring.profiles.active对应的环境,如果没有设置多环境,则文件名 prefix.file-extension
file-extension: 目前只支持properties和yaml
namespace:项目隔离的作用
配置案例configinfo-dev.yaml 1 2 3 4 5 6 server: port: 10086 user: name: zhangsan age: 10 scholl: 五老村小学
属性获取类 1 2 3 4 5 6 7 8 9 10 @Component @RefreshScope @ConfigurationProperties(prefix = "user") @Data public class User { String name; int age; String school; }
主启动类 1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication @EnableDiscoveryClient public class Base05ConfiginfoApplication { public static void main (String[] args) { ConfigurableApplicationContext ctx = SpringApplication.run(Base05ConfiginfoApplication.class, args); User bean = ctx.getBean(User.class); System.out.println(bean); } }
sentinel Sentinel是alibaba提供的用于实时监控、流量控制、异常熔断等管理工具,它可以于nacos进行组合使用,可以对项目进行图形化的配置和管理。
运行启动sentinel,可以通过–server.port指定端口号
1 java -jar sentinel-dashboard-1.8.2.jar --server.port=8081
依赖 流量控制 监控应用流量的QPS(每秒请求次数)或者并发线程数,当达到指定阈值的时候进行流量控制,以避免被瞬间的流量高峰击垮,从而保证应用的高可用性。
资源源:唯一,默认是请求的路径
针对来源:sentinel可以对调用者进行限流,默认是default(不区分来源),想要区分来源的情况,填写来源的服务名
阈值类型:
QPS: 每秒请求数量,达到此阈值的时候会限流
并发线程数
流控模式:
直接:直接限流
关联:如果请求m1关联m2, 如果m2达到阈值,则限流m1
链路:需要设置入口资源,对整个链路进行流量限制
流控效果:
快速失败:超出阈值后,直接抛出异常,请求失败
warm up:预热/冷启动模式,经过一定的市场(n秒),从codeFactor(=3)阈值慢慢达到指定的阈值
排队等待:当请求超出阈值之后,需要进行排队等待,等待的时间可以进行设置。
1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("test") @SentinelResource(value = "test",fallback = "fallbackMethod" ,fallbackClass = InfoFallBackComponent.class) public String test () { System.out.println("test: " + LocalDateTime.now()); return "game over" ; }
熔断降级 Sentinel熔断降级主要是适用某个资源请求处理不稳定的情况下,对此资源进行调用限制。
不稳定的因素:调用时间比较常,异常出现的频率高
统计1秒种(1000ms)时间内,如果请求的次数达到2次以上(最小请求数),慢调用(请求的时间超过100猫喵)的比例,达到0.5的情况,就会熔断20秒。
热点key设置 调用后端接口的参数,根据方法上来,0为第一个参数,1为第二个参数。
资源名:可以是请求的url,也可以是@SentinelResource的value值。
blockHandler对应的方法,除了参数以及返回值之外,还需要增加一个BlockException参数
sentinel和openfeign进行整合,如何进行服务降级处理。
1 2 3 4 5 6 7 8 @GetMapping("/testHotkey") @SentinelResource(value = "/testHotkey",blockHandler = "blockHandler") public String testHotKey (String p1,String p2) { return "success" ; } public String blockHandler (String p1, String p2, BlockException ex) { return "blockHandler" ; }