当前位置: 首页 > ds >正文

面试专栏04-SpringCloud

三、SpringCloud

一、分布式系统的组成

  • 微服务SpringBoot
  • 注册中心/配置中心Spring Cloud Alibaba Nacos
  • 网关Spring Cloud Gateway
  • 远程调用Spring Cloud OpenFeign
  • 服务熔断Spring Cloud Alibaba Sentinel
  • 分布式事务Spring Cloud Alibaba Seata

二、获取所有服务列表

    @ResourceDiscoveryClient discoveryClient;@Testvoid discoveryClientTest(){// 获取所有服务的名字List<String> services = discoveryClient.getServices();services.forEach(System.out::println);// 获取服务的实例(参数是服务名字)services.forEach((name->{List<ServiceInstance> instances = discoveryClient.getInstances(name);instances.forEach(i -> {String instanceId = i.getInstanceId();String host = i.getHost();int port = i.getPort();String serviceId = i.getServiceId();URI uri = i.getUri();String scheme = i.getScheme();System.out.println("实例id:"+instanceId+"\n主机:"+host+"\n端口:"+port+"\n服务id:"+serviceId+"\nuri:"+uri+"\nscheme:"+scheme);System.out.println();});}));}

三、调用远程服务的三种方式

第一种:直接调用,自己选择服务器
    // 基础:没有负载均衡的获取远程服务private Product getProductRemote(Long productId) {// 获取远程的服务器列表List<ServiceInstance> instances = discoveryClient.getInstances("service-product");// 获取第一个服务器实例ServiceInstance instance = instances.get(0);// 构建请求URLString url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;log.info("远程请求URL:{}", url);// 发送请求return restTemplate.getForObject(url, Product.class);}
第二种:使用LoadBalancerClient
    // 进阶2:完成负载均衡的获取远程服务private Product getProductRemoteWithLoadBalance(Long productId) {// 获取远程的服务器列表 -- (负载均衡的获取)ServiceInstance instance = loadBalancerClient.choose("service-product");// 构建请求URLString url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;log.info("远程请求URL:{}", url);// 发送请求return restTemplate.getForObject(url, Product.class);}
第三种:使用@LoadBalanced
    // 进阶3:直接在 config 中 OrderServiceConfig 类,创建restTemplate对象时,指定使用负载均衡,那末,在调用的时候直接就会// 使用负载均衡的方式来获取远程服务private Product getProductRemoteWithLoadBalanceAnnotation(Long productId) {// 构建请求URL,直接写微服务的名字就好了,不用写具体的地址和端口,// 因为在 config 中 OrderServiceConfig 类,创建restTemplate对象时,指定使用负载均衡,// service-product 会被动态替换为负载均衡器的地址和端口String url = "http://service-product/product/" + productId;log.info("远程请求URL:{}", url);// 发送请求return restTemplate.getForObject(url, Product.class);}

四、注册中心宕机情况

注册中心宕机,分为两种情况:

第一种:如果注册中心是在服务注册后且执行了远程调用后宕机的,那末,由于远程调用过,因此会存在缓存,不会每次都请求注册中心获取最新的服务
列表而是使用缓存中的服务列表,只有当注册中心更新时,才会从注册中心重新获取的服务列表。如果注册中心宕机,那末,也不影响从缓存中获取请求地址

第二种:如果注册中心是在服务注册前或调用前就宕机的,那末,由于注册中心还没启动,因此第一次远程调用会请求注册中心,而此时的注册中心处于
宕机状态,因此会报错。

五、配置中心

1、配置中心的依赖
        <!--配置中心的依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
2、导入配置中心的配置文件

在导入配置中心的依赖后,在微服务的配置文件中导入配置文件

spring:config:import: nacos:service-order.yml # 从 nacos 的配置中心导入配置文件
3、在配置中心创建配置文件

注意:配置文件的 Data ID 就是导入时指定的文件名: service-order.yml(注意有后缀)

4、添加自动刷新的注解,启用自动刷新配置文件,当配置文件更新时
@RefreshScope // 表示对于nacos配置中心的配置文件的变化,会重新加载配置文件(自动刷新更新配置的值)
@RestController
public class OrderController {}
5、不检查配置文件的引入

如果引入了配置中心的依赖,但是没有在配置中导入配置文件,那末,在启动时会报错,因此需要在配置文件中添加以下配置,
来禁用检查配置文件的引入。

配置的声明规则,先导入优先,后声明优先,先用项目中的配置,再用配置中心的配置,因此如果配置相同
则优先使用项目中的配置。
报错信息:

Connected to the target VM, address: '127.0.0.1:62555', transport: 'socket'
16:49:36.515 [main] ERROR org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter -- ***************************
APPLICATION FAILED TO START
***************************Description:No spring.config.import property has been definedAction:Add a spring.config.import=nacos: property to your configuration.If configuration is not required add spring.config.import=optional:nacos: instead.To disable this check, set spring.cloud.nacos.config.import-check.enabled=false.Disconnected from the target VM, address: '127.0.0.1:62555', transport: 'socket'

解决办法:增加一个配置信息,禁用检查配置文件的引入

spring.cloud.nacos.config.import-check.enabled=false
6、配置中心的数据隔离

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

六、OpenFeign 声明式远程调用

1、依赖引入

注意:前提要引入 loadbalancer 的依赖,因为是默认的负载均衡。

        <!--OpenFeign的依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
2、开启OpenFeign的功能
@SpringBootApplication
@EnableFeignClients // 开启OpenFeign的功能
public class ServiceOrderApplication {}
3、编写远程调用的接口
@FeignClient(value = "service-product") // 指定远程调用的服务的名字
public interface ProductClient {@GetMapping("/product/{productId}") // 指定远程调用的服务的路径Product getProduct(@PathVariable("productId") Long productId); // 指定远程调用的服务的方法@GetMapping("/productWhitToken/{productId}") // 指定远程调用的服务的路径Product getProduct(@PathVariable("productId") Long productId, @RequestHeader("Authorization") String token);
}   
4、调用远程服务
    // 注入远程调用的接口,直接调用即可@ResourceProductClient productClient;

补充:不仅可以调用注册中心的微服务,还可以使用第三方的AIP,只需要指明
url 路径即可。例如:

   // 通过 url 路径调用第三方的API@FeignClient(value = "remote-service",url = "http://www.baidu.com")public interface ProductClient {@GetMapping("/{wd}") // 指定远程调用的服务的路径Product getProduct(@PathVariable("wd") String word); // 指定远程调用的服务的方法}
5、Feign 的日志配置
  • 在配置文件中指定远程调用所在的类 或 包。
      logging:level:dz.cn.remote: debug
    
  • 在配置类中,引入 bean 对象,指定日志的级别
      @Configurationpublic class FeignConfig {@BeanLogger.Level feignLoggerLevel(){return Logger.Level.FULL;}}
    
6、Feign 的超时控制

当某个链路中的一个远程调用的服务出现卡顿时,可能导致整个链路都出现卡顿,如果这个时候
有大量的请求同时访问,那末可能导致整个服务器的资源耗尽,导致服务雪崩。

  • 连接超时(connectTimeout):默认连接超时 10 秒,默认返回读取信息
  • 读超时(readTimeout):读取超时 60 秒
spring:cloud:openfeign:client:config:default: # 默认配置,如果没有指定,直接使用默认配置connect-timeout: 1000read-timeout: 3000service-product: # 单独配置connect-timeout: 3000 # 连接超时时间read-timeout: 5000 # 读取超时时间
7、Feign 的重试机制

远程调用失败后,还可以进行多次尝试,如果某次成功,则返回ok,否则依然返回错误。
需要向Spring中注入重试机制的配置类。

    // 配置重试次数,也可以传参数:间隔时间,最大间隔时间,重试次数,// 注意每次的间隔时间是递增的,默认是 100 毫秒,下一次请求为上一次间隔时间的 1.5 倍// 但是不超过指定的最大间隔时间@BeanRetryer retryer(){return new Retryer.Default(); // 默认值为:this(100L, TimeUnit.SECONDS.toMillis(1L), 5);}
8、Feign 的请求拦截器

在远程调用服务之前,可以对请求内容进行拦截修改,例如:添加请求头,添加请求参数等。

@Component // 将该类注入到Spring容器中,这样就会自动扫描,不需要在配置文件中配置
public class XTokenRequestInterceptor implements RequestInterceptor {/*** 请求拦截器* @param requestTemplate 请求模板(请求信息)*/@Overridepublic void apply(RequestTemplate requestTemplate) {System.out.println("拦截器调用");// 添加请求头requestTemplate.header("X-Token", UUID.randomUUID().toString());// 添加请求体// requestTemplate.body(byte[] data, Charset charset);// 添加查询信息requestTemplate.query("hh",  "hhh");}
}
9、Feign 的Fallback 兜底返回

当远程调用失败时,可以进行兜底返回,例如:返回一个默认的响应信息,或者返回一个错误码。

注意:兜底返回需要一个熔断配置,需要引入熔断的依赖,以及开启熔断功能。

# 开启熔断机制
feign:
sentinel:
enabled: true
// 1、创建一个类,实现远程调用的接口,并实现其中的方法。
@Component
public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getProductById(Long id, String token) {Product product = new Product();product.setId(id);product.setProductName("iPhone 15-" + id);product.setPrice(new BigDecimal("12.5"));product.setNum((int) (id + 10));return product;}
}// 2、在远程调用接口中,指明兜底回调类 fallback = ProductFeignClientFallback.class
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {@GetMapping("/product/{id}")Product getProductById(@PathVariable("id") Long id,@RequestHeader("Authorization") String token);
}

七、Sentinel 流量控制

1、Sentinel 的基本工作原理

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Spring Cloud Alibaba Sentinel 以流量为切入点,从流量控制
流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。

  • 定义资源:
    • 主流框架自动适配(web Servlet、Dubbo、SpringCloud)所有 web 接口均为资源
    • 编程式:SphU API
    • 声明式:@SentinelResource 注解
  • 定义规则:
    • 流量控制
    • 熔断降级
    • 系统保护
    • 来源访问控制
    • 热点参数限流
2、Sentinel 启动

在服务运行的机器中,通过命令行启动 sentinel,的 jar 包。

spring:cloud:sentinel:transport:dashboard: localhost:8080 # 指定 sentinel 控制台的 ip 和端口

注意:

  • 放行端口:
    • sentinel 启动时,首先会占用一个端口,后面会为每个引入的模块分配一个 端口, 端口号默认从 8719 开始,后面一次递增,因此服务器一定要开启
    • 8719 等后面的端口。
  • 公网 ip:
    • 由于阿里云服务器只能连接访问到公网 ip,因此,如果在服务其上 ping 自己的内网 ip,是 ping 不通的。
3、Sentinel 流控资源分类
  • 默认情况下,Sentinel 会自动为所有 web 接口生成资源,例如:/order/create。
  • 如果需要手动指定资源,则需要使用 @SentinelResource 注解。
1、Web 接口

原理:通过拦截器进行拦截

// 继承 BlockExceptionHandler 接口,其实就是利用拦截器进行判断拦截
@Component
public class MyExceptionHandler implements BlockExceptionHandler {// 用于将对象转化为jason字符串,springMVC自带的private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void handle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,String resourceName, BlockException e) throws Exception {httpServletResponse.setContentType("application/json;charset=UTF-8");PrintWriter printWriter = httpServletResponse.getWriter();// 自定义 web 限流返回信息R ok = R.ok(500, resourceName + "资源的流量超出限制", e);String json = objectMapper.writeValueAsString(ok);printWriter.print(json);}
}
2、@SentinelResource 注解

原理:SentinelResourceAspect 切面来进行,只要是添加了 @SentinelResource 注解的接口,就会有这个切面
切面内部的异常处理逻辑是:

  • 先判断:blockHandler 是否有值
  • 如果没有值,则在判断 fallback 是否有值
  • 如果没有值,则判断 defaultFallback 是否有值
  • 如果都没有值,则直接 throw 抛出给 SpringBoot 处理,可以通过全局异常处理捕获
    @Override
@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")
public Order createOrder(Long productId, Long userId) {// 正常逻辑        return order;
}// 限流异常处理的兜底数据 BlockException 不是 Exception
public Order createOrderFallback(Long productId, Long userId, BlockException e) {Order order = new Order();order.setId(0L);order.setTotalAmount(new BigDecimal("0.0"));order.setUserId(userId);order.setNickName("未知用户");order.setAddress("错误:" + e.getClass());return order;
}
3、OpenFeign 远程调用

如果出现限流控制,则会返回 OpenFeign 的远程调用在定义时的兜底回调,没有指定,则使用 SpringBoot 的异常处理

4、SphU 硬编码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3、常用的控制规则
1、流控规则

针对来源:default,表示所有来源,可以指定 ip 或者 serviceId
阀值类型:QPS:每秒调用次数
是否集群:可以配置每个服务器的流量控制
流控模式:

  • 直接策略:直接限流,例如:当 QPS 超过阈值时,直接限流

  • 链路策略:额可以指定入口资源,如果是普通的 creat 来的,不做限流,如果是 seckill 过来的都限流

  • 关联策略:当该资源的关联资源请求量大时,限制该资源 \

注意:只有流控效果为快速失败

流控效果:

  • 快速失败:直接限流,多余的丢弃
  • Warm Up(预热):指定限流的最大值,当有大量请求时,会默认先让一部分(1/3)通过,然后慢慢增加,直到达到最大值
  • 匀速排队:会按照QPS的指定数在 1s 上的等分,比如:QPS = 2 ,每秒 2 个请求,每500毫秒一个请求,因此指定的 QPS < 1000
    同时,对于其他的请求会进项排队等待,但是有超时时间,默认为 20s,20s 后,也会丢弃
2、熔断规则
  • 熔断降级:
    如果当前微服务发现自己调用微服务时,总是抛出异常,那末,下一次在调用时,可以直接返回错误信息。
    熔断降级作为自身的保护手段,通常在客户端(调用端)进行配置。
    有利于
    • 切断不稳定调用
    • 快速返回步积压
    • 避免服务雪崩,即由于链路中的某个微服务故障,同时有大量请求到来,导致服务器的资源耗尽,从而导致整个系统崩溃。
  • 熔断开关工作原理:
    • 首先,如果远程调用失败,那末直接返回错误信息,直到达到规定的阈值
    • 断开状态:达到规定的阈值后,熔断开关会处于 断开状态 此时会拒绝所有请求,但是会有规定的断开时间
    • 半闭合状态:如果达到规定的时间后,会进入 半闭合装填 此时,会先尝试放行一个请求,如果请求成功,并且是在合法的范围内(不被认为是慢调用等状态)
      则进入打开状态,如果失败,则进入 断开状态再次重复循环。
  • 熔断开关的阈值规定:
    • 慢调用比例:设置慢时间,在统计时长内,所有请求中,请求时间大于慢时间的比例 大于 慢调用比例,断开
    • 异常比例数:在统计时长内,在统计时长内,所有请求中,异常请求的比例 大于 异常比例数,断开
    • 异常数:在统计时长内,请求的返回的是异常的请求个数 大于 异常数,断开
  • 热点参数限流

注意:有熔断和没有熔断的区别
1、没有熔断:在进行远程调用的时候,发现超时或者远程调用的结果错误,那末执行兜底回调(每次都要向远程发请求
2、有熔断:在进行远程调用的时候,出错了,那末执行兜底回调,并在熔断期间内,不会在向远程发送请求,而是直接返回兜底数据

八、gateway 网关

1、引入网关依赖

两个都需要引入,因为通过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>
2、predicates 配置

routes 中的 predicates 属性是一个数组,即配置多种匹配规则,如下图所示。同时,对于每种匹配的参数信息,都保存在
RoutePredicateFactory 的实现类中,每个实现类删去后缀,就是规则的名字,而参数都在实现类的 Config 内部类中
如果添加多个匹配规则,那末只有所有的匹配规则都满足了,才会转发请求
例如:

spring:cloud:gateway:routes:- id: order-routeuri: lb://service-order # lb 表示使用负载均衡,会在nacos中查找对应的服务predicates:- name: Pathargs:patterns: /api/order/**matchTrailingSlash: true # 是否精确比配,true:/xxx/1 和 /xxx/1/ 都匹配,false: /xxx/1 和 /xxx/1/ 不匹配- name: Header # 向请求头中添加参数args:header: token  # 参数的名字regexp: xxxxx  # 参数的值,可以是正则表达式

同样,也可以自定义匹配规则,只需要写的跟 RoutePredicateFactory 的实现类类似即可

在这里插入图片描述

spring:cloud:gateway:routes:- id: order-routeuri: lb://service-order # lb 表示使用负载均衡,会在nacos中查找对应的服务predicates:- Path=/api/order/**- id: product-routeuri: lb://service-productpredicates:- Path=/api/product/**
#        - id: url-test
#          uri: https://www.baidu.com # uri 可以是网址,既可以转发给其他的网页,直接访问该网址
#          predicates:
#            - Path=/**- id: route-testuri: http://localhost:8081predicates:- name: Path

注意:使用的是外部配置文件,需要在默认的配置文件中,包含外部配置文件,外部配置文件的命名规则为 application-{配置文件名}.yml
public List shortcutFieldOrder() {return Arrays.asList(“patterns”, “matchTrailingSlash”);}
表示短格式的参数,参数的顺序必须和这个方法返回的顺序一致,eg:- Path=/api/order/**,true

3、filters 配置

修改转发路径,添加请求头,修改请求参数,修改响应头等

spring:cloud:gateway:routes:- id: order-routeuri: lb://service-orderpredicates:- Path=/api/order/**,truefilters: # 路径重写,匹配 /api/order/** 的请求,将 /api/order/** 删除前缀 /api/order/- RewritePath=/api/order/(?<path>.*),/$\{path}- id: product-routeuri: lb://service-productpredicates:- Path=/api/product/**,truefilters:- RewritePath=/api/product/(?<path>.*),/$\{path}default-filters: # 默认过滤器,为所有的路由添加- AddResponseHeader=default-filter, 默认值

自定义全局过滤器

@Slf4j
@Component // 加入容器后生效
public class RtGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().toString();long start = System.currentTimeMillis();log.info("请求【{}】开始,时间:{}",uri,start);//===================以上是前置逻辑===================// 由于是异步的,所以需要使用doFinally,而不是直接写在下边Mono<Void> filter = chain.filter(exchange).doFinally(r ->{//==================以下是后置逻辑===================long end = System.currentTimeMillis();log.info("请求【{}】结束,耗时:{}ms",uri,end-start);});return filter;}// 顺序,返回的数值越小,优先级越高@Overridepublic int getOrder() {return 0;}
}

自定义yml配置的过滤器
还是参考官方的配置类编写,继承 GatewayFilterFactory 或中其中的抽象类
再次实现抽象类的方法,例如继承:AbstractNameValueGatewayFilterFactory 类,这就是传入的是 参数名和参数值 \

开启全局跨域

spring:cloud:gateway:globalcors:cors-configurations:'[/**]': # 对于所有请求以及请求类型都允许跨域操作allowed-origin-patterns: '*'allowed-headers: '*'allowed-methods: '*'

九、Seata 分布式事务

TC (Transaction Coordinator) 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚
TM (Transaction Manager) 资源管理器:定义全局事务的范围,驱动全局事务提交或回滚
RM (Resource Manager):管理分支事务对应的资源,与 TC 交谈以注册分支事务和提交或回滚分支事务 \

1、Seata 的引入

客户端下载网址:https://seata.apache.org/zh-cn/download/seata-server
依赖的引入 \

        <!--分布式事务--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency>

配置文件,所有引入 seata 的服务都需要配置这个文件,指定 seata 的服务器地址 \

service {vgroupMapping.default_tx_group = "default"default.grouplist = "localhost:8091"enableDegrade = falsedisableGlobalTransaction = false
}
2、Seata 的简单使用

在总的事务调用方法上添加 @GlobalTransactional 注解,其他分支事务添加 @Transactional 注解

3、Seata 的原理

通过二个阶段的提交,保证事务的一致性。

首先:每个微服务的事务开始执行,根据更新数据的结果,记录更前的数据和更新后的数据。
其次:单个微服务的事务提交前,先获取全局锁,保证没有其他的事务对数据进行操作。
然后:单个微服务的事务提交,这里已经更新数据库,并向数据库中的 undolog 日志插入了一条数据,用于记录提交前的数据和更新后的数据,并向全局事务报告事务的状态
最后:

  • 如果所有事务的装填全部是成功,那末直接结束全局事务。
  • 如果有一个事务的状态是回滚,那末,对于已经成功的事务,将根据undolog 日志,进行回滚(即再次修改为原来的数据),然后结束全局事务。
4、Seata 模式
  • AT:默认的自动模式
  • XA:自动模式,但是是根据数据库的XA协议进行实现的,利用数据库的事务做到全局事务,即全局事务在完成前,微服务的事务不会真的提交,而是阻塞
  • TCC:手动模式,需要自己实现开启,提交,回滚,而 seata 只是在合适的时候调用这些方法,用于不仅仅是数据库的事务,也可以是广义上的事务
  • SAGA:用于处理长事务
http://www.xdnf.cn/news/8887.html

相关文章:

  • 相机内参 opencv
  • 基于Web组件实现随机抽奖
  • 云手机安卓12哪个好?掌派云手机安卓12系统上线,开启流畅体验新纪元
  • 指针数组和数组指针的区别
  • 华为OD机试真题—— 判断字符串子序列(2025B卷:100分)Java/python/JavaScript/C/C++/GO最佳实现
  • 【EcelVBA】系统学习 ActiveX 控件
  • 恒坤新材闯上市:利润受益于大额补贴,产能利用率低仍要募资扩产
  • OD 算法题 B卷【最长公共后缀】
  • C++修炼:哈希表的模拟实现
  • 【python实战】-- 选择解压汇总mode进行数据汇总20250525更新(篇幅2)
  • 塔能科技:以多元技术赋能全行业能耗节能转型
  • 力扣刷题(第三十七天)
  • Linux之概述和安装vm虚拟机
  • Oracle附加日志概述
  • Day 31 训练
  • 哪款云手机支持安卓12系统?掌派云手机-性价比之选
  • Threejs 透明模型渲染嵌套以及深度测试解决共存问题
  • 什么是ESLint?它有什么作用?
  • 10G/25G PCS only mode for CoaXPress Over Fiber
  • 9. Spring AI 各版本的详细功能与发布时间整理
  • 华为OD机试真题——出租车计费/靠谱的车 (2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现
  • Spring Cloud Sleuth与Zipkin深度整合指南:微服务链路追踪实战
  • Python实战:轻松连接与高效操作Elasticsearch
  • HDFS存储原理与MapReduce计算模型
  • 嵌入式学习笔记——day27
  • 奈雪小程序任务脚本
  • 计算机病毒的发展历程及其分类
  • Lua 脚本在 Redis 中的运用-22
  • leetcode 39. Combination Sum和40. Combination Sum II
  • 容器化:用于机器学习的 Docker 和 Kubernetes