SpringCloude快速入门

发布于:2025-07-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

近期简单了解一下SpringCloude微服务,本文主要就是我学习中所记录的笔记,然后具体原理可能等以后再来深究,本文可能有些地方用词不专业还望包容一下,感兴趣可以参考官方文档来深入学习一下微服务,然后我的下一步学习就是docker和linux了。

nacos: Nacos 快速开始 | Nacos 官网

sentinel: home | Sentinel

seata: Seata Java Download | Apache Seata


单体架构

域名主要是绑定IP方便记名字,然后节点就是服务器的专业说法,当然一台服务器也可以同时部署数据库和应用(好处就是部署简单)

一台服务器性能有限,比如请求数量过多就不够了

集群架构(大量公司常用)

多个服务干同一件事就是集群,干不同事就是分布式

对服务器(节点)做的复制品即为副本,而这些服务器构成的一个整体就叫做集群

域名不能单独指向任何一个副本(否则和单体架构没区别)必须通过网关(一般也需要部署在一个服务器上边,比如Nginx),所以域名必须绑定网关的公网IP(网关需要完成功能:请求的路由,而且里边我记得conf配置文件可以配置访问的IP来实现负载均衡),数据库也可能产生瓶颈,所以依然可以多复制几个节点构成数据库集群。然后比如双十一流量大就可以扩容几个副本,然后等流量过去在释放服务器来释放资源。

缺点: 1.维护和模块化升级比较麻烦,牵一发而动全身,每次都要重新打包部署 2.如果涉及到多个语言交互就不能简单打jar包部署服务器了

分布式架构

每个应用/数据库可以按照业务功能模块拆成一个个小应用(微服务)还实现了数据隔离,比如订单找商品要,而不是直接连上商品的数据库

当然为了增加并发量,仍然可以使用副本思想,比如商品肯定使用的最多,那么每台服务器都部署一份,其余也可以看情况选择部署几份

注意:

1.一个服务器不能全部部署多个商品副本(微服务),因为如果这个服务器宕机其他服务器也不能正常工作了,即单点故障

2.如果需要访问的业务在别的服务器,那么就需要远程调用(类似HttpClient吧)获取返回的json数据即可(这是最常用的一种RPC方式)。

3.注册中心组件包含服务中心服务注册功能: 通过注册中心可以知道微服务在哪个服务器存在,并且调用时也可以从注册中心知道业务在哪里(服务发现),而且这时候还可以用到网关(使用负载均衡,比如一种模式是按顺序先后访问,还有按权重等)。

4.每个微服务都可以保存在配置中心来统一管理。

5.高并发情况下,如果一个微服务卡顿导致整个调用链卡顿(有很多个请求,就执行了很多调用链),然后可能导致资源耗尽,即服务器雪崩。所以需要服务熔断(可以设置规则,比如几秒内有多少占比卡顿,那么以后的请求即快速释放掉,防止占用资源)。


总结: 分布式就是一个大型应用被拆分成很多小应用分布部署在各个机器。一种架构(工作)方式

集群: 只是一种物理形态,只要部署在多个机器上就可以称为集群。

网关: 这里的请求路由就可以配置规则,比如以order开头就要转给订单所在的服务器(这时候需要问注册中心)

分布式事务: 比如下完订单和给用户加积分俩个是一个整体,如果是单架构,则@Transactional基本可以解决,现在因为每个数据库可能在不同服务器,所以就不能像以前那样。

上述的各种方法即可解决各种问题。

Nacos

1.服务注册

1.<packaging>pom</packaging>写上就可以管理其他子项目,并且自身不用写代码了,一般最外层管理版本,再来一层来写公共依赖,里边再重新写多个模块即可。

所有服务创建后都应该经过注册中心来注册服务

在bin目录下 startup.cmd -m standalone 以单机模式启动(集群以后再了解吧)

然后输入: localhost:8848/nacos 即可成功访问

由于每个微服务都继承了父类的springboot依赖,所以每个微服务也是springboot应用,然后也是定义application类,让springboot应用跑起来。配置的yml文件也写好基本就能成功了

<!--这里父项目指定了版本,所以不用写version,而且所管理的子项目也会一起引入依赖-->
<!--一般是外层的pom来引入方便统一管理,并且都需要这个依赖-->
<!--服务发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--远程调用-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

可以配置端口号,防止占用 --server.port=8084

2.服务发现

第一步就是查看已经注册的微服务

@SpringBootTest
public class DiscoveryTest {
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    NacosServiceDiscovery nacosServiceDiscovery;
​
    @Test
    void discoveryClientTest(){
        for (String service : discoveryClient.getServices()) {
            System.out.println(service);
            // 获取每个服务名下的相应的ip+端口
            discoveryClient.getInstances(service).forEach(instance -> {
                System.out.println("ip: " + instance.getHost() + " port: " + instance.getPort());
            });
        }
    }
}

3.远程调用(编码式)

实体类: 除了可以pojo,也可以写成bean

localhost:8081/order/create?userId=1&productId=6

    /**
     * 目前url其实也写死了,所有后续负载均衡要改进一下
     * @param productId
     * @return
     */
    private Product getProductFromRemote(Long productId) {
        // 1. 获取到商品服务所在的所有机器ip+port
        // 目前我门已经知道serviceId了,所以为了方便演示就不获取了
        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
        ServiceInstance serviceInstance = instances.get(0); // 随便用一个
        // 后边的即是定义的controller层地址
        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/" + productId;
        log.info("远程地址为: {}", url);
        // 2. 开始发送请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }

负载均衡

<!--订单要对访问的商品做负载均衡-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
哪个业务需要就引入

默认是轮询,如果想要制定其他规则,需要手写。

方式一: 底层原理
private Product getProductFromRemoteWithLoadBalancer(Long productId) {
    // 1. 获取到商品服务所在的所有机器ip+port
    // 目前我门已经知道serviceId了,所以为了方便演示就不获取了
    ServiceInstance choose = loadBalancerClient.choose("service-product");
    // 后边的即是定义的controller层地址
    String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + productId;
    log.info("远程地址为: {}", url);
    // 2. 开始发送请求
    Product product = restTemplate.getForObject(url, Product.class);
    return product;
}

方式二: @LoadBalanced注解

在需要进行负载均衡访问的配置类加上该注解就可以自动调用方法了

@Configuration
public class OrderConfig {
​
    /**
     * 线程安全,这样直接引入bean对象,这个方法就会自动调用哦
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
private Product getProductFromRemoteWithLoadBalancerAnnotation(Long productId) {
    // service-product会被动态替换成相应的ip和端口号
    String url = "http://service-product/product/" + productId;
    log.info("远程地址为: {}", url);
    // 2. 开始发送请求
    Product product = restTemplate.getForObject(url, Product.class);
    return product;
}

面试考点

总结就是如果第一次调用时,会先向注册中心拿到ip和port并存储到缓存中,之后如果注册中心没有更新,则会直接向缓存中拿ip和port,这样不仅提高了效率,还保证了注册中心宕机也会使用(当然前提你的ip和port都是能够正常访问的而且第一次不成功没有存缓存,所以就失败了)

4.配置中心

修改配置时只需在配置中心修改,这样就能实时推送,并且不需要停机重新打包

配置中心基本使用:

yml格式的话,信息配置都要正确,也可以使用Properties。这里导入的nacos不写地址就是默认的

方式一: @Value+@RefreshScope 获取绑定值

@value就是获取yml中自定义绑定的属性值

@RefreshScope可以激活配置属性的自动刷新功能,不用重启就能拿到配置集的信息。

方式二: @ConfigurationProperties

专门抽取一个类来获取绑定的属性值,可以实现无感自动刷新。

方式三 编码监听配置变化

面试考点

后导入优先,外部优先。

1.如果配置中心和本身项目中定义的yml里的数据重复了,优先使用配置中心。

2.如果import: nacos:service-product.yml, classpath:application.yml,则后边的会覆盖前面的

5.数据隔离

nacos: Namespace(命名空间: 区分多种环境)=>Group(分组: 区分多套微服务)=>Data-id(数据集: 区分多种配置)

而在springboot中可以让开发、测试、生成环境都绑定一套名称空间,项目启动时任意激活一个环境就可以按需加载使用。

注意其实命名都行,不过尽量规范就行,而且这个其实也不是真实的微服务名称,只不过方便统一管理

小结

6.OpenFeign

远程调用(声明式)(重点)

补充小知识
@FeignClient("service-product") 可以指定微服务,如果微服务不存在的话
@FeignClient(value = "test-client", url = "http://xxx.com" ) 我们指定的url是协议加域名,后面的请求路径则mvc注解来写
这里:前面的即为协议-http,而后边第一个/前面的即为域名xxx.com或者localhost:8080,最后的一部分即为请求路径(比如@GetMapping(/ order)里边指定的内容),而请求路径后面也可能带上参数来相互传递

小技巧

1.调用业务api: 如果是我们自己写得客户端想要用openfeign在一个业务调用其他业务的方法,可以直接将该controller层该方法和mvc注解复制一下即可,这时只要传递的参数正确一般都能正常访问。

2.调用第三方api: 这时就需要参照第三方接口文档来定义。

3.客户端负载均衡: openFeign其实已经利用负载均衡算法选了一个地址调用。我们可以自己调整来解决高并发。

4.服务端负载均衡: 第三方一般只暴露了一个固定的地址,所以其内部自行进行负载均衡,我们就无需考虑。

进阶配置

日志

首先需要配置一下yml:

超时控制

我们即使不设置,连接超时默认10s,读取超时默认60s。

在yml中配置一下即可。

重试机制

如果远程调用超时失败后,还可以多次尝试,仍然失败则返回错误。

如上图所示的默认配置: 初始定位100mm,那么每次失败后的间隔时间就会100依次乘1.5,最大间隔定义为1s,然后尝试次数为5次。

方式一: yml中配置

方式二: 手动配置

@Configuration // 表示可以有多个Bean
public class OrderConfig {
​
    /**
     * 线程安全,这样直接引入bean对象,这个方法就会自动调用哦
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
​
    /**
     * 配置事务,yml中也可以配置
     * @return
     */
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL; // 固定写法,注意包导入feign下的
    }
​
    @Bean
    Retryer retryer(){
        return new Retryer.Default(10, 1000, 4);
    }
}

拦截器

@Component加上就可以全局拦截自动生效,如果想要局部拦截可以不加,直接在yml中配置。

包括请求拦截器(常用)和响应拦截器(用的较少)

@Component
public class XTokenInterceptor implements RequestInterceptor {
    /**
     * 注意这个继承的是feign里边的拦截器方法
     * @param requestTemplate (详细信息封装在了这个模板中)
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        System.out.println("拦截器启动");
        requestTemplate.header("X-token", UUID.randomUUID().toString());
    }
}

Fallback兜底

注意此时必须用到sentinel才可以

/**
 * 也是实现了自己写得接口,只有失败才会触发
 * @FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
 * 实现接口内的上述注解也不能少
 * 也要放在容器中
 */
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
    @Override
    public Product getByProductId(Long productId) {
        System.out.println("兜底回调...");
        Product product = new Product();
        product.setId(productId);
        product.setPrice(new BigDecimal("999"));
        product.setProductName("隐藏商品");
        product.setNum(0);
​
        return product;
    }
}

7.Sentinel(可保护资源)

定义的规则可以存到Nacos、Zookeeper等配置中心来持久化使用

服务保护(限流||熔断降级)

一般三步:1.定义资源 2.定义资源的保护规则 3.如何处理违反规则的异常

首先需要下载sentinel-dashboard-1.8.8.jar包然后cmd java - jar启动即可(如果想修改端口也可以写),然后默认用户名和密码都是sentinel

然后我们的项目微服务需要配置连接上sentinel控制台即可,然后每个微服务都需要保护,所以依赖写在最外边。

/**
 * controller中是默认都为资源
 * 然后这个业务层我们也可以手动设置资源名让它也被保护起来
 */
@SentinelResource("createOrder")
@Override
public Order createOrder(Long productId, Long userId) {...}

当然在控制台指定规则后所显示的默认错误页面不是json数据,传递给前端不友好,所以我们可以自定义异常处理器来解决

异常处理

如果不需要返回兜底数据,那么我们直接全局异常处理器即可显示,而下方的各种异常如果使用了都可以覆盖这个最外层的处理

/**
 * 是下边这俩个异常的结合
 * @ControllerAdvice
 * @ResponseBody
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
​
    @ExceptionHandler(Exception.class) // 捕捉所有异常
    public R handleException(Exception e) {
        return R.error("全局异常处理");
    }
}

处理Web接口

注意: 此异常处理器只能处理默认的资源(比如controller下的各种mvc请求),而@SentinelResource自定义的资源不会处理,因为俩者底层不太一样。

在exception包下新建一个处理类即可,不过注意由于sentinel在内存中,所以每次重启项目都需要重新配置规则

@Component // 也是放进容器中即可
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    private ObjectMapper objectMapper = new ObjectMapper();
    /**
     * resourceName就是我们控制台定义的资源名称
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       String resourceName, BlockException e) throws Exception {
        response.setContentType("application/json;charset=utf-8"); // 设置编码格式,最开始就设置
        PrintWriter writer = response.getWriter();
        R error = R.error(resourceName + "被Sentinel限流了,请稍后再试,原因: " + e.getMessage()
                + "  错误类: " + e.getClass());
        String json = objectMapper.writeValueAsString(error); // 可以用jackson来转换成json字符串
        // 阿里的fastjson也行,不过jackson框架中常使用
        writer.write(json);
        writer.flush();
        writer.close();
    }
}

处理@SentinelResource(一般标注在非controller层)
    /**
     * controller中是默认都为资源
     * 然后这个业务层我们也可以手动设置资源名让它也被保护起来
     * blockHandler = "createOrderFallback"注意需要指定一下就会调用我们写得兜底回调
     * 一般用这个blockHandler就行
     */
    @SentinelResource(value = "createOrder", blockHandler = "createOrderFallback")
    @Override
    public Order createOrder(Long productId, Long userId) {
        // 获取商品
//        Product product = getProductFromRemoteWithLoadBalancerAnnotation(productId);
        Product product = productFeignClient.getByProductId(productId);
        Order order = new Order();
        order.setId(1L);
        // 获取总金额
        // 因为类型不同,所以转换一下
        BigDecimal sum = product.getPrice().multiply(new BigDecimal(product.getNum()));
        order.setTotalAmount(sum);
        order.setUserId(userId);
        order.setNickName("清然");
        order.setAddress("万达超市");
        // 远程查询商品列表,也是转换一下
        order.setProductList(Arrays.asList(product));
​
        return order;
    }
​
    /**
     * 参数写法基本和我们的业务保持一致,然后加上BlockException可以了解出现了什么异常
     * 区分一下这是保护方法的兜底回调,以前的是OpenFeign超时配置的兜底回调
     */
    public Order createOrderFallback(Long productId, Long userId, BlockException e){
        Order order = new Order();
        order.setId(0L);
        order.setTotalAmount(new BigDecimal("0"));
        order.setUserId(userId);
        order.setNickName("雅小姐");
        order.setAddress("异常位置: " + e.getClass());
​
        return order;
    }

处理OpenFeign

这个我们之前写过,所以也会自动生效的

/**
 * 订单中专门用Feign来调用商品微服务
 */
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class) // 订单客户端专门用来调用商品服务
public interface ProductFeignClient {
​
    // mvc注解此时标注在Feign上,所以是发送请求
    @GetMapping("/product/{id}") // 这个是路径,不包含http
    Product getByProductId(@PathVariable("id") Long productId);
    // 如果用String则单纯返回json字符串,而用Product则会自动封装成该对象
}
/**
 * 也是实现了自己写得接口,只有失败才会触发
 * @FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
 * 实现接口内的上述注解也不能少
 * 也要放在容器中
 */
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
    @Override
    public Product getByProductId(Long productId) {
        System.out.println("兜底回调...");
        Product product = new Product();
        product.setId(productId);
        product.setPrice(new BigDecimal("999"));
        product.setProductName("隐藏商品");
        product.setNum(0);
​
        return product;
    }
}

注意这个用到的是OpenFeign的注解,所以写法是接口加继承,如果用SentinelResource注解则可以在一个类下写

Sphu

这个就是硬编码的方式来处理,手动try-catch

try (Entry entry = SphU.entry("orderService")) {
        // 处理订单逻辑
        System.out.println("订单处理中...");
} catch (
BlockException ex) {
        // 触发限流,返回友好提示
        System.out.println("当前请求过多,请稍后重试");
}

流控规则

阈值类型: 1.QPS(每秒的请求数,底层通过计数器来统计,轻量速度快) 2.并发线程数(配合线程池,控制线程池中的数量,可能涉及线程调度)

是否集群: 1.单机均摊(比如每个机器都1s内可以处理5个请求) 2.总体阈值(1s内一共可以处理5个请求)

流控模式(只有快速失败才适用)

1.直接(默认模式,这样只要资源访问量大就做限流处理)

2.链路策略: 如果是俩个上下文,则必须先在yml中关闭,然后这样每个不同请求路径都是一个链路,否则所有请求默认都在一个下边

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
#            loggerLevel: full # 日志级别
            connectTimeout: 4000 #以mm为单位
            readTimeout: 2000 #2s
          service-product: # 精确设置微服务名
#            loggerLevel: full # 日志级别
            connectTimeout: 5000 #以mm为单位
            readTimeout: 2000 #2s
#            request-interceptors:
#              - com.daxz.order.interceptor.XTokenInterceptor
#            retryer: feign.Retryer.Default
    sentinel:
      transport:
        dashboard: localhost:8080 # 指定sentinel控制台地址
      eager: true # 关闭懒加载(指只有真正发送请求时才开始建立连接,这样可以适当减小开销)为了方便,我们先关闭
      web-context-unify: false # 关闭,这样可以开启链路模式
​
feign:
  sentinel:
    enabled: true

3.当系统出现资源竞争时,可以采用关联策略。(可以指定哪个资源要优先进行)

流控效果

1.快速失败: 没有超出阈值就交给业务处理,否则直接抛出异常

2.Warm Up: 有冷启动时间

3.匀速排队: 表示QPS的阈值在1s内均分,比如QPS=2,则500mm一个,不支持QPS>1000,因为这样失效,而且会设置超时时间,没排上队就抛出异常即可。

熔断规则

熔断一般用于远程调用(所以尽量自己编写好兜底回调),并且熔断配置在调用方

我们初始自定义的超时时间或者编写的程序出现错误(比如1/0)都可以叫做异常,然后兜底返回。

错误纠正: 上边的统计时长应该是熔断策略开始统计的时间,超过最小请求数才会进行该过程。

最大RT就是预定时长。

热点规则

实际上比流控规则就多了一条对指定参数的限制,更细致一点

// 测试了一个秒杀方法
@GetMapping("/seckill")
// 此时又重新起个名字,因为热点规则对Web不是自动适配的,还需要手写
@SentinelResource(value = "seckill-order", fallback = "seckillFallback")
public Order seckill(Long userId, Long productId) {
    Order order = orderService.createOrder(productId, Long.MAX_VALUE);
    return order;
}
​
// 上述的兜底回调方法
public Order seckillFallback(Long userId, Long productId, Throwable e) {
    Order order = new Order();
    order.setId(productId);
    order.setUserId(userId);
    order.setAddress("秒杀失败,原因是: " + e.getMessage());
    return order;
}

参数索引也是从0开始表示代码中的第一个参数。

当然如果required = false,表示可以没有参数,这样没有的话就不会触发相应的规则,如果defaultValue = ... 仍然有默认参数会除法规则

注意: @SentinelResource中的 blockHandler可以专门处理BlockException流控异常,如果使用fallback需要写为Throwable所有异常

因为fallback还能处理1/0等业务异常,所以范围比BlockException更大。

授权规则和系统规则

这些基本没啥用,因为基本都需要相互访问,授权规则没啥用,而系统规则则不太精确,以后k8s会更加精确,所以系统规则也不用管。

持久化

由于sentinel是在内存中,所以idea每次重启都会丢失,这时候只需要将其配置存储到nacos并且还可以连接数据库,这样就不会消失了。

8.Gateway

网关

属于架构部分,所以可以直接创建在最外层即可,而且也能自动负载均衡

<dependencies>
    <!--也需要nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--响应式网关-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--也需要负载均衡-->
    <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

路由

指routes下的id、uri、断言规则、过滤器等每组整体称作路由

配置好ym就可以简单测试访问网关。

server:
  port: 80
spring:
  profiles:
    include: route
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
#这样可以通过网关来访问 localhost/api/order/请求路径即可访问
spring:
  cloud:
    gateway:
      routes:
        - id: order-route # 我们起的名称
          uri: lb://service-order # lb表示负载均衡的访问我们项目的微服务名称
          predicates: # 表示遵守的规则,也是各种集合,比如路径规则
            - Path=/api/order/**
          # 还有更多规则,以后可以慢慢学
        - id: product-route
          uri: lb://service-product
          predicates:
            - Path=/api/product/**

原理

首先配置的id是全局唯一,然后满足断言规则(可以有多个),然后转给uri目的地,这其中还可以有Filter过滤器。

注意: 如果不指定order排序(数字越小优先级越高),那么就自定向下匹配,如果满足某id下的断言后,其余就不会生效。

spring:
  cloud:
    gateway:
      routes:
        # 这就是个例子,当然很多网站可能都要登录,所以匹配时可能也是错误页面
        - id: test-route
          uri: https://www.bilibili.com/ #https://cn.bing.com
          predicates:
            - Path=/**
          order: 10
        - id: order-route # 我们起的名称
          uri: lb://service-order # lb表示负载均衡的访问我们项目的微服务名称
          predicates: # 表示遵守的规则,也是各种集合,比如路径规则
            - Path=/api/order/**
          # 还有更多规则,以后可以慢慢学
          order: 0
        - id: product-route
          uri: lb://service-product
          predicates:
            - Path=/api/product/**
          order: 1
#如果不指定order,则顺序匹配了规则,这样即使localhost/api/order/config也不会正常访问我们的地址

断言

短写法
  • Path=/api/order/** yml中各种名称来自于断言工厂xxxRoutePredicateFactory,然后里边Config.class基本就是这些名称来源了

长写法

各种规则

上边是一个Query例子。

自定义断言工厂

这样自定义配置后yml就可以用到了,断言逻辑,还有我们参数的顺序也是自己指定,然后长短写法都ok

过滤器

大多就是修改请求和响应,比如请求头、响应体。

如果想要微服务都生效可以使用默认filter

spring:
  cloud:
    gateway:
      routes:
        # 这就是个例子,当然很多网站可能都要登录,所以匹配时可能也是错误页面
        - id: test-route
          uri: https://cn.bing.com
          predicates:
            - Path=/**
          order: 10
        - id: order-route # 我们起的名称
          uri: lb://service-order # lb表示负载均衡的访问我们项目的微服务名称
          predicates: # 表示遵守的规则,也是各种集合,比如路径规则
            - Path=/api/order/** # 匹配后也会按照这个请求发给微服务,如果不想全部修改微服务请求就可以重写路径
          filters: #RewritePath=/api/order/?(?<segment>.*), /${segment} 这种就是正则来重写路径
#            - AddResponseHeader=X-Response-saber, Love 也可以自定义请求头信息
          # 还有更多规则,以后可以慢慢学
          order: 0
        - id: product-route
          uri: lb://service-product
          predicates:
            - Path=/api/product/**
          order: 1
      default-filters: # 全局过滤器,所有请求都会经过
        - AddResponseHeader=X-Response-saber, Love
#        - AddRequestHeader=X-Request-role, violet 这个是提供给后端服务,所以浏览器不可见
#如果不指定order,则顺序匹配了规则,这样即使localhost/api/order/config也不会正常访问我们的地址

也可以自定义一个全局filter:

package com.daxz.filter;
​
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
/**
 * 用响应完成时间-请求开始时间就能知道响应时间(即请求耗费了多久)
 */
@Component // 也要加注入容器
@Slf4j
public class RtGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // filter中exchange包装了请求和响应
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String uri = request.getURI().toString(); // 拿到请求地址
        long start = System.currentTimeMillis();
        log.info("请求地址[{}], 请求时间: {}", uri, start);
        // =================以上是前置逻辑,然后我们就可以用chain放行=================
        // 不过由于Mono是异步响应式,所以不能单纯在下方写后置逻辑,会瞬间执行
        // 流式处理加链式方法
        Mono<Void> filter = chain.filter(exchange).doFinally((result) -> {
            long end = System.currentTimeMillis();
            log.info("请求地址[{}], 响应时间: {}", uri, end - start);
                });
        // ================如果是用传统的servlet则需要在下面写后置逻辑================
        return filter;
    }
​
    // 可以指定顺序
    @Override
    public int getOrder() {
        return 0;
    }
}

自定义过滤器工厂

和上边的断言工厂也一样,就是yml中可以写自定义的内容,具体的原理以后可以再深入学习

全局跨越

原始方案是给每个controller代码都加上@CrossOrigin即可解决,不过有些麻烦,所以可以编写跨域filter来解决跨域问题

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origin-patterns: '*'
            allowed-headers: '*'
            allowed-methods: '*'

9.Seata

以前是一条连接里边的事务才可以一起提交,一起回滚。传统事务是连接级别,所以微服务自治数据库即使加上事务,有多个连接则没有用

注意,目前是每个微服务都有独立的数据库,而不是一个数据库里边有微服务对应的多张表,那样就是单纯的单体/集群架构了。

当然如果是对一个数据库中的各种表进行操作,引入事务注解还是可以操作的。

监听8091,但也提供web页面在7091,然后高版本好像有些问题,推荐2.1

必须引入TC才能管理事务

resource下配置file.conf即可连上seata,具体原理可以单独学学

service {
  #transaction service group mapping
  vgroupMapping.default_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

只需要在最大的方法事务,标注全局事务即可@GlobalTransactional(可以在service层标注,因为这里集中处理了事务)

@GlobalTransactional原理

二阶提交协议

前镜像: 在调用sql方法前来执行一次查询操作

后镜像: 更新后在执行一次查询操作

还牵扯到全局锁,可以多看几遍了解原理

全部成功后才会删除undo_log。

肯定效率不太高,以后深入学习再了解吧。

四种事务模式
AT模式
XA模式

AT和XA都是自动的,不过XA不会出现脏读(可以搜搜),但效率低

TCC模式

这就是广义事务(分三步),狭隘事务就是单纯指对数据库操作,当然TCC需要手写

Saga模式

长事务,具体原理可以上网了解


网站公告

今日签到

点亮在社区的每一天
去签到