前言: 在上一篇博客中, 我介绍了从零开始安装 Elasticsearch,es 是可以理解为一个操作数据的中间件, 可以把它作为数据的存储仓库来对待, 它具备强大的吞吐能力和计算能力, 其基于 Lucene 服务器开发, 在搜索领域具有统治般的地位. 平时可以通过命令来执行语句来查询 ES, 但是在实际的开发中, 还是以使用 API 居多, 关于 ES 的第三方框架有很多, 比如 BBOSS,spring 也对其进行封装叫做 spring-Elasticsearch-data, 本篇博客, 我们就来聚焦 Spring 的官方 API, 来做一个基本的 demo 实现 API 来操作 Es(Elasticsearch 的简称)
本篇博客的目录
一: Elasticsearch 的基本概念
二: ElasticSearch5.2.2 的安装
三: Elasticsearch 的官方 API 与 Demo 实现
四: 总结
一: Elasticsearch 的基本概念
1.1: 索引 index
索引是 ES 存储数据的基本顶层单元目录. 它好比就是关系型数据库中的数据库, 是存储数据的地方. 当搜索数据的时候, 会直接从索引中查取, 注意这里要与关系型数据的中的索引区分, 他们是完全不同的概念. index 的名字必须是小写, 不能包含逗号, 下划线或者大写字母
1.2: 类型 type
type 表示一类或者一种事物的抽象, 在关系型数据库中我们经常将一类结构相似的数据放在一个表里, 而在 Elasticsearch 中, 使用相同类型的 type 表示相同的一类事物, 它相当于关系型数据库中的表 (table), 用于描述文档中的各个字段的定义. 每个 type 都有自己的映射(mapping) 或者结构定义. type 的名字可以是大写或者小写, 不能包含下划线或者逗号.
1.3: 文档 document
document 是 index 中的单条数据序列化成的 JSON 格式的数据, 它以唯一 id(_id)存储在 ES 中, 相当于关系表的数据行, 存储数据的载体, 包含一个或多个存有数据的字段;
. 字段(Field): 文档的一个 Key/Value 对;
. 词(Term): 表示文本中的一个单词;
. 标记 (Token): 表示在字段中出现的词, 由该词的文本, 偏移量(开始和结束) 以及类型组成
1.4:Node 与 Cluster
Elastic 本质上是一个分布式数据库, 允许多台服务器协同工作, 每台服务器可以运行多个 Elastic 实例. 单个 Elastic 实例称为一个节点(node). 一组节点构成一个集群(cluster).
1.5: 与 MySQL 进行比较
二: ElasticSearch5.2.2 的安装
上一篇博客已经介绍了 ES2.0 版本的安装, 这篇就不再赘述了. 不过我还是决定说一些在安装过程中的坑, 安装 ES 的坑确实很多, 楼主分别安装了 2.0 版本, 5.5.2 版本, 5.2.2 版本, 还用 docker 安装了, 但是因为 docker 玩的不够熟练, 在配置文件上更改还是出了很多问题, 最终还是靠传统的安装方式解决的. 接下来就说说 ES 安装过程中的一些坑以及主要的点:
2.1:cannot allocate memory
这个是因为 ES 无法获取到足够的内存, 解决办法就是, 修改 elasticseach 的 config 目录下的 jvm.options,ES 默认的大小是 1G, 最好修改成 2 的整数倍的容量, 具体依自己的内存而定, 我修改的是 256m
2.2:Exception in thread "main" java.lang.RuntimeException: don't run Elasticsearch as root.
无法以 root 用户启动, ES 直接以 root 用户是无法启动的, 解决办法很简单, 就是建立一个 ES 的专用的组和用户:
- groupadd elasticsearchgroup
- useradd elasticsearchgroup -g Elasticsearch -p Elasticsearch
- chown -R elasticsearchgroup:Elasticsearch Elasticsearch-5.2.2
这里是先建立了一个 Elasticsearch 的组, 然后再添加 Elasticsearch 这个用户, 密码也是 Elasticsearch, 再给 ES 的安装目录添加权限
2.3:ERROR: Bootstrap checks failed max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
解决办法就是修改系统的修改配置 sysctl.conf 最大文件交换数量:
- vi.sysctl.conf
- vm.max_map_count=655360
- sysctl -p
- 2.4: org.Elasticsearch.client.transport.NoNodeAvailableException
这个错误, 是后台在连接 API 出现的错误, 为了解决这个问题, 我花了很久(下面会说到版本号的问题), 还有需要更改一下配置:
首先是编辑 config 目录下的 Elasticsearch.YAML 文件:
这里的 host 要把 #号打开, 然后写上自己的外网 IP 的地址, 还有 cluster.name 的名字也要记住, 它在配置 ES 中需要用到
三: Elasticsearch 的官方 API 与 Demo 实现
3.1.1: 引入 TransPort5.2.2
这里必须注意引入 5.2.2 版本, 因为我服务上安装的 ES 就是 5.2.2 版本. ES 官方 API 众多, 每个版本之间不是互相兼容的, 如果引入的版本对应, 会报一个错误:
- org.Elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available.(这个错误折磨了我很久, 所以一定要以自己的服务器上的版本为准, 引入 netty 和 Elasticsearch,transport 的版本互相对应)
- <dependency>
- <groupId>org.Elasticsearch.plugin</groupId>
- <artifactId>transport-netty4-client</artifactId>
- <version>5.2.2</version>
- </dependency>
- <dependency>
- <groupId>org.Elasticsearch</groupId>
- <artifactId>Elasticsearch</artifactId>
- <version>5.2.2</version>
- </dependency>
- <dependency>
- <groupId>org.Elasticsearch.client</groupId>
- <artifactId>transport</artifactId>
- <version>5.2.2</version>
- </dependency>
3.1.2:springboot 配置 ES
新建一个类叫做 ESconfig, 主要是配置 ES 服务器的 IP 和端口(注意这里是 9300 而不是 9200),9300 是 ES 的 TCP 服务端口, 然后实例化客户端;
- package com.wyq.Elasticsearch.easticsearchtest.config;
- import org.Elasticsearch.client.transport.TransportClient;
- import org.Elasticsearch.common.settings.Settings;
- import org.Elasticsearch.common.transport.InetSocketTransportAddress;
- import org.Elasticsearch.transport.client.PreBuiltTransportClient;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import java.NET.InetAddress;
- import java.NET.UnknownHostException;
- @Configuration
- public class ESConfig {
- @Bean
- public TransportClient client() throws UnknownHostException {
- // 9300 是 es 的 tcp 服务端口
- final String host = "176.122.132.220";
- InetSocketTransportAddress node = new InetSocketTransportAddress(
- InetAddress.getByName(host),
- 9300);
- // 设置 es 节点的配置信息
- Settings settings = Settings.builder()
- .put("cluster.name", "application").build();
- // 实例化 es 的客户端对象
- TransportClient client = new PreBuiltTransportClient(settings);
- client.addTransportAddress(node);
- return client;
- }
- }
3.1.3: 添加 index
如果在 ES 里面添加一个 index, 我们需要以下命令, 解释以下就是指定数据的 index 和 type, 在里面在指定 fieldname
- curl -X PUT 'ip:port/index/type/id' -d '
- {
- "filedname": "xxx"
- }'
ES 提供了同等的 API 来供我们使用, 按照下面的例子, 我们将会添加一个 index 叫做 animal,type 叫做 person 的数据, 并通过 Springboot 注入 TransPortClient, 然后用 MVC 来获取请求参数, 交给 ES 的 API 去处理: XContentFactory.jsonBuilder()去拼接不同的 JSON 字符串, 用 client 去处理:
- @RestController
- public class ElasticSearchDemoController {
- @Autowired
- private TransportClient client;
- public static final String index = "product";
- public static final String type = "person";
- /**
- * 添加一个人的数据
- *
- * @param name 名字
- * @param sex 性别
- * @param message 说明
- * @param job 工作
- * @param onlyMark 唯一标志
- * @return
- */
- @PostMapping("/es/add")
- public ResponseEntity add(@RequestParam("name") String name,
- @RequestParam("sex") int sex,
- @RequestParam("message") String message,
- @RequestParam("job") String job,
- @RequestParam("onlyMark") int onlyMark) {
- try {
- // 将参数 build 成一个 JSON 对象
- XContentBuilder content = XContentFactory.jsonBuilder()
- .startObject()
- .field("uniqueId", onlyMark)
- .field("name", name)
- .field("sex", sex)
- .field("message", message)
- .field("job", job)
- .endObject();
- IndexResponse response = client.prepareIndex(index, type)
- .setSource(content)
- .get();
- return ResponseEntity.getSuccess(response.getResult()+""+response.getId(), HttpStatus.OK.value());
- } catch (Exception e) {
- e.printStackTrace();
- return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR.value());
- }
- }
- }
3.1.4: 查询 index
通过 id 去查询, 然后通过 client(TransPortClient)的 prepareGet 方法去查询, 最后返回一个 GetResponse 结果然后获取它的 source 结果:
- /**
- * 按 id 查询
- *
- * @param id
- * @return
- */
- @GetMapping("/es/get")
- public ResponseEntity searchById(@RequestParam("id") String id) {
- if (id.isEmpty()) {
- return new ResponseEntity(HttpStatus.NOT_FOUND.value());
- }
- // 通过索引, 类型, id 向 es 进行查询数据
- GetResponse response = client.prepareGet(index, type, id).get();
- if (!response.isExists()) {
- return new ResponseEntity(HttpStatus.NOT_FOUND.value());
- }
- return ResponseEntity.getSuccess(response.getSource(), HttpStatus.OK.value());
- }
3.1.5: 删除 index
同样删除 index, 也是通过 id 来匹配的, id 是唯一标志, 然后交给 TransPort 的 prepareDelete 方法去删除
- /**
- * 按 id 删除数据
- *
- * @param id
- * @return
- */
- @GetMapping("/es/delete")
- public ResponseEntity delete(@RequestParam("id") String id) {
- DeleteResponse response = client.prepareDelete(index, type, id).get();
- return ResponseEntity.getSuccess(response.getResult(), HttpStatus.OK.value());
- }
3.1.6: 更新 index
通过获取更新的内容, 然后交给 TransPort 的 update 方法去更新需要更新的字段, 最终返回更新的内容:
- /**
- * 根据文档 id 更新某个文档的数据
- *
- * @param uniqueId
- * @param name
- * @param sex
- * @param message
- * @param job
- * @return
- */
- @PutMapping("/es/update")
- public ResponseEntity update(@RequestParam("id") String id,
- @RequestParam(value = "name", required = false) String name,
- @RequestParam(value = "sex", required = false) Integer sex,
- @RequestParam(value = "message", required = false) String message,
- @RequestParam(value = "job", required = false) String job,
- @RequestParam(value = "uniqueId", required = false) Integer uniqueId){
- UpdateRequest update = new UpdateRequest(index, type, id);
- try {
- XContentBuilder builder = XContentFactory.jsonBuilder()
- .startObject();
- if (Objects.nonNull(name)) {
- builder.field("name", name);
- }
- if (Objects.nonNull(sex)) {
- builder.field("sex", sex);
- }
- if (Objects.nonNull(message)) {
- builder.field("message", message);
- }
- if (Objects.nonNull(job)) {
- builder.field("job", job);
- }
- if (Objects.nonNull(uniqueId)){
- builder.field("uniqueId",uniqueId);
- }
- builder.endObject();
- update.doc(builder);
- UpdateResponse response = client.update(update).get();
- return ResponseEntity.getSuccess(response.getResult(), HttpStatus.OK.value());
- } catch (Exception e) {
- e.printStackTrace();
- return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR.value());
- }
- }
3.2: 测试
3.2.1: 打开 postman, 然后我们来先来添加一个 index, 添加一些属性, 后台提示成功: 我们在去 ES 中查询一下:
3.2.2: 在服务器中打开, 然后输入查询所有 index 的命令, 查询 animal 这个 index 对应的数据, 可以看到我们的数据已经顺利添加:
3.2.3: 查询 index
上面的方式是通过命令的方式来查询的, 我们再来通过程序来测试查询 index, 可以看到数据查询到了:
3.2.4: 更新 index
后台通过传入的字段对指定有值的数据进行更新, 在这里我们更新 message:
3.2.5: 为了验证我们的更改是否生效, 我们再查询一下这个 index, 发现 message 已经变化了:
3.2.6: 删除 index
3.2.7: 同样我们在验证一下是否删除成功, 可以看出 total 为 0, 也代表没有数据了:
四: 总结
碍于篇幅, 本片博文就介绍到这里, 主要是讲述了 Elasticsearch 的官方 API 的使用, 以及搭建 ES 中的一些坑, 为了解决这些坑, 我在下班之余耗费了好几个星期研究这些问题, ES 的版本众多, 一定要注意版本的选择. 本篇博文适合入门级别, 没有介绍 ES 的高级特性, 关于它本身具有很多高端特性, 实乃搜索利器, 我们不能把它作为数据库来看待, 实际上它本身可以理解为一个搜索引擎, 有着丰富的使用场景,"它远大于数据库, 存储只是它的一个细小的功能", 好了, 希望本篇博客可以帮助到你.
本篇博客的代码分享: 链接: https://pan.baidu.com/s/1k-E15_EtHArG3iYi_3wc9A 密码: 0nbp
如果关于本篇博客有任何问题, 请加群: 618626589
参考文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
http://www.ruanyifeng.com/blog/2017/08/elasticsearch.html
来源: http://www.bubuko.com/infodetail-3086919.html