1.注册中心Eureka
⦁ (1)0renew 续租,客户端默认使用心跳(30s)来续租,通知eureka实例仍然活着,否则会被eureka踢掉
Fetch Registry 客户端从服务端拉取注册表缓存到本地内存,之后进行增量或者动态调整表
(2)Eureka自我保护机制
正常情况下,一定时间内,没有接受到某个服务的心跳,则会注销该服务,但如果网络分区导致的通信异常,则服务注销非常危险
为防止网络故障导致的服务注销,Eureka server在短时间内丢失过多客户端时,server会进入自我保护模式,注册表中的微服务不被注销,网络恢复后,退出自我保护
自我保护触发:客户端每分钟续约数量小于客户端总数的85%时会触发保护机制,eg:实例10个,每分钟续约102-20,阈值200.85=17 少于17则开启
服务提供方使用eureka client必须依赖springbootstarter-web,否则无法注册
当续租数量少于阈值时,则出现红色大写警告
#自我保护机制关闭,则达不到续租阈值时就会剔除服务,dev一般关,生产一般开
enable-self-preservation: false
(3)
使用进行actuator springboot健康检查 官网
https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#health
eureka-rest-opretions api
对服务做以下配置
eureka:
client:
healthcheck:
enabled: true
<dependency>
<!--服务的健康检测-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
访问http://127.0.0.1:6061/actuator/ 具体内容见官网
(4)
eureka提供标准的RESTFUL风格的HTTP协议接口,只要使用该接口,不管什么语言的应用都能够将自己注册进去,同时也能够调用接口查看eureka中的服务信息
https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
ps:服务手动上下线:
eureka定义的服务状态
UP,DOWN ,STARTING, OUT_OF_SERVICE,UNKNOWN
查看文档 发送http put请求即可:
http://127.0.0.1:7900/eureka/apps/SERVICE-VERIFICATION-CODE/SF01398017M01.sf.com:service-verification-code:8012/status?value=UP/DOWN
(1).注册中心做两件事:
机器A会把自己的ip:port注册到服务中心,此外,A还是下载服务注册表,A才能知 道自己要调用的服务在哪台机器;
(2)Eureka client启动后,会周期性向注册中心发送心跳检验,默认30s,Eureka Server在几个周期内没收到的话,就会将服务从列表中移除;
(3)单机版的Eureka server则一般会将server的自我注册去掉;同时也不需要从注册中心获取服务列表;
#是否将自己也注册到注册中心,单机版Eureka Server不需要
eureka.client.register-with-eureka=false
#是否从服务中心获得注册列表
eureka.client.fetch-registry=false
(4)Eureka架构图
(5)springcloud中对服务管理的最大粒度就是一个单体应用
register表示注册到注册中心;
renew,即客户端向注册中心发心跳包,防止注册中心将自己移除
cancel,服务下线,主动让注册中心将自己移除,防止消费者调用到空的服务;
get Regitry,获得注册列表
(6)参考:https://blog.csdn.net/zhuyanlin09/article/details/89598245
2.springcloud 一定要注意与springboot的版本对应关系
亲测 Fincley 与2.0.2匹配是ok的,其他的版本试了好多都不行,会出现各种问题
参考:https://blog.csdn.net/heshengfu1211/article/details/97620767
3.jar包:spring-cloud-starter-netflix-eureka-server和spring-cloud-netflix-eureka-server区别在于:
(1)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
(2)
<--springcloud中的启动器类-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
</dependency>
(1)中的jar包相当于(2)中两个之和,maven使用(1)将(2)中的两个jar进行了合并,类似于@RestController = @Controller + @ResponseBody;
在添加jar包时要注意不能漏,也不能多,否则会报错;比如选择(2)中的服务端时,没有加上启动类jar,则报错:
@RestController = @Controller + @ResponseBody
4.springcloud调用服务的两种方式:
(1)Ribbon+RestTemplate
i.Ribbon是做负载均衡,restTemplate请求
@LoadBalanced+RestTemplate 原理就是将RibbonLoadBalancerClient与restTemplate进行了合并
//启动类上加resttemplate的bean
//调用服务的方式之一:使用RestTemplate和Ribbon进行服务调用
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
//在controller里取调用服务
//做服务调用,添加依赖注入
@Autowired
RestTemplate restTemplate;
@RequestMapping("/gs")
public String getService(){
//这里通过http请求服务/路径
return restTemplate.getForObject("http://transfer-service/transferController/transfer/10000",String.class);
}
ii. ribbon负载均衡
⦁ 服务端负载均衡: 在client和server之间加代理,如nginx
⦁ 客户端负载:需要客户端知道所有ip,ribbon属于客户端代理,consumer得注册中心知道所有服务的地址,然后选择一个
⦁iii. ribbon的负载均衡算法:
⦁ 负载均衡策略抽象类IRule,各种策略如下
⦁ ZoneAvoidanceRule:默认,复合判断server所在的zone的性能和可用性,根据是否可用以及server的负载,综合选出一个server
⦁ BestAvailableRule:最低并发策略,先过滤访问故障的服务,然后返回一个最低并发量的server随机列表供选择
WeightedResponseTimeRule: 响应时间加权策略,给 每个server一个基于平均响应时间的动态权重,
eg:A(wt=10), B(wt=30), C(wt=40),
* D(wt=20). 则每次产生随机数Random,若在1-10 则发送给A,11-40则B,41-80则C,81-100则D,是一种加权的概率运算
当数据不够时,使用RoundRobinRule轮训策略
roundRobinRule(轮询策略):以简单轮询选择一个服务器。按顺序循环选择一个server。
RandomRule(随机策略):随机选择一个服务器。
iv.配置负载均衡策略(在consumer中):
(1)代码配置
@Bean
public IRule roundRobinRule(){
return new RoundRobinRule();
}
(2) consuemr的配置文件配置,此配置编译器无法识别
#配置负载均衡策略,大于代码中的配置,
service-provider: #service-privider是具体服务的id
ribbon:
NFLoadBalancerRuleClassName:
com.netflix.loadbalancer.RoundRobinRule
坑: 如果一个服务中连续调用两个微服务,这种情况下,由于一个微服务是一个LB,在不重写IRUle时,每个LB默认持有一个rule对象,而重写时则两个微服务LB会公用一个rule对象,此时调用第两个微服务之后,第三次再调用第一个微服务时,由于上次rule对象持有的lb是第二个的,就会报404,解决办法是spring的rule的bean改成多例,而非单例
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public IRule rule(){
return new RoundRobinRule();
}
坑的源码:
baseloadbalancer
public void setRule(IRule rule) {
if (rule != null) {
this.rule = rule;
} else {
/* default rule */
this.rule = new RoundRobinRule();
}
if (this.rule.getLoadBalancer() != this) {
this.rule.setLoadBalancer(this);
}
}
V.ribbon配置连接超时和读取超时(服务业务逻辑超时)
VI.restemplate的常用api使用
#配置连接超时和读取超时时间(业务处理超时时间)
ribbon:
ConnectTimeout:
1000
ReadTimeout:
4000
#同一台机器最大重试次数,不包括首次
MaxAutoRetries:
2
#当同一台机器重试MaxAutoRetries次数后还是不行,仍要负载几次到下一台机器上,eg:1 表示再负载一次
MaxAutoRetriesNextServer:
1
#是否所有的操作都重试,否,则post请求不重试,
#重试要小心幂等性,post表单提交一般不配置重试, 而是熔断
# 默认false,设置为true,则所有超时(connect和read)都重试,有幂等性风险,
#设置为false时,非get请求则只有连接超时时重试,readtimeout不重试,避免幂等风险
#true的话只有get请求无风险(因为都是读取操作)
#源码在FeignLoadBalancer中的getRequestSpecificRetryHandler方法
#经测试 如果将@RequestMapping默认发出的是get请求,而如果明确@PostMapping时,就不会重试
OkToRetryOnAllOperations:
false
String url = "http://service-provider/test/person";
//get请求
//这里通过http请求服务/路
//getForEntity方法的返回值是一个ResponseEntity,ResponseEntity是Spring对HTTP请求响应的封装,
// 包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。
System.out.println("----getForEntity----");
System.out.println(restTemplate.getForEntity(url, Person.class).getBody());
//直接取出body中的对象
System.out.println("---------getForObject---------");
System.out.println(restTemplate.getForObject(url,Person.class));
System.out.println("---------get请求传参占位符---------");
String url1 = "http://service-provider/test/person1?name={1}";
System.out.println(restTemplate.getForObject(url1,Person.class,"占位符"));
System.out.println("---------get请求传参map---------");
String url2 = "http://service-provider/test/person1/{name}";
System.out.println(restTemplate.getForEntity(url2,Person.class, Collections.singletonMap("name","map传参")).getBody());
//post请求
String url3 = "http://service-provider/test/person2";
System.out.println("---------post请求提交---------");
//设置请求头则可以使用exchange方法或者增加拦截器
System.out.println(restTemplate.postForEntity(url3,new Person("postTest",4),Person.class).getBody());
System.out.println("----------exchange提交,可以设置header等--------");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.ACCEPT_LANGUAGE,"cn");
HttpEntity<Person> httpEntity = new HttpEntity<>(new Person("testExchange",4),httpHeaders);
System.out.println(restTemplate.exchange(url3, HttpMethod.POST,httpEntity,Person.class).getBody());
private static final int TIME_OUT = 5000;
ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
log.info("restTemplate --> URI:{},METHOD:{},BODY:{}", request.getURI(), request.getMethod(), new String(body, StandardCharsets.UTF_8));
ClientHttpResponse response = execution.execute(request, body);
recordResponse(response);
return response;
};
// 基本的
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory httpClientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate(httpClientHttpRequestFactory);
restTemplate.setInterceptors(Lists.newArrayList(interceptor));
return restTemplate;
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
BufferingClientHttpRequestFactory simpleBufferingClientHttpRequest =
new BufferingClientHttpRequestFactory(clientHttpRequestFactory);
clientHttpRequestFactory.setConnectTimeout(TIME_OUT);
clientHttpRequestFactory.setReadTimeout(TIME_OUT);
return simpleBufferingClientHttpRequest;
}
private void recordResponse(ClientHttpResponse response) {
InputStream content;
try {
content = response.getBody();
byte[] bytes = new byte[content.available()];
content.read(bytes);
log.info("restTemplate <-- {}", new String(bytes, StandardCharsets.UTF_8));
} catch (IOException e) {
log.error("resttemplate 响应异常", e);
}
}
ps: resttemplate上传文件:
上传文件流
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(imgBytes)){
@Override
public String getFilename() {
return "temp.jpg";
}
@Override
public long contentLength() throws IOException {
return imgBytes.length;
}
};
如果是文件File而不inputstream流则使用FileResource:
//FileSystemResource resource = new FileSystemResource(new File(""));
MultiValueMap<String,Object> params = new LinkedMultiValueMap<>();
params.add(WECHAT_KEY_MEDIA.getKey(),fileSystemResource);
MultiValueMap<String,Object> params = new LinkedMultiValueMap<>();
params.add(WECHAT_KEY_MEDIA.getKey(),resource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity< MultiValueMap<String,Object> > httpEntity = new HttpEntity<>(params,headers);
ResponseEntity<String> forEntity = restTemplate.postForEntity(url, httpEntity,String.class);
UploadThumbImgeResDTO result = getRestResult(forEntity,UploadThumbImgeResDTO.class);
(2)Feign调用好处在于一个服务的调用以接口的形式固定下来后能够方便的进行多次调用,而ribbon每次调用都要写http请求
i.使用过程
pom.xml增加依赖
<!--此处使用Feign实现服务请求-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
新增接口:
@FeignClient(value="transfer-service")
public interface IFeignService {
//提供者中的路径写在服务接口上
@RequestMapping("/transferController/transfer/{money}")
String transfer(@PathVariable String money);
}
controller中直接以接口的形式调用
@RequestMapping("/feign")
public String getFeignService(){
return iFeignService.transfer("10000");
}
ps:所有的降级 重试 负载等机制都是在消费端完成,服务端只提供服务
ii.Feign封装了HTTP调用过程,同时不用写大量的http请求,更适合面向接口的编程习惯
iii Feign的调用逻辑:
参考:https://blog.csdn.net/luanlouis/article/details/82821294
5.hystrix断路器防止级联异常
级联异常:系统局部性异常导致整体性异常
本质是做隔离设置,如果一个底层服务出现问题,故障会被向上传播给调用服务,断路器的存在可以阻断故障传播
(1)场景
i.hystrix降级:返回友好提示,同时做其他操作,比如发mq,或者发邮件等,或者返回给页面一些非最新的数据,好歹还能用
⦁ 为系统提供保护机制。在依赖的服务出现高延迟或失败时,为系统提供保护和控制。
⦁ 防止雪崩
⦁ 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中运行。
⦁ 跳闸机制:当某服务失败率达到一定的阈值时,Hystrix可以自动跳闸,停止请求该服务一段时间。
⦁ 资源隔离:Hystrix为每个请求都的依赖都维护了一个小型线程池,如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。防止级联失败。
⦁ 快速失败:Fail Fast。同时能快速恢复。侧重点是:(不去真正的请求服务,发生异常再返回),而是直接失败。
⦁ 监控:Hystrix可以实时监控运行指标和配置的变化,提供近实时的监控、报警、运维控制。
⦁ 回退机制:fallback,当请求失败、超时、被拒绝,或当断路器被打开时,执行回退逻辑。回退逻辑我们自定义,提供优雅的服务降级。
⦁ 自我修复:断路器打开一段时间后,会自动进入“半开”状态,可以进行打开,关闭,半开状态的转换
(2)使用:
i.与restempate结合使用
pom.xml:
与feign结合使用时不需要加
<!--断路器hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
ribbon使用断路器: 只需要在调用方法上添加一个fallback即可
消费端启动类:@EnableCircuitBreaker
@RequestMapping("/restHy")
//fallbackMethod的方法应该在同一个类中
@HystrixCommand(fallbackMethod = "back")
public String restHy() {
return restTemplate.getForObject("http://service-provider/test/", String.class);
}
private String back(){
return "back--error";
}
ii与feign结合使用
#使用Hystrix降级时,所有的ribbon重试都会失效,只调用一次
feign:
hystrix:
enabled: true
@FeignClient(name = "service-provider",fallback = ProviderHystrix.class)
public interface UserService extends UserApi { //userApi中类上原本的@REquestMapping要去掉,不知道为什么
}
@Component
public class ProviderHystrix implements UserService {
@Override
public Person getPerson() {
return new Person("降级结果",12);
}
fallbackFactory相比较fallback能够捕获到具体的异常Throwable,降级时能够根据不同的异常提供更细粒度的降级
@Component
public class ProviderHystrixFactory implements FallbackFactory<UserService> {
@Override
public UserService create(Throwable throwable) {
return new ProviderHystrixCause(throwable);
}
}
@FeignClient(name = "service-provider",fallbackFactory = ProviderHystrixFactory.class)
public interface UserService extends UserApi {
}
(3) Hystrix的底层原理是spring的AOP,对Annotation进行了切面
参考:https://blog.csdn.net/turbo_zone/article/details/83831992
HystrixCommandAspect.java中定义的aspect切面
(4)Hystrix信号量隔离(使用tomcat线程池)和线程池隔离
默认是hystrix使用线程池控制隔离
i.服务调用包括:本地线程调用和远程线程调用,进行远程线程调用时可选择是否使用tomcat的worker线程池,若使用则是信号量隔离,否则使用的是线程池隔离,防止被服务出问题,导致整个线程耗尽
ii.信号量策略:用于隔离本地代码或者可快速返回(redis/计算密集等)的远程调用,可降低线程池中线程上下文切换的开销,主要适用于并发需求不大的依赖调用,并发大的话会占用过多tomcat线程,以为远程调用使用的是tomcat的worker线程
iii.线程池隔离:将远程调用的线程与本地调用线程(tomcat)分开,配置每个服务的线程池数量,避免都使用tomcat的woker线程,因为远程问题导致 tomcat线程耗尽,将影响缩小到线程池范围内
通过为每个包裹了 HystrixCommand 的 API 接口设置独立的、固定大小的线程池(hystrix.threadpool.default.coreSize)来控制并发访问量,当线程饱和的时候可以拒绝服务,防止依赖问题扩散。
配置:
hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore
thread 通过线程数量来限制并发请求数,可以提供额外的保护,但有一定的延迟。一般用于网络调用
semaphore 通过semaphore count来限制并发请求数,适用于无网络的高并发请求
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms
hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
semaphore应该占整个容器(tomcat)的线程池的一小部分。
iv.测试线程隔离策略
在fallback的降级方法中打印线程名称,可观察到如果是信号量 则为http-nio-exe-n ,否则为HystrixTimer-3;线程池策略中,配置coresize为8,并发9时则在生产端可看到只有8次打印,其余3次并发 直接在consumer端快速失败
6 sprigncloud-gateway做网关:
(1)网关功能:
是所有微服务入口,进行分发。
身份认证与安全。识别合法的请求,拦截不合法的请求。(过滤器)
监控。在入口处监控,更全面。
动态路由。动态将请求分发到不同的后端集群。
压力测试。可以逐渐增加对后端服务的流量,进行测试。(限流)
负载均衡。也是用ribbon。
限流(望京超市)。比如我每秒只要1000次,10001次就不让访问了。
服务熔断
(2)gateway的搭建
pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
配置日志
logging:
level:
org.springframework.cloud.gateway: DEBUG
reactor.netty.http.client: DEBUG
(3)路由 、断言、过滤器是网关的三个部分
(4)过滤器和各种断言的配置
参考官网:
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
全局过滤器的使用:
全局自定义过滤器,使用ordered来决定其与配置文件中gatewayFilter的顺序
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//当ordered为highest时,这里会NPE,原因是gwatewayfillter还没设置值
//lowest时则可打印
exchange.getRequest().getHeaders().get("name").get(0);
System.out.println("custom global filter");
if(true){
return chain.filter(exchange);
}else{
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().wrap(JSONObject.fromObject(result).toString().getBytes());
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().writeWith(Mono.just(dataBuffer));
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
(5)使用gateway做限流的配置
自定义限流的大体原理
gateway 默认使用RequestRateLimiter 做限流, redis的lua脚本实现
pom:
<!--限流用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
自定义限流的条件key:
@Configuration
public class GatewayCfg {
@Bean
public KeyResolver hostAddrKeyResolver() {
//获取请求用户的ip作为限流key
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
// 请求地址作为限流 Mono.just(exchange.getRequest().getPath().value());
}
//按照请求路径作为key,每个请求可以路径每秒最大20个并发,而不是一个IP限制20并发
@Bean(name = "PathKeyResolver")
public KeyResolver hostAddrKeyResolver() {
//这样相当于每个接口作为key进行限流
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
}
配置redis:
spring:
application:
name: gateway
#限流使用redis
redis:
host: 127.0.0.1
password: sf123456 #Scada2021#0312
port: 6379
其他限流配置见下面
(6)各种cloud gateway的匹配规则,过滤器和限流的配置:
使用: gateway 端口9999 目标服务端口9090,访问:http:127.0.0.1:9999/test/1
按照下面的匹配规则,会匹配到之后然后转发访问http://127.0.0.1:9090/test/1
spring:
cloud:
gateway:
default-filters:
#默认过滤器
- AddResponseHeader=X-Response-Default-Red, Default-Blue
routes:
- id: path_route
# uri: lb://api-passenger 网关后接多个服务,可以以lb+服务名,则会轮训调用
uri: http://127.0.0.1:9090 #uri是谓词匹配上之后的目标路径
谓词匹配路由规则
predicates:
#**和*不一样,*表示test后面的路径只有一个,而**则表示任意个数的路径
- Path=/test/**
#匹配参数所述Query路线谓词工厂采用两个参数:所要求的param和可选的regexp(其是Java正则表达式)
- Query=name,^zs.*
#头报头name和一个regexp(其是Java正则表达式),name必须一样,value则必须匹配regexp
# - Header=client,^zsx.*
#Cookie与Header类似
# - Cookie=userName,zsx
#每一套路由规则 能够添加自己的过滤器
过滤器对匹配到的请求进行过滤
filters:
#常用内置的过滤器工厂
#(1)添加请求头
- AddRequestHeader=name,zsx
#添加请求参数,如果已经有该参数,则过滤器中的值逗号隔开后缀,eg:name=qwe过滤后 name=qwe,zsx
- AddRequestParameter=name,zsx
#响应头
- AddResponseHeader=responsetest,zsx
#断路器过滤器,如果服务器本身断开,无法通信,则跳转到/request/notFound路径,
# - name: CircuitBreaker
# args:
# name: fetchIngredients
# fallbackUri: forward:/request/notFound
限流配置
- name: RequestRateLimiter
args:
#这里配置的含义:并发最多请求20,后面1s只接受一个请求,其他请求则响应 too many requests 429
redis-rate-limiter.replenishRate: 1 # 令牌桶填充的速率,1s填产生多少个令牌
redis-rate-limiter.burstCapacity: 20 #令牌token的总量
redis-rate-limiter.requestedTokens: 1 #一个请求消耗多少token
#配置限流的条件
key-resolver: "#{@hostAddrKeyResolver}"
第二套配置,针对上面断路器过滤器的跳转
- id: ingredients-fallback
uri: http://127.0.0.1:9999
predicates:
- Path=/request/notFound
(7)常见限流策略
按用户名、ip、请求接口、服务名等为key,分别要写不同的keyResolver
//这样相当于每个接口作为key进行限流
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
//每个服务配置contextPath为服务名,前端请求时以服务名为开头请求,在keyResolver中截取服务名
// return exchange -> Mono.just(exchange.getRequest().getPath().subPath(1,2).toString());
//用户名为key限流
// return exchange -> Mono.just(exchange.getRequest().getHeaders().get("userName").get(0));
(8)gateway目前提供的限流器是redisRATElimiter,无法动态调整流量,比如以ip为key对某个ip进行动态调整,可以继承RedisRateLimiter,重写isAllowed方法,然后将每个ip和配置做对应存放在redis中,每次从redis拿限流配置,而不是从yml中拿,参考gitee上的代码
@Component
public class CustomRedisRateLimiter extends RedisRateLimiter {
@Override
public Mono<Response> isAllowed(String routeId, String id) {
//核心代码
//这里的id就是keyResolver提供的
Config routeConfig = loadConfiguration(id);
// How many requests per second do you want a user to be allowed to do?
int replenishRate = routeConfig.getReplenishRate();
.....
}
Config loadConfiguration(String id) {
HashOperations<String,String,RateConfig> hashOperations = redis.opsForHash();
RateConfig routeConfig0 = hashOperations.get(RATE_LIMIT, id);
Config routeConfig = new Config();
if( routeConfig0 == null){
Config config = new Config();
config.setRequestedTokens(1);
config.setReplenishRate(1);
config.setBurstCapacity(10);
routeConfig = config;
RateConfig routeConfig1 = new RateConfig();
BeanUtils.copyProperties(config,routeConfig1);
hashOperations.put(RATE_LIMIT,id,routeConfig1);
}else{
BeanUtils.copyProperties(routeConfig0,routeConfig);
}
/* if (routeConfig == null) {
throw new IllegalArgumentException(
"No Configuration found for id "+ id+ " or defaultFilters");
}*/
return routeConfig;
}
}
//可以动态修改流量
@PostMapping("/ratelimit")
public RateConfig change(@RequestBody RateConfig rateConfig){
final HashOperations<String, String, RateConfig> ops = redis.opsForHash();
ops.put(RATE_LIMIT,rateConfig.getIp(),rateConfig);
return ops.get(RATE_LIMIT,rateConfig.getIp());
}
7.springcloud的五大核心组件:
(1)Eureka/nacos 服务注册和发现
(2)Feign 动态代理的服务调用
(3)Ribbon 客户端做负载均衡+服务调用
(4)Hystrix 服务间隔离,服务降级,防止级联异常,避免服务雪崩的问题
(5)网关cloud gateway 网关管理,网关是微服务对外服务的窗口,执行系统内外之间的隔离,保证后台服务的安全性,流量控制等等
- springcloud 使用http调用服务的优点:
(1)http无状态,可拔插,多个服务组成集群,上线下线消费者都无感觉,如果是dubbo长连接不行,双方长连接
(2)异构系统,http接口各种语言都可以提供,不同的语言构成一个系统
(3)spring强大的生态
(4)分区容错性提高,如果出现网络分区,http可以直接换另一个集群服务调用,提高可用性,dubbo长连接不行
当然dubbo长连接效率高,同时字节数组序列和反序列比http的json效率更高,springcloud牺牲了一定的性能换取了系统高可用
9.微服务配置springboot admin,在线对多台服务进行管理和监控
其实就是把actuator的信息图形化展示了
(1)分为服务端和客户端,服务端启动服务,客户端进行连接和上报
(2)服务端配置:
pom.xml 注意版本不能随意,2.2.0是可用的,但是其他版本不一定
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Admin 界面 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>2.2.0</version>
</dependency>
加注解:
@SpringBootApplication
@EnableAdminServer
//使用springboot admin 监控微服务
public class AdminApplication
端口配置10000
(3)客户端配置,哪些微服务应用想被监控,哪些就配置客户端
代码上无侵入
pom:
<!--admin 进行监控-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置即可:
spring:
boot:
admin:
client:
url:
http://localhost:10000
#配置actuator监控粒度
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
10.nacos注册中心/配置中心的使用
(1)服务端安装和配置
直接官网下载zip包(可能下载不了),替代方案是gitee上拉源码进行maven编译
参考: https://my.oschina.net/u/4369262/blog/4907005
maven编译命令
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
编译完后,找到tart.gz(linux)或者zip(windows)
在linux上启动:
sh startup.sh -m standalone
如果是在window上编译的,由于\r\n的原因,可能会早知startup.sh有很多\r,可以直接sed进行替换: sed -i 's/\r//g' startup.sh
启动后访问 http://ip:8848/nacos
账户密码默认nacos/nacos
(2)nacos注册中心在客户端的使用
pom:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置服务器地址:
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
增加启动类注解:@EnableDiscoveryClient(import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
)说明仍需要springcloud的支持
在注册中心能够看到以下服务列表即成功
(3)nacos配置中心的使用
pom:
在上面注册中心的基础上增加
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
需要使用配置中心的服务中resource中都增加bootstrap.yml文件
boostrap.yml配置:
spring:
application:
name: service-consumer
cloud:
nacos:
server-addr: 10.206.47.237:8848
config:
server-addr: 10.206.47.237:8848 nacos地址
file-extension: yml 文件的扩展类型
group: DEFAULT_GROUP 分组的名称,默认是DEFAULT_GROUP
name: zsx-conf1 dataid 默认是spring.application.扩展名(properties/yml)
然后在服务端的配置管理中增加配置:
客户端的config.name与服务端的dataid要一致, group也得一致
客户端测试配置中心是否生效:
@RestController
@RefreshScope 这个注解标注后,每次当类的属性值比之前有变化时,
就会往spring容器中new一个新的实例,如果没变化则不会替换之前的,
public class TestController implements UserApi {
@Value("${server.port}")
String port;
@Value("${app-version}") //这里本地配置文件虽然没有配置,但是会一开始从配置中心拿,因此不会空指针,
// 在服务端编辑配置后,这里可以调接口看下变化
String version;
}