LRZ2004

导航

微服务学习

有商品服务,订单服务。
首先在父项依赖中统一依赖版本。





org.springframework.cloud
spring-cloud-dependencies
2021.0.4
pom
import



com.alibaba.cloud
spring-cloud-alibaba-dependencies
2021.0.4.0
pom
import


一 、服务治理 Nacos Discovery
默认8848端口,用于实现各个微服务的自动化注册与发现。
①写nacos依赖

com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery

②在配置文件中添加Nacos服务的地址
spring:
application:
name: xxx #告诉nacos我的名字
cloud:
nacos:
discovery:
server-addr: localhost:8848 #找到nacos的地址

二、远程调用负载均衡LoadBalancer
分为服务端负载均衡和客户端负载均衡

①写入依赖

org.springframework.cloud
spring-cloud-starter-loadbalancer

②注解@LoadBalanced
③负载均衡策略
1.RandomLoadBalancer-随机分配策略

2.RoundRobinLoadBalancer-轮询分配策略(默认)

三、远程调用Feign(默认集成了负载均衡)
①依赖

org.springframework.cloud
spring-cloud-starter-openfeign

②在启动类OrderServer.java上添加Fegin的扫描注解
@EnableFeignClients

③在shop-order-server项目中新增接口ProductFeignApi
/**

  • name属性:访问远端服务器的名字
  • fallback属性:当远端服务访问失败时,本地的错误处理类
    */
    @FeignClient(name = "product",fallback = ProductFeignFallback.class)
    public interface ProductFeignApi {
    @GetMapping("/product/get/{pid}")
    TShopProduct get(@PathVariable("pid") Long pid);
    }

④访问失败处理类
/**

  • 本地容错类,当Feign接口请求远端服务器失败时,就会调用该类获取兜底数据
    */
    @Component
    public class ProductFeignFallback implements ProductFeignApi {

    @Override
    public TShopProduct get(Long pid) {
    TShopProduct product = new TShopProduct();
    product.setPid(-1L);
    product.setPname("商品错误");
    product.setPprice(0.0D);
    product.setStock(0);

    return product;
    

    }
    }

⑤远程调用方法
//远程调用商品微服务,查询商品信息
Product product = productFeignApi.findByPid(productId);

四、服务熔断降级 Sentinel!!!!重点
规则默认放在内存中,如果想要进行持久化需要写配置
sentinel可视化控制台的端口号默认为:8080
sentinel与服务之间的交互端口号默认为:8719
sentinel中有两种资源:
1.在sentinel中,Controller中的接口默认被识别成sentinel的资源

2.使用@SentinelResource注解也可以声明资源,注解括号里的名字是资源名

常见容错方案:隔离机制、超时机制、限流机制、熔断机制、降级机制

①Sentinel依赖

com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel

②修改配置文件
spring:
cloud:
sentinel:
transport:
port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8080 # 指定控制台服务的地址

sentinel规则种类:流控规则、降级规则、热点规则、系统规则、授权规则

4.1 流控规则
流控模式:直接、关联、链路
直接:谁超过阈值范围就给谁限流
关联:比如资源让行,自己超过阈值,则让其他限流
实际案例:付费网站,普通用户给vip用户让行
链路:访问同一个资源,不同的用户走不同的链路,例如vip1走vip1链路、vip2走vip2链路、普通用户走普通用户链路
流控效果:快速失败、Warm Up、排队等待
快速失败(默认):直接抛出500异常或显示Blocked by Sentinel(flow limiting)
Warm up:经过预热时长最终会达到阈值数,开始的阈值数为设置的阈值数的三分之一,如果在此期间达到阈值也会抛出异常
实际场景:(可理解为机械的最大性能)例如服务器刚启动性能还没有完全发挥,先让它预热
排队等待:如果超出阈值,则会进行等待,如果等待时间用完还不能进去,则一样抛出异常
实际场景(用得少,仅适用于小网站,如果大一点的则会雪崩):网站用户量稳定假设只有2000,每年只增长100,服务器能承受的数量为2200,当超过2200则可以让超过的用户访问的慢一点,但是如果买了一个新的服务器,则需要20年才可以勉强用满

4.2 降级规则
根据服务器的状况来进行降级,流控规则是根据访问的线程数来进行管理。
①慢调用比例
RT:最大响应时间
最小请求数:自己设置
实际场景:tomcat每秒可以请求千八百个请求,如果只来了10个请求,还有问题,只能说明是网络或者硬件问题,不是服务器高并发问题
熔断时长:
服务器默认是熔断关闭状态,当慢调用比例达到阈值会变成熔断开启状态,则接下来的熔断时长内的请求会自动被熔断。经过熔断时长后熔断器会进入半熔断状态,若接下来的一个请求响应的时间小于设置的慢调用RT则会结束熔断,否则会再次熔断
②异常比例
③异常数
单位时间内,异常数超过阈值

4.3 热点规则
用来限定热点的

    实际场景:如果在商品网站中大家都来抢购热点商品,但是不能因为这个,让不来抢购热点商品的用户感受到卡顿

这里要注意!!!,在Controller中写接口的时候,要在接口上写上@SentinelResource("资源名")注解

参数索引:参数在的下标位置

单机阈值:几秒钟中最多访问几次

参数例外项是用来设置特殊商品的,例如热点商品

4.4 授权规则
①要写一个请求解析器,用来解析请求的目标来源
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;

@Component
public class RemoteOriginParser implements RequestOriginParser {

@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
    String fromsource = httpServletRequest.getHeader("fromsource");
    if (fromsource == null || "".equals(fromsource)){
        fromsource = "error";
    }
    return fromsource;
}

}

②接口
@RequestMapping("/auth1")
public String auth1(String serviceName){
log.info("应用:{},访问接口",serviceName);
return "auth1";
}
③新增授权规则
④访问测试
4.5 系统规则
针对整个服务器层面,不针对指定的接口
一般实际开发中,用的多的是Load自适应和入口QPS
4.6 Sentinel 自定义返回异常
分为一下几种异常:
FlowException 限流异常
DegradeException 降级异常
ParamFlowException 参数限流异常(热点异常)
AuthorityException 授权异常
SystemBlockException 系统负载异常(系统异常)
4.6.1异常处理类(只捕获Sentinel的异常)
/**

  • 能解决在触发sentinel规则后,出现'block by sentinel(Flow limiting)'的异常;直接抛出500的异常解决不了
  • 只捕获sentinel的异常
    /
    @Component
    public class ShopBlockExceptionHandler implements BlockExceptionHandler {
    /
    *
    • 当发生异常就会进入这个方法
      */
      @Override
      public void handle(HttpServletRequest req, HttpServletResponse resp, BlockException e) throws Exception {

      Map<String,Object> map = new HashMap<>();
      if (e instanceof FlowException){
      // 限流异常
      map.put("code",-1);
      map.put("msg","限流异常");
      }else if (e instanceof DegradeException){
      // 降级异常
      map.put("code",-2);
      map.put("msg","降级异常");
      }else if (e instanceof ParamFlowException){
      // 热点异常
      map.put("code",-3);
      map.put("msg","热点异常");
      }else if (e instanceof AuthorityException){
      // 授权异常
      map.put("code",-4);
      map.put("msg","授权异常");
      }else if (e instanceof SystemBlockException){
      // 系统异常
      map.put("code",-5);
      map.put("msg","系统异常");
      }

      String jsonString = JSON.toJSONString(map);
      resp.setContentType("application/json;charset=utf-8");
      resp.getWriter().write(jsonString);
      }
      }

4.6.2统一异常处理(可以捕获所有的异常)
/**

  • 统一异常处理,可以捕获所有的异常
    */
    @RestControllerAdvice
    public class MyExceptionHandler{

    @ExceptionHandler(BlockException.class)
    public Object handleBlockException(BlockException e){
    Map<String,Object> map = new HashMap<>();
    if (e instanceof FlowException){
    // 限流异常
    map.put("code",-1);
    map.put("msg","限流异常");
    }else if (e instanceof DegradeException){
    // 降级异常
    map.put("code",-2);
    map.put("msg","降级异常");
    }else if (e instanceof ParamFlowException){
    // 热点异常
    map.put("code",-3);
    map.put("msg","热点异常");
    }else if (e instanceof AuthorityException){
    // 授权异常
    map.put("code",-4);
    map.put("msg","授权异常");
    }else if (e instanceof SystemBlockException){
    // 系统异常
    map.put("code",-5);
    map.put("msg","系统异常");
    }
    return map;
    }

}

4.7@SentinelResource的使用
可用于:

    1.通过不同接口访问同一个资源

    2.热点规则,在接口上写上这个注解

主要参数:

     value:设置资源名称

            可以为空,此时资源名为包名.类名.方法名

    blockHandler/blockHandlerClass和fallback/fallbackClass用于指定异常的处理,但是一般不会这样用,我们基本都是用同一异常来进行异常处理

    blockHander用于处理Sentinel发生的异常,而fallback用于处理其他的异常

    exceptionsToIgnore:忽略某些异常

4.8 Feign整合Sentinel
之前写的代码思路都是正向请求,意思是我们请求的东西一定会被请求到。

现在我们如果请求不到某些东西,则要用Sentinel进行降级处理(fallback)。

4.8.1依赖
feign:

sentinel:

enabled: true

AI运行代码
java
之所以是写这个依赖,是因为现在的框架集成度很高,feign可以用别的来进行熔断处理不一定是sentinel,所以要把他开启

4.8.2创建容错类
/**

  • 本地容错类,当Feign接口请求远端服务器失败时,就会调用该类获取兜底数据
    */
    @Component
    public class ProductFeignFallback implements ProductFeignApi {

    @Override
    public TShopProduct get(Long pid) {
    TShopProduct product = new TShopProduct();
    product.setPid(-1L);
    product.setPname("商品错误");
    product.setPprice(0.0D);
    product.setStock(0);

    return product;
    

    }

}

4.8.3在feign接口中定义容错类
/**

  • name属性:访问远端服务器的名字

  • fallback属性:当远端服务访问失败时,本地的错误处理类
    */
    @FeignClient(name = "product",fallback = ProductFeignFallback.class)
    public interface ProductFeignApi {

    @GetMapping("/product/get/{pid}")
    TShopProduct get(@PathVariable("pid") Long pid);
    }

有一种情况面试会问:当前项目中出现这么一个问题,A服务访问B服务第一次访问成功,第二次失败,以此类推

分析:一会请求成功,一会请求失败,说明有多台服务器,失败的情况就是访问到了不好用的服务器(网络问题、重编译了、下线了)

答:因为负载均衡,其中有某一个服务器是挂了的情况

五、 服务网关Gateway
Gateway网关不是基于tomcat体系的,而是基于Netty服务器

Gateway也可以负载均衡

网关能找到服务器,则说明Gateway是nacos中的一个客户端

5.1 集成服务网关
①需要导入3个依赖

org.springframework.cloud spring-cloud-starter-gateway com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-loadbalancer

②启动类没有什么好说的了
③配置文件
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务

gateway是springCloud团队开发的,nacos是alibaba开发的,代码不是互通的,所以要写spring.cloud.gateway.discovery.locator.enabled=true这个配置
这样网关就配置好了,接下来进行测试:
之前访问 localhost:8091/sentinel2
现在访问 localhost:9000/order/sentinel2 访问的是一个内容,其中9000是网关的端口号,order是要请求的服务名
5.2 自定义网关路由
如果用默认的网关路由会出现以下问题:
暴露服务名,不安全
服务名太长,输入地址不方便
编写自定义路由,只需要在application.yml中添加新的路由规则:
spring:
cloud:
gateway:
routes: # 一个网关中有多个路由,注意不要写成route
- id: product_route
uri: lb://product # 这个要写微服务的名字
predicates:
- Path=/p/** # 只有满足这个路径,才会走网关
filters:
- StripPrefix=1 # 跳过一级路径,也就是p
- id: order_route
uri: lb://order
predicates:
- Path=/o/**
filters:
- StripPrefix=1

注意还有一个重要参数order,值越小越先进行匹配

执行流程:

5.3 过滤器 Filter
过滤器就是在请求的传递过程中,对请求和响应做一些手脚。
在Gateway中,Filter的生命周期只有两个:“pre”和“post”。
PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。
在Gateway中,Filter的作用范围分为两种:
GatewayFilter:应用到单个路由或者一个分组的路由上。
GlobalFilter:应用到所有的路由上。
5.4 局部路由过滤器统计耗时
在配置路由规则中的filters下,加入- show=true
@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<TimeGatewayFilterFactory.MyConfig> {

public TimeGatewayFilterFactory(){
    super(MyConfig.class);
}

@Override
public List<String> shortcutFieldOrder() {

// Stream.of("a", "str", "b").toList();
List collect = Stream.of("show").collect(Collectors.toList());
return collect;
}

// 加工过滤器的方法
@Override
public GatewayFilter apply(MyConfig config) {
    // 生成的过滤器,并返回
    return new GatewayFilter() {
        /**
         *  过滤器的过滤方法
         *  参数1:相当于tomcat容器体系下,request和response对象的整合
         *  参数2:过滤器链
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            if (!config.show){ // 如果配置中设置为false,直接放行,不需要统计耗时
                return chain.filter(exchange);
            }
            // pre过滤器
            long begin = System.currentTimeMillis();
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
                // post过滤器
                long end = System.currentTimeMillis();
                // 发请求的客户端域名
                String ip = exchange.getRequest().getRemoteAddress().getHostName();
                // 请求的资源地址
                String path = exchange.getRequest().getURI().getPath();

                StringBuffer stringBuffer = new StringBuffer();
                stringBuffer.append(ip).append("访问接口").append(path).append("耗时").append(end-begin).append("毫秒");
                System.err.println(stringBuffer.toString());
            }));
        }
    };
}

// 用于存放配置文件中的参数
@Data
public static class MyConfig{
    private boolean show;
}

}

5.5 全局路由过滤器
@Component
public class ShopAuthFilter implements GlobalFilter {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

// System.err.println("全局路由过滤器被执行了");
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String token = request.getQueryParams().getFirst("token");
if (StringUtils.isBlank(token)) { // 传递的token是null,"",或者空白符
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}else {
// 验证token合法性
// 如果token不合法,返回401
}
return chain.filter(exchange); // 放行
}
}

5.6 集成Sentinel实现网关限流
①依赖

com.alibaba.csp sentinel-spring-cloud-gateway-adapter com.alibaba.cloud spring-cloud-starter-alibaba-sentinel com.alibaba.cloud spring-cloud-alibaba-sentinel-gateway

②添加配置
spring:
cloud:
sentinel:
transport:
port: 9999
dashboard: localhost:8080
AI运行代码
③配置完成后进行限流操作
5.7 API分组
是方法层面上的限流,更精细

一共有3种匹配模式:精确、前缀、正则
现在我们定义了如下的接口地址

@RestController
@RequestMapping("/v1")
public class TestController {
@RequestMapping("/test1")
public String test1() {
return "test1";
}

@RequestMapping("/test2")
public String test2() {
    return "test2";
}

@RequestMapping("/test3/test")
public String test3() {
    return "test3";
}

}

5.7.1 精准匹配
1.在API管理中新建API分组,匹配模式选择精准匹配,匹配串写请求URL地址

2.在流控规则中,API类型中选择API分组,然后在API名称中选择我们刚刚定义的V1限流

3.此时上面三个请求中,只有 /o/v1/test1会被限流

5.7.2 前缀匹配

然后在增加流控规则,此时/o/v1/**路径下的所有请求都会被流控

注意: 如果路径为/*表示匹配一级路径,如果路径为/**表示多级路径

5.7.3 正则匹配

5.8 修改限流默认返回格式
@Configuration
public class ShopGatewayConfig {
// 不用主动调用
@PostConstruct
public void ShopGatewayExceptionHandle(){

    BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
        @Override
        public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
            Map<String,Object> map = new HashMap<>();
            map.put("code",-100);
            map.put("message","网关路由被限流");
            return ServerResponse.status(HttpStatus.OK) // 状态
                    .contentType(MediaType.APPLICATION_JSON) // JSON类型
                    .body(BodyInserters.fromValue(map)); //用Body插入器插入map
        }
    };

    GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}

}

六、链路追踪 Sleuth&Zipkin
Sleuth是用来与@Slf4j配合打日志的

6.1 集成链路追踪组件Sleuth
导入依赖

org.springframework.cloud spring-cloud-starter-sleuth

6.2 日志参数解释
日志格式:

[order,c323c72e7009c077,fba72d9c65745e60,true]

1、第一个值,spring.application.name的值

2、第二个值,c323c72e7009c077 ,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID

3、第三个值,fba72d9c65745e60,spanID 基本的工作单元,获取元数据,如发送一个http

4、第四个值:true,是否要将该信息输出到zipkin服务中来收集和展示。

可以根据时间判断哪个日志先执行的

6.3 Zipkin+Sleuth整合
①导入依赖

org.springframework.cloud spring-cloud-starter-zipkin 2.2.8.RELEASE

②加入配置

spring:
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin server的请求地址
discoveryClientEnabled: false #让nacos把它当成一个URL,而不要当做服务名
sleuth:
sampler:
probability: 1.0 #采样的百分比

七、配置中心 Nacos Config
微服务架构下关于配置文件的一些问题:

    1.配置文件相对分散。在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散在各个微服务中,不好统一配置和管理。

    2.配置文件无法区分环境。微服务项目可能会有多个环境,例如:测试环境、预发布环境、生产环境。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动维护,这比较困难。

    3. 配置文件无法实时更新。我们修改了配置文件之后,必须重新启动微服务才能使配置生效,这对一个正在运行的项目来说是非常不友好的。

基于上面这些问题,我们就需要配置中心的加入来解决这些问题

配置中心的思路是:

首先把项目中各种配置全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。

当各个服务需要获取配置的时候,就来配置中心的接口拉取自己的配置。

当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。

当加入了服务配置中心之后,我们的系统架构图会变成下面这样:

7.1 Nacos Config入门
1.导入依赖

com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.cloud spring-cloud-starter-bootstrap

2.在微服务中添加config的配置

注意:不能使用原来的 application.yml 作为配置文件,而是新建一个 bootstrap.yml 作为配置文件

spring:
application:
name: product
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos中心地址
file-extension: yml # 配置文件格式
profiles:
active: dev # 环境标识

3.在nacos中添加配置,然后把商品微服务application.yml配置复制到配置内容中.

4.注释本地的application.yam中的内容, 启动程序进行测试

7.2 配置动态刷新
只需一个注解@RefreshScope

1.在nacos中的product-service-dev.yaml配置项中添加下面配置:

appConfig:

name: product

  1. 在商品微服务中新增NacosConfigControlller.java

@RestController
@RefreshScope // 配置动态刷新
public class NacosConfigController {
@Value("${appConfig.name}")
private String appConfigName;
@RequestMapping("/nacosConfig1")
public String nacosConfig(){
return "远程信息:"+appConfigName;
}
}

7.3 配置共享
同一个微服务的不同环境之间共享配置
①环境不同
同一个product微服务可能有不同环境,例如开发环境、测试环境。
把开发环境和测试环境的配置相同的部分抽取出来,放到product.yml中。
把独有的配置放到自己的配置文件中,开发环境独有的server.port=8091放到product-dev.yml中,测试环境独有的server.port=8099放到product-test.yml中。
当程序运行时,会先读取product.yml文件覆盖到本地的配置文件中,然后再读取相应环境的配置文件再进行覆盖。
②端口号不同
product微服务进行了集群,端口分别为8081、8082、8083。
也是先抽取相同的配置放到product.yml中,然后再将不同的配置分别放到product-dev8081.yml、product-dev8082.yml、product-dev8083.yml中。
然后通过idea来配置,

不同微服务中间共享配置
用于网站外联

  1. 在nacos中定义一个DataID为global-config.yaml的配置,用于所有微服务共享

globalConfig: global

  1. 修改bootstrap.yaml

spring:
application:
name: product
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos中心地址
file-extension: yml # 配置文件格式
shared-configs:
- data-id: global-config.yaml # 配置要引入的配置
refresh: true
profiles:
active: test # 环境标识

7.4Sentinel规则持久化
1.导入依赖

com.alibaba.csp sentinel-datasource-nacos

2.修改bootstrap.yml文件,增加datasource的配置

spring:
sentinel:
datasource: # sentinel数据源
ds1: # 自定义连接名
nacos:
server-addr: localhost:8848 # nacos服务地址
data-id: sentinel-consumer-ds1 # nacos的dataId
group-id: DEFAULT_GROUP # 默认分组
data-type: json # 数据类型 json类型
rule-type: flow # flow表示流控规则

rule-type有五种的流控规则
authority (授权规则)
degrade (降级规则)
flow (流控规则)
param (热点规则)
system (系统规则)
3. 在Nacos的控制面板新建 sentinel-consumer-ds1 的DataId,具体配置内容如下
[
{
"resource":"/sentinel2", // 资源名
"limitApp":"default", // 默认来源
"grade": 1, // QPS
"count": 2, // 一秒2请求
"strategy": 0, // 直接流控
"controlBehavior": 0, // 快速失败
"clusterMode": false // 是不是集群
}
]

  1. 然后访问/sentinel2,可以看到在Sentinel的管控台中看到流控规则了.

posted on 2025-09-15 16:45  L_RZ  阅读(17)  评论(0)    收藏  举报