Spring cloud负载均衡@LoadBalanced LoadBalancerClient

LoadBalance vs Ribbon

由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件,我们讨论Spring负载均衡以Spring Cloud2020之后版本为主,学习Spring Cloud LoadBalance,暂不讨论Ribbon。

两者有什么区别、Spring Cloud为什么移除了Ribbon转向了Spring Cloud LoadBalancer,改日研究。

回顾

上篇文章我们学习了Spring Cloud LoadBalance负载均衡底层原理中的:

  1. @LoadBalanced注解的使用:与@Bean一起作用在RestTemplate上:
   @Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}

可以实现:在注入RestTemplate对象到Spring IoC容器的同时,启用Spring的负载均衡机制。
2. @LoadBalanced注解的底层原理:在LoadBalancerAutoConfiguration初始化的过程中,创建拦截器LoadBalancerInterceptor,对请求进行拦截从而实现负载均衡。
3. LoadBalancerInterceptor拦截器在执行请求前调用其intercept方法,intercept负责负载均衡的实现(具体的实现逻辑尚未研究)

其中第3点,intercept方法是怎么实现负载均衡的,我们还没有深入研究,这是我们今天这篇文章的主要目的。

Spring Cloud负载均衡原理

LoadBalancerClient及ReactorLoadBalancer初始化

LoadBalancerInterceptor拦截器的intercept方法究竟是怎么实现负载均衡的?

拦截方法intercept中会通过LoadBalancerClient对象(从Spring IoC容器中获取)实现负载均衡,LoadBalancerClient对象的注入以及拦截原理这个过程稍微复杂一点,所以我们先用简单的方式描述其实现逻辑,然后再从源码角度进行跟踪。

我们在上一篇文章中说过的spring-cloud-commons包下的自动配置类(如图):
在这里插入图片描述
比如对@LoadBalanced注解的解析、LoadBalancerInterceptor的注入等等,就是上面自动配置类LoadBalancerAutoConfiguration完成的。

Spring cloud有两个名字一样的自动配置类LoadBalancerAutoConfiguration,位于不同的包下,上面一个是在spring-cloud-commes包下,下面还要提到的一个是在spring-cloud-loadbalancer包下。

在这里插入图片描述
spring-cloud-loadbalancer包下的自动配置类LoadBalancerAutoConfiguration负责注入LoadBalancerClientFactory对象,LoadBalancerClientFactory负责创建子容器(SpringCloud通过子容器来隔离各微服务的访问参数、负载均衡策略等)。创建LoadBalancerClientFactory对象的过程中将LoadBalancerClientConfiguration设置给他的defaultConfigType属性,在子容器初始化的过程中将LoadBalancerClientConfiguration注册为配置类,从而通过LoadBalancerClientConfiguration配置类完成ReactorLoadBalancer的创建并注入子容器中。ReactorLoadBalancer是负载均衡策略接口,默认的负载均衡策略为RoundRobinLoadBalancer。

spring-cloud-loadbalancer包下的另外一个自动配置类BlockingLoadBalancerClientAutoConfiguration负责注入拦截器中的LoadBalancerClient,实际注入的是BlockingLoadBalancerClient对象,BlockingLoadBalancerClient会持有LoadBalancerClientFactory对象。

LoadBalancerInterceptor的intercept方法会转交给BlockingLoadBalancerClient处理,BlockingLoadBalancerClient通过LoadBalancerClientFactory对象向子容器(子容器不存在的话首先创建子容器)获取相关配置以及负载均衡策略RoundRobinLoadBalancer,最终通过RoundRobinLoadBalancer实现负载均衡。

需要注意,子容器不是在系统初始化过程中创建的,而是在处理请求的过程中创建的。

下面分析源码。

LoadBalancerClient

从应用层入手分析,先看上一篇文章的案例中的orderServicede的代码:

@Service
public class OrderService {@Autowiredprivate RestTemplate restTemplate;public String getOrder(){//通过userService获取user信息String url="http://userservice/user/getUser";System.out.println("url"+url);User user=restTemplate.getForObject(url,User.class);System.out.println(user);return user.getName();}
}

restTemplate.getForObject最终会调用到LoadBalancerInterceptor的intercept方法:

    private LoadBalancerClient loadBalancer;public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) throws IOException {final URI originalUri = request.getURI();String serviceName = originalUri.getHost();Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}

调用loadBalancer的execute方法,而loadBalancer是LoadBalancerClient 对象、是LoadBalancerInterceptor初始化过程中通过方法参数从SpringIoC容器中注入进来的。

前面提到过,自动配置类BlockingLoadBalancerClientAutoConfiguration负责注入拦截器中的LoadBalancerClient,实际注入的是BlockingLoadBalancerClient对象。为了不影响可读性,我们稍后再看这部分源码。

继续跟踪loadBalancer的execute方法。首先看一下LoadBalancerClient 的类结构;
在这里插入图片描述
接口LoadBalancerClient继承自接口ServiceInstanceChooser,接口定义了choose方法及execute方法(包括其重载方法)。其中execute是调用入口、也是模板方法:根据请求的服务serviceId(比如userService)通过调用choose方法获取到最终要调用的服务实例serviceInstance,最终调用到服务实例所提供的服务:

	@Overridepublic <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {String hint = getHint(serviceId);LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,buildRequestContext(request, hint));Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));ServiceInstance serviceInstance = choose(serviceId, lbRequest);if (serviceInstance == null) {supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));throw new IllegalStateException("No instances available for " + serviceId);}return execute(serviceId, serviceInstance, lbRequest);}

继续跟踪choose方法,是在BlockingLoadBalancerClient类中实现的。

BlockingLoadBalancerClient

我们已经知道注入到Spring Ioc容器中的LoadBalancerClient其实是BlockingLoadBalancerClient对象,所以继续跟踪BlockingLoadBalancerClient的choose方法:

	@Overridepublic <T> ServiceInstance choose(String serviceId, Request<T> request) {ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;}Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();if (loadBalancerResponse == null) {return null;}return loadBalancerResponse.getServer();}

我们需要重点关注的是两个方法:

第一个是LoadBalancerClientFactory的getInstance方法:通过serviceId从子容器中拿到ReactiveLoadBalancer,参数serviceId(服务Id),指的就是我们注册到Eureka注册中心的服务Id,比如前面案例中的userService。

第二个是ReactiveLoadBalancer的choose方法:根据不同的负载均衡策略,从服务队列中拿到serviceInstance。Spring cloud提供了两种负载均衡策略:随机策略RandomLoadBalancer和循环策略RoundRobinLoadBalancer。

我们先来看第一步:从子容器中获取ReactiveLoadBalancer对象。

子容器的创建

如果是首次调用、子容器不存在的情况下,LoadBalancerClientFactory负责创建子容器。

LoadBalancerClientFactory是reactiveLoadBalancer.Factory的实现类,继承自虚拟类NamedContextFactory,创建子容器的大部分代码都在NamedContextFactory类中。

我们首先看一下这个LoadBalancerClientFactory是怎么初始化的Spring IoC容器中的。其实前面已经说过了,是通过spring-cloud-loadbalancer包下的自动配置类LoadBalancerAutoConfiguration负责注入。

我们从源码角度验证一下,LoadBalancerAutoConfiguration源码:

	@ConditionalOnMissingBean@Beanpublic LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties);clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));return clientFactory;}

看一下LoadBalancerClientFactory的构造方法:

	public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);this.properties = properties;}

父类的构造方法:

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {this.defaultConfigType = defaultConfigType;this.propertySourceName = propertySourceName;this.propertyName = propertyName;}

可以看到LoadBalancerClientFactory创建的时候将LoadBalancerClientConfiguration.class赋值给他的父类NamedContextFactory的defaultConfigType属性,在创建子容器的时候LoadBalancerClientConfiguration类会被注册为子容器的配置类、从而通过LoadBalancerClientConfiguration完成ReactorLoadBalancer对象的注入(注入到子容器中)

NamedContextFactory

先对LoadBalancerClientFactory做一个简单的认识。

在这里插入图片描述LoadBalancerClientFactory继承自虚拟类NamedContextFactory,实现了接口DisposableBean和ApplicationContextAware,这两个接口我们并不陌生,在Spring生命周期回调的学习过程中中我们了解过这两个接口,Spring会在容器创建完成后通过ApplicationContextAware的setApplicationContext方法把ApplicationContext送回来、在容器销毁的时候回调DisposableBean接口的destroy方法。LoadBalancerClientFactory实现了这两个接口,所以LoadBalancerClientFactory就可以获取到Spring IoC根容器的applicationContext:

	@Overridepublic void setApplicationContext(ApplicationContext parent) throws BeansException {this.parent = parent;}

检查NamedContextFactory的setApplicatonContext方法,发现他把Spring IoC容器设置为自己的父容器了:这也很好理解,从NamedContextFactory类名称判断,这个类的目的就是要创建“Named”容器、也就是命名容器,其实我们后面会发现就是用serviceId命名的容器,比如我们有userservice,那就会创建一个名字叫userservice的容器。通过ApplicationContextAware回调setApplicationContext方法将Spring Ioc容器设置为命名容器的“父容器”。

继续跟踪LoadBalancerClientFactory的getInstance方法,调用到父类NamedContextFactory的getInstance:

	@Overridepublic ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);}public <T> T getInstance(String name, Class<T> type) {AnnotationConfigApplicationContext context = getContext(name);try {return context.getBean(type);}catch (NoSuchBeanDefinitionException e) {// ignore}return null;}

最终是向Spring的ApplicationContext获取类型为ReactorServiceInstanceLoadBalancer的bean。其中ApplicationContext通过getContext方法获取:

    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();protected AnnotationConfigApplicationContext getContext(String name) {if (!this.contexts.containsKey(name)) {synchronized (this.contexts) {if (!this.contexts.containsKey(name)) {this.contexts.put(name, createContext(name));}}}return this.contexts.get(name);}

首先从contexts查找,contexts是以serviceId为键值的ConcurrentHashMap,缓存创建的ApplicationContext,如果尚未创建,则调用createContext方法创建后缓存到contexts中。

这个名字为contexts的ConcurrentHashMap其实就是NamedContextFactory的核心:创建的ApplicationContext缓存在以serviceId为键值的HashMap中,获取的时候以serviceId到contexts中去查找,查找到则直接返回、查找不到则创建后缓存。

createContext方法:

protected AnnotationConfigApplicationContext createContext(String name) {AnnotationConfigApplicationContext context;if (this.parent != null) {// jdk11 issue// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101// https://github.com/spring-cloud/spring-cloud-openfeign/issues/475DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();if (parent instanceof ConfigurableApplicationContext) {beanFactory.setBeanClassLoader(((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());}else {beanFactory.setBeanClassLoader(parent.getClassLoader());}context = new AnnotationConfigApplicationContext(beanFactory);context.setClassLoader(this.parent.getClassLoader());}else {context = new AnnotationConfigApplicationContext();}if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {context.register(configuration);}}for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);}}}//注意这里会注册this.defaultConfigType到容器中context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object>singletonMap(this.propertyName, name)));if (this.parent != null) {// Uses Environment from parent as well as beanscontext.setParent(this.parent);}context.setDisplayName(generateDisplayName(name));context.refresh();return context;}

代码比较长但是并不复杂,仔细看一下其实就是Spring IoC容器的初始化过程:

  1. 创建DefaultListableBeanFactory
  2. 创建AnnotationConfigApplicationContext
  3. 加载属于当前serviceId的配置
  4. 加载所有的“默认”配置(也就是以default.开头的配置项)
  5. 加载配置文件(从配置文件及环境变量中加载),注册配置类 this.defaultConfigType,其实就是LoadBalancerClientConfiguration配置类
  6. 设置父容器(Spring Ioc的主容器设置为父容器)
  7. 刷新容器
  8. 返回容器

一个需要关注的重点就是:子容器创建的过程中,将配置类LoadBalancerClientConfiguration注册到容器中,在容器刷新的时候,这个配置类会被加载。

ReactorLoadBalancer & LoadBalancerClientConfiguration

子容器创建出来之后,我们还是返回到上面的NamedContextFactory的getInstance方法中:

	@Overridepublic ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);}

会向子容器获取ReactorServiceInstanceLoadBalancer对象。

所以我们现在两个任务:第一个是了解一下ReactorServiceInstanceLoadBalancer类,第二个是要了解到注入到子容器中的ReactorServiceInstanceLoadBalancer究竟是个什么对象。

第一步:看一眼ReactorLoadBalancer的类结构:
在这里插入图片描述
ReactorServiceInstanceLoadBalancer接口继承自ReactorLoadBalancer,Spring Cloud提供了他的两个实现类:随机策略类和轮询策略类。

第二步,注入到子容器中的ReactorServiceInstanceLoadBalancer究竟是个什么对象?就需要研究一下ReactorLoadBalancer的初始化过程。

子容器通过配置类LoadBalancerClientConfiguration实现ReactorLoadBalancer的注入,默认实现类是RoundRobinLoadBalancer:

	@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}

OK!

真相大白了,默认就是轮询策略RoundRobinLoadBalancer。

负载均衡策略的配置

Spring Cloud默认的负载均衡策略是RoundRobinLoadBalancer,我们可以通过配置调整负载均衡策略为随机策略RandomLoadBalancer。

调整方法很简单,官网说了:

在这里插入图片描述
余事以后再说吧。

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

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

相关文章

Win10 开始菜单、微软app和设置都打不开(未解决)

环境&#xff1a; Win10专业版 问题描述&#xff1a; Win10 开始菜单、微软app和设置都打不开,桌面个性话打开就报错&#xff0c;打开个性化该文件没有与之关联的程序来执行该操作 解决方案&#xff1a; 一般造成原因是MS-Settings文件系统错误 1.先重启电脑&#xff08;重…

【开源】基于Vue.js的高校宿舍调配管理系统

项目编号&#xff1a; S 051 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S051&#xff0c;文末获取源码。} 项目编号&#xff1a;S051&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统…

Pycharm中添加Python库指南

一、介绍 Pycharm是一款为Python开发者提供的集成开发环境&#xff08;IDE&#xff09;&#xff0c;支持执行、调试Python代码&#xff0c;并提供了许多有用的工具和功能&#xff0c;其中之一就是在Pycharm中添加Python库。 添加Python库有许多好处&#xff0c;比如能够增加开…

【云栖 2023】林伟:大数据 AI 一体化的解读

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;林伟 | 阿里云研究员&#xff0c;阿里云计算平台事业部首席架构师&#xff0c;阿里云人工智能平台 PAI 和大数据开发治理平台 DataWorks 负责人 演讲主题&#xff1a…

推荐10 本软件架构技术的好书【赠书活动|第11期《高并发架构实战》】

相信大家都对未来的职业发展有着憧憬和规划&#xff0c;要做架构师、要做技术总监、要做CTO。对于如何实现自己的职业规划也都信心满满&#xff0c;努力工作、好好学习、不断提升自己。 文章目录 《高并发架构实战&#xff1a;从需求分析到系统设计》《架构师的自我修炼&#x…

手写消息队列(基于RabbitMQ)

一、什么是消息队列&#xff1f; 提到消息队列是否唤醒了你脑海深处的记忆&#xff1f;回看前面的这篇文章&#xff1a;《Java 多线程系列Ⅳ&#xff08;单例模式阻塞式队列定时器线程池&#xff09;》&#xff0c;其中我们在介绍阻塞队列时说过&#xff0c;阻塞队列最大的用途…

在VSCode创建vue项目,出现“因为在此系统上禁止运行脚本”问题

问题&#xff1a;vue : 无法加载文件 C:\Users\***\***\Roaming\npm\vue.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 ht tps:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。 所在位置 行:1 字符: 1 解决&#xff…

目标检测标注工具AutoDistill

引言 在快速发展的机器学习领域&#xff0c;有一个方面一直保持不变&#xff1a;繁琐和耗时的数据标注任务。无论是用于图像分类、目标检测还是语义分割&#xff0c;长期以来人工标记的数据集一直是监督学习的基础。 然而&#xff0c;由于一个创新性的工具 AutoDistill&#x…

Linux shell编程学习笔记26:stty(set tty)

之前我们探讨了Linux中的tty&#xff0c;tty命令的主要功能是显示当前使用的终端名称。 如果我们想进一步对tty进行设置&#xff0c;就要用到stty。 stty的功能&#xff1a;显示和修改终端特性&#xff08;Print or change terminal characteristics&#xff09;。 1 stty -…

轻量封装WebGPU渲染系统示例<35>- HDR环境数据应用到PBR渲染材质

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/BasePbrMaterialTest.ts 当前示例运行效果: 微调参数之后的效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下: export class BasePbrMateri…

Sentinel 熔断规则 (DegradeRule)

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 SpringbootDubboNacos 集成 Sentinel&…

【Linux】21、软中断、网络小包、SYN FLOOD 攻击、sar tcpdump

文章目录 一、通俗理解&#xff1a;从“取外卖”看中断二、软中断2.1 网卡收发数据包2.2 查看软中断和内核线程2.3 案例2.3.1 案例&#xff1a;动态库 sleep 导致软中断2.3.2 Nginx 进程的不可中断状态是系统的一种保护机制&#xff0c;可以保证硬件的交互过程不被意外打断。所…

QT自定义信号,信号emit,信号参数注册

qt如何自定义信号 使用signals声明返回值是void在需要发送信号的地方使用 emit 信号名字(参数)进行发送 在需要链接的地方使用connect进行链接 ct进行链接

无需云盘,不限流量实现Zotero跨平台同步:内网穿透+私有WebDAV服务器

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 无需云盘&#xff0c;不限流量实现Zotero跨平台同步&#xff1a;内网穿透私有WebDAV服务器 文章目…

系列十一、你平时工作用过的JVM常用基本配置参数有哪些?

一、常用参数 1.1、-Xms 功能&#xff1a;初始内存大小&#xff0c;默认为物理内存的1/64&#xff0c;等价于 -XX:InitialHeapSize 1.2、-Xmx 功能&#xff1a;最大分配内存&#xff0c;默认为物理内存的1/4&#xff0c;等价于 -XX:MaxHeapSize 1.3、-Xss 功能&#xff1a;设置…

RoCE、IB和TCP等网络的基本知识及差异对比

目前有三种RDMA网络&#xff0c;分别是Infiniband、RoCE(RDMA over Converged Ethernet)、iWARP。 其中&#xff0c;Infiniband是一种专为RDMA设计的网络&#xff0c;从硬件级别保证可靠传输 &#xff0c;技术先进&#xff0c;但是成本高昂。 而RoCE 和 iWARP都是基于以太网的…

9.docker镜像Tag为none的原因

1.现象 使用docker images命令查看镜像列表&#xff0c;会发现存在许多标签为none的镜像&#xff1a; 2. 原因 docker镜像标签为none的原因如下&#xff1a; &#xff08;1&#xff09;构建或重新拉取同名同Tag的新镜像&#xff1a;构建或重新拉取同名同Tag的新镜像后&…

什么是单域名SSL安全证书?

单域名证书是什么&#xff1f; 单域名证书是指只包含一个具体域名的SSL/TLS证书&#xff0c;它可以用于保护单个主机名的HTTPS通信。例如&#xff0c;如果您有一个网站http://www.example.com&#xff0c;则单域名证书将仅为该域名颁发。 这种证书在保护单个域的安全方面很有…

open3d ICP 配准

文章目录 Three common registration techniquesPoint-to-point techniquePoint-to-plane registration ICP registrationHelper visualization functionInputGlobal registrationExtract geometric featureInputRANSAC Point-to-point ICPPoint-to-plane ICP References Three…

【Java 进阶篇】Ajax 入门:打开前端异步交互的大门

欢迎来到前端异步交互的世界&#xff01;在这篇博客中&#xff0c;我们将深入探讨 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;&#xff0c;这是一项能够让你的网页在不刷新的情况下与服务器进行数据交互的技术。无论你是刚刚踏入前端开发的小白&#xff0c;…