一文说清Elasticsearch java restful api 跨大版本兼容解决方案

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

需求来源

之前的文章 Elasticsearch&Kibana从6.0升级到7.9的踩坑全过程 已经已经介绍了该需求的来源,也已经将elasticsearch的server端从6.0.0升级到了7.9.0,server端的问题解决了,接下来就是client端的问题了。

虽然说“不作死就不会死”,但是也不能“抱残守缺,因循守旧”,还是要“拥抱变化,跟上脚步”,大不了自己挖的坑,跪着填上呗。不怕遇到问题,就怕失去解决问题的信心。扯远了,自我鼓励的口号喊完了,剩下就看怎么填坑了。

经过分析,怎么解决下面的问题是首要考虑的问题:

  • 之前6.0.0版本使用的是elasticsearch的JAVA api,这个版本的API在7.X版本已经变成deprecated状态,并会在8.0版本被移除,所以需要将JAVA api转化为JAVA restful api
  • 由于公司的产品除了官服外都是在客户现场的私有化部署,由于elasticsearch的应用比较广泛加上客户为了方便管理,所以很多情况下elasticsearch集群都是直接使用客户提供或者构建的固有集群,集群的版本不固定,包含但不限于5.4.0,6.X以及未来将要升级到的7.9.0,繁杂的版本使得client端的代码兼容变成了一件无比繁杂且重要的一件事情
  • client端的支撑要考虑到代码变更的复杂性以及工作量,尽量使用最少的工作量达到目的

正文

方案目标

1、one stack to rule them all(一套代码解决所有问题)

2、尽量减少适配和代码维护工作量

方案详情

前后一共考虑了两版方案:

  • 第一版方案就是正面刚,彻底解决问题,但是适配和维护工作工作量过大,被迫放弃。
  • 第二版方案是一个这种方案,虽然可能在未来再次升级后会出现问题,但是能做到平稳过度,最大化减少代码升级以及维护的工作量。

下面就分别说一说这两套方案:

首先先简单科普下elasticsearch的java restful client:

  • restful client分为high level restful api(下面简称high)和low level restful api(下面简称low)
  • 其中high是通过low实现的,而low是直接执行底层命令来获得返回结果,其中底层命令就是在kibana中执行的命令,返回值也完全一样,需要自己去手动解析
  • 鉴于2中介绍的两者特点,理论上来说low是可以基本上做到版本完全兼容的,而high则和版本相关
  • high出现的原因,更多的是为使用java api的项目快速适配以及方便后续使用而封装的,所以如果完全使用high的话,和java api的兼容性会很好,修改适配的工作量相对较少
  • low一直存在,high一直在发展,6.0.0版本的high只有document相关的操作,而7.9.0则所有的操作都有high相关的实现

正面刚方案

  • 针对6.X和7.9.0版本,各维护一套es-client的api包,封装同样的接口供业务模块使用,这样的结果是维护一套业务代码,两套es-client的代码
  • 这样设计的好处是业务代码解耦合,维护一套即可,屏蔽了es版本的差异;两套es-client的代码分别维护,可以最大程度的减少版本不兼容带来的各种问题,提升稳定性
  • 但是凡事都有两面性,这样做的缺点就是服务端需要维护的额代码变多,es-client的工作量加倍
  • 最重要的是,公司的业务特点就是私有化部署的客户遍及全中国,运维团队需要维护的内容多到离谱,再给他们增加es-client的适配无疑是一个很大的压力,而且繁杂度的增加带来的是犯错误的几率提升,对整个流程来说并不友好(公司团队决定的,运维人员的资源并不多

侧面迂回方案

既然强攻不成,那就迂回包抄,通过某些特殊的手段,包含但不限于使用low来实现部分high无法兼容的代码,调整使用方式避开跨版本不兼容的用法,甚至修改部分源码以达到一套es-client代码适配所有业务代码以及所有版本ES的目的。

当然这么做的风险是仅仅能支持6.X和7.9.0版本,对后续版本的兼容性未知,但是当前版本可以作为过渡版本,当后续ES版本更迭的时候可以淘汰掉6.X版本而专心考虑高版本(6.X和7.X的差异实在太大,兼容性搞起来有很多坑需要填....)

于是有了下面的方案:

  • 直接维护一套es-client的api包,在该包中封装6.X和7.9.0相关的对象以屏蔽版本的差异,另外尽量使用以前java client相关的方法体,里面的实现替换成rest-client的实现,以最大程度减少业务代码的修改。
  • 在填坑过程中最后还是没有绕过修改elasticsearch源码,针对SearchRequest进行了一些封装,来兼容各种版本的search属性

下面说说具体的方案实施过程:

版本差异收集

  1. seq_no_primary_term:6.7版本增加的默认search属性
  2. ignore_throttled:6.8.1版本增加的默认search属性
  3. ccsMinimizeRoundtrips:7.0.0版本增加的默认search属性
  4. SearchHits类的totalHits属性在6.X版本是long类型,在7.X版本是TotalHits对象属性,导致使用6.X版本client访问7.X版本的server时得到的totalHits属性为0,该问题在6.8的client中做了适配

推进过程

  1. 最开始的时候想使用7.9.0的high来解决所有问题,后来发现在es6.0.0版本上2,3两个属性导致所有的search返回为null,报的错误就是search不支持ignore_throttled和ccsMinimizeRoundtrips两个属性
  2. 见招拆招,选取6.8.0版本来跳过这两个我们暂时并不需要的属性,修改之后发现绝大多数的search返回结果都正常了,但是TopHitsAggregationBuilder相关的API却返回为null,报的错误是search不支持seq_no_primary_term属性
  3. 将适配的工作进行到底,经过查询发现,seq_no_primary_term是es6.7版本增加的search属性,于是将rest-client版本降到6.6.2,在6.X版本上所有问题都解决了,所有业务系统代码正常运行,还没高兴多久,在7.9.0版本的es测试中发现,search返回的总条数总是0,于是再次网上学习查看源码,发现了上面第4条中出现的问题
  4. 现在问题摆在眼前,版本冲突了,没有一个版本能兼容所有的问题了,于是痛下决心,在6.6.2的版本基础上修改elasticsearch的源码,来兼容第4条带来的问题

下面贴出具体的核心结构与代码:

上面的代码就是es-client的restful实现,EsClient里面封装了es各种基础操作的restful实现,request包中是为了屏蔽es版本差异和兼容原有es的java api实现而做的抽象,都是继承对应的request后而做的封装

至于源码修改,就是在SearchRequest以及相关的requet中增加了totalHits4Object属性来判断,是否需要将topHits对象转为转为Long,下面就是在SearchRequest中增加的属性

然后在EsClient中根据low的api获取es的版本,然后根据版本来判断该属性该如何设置

下面的代码是根据low api获取elasticsearch版本的方法

hasType属性就是es版本小于7.X的标识,即es的版本为7.X时,需要将topHits对象转为转为Long,然后在search参数组织时进行判断,根据不同版本做不同的处理,具体如下:

判断SearchRequest中的totalHits4Object来判断是否需要增加rest_total_hits_as_int属性来处理SearchRequest

最后还需要在查询校验的时候去掉对于type的校验,防止高版本的es如es7及以上版本只会返回报错,具体做法是将IndexRequest中的相关校验注释掉。

至此所有的坑以及问题都得以解决,暂时已经编译通过并正常运行,后续的功能以及性能测试再出现坑再去填吧,反正已经修改了源码,理论上一切问题都可以通过修改源码来解决了

后记

工作了这么久,才第一次尝试修改源码来解决问题,很是惭愧和汗颜....但是话说回来了,第一次迈出了脚步,后续的话希望会一发而不可收拾。另外在这次解决问题和填坑的时候仔细的阅读了很多es的源码,了解了很多es的内部实现细节,也学习了很多的高级和优雅的编码用法,收获甚多。辛苦但充实,困难但开心,这也许就是作为一个技术人孜孜不倦所追求的东西吧。

下一篇文章会分享下java transport api向java rest client api迁移相关的操作,以及结合java rest client api跨版本操作,并附上github上的工程源码,大家敬请期待!

猜你喜欢

转载自juejin.im/post/7109293837608026119
今日推荐