在微服务中如何实现全链路的金丝雀发布?

目录

1. 什么金丝雀发布?它有什么用?

2.如何实现全链路的金丝雀发布

2.1 负载均衡模块

2.2 网关模块

2.3 服务模块

2.3.1 注册为灰色服务实例

2.3.2 设置负载均衡器

2.3.3 传递灰度发布标签

2.4 其他代码

2.4.1 其他业务代码

2.4.2 pom.xml 关键代码

2.4.3 application.yml 相关代码

3. 验证全链路金丝雀发布的实现效果


1. 什么金丝雀发布?它有什么用?

        金丝雀发布(Canary Release,也称为灰度发布)是指在软件或服务发布过程中,将新版本的功能或服务以较小的比例引入到生产环境中,仅向部分用户或节点提供新功能的一种发布策略。

        而在传统的全量发布中,新版本将会立即部署到所有用户或节点上。金丝雀发布的核心思想是逐步推进,监测新版本的稳定性和性能,以确保在全面发布之前能够解决潜在的问题。

假设某款在线多人游戏决定上线一个全新的多人模式功能。在传统的全量发布中,它会将这个新功能立即部署到所有玩家的游戏客户端中,然后在全面发布后等待用户的反馈。而使用金丝雀发布,它的发布流程就变成了这样:

  1. 内测阶段

    • 游戏开发团队首先将新多人模式功能引入到游戏的内测版本中,但仅向少数特定的内测玩家提供。
    • 这些内测玩家是经过筛选或自愿参与的,他们了解可能会遇到问题,并愿意分享反馈。
    • 内测玩家可以在一定时间内使用新功能,并向开发团队报告问题、提供建议和反馈意见。
  2. 监测和改进

    • 游戏开发团队密切关注内测玩家的游戏体验、性能和稳定性。
    • 如果在内测期间发现了问题,团队可以及时进行修复和改进,并确保新功能在全面发布前达到高质量标准。
  3. 逐步扩展

    • 在确认新功能在内测阶段表现良好后,开发团队逐步扩展金丝雀发布的范围。
    • 他们可以将新功能提供给更多的玩家,但仍然限制在一小部分,比如10%的玩家。
    • 这一阶段被称为金丝雀发布的初期阶段,新功能仅对一小部分用户可见。
  4. 全面发布

    • 在经过一系列逐步扩展和监测后,开发团队最终将新多人模式功能发布给了所有玩家。
    • 此时,新功能已经通过了多轮测试和改进,用户体验较好,且潜在问题得到了解决。

从上述游戏上线新功能的金丝雀发布流程中能看出,金丝雀发布相比传统的全量发布有以下好处:

  • 逐步引入新功能,降低全面发布的风险。
  • 及时获取内测玩家的反馈,加速问题的修复。
  • 确保新功能在全面发布时达到高质量标准。
  • 提供更好的用户体验,减少潜在问题对所有用户的影响。

2.如何实现全链路的金丝雀发布

Spring Cloud 全链路金丝雀发布的实现思路图如下:

 金丝雀发布的具体实现步骤大致分为以下几步:

  1. 前端程序在灰度测试的用户 Header 头中打上标签,例如在 Header 中添加 "gray-tag:true",表示要访问灰度服务,其他则为正式服务。(前端)
  2. 在负载均衡器 Spring Cloud LoadBalancer 中,拿到 Header 中的 "gray-tag" 进行判断,如果此标签不为空,并且等于 "true" 的话,则表示要访问灰度发布的服务,否则只访问正式的服务。(客户端负载均衡)
  3. 在网关 Spring Cloud Gateway 中,将 Header 标签 "gray-tag:true" 传递到下一个调用的服务。(网关)
  4. 后续的服务调用中,还需要做两件事:(内部服务)
    1. 在 Spring Cloud LoadBalancer 中,判断灰度发布标签,将请求分发给对应的服务。
    2. 在内部的服务调用过程中,传递灰度发布标签。

由此可见,全链路的灰色发布只需要解决两个大问题:

1. Gateway 中的问题

  • Gateway 的调度转发问题。
  • Gateway 灰色发布标签的传递问题。

2. 内部服务中的问题

  • 服务的灰度转发问题。
  • 服务内部灰色发布标签的传递问题。

【金丝雀发布代码案例】

根据 Spring Cloud 全链路金丝雀发布的实现思路图来编写代码,

创建 Spring 多模块项目,然后准备 7 个模块:user-service、new-user-service、order-service、log-service、new-log-service、gray-loadbalancer、gateway。

2.1 负载均衡模块

操作 gray-loadblancer 模块,这个模块作为一个公共模块,可以不需要启动类。

① 自定义负载均衡器

这里可以参考默认的轮询负载均衡策略里面的实现:

  1. 实现 ReactorServiceInstanceLoadBalancer 接口
  2. 复制其他代码,修改关键地方的类名
  3. 重写 getInstanceResponse 方法
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;/*** 定义灰度发布的负载均衡算法*/public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final Log log = LogFactory.getLog(GrayLoadBalancer.class);private final String serviceId;private AtomicInteger position;  // 下标private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;this.position = new AtomicInteger(new Random().nextInt(1000));}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,request);});}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,List<ServiceInstance> serviceInstances,Request request) {Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances,request);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,Request request) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else {// 灰度业务的实现// 0.得到 Request 对象 [通过方法参数的传递拿到此对象]// 1.从 Request 对象的 Header 中得到灰度标签RequestDataContext requestDataContext = (RequestDataContext) request.getContext();HttpHeaders headers = requestDataContext.getClientRequest().getHeaders();// 获取名为 "gray-tag" 的头部信息的值List<String> headersList = headers.get(GlobalVariable.GRAY_TAGE);if (headersList != null && !headersList.isEmpty() &&headersList.get(0).equals("true")) { // 灰度请求// 灰度列表List<ServiceInstance> grayList = instances.stream().filter(i -> i.getMetadata().get(GlobalVariable.GRAY_TAGE) != null &&i.getMetadata().get(GlobalVariable.GRAY_TAGE).equals("true")).toList();if(!grayList.isEmpty()) {instances = grayList;}} else {  // 正式节点// 2.将实例进行进行分组 【生产服务列表|灰度服务列表】instances = instances.stream(). // 取反filter(i -> i.getMetadata().get(GlobalVariable.GRAY_TAGE) == null ||!i.getMetadata().get(GlobalVariable.GRAY_TAGE).equals("true")).toList();}// 3.使用负载均衡算法选择上一步列表中的某个节点int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);}}
}
/*** 全局变量*/
public class GlobalVariable {public static final String GRAY_TAGE = "gray-tag";
}

② 封装负载均衡器

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;/*** 封装灰度发布负载均衡器*/
public class GrayLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> grayLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);// 灰度发布的负载均衡器return new GrayLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}
}

2.2 网关模块

通过全局过滤器,来判断或设置灰度标识,

import com.loadbalancer.gray.GlobalVariable;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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
public class LoadBalancerFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 得到 request、response 对象ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();// 判断灰度标签String tag = request.getQueryParams().getFirst(GlobalVariable.GRAY_TAGE);if(tag != null) {// 设置灰度标识response.getHeaders().set(GlobalVariable.GRAY_TAGE,"true");}// 此步骤正常,执行下一步return chain.filter(exchange);}
}

2.3 服务模块

2.3.1 注册为灰色服务实例

将测试版的服务,注册为灰色服务实例:new-user-service、new-log-service

spring:application:name: user-service-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosmetadata: {"gray-tag": "true"}  # 金丝雀标识
server:port: 0

2.3.2 设置负载均衡器

在服务启动类上设置负载均衡和开启 OpenFeign 服务:user-service、new-user-service、order-service。

import com.loadbalancer.gray.GrayLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients
@LoadBalancerClients(defaultConfiguration =GrayLoadBalancerConfig.class)
public class UserServiceApplication {public static void main(String[] args) {SpringApplication.run(UserServiceApplication.class, args);}}

 在网关模块中设置负载均衡,

import com.loadbalancer.gray.GrayLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;@SpringBootApplication
@LoadBalancerClients(defaultConfiguration =GrayLoadBalancerConfig.class)
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}}

2.3.3 传递灰度发布标签

在服务内部传递灰度发布标签:user-service、new-user-service、order-service

方式一:传递request中所有的header,所有的header中就包含了灰度发布标签。

import feign.RequestInterceptor;
import feign.RequestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.util.Enumeration;@Component
public class FeignRequestInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {// 从 RequestContextHolder 中获取 HttpServletRequestServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 传递所有的 header,就包含了灰度发布标签Enumeration<String> headerNames = request.getHeaderNames();while(headerNames.hasMoreElements()) {String key = headerNames.nextElement();String value = request.getHeader(key);requestTemplate.header(key,value);}}
}

方式二:只传递header中的灰度发布标签

import feign.RequestInterceptor;
import feign.RequestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;@Component
public class FeignRequestInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {// 从 RequestContextHolder 中获取 HttpServletRequestServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();// 获取 RequestContextHolder 中的信息Map<String,String> headers = getHeaders(attributes.getRequest());// 放入 openfeign 的 requestTemplate 中for(Map.Entry<String,String> entry : headers.entrySet()) {requestTemplate.header(entry.getKey(), entry.getValue());}}/*** 获取原请求头*/private Map<String,String> getHeaders(HttpServletRequest request) {Map<String,String> map = new LinkedHashMap<>();Enumeration<String> enumeration = request.getHeaderNames();if(enumeration!=null) {while(enumeration.hasMoreElements()) {String key = enumeration.nextElement();String value = request.getHeader(key);map.put(key,value);}}return map;}
}

2.4 其他代码

2.4.1 其他业务代码

① user-service 模块的 controller

import com.example.userservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate OrderService orderService;@RequestMapping("/getname")public String getName() {String result = orderService.getOrder();return "正式版:User Service getName." +result;}
}

② user-service 模块的 service

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;@FeignClient("order-service-gray")
@Service
public interface OrderService {@RequestMapping("/order/getorder")public String getOrder();
}

③ new-user-service 模块的 controller

import com.example.newuserservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate OrderService orderService;@RequestMapping("/getname")public String getName() {String result = orderService.getOrder();return "测试版:User Service getName." +result;}
}

④ new-user-service 模块的 service

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;@FeignClient("order-service-gray")
@Service
public interface OrderService {@RequestMapping("/order/getorder")public String getOrder();
}

⑤ order-service 模块的 controller

import com.example.orderservice.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate LogService logService;@RequestMapping("/getorder")public String getOrder() {String result = logService.getLog();return "Do OrderService getOrder Method." +result;}
}

⑥ order-service 模块的 service

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;@Service
@FeignClient("log-service-gray")
public interface LogService {@RequestMapping("/log/getlog")public String getLog();
}

⑦ log-service 模块的 controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/log")
public class LogController {@RequestMapping("/getlog")public String getLog() {return "正式版:Log Service getLog";}
}

⑧ new-log-service 模块的 controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/log")
public class LogController {@RequestMapping("/getlog")public String getLog() {return "测试版:Log Service getLog";}
}

2.4.2 pom.xml 关键代码

① 父模块的 pom.xml

modules 中先加载服务调用链中靠后的服务,

<packaging>pom</packaging><!-- ....省略 --><!-- 注意打包顺序 -->
<modules><module>gray-loadbalancer</module><module>gateway</module><module>new-log-service</module><module>log-service</module><module>order-service</module><module>user-service</module><module>new-user-service</module>
</modules><!-- ....省略 --><dependencies><!-- nacos服务注册 --><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-test</artifactId><scope>test</scope></dependency><!-- 负载均衡 --><dependency><groupId>com.example</groupId><artifactId>gray-loadbalancer</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
</dependencies>

② user-service 模块的 pom.xml

<parent><groupId>com.example</groupId><artifactId>gray-demo</artifactId><version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
</dependencies>

③ new-user-service 模块的 pom.xml

<parent><groupId>com.example</groupId><artifactId>gray-demo</artifactId><version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>com.example</groupId><artifactId>gray-loadbalancer</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
</dependencies>

③ order-service 模块的 pom.xml

<parent><groupId>com.example</groupId><artifactId>gray-demo</artifactId><version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>com.example</groupId><artifactId>gray-loadbalancer</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
</dependencies>

④ log-service 和 new-log-service 模块的 pom.xml

<parent><groupId>com.example</groupId><artifactId>gray-demo</artifactId><version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

⑤ gray-loadbalancer 模块的 pom.xml

<parent><groupId>com.example</groupId><artifactId>gray-demo</artifactId><version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

⑥ gateway 模块的 pom.xml

<parent><groupId>com.example</groupId><artifactId>gray-demo</artifactId><version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>com.example</groupId><artifactId>gray-loadbalancer</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
</dependencies>

2.4.3 application.yml 相关代码

① user-service 模块的 application.yml

spring:application:name: user-service-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacos
server:port: 0

② new-user-servicce 模块的 application.yml

spring:application:name: user-service-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosmetadata: {"gray-tag": "true"}  # 金丝雀标识
server:port: 0

③ order-servicce 模块的 application.yml

spring:application:name: order-service-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacos
server:port: 0

④ log-servicce 模块的 application.yml

spring:application:name: log-service-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacos
server:port: 0

⑤ new-log-servicce 模块的 application.yml

spring:application:name: log-service-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosmetadata: {"gray-tag": "true"}  # 金丝雀标识
server:port: 0

⑥ gateway 模块的 application.yml

spring:main:web-application-type: reactive  # Spring Web 和 reactive web 冲突application:name: gateway-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosregister-enabled: false  # 网关不需要注册到 nacosgateway:routes:- id: user-serviceuri: lb://user-service-graypredicates:- Path=/user/**
server:port: 10086

3. 验证全链路金丝雀发布的实现效果

按顺序启动 log、order、user 的正式及测试服务,以及 gateway 模块,

使用 Postman 来验证全链路金丝雀发布的实现效果:

1. 请求头中不带 "gray-tag" 灰度标签,访问正式版服务

验证结果:无论访问多少次,不管是否服务集群,只要请求头中不带 "gray-tag" 灰度标签,只能访问到正式版的服务。

2. 请求头中带上 "gray-tag" 灰度标签,并且值为 true,访问测试版服务

验证结果:无论访问多少次,不管是否服务集群,只要请求头中带上 "gray-tag" 灰度标签,并且值为 true ,就只能访问到测试版的服务。


至此,微服务中全链路的金丝雀发布就实现好了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/226265.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TrustZone之可信操作系统

有许多可信内核&#xff0c;包括商业和开源的。一个例子是OP-TEE&#xff0c;最初由ST-Ericsson开发&#xff0c;但现在是由Linaro托管的开源项目。OP-TEE提供了一个功能齐全的可信执行环境&#xff0c;您可以在OP-TEE项目网站上找到详细的描述。 OP-TEE的结构如下图所示&…

申请sectigo和certum的IP证书注意事项

IP数字证书可以为只有公网IP地址的站点提供网站传输信息加密服务&#xff0c;一方面可以消除用户在浏览器访问网站时的“不安全”提示&#xff0c;另一方面现在主流浏览器会优先收录安装了数字证书的网站&#xff0c;为公网IP地址网站安装IP证书有利于提升网站SEO&#xff08;搜…

数据结构学习 Leetcode474 一和零

关键词&#xff1a;动态规划 01背包 一个套路&#xff1a; 01背包&#xff1a;空间优化之后dp【target1】&#xff0c;遍历的时候要逆序遍历完全背包&#xff1a;空间优化之后dp【target1】&#xff0c;遍历的时候要正序遍历 目录 题目&#xff1a; 思路&#xff1a; 复杂…

Seata 中封装了四种分布式事务模式,分别是: AT 模式, TCC 模式, Saga 模式, XA 模式,

文章目录 seata概述Seata 中封装了四种分布式事务模式&#xff0c;分别是&#xff1a;AT 模式&#xff0c;TCC 模式&#xff0c;Saga 模式&#xff0c;XA 模式&#xff0c; 今天我们来聊聊seata seata 概述 在微服务架构下&#xff0c;由于数据库和应用服务的拆分&#xff0c…

Vue3-27-路由-路径参数的简单使用

什么是路径参数 在路由配置中&#xff0c;可以将【参数】放在【路由路径】中&#xff0c; 从而实现&#xff0c;同一个 路由&#xff0c;同一个组件&#xff0c;因路径参数不同&#xff0c;可以渲染出不同的内容。特点 &#xff1a; 1、当携带不同路径参数的路由相互跳转时&am…

React 路由

引言 在我们之前写的页面当中&#xff0c;用我们的惯用思维去思考的话&#xff0c;可能会需要写很多的页面&#xff0c;例如做一个 tab 栏&#xff0c;我们可能会想每个选项都要对应一个 HTML 文件&#xff0c;这样会很麻烦&#xff0c;甚至不友好&#xff0c;我们把这种称为 …

把这些软件测试经典面试题!全背下来,拿offer就像喝水一样!

1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 兼容测试主要是检查软件在不同的硬件平台、软件平台上是否可以正常的运行&#xff0c;即是通常说的软件的可移植性。兼容的类型&#xff0c;如果细分的话&#xff0c;有平台的兼容&#xff0c;网络兼容&am…

SpingBoot的项目实战--模拟电商【2.登录】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringBoot电商项目的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.功能需求 二.代码编写 …

Illustrator脚本 #015 自动角线

这是一个在画板上自动生成辅助线和角线的脚本&#xff0c;只要单击最右边按钮运行脚本即可。 绿色的为参考线及出血线。 #target "Illustrator" var settings {addTrim : true,addBleedGuide : true,addCenterGuide : true,addCover : false,overlapAlert : false,…

GameFi 2024年或将迎来新的爆发!

在数字时代&#xff0c;游戏已经不仅仅是一种娱乐方式&#xff0c;更是一种跨越现实和虚拟界限的全球性文化现象。而游戏金融&#xff08;GameFi&#xff09;正是这场数字革命的下一个巨大风潮。 随着科技的不断发展和创新&#xff0c;2024年&#xff0c;GAMEFI&#xff08;Gam…

算法——链表

链表常用技巧 画图分析&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;——直观形象&#xff0c;便于理解、大多数都是模拟引入虚拟头结点&#xff08;哨兵位&#xff09; 典型的就是在第一个节点…

智能优化算法应用:基于沙猫群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于沙猫群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于沙猫群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.沙猫群算法4.实验参数设定5.算法结果6.参考文…

javaweb初体验

javaweb初体验 文章目录 javaweb初体验前言一、流程&#xff1a;1.创建Maven的父工程2.创建Maven&#xff0c;Webapp的子工程3.在pom.xml文件中添加依赖&#xff08;父工程与子工程共用&#xff09;4.写一个helloservlet类实现httpservlet接口&#xff0c;重写doget&#xff0c…

idea中终端Terminal页面输入命令git log后如何退出

1、idea中Terminal输入命令git log后如何退出&#xff1f; 2、解决 输入q键会自动退出git log命令

【Java系列】多线程案例学习——基于阻塞队列实现生产者消费者模型

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习JavaEE的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录…

ubuntu 在线安装 python3 pip

ubuntu 在线安装 python3 pip 安装 python3 pip sudo apt -y install python3 python3-pip升级 pip python3 -m pip install --upgrade pip

【头歌实训】kafka-入门篇

文章目录 第1关&#xff1a;kafka - 初体验任务描述相关知识Kafka 简述Kafka 应用场景Kafka 架构组件kafka 常用命令 编程要求测试说明答案代码 第2关&#xff1a;生产者 &#xff08;Producer &#xff09; - 简单模式任务描述相关知识Producer 简单模式Producer 的开发步骤Ka…

ROS多机通信

1&#xff1a;安装ssh sudo apt-get install openssh-server ps -e|grep ssh2&#xff1a;网络静态IP设置 3&#xff1a;配置文件修改 sudo gedit /etc/hosts192.168.3.11 用户名 192.168.3.22 用户名另一台4&#xff1a;重启网络 sudo /etc/init.d/network-manager resta…

2023年度业务风险报告:四个新风险趋势

目录 倒票的黄牛愈加疯狂 暴增的恶意网络爬虫 愈加猖獗的羊毛党 层出不穷的新风险 业务风险呈现四个趋势 防御云业务安全情报中心“2023年业务风险数据”统计显示&#xff0c;恶意爬虫风险最多&#xff0c;占总数的37.8%&#xff1b;其次是虚假账号注册&#xff0c;占18.79%&am…

MySQL事务、四大原则、执行步骤、四种隔离级别、锁、脏读、脏写等

MySQL事务 MySQL事务1.什么是事务&#xff1f;2.事务的四大原则3.事务执行的步骤4、事务的隔离性5、MySQL中的锁 MySQL事务 模拟一个转账业务&#xff1a; 上图中的sql语句&#xff1a; update from table set money mongey - 100 where name A; update from table set mone…