背景
近期我们团队接到这样一个需求,用户通过附件上传,附件解析,将word文档上传到服务器,且把word里的内容存储到了数据库里。用户希望根据文档内容里的某些字、词来进行搜索,确定哪些文档里包含这些内容。
针对这个搜索的需求,我当时考虑的方案有两个:
1、全文索引,
2、Elasticsearch(ES),
两者都可以实现内容的全文检索,不过全文索引是关系型数据库中常用的。而ES主要用于做搜索引擎。综合考虑数据的及时性,服务的可扩展性,搜索的高效性后,我决定采用ES。因为相比于关系型数据库的索引,ES的性能更好,易扩展,数据入库后即可进行搜索,而全文索引则需要在数据入库后对索引进行一次重建,才能查询新入库的数据。
Elasticsearch介绍
Elasticsearch是一个开源的分布式全文搜索和分析引擎,基于Apache Lucene项目构建。它提供了一个分布式的、多租户的全文搜索引擎,可以快速地进行数据检索和分析。
Elasticsearch可以帮助用户处理大量的结构化和非结构化数据,支持实时搜索、分析和可视化等功能。它可以处理大数据量,支持分布式架构,可以横向扩展以满足不同的需求。Elasticsearch还提供了丰富的API,可以与其他应用程序进行集成,支持多种编程语言。
Elasticsearch的核心功能包括:
1)分布式搜索和分析:支持实时搜索和分析大量的数据。
2)多租户支持:可以为不同的用户或应用程序提供独立的搜索和分析功能。
3)高可用性和容错性:支持自动故障转移和自动恢复功能,确保系统的高可用性和容错性。
4)数据安全:支持数据加密、身份验证和访问控制等功能,确保用户数据的安全性和隐私性。
5)可扩展性:支持分布式架构,可以根据需要添加新的节点和服务器,以满足不同的需求。
除了以上核心功能,Elasticsearch还提供了一些高级功能,如聚合分析、自动完成、拼写纠正、近义词搜索、地理位置搜索等。
总之,Elasticsearch是一个功能强大的全文搜索和分析引擎,可以帮助用户处理大量的结构化和非结构化数据,支持实时搜索、分析和可视化等功能。它是一个开源的项目,拥有活跃的社区和丰富的文档,可以满足不同用户的需求。
ES与关系型数据库之间的名词对应关系如下
Elasticsearch实践
1、ES的Linux安装
1、下载Elasticsearch安装包,下载路径如下:ES发装包链接
2、下载到本地之后,上传到linux服务器的文件夹下,通过以下命令解压:tar -zxvf elasticsearch-7.8.1-linux-x86_64.tar.gz
3、创建用户并授权。(我的用户是esuser)
useradd esuser
passwd esuser ****(这里是密码)
chown -R esuser:esuser /home/esuser/elasticsearch7.8
4、更改解压的文件夹下elasticsearch.yml里的配置:
cluster.name: elasticsearch #集群名字
node.name: es-leixi-01 #节点名称
path.data: /home/esuser/elasticsearch7.8/data #数据存放位置
path.logs: /home/esuser/elasticsearch7.8/logs #日志存放位置
http.port: 19200 #端口号
network.host: 0.0.0.0
cluster.initial_master_nodes: ["es-leixi-01"] #当前集群包含的节点
5、把linux里的端口号19200打开,这样就可以在其他服务访问linux的es服务了(不同的linux服务器该命令会有所不同,我的是RadHat)
firewall-cmd --zone=public --add-port=19200/tcp --permanent
firewall-cmd --reload
6、修改sysctl.conf文件,
vim /etc/sysctl.conf
vm.max_map_count = 262145
如果不修改会报以下错误:
7、进入es服务器路径下/bin, 执行命令./elasticsearch 即可启动es
8、启动es之后,退出linux黑窗口,发现es服务也停了,这时候需要用以下命令,即使关了黑窗口,es也可以正常运行。
nohup ./elasticsearch >/home/esuser/es.log 2>&1 &
2、浏览器实现ES数据的操作
推荐一个插件:elasticsearch-head。它是一个开源的Elasticsearch集群管理工具。它提供了一个基于Web的用户界面,可以方便地管理Elasticsearch集群,包括索引、文档、节点和分片等。把它安装在浏览器上,就可以通过浏览器对es进行增删改查了。安装方式和操作方法见:elasticsearch-head浏览器插件安装使用。
我在初次使用Elasticsearch-head进行查询时,报406 elasticsearch-head "error": "Content-Type header [application/x-www-form-urlencoded] is not supported", , 使用的是以下方式解决:
1、进入head安装目录;
2、打开文件夹\elasticsearch-head\
3、编辑vendor.js 共有两处
①. 6886行 contentType: "application/x-www-form-urlencoded 改成
contentType: "application/json;charset=UTF-8"
②. 7574行 var inspectData = s.contentType === "application/x-www-form-urlencoded" && 改成
var inspectData = s.contentType === "application/json;charset=UTF-8" &&
以下是我用elasticsearch-head的一些实践示例:
PUT 索引名 新增索引
DELETE 索引名 删除索引
GET 索引名/_mapping 查看索引的结构
GET 索引名/_search 查询索引信息
GET 索引名/settings 查看索引配置
PUT 索引名/_mapping 添加字段
POST 索引名/_doc/主键 添加数据
POST 索引名/_update/主键 修改数据
DELETE 索引名/_doc/主键 删除数据
POST 索引名/_search 查询索引信息 --注意,如果查询索引时使用查询条件,则需要用POST的形式
3、Springboot实现ES数据的操作
接下来,咱们尝试使用Springboot来对Elasticsearch进行一些基础的操作。这个实践我照抄 参考了以下博客:从零搭建springcloud项目- elasticsearch(7)_springcloud nacos配置 resthighlevelclient 多个节点-CSDN博客文章浏览阅读1.1k次。springcloud集成elasticsearch_springcloud nacos配置 resthighlevelclient 多个节点https://blog.csdn.net/qq_29673919/article/details/122859463
雷袭在这里感谢大佬指路了。
1、添加maven依赖
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.1.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.1.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</exclusion>
</exclusions>
<version>7.1.1</version>
</dependency>
2、application.yml配置
elasticSearch:
# es集群地址
host: 10.0.6.183
port: 19200
username:
password:
3、config文件配置
@Configuration
@Primary
public class EsConfig extends AbstractFactoryBean<Object> {
private static final Logger LOG = LoggerFactory.getLogger(EsConfig.class);
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private int port;
@Value("${elasticsearch.username}")
private String username;
@Value("${elasticsearch.password}")
private String password;
private RestHighLevelClient restHighLevelClient;
@Override
public void destroy() throws Exception {
// 关闭Client
if (restHighLevelClient != null) {
restHighLevelClient.close();
}
}
@Override
public Class<RestHighLevelClient> getObjectType() {
return RestHighLevelClient.class;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
protected Object createInstance() throws Exception {
try {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
// 如果有多个节点,构建多个HttpHost
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, "http"))
.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
});
restHighLevelClient = new RestHighLevelClient(builder);
} catch (Exception e) {
LOG.error(e.getMessage());
}
return restHighLevelClient;
}
}
4、编写操作es的工具类,测试的Entity
// 服务层
/**
*
* @author leixiyueqi
* @since 2023/12/28 22:00
*/
@Service
public class ElasticService {
@Autowired
private RestHighLevelClient client;
/**
* 功能描述: 创建索引
*
* @param index 索引名称
* @param source 配置
* @author leixi
* @return boolean
*/
public boolean createIndex(String index, String source) {
try {
CreateIndexRequest request = new CreateIndexRequest(index);
request.mapping(source, XContentType.JSON);
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
return createIndexResponse.isAcknowledged();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public boolean deleteIndex(String index) {
try {
DeleteIndexRequest request = new DeleteIndexRequest(index);
AcknowledgedResponse response= client.indices().delete(request, RequestOptions.DEFAULT);
return response.isAcknowledged();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 功能描述: 判断索引是否存在
*
* @param index 索引名称
* @author leixi
* @return boolean
*/
public boolean indexExist(String index) {
try {
GetIndexRequest request = new GetIndexRequest(index);
request.local(false);
request.humanReadable(true);
request.includeDefaults(false);
return client.indices().exists(request, RequestOptions.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 功能描述: 插入数据
*
* @param index 索引名称
* @param entity 数据类
* @author leixi
* @return boolean
*/
public boolean insertOne(String index, ElasticEntity entity) {
IndexRequest indexRequest = new IndexRequest(index);
String userJson = JSONObject.toJSONString(entity.getData());
indexRequest.source(userJson, XContentType.JSON).id(entity.get_id());
try {
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
if (indexResponse != null) {
if (indexResponse.getResult() == DocWriteResponse.Result.CREATED || indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 功能描述: 查询数据
*
* @param indexArray 索引数组,用于多个索引一起查询
* @param searchSourceBuilder 查询builder
* @param c 参数类型
* @author leixi
* @return * @return: null
* @throws Exception 异常
*/
public <T> List<T> listByBuilder(String[] indexArray, SearchSourceBuilder searchSourceBuilder, Class<T> c) {
SearchRequest request = new SearchRequest(indexArray);
searchSourceBuilder.trackTotalHits(true);
request.source(searchSourceBuilder);
try {
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
List<T> res = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String json = JSON.toJSONString(sourceAsMap);
res.add(JSON.parseObject(json, c));
}
return res;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
//实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ElasticEntity<T> {
private String id;
private String _id;
private T data;
}
//测试类
/**
*
* @author leixiyueqi
* @since 2023/12/28 22:00
*/
@Data
public class EsTestEntity {
private long id;
private String age;
private String name;
private String sex;
private Integer height;
}
5、添加controller
@RestController
public class EsController {
@Autowired
private ElasticService esService;
@GetMapping("/createIndex")
public Object createIndex() {
String source = "{\"properties\":{\"sex\":{\"type\":\"text\"},\"name\":{\"type\":\"text\"},\"id\":{\"type\":\"long\"},\"age\":{\"type\":\"text\"},\"height\":{\"type\":\"integer\"}}}";
JSONObject json = JSONObject.parseObject(source);
String str = JSONObject.toJSONString(json);
return esService.createIndex("es_test_2023", str);
}
@PostMapping("/insertDataIntoEs")
public Object insertDataIntoEs(@RequestBody EsTestEntity entity) {
ElasticEntity po = new ElasticEntity();
po.setData(entity);
return esService.insertOne("es_test_2023", po);
}
@PostMapping("/queryEsData")
public Object queryEsData() {
// 使用分词,查询name中含有张三的
QueryBuilder nameQuery = QueryBuilders.matchQuery("name", "张三");
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(nameQuery);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.explain(true);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
sourceBuilder.query(boolQueryBuilder);
String[] indexArray = new String[]{"es_test_2023"};
return esService.listByBuilder(indexArray, sourceBuilder, EsTestEntity.class);
}
}
6、通过Postman测分别进行创建索引,添加数据,查询数据的测试,结果如下:
最后,再次感觉在我实践过程中给予我指点和帮助的大佬们,码文不易,希望这篇文章可以帮助到后来者们。