Easy-Es框架实践测试整理 基于ElasticSearch的ORM框架
Posted ZWZhangYu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Easy-Es框架实践测试整理 基于ElasticSearch的ORM框架相关的知识,希望对你有一定的参考价值。
文章目录
介绍
Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生。EE是Mybatis-Plus的Es平替版,在有些方面甚至比MP更简单,同时也融入了更多Es独有的功能,助力您快速实现各种场景的开发.
(1)Elasticsearch java 客户端种类
Elasticsearch 官方提供了很多版本的 Java 客户端,包含但不限于:
【1】Transport 客户端
【2】Java REST 客户端
【3】Low Level REST 客户端
【4】High Level REST 客户端
【5】Java API 客户端
非官方的 Java 客户端,包含但不限于:
【1】Jest 客户端
【2】BBoss 客户端
【3】Spring Data Elasticsearch客户端
【4】easy-es客户端
(2)优势和特性分析
【1】全自动索引托管
全球开源首创的索引托管模式,开发者无需关心索引的创建更新及数据迁移等繁琐步骤,索引全生命周期皆可托管给框架,由框架自动完成,过程零停机,用户无感知,彻底解放开发者。该特性可以帮助我们在修改索引名称,索引配置,索引结构后自动更新,并迁移数据,减少运维成本。
【2】屏蔽语言差异。
提供类似Mybatis-Plus使用方式,相对于RestHighLevelClient使用便利不少,相对于Springdata-ElasticSearch的使用也有进一步的改进,更加符合国人的使用习惯。
【3】零魔法值和代码量极少。
这一点主要是针对RestHighLevelClient代码的臃肿问题解决,使用上更加简单。
【4】强大的 CRUD 操作。
内置通用 Mapper,仅仅通过少量配置即可实现大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求。
【5】支持 Lambda 形式调用
通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错段。
【6】内置分页插件。
基于RestHighLevelClient 物理分页,开发者无需关心具体操作,且无需额外配置插件,写分页等同于普通 List 查询,且保持和PageHelper插件同样的分页返回字段,无需担心命名影响。
【7】支持ES高阶语法
支持高亮搜索,分词查询,权重查询,Geo地理位置查询,IP查询,聚合查询等高阶语法。
【8】良好的拓展性,支持混合使用。
底层仍使用RestHighLevelClient,可保持其拓展性,开发者在使用EE的同时,仍可使用RestHighLevelClient的功能。
(3)性能、安全、拓展、社区
Easy-Es对于性能和安全问题专门做了文档描述,链接如下所示:
https://www.easy-es.cn/pages/6e2197
根据文档描述整体的性能还是很好的,安全上已接入OSCS墨菲安全扫描无安全风险。单元测试用例综合覆盖率超95%,已上线的所有功能均有测试用例覆盖,且经过生产环境和开源社区大量用户使用验证。
EE底层用的就是Es官方提供的RestHighLevelClient,我们只是对RestHighLevelClient做了增强,并没有改变减少或是削弱它原有的功能。我们在项目中可以使用EE框架也可以根据需要直接使用RestHighLevelClient,是支持混合使用的。
目前该开源框架已经加入dromara开源社区,社区目前活跃,每年会发很多个版本,不断提升用户体验。
gitee仓库情况:
github仓库情况:
附上同类型的产品spring-data-elasticsearch仓库情况:
(2)ES版本及SpringBoot版本说明
Easy-Es底层用了ES官方的RestHighLevelClient,所以对ES版本有要求,要求ES和RestHighLevelClient JAR依赖版本必须为7.14.0,至于es客户端,实际 测下来7.X任意版本都可以很好的兼容.
值得注意的是,由于Springdata-ElasticSearch的存在,Springboot它内置了和ES及RestHighLevelClient依赖版本,这导致了不同版本的Springboot实际引入的ES及RestHighLevelClient 版本不同,而ES官方的这两个依赖在不同版本间的兼容性非常差,进一步导致很多用户无法正常使用Easy-Es,这实际上这是一个依赖冲突的问题. Easy-Es在项目启动时做了依赖校验,如果项目在启动时可以在控制台看到打印出级别为Error且内容为"Easy-Es supported elasticsearch and restHighLevelClient jar version is:7.14.0 ,Please resolve the dependency conflict!" 的日志时,则说明有依赖冲突待您解决. 解决方案其实很简单,可以像下面一样配置maven的exclude移除Springboot或Easy-Es已经声明的ES及RestHighLevelClient依赖,然后重新引入,引入时指定版本号为7.14.0即可解决.
<dependency>
<groupId>cn.easy-es</groupId>
<artifactId>easy-es-boot-starter</artifactId>
<version>1.1.0</version>
<exclusions>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.14.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.14.0</version>
</dependency>
也可以简单粗暴的把springboot版本调整到2.5.5,其它都不需要调整,也可以勉强正常使用.
索引处理
(一)索引别名策略
为了更好的理解和使用索引的更新原理,最好先了解下ES索引别名的机制。
索引别名是指给一个或者多个索引定义另外一个名称,使索引别名和索引之间可以建立某种逻辑关系。
可以用别名表示别名和索引之间的包含关系,假设我们当前有多个日期日志索引记录,比如log_index_01,log_index_02,log_index_03…,那么我们为了统一检索可以设置一个别名为log_index,然后请求索引为log_index,这样就可以通过log_index查询多个索引的数据,而不用一个一个的指定查询了。
需要指出的是,在默认情况下,当一个别名只指向一个索引时,写入数据的请求可以指向这个别名,如果这个别名指向多个索引,则写入数据的请求是不可以指向这个别名的。
引入别名之后,还可以用别名表示索引之间的替代关系。这种关系一般是在某个索引被创建后,有些参数是不能更改的(如主分片的个数),但随着业务发展,索引中的数据增多,需要更改索引参数进行优化。我们需要平滑地解决该问题,既要更改索引的设置,又不能改变索引名称,这时就可以使用索引别名。
假设一个酒店的搜索别名设置为hotel,初期创建索引hotel_1时,主分片个数设置为5,然后设置hotel_1的别名为hotel。此时客户端使用索引别名hotel进行搜索请求,该请求会转发到索引hotel_1中。假设此时酒店索引中的新增数据急剧增长,索引分片需要扩展,需要将其扩展成为10个分片的索引。但是一个索引在创建后,主分片个数已经不能更改,因此只能考虑使用索引替换来完成索引的扩展。这时可以创建一个索引hotel_2,除了将其主分片个数设置为10外,其他设置与hotel_1相同。当hotel_2的索引数据准备好后,删除hotel_1的别名hotel,同时,置hotel_2的别名为hotel。此时客户端不用进行任何改动,继续使用hotel进行搜索请求时,该请求会转发给索引hotel_2。如果服务稳定,最后将hotel_1删除即可。此时借助别名就完成了一次索引替换工作。如下图所示,在左图中,hotel索引别名暂时指向hotel_1,hotel_2做好了数据准备;在右图中,hotel索引别名指向hotel_2,完成了索引的扩展切换。
参考《Elasticsearch搜索引擎构建入门与实战》
索引别名在这种需要变更索引的情况下,搜索端不需要任何变更即可完成切换,这在实际的生产环境中是非常方便的。
(二)easy-es索引的自动托管之平滑模式实践
(1)介绍
提示:如果在使用过程中发现索引未得到更新,建议对照文章“ES版本及SpringBoot版本”部分检查下版本。
自动托管之平滑模式(自动挡-雪地模式) 默认开启此模式。
在此模式下,索引的创建更新数据迁移等全生命周期用户均不需要任何操作即可完成,过程零停机,用户无感知,可实现在生产环境的平滑过渡,类似汽车的自动档-雪地模式,平稳舒适,彻底解放用户! 需要值得特别注意的是,在自动托管模式下,系统会自动生成一条名为ee-distribute-lock的索引,该索引为框架内部使用,用户可忽略,若不幸因断电等其它因素极小概率下发生死锁,可删除该索引即可.另外,在使用时如碰到索引变更,原索引名称可能会被追加后缀_s0或_s1,不必慌张,这是全自动平滑迁移零停机的必经之路,索引后缀不影响使用,框架会自动激活该新索引.关于_s0和_s1后缀,在此模式下无法避免,因为要保留原索引数据迁移,又不能同时存在两个同名索引,凡是都是要付出代价的,如果您不认可此种处理方式,可以使用其他的方式。
(2)实践测试
【1】创建实体类并绑定索引名称为document,添加相关的字段属性,当前索引未创建。
【2】修改日志为debug模式以便查看DSL语句。启动项目,可以看到框架帮助我们自动创建了索引。
【3】默认创建的主分片为1,副本数为1
【4】现在我们添加一些测试数据,直接通过框架提供的API测试,现在已经创建了三条数据。
【5】接下来修改实体类的主分片大小,副本数大小,添加字段,然后再重启项目。
【6】下面观察输出的DSL语句,分析实现原理。
1:首先创建一个新的索引,索引名称为:原索引名称_s0
2:然后通过reindex命令将原索引的数据迁移到新的索引上,DSL语句如下所示
3:修改别名,将原先的别名下包含的旧的索引去除,然后添加刚刚创建的新的索引,这样通过原先的索引别名依然可以正常查询处理数据。
4:在完成上述文档迁移操作后,将旧的索引直接删除
5:以上执行流程中还包括了创建ee-distribute-lock索引,该索引为框架内部使用,可忽略。接着通过查询接口查询过往的数据,可以正常查询到历史的数据,并且其_settings属性和_mapping表结构都得到了更新。
索引文档的增删改查
插入记录
// 插入一条记录
Integer insert(T entity);
// 批量插入多条记录
Integer insertBatch(Collection<T> entityList)
更新记录
//根据 ID 更新
Integer updateById(T entity);
// 根据ID 批量更新
Integer updateBatchByIds(Collection<T> entityList);
// 根据动态条件 更新记录
Integer update(T entity, LambdaEsUpdateWrapper<T> updateWrapper);
删除记录
// 根据 ID 删除
Integer deleteById(Serializable id);
// 根据 entity 条件,删除记录
Integer delete(LambdaEsQueryWrapper<T> wrapper);
// 删除(根据ID 批量删除)
Integer deleteBatchIds(Collection<? extends Serializable> idList);
keyword精确查询
当我们需要对查询字段进行精确匹配,左模糊,右模糊,全模糊,排序聚合等操作时,需要该字段的索引类型为keyword类型,否则你会发现查询没有查出想要的结果,甚至报错. 比如EE中常用的API eq(),like(),distinct()等都需要字段类型为keyword类型.
@GetMapping("/search")
public List<UserInfo> search(String userName)
LambdaEsQueryWrapper<UserInfo> wrapper = new LambdaEsQueryWrapper<>();
wrapper.eq(UserInfo::getUserName, userName);
return userInfoMapper.selectList(wrapper);
keyword模糊查询
LambdaEsQueryWrapper<UserInfo> wrapper = new LambdaEsQueryWrapper<>();
wrapper.like(UserInfo::getUserName, userName);
text分词查询
当我们需要对字段进行分词查询时,需要该字段的类型为text类型,并且指定分词器(不指定就用ES默认分词器,效果通常不理想). 比如EE中常用的API match()等都需要字段类型为text类型. 当使用match查询时未查询到预期结果时,可以先检查索引类型,然后再检查分词器,因为如果一个词没被分词器分出来,那结果也是查询不出来的.
中文需要提前在ES中安装分词器。
/**
* 分词测试
*/
@GetMapping("/match")
public EsPageInfo<UserInfo> match(String word)
LambdaEsQueryWrapper<UserInfo> wrapper = new LambdaEsQueryWrapper<>();
wrapper.match(UserInfo::getContent, word);
return EsPageInfo.of(userInfoMapper.selectList(wrapper));
条件构造器
query_string方式
// 假设我的查询条件是:创建者等于老王,且创建者分词匹配"隔壁"(比如:隔壁老汉,隔壁老王),或者创建者包含猪蹄
// 对应mysql语法是(creator="老王" and creator like "老王") or creator like "%猪蹄%",下面用es的queryString来演示实现一样的效果
// 足够灵活,非常适合前端页面中的查询条件列表字段及条件不固定,且可选"与或"的场景.
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
String queryStr = QueryUtils.combine(Link.OR,
QueryUtils.buildQueryString(Document::getCreator, "老王", Query.EQ, Link.AND),
QueryUtils.buildQueryString(Document::getCreator, "隔壁", Query.MATCH))
+ QueryUtils.buildQueryString(Document::getCreator, "*猪蹄*", Query.EQ);
wrapper.queryStringQuery(queryStr);
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
对应的DSL语句和结果展示
分页查询
关于分页,支持了ES的三种分页模式,大家可参考下表,按需选择.
// 物理分页
EsPageInfo<T> pageQuery(LambdaEsQueryWrapper<T> wrapper, Integer pageNum, Integer pageSize);
文档链接:
https://www.easy-es.cn/pages/0cf11e/#浅分页
注意事项
【1】目前还不支持ElasticSearch8.X的版本,目前暂时只支持es7x,也不支持6.X版本。
【2】easy-es索引的自动托管之平滑模式使用起来很方便,但是注意它会把原索引删除,使用新的索引,虽然在框架内使用不受影响,但是如果其他地方依赖了该索引那么可能会造成影响,如果使用这种方式,建议采用别名策略,不直接访问索引。
【3】对比于spring-data-elasticsearch和easy-es,二者在使用上都相对于Es官方提供的RestHighLevelClient有着更简洁的使用操作性。spring-data-elasticsearch因为属于spring-data项目维护,社区更加活跃更新也比较频繁,目前已经支持8.X版本了。easy-es在使用上相对来说更加友好而且更加符合国人习惯。
【4】easy-es所支持的混合模式,当easy-es无法满足需求时可以使用原生的RestHighLevelClient这对于实际应用非常适用。
【5】ES查询功能非常强,特性众多,因时间问题无法测试所有的功能情况。但是目前针对于常用的功能进行的测试,从结果看,基本上是满足日常的开发使用的,而且操作起来还是非常简便的。根据issues的反馈,easy-es预计在今年推出2.X版本,该版本将会有很多的优化和新的功能点,进一步满足开发使用。
参考文档
https://www.easy-es.cn/pages/ec7460/
https://github.com/zwzhangyu/ZyCodeHub/tree/main/middleware/elasticsearch/easy-es
以上是关于Easy-Es框架实践测试整理 基于ElasticSearch的ORM框架的主要内容,如果未能解决你的问题,请参考以下文章