ElasticSearch
ElasticSearch1、什么是ElasticSearch?1.1、概念Elasticsearch (ES)是一个基于Lucene开源的高扩展的分布式全文检索引擎,能够安全可靠地获取任何来源、任何格式的数据,然后实时的对数据进行检索、分析和可视化,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据,它不但稳定、可靠、快速,而且也具有良好的水平扩展能力,是
ElasticSearch
1、什么是ElasticSearch?
1.1、概念
Elasticsearch (ES)是一个基于Lucene
开源的高扩展的分布式全文检索引擎
,能够安全可靠地获取任何来源、任何格式的数据,然后实时的对数据进行检索、分析和可视化,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据,它不但稳定、可靠、快速,而且也具有良好的水平扩展能力,是专门为分布式环境设计的,它的目的是通过简单的RESTFUL API
来隐藏Lucene的复杂性,从而使全文搜索变得简单
1.2、了解ELK
ELK是Elasticsearch
、Logstash
、 Kibana
三大开源框架首字母大写简称。 市面上也被成为Elastic Stack
。其中ES是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架
。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用ES作为底层支持框架,由此可见ES提供的搜索能力确实非常强大。Logstash是ELK的中央数据流引擎
,用于从不同目标(文件/数据存储/MQ )收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。Kibana可以将ES的数据通过友好的页面展示出来,提供实时分析的功能
市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。
1.3、ES应用案例
- GitHub: 2013 年初,抛弃了 Solr,采取 Elasticsearch 来做 PB 级的搜索。“GitHub 使用 Elasticsearch 搜索 20TB 的数据,包括 13 亿文件和 1300 亿行代码”
- 维基百科:启动以 Elasticsearch 为基础的核心搜索架构
- 百度:目前广泛使用 Elasticsearch 作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部 20 多个业务线(包括云分析、网盟、预测、文库、 直达号、钱包、风控等),单集群最大100 台机器,200 个ES节点,每天导入 30TB+ 数据
- SoundCloud:“SoundCloud”使用Elasticsearch为 1.8亿用户提供即时而精准的音乐搜索服务
- 新浪:使用 Elasticsearch 分析处理 32 亿条实时日志
- 阿里:使用 Elasticsearch 构建日志采集和分析体系
- Stack Overflow:解决 Bug 问题的网站,全英文,编程人员交流的网站
2、ElasticSearch安装
2.1、安装ElasticSearch
下载
window安装
-
解压即可
-
了解目录
- bin:启动文件
- config:配置文件
- jdk:内置 JDK目录
- lib:相关jar包
- logs:日志
- modules:功能模块
- plugins:插件
-
启动
打开
bin
目录,双击elasticsearch.bat
启动 -
访问测试,
9200
端口,注:9300
端口为ES集群间组件的通信端口
安装Es可视化界面 head插件
注:前提需要有
node.js
环境
-
启动
cnpm install npm run start
-
连接测试发现端口存在跨域问题:配置
elasticsearch.yml
http.cors.enabled: true http.cors.allow-origin: "*"
-
重启Es服务,再次连接测试,健康值绿色表示连接成功!
2.2、安装Kibana
Kibana是一个针对ES的开源分析及可视化平台,用来搜索、查看交互存储在ES索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板( dashboard )实时显示ES查询动态。
注:Kibana版本要与ES版本保持一致!
下载完成,解压缩即可(解压缩需要一定时间,标准的前端化工程)
启动测试
打开bin
目录,双击kibana.bat
启动即可
访问测试,5601
端口
开发工具(PostMan、head……)
汉化,进入kibana config目录下,修改kibana.yml
配置文件,zh-CN
3、ES核心概念
Elasticsearch 是面向文档型数据库,关系型数据库和elasticsearch客观的类比
Relational DB | ElastiSearch |
---|---|
DataBase(数据库) | Index(索引) |
table(表) | Type(类型) |
row(行) | Documents(文档) |
columns(列) | Field(字段) |
ES 里的 Index 可以看做一个库,而 Type 相当于表,Documents 则相当于表的行。 这里 Types 的概念已经被逐渐弱化,Elasticsearch 6.X 中,一个 index 下已经只能包含一个 type,Elasticsearch 7.X 中,Type 的概念已经被删除了
ES使用JSON
作为文档序列化的格式
物理设计:
elasticsearch在后台把每个索引划分成多个分片,每分分片可以在集群中的不同服务器间迁移
一个ES就是一个集群!默认的集群名称就是elasticsearch
逻辑设计:
文档
就是每条记录
之前说elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性:
- 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value !
- 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的!
- 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段
索引
就是数据库
索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。 索引存储了映射类型的字段和其他设置。然后它们被存储搭配各个分片上
映射(Mapping)
映射是用来定义一个文档,和其包含的字段,是如何存储和索引的过程,相当于关系型数据库中给列添加约束
4、IK分词器插件
4.1、什么是IK分词器?
分词:就是把一段中文或者别的划分成一个个的关键字,在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如“我爱中国会被分为"我",“爱”,“中”,“国”,这显然是不符合要求的,所以需要安装中文分词器ik来解决这个问题
IK提供了两个分词算法:ik. _smart
和ik_ max_ word
,其中ik. smart为最少切分,ik_ max_ word为最细粒度划分
4.2、安装Ik分词器
-
下载完毕后,解压缩存放在es插件文件夹目录即可
-
重启观察es,可以看到ik分词器被加载了
-
elasticsearch-plugin 可以通过该命令查看加载进来的插件
-
使用kibana测试
查看不同的分词效果
ik. smart为最少切分
ik_ max_ word为最细粒度划分,穷尽词库的可能性
如果需要自己配置分词可以在自定义的dic
文件中进行配置即可
注:分词器也要和es版本对应
5、Rest风格
5.1、Rest说明
一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁 ,更有层次 ,更易于实现缓存等机制
基本Rest命令说明:
mehtod | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
GET | localhost:9200/索引名称/类型名称/文档id | 通过id查询文档 |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
5.2、索引操作
创建一个索引
PUT /索引名/类型名/文档id
自动增加索引,数据成功添加
创建索引具体规则
获得索引
GET 索引名
查看默认信息
GET _cat/indices 可以获取es的很多具体相关信息
修改文档数据 方式一:直接覆盖
修改文档数据 方式二:修改指定字段
删除索引 DELETE order
删除索引文档 DELETE order/_doc/1
5.3、文档操作
5.3.1、基本操作
添加数据
PUT /xiaozhang/user/1
{
"username":"小张",
"gender":"男",
"age":20,
"birth":"2001-06-06",
"hobby":["code","girl","music"]
}
查询数据
单查询 GET 索引名称/类型/文档id
全查询 GET 索引名称/类型/_search
更新数据 跟上述一致,可分为两种修改 PUT和POST,如果使用PUT的话没有填写的字段值均设为空,POST可以灵活设置字段 推荐使用POST
删除数据 DELETE 索引名称/类型/文档id
5.3.2、复杂操作
排序、分页、模糊查询、精准查询、高亮显示
结果过滤
排序
分页
布尔值查询
must (and)所有条件都需要符合
should(or)只需要符合一个条件即可
must_not(not)
匹配多个条件
精确查询
高亮查询
自定义高亮条件
6、集成SpringBoot
SpringBoot平台并没有跟随ES的更新速度进行同步更新,ES提供了High Level Client操作ES
-
导入依赖
<!-- elasticsearch 的客户端 --> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId </dependency>
-
找出对应的类(对象)
6.1、索引操作
-
创建索引
private RestHighLevelClient client; @BeforeEach void setUp() { this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200"))); } @AfterEach void tearDown() throws IOException { // 关闭客户端 this.client.close(); } // 创建索引 @Test void createIndex() throws IOException { // 1、创建索引请求 CreateIndexRequest request = new CreateIndexRequest("order"); // 2、客户端执行请求 IndicesClient 请求后获得响应 CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT); System.out.println(response.index()); client.close(); }
-
查看索引
// 查询索引 @Test void getIndex() throws IOException { GetIndexRequest request = new GetIndexRequest("order"); GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT); System.out.println(response.getSettings()); client.close(); }
-
删除索引
// 删除索引 @Test void deleteIndex() throws IOException { DeleteIndexRequest request = new DeleteIndexRequest("order"); AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT); System.out.println("是否删除成功:" + response.isAcknowledged()); client.close(); }
6.2、文档操作
-
创建文档
private RestHighLevelClient client; // 文档操作 // 创建文档 @Test public void CreateDocument() throws IOException { Cart cart = new Cart("小米手机", 3699.00, 1000, new Date()); // 创建请求 IndexRequest request = new IndexRequest("cart"); // PUT cart/_doc/1 request.id("1"); request.timeout(TimeValue.timeValueSeconds(1)); // 将数据放入请求中 需要Json格式的 request.source(JSON.toJSONString(cart), XContentType.JSON); // 客户端发送请求 获取响应结果 IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(response.toString()); System.out.println(response.getResult()); client.close(); }
-
查询文档是否存在
// 测试文档是否存在 @Test public void isExists() throws IOException { GetRequest request = new GetRequest("cart", "1"); boolean exists = client.exists(request, RequestOptions.DEFAULT); System.out.println(exists); client.close(); }
-
查询文档
@Test // 获取文档信息 public void queryDocument() throws IOException { GetRequest getRequest = new GetRequest("cart", "1"); // GET cart/_doc/1 GetResponse response = client.get(getRequest, RequestOptions.DEFAULT); // 获取文档的内容 System.out.println(response.getSourceAsString()); System.out.println(response); // 获取全部信息 client.close(); }
-
修改文档
@Test // 修改文档 public void updateDocument() throws IOException { UpdateRequest request = new UpdateRequest("cart", "1"); request.timeout(TimeValue.timeValueSeconds(1)); // POST cart/_doc/1 Cart cart = new Cart("华为手机", 5299.00, 5000, new Date()); request.doc(JSON.toJSONString(cart), XContentType.JSON); UpdateResponse response = client.update(request, RequestOptions.DEFAULT); System.out.println(response.getResult()); }
-
删除文档
@Test // 删除文档 public void deleteDocument() throws IOException { DeleteRequest request = new DeleteRequest("cart", "1"); // DELETE cart/_doc/1 request.timeout(TimeValue.timeValueSeconds(1)); DeleteResponse response = client.delete(request, RequestOptions.DEFAULT); System.out.println(response.getResult()); client.close(); }
-
批量插入
@Test // 批量插入 public void bulkRequestAdd() throws IOException { BulkRequest bulkRequest = new BulkRequest(); bulkRequest.timeout("10s"); List<Cart> cartList = new ArrayList<>(); cartList.add(new Cart("小米手机", 1799.00, 5000, new Date())); cartList.add(new Cart("华为手机", 5299.00, 2002, new Date())); cartList.add(new Cart("苹果手机", 6999.00, 1255, new Date())); cartList.add(new Cart("OPPO手机", 1999.00, 3000, new Date())); cartList.add(new Cart("荣耀手机", 2299.00, 3500, new Date())); for (int i = 0; i < cartList.size(); i++) { // 批量修改和删除,在此次修改对应请求即可 bulkRequest.add(new IndexRequest() .index("cart") .id("100" + (i + 1)) .source(JSON.toJSONString(cartList.get(i)), XContentType.JSON) ); } BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); // 返回false 表示成功! System.out.println("是否失败:" + response.hasFailures()); System.out.println("花费时间:" + response.getTook()); }
-
搜索
@Test // 搜索 // 搜索请求:SearchRequest // 条件构造:SearchSourceBuilder public void search() throws IOException { SearchRequest searchRequest = new SearchRequest(); // 构建搜索条件 SearchSourceBuilder builder = new SearchSourceBuilder(); // 查询条件 // 精确查询:termQuery // 匹配所有:matchAllQuery MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery(); builder.query(matchAllQuery); // 设置超时时长 builder.timeout(new TimeValue(60, TimeUnit.SECONDS)); searchRequest.source(builder); SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); for (SearchHit hit : response.getHits().getHits()) { System.out.println(hit.getSourceAsString()); } }
7、练习
7.1、获取数据
-
导入依赖
<!--解析网页--> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.14.2</version> </dependency>
-
编写实体类
-
编写解析网页数据工具类
@Component public class HtmlParseUtil { public static void main(String[] args) throws Exception { List<Content> contents = parseJD("spring"); for (Content content : contents) { System.out.println("https:" + content.getImage()); System.out.println(content.getPrice()); System.out.println(content.getTitle()); System.out.println(content.getShop()); } } public static List<Content> parseJD(String keyword) throws Exception { String url = "https://search.jd.com/Search?keyword=" + keyword; List<Content> goodsList = new ArrayList<>(); Document document = Jsoup.parse(new URL(url), 30000); Element element = document.getElementById("J_goodsList"); Elements elements = element.getElementsByTag("li"); for (Element el : elements) { String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img"); String price = el.getElementsByClass("p-price").eq(0).text(); String title = el.getElementsByClass("p-name").eq(0).text(); String shop = el.getElementsByClass("curr-shop").eq(0).attr("title"); Content content = new Content(img, price, title, shop); goodsList.add(content); } return goodsList; } }
7.2、获取的数据存放到Es中
-
编写ES客户端配置类
@Configuration public class ElasticSearchClientConfig { @Bean public RestHighLevelClient restHighLevelClient() { return new RestHighLevelClient(RestClient.builder( new HttpHost("localhost", 9200, "http")) ); } }
-
编写service层
@Service public class ContentService { @Autowired @Qualifier("restHighLevelClient") private RestHighLevelClient client; // 解析数据放入es索引中 public Boolean parseContent(String keyword) throws Exception { List<Content> contents = HtmlParseUtil.parseJD(keyword); BulkRequest bulkRequest = new BulkRequest(); bulkRequest.timeout("15s"); for (int i = 0; i < contents.size(); i++) { bulkRequest.add(new IndexRequest("jd_good") .source(JSON.toJSONString(contents.get(i)), XContentType.JSON)); } BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT); return !bulk.hasFailures(); } }
-
实现高亮功能
// 获取数据实现搜索高亮功能 public List<Map<String, Object>> searchPageHighLight(String keyword, int pageNo, int pageSize) throws Exception { if (pageNo <= 1) { pageNo = 1; } // 条件搜索 SearchRequest searchRequest = new SearchRequest("jd_good"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 分页 sourceBuilder.from(pageNo); sourceBuilder.size(pageSize); // 高亮 HighlightBuilder highlightBuilder = new HighlightBuilder(); // 关闭多个高亮文本 只显示第一个文本值的高亮 highlightBuilder.field("title"); highlightBuilder.preTags("<span style='color:red;'>"); highlightBuilder.postTags("</span>"); sourceBuilder.highlighter(highlightBuilder); // 精准匹配 TermQueryBuilder queryBuilder = QueryBuilders.termQuery("title", keyword); sourceBuilder.query(queryBuilder); sourceBuilder.timeout(new TimeValue(20, TimeUnit.SECONDS)); // 执行搜索 searchRequest.source(sourceBuilder); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); // 解析结果 List<Map<String, Object>> maps = new ArrayList<>(); for (SearchHit hit : searchResponse.getHits()) { // 解析高亮的字段 Map<String, HighlightField> highlightFields = hit.getHighlightFields(); HighlightField title = highlightFields.get("title"); Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 之前的结果 // 将原来的字段换成高亮的字段即可! if (title != null) { Text[] fragments = title.fragments(); String newTitle = ""; for (Text fragment : fragments) { newTitle += fragment; } sourceAsMap.put("title", newTitle); // 获得的高亮字段替换之前普通标题的字段即可 } maps.add(sourceAsMap); } return maps; }
-
编写controller层
@RestController public class ContentController { @Autowired private ContentService contentService; @GetMapping("/parse/{keyword}") public boolean parse(@PathVariable String keyword) throws Exception { return contentService.parseContent(keyword); } @GetMapping("/search/{keyWord}/{pageNo}/{pageSize}") public List<Map<String, Object>> search(@PathVariable("keyWord") String keyWord, @PathVariable("pageNo") int pageNo, @PathVariable("pageSize") int pageSize) throws Exception { return contentService.searchPageHighLight(keyWord, pageNo, pageSize); } }
-
测试访问
数据查询完毕,与前端联调即可!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)