在我们的微服务架构通讯,服务之间依赖关系非常大,如果通过传统的方式管理我们服务的url地址的情况下,一旦地址发生变化的情况下,还需要人工修改rpc远程调用地址。
我们在微服务架构通讯,服务之间的依赖关系非常大,每个服务的url管理地址非常复杂,所以在这时候我们采用服务url治理技术,治理的技术可以实现对我们整个实现动态服务着注册与发现、本地负载均衡、容错等。
注:分布式微服务解决方案的框架,并不直接是rpc调用。

nacos

注册中心:管理和存放我们的服务的ul地址信息,能够实现动态感知。
Zookeeper、Eureka、Consul、Nacos、redis、数据库都可以当作注册中心。
服务注册:提供服务接口地址信息存放
生产者:提供我们接口被其他的服务调用。
消费者:调用接口实现消费。
启动命令: linux启动:sh startup.sh -m standalone
sh startup.sh
服务注册原理:
1、生产者启动的时候 key=服务的名称 value=ip和端口号 注册到微服务的注册中心上。
service_name 192.168.212.110:8080
2、注册存放服务地址列表类型:key唯一,列表是list集合。
Map>
{
service_name :[“192.168.212.110:8080”,”192.168.212.110:8081”]
}
3、我们的消费者从我们的注册中心上根据服务名称查询服务地址列表(集合)
service_name
34、消费者获取到集群列表之后,采用负载均衡器选择一个地址实现rpc远程调用。

nacos的基本介绍

Nacos可以实现分布式服务注册与发现/分布式
http://192.168.200.200:8848/nacos nacos:nacos 账户:密码
http://192.168.200.200:8848/nacos/v1/ns/instance/list?serviceName=服务名
http://192.168.200.200:8848/nacos/v1/ns/instance?serviceName=服务名&ip=xxx&port=xxx
*nacos启动命令:sh startup.sh -m standalone

本地负载均衡器与openfen声明式客户端调用工具

一、本地负载均衡器和nacos服务注册中心:

我们消费者从我们的注册中心上获取接口调用地址列表,本地实现负载均衡算法(轮询、随机、hash一致性、权重)等原理:获取接口地址,采用算法获取选择一个接口地址实现本地rpc远程调用。
本地负载均衡:
1、@LoadBalanced注解写在resttemplate上实现
2、自定义loadbalance是实现 getSingleAddress方法

  1. @Component
  2. public class RotationLoadBalancer implements LoadBalancer{
  3. //从0开始去计数
  4. private AtomicInteger atomicInteger=new AtomicInteger(0);
  5. @Override
  6. public ServiceInstance getSingleAddress(List<ServiceInstance> serviceInstances) {
  7. int index = atomicInteger.incrementAndGet() % serviceInstances.size();
  8. return serviceInstances.get(index);
  9. }
  10. }
  1. 3、注入spring中的负载均衡接口springcloud中的实现类org.springframework.cloud.client.loadbalancer默认是轮询的算法。
  1. @Autowired
  2. LoadBalancerClient loadBalancerClient;
  3. @GetMapping("/loadBalancerMember")
  4. public String loadBalancerMember(){
  5. ServiceInstance serviceInstance = loadBalancerClient.choose("member");
  6. //1、获取服务url和端口号
  7. URI rpcMemberUrl = serviceInstance.getUri();
  8. String result = restTemplate.getForObject(rpcMemberUrl+"/getUser", String.class);
  9. //2、列表任意选择一个 实现本地rpc调用rest
  10. return result+serviceInstance.getPort();
  11. }

二、openfeign客户端

springcloud第一代使用feign第二代使用openfeign
openfeign客户端作用:是一个Web声明式的Http客户端远程调用工具,底层封装的是HttpClient技术。
使用
1、(nacosdiscover依赖)生产者模块创建并且将要消费的接口实现,并将服务注册到nacos上
2、(openfiegn依赖)feign模块创建。
3、(nacosdiscover依赖)消费者模块创建并导入feign模块的jar包然后使用spring注入需要使用的模块,然后feign调用对应服务。完成rpc远程服务调用。

三、Nacos的分布式配置中心

Nacos配置中心的服务流程图:
image.png
配置bootstrap.yml如下:

  1. # 应用名称
  2. spring:
  3. application:
  4. name: producer
  5. cloud:
  6. nacos:
  7. #注册中心
  8. discovery:
  9. # Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
  10. # Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
  11. # Nacos认证信息
  12. # username: nacos
  13. # password: nacos
  14. server-addr: 192.168.200.200:8848
  15. # 注册到 nacos 的指定 namespace,默认为 public
  16. namespace: public
  17. #配置中心
  18. config:
  19. server-addr: 192.168.200.200:8848
  20. # username: nacos
  21. # password: nacos
  22. #分组
  23. group: DEFAULT_GROUP
  24. #默认加载properties
  25. file-extension: yaml
  26. #引用yml后缀为devyml文件.多版本环境配置
  27. profiles:
  28. active: dev
  29. # shared-configs:
  30. # - data-id: xx.yaml
  31. # group:
  32. # refresh: true
  33. servlet:
  34. multipart:
  35. max-file-size: 100MB
  36. # 防止端口号冲突8080开头表示member 8090表示order
  37. server:
  38. port: 8080
  39. producer:
  40. msg: "这是nacos上的一段信息"

pom依赖如下:

  1. <!--向Nacos注册自身信息-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  5. <version>2.2.6.RELEASE</version>
  6. </dependency>
  7. <!--配置中心,启动从注册中心下载yaml等配置文件覆盖本地application.yml-->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  11. <version>2.2.6.RELEASE</version>
  12. </dependency>

四、集群部署与实现

Eureka、Nacos、Zookeeper等注册中心
注册中心CAP都是指的什么:
C: 数据一致性
A: 高可用性
P: 分区容错性
相同点:都可以实现分布式服务注册中心
不同点:Zookeeper采用CP保证数据的一致性的问题,原理采用Zap原子广播协议,当我们的zk领导因为某种原因宕机的情况下,会自动重新选举一个新的领导角色,在选举过程中整个zk服务是无法使用的。

服务网关Gateway

一、为什么用Gateway

Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。
Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。
比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。
比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。

二、gateway高可用集群与动态网关

1、微服务网关作用

微服务网关是整个微服务api请求的入口,可以实现日志拦截、权限控制、解决跨域问题、限流、熔断、负载均衡、黑名单与白名单拦截、授权。

2、常用网关架构

Nginx配置的两个网关可以进行环境配置如下:
网关 1:127.0.0.1:81
网关2 :127.0.0.1:82
此时会存在一个如下服务器:
Nginx服务器 127.0.0.1:80

3、网关一些功能实现代码

  1. gateway解决跨域问题

    1. @Component
    2. public class CrossOriginFilter implements GlobalFilter, Ordered {
    3. @Override
    4. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    5. ServerHttpResponse response = exchange.getResponse();
    6. HttpHeaders headers = response.getHeaders();
    7. headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,"*");
    8. headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,"POST,GET,PUT,DELETE,UPDATE");
    9. headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS,"true");
    10. headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,"*");
    11. headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,"*");
    12. return chain.filter(exchange);
    13. }
    14. @Override
    15. public int getOrder() {
    16. return 0;
    17. }
    18. }
  2. gateway实现token过滤

    1. @Component
    2. public class TokenGlobalFilter implements Ordered, GlobalFilter {
    3. @Override
    4. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    5. ServerHttpRequest request = exchange.getRequest();
    6. String token = request.getQueryParams().getFirst("token");
    7. if(StringUtils.isEmpty(token)){
    8. ServerHttpResponse response = exchange.getResponse();
    9. response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
    10. String msg = "token is not null";
    11. DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes(StandardCharsets.UTF_8));
    12. return response.writeWith(Mono.just(buffer));
    13. }
    14. // 直接转发到我们真实服务
    15. return chain.filter(exchange);
    16. }
    17. @Override
    18. public int getOrder() {
    19. return 1;
    20. }
    21. }
  3. gateway的application.yml文件配置参数

    1. spring:
    2. application:
    3. name: geteway-study
    4. cloud:
    5. nacos:
    6. discovery:
    7. server-addr: 192.168.200.200:8848
    8. namespace: public
    9. gateway:
    10. discovery:
    11. locator:
    12. enabled: true #开启以服务id去注册中心上获取转发地址
    13. routes:
    14. - id: producer
    15. uri: lb://producer/
    16. # 匹配规则
    17. predicates:
    18. - Path=/producertest/*
    19. # - Path=/*
    20. filters:
    21. - StripPrefix=1
    22. server:
    23. port: 8848
    24. ### 127.0.0.1/mayikt 转到 http://www.mayikt.com/

    sentinel服务保护框架

    官网地址:(https://github.com/alibaba/Sentinel
    一、服务接口保护有哪些方案

  4. 黑名单和白名单

  5. 对IP实现限流(限制请求熔断)/熔断机制
  6. 服务降级
  7. 服务隔离机制

*这里说一下限流和熔断的区别,
限流: 服务限流就是相当于服务接口每秒钟设置一定的QBS允许访问,一旦超过QBS,会拒绝访问请求。
熔断:服务限流就是相当于服务接口每秒钟设置一定的QBS允许访问,一旦超过QBS,会走本地的falback方法。 注意:服务降级的目的:返回一个友好的提示给客户端。
*服务雪崩的效应:tomcat服务器只有一个线程池处理所有接口的请求。高并发情况下导致所有请求堆积到同一个接口上,那么会产生服务器所有线程都在处理该接口,导致其他接口无法访问。短暂没有线程处理。

服务雪崩解决方案
服务隔离机制:线程池隔离或者信号量隔离机制。
线程池隔离:每个接口都有自己独立的线程池维护我们的请求,每个线程池互不影响,缺点:占用服务器内存非常大。
信号量隔离:设置做多允许我们某个接口有一阈值的线程数处理我们的接口,如果超出该线程数量,则拒绝访问。

二、Sentine与hystrix的区别
Hystrix属于第一代fs旗下,Sentinel属于阿里巴巴旗下。
限流有哪些算法:滑动窗口、令牌桶


Sentinel Hystrix
隔离策略 信号量隔离 线程池隔离/信号量隔离
熔断降级策略 基于响应时间或失败比率 基于失败比率
实时指标实现 滑动窗口 滑动窗口(基于RxJava)
规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件的形式
基于注释的支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 不支持
流量整形 支持慢启动、匀速器模式 不支持
系统负载保护 支持 不支持
控制台 开箱即用、可配置规则、查看秒级监控、机器发现等 不完善
常见框架的适配 Servlet、Spring Cloud、Dubbo、gRPC等 Servlet、Spring Club Netflix

三、Sentinel控制台项目应用的启动
1、 下载:Sentinel dashboard下载:https://github.com/alibaba/Sentinel/releases
2、执行命令:Java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar sentinel-dashboard-1.7.1.jar
3、登入:输入127.0.0.0:8718登入到Sentinel
用户和密码都是Sentinel
界面如下:
1、服务注册
image.png
2、新增限流规则(默认是@RequestMapping后的接口名;也可以是@SentinelResource(value = GETORDER_KEY, blockHandler = “xxxQpsException”)自定义的名字)
image.png
3、新增服务熔断降级规则(当这个资源访问次数达到一定的数量超过阈值就会熔断导致当前接口无法访问(时间为<时间窗口>s)而是去访问我们的降级服务)
image.png
四、Sentinel持续化的四种方案
image.png
还待完善。。。
实现思路:将限流策略写在nacos配置中心上,从服务中读取配置然后通过代码书写sentinel实现策略。
五、springcloud之gateway整合Sentinel实现限流

六、流Sentinel服务降级的几种产生策略

  1. 平均响应时间(请求次数熔断)

    (1) 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
    (2)在规定的时间窗口内一直执行我们的服务降级的方法,不能够执行我们的真实业务逻辑。
    举例:

    1. @SentinelResource(value = GETORDER_KEY, blockHandler = "getProducerPortDowngradeRtType")
    2. @GetMapping(value = "/getProducerPort3")
    3. String getProducerPort3() {
    4. try {
    5. Thread.sleep(3000);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. return "访问到了producer中的信息" + port;
    10. }
    11. public String getProducerPortDowngradeRtType(BlockException e) {
    12. e.printStackTrace();
    13. return "这个接口被熔断降级了";
    14. }

    新增降级规则:
    资源名:getorder-key
    降级规则:RT
    RT:10
    时间窗口:2
    若 RT 为60,则一直出现’’执行本地的服务降级方法’’,不会执行
    ‘’正常执行业务逻辑’’
    改RT上限:要改Dcsp. sentinel. statistic .max.rt=xxx配置

  2. 错误比例(请求接口报错的错误比例熔断)

    异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
    三、操作代码
    基于错误率服务降级:

    1. @SentinelResource(value = "getProducerPortDowngradeErrorType", fallback="getProducerPortDowngradeErrorTypeFallback")
    2. @RequestMapping( "getProducerPortDowngradeErrorType")
    3. public String get0rderDowngradeErrorType(int age) {
    4. int j = 1 / age;
    5. return "正常执行我们业务逻辑:j"+j;
    6. }
    7. public String getProducerPortDowngradeErrorTypeFallback(int age) {
    8. return"错误率太高,暂时无法访问该接口";
    9. }
  3. 错误次数( 异常数)

    异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
    三、操作代码
    基于错误率服务降级:
    * 注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。示例:

    1. Entry entry = null;
    2. try {
    3. entry = SphU.entry(key, EntryType.IN, key);
    4. // Write your biz code here.
    5. // <<BIZ CODE>>
    6. } catch (Throwable t) {
    7. if (!BlockException.isBlockException(t)) {
    8. Tracer.trace(t);
    9. }
    10. } finally {
    11. if (entry != null) {
    12. entry.exit();
    13. }
    14. }

    七、Sentinel热词限流配置
    热点参数限流:对接口中的热词限流。
    1、秒杀接口实现热词限流(手动实现):

    1. //秒杀限流规则
    2. public static final String SECKILL_KEY = "seckill_key";
    3. //秒杀接口限流配置策略
    4. static {
    5. ParamFlowRule rule = new ParamFlowRule(SECKILL_KEY)
    6. .setParamIdx(0)
    7. .setGrade(RuleConstant.FLOW_GRADE_QPS)
    8. .setCount(1);
    9. ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
    10. System.out.println(">>>>秒杀接口限流策略配置成功<<<<");
    11. }
    12. @RequestMapping("/seckill")
    13. public String seckill(Long userId, Long orderId) {
    14. //把接口和热词规则的方法匹配上
    15. try {
    16. Entry entry = SphU.entry(SECKILL_KEY, EntryType.IN, 1, userId, orderId);
    17. } catch (BlockException e) {
    18. e.printStackTrace();
    19. return "当前用户访问频率太高,秒杀失败!";
    20. }
    21. return "秒杀成功!";
    22. }

    2、Sentinel控制台环境搭建
    8718是界面端口号8719是服务间通讯的端口号。
    1)sentinel实现的思路
    image.png
    2)如何设置
    image.png
    代码如下:

    1. //秒杀限流规则
    2. public static final String SECKILL_RULE = "seckill_rule";
    3. @RequestMapping("/seckill2")
    4. @SentinelResource(value = SECKILL_RULE,fallback = "hotWordsFallBack",blockHandler = "hotWordsBlockHandler")
    5. public String seckill2(Long userId, Long orderId) {
    6. return "秒杀成功!"+userId+orderId;
    7. }
    8. private String hotWordsFallBack(){
    9. return "同一用户不可重复秒杀!";
    10. }
    11. private String hotWordsBlockHandler(){
    12. return "访问次数过多!";
    13. }

    特殊用户通道:可以支持对特殊的参数可以不同限制。
    image.png