需求来源

之前的文章elasticsearch&kibana从6.0升级到7.9完整过程记录已经已经介绍了该需求的来源,也已经将elasticsearch的server端从6.0.0升级到了7.9.0,server端的问题解决了,接下来就是client端的问题了,怎么解决下面的问题:

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

方案目标

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

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

方案详情

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

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

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

正面刚方案:

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

侧面迂回方案:

    既然强攻不成,那就迂回包抄,通过某些特殊的手段,包含但不限于使用low来实现部分high无法兼容的代码,调整使用方式避开跨版本不兼容的用法,甚至修改部分源码以达到一套es-client代码适配所有业务代码以及所有版本ES的目的,当然这么做的风险是仅仅能支持6.X和7.9.0版本,对后续版本的兼容性未知,但是当前版本可以作为过渡版本,当后续ES版本更迭的时候可以淘汰掉6.X版本而专心考虑高版本(6.X和7.X的差异实在太大,兼容性搞起来有很多坑需要填....)

  1. 直接维护一套es-client的api包,在该包中封装6.X和7.9.0相关的对象以屏蔽版本的差异,另外尽量使用以前java client相关的方法体,里面的实现替换成rest-client的实现,以最大程度减少业务代码的修改。
  2. 在填坑过程中最后还是没有绕过修改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的版本,然后根据版本来判断该属性该如何设置

   

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

   

   

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

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

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

后记

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

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐