ES客户端8版本与ES服务端7版本的兼容问题

🎬 第一阶段:发现问题(认识症状)

🔴 看到的错误日志:

2025-09-30 14:23:45.123 ERROR - ES全量同步任务执行失败
co.elastic.clients.transport.TransportException: 
Missing [X-Elastic-Product] header. 
Please check that you are connecting to an Elasticsearch instance, 
error trace ElasticsearchException[Missing [X-Elastic-Product] header]

💭 我的第一反应:

思考1: "Missing header" - 缺少HTTP头部
      ↓
思考2: 这个错误很具体,是ES客户端在检查某个头部
      ↓
思考3: 先去Google搜一下这个错误

🔎 第二阶段:信息收集(10分钟)

搜索关键词:elasticsearch Missing X-Elastic-Product header

找到几个关键信息:

Stack Overflow:
"This error occurs when using Elasticsearch 8.x client 
 with Elasticsearch 7.x server"

GitHub Issue:
"ES 8.x client requires X-Elastic-Product header in response
 but ES 7.x server doesn't send it"

官方文档:
"Starting from 8.0, the client validates the server 
 by checking the X-Elastic-Product header"

💡 初步结论:

问题根源:
├─ ES 8.x 客户端有新的安全检查
├─ 要求服务器响应必须包含 X-Elastic-Product 头部
└─ ES 7.x 服务器不返回这个头部

解决方向:
1. 升级服务器到8.x(成本高)
2. 降级客户端到7.x(依赖地狱)
3. 让客户端跳过检查(可能不安全)
4. 在响应中添加这个头部(?如何做到)

🧪 第三阶段:第一次尝试(方向错误)

❌ 尝试1:在请求中添加头部(15分钟)

我的想法:

"既然缺少头部,那我在请求中加上不就行了?"

修改代码:

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("X-Elastic-Product", "Elasticsearch");

结果:

还是报同样的错误!

为什么?
仔细看错误信息:"Missing [X-Elastic-Product] header"
客户端是在检查响应,不是请求!

❌ 方向错了!

💭 反思:

错误分析:
我理解错了问题
- 客户端发送请求 → 服务器
- 服务器返回响应 → 客户端
- 客户端检查响应头部 ← 这里出错了!

所以:
✅ 需要修改的是:服务器的响应
❌ 不是:客户端的请求

🔄 第四阶段:调整思路(关键转折)

💡 新思路:

问题:客户端检查响应头
      ↓
难点:我改不了服务器(ES 7.x本身)
      ↓
想法:能不能在响应到达客户端之前,
      先"拦截"它,然后添加头部?
      ↓
关键词:HTTP 拦截器(Interceptor)

🔍 搜索:apache httpclient response interceptor

找到了:

HttpResponseInterceptor
- 可以拦截HTTP响应
- 在响应到达客户端前处理
- 可以修改响应头部

✅ 这就是我要找的!

🛠️ 第五阶段:第二次尝试(方向正确但实现有问题)

✅ 尝试2:使用响应拦截器(30分钟)

第一版代码:

HttpResponseInterceptor interceptor = new HttpResponseInterceptor() {
    @Override
    public void process(HttpResponse response, HttpContext context) {
        response.addHeader("X-Elastic-Product", "Elasticsearch");
    }
};

// 但是在哪里注册这个拦截器???

🤔 问题:拦截器应该注册在哪里?

查看ES客户端的层级结构:

调用顺序(从上到下):

ElasticsearchClient(高层API)
    ↓
RestClientTransport(传输层)
    ↓
RestClient(HTTP客户端)← 在这里!
    ↓
Apache HttpClient(底层HTTP)← 拦截器在这里工作

查找RestClient的API文档:

RestClient.builder(HttpHost...)
    .setHttpClientConfigCallback(callback -> {
        // 在这里可以配置Apache HttpClient
        // 包括添加拦截器!
    })

💡 找到了注册位置!

📝 第六阶段:编写完整代码(20分钟)

完整实现:

@Bean
@Primary
public RestClient elasticsearchRestClient() {
    // 1. 创建响应拦截器
    HttpResponseInterceptor responseInterceptor = new HttpResponseInterceptor() {
        @Override
        public void process(HttpResponse response, HttpContext context) {
            if (!response.containsHeader("X-Elastic-Product")) {
                response.addHeader("X-Elastic-Product", "Elasticsearch");
                log.debug("✅ 已添加 X-Elastic-Product 头部");
            }
        }
    };
    
    // 2. 创建RestClient,注册拦截器
    RestClientBuilder builder = RestClient.builder(
        new HttpHost(host, port, scheme)
    )
    .setHttpClientConfigCallback(httpClientBuilder -> {
        // 3. 在这里添加拦截器到Apache HttpClient
        httpClientBuilder.addInterceptorLast(responseInterceptor);
        return httpClientBuilder;
    });
    
    return builder.build();
}

🐛 第七阶段:编译和运行(遇到新问题)

❌ 问题3:Bean冲突(10分钟)

编译通过,但运行报错:

BeanCreationException: 
Error creating bean with name 'elasticsearchClient': 
Ambiguous factory method matches found

分析:

原因:
我的配置类继承了 ElasticsearchConfiguration
    ↓
ElasticsearchConfiguration 已经提供了 elasticsearchClient bean
    ↓
我又自己定义了一个 elasticsearchClient bean
    ↓
Spring 不知道用哪个!

解决:

// 移除继承
// public class ElasticsearchClientConfig extends ElasticsearchConfiguration {

// 改为
public class ElasticsearchClientConfig {
    // 手动定义所有需要的Bean
}

🔄 第八阶段:补全所有Bean(30分钟)

问题:移除继承后,缺少其他Bean

NoSuchBeanDefinitionException: 
No qualifying bean of type 
'org.springframework.data.elasticsearch.core.ElasticsearchOperations'

💭 分析Bean依赖关系:

需要的Bean链条:

ElasticsearchOperations(Spring Data的核心接口)
    ↓ 需要
ElasticsearchTemplate(实现类)
    ↓ 需要
ElasticsearchClient(ES客户端)
    ↓ 需要
RestClient(HTTP客户端)← 我已经有了

还需要:
ElasticsearchConverter(数据转换器)

补全所有Bean:

@Bean
@Primary
public RestClient elasticsearchRestClient() {
    // 已有
}

@Bean
@Primary
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
    RestClientTransport transport = new RestClientTransport(
        restClient,
        new JacksonJsonpMapper()
    );
    return new ElasticsearchClient(transport);
}

@Bean
@Primary
public ElasticsearchConverter elasticsearchConverter() {
    return new MappingElasticsearchConverter(
        new SimpleElasticsearchMappingContext()
    );
}

@Bean
@Primary
public ElasticsearchOperations elasticsearchOperations(
        ElasticsearchClient elasticsearchClient,
        ElasticsearchConverter elasticsearchConverter) {
    return new ElasticsearchTemplate(
        elasticsearchClient,
        elasticsearchConverter
    );
}

🚀 第九阶段:首次成功运行(但还有其他问题)

✅ 响应拦截器工作了!

日志显示:

✅ 响应拦截器已添加头部: X-Elastic-Product=Elasticsearch
✅ Elasticsearch客户端验证通过

❌ 但出现了新错误:

java.lang.reflect.InaccessibleObjectException: 
Unable to make field private final java.math.BigInteger 
java.math.BigDecimal.intVal accessible: 
module java.base does not "opens java.math" to unnamed module

🔧 第十阶段:解决BigDecimal问题(20分钟)

💭 分析新错误:

错误原因:
Java 17的模块系统限制了反射访问
    ↓
Spring Data Elasticsearch在序列化时
需要反射访问BigDecimal的内部字段
    ↓
但Java 17不允许这样做

尝试的解决方案:

方案A:添加JVM参数(不推荐)

--add-opens java.base/java.math=ALL-UNNAMED

✅ 可以工作,但是治标不治本

方案B:改用Double(推荐)

// 修改实体类
@Field(type = FieldType.Double)
private Double standardPrice;  // 从 BigDecimal 改为 Double

// 修改同步逻辑
esProduct.setStandardPrice(
    product.getStandardPrice() != null ? 
    product.getStandardPrice().doubleValue() : null
);

✅ 彻底解决,不需要JVM参数

我选择了方案B

🧹 第十一阶段:清理旧代码(10分钟)

删除不需要的文件:

❌ ElasticsearchRequestOptionsConfig.java
   - 使用旧API(RequestOptions)
   - ES 8.x不再需要
   - 功能已被响应拦截器替代

更新配置文件:

# 禁用兼容性配置(因为已经在主配置中处理)
commerce:
  elasticsearch:
    compatibility:
      enabled: false

✅ 第十二阶段:全面测试和验证(30分钟)

测试清单:

✅ 1. 应用启动测试
   - ElasticsearchClientConfig 正确初始化
   - 所有Bean正确创建
   - 无错误日志

✅ 2. ES连接测试
   - 响应拦截器正常工作
   - X-Elastic-Product头部正确添加
   - 客户端验证通过

✅ 3. 数据同步测试
   - XXL-JOB任务触发
   - 商品数据正确同步
   - 价格字段正确转换(Double)
   - 返回码:200

✅ 4. 搜索功能测试
   - 索引创建成功
   - 数据可以搜索
   - 性能正常
posted @ 2025-10-12 23:29  星星永远发着光  阅读(33)  评论(0)    收藏  举报