2024年1月

前言

看到标题大家可能会有点疑惑吧:OpenFeign 不是挺好用的吗?尤其是微服务之间的远程调用,平时用的也挺习惯的,为啥要替换呢?

背景和原因是这样的:

  1. 部门/团队在安全性上有所考虑,即尽可能地减少/消除引入外部依赖,尽量只使用自研依赖、apache、Spring等必须的开源依赖;
  2. 而 OpenFeign 的使用则是引入了 Spring Cloud 依赖(不在安全要求范围内),所以需要考虑替换;
  3. 为以后团队的项目上 Spring 6做铺垫,Spring 6 会有 Spring 内置的 Http Interface 发起远程服务调用。

下面将从介绍 OpenFeign、常见的 Http API 以及重点介绍
Spring 自带的 RestTemplate Http 模板
这3个方面展开。


一、何为OpenFeign

OpenFeign 是 Spring Cloud 在 Feign 的基础上支持了 SpringMVC 的注解,如 @RequesMapping 等,
其底层默认使用的是 URLConnection 实现。

OpenFeign 的 @FeignClient 注解可以解析 SpringMVC 的@RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

1.1@FeignClient注解

只要是使用 OpenFeign 那么这个注解是一定会使用到的,该注解的主要属性如下:

  • url:可以手动指定 @FeignClient 调用的远程服务地址,
    如果同时声明 url 和 name 则以 url 为准
    ,此时 name 仅作为该 FeignClient 的名称而已;
  • name:指定当前 FeignClient 的名称,
    如果项目使用了 Ribbon,那么 name 属性会作为微服务的名称,用于服务的发现
  • value:
    实际上和 name 是用一个属性
    ,因为这两个属性互相使用了别名,使用的时候两者选其一即可;
  • path:定义当前 FeignClient 的统一前缀,即表示所有调用的远程服务都会走这个 path 声明的 http 前缀;
  • configuration:Feign 的配置类,可以自定义 Feign 的 Encoder、Decoder、LogLevel、Contract 等;
  • fallback:定义容错的处理类,当调用远程接口失败或超时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。

简单示例如下:

@FeignClient(url = "https://xxx.abcdef.com", name = "SubmitTaskClient", 
             configuration = OpenFeignFormConfig.class, fallback = HystrixFallbackConfig.class)
public interface SubmitTaskClient {

    /**
     * 调用远程接口实现,入参为 json 字符串
     * @param paramJsonStr
     * @param header
     * @return
     */
    @PostMapping
    String submitNormalTask(@RequestBody String paramJsonStr, @RequestHeader Map<String, String> header);

    /**
     * 调用远程接口实现,入参为 map 的表单形式
     * @param map
     * @return
     */
    @PostMapping(value = "/task/create", headers = {"content-type=application/x-www-form-urlencoded"})
    String submitTransTask(Map<String, ?> map);

}

1.2注意事项

在远程服务调用一般存在两种情况:

  1. 远程服务在注册中心

    如果远程服务的提供方已经注册到注册中心,那么 name 或者 value 的值为:注册中心的服务名称,且必须为所有客户端指定一个 name 或者 value。

    @FeignClient(name = "SubmitTaskService", configuration = OpenFeignFormConfig.class, fallback = HystrixFallbackConfig.class)
    
  2. 单独的远程 http 接口

    此处 name 的值为当前 feignClient 客户端的名称,指定的 url 则为远程服务的地址。

    @FeignClient(url = "https://xxx.abcdef.com", name = "SubmitTaskClient", configuration = OpenFeignFormConfig.class)
    

    以上两种方式都能正常进行远程服务调用。name 可以为注册中心的服务名称,同时有 url 属性时,name 就与注册中心服务名称无关。


二、常见的Http API

OpenFeign 本质上还是使用 http 请求完成服务的调用
,其实使用以下的这些 Http API 经过适当的改造后,也可以达到效果。

2.1Apache

在后端领域,出现比较早而且使用仍然很广泛的 HTTP 客户端框架非 Apache HttpClien 莫属了,目前大量项目和公司仍在采用该框架。

Apache HttpClient 有着不错的性能、丰富的功能以及强大的自定义实现等特色。但是随着技术的发展和设计理念的改变,Apache HttpClient 显的有些落伍了。

个人认为其最不受欢迎的点主要在于 API 的设计过于臃肿,大量的配置需要手动声明,当见过了更多好的的 Http API 后你可能就会不太想继续用了。
当然公司框架正在使用 Apache HttpClient 的情况下也无可厚非,虽然复杂点,但用还是可以用的。

引入 pom 依赖:

        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>

POST 请求示例如下:

    public String apacheHttpClientPost(String url, String params) throws Exception {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Content-Type", "application/json");
        String charSet = "UTF-8";
        StringEntity entity = new StringEntity(params, charSet);
        httpPost.setEntity(entity);
        CloseableHttpResponse response = null;
        try {
            response = httpclient.execute(httpPost);
            StatusLine status = response.getStatusLine();
            int state = status.getStatusCode();
            if (state == 200) {
                HttpEntity responseEntity = response.getEntity();
                return EntityUtils.toString(responseEntity);
            }
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

2.2Okhttp

OkHttp3 是 OkHttp 发展到版本3.0之后的名字。在 maven 中央仓库搜索 okhttp,可以看到 3.0 之后的版本统一称为 OkHttp3。

OKHttp3 是一个当前主流的网络请求的开源框架,由 Square 公司开发,目标是用于替代 HttpUrlConnection 和 Apache HttpClient。

        <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.1</version>
        </dependency>

POST 请求示例如下:

	public String okHttpPostMethod(String url,String body,  OkHttpClient okHttpClient) throws IOException {
        MediaType JSON_TYPE = MediaType.parse("application/json");
        Request request = new Request.Builder()
                .url(url).post(RequestBody.create(JSON_TYPE, body)).addHeader("Content-Type", "application/json")
                .build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
        } catch (Exception e) {
            e.printStackTrace();
        }
        assert response != null;
        if (response.isSuccessful()) {
            return response.body() == null ? "" : response.body().string();
        }
        return null;
    }

对于需要单独处理 POST、GET 等请求的情况来说,OkHttp3 是很适合的。

但是对于一些通用请求,比如在一个通用方法的参数里只需要传入 Method 枚举(POST、GET 等)就可以实现对应类型的请求,Hutool 和 RestTemplate 可能更为合适。

2.3Hutool

Hutool 中的工具方法来自每个用户的精雕细琢,它涵盖了 Java 开发底层代码中的方方面面,是国内 Java 开发工具类库的集大成者,很多公司的很多项目都在用。

其中 Hutool 的 http 部分是基于 HttpUrlConnection 的 Http 客户端封装,大致发起调用的步骤:首先构建一个http请求,包括请求的地址、请求方式、请求头、请求参数等信息,然后执行请求返回一个 http 响应类,最后通过这个相应类可以获取响应的主体、是否请求成功等信息。

但遗憾的是,团队里也有比较明确的安全规定:不允许在项目中引入 Hutool 依赖包。

引入 pom 依赖:

        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.8</version>
        </dependency>

创建通用请求
的示例如下:

    public String huToolMethod(String url, HttpMethod httpMethod, RequestBody body) {
        Map<String, String> headers = new HashMap<>();
        headers.put(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf-8");
        // 创建通用请求, 可以涵盖所有常见的 HTTP 方法, 同时放入 url
        HttpRequest request = HttpUtil.createRequest(Method.valueOf(httpMethod.name()), url);
        // 放入请求的 header 和 body
        HttpResponse response = request.addHeaders(headers).body(JSON.toJSONString(body)).execute();
        return response.body();
    }


三、RestTemplate

RestTemplate 是 Spring 框架用来访问 RESTFUL 服务的客户端模板类,主要功能有:

1、发起 HTTP 请求,包括 RESTful 风格的 GET,POST,PUT,DELETE 等常见方法;

2、自动将响应结果映射为 Java 对象,不用手动解析 JSON 或 XML。

3、自定义设置请求头、消息转码、Cookie 等功能。

4、对不同的输入/输出类型提供对应的方法,如字符串、对象、多部分等。

5、同时还支持远程调用,不受同源策略限制。

引入 pom 依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

配置类:

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(10000);
        factory.setReadTimeout(10000);
        return factory;
    }
}

3.1详解.execute()

.execute() 是 RestTemplate 中最常见的关于执行 HTTP 请求的方法,它允许开发人员高度定制 HTTP 请求。

先来给一段示例:

    public String restTemplateExecuteMethod(String url, String token, Object body, HttpMethodName method){
        HttpHeaders httpHeaders = new HttpHeaders();
        // headers:HttpHeaders 类型,包括所有头信息
        httpHeaders.add("Authorization", token);
        httpHeaders.add("Content-Type", "application/json;charset=utf-8");
        // body:请求体,可以是任何对象,也可以是 null
        HttpEntity<Object> httpEntity = new HttpEntity<>(JSON.toJSONString(body), httpHeaders);
        RequestCallback requestCallback = restTemplate.httpEntityCallback(httpEntity, Object.class);
        ResponseExtractor<ResponseEntity<Object>> responseExtractor = restTemplate.responseEntityExtractor(Object.class);
        // 发送请求,method.name() 表示传入的方法,包括 GET、POST、DELETE 等
        ResponseEntity<Object> entity = restTemplate.execute(url, HttpMethod.valueOf(method.name()), requestCallback, responseExtractor);
        // 直接返回 body
        Assert.notNull(entity, "返回体为空!");
        log.info("---返回的内容:{}---", JSON.toJSONString(entity.getBody()));
        return JSON.toJSONString(entity.getBody());
    }

下面是一些对象的介绍:

  • HttpEntity 对象

    它主要有两个作用:

    1、表示 HTTP 请求:当表示 HTTP 请求时,HttpEntity 有两个主要组成部分:请求头和请求体。

    2、表示 HTTP 响应:当表示 HTTP 响应时,有三个部分:状态码、响应头和响应体。

    其中的参数:


    • headers:HttpHeaders 类型,包括所有头信息;
    • body:请求或响应体,可以是任何对象,也可以是null;
    • statusCode:HttpStatus 类型,只有在表示响应时才有效。
  • RequestCallback 对象

    RequestCallback 是 RestTemplate中用来定制HTTP请求的一个接口,可以设置请求头、请求体、查询字符串参数。

    Callback接口只有一个方法:

    void doWithRequest(ClientHttpRequest request) throws IOException
    


四、文章小结

文章的最后,我选择了 okhttp3 和 RestTemplate 来进行 OpenFeign 的替换工作:okhttp3 处理单个 POST/GET 等请求,使用.execute() 处理通用 HTTP 请求。

那么如何使用 Http API 代替 OpenFeign 进行远程服务调用的分享到这里就结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!

本文分享自华为云社区《
【昇思技术公开课笔记-大模型】Bert理论知识
》,作者: JeffDing。

NLP中的预训练模型

语言模型演变经历的几个阶段
image.png

  1. word2vec/Glove将离散的文本数据转换为固定长度的静态词向量,后根据下游任务训练不同的语言模型
  2. ELMo预训练模型将文本数据结合上下文信息,转换为动态词向量,后根据下游任务训练不同的语言模型
  3. BERT同样将文本数据转换为动态词向量,能够更好地捕捉句子级别的信息与语境信息,后续只需对BERT参数进行微调,仅重新训练最后的输出层即可适配下游任务
  4. GPT等预训练语言模型主要用于文本生成类任务,需要通过prompt方法来应用于下游任务,指导模型生成特定的输出。

BERT模型本质上是结合了ELMo模型与GPT模型的优势。

  • 相比于ELMo,BERT仅需改动最后的输出层,而非模型架构,便可以在下游任务中达到很好的效果;
  • 相比于GPT,BERT在处理词元表示时考虑到了双向上下文的信息;

Bert介绍

image.png

2018年Google发布了BERT(来自Transformer的双向自编码器)预训练模型,旨在通过联合左侧和右侧的上下文,从未标记文本中预训练出一个深度双向表示模型。因此,BERT可以通过增加一个额外的输出层来进行微调,就可以达到为广泛的任务创建State-of-the-arts 模型的效果,比如QA、语言推理任务。

当时将预训练模应用于下游任务的策略通常有两种:基于特征的(feature-based)和基于微调(fine-tuning);前者比如ELMo,后者比如OpenAI GPT;

这两种策略在预训练期间具有相同的目标函数,在预训练期间,它们使用单向语言模型来学习一般的语言表示。但当前对预训练方法的限制(尤其是对基于微调的方法)是标准语言模型是单向(unidirectional)的,所以限制了在预训练阶段可选的模型结构体系。

比如GPT是从左到右的,每个token只能关注到前一个token的self-attention layers。这种局限对于句子级任务(sentence-level tasks)来说还不是很打紧,但是对于token-level tasks(比如QA)就很致命,所以结合两个方向的上下文信息至关重要。

Bert Input

第一步:Tokenization, 输入的句子经过分词后,首尾添加[CLS]与[SEP]特殊字符,后转换为数字id
image.png

第二步:Embedding, 输入到BERT模型的信息由三部分内容组成:

表示内容的token ids

表示位置的position ids

用于区分不同句子的token type ids

image.png

将三种信息分别输入Embedding层

image.png

如果出现输入是句子对的情况呢?

image.png

BERT Architecture

BERT由Encoder Layer堆叠而成,Encoder Layer的组成与Transformer的Encoder Layer一致:

自注意力层 + 前馈神经网络,中间通过residual connection和LayerNorm连接

image.png

BERT(Bidirectional Encoder Representation from Transformers)是由Transformer的Encoder层堆叠而成,BERT的模型大小有如下两种:

BERT BASE:与Transformer参数量齐平,用于比较模型效果(110M parameters)

BERT LARGE:在BERT BASE基础上扩大参数量,达到了当时各任务最好的结果(340M parameters)

BERT Output

BERT会针对每一个位置输出大小为hidden size的向量,在下游任务中,会根据任务内容的不同,选取不同的向量放入输出层

image.png

pooler output

例如,在诈骗邮件分类任务中,我们会将表示句子级别信息的
[CLS]
token所对应的向量,经过Bert Pooler放入classfier中,得到对spam/not spam分类的预测。

image.png

我们一般称[CLS]经过线性层+激活函数tanh的输出为pooler output,用于句子级别的分类/回归任务

sequence output

例如,在词性标注任务(POS Tagging)中,我们需要获得每一个token所对应的类别,因此需要将[CLS]和[SEP]中有实际意义的token输出,分别输入对应的classifier中。

image.png

我们一般称BERT输出的每个位置对应的vector为sequence output

BERT的不同下游任务

image.png

image.png

BERT预训练

BERT预训练任务有两种:Masked Language Modelling(MLM) 和 Next Sentence Prediction (NSP)。

MLM:随机遮盖输入句子中的一些词语,并预测被遮盖的词语是什么(完形填空)

NSP:预测两个句子是不是上下文的关系

image.png

Masked Language Model(MLM)

Masked Language Modelling(MLM) 捕捉词语级别的信息

在输入中随机遮盖15%的token(即将token替换为[MASK])

将[MASK]位置对应的BERT输出放入输出层中,预测被遮盖的token

image.png

在将[MASK]位置所对应的BERT输出放入输出层后,本质上是在进行一个多分类任务

image.png
image.png

为了使得预训练任务和推理任务尽可能接近,BERT在随机遮盖的15%的tokens中又进行了进一步的处理:

80%的概率替换为[MASK]

10%的概率替换为文本中的随机词

10%的概率不进行替换,保持原有的词元

image.png

Next Sentence Prediction(NSP)

Next Sentence Prediction (NSP) 捕捉句子级别信息,简单来说是一个针对句子对的分类问题,判断一组句子中,句子B是否为句子A的下一句(IsNext or NotNext)

image.png

image.png

Bert微调

在下游任务中,我们使用少量的标注数据(labelled data)对预训练Transformer编码器的所有参数进行微调,额外的输出层将从头开始训练。

image.png

小景最近在工作中遇到了一个问题,用impdp做数据导入的时候,有以下报错,下面是问题排查过程:

首先看到了ORA-01950:no privileges on tablespace ‘PUBDATA’ 这个报错,小景想到了以下原因:

  1. 权限问题
    :ORA-01950错误表示用户没有在PUBDATA表空间上的特定对象的权限。这可能是由于数据库权限配置不当,导致导出进程无法访问相关的表空间和对象。解决方案是检查用户权限设置,确保用户拥有足够的权限来执行数据导出操作。
  2. 作业不存在
    :ORA-31626错误表示尝试操作的作业不存在。这可能是由于指定的作业名称不正确或者作业已经被删除或更改。需要检查作业名称的拼写和准确性,并确保作业存在且具有正确的配置。
  3. 无法创建主表
    :ORA-31633错误表示无法创建主表。这可能是由于表空间不足、磁盘空间不足或其他存储问题导致的。需要检查数据库的存储配置和可用空间,确保有足够的空间来创建所需的表结构。
  4. 内部错误
    :ORA-06512错误表示遇到了内部错误。这可能是由于Oracle数据库内部的问题或配置不当导致的。需要进一步检查Oracle数据库的状态和配置,以及版本兼容性问题。

然后我把关注点主要聚焦在ORA-01950权限问题和ORA-31633无法创建主表这两个报错上,针对ORA-01950权限问题,我这边直接用system用户来操作,然后查了下对应的用户下的表空间:

1 SELECT   
2 u.username,3 t.tablespace_name,4     ROUND((t.total_mb - t.free_mb) / t.total_mb * 100, 2) ASused_percent5 FROM   
6 dba_users u7 JOIN   
8     (SELECT   
9         user#,10         SUM(bytes) / 1024 / 1024 AStotal_mb,11         SUM(bytes) / 1024 / 1024 - SUM(NVL(free_bytes, 0)) / 1024 / 1024 ASfree_mb12     FROM   
13 dba_data_files14     WHERE   
15         owner = 'SYSTEM'  
16     GROUP BY user#) t17 ON   
18     u.user# = t.user#;

这个查询将返回
SYSTEM
用户所拥有的每个表空间的名称、使用率和总大小。通过这个查询,小景发现system和sysaux用户下的表空间使用率都达到了99%,那么下面就是要进行表空间的拓展:

1. 登录到数据库

使用SQL*Plus或其他数据库客户端工具,通过具有足够权限的用户(如SYSDBA)连接到目标数据库。

2. 检查表空间状态

在执行任何操作之前,了解表空间的使用情况是非常重要的。你可以使用以下查询来获取更详细的表空间使用情况:

1 SELECT   
2 tablespace_name,3     SUM(bytes) / 1024 / 1024 AStotal_mb,4     SUM(used_bytes) / 1024 / 1024 ASused_mb,5     SUM(free_bytes) / 1024 / 1024 ASfree_mb,6     ROUND((SUM(used_bytes) / SUM(bytes)) * 100, 2) ASused_percent7 FROM   
8 dba_data_files9 GROUP BY tablespace_name;

这将显示每个表空间的名称、总大小、已用大小、可用大小以及使用百分比。

3. 添加数据文件到表空间

使用
ALTER TABLESPACE
语句添加新的数据文件。你需要指定数据文件的路径、文件名以及大小。例如:

1
ALTER
TABLESPACE your_tablespace_name
ADD
DATAFILE
'
/path_to_datafile/datafile_name.dbf
'
SIZE 500M;

这将向名为
your_tablespace_name
的表空间添加一个大小为500MB的数据文件。确保替换
/path_to_datafile/datafile_name.dbf
为实际的文件路径和名称。

4. 调整现有数据文件的大小

如果需要调整现有数据文件的大小,可以使用以下语句:

1
ALTER
DATABASE
DATAFILE
'
/path_to_datafile/datafile_name.dbf
'
RESIZE 1024M;

这将调整名为
/path_to_datafile/datafile_name.dbf
的数据文件的大小为1024MB。确保替换为实际的文件路径和名称。

5. 重新构建索引(可选)

在某些情况下,添加或调整数据文件后,可能需要重新构建表空间的索引以优化性能。你可以使用以下命令来重新构建索引:

1
ALTER
INDEX
your_tablespace_name REBUILD;

这将重新构建存储在名为
your_tablespace_name
的表空间中的所有索引。确保替换为实际的表空间名称。

6. 验证扩展操作(可选)

执行适当的查询和检查,以确保表空间已成功扩展并且应用程序能够正常使用。你可以再次运行步骤2中的查询来验证扩展是否成功。确保检查数据库的性能和响应时间,以确保一切正常。

7. 定期监控和维护(可选)

为了保持表空间的健康和性能,建议定期监控表空间的使用情况,并执行常规的维护任务,如重新组织表和索引。确保在需要时根据业务需求调整表空间大小。可以使用Oracle提供的工具和监视工具来监控表空间的使用情况。

至此,问题完美解决,下面是一些总结延申及拓展:

1. 检查该用户是否具有表空间中特定对象的访问权限:

    1. 特定对象的访问权限:用户需要具有对PUBDATA表空间中特定对象的访问权限。您可以查询数据库中的权限表,例如USER_TAB_PRIVS,以检查用户是否具有对目标对象的访问权限。
      1. 1
        sql`
        SELECT
        *
        FROM
        USER_TAB_PRIVS
        WHERE
        TABLE_NAME
        =
        '
        YOUR_TABLE
        '
        ;`
    2. 表空间的存储权限:用户还需要具有在PUBDATA表空间中创建、修改和删除对象的存储权限。您可以查询数据库中的权限表,例如USER_TS_QUOTAS,以检查用户在表空间上的配额和限制。
      1. 1
        sql`
        SELECT
        *
        FROM
        USER_TS_QUOTAS
        WHERE
        TABLESPACE_NAME
        =
        '
        PUBDATA
        '
        ;`

2. impdp中的重定向:

1 impdp system/123456@orcl directory=kdbak schemas=K3CLOUDDCUSERORACLE dumpfile=K3Cloud20200717000101.dmp table_exists_action=replace exclude=statistics transform=OID:N Remap_schema=hr:hr2

其中要指定一下schema,schema就是导入的目标用户,需要重定向就用remap即可,例如:你的表都在hr这个用户下,还导入到hr下,就指定schema=hr,如果要导入到hr2用户下,就用remap重定向。

最后,希望小景的这篇文章可以帮助您解决实际工作中的问题,有什么问题可以评论区或者关注我的微信公众号来交流。

一、pom.xml需要引入的依赖
二、项目开启熔断器开关
2.1 注解方式
2.2 xml方式
三、依赖类缺失问题
四、版本匹配安全检查问题
五、测试验证
六、结论

一、pom.xml需要引入的依赖

pom.xml

 <!-- springboot升级到2.6.7,同样适用于2.7.0,2.7.18等 -->
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.6.7</version>
       <relativePath/> <!-- lookup parent from repository -->
   </parent>  
        
     <!-- hystrix需要升级到2.x以上版本 -->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
           <version>2.2.10.RELEASE</version>
      </dependency>
 
    <!-- 升级后运行时缺失依赖 -->
     <dependency>
       <groupId>org.hdrhistogram</groupId>
       <artifactId>HdrHistogram</artifactId>
       <version>2.1.12</version>
     </dependency>

二、项目开启熔断器开关

2.1 注解方式

@EnableHystrix

@EnableCircuitBreaker

注:项目启动类中加上以上注解,以上两者取其一即可;

2.2 xml方式

以上注解方式底层就是实例化此类:

三、依赖类缺失问题

此时运行项目将报以下错误:

errpr.log
 D:\JDK\jdk1.8\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:55698,suspend=y,server=n -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\mingming.chen\AppData\Local\JetBrains\IntelliJIdea2021.3\captureAgent\debugger-agent.jar -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "D:\JDK\jdk1.8\jre\lib\charsets.jar;D:\JDK\jdk1.8\jre\lib\deploy.jar;D:\JDK\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\JDK\jdk1.8\jre\lib\ext\cldrdata.jar;D:\JDK\jdk1.8\jre\lib\ext\dnsns.jar;D:\JDK\jdk1.8\jre\lib\ext\jaccess.jar;D:\JDK\jdk1.8\jre\lib\ext\jfxrt.jar;D:\JDK\jdk1.8\jre\lib\ext\localedata.jar;D:\JDK\jdk1.8\jre\lib\ext\nashorn.jar;D:\JDK\jdk1.8\jre\lib\ext\sunec.jar;D:\JDK\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\JDK\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\JDK\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\JDK\jdk1.8\jre\lib\ext\zipfs.jar;D:\JDK\jdk1.8\jre\lib\javaws.jar;D:\JDK\jdk1.8\jre\lib\jce.jar;D:\JDK\jdk1.8\jre\lib\jfr.jar;D:\JDK\jdk1.8\jre\lib\jfxswt.jar;D:\JDK\jdk1.8\jre\lib\jsse.jar;D:\JDK\jdk1.8\jre\lib\management-agent.jar;D:\JDK\jdk1.8\jre\lib\plugin.jar;D:\JDK\jdk1.8\jre\lib\resources.jar;D:\JDK\jdk1.8\jre\lib\rt.jar;D:\LocalReposition\oherproject\testProject\PersonalAssistant\target\classes;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-starter\2.6.7\spring-boot-starter-2.6.7.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot\2.6.7\spring-boot-2.6.7.jar;D:\MAVEN\MavenRepository\org\springframework\spring-context\5.3.19\spring-context-5.3.19.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-autoconfigure\2.6.7\spring-boot-autoconfigure-2.6.7.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-starter-logging\2.6.7\spring-boot-starter-logging-2.6.7.jar;D:\MAVEN\MavenRepository\ch\qos\logback\logback-classic\1.2.11\logback-classic-1.2.11.jar;D:\MAVEN\MavenRepository\ch\qos\logback\logback-core\1.2.11\logback-core-1.2.11.jar;D:\MAVEN\MavenRepository\org\apache\logging\log4j\log4j-to-slf4j\2.17.2\log4j-to-slf4j-2.17.2.jar;D:\MAVEN\MavenRepository\org\slf4j\jul-to-slf4j\1.7.36\jul-to-slf4j-1.7.36.jar;D:\MAVEN\MavenRepository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\MAVEN\MavenRepository\org\springframework\spring-core\5.3.19\spring-core-5.3.19.jar;D:\MAVEN\MavenRepository\org\springframework\spring-jcl\5.3.19\spring-jcl-5.3.19.jar;D:\MAVEN\MavenRepository\org\yaml\snakeyaml\1.29\snakeyaml-1.29.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-starter-test\2.6.7\spring-boot-starter-test-2.6.7.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-test\2.6.7\spring-boot-test-2.6.7.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-test-autoconfigure\2.6.7\spring-boot-test-autoconfigure-2.6.7.jar;D:\MAVEN\MavenRepository\com\jayway\jsonpath\json-path\2.6.0\json-path-2.6.0.jar;D:\MAVEN\MavenRepository\net\minidev\json-smart\2.4.8\json-smart-2.4.8.jar;D:\MAVEN\MavenRepository\net\minidev\accessors-smart\2.4.8\accessors-smart-2.4.8.jar;D:\MAVEN\MavenRepository\jakarta\xml\bind\jakarta.xml.bind-api\2.3.3\jakarta.xml.bind-api-2.3.3.jar;D:\MAVEN\MavenRepository\jakarta\activation\jakarta.activation-api\1.2.2\jakarta.activation-api-1.2.2.jar;D:\MAVEN\MavenRepository\org\assertj\assertj-core\3.21.0\assertj-core-3.21.0.jar;D:\MAVEN\MavenRepository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;D:\MAVEN\MavenRepository\org\junit\jupiter\junit-jupiter\5.8.2\junit-jupiter-5.8.2.jar;D:\MAVEN\MavenRepository\org\junit\jupiter\junit-jupiter-api\5.8.2\junit-jupiter-api-5.8.2.jar;D:\MAVEN\MavenRepository\org\opentest4j\opentest4j\1.2.0\opentest4j-1.2.0.jar;D:\MAVEN\MavenRepository\org\junit\platform\junit-platform-commons\1.8.2\junit-platform-commons-1.8.2.jar;D:\MAVEN\MavenRepository\org\apiguardian\apiguardian-api\1.1.2\apiguardian-api-1.1.2.jar;D:\MAVEN\MavenRepository\org\junit\jupiter\junit-jupiter-params\5.8.2\junit-jupiter-params-5.8.2.jar;D:\MAVEN\MavenRepository\org\junit\jupiter\junit-jupiter-engine\5.8.2\junit-jupiter-engine-5.8.2.jar;D:\MAVEN\MavenRepository\org\junit\platform\junit-platform-engine\1.8.2\junit-platform-engine-1.8.2.jar;D:\MAVEN\MavenRepository\org\mockito\mockito-core\4.0.0\mockito-core-4.0.0.jar;D:\MAVEN\MavenRepository\net\bytebuddy\byte-buddy\1.11.22\byte-buddy-1.11.22.jar;D:\MAVEN\MavenRepository\net\bytebuddy\byte-buddy-agent\1.11.22\byte-buddy-agent-1.11.22.jar;D:\MAVEN\MavenRepository\org\objenesis\objenesis\3.2\objenesis-3.2.jar;D:\MAVEN\MavenRepository\org\mockito\mockito-junit-jupiter\4.0.0\mockito-junit-jupiter-4.0.0.jar;D:\MAVEN\MavenRepository\org\skyscreamer\jsonassert\1.5.0\jsonassert-1.5.0.jar;D:\MAVEN\MavenRepository\com\vaadin\external\google\android-json\0.0.20131108.vaadin1\android-json-0.0.20131108.vaadin1.jar;D:\MAVEN\MavenRepository\org\springframework\spring-test\5.3.19\spring-test-5.3.19.jar;D:\MAVEN\MavenRepository\org\xmlunit\xmlunit-core\2.8.4\xmlunit-core-2.8.4.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-starter-web\2.6.7\spring-boot-starter-web-2.6.7.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-starter-json\2.6.7\spring-boot-starter-json-2.6.7.jar;D:\MAVEN\MavenRepository\com\fasterxml\jackson\core\jackson-databind\2.13.2.1\jackson-databind-2.13.2.1.jar;D:\MAVEN\MavenRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.2\jackson-datatype-jdk8-2.13.2.jar;D:\MAVEN\MavenRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.2\jackson-datatype-jsr310-2.13.2.jar;D:\MAVEN\MavenRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.2\jackson-module-parameter-names-2.13.2.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-starter-tomcat\2.6.7\spring-boot-starter-tomcat-2.6.7.jar;D:\MAVEN\MavenRepository\org\apache\tomcat\embed\tomcat-embed-core\9.0.62\tomcat-embed-core-9.0.62.jar;D:\MAVEN\MavenRepository\org\apache\tomcat\embed\tomcat-embed-el\9.0.62\tomcat-embed-el-9.0.62.jar;D:\MAVEN\MavenRepository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.62\tomcat-embed-websocket-9.0.62.jar;D:\MAVEN\MavenRepository\org\springframework\spring-web\5.3.19\spring-web-5.3.19.jar;D:\MAVEN\MavenRepository\org\springframework\spring-beans\5.3.19\spring-beans-5.3.19.jar;D:\MAVEN\MavenRepository\org\springframework\spring-webmvc\5.3.19\spring-webmvc-5.3.19.jar;D:\MAVEN\MavenRepository\org\springframework\spring-aop\5.3.19\spring-aop-5.3.19.jar;D:\MAVEN\MavenRepository\org\springframework\spring-expression\5.3.19\spring-expression-5.3.19.jar;D:\MAVEN\MavenRepository\org\apache\mahout\mahout-math\0.13.0\mahout-math-0.13.0.jar;D:\MAVEN\MavenRepository\org\apache\commons\commons-math3\3.2\commons-math3-3.2.jar;D:\MAVEN\MavenRepository\com\google\guava\guava\14.0.1\guava-14.0.1.jar;D:\MAVEN\MavenRepository\it\unimi\dsi\fastutil\7.0.12\fastutil-7.0.12.jar;D:\MAVEN\MavenRepository\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar;D:\MAVEN\MavenRepository\com\tdunning\t-digest\3.1\t-digest-3.1.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-clustering-kmeans\4.3.1\tribuo-clustering-kmeans-4.3.1.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-core\4.3.1\tribuo-core-4.3.1.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-util-onnx\4.3.1\tribuo-util-onnx-4.3.1.jar;D:\MAVEN\MavenRepository\com\oracle\labs\olcut\olcut-config-protobuf\5.2.1\olcut-config-protobuf-5.2.1.jar;D:\MAVEN\MavenRepository\com\google\protobuf\protobuf-java\3.19.6\protobuf-java-3.19.6.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-data\4.3.1\tribuo-data-4.3.1.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-util-tokenization\4.3.1\tribuo-util-tokenization-4.3.1.jar;D:\MAVEN\MavenRepository\com\opencsv\opencsv\5.7.1\opencsv-5.7.1.jar;D:\MAVEN\MavenRepository\org\apache\commons\commons-text\1.10.0\commons-text-1.10.0.jar;D:\MAVEN\MavenRepository\commons-beanutils\commons-beanutils\1.9.4\commons-beanutils-1.9.4.jar;D:\MAVEN\MavenRepository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\MAVEN\MavenRepository\commons-collections\commons-collections\3.2.2\commons-collections-3.2.2.jar;D:\MAVEN\MavenRepository\org\apache\commons\commons-lang3\3.12.0\commons-lang3-3.12.0.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-math\4.3.1\tribuo-math-4.3.1.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-clustering-core\4.3.1\tribuo-clustering-core-4.3.1.jar;D:\MAVEN\MavenRepository\org\tribuo\tribuo-util-infotheory\4.3.1\tribuo-util-infotheory-4.3.1.jar;D:\MAVEN\MavenRepository\com\oracle\labs\olcut\olcut-core\5.2.1\olcut-core-5.2.1.jar;D:\MAVEN\MavenRepository\org\jline\jline-terminal\3.21.0\jline-terminal-3.21.0.jar;D:\MAVEN\MavenRepository\org\jline\jline-reader\3.21.0\jline-reader-3.21.0.jar;D:\MAVEN\MavenRepository\org\jline\jline-builtins\3.21.0\jline-builtins-3.21.0.jar;D:\MAVEN\MavenRepository\org\jline\jline-style\3.21.0\jline-style-3.21.0.jar;D:\MAVEN\MavenRepository\org\jline\jline-terminal-jansi\3.21.0\jline-terminal-jansi-3.21.0.jar;D:\MAVEN\MavenRepository\org\fusesource\jansi\jansi\2.4.0\jansi-2.4.0.jar;D:\MAVEN\MavenRepository\cn\xiaoandcai\JsonDiff\1.2.1\JsonDiff-1.2.1.jar;D:\MAVEN\MavenRepository\com\alibaba\fastjson2\fastjson2\2.0.7\fastjson2-2.0.7.jar;D:\MAVEN\MavenRepository\org\apache\poi\poi\5.2.3\poi-5.2.3.jar;D:\MAVEN\MavenRepository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;D:\MAVEN\MavenRepository\org\apache\commons\commons-collections4\4.4\commons-collections4-4.4.jar;D:\MAVEN\MavenRepository\commons-io\commons-io\2.11.0\commons-io-2.11.0.jar;D:\MAVEN\MavenRepository\com\zaxxer\SparseBitSet\1.2\SparseBitSet-1.2.jar;D:\MAVEN\MavenRepository\org\apache\logging\log4j\log4j-api\2.17.2\log4j-api-2.17.2.jar;D:\MAVEN\MavenRepository\org\apache\poi\poi-ooxml\5.2.3\poi-ooxml-5.2.3.jar;D:\MAVEN\MavenRepository\org\apache\poi\poi-ooxml-lite\5.2.3\poi-ooxml-lite-5.2.3.jar;D:\MAVEN\MavenRepository\org\apache\xmlbeans\xmlbeans\5.1.1\xmlbeans-5.1.1.jar;D:\MAVEN\MavenRepository\xml-apis\xml-apis\1.4.01\xml-apis-1.4.01.jar;D:\MAVEN\MavenRepository\org\apache\commons\commons-compress\1.21\commons-compress-1.21.jar;D:\MAVEN\MavenRepository\com\github\virtuald\curvesapi\1.07\curvesapi-1.07.jar;D:\MAVEN\MavenRepository\com\alibaba\easyexcel\3.3.2\easyexcel-3.3.2.jar;D:\MAVEN\MavenRepository\com\alibaba\easyexcel-core\3.3.2\easyexcel-core-3.3.2.jar;D:\MAVEN\MavenRepository\com\alibaba\easyexcel-support\3.3.2\easyexcel-support-3.3.2.jar;D:\MAVEN\MavenRepository\org\apache\poi\poi-ooxml-schemas\4.1.2\poi-ooxml-schemas-4.1.2.jar;D:\MAVEN\MavenRepository\org\apache\commons\commons-csv\1.8\commons-csv-1.8.jar;D:\MAVEN\MavenRepository\org\ehcache\ehcache\3.9.9\ehcache-3.9.9.jar;D:\MAVEN\MavenRepository\org\apache\httpcomponents\client5\httpclient5\5.2.1\httpclient5-5.2.1.jar;D:\MAVEN\MavenRepository\org\apache\httpcomponents\core5\httpcore5\5.1.3\httpcore5-5.1.3.jar;D:\MAVEN\MavenRepository\org\apache\httpcomponents\core5\httpcore5-h2\5.1.3\httpcore5-h2-5.1.3.jar;D:\MAVEN\MavenRepository\cn\hutool\hutool-http\5.8.22\hutool-http-5.8.22.jar;D:\MAVEN\MavenRepository\cn\hutool\hutool-core\5.8.22\hutool-core-5.8.22.jar;D:\MAVEN\MavenRepository\cn\hutool\hutool-all\5.8.22\hutool-all-5.8.22.jar;D:\MAVEN\MavenRepository\redis\clients\jedis\5.0.0\jedis-5.0.0.jar;D:\MAVEN\MavenRepository\org\apache\commons\commons-pool2\2.11.1\commons-pool2-2.11.1.jar;D:\MAVEN\MavenRepository\org\json\json\20230618\json-20230618.jar;D:\MAVEN\MavenRepository\com\google\code\gson\gson\2.8.9\gson-2.8.9.jar;D:\MAVEN\MavenRepository\com\alibaba\fastjson\1.2.67\fastjson-1.2.67.jar;D:\MAVEN\MavenRepository\org\projectlombok\lombok\1.18.24\lombok-1.18.24.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-starter-netflix-hystrix\2.2.10.RELEASE\spring-cloud-starter-netflix-hystrix-2.2.10.RELEASE.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-starter\2.2.9.RELEASE\spring-cloud-starter-2.2.9.RELEASE.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-context\2.2.9.RELEASE\spring-cloud-context-2.2.9.RELEASE.jar;D:\MAVEN\MavenRepository\org\springframework\security\spring-security-crypto\5.6.3\spring-security-crypto-5.6.3.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-commons\2.2.9.RELEASE\spring-cloud-commons-2.2.9.RELEASE.jar;D:\MAVEN\MavenRepository\org\springframework\security\spring-security-rsa\1.0.9.RELEASE\spring-security-rsa-1.0.9.RELEASE.jar;D:\MAVEN\MavenRepository\org\bouncycastle\bcpkix-jdk15on\1.64\bcpkix-jdk15on-1.64.jar;D:\MAVEN\MavenRepository\org\bouncycastle\bcprov-jdk15on\1.64\bcprov-jdk15on-1.64.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-netflix-hystrix\2.2.10.RELEASE\spring-cloud-netflix-hystrix-2.2.10.RELEASE.jar;D:\MAVEN\MavenRepository\org\springframework\boot\spring-boot-starter-aop\2.6.7\spring-boot-starter-aop-2.6.7.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-netflix-ribbon\2.2.10.RELEASE\spring-cloud-netflix-ribbon-2.2.10.RELEASE.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-netflix-archaius\2.2.10.RELEASE\spring-cloud-netflix-archaius-2.2.10.RELEASE.jar;D:\MAVEN\MavenRepository\org\springframework\cloud\spring-cloud-starter-netflix-archaius\2.2.10.RELEASE\spring-cloud-starter-netflix-archaius-2.2.10.RELEASE.jar;D:\MAVEN\MavenRepository\com\netflix\archaius\archaius-core\0.7.7\archaius-core-0.7.7.jar;D:\MAVEN\MavenRepository\com\google\code\findbugs\jsr305\3.0.1\jsr305-3.0.1.jar;D:\MAVEN\MavenRepository\commons-configuration\commons-configuration\1.8\commons-configuration-1.8.jar;D:\MAVEN\MavenRepository\commons-lang\commons-lang\2.6\commons-lang-2.6.jar;D:\MAVEN\MavenRepository\com\netflix\hystrix\hystrix-core\1.5.18\hystrix-core-1.5.18.jar;D:\MAVEN\MavenRepository\com\netflix\hystrix\hystrix-serialization\1.5.18\hystrix-serialization-1.5.18.jar;D:\MAVEN\MavenRepository\com\fasterxml\jackson\module\jackson-module-afterburner\2.13.2\jackson-module-afterburner-2.13.2.jar;D:\MAVEN\MavenRepository\com\fasterxml\jackson\core\jackson-core\2.13.2\jackson-core-2.13.2.jar;D:\MAVEN\MavenRepository\com\fasterxml\jackson\core\jackson-annotations\2.13.2\jackson-annotations-2.13.2.jar;D:\MAVEN\MavenRepository\com\netflix\hystrix\hystrix-metrics-event-stream\1.5.18\hystrix-metrics-event-stream-1.5.18.jar;D:\MAVEN\MavenRepository\com\netflix\hystrix\hystrix-javanica\1.5.18\hystrix-javanica-1.5.18.jar;D:\MAVEN\MavenRepository\org\ow2\asm\asm\5.0.4\asm-5.0.4.jar;D:\MAVEN\MavenRepository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar;D:\MAVEN\MavenRepository\io\reactivex\rxjava-reactive-streams\1.2.1\rxjava-reactive-streams-1.2.1.jar;D:\MAVEN\MavenRepository\io\reactivex\rxjava\1.3.8\rxjava-1.3.8.jar;D:\MAVEN\MavenRepository\org\reactivestreams\reactive-streams\1.0.3\reactive-streams-1.0.3.jar;D:\MAVEN\MavenRepository\org\hdrhistogram\HdrHistogram\2.1.12\HdrHistogram-2.1.12.jar;D:\idea\IntelliJ IDEA 2021.3\lib\idea_rt.jar" com.example.springbootdame.PersonalAssistantApplication
Connected to the target VM, address: '127.0.0.1:55698', transport: 'socket'
2024-01-16 15:44:01.575 [al-service] [main] WARN  [org.springframework.context.annotation.AnnotationConfigApplicationContext] -Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configurationPropertiesBeans' defined in class path resource [org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
2024-01-16 15:44:01.581 [al-service] [main] INFO  [org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener] -
 
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2024-01-16 15:44:01.603 [al-service] [main] ERROR [org.springframework.boot.SpringApplication] -Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configurationPropertiesBeans' defined in class path resource [org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:270)
    at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:762)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:567)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
    at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:164)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:212)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:117)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:74)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
    at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:85)
    at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114)
    at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65)
    at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:339)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:297)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)
    at com.example.springbootdame.PersonalAssistantApplication.main(PersonalAssistantApplication.java:12)
Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:485)
    at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.buildLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:232)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.findLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:210)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(InitDestroyAnnotationBeanPostProcessor.java:149)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(CommonAnnotationBeanPostProcessor.java:305)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors(AbstractAutowireCapableBeanFactory.java:6666666)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
    ... 30 common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
    at java.lang.Class.getDeclaredMethods(Class.java:1975)
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:467)
    ... 37 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 41 common frames omitted
2024-01-16 15:44:01.606 [al-service] [main] ERROR [org.springframework.boot.SpringApplication] -Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configurationPropertiesBeans' defined in class path resource [org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:270)
    at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:762)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:567)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
    at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:164)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:212)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:117)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:74)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
    at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:85)
    at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114)
    at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65)
    at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:339)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:297)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)
    at com.example.springbootdame.PersonalAssistantApplication.main(PersonalAssistantApplication.java:12)
Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.springframework.cloud.context.properties.ConfigurationPropertiesBeans] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:485)
    at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.buildLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:232)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.findLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:210)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(InitDestroyAnnotationBeanPostProcessor.java:149)
    at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(CommonAnnotationBeanPostProcessor.java:305)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors(AbstractAutowireCapableBeanFactory.java:6666666)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
    ... 30 common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
    at java.lang.Class.getDeclaredMethods(Class.java:1975)
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:467)
    ... 37 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 41 common frames omitted
Disconnected from the target VM, address: '127.0.0.1:55698', transport: 'socket'
 
Process finished with exit code 1

报错原因是缺失特定路径下的类org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata,如下所示:

需要解决此问题,自定义此类即可,注意一定要定义成框架所需的包路径(package org.springframework.boot.context.properties;):

ConfigurationBeanFactoryMetadata.java

 package org.springframework.boot.context.properties;
 
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
 
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
 
/**
 * @Author: mingming.chen
 * @Date: 2024/1/15 14 26
 * @Description:
 **/
public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcessor {
 
    private ConfigurableListableBeanFactory beanFactory;
 
    private Map<String, MetaData> beans = new HashMap<String, MetaData>();
 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        this.beanFactory = beanFactory;
        for (String name : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition definition = beanFactory.getBeanDefinition(name);
            String method = definition.getFactoryMethodName();
            String bean = definition.getFactoryBeanName();
            if (method != null && bean != null) {
                this.beans.put(name, new MetaData(bean, method));
            }
        }
    }
 
    public <A extends Annotation> Map<String, Object> getBeansWithFactoryAnnotation(
            Class<A> type) {
        Map<String, Object> result = new HashMap<String, Object>();
        for (String name : this.beans.keySet()) {
            if (findFactoryAnnotation(name, type) != null) {
                result.put(name, this.beanFactory.getBean(name));
            }
        }
        return result;
    }
 
    public <A extends Annotation> A findFactoryAnnotation(String beanName,
                                                          Class<A> type) {
        Method method = findFactoryMethod(beanName);
        return (method == null ? null : AnnotationUtils.findAnnotation(method, type));
    }
 
    private Method findFactoryMethod(String beanName) {
        if (!this.beans.containsKey(beanName)) {
            return null;
        }
        final AtomicReference<Method> found = new AtomicReference<Method>(null);
        MetaData meta = this.beans.get(beanName);
        final String factory = meta.getMethod();
        Class<?> type = this.beanFactory.getType(meta.getBean());
        ReflectionUtils.doWithMethods(type, new ReflectionUtils.MethodCallback() {
            @Override
            public void doWith(Method method)
                    throws IllegalArgumentException, IllegalAccessException {
                if (method.getName().equals(factory)) {
                    found.compareAndSet(null, method);
                }
            }
        });
        return found.get();
    }
 
    private static class MetaData {
 
        private String bean;
 
        private String method;
 
        MetaData(String bean, String method) {
            this.bean = bean;
            this.method = method;
        }
 
        public String getBean() {
            return this.bean;
        }
 
        public String getMethod() {
            return this.method;
        }
    }
}

四、版本匹配安全检查问题

此时,同样启动项目,报以下错误,并中断进程:

如上所示,由于spring-cloud-starter-netflix-hystrix 2021年后不更新,此依赖包对应的springCloud版本和springboot版本不匹配,会报版本不匹配异常,需要禁用安全检查功能,如下配置在application.yml文件中:

spring:
cloud:
compatibility-verifier:
enabled: false

检查配置说明:

兼容性验证器是Spring Cloud框架中的一个功能,用于确保所使用的Spring Boot版本与Spring Cloud版本兼容。当Spring Boot版本与Spring Cloud版本不兼容时,可能会导致应用程序在运行时出现问题。

在上述代码中,
cloud.compatibility-verifier.enabled
属性被设置为
false
,意味着禁用了兼容性验证器。这意味着应用程序将不再执行Spring Boot与Spring Cloud版本的兼容性检查。

禁用兼容性验证器可能会导致以下问题:

  1. 不兼容的Spring Boot和Spring Cloud版本可能导致应用程序在运行时出现错误或异常。
  2. 可能会错过一些Spring Cloud提供的新功能或修复的问题。

因此,在禁用兼容性验证器之前,应该确保所使用的Spring Boot版本与Spring Cloud版本兼容,并且了解可能的风险和影响。

五、测试验证

  1. 熔断;
  2. 降级;
  3. 线程池隔离:并发数超过设置线程池个数请求进行降级;

六、结论

完成以上前四步即可在springboot2.6.x和2.7.x使用hystrix组件。

模型评估
在统计学和机器学习中具有至关重要,它帮助我们主要目标是量化模型预测新数据的能力。

在这个数据充斥的时代,没有评估的模型就如同盲人摸象,可能带来误导和误判。
模型评估
不仅是一种方法,更是一种保障,确保我们在数据海洋中航行时,能够依赖准确的模型,做出明智的决策。

本篇主要介绍
模型评估
时,如何利用
scikit-learn
帮助我们快速进行各种
误差
的分析。

1. 平均绝对误差

平均绝对误差
(Mean Absolute Error,简称
MAE
),它用于度量预测值与真实值之间的
平均误差大小

它能直观地反映出预测的准确性,MAE
越小
,说明模型的预测能力
越好

1.1. 计算公式

平均绝对误差
的计算公式如下:
\(\text{MAE}(y, \hat{y}) = \frac{1}{n} \sum_{i=0}^{n-1} \left| y_i - \hat{y}_i \right|.\)
其中,
\(n\)
是样本数量,
\(y_i\)
是真实值,
\(\hat{y_i}\)
是预测值。

1.2. 使用示例

from sklearn.metrics import mean_absolute_error
import numpy as np

# 随机生成100个sample
n = 100
y_true = np.random.randint(1, 100, n)
y_pred = np.random.randint(1, 100, n)

mean_absolute_error(y_true, y_pred)

mean_absolute_error
就是
scikit-learn
中用来计算
MAE
的函数。

2. 均方误差

均方误差

Mean Squared Error
,简称
MSE
),它用于衡量模型的预测值与实际观测值之间的差异。
MSE
越小,表示模型的预测值与实际观测值之间的差异较小,即模型具有较高的预测精度。

2.1. 计算公式

\(\text{MSE}(y, \hat{y}) = \frac{1}{n} \sum_{i=0}^{n - 1} (y_i - \hat{y}_i)^2.\)
其中,
\(n\)
是样本数量,
\(y_i\)
是真实值,
\(\hat{y_i}\)
是预测值。

2.2. 使用示例

from sklearn.metrics import mean_squared_error
import numpy as np

n = 100
y_true = np.random.randint(1, 100, n)
y_pred = np.random.randint(1, 100, n)

mean_squared_error(y_true, y_pred)

mean_squared_error
就是
scikit-learn
中用来计算
MSE
的函数。

3. 均方对数误差

均方对数误差

Mean Squared Log Error
,简称
MSLE
),与
均方误差

MSE
)相比,
MSLE
在计算误差时先对预测值和真实值取对数。
通过对数转换,
MSLE
能够
减小
较大值和较小值之间的
差异
,使得误差度量更为稳定。
MSLE
的值越小,表示预测结果与真实值的差异越小,即模型的拟合程度越好。

3.1. 计算公式

\(\text{MSLE}(y, \hat{y}) = \frac{1}{n} \sum_{i=0}^{n - 1} (\log_e (1 + y_i) - \log_e (1 + \hat{y}_i) )^2.\)
其中,
\(n\)
是样本数量,
\(y_i\)
是真实值,
\(\hat{y_i}\)
是预测值。

3.2. 使用示例

from sklearn.metrics import mean_squared_log_error
import numpy as np

n = 100
y_true = np.random.randint(1, 100, n)
y_pred = np.random.randint(1, 100, n)

mean_squared_log_error(y_true, y_pred)

mean_squared_log_error
就是
scikit-learn
中用来计算
MSLE
的函数。

4. 平均绝对百分比误差

平均绝对百分比误差

Mean Absolute Percentage Error
,简称
MAPE
),
平均绝对误差

MAE
)相比,
MAPE
将误差转化为百分比形式,这使得它在不同尺度的数据上具有更好的可比性。
MAPE
越小,表示模型的预测结果与实际结果的差异较小,即模型的预测准确性较高。

4.1. 计算公式

\(\text{MAPE}(y, \hat{y}) = \frac{1}{n} \sum_{i=0}^{n-1} \frac{{}\left| y_i - \hat{y}_i \right|}{\max(\epsilon, \left| y_i \right|)}\)
其中,
\(n\)
是样本数量,
\(y_i\)
是真实值,
\(\hat{y_i}\)
是预测值。
\(\epsilon\)
是一个任意小但严格为正的数,以避免在
\(y_i\)
为零时出现未定义的结果。

4.2. 使用示例

from sklearn.metrics import mean_absolute_percentage_error
import numpy as np

n = 100
y_true = np.random.randint(1, 100, n)
y_pred = np.random.randint(1, 100, n)

mean_absolute_percentage_error(y_true, y_pred)

mean_absolute_percentage_error
就是
scikit-learn
中用来计算
MAPE
的函数。

5. 绝对误差中值

绝对误差中值

Median Absolute Error
,简称
MedAE
),它用于衡量预测模型对于数据集的精度。

平均误差
相比,中值对异常值更为稳健,对于数据集中的异常值和离群点,
绝对误差中值
具有较强的抗性。
MedAE
越小的模型,通常意味着它在大多数数据点上的预测更为准确。

5.1. 计算公式

\(\text{MedAE}(y, \hat{y}) = \text{median}(\mid y_1 - \hat{y}_1 \mid, \ldots, \mid y_n - \hat{y}_n \mid).\)
其中,
\(y_i\)
是真实值,
\(\hat{y_i}\)
是预测值,
\(median\)
表示取中位数。

5.2. 使用示例

from sklearn.metrics import median_absolute_error
import numpy as np

n = 100
y_true = np.random.randint(1, 100, n)
y_pred = np.random.randint(1, 100, n)

median_absolute_error(y_true, y_pred)

median_absolute_error
就是
scikit-learn
中用来计算
MedAE
的函数。

6. 最大误差

最大误差

Max Error
),它用于衡量模型预测值与真实值之间的最大差异,揭示模型在最坏情况下的表现。
如果模型在大多数情况下的预测误差都很小,但
最大误差
很大,那么这可能意味着模型对于某些特定情况的处理不够好,需要进一步优化。

6.1. 计算公式

\(\text{Max Error}(y, \hat{y}) = \max(| y_i - \hat{y}_i |)\)
其中,
\(y_i\)
是真实值,
\(\hat{y_i}\)
是预测值,
\(max\)
表示取最大值。

6.2. 使用示例

from sklearn.metrics import max_error
import numpy as np

n = 100
y_true = np.random.randint(1, 100, n)
y_pred = np.random.randint(1, 100, n)

max_error(y_true, y_pred)

max_error
就是
scikit-learn
中用来计算
Max Error
的函数。

7. 总结

本篇主要介绍了
6种
常用的误差分析函数,包括:

  • 平均绝对误差
  • 均方误差
  • 均方对数误差
  • 平均绝对百分比误差
  • 绝对误差中值
  • 最大误差

误差的计算方式其实也不算不复杂,不过,掌握
scikit-learn
中封装好的各种误差计算函数,
还是可以帮助我们在评估回归模型时节约不少时间。