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

Apache Seata配置管理原理解析

本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。
本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。

Apache Seata配置管理原理解析

说到Seata中的配置管理,大家可能会想到Seata中适配的各种配置中心,其实今天要说的不是这个,虽然也会简单分析Seata和各配置中心的适配过程,但主要还是讲解Seata配置管理的核心实现

Server启动流程

在讲配置中心之前,先简单介绍一下Server端的启动流程,因为这一块就涉及到配置管理的初始化,核心流程如下:

  1. 程序入口在Server#main方法中
  2. 获取port的几种形式:从容器中获取;从命令行获取;默认端口
  3. 解析命令行参数:host、port、storeMode等参数,这个过程可能涉及到配置管理的初始化
  4. Metric相关:无关紧要,跳过
  5. NettyServer初始化
  6. 核心控制器初始化:Server端的核心,还包括几个定时任务
  7. NettyServer启动

代码如下,删除了非核心代码

public static void main(String[] args) throws IOException {// 获取port的几种形式:从容器中获取;从命令行获取;默认端口, use to logback.xmlint port = PortHelper.getPort(args);System.setProperty(ConfigurationKeys.SERVER_PORT, Integer.toString(port));// 解析启动参数,分容器和非容器两种情况ParameterParser parameterParser = new ParameterParser(args);// Metric相关MetricsManager.get().init();// NettyServer初始化NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(workingThreads);// 核心控制器初始化DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer);coordinator.init();// NettyServer启动nettyRemotingServer.init();
}

为社么说步骤3中肯能涉及到配置管理的初始化呢?核心代码如下:

if (StringUtils.isBlank(storeMode)) {storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE,SERVER_DEFAULT_STORE_MODE);
}

如果在启动参数中没有特别指定storeMode,就会通过ConfigurationFactory相关API去获取该配置,在ConfigurationFactory.getInstance()这行代码中,涉及到两部分内容:ConfigurationFactory静态代码初始化和Configuration初始化。接下来我们重点分析这部分内容

配置管理初始化

ConfigurationFactory初始化

我们知道在Seata中有两个关键配置文件:一个是registry.conf,这是核心配置文件,必须要有;另一个是file.conf,只有在配置中心是File的情况下才需要用到。ConfigurationFactory静态代码块中,其实主要就是加载registry.conf文件,大概如下:

1、在没有手动配置的情况,默认读取registry.conf文件,封装成一个FileConfiguration对象,核心代码如下:

Configuration configuration = new FileConfiguration(seataConfigName,false);
FileConfigFactory.load("registry.conf", "registry");
FileConfig fileConfig = EnhancedServiceLoader.load(FileConfig.class, "CONF", argsType, args);

2、配置增强:类似代理模式,获取配置时,在代理对象里面做一些其他处理,下面详细介绍

Configuration extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);

3、将步骤2中的代理对象赋值给CURRENT_FILE_INSTANCE引用,在很多地方都直接用到了CURRENT_FILE_INSTANCE这个静态引用

CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration;

可以简单的认为:CURRENT_FILE_INSTANCE对应的就是registry.conf里面的内容。我认为registry.conf这个文件名取的不太好,歧义太大,叫做bootstrap.conf是不是更好一些?

Configuration初始化

Configuration其实就是对应配置中心,Seata目前支持的配置中心很多,几乎主流的配置中心都支持,如:apollo、consul、etcd、nacos、zk、springCloud、本地文件。当使用本地文件作为配置中心的时候,涉及到file.conf的加载,当然这是默认的名字,可以自己配置。到这里,大家也基本上知道了registry.conffile.conf的关系了。

Configuration作为单例放在ConfigurationFactory中,所以Configuration的初始化逻辑也是在ConfigurationFactory中,大概流程如下:
1、先从registry.conf文件中读取config.type属性,默认就是file

configTypeName = CURRENT_FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR+ ConfigurationKeys.FILE_ROOT_TYPE);

2、基于config.type属性值加载配置中心,比如默认是:file,则先从registry.conf文件中读取config.file.name读取本地文件配置中心对应的文件名,然后基于该文件名创建FileConfiguration对象,这样就将file.conf中的配置加载到内存中了。同理,如果配置的是其他配置中心,则会通过SPI初始化其他配置中心。还有一点需要注意的是,在这阶段,如果配置中心是本地文件,则会为其创建代理对象;如果不是本地文件,则通过SPI加载对应的配置中心

if (ConfigType.File == configType) {String pathDataId = String.join("config.file.name");String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId);configuration = new FileConfiguration(name);try {// 配置增强 代理extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);} catch (Exception e) {LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);}
} else {configuration = EnhancedServiceLoader.load(ConfigurationProvider.class, Objects.requireNonNull(configType).name()).provide();
}

3、基于步骤2创建的Configuration对象,为其再创建一层代理,这个代理对象有两个作用:一个是本地缓存,不需要每次获取配置的时候都从配置中获取;另一个是监听,当配置发生变更会更新它维护的缓存。如下:

if (null != extConfiguration) {configurationCache = ConfigurationCache.getInstance().proxy(extConfiguration);
} else {configurationCache = ConfigurationCache.getInstance().proxy(configuration);
}

到这里,配置管理的初始化就完成了。Seata通过先先加载registry.conf文件获取对应的配置中心信息、注册中心,然后再根据获取到的的对应信息初始化配置中心。在使用本地文件作为配置中心的情况下,默认是加载file.conf文件。然后再为对应的配置中心创建对应的代理对象,使其支持本地缓存和配置监听

整理流程还是比较简单,在这里我要抛出几个问题:

  1. 什么是配置增强?Seata中的配置增强是怎么做的?
  2. 如果使用本地文件作为配置中心,就必须要将配置定义在file.conf文件中。如果是Spring应用,能不能直接将对应的配置项定义在application.yaml中?
  3. 在上面说的步骤2中,为什么在使用本地文件配置中心的情况下,要先为Configuration创建对应配置增强代理对象,而其他配置中心不用?

这3个问题都是紧密联系的,都和Seata的配置增加相关。下面详细介绍

配置管理增强

配置增强,简单来说就是为其创建一个代理对象,在执行目标独对象的目标方法时候,执行代理逻辑,从配置中心的角度来讲,就是在通过配置中心获取对应配置的时候,执行代理逻辑。

  1. 通过ConfigurationFactory.CURRENT_FILE_INSTANCE.getgetConfig(key)获取配置
  2. 加载registry.conf文件创建FileConfiguration对象configuration
  3. 基于SpringBootConfigurationProviderconfiguration创建代理对象configurationProxy
  4. configurationProxy中获取配置中心的连接信息file zk nacos 等
  5. 基于连接信息创建配中心Configuration对象configuration2
  6. 基于SpringBootConfigurationProviderconfiguration2创建代理对象configurationProxy2
  7. 执行configurationProxy2的代理逻辑
  8. 基于key找到对应的SpringBean
  9. 执行SpringBean的getXxx方法

配置增强实现

上面也简单提到过配置增强,相关代码如下:

EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
  1. 首先通过SPI机获取一个ExtConfigurationProvider对象,在Seata中默认只有一个实现:SpringBootConfigurationProvider
  2. 通过ExtConfigurationProvider#provider方法为Configuration创建代理对象

核心代码如下:

public Configuration provide(Configuration originalConfiguration) {return (Configuration) Enhancer.create(originalConfiguration.getClass(), new MethodInterceptor() {@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)throws Throwable {if (method.getName().startsWith("get") && args.length > 0) {Object result = null;String rawDataId = (String) args[0];if (args.length == 1) {result = get(convertDataId(rawDataId));} else if (args.length == 2) {result = get(convertDataId(rawDataId), args[1]);} else if (args.length == 3) {result = get(convertDataId(rawDataId), args[1], (Long) args[2]);}if (result != null) {//If the return type is String,need to convert the object to stringif (method.getReturnType().equals(String.class)) {return String.valueOf(result);}return result;}}return method.invoke(originalConfiguration, args);}});
}private Object get(String dataId) throws IllegalAccessException, InstantiationException {String propertyPrefix = getPropertyPrefix(dataId);String propertySuffix = getPropertySuffix(dataId);ApplicationContext applicationContext = (ApplicationContext) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT);Class<?> propertyClass = PROPERTY_BEAN_MAP.get(propertyPrefix);Object valueObject = null;if (propertyClass != null) {try {Object propertyBean = applicationContext.getBean(propertyClass);valueObject = getFieldValue(propertyBean, propertySuffix, dataId);} catch (NoSuchBeanDefinitionException ignore) {}} else {throw new ShouldNeverHappenException("PropertyClass for prefix: [" + propertyPrefix + "] should not be null.");}if (valueObject == null) {valueObject = getFieldValue(propertyClass.newInstance(), propertySuffix, dataId);}return valueObject;
}

1、如果方法是以get开头,并且参数个数为1/2/3,则执行其他的获取配置的逻辑,否则执行原生Configuration对象的逻辑
2、我们没必要纠结为啥是这样的规则,这就是Seata的一个约定
3、其他获取配置的逻辑,就是指通过Spring的方式获取对应配置值

到这里已经清楚了配置增强的原理,同时,也可以猜测得出唯一的ExtConfigurationProvider实现SpringBootConfigurationProvider,肯定是和Spring相关

配置增强与Spring

在介绍这块内容之前,我们先简单介绍一下Seata的使用方式:

  1. 非Starter方式:引入依赖 seata-all, 然后手动配置几个核心的Bean
  2. Starter方式: 引入依赖seata-spring-boot-starter,全自动准配,不需要自动注入核心Bean

SpringBootConfigurationProvider就在seata-spring-boot-starter模块中,也就是说,当我们通过引入seata-all的方式来使用Seata时,配置增强其实没有什么作用,因为此时根本找不到ExtConfigurationProvider实现类,自然就不会增强。

seata-spring-boot-starter是如何将这些东西串联起来的?

1、首先,在seata-spring-boot-starter模块的resources/META-INF/services目录下,存在一个spring.factories文件,内容分如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.seata.spring.boot.autoconfigure.SeataAutoConfiguration,\# 暂时不管
io.seata.spring.boot.autoconfigure.HttpAutoConfiguration

2、在SeataAutoConfiguration文件中,会创建以下Bean: GlobalTransactionScanner 、SeataDataSourceBeanPostProcessor、SeataAutoDataSourceProxyCreator、SpringApplicationContextProvider。前3个和我们本文要讲的内容不相关,主要关注SpringApplicationContextProvider,核心代码非常简单,就是将ApplicationContext保存下来:

public class SpringApplicationContextProvider implements ApplicationContextAware {@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT, applicationContext);}
}

3、然后,在SeataAutoConfiguration文件中,还会将一些xxxProperties.Class和对应的Key前缀缓存到PROPERTY_BEAN_MAP中。``xxxProperties就简单理解成application.yaml`中的各种配置项:

static {PROPERTY_BEAN_MAP.put(SEATA_PREFIX, SeataProperties.class);PROPERTY_BEAN_MAP.put(CLIENT_RM_PREFIX, RmProperties.class);PROPERTY_BEAN_MAP.put(SHUTDOWN_PREFIX, ShutdownProperties.class);...省略...
}

至此,整个流程其实已经很清晰,在有SpringBootConfigurationProvider配置增强的时候,我们获取一个配置项的流程如下:

  1. 先根据p配置项Key获取对应的xxxProperties对象
  2. 通过ObjectHolder中的ApplicationContext获取对应xxxProperties的SpringBean
  3. 基于xxxProperties的SpringBean获取对应配置的值
  4. 至此,通过配置增强,我们成功的获取到application.yaml中的值

相关文章:

Apache Seata配置管理原理解析

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata配置管理原理解析 说到Seata中的配置管理&#xff0c;大家可能会想到Seata中适配…...

深入理解C# log4Net日志框架:功能、使用方法与性能优势

文章目录 1、log4Net的主要特性2、log4Net框架详解配置日志级别 3、log4Net的使用示例4、性能优化与对比5、总结与展望 在软件开发过程中&#xff0c;日志记录是一个不可或缺的功能。它可以帮助开发者追踪错误、监控应用程序性能&#xff0c;以及进行调试。在C#生态系统中&…...

BDD 100K dataset 的标签数据结构(json文件)

最近在筛选自己需要的labels&#xff0c;所以要弄清楚这个数据集的数据结构才行&#xff1a; 1.整个json文件以列表形式储存 2.每张图片以一个字典形式储存 3.存储图片的字典内的以‘name’为key的键值对对应的‘value’是我需要的图片名称信息 4.存储图片的字典内的以‘label…...

AcWing 1550:完全二叉搜索树

【题目来源】https://www.acwing.com/problem/content/1552/【题目描述】二叉搜索树 (BST) 递归定义为具有以下属性的二叉树&#xff1a; &#xff08;1&#xff09;若它的左子树不空&#xff0c;则左子树上所有结点的值均小于它的根结点的值 &#xff08;2&#xff09;若它的右…...

使用kali Linux启动盘轻松破解Windows电脑密码

破解分析文章仅限用于学习和研究目的&#xff1b;不得将上述内容用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。谢谢&#xff01;&#xff01; 效果展示&#xff1a; 使用kali Linux可以轻松破解Windows用户及密码 准备阶段&#xff1a; &#xff08…...

Vue2中跨组件共享公共属性的方法、优缺点与实现

一、vuex&#xff08;最常用&#xff09; 优缺点 优点&#xff1a;集中管理状态&#xff0c;组件间解耦&#xff0c;易于调试和测试。缺点&#xff1a;学习成本较高&#xff0c;对于小项目可能过于复杂。 适用场景 大型、复杂的单页面应用&#xff08;SPA&#xff09;。需要全局…...

2024亚太杯数学建模竞赛(B题)的全面解析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024亚太杯数学建模竞赛&#xff08;B题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有详尽的建模过程和解…...

【PWN · ret2syscall | GoPwn】[2024CISCN · 华中赛区]go_note

一道GoPwn&#xff0c;此外便是ret2syscall的利用。然而过程有不小的曲折&#xff0c;参考 返璞归真 师傅的wp&#xff0c;堪堪完成了复现。复现过程中&#xff0c;师傅也灰常热情回答我菜菜的疑问&#xff0c;感谢&#xff01;2024全国大学生信息安全竞赛&#xff08;ciscn&am…...

关于学习方法的优化

这是一种新的学习方法&#xff0c;一种新的学习形式&#xff0c;可以通过歌唱的方式&#xff0c;运用&#xff0c;把自己每天要进行的内容进行一个复习&#xff0c;进行一个重复&#xff0c;这样可以实现随时随地进行一个学习&#xff0c;这样可以帮助快速走出来&#xff01; 您…...

万界星空科技MES系统中的排版排产功能

在当今高度竞争的市场环境中&#xff0c;企业对于生产管理的效率和质量要求日益提高。作为智能制造的重要组成部分&#xff0c;制造执行系统&#xff08;MES&#xff09;以其强大的功能&#xff0c;在提升企业生产能力方面发挥着不可替代的作用。万界星空科技作为行业领先的智能…...

kubeadm离线部署kubernetesv1.30.0

背景&#xff1a;最近由于docker image获取镜像受限的问题&#xff0c;以及公司内部部署kubernetes受限于内部网络无法访问公网的问题&#xff0c;对于离线部署kubernetes成为不是十分方便。谨以此文仅供参考。 kubernetes部署节点信息 kubernetes版本 1.30.0 操作系统版本&a…...

【PYG】dataloader和densedataloader

DenseDataLoader 是专门用于处理稠密图数据的&#xff0c;而 DataLoader 通常用于处理稀疏图数据。两者的主要区别在于它们的输入数据格式和处理方式。DenseDataLoader 适合处理固定大小的邻接矩阵和节点特征矩阵的数据&#xff0c;而 DataLoader 更加灵活&#xff0c;可以处理…...

完美解决ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: NO)

已解决ERROR 1045 (28000): Access denied for user ‘root‘‘localhost‘ (using password: NO) 下滑查看解决方法 文章目录 报错问题解决思路解决方法交流 报错问题 ERROR 1045 (28000): Access denied for user ‘root‘‘localhost‘ (using password: NO) 解决思路 对…...

ForkJoinPool 简介

引言 在现代并行编程中&#xff0c;处理大规模任务时将任务分割成更小的子任务并行执行是一种常见的策略。Java 提供了 Fork/Join 框架来支持这一模式&#xff0c;其中 ForkJoinPool 是其核心组件。本文将详细介绍 ForkJoinPool 的概念、使用方法和实际应用。 1. ForkJoinPoo…...

复现YOLO_ORB_SLAM3_with_pointcloud_map项目记录

文章目录 1.环境问题2.遇到的问题2.1编译问题1 monotonic_clock2.2 associate.py2.3 associate.py问题 3.运行问题 1.环境问题 首先环境大家就按照github上的指定环境安装即可 环境怎么安装网上大把的资源&#xff0c;自己去找。 2.遇到的问题 2.1编译问题1 monotonic_cloc…...

Docker:Docker网络

Docker Network 是 Docker 平台中的一项功能&#xff0c;允许容器相互通信以及与外界通信。它提供了一种在 Docker 环境中创建和管理虚拟网络的方法。Docker 网络使容器能够连接到一个或多个网络&#xff0c;从而使它们能够安全地共享信息和资源。 预备知识 推荐先看视频先有…...

Ubuntu 24.04-自动安装-Nvidia驱动

教程 但在安全启动模式下可能会报错。 先在Nvidia官网找到GPU对应的驱动版&#xff0c; 1. 在软件与更新中选择合适的驱动 2. ubuntu自动安装驱动 sudo ubuntu-drivers autoinstall显示驱动 ubuntu-drivers devices3. 安装你想要的驱动 sudo apt install nvidia-driver-ve…...

【CSAPP】-attacklab实验

目录 实验目的与要求 实验原理与内容 实验设备与软件环境 实验过程与结果&#xff08;可贴图&#xff09; 实验总结 实验目的与要求 1. 强化机器级表示、汇编语言、调试器和逆向工程等方面基础知识&#xff0c;并结合栈帧工作原理实现简单的栈溢出攻击&#xff0c;掌握其基…...

docker部署onlyoffice,开启JWT权限校验Token

原来的部署方式 之前的方式是禁用了JWT&#xff1a; docker run -itd -p 8080:80 --name docserver --network host -e JWT_ENABLEDfalse --restartalways onlyoffice/documentserver:8 新的部署方式 参考文档&#xff1a;https://helpcenter.onlyoffice.com/installation/…...

Hive排序字段解析

Hive排序字段解析 在Hive中&#xff0c;CLUSTER BY、DISTRIBUTE BY、SORT BY和ORDER BY是用于数据分发和排序的关键子句&#xff0c;它们各自有不同的用途和性能特点。让我们逐一解析这些子句&#xff1a; 1. DISTRIBUTE BY 用途: 主要用于控制如何将数据分发到Reducer。它可…...

3101.力扣每日一题7/6 Java(接近100%解法)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 思路 解题方法 时间复杂度 空间复杂度 Code 思路 主要是基于对…...

virtualbox窗口和win10窗口的切换

1、问题&#xff1a; 从windows切换到虚拟机可以用快捷键 ALTTAB&#xff0c;但是从虚拟机到windows使用 ALTTAB 无法成功切换 2、解决方法&#xff1a; 按下图操作 按上面步骤设置之后&#xff0c;每次要从虚拟机窗口切换到windows窗口 只需要先按 CtrlAlt 跳出虚拟机窗口&…...

卫星轨道平面简单认识

目录 一、轨道平面 1.1 轨道根数 1.2 应用考虑 二、分类 2.1 根据运行高度 2.2 根据运行轨迹偏心率 2.3 根据倾角大小 三、卫星星座中的轨道平面 四、设计轨道平面的考虑因素 一、轨道平面 1.1 轨道根数 轨道平面是定义卫星或其他天体绕行另一天体运动的平面。这个平…...

IP-Guard定制函数配置说明

设置客户端配置屏蔽&#xff1a; 关键字&#xff1a;disfunc_austascrtrd 内容&#xff1a;1 策略效果&#xff1a;屏幕整个屏幕监控模块。会导致屏幕历史查询这个功能也不能使用。 security_proxy1 安全代理参数 safe_enforce_authproc进程 强制软件上 安全代理网关&#xf…...

C++常用类

C常用类 1. std::string类2. std::vector 类2.1 特性2.2 用法 1. std::string类 std::string 是 C 标准库中的一个类&#xff0c;用于处理字符串。它提供了许多方法来创建、操作和管理字符串&#xff0c;如连接、查找、比较、替换和分割等操作。std::string 类定义在 头文件中…...

React Hooks --- 分享自己开发中常用的自定义的Hooks (1)

为什么要使用自定义 Hooks 自定义 Hooks 是 React 中一种复用逻辑的机制&#xff0c;通过它们可以抽离组件中的逻辑&#xff0c;使代码更加简洁、易读、易维护。它们可以在多个组件中复用相同的逻辑&#xff0c;减少重复代码。 1、useThrottle 代码 import React,{ useRef,…...

uniapp H5页面设置跨域请求

记录一下本地服务在uniapp H5页面访问请求报跨域的错误 这是我在本地起的服务端口号为8088 ip大家可打开cmd 输入ipconfig 查看 第一种方法 在源码视图中配置 "devServer": {"https": false, // 是否启用 https 协议&#xff0c;默认false"port&q…...

使用myCobot280和OAK-D OpenCV DepthAI摄像头制作一个实时脸部跟踪的手机支架!

引言 由于YouTube和Netflix的出现&#xff0c;我们开始躺着看手机。然而&#xff0c;长时间用手拿着手机会让人感到疲劳。这次我们制作了一个可以在你眼前保持适当距离并调整位置的自动移动手机支架&#xff0c;让你无需用手拿着手机。请务必试试&#xff01; 准备工作 这次我们…...

Xilinx FPGA:vivado关于单端ROM的一个只读小实验

一、实验要求 将生成好的voe文件里的数据使用rom读取出来&#xff0c;采用串口工具发送给电脑&#xff08;当按键来临时&#xff09;。 二、程序设计 按键消抖模块&#xff1a; timescale 1ns / 1ps module key_debounce(input sys_clk ,input rst_n…...

集成学习(一)Bagging

前边学习了&#xff1a;十大集成学习模型&#xff08;简单版&#xff09;-CSDN博客 Bagging又称为“装袋法”&#xff0c;它是所有集成学习方法当中最为著名、最为简单、也最为有效的操作之一。 在Bagging集成当中&#xff0c;我们并行建立多个弱评估器&#xff08;通常是决策…...