网站首页
es/mysql/mongodb/redis区别
关系型数据库: MySQL
关系型数据库是一种基于关系的数据库,而关系模型可经过二维表来进行表示,因此数据的存储方式是由行列组成的表,每一列是一个字段,每一行是一个记录。在关系型数据库中一般包含了三个概念:数据库(database)、表(table)、记录(record)。在大部分关系型数据库中,都是适用B+树做为索引,好比MySQL。php
MySQL也是一种硬盘型数据库,操做数据是IO级别的,它全部的数据都是存放在硬盘中,须要使用的时候才会交换到内存中。所以MySQL可以处理海量的数据,可是数据量很大的时,速度会稍慢。html
MySQL的使用须要提早建表,不适用于数据结构变换频繁的状况前端
非关系型数据库:MongoDB、Redis
MongoDB介绍
MongoDB是由c++语言编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,其内容存储相似JSON对象,它的字段能够包含其余的文档、数组以及文档数组。MongoDB包含了三个层次概念:数据库(database)、集合(collection)、文档(document)。MongoDB的数据索引是B-树。vue
MongoDB 在建立数据库的时候,会直接在磁盘上面分配一组数据文件,全部的集合、索引和数据库的其余元数据都保存在这些文件中。java
在使用MongoDB中,操做系统会经过mmap将进程所须要的全部数据都映射到虚拟内存中,而后在将当前须要处理的数据映射到内存中。当须要访问的数据不在虚拟内存的时候,会触发page fault,而后os就会硬盘中的数据加载到虚拟内存和内存中。而当内存已满时,会触发swap-out操做,将一些数据写回硬盘。因此有了这种内存映射文件的方法,就会有种好像全部须要访问的数据都在内存里同样。mysql
MongoDB的特色:c++
提供面向文档存储,操做简单web
扩展性强,第三方支持丰富面试
具备failover机制(失效转移:一种备份操做模式,当一个系统由于一些故障没法完成工做的时候,另外一个系统自动接替已失效系统的工做继续执行)redis
支持大容量存储,内置GridFS(可用于存放大量的小文件)
在高负载的状况下,能够添加更多的节点,保证服务器性能
缺点
无事务机制(数据库事务(database transaction)对单个的逻辑单元执行一系列的操做,要么彻底执行,要么彻底不执行)
占用空间过大
没有mysql那样成熟的维护工具
适用场景
适合那种数据格式不明确或者常常变化的模型,好比事件记录、内容管理或者博客平台。
Redis
Redis是一种内存数据库,全部的数据都是放在内存之中,按期写入磁盘中,当内存不够的时候,可选择指定的LRU算法删除数据。Redis是基于哈希字典创建的,所以其索引方式是哈希。
特色
因为数据存放在内存中,所以读写性能高
支持丰富的数据类型,如键值对、集合、列表、散列存储
elasticsearch
一、Elasticsearch和MongoDB/Redis/Memcache同样,是非关系型数据库。是一个接近实时的搜索平台,从索引这个文档到这个文档可以被搜索到只有一个轻微的延迟,企业应用定位:采用Restful API标准的可扩展和高可用的实时数据分析的全文搜索工具。
二、可拓展:支持一主多从且扩容简易,只要cluster.name一致且在同一个网络中就能自动加入当前集群;自己就是开源软件,也支持不少开源的第三方插件。
三、高可用:在一个集群的多个节点中进行分布式存储,索引支持shards和复制,即便部分节点down掉,也能自动进行数据恢复和主从切换。
三、采用RestfulAPI标准:经过http接口使用JSON格式进行操做数据。
四、数据存储的最小单位是文档,本质上是一个JSON 文本;
实际项目开发中,几乎每一个系统都会有一个搜索的功能,数据量少时能够直接从主数据库中好比Mysql搜索,但当搜索作到必定程度时,好比系统数据量上了10亿、100亿条的时候,传统的关系型数据库的I/O性能和统计分析性能就难以知足用户须要了。因此不少公司都会把搜索单独作成一个独立的模块,用ElasticSearch等来实现。虽然内存缓存数据库的读写性能很高,但彻底把数据放在内存中是不太现实的
需求:使用es作站内搜索
核心:怎么将mongodb中的数据添加到elasticsearch中,同步哪一些数据?
好比:搜索游记中title和summary中含有广州字样的游记,做为以广州为条件搜索的结果,首先要到mongodb中去把知足条件的数据找到显示出来。
从mongodb中同步条件列数据以及主键id到es中(推荐:由于内存资源宝贵,选择牺牲性能)
先匹配es中条件列搜索知足条件的数据,获得主键id集合,而后以id集合做为条件去mongodb中对应的id数据集合,以后再页面显示;
优势:节省内存空间(数据量小了);
缺点:稍微有损性能(去两个数据库中查询了);
从mongodb中同步页面须要的全部数据(包括条件列数据)以及主键id,把数据都放到es中存起来
先匹配es中条件列搜索知足条件的数据,获得数据集合,直接在页面显示;
优势:查询快(全部的数据都在es中了);
缺点:内存空间消耗大(数据量大了);
关键字搜索
进入首页后,输入关键字, 选择不一样搜索维度(默认是所有), 进入搜索页面
关键字搜索, 也称之站内搜索, 系统暂时仅对攻略, 游记, 目的地, 用户进行关键字查询, 固然也支持所有查询。
1:关键词搜索
所有搜索, 会对目的地, 攻略, 游记, 用户对象(关键字段)进行全文搜索
目的地:名称(name), 简介(info)
攻略:标题(title), 副标题(subTitle), 概要(summary)
游记:标题(title), 概要(summary)
用户:简介(info), 城市(city)
查询到的关键词进行高亮显示
添加依赖:
<!--elasticsearch--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>
es的配置:
es中数据的初始化
目的地:
search.domain
search.repository
search.service
攻略:
其余组件是同样的,拷贝替换就好;
游记:
其余组件是同样的,拷贝替换就好;
用户:
其余组件是同样的,拷贝替换就好;
初始化controller
public class DataController { //es相关服务 private IDestinationEsService destinationEsService; private IStrategyEsService strategyEsService; private ITravelEsService travelEsService; private IUserInfoEsService userInfoEsService; //mongodb相关服务 private IDestinationService destinationService; private IStrategyService strategyService; private ITravelService travelService; private IUserInfoService userInfoService; "/dataInit") ( public Object dataInit() { //攻略须要存到es中的数据初始化 List<Strategy> sts = strategyService.list(); for (Strategy st : sts) { StrategyEs es = new StrategyEs(); BeanUtils.copyProperties(st, es); strategyEsService.save(es); } //游记须要存到es中的数据初始化 List<Travel> ts = travelService.list(); for (Travel t : ts) { TravelEs es = new TravelEs(); BeanUtils.copyProperties(t, es); travelEsService.save(es); } //用户须要存到es中的数据初始化 List<UserInfo> uf = userInfoService.list(); for (UserInfo u : uf) { UserInfoEs es = new UserInfoEs(); BeanUtils.copyProperties(u, es); userInfoEsService.save(es); } //目的地须要存到es中的数据初始化 List<Destination> dests = destinationService.list(); for (Destination d : dests) { DestinationEs es = new DestinationEs(); BeanUtils.copyProperties(d, es); destinationEsService.save(es); } return "ok"; } }
查到数据放到es中:
同理可得,剩下的拷贝。
启动服务器:检查head中的数据是否按要求建立好了索引了:
索引信息必定要和配置的一致;
打开mongodb数据库:必需要保证全部的数据是合法合规的,把本身加的错误的坏的数据删了。
以后再进行数据的初始化:发出初始化数据的请求,刚刚设置的controller
查看初始化完成的数据是否正确:
关键字搜索
注意:目的地是精确搜索,无高亮显示,找不到就找不到;其余的是全文搜索,关键字高亮显示;
目的地关键词搜索
目的查询:输入关键词是精确查询输入的地区, 若是找到, 显示该目的地下全部攻略, 游记, 用户
若是目的找不到, 显示:
前端代码:
查看首页前台代码引用了rip-website\js\vue\common.js:
高查条件的封装,后面要用于分页,根据前台以int类型来区分集中不一样的搜索目标来设计qo:
全部的搜索请求都是同一个映射地址:
一个方法中完成不一样的搜索目的,如何区分?
怎么将这些不一样的搜索类型区分开:用switch语句
这样处理还有一个问题:不一样的搜索目标类型,请求的返回数据是不同的;
如何处理:由分支的方法本身来处理;
目的地关键词搜索:
system/search/searchDest.js
页面html:
trip-website\views\search\searchDest.html
显然result是键值对的存在,使用map仍是用对象(相似vo)封装,选择第二种;
后台:
JPA中定义的方法ByXxx()要去检查一下es中是否有Xxx属性,不然报错:
去哪个数据库查询数据给前台?由前台须要显示的数据来决定。es能不能知足页面全部的显示的数据。
其余的三个查询方式相同;
定义封装result数据的类型:
用result封装数据:
返回结果
测试查看查询的数据:
查不到数据:
get请求的时候:会将中文字符进行编码了,
后台须要解码,才能转换成中文:
再测试:
看页面少了引用:
报错:找不到用户昵称,查看数据有没有到后天,查看前台有没有按要求封装数据;
头像没了:打印后台传过来的数据,发现没有头像信息;
测试,0条的0没有显示:或者在SearchResultVO中设置total默认值为0;
攻略全文搜索:
仅仅对攻略进行全文搜索
攻略:标题(title), 副标题(subTitle), 概要(summary)
拷贝接口:
拷贝实现类:
修改BeanUtils
为何这么写:由于查询高亮的接口的定义,对好比下:
测试:
查看攻略查询结果正不正常,有没有高亮显示关键字;
攻略
游记
用户
所有
默认状况下查询所有显示:
数据的封装:
测试:
全文搜索方法设计的解释:
EQL语句全文检索:
方法设计:
根据上面的语句如何设计全文搜索的方法:这个方法中有重复的操做,怎么保证通用性呢?————使用泛型设计方法,方法的可变参数
/** * 全部 es 公共服务,全文搜索并高亮显示关键词 */ public interface ISearchService { /** * 全文搜索 + 高亮显示 * * @param index 索引 * @param type 类型 * @param clz 经过字节码对象告诉Page<T>中的 T 究竟是什么类型,传什么封装什么 * @param qo 高查条件(关键词等)都在qo中 * @param fields 字段:须要对哪些字段中的内容作关键词匹配,不一样的需求字段不同,可变参数可完美匹配 * @param <T> * @return 带有分页的全文搜索(高亮显示)结果集,返回的结果集用泛型来达到通用的目的 * <p> * <T> 泛型方法的语法:申明泛型,让java不去解析 T 具体是什么类型,不加就报没法解析的错。 */ <T> Page<T> searchWithHighlight(String index, String type, Class<T> clz, SearchQueryObject qo, String... fields); }
方法中须要作什么:
EQL语句查询到的响应结果:
怎么把结果解析成前台认识的页面:
高亮解析:
代码:
public class SearchServiceImpl implements ISearchService { private IUserInfoService userInfoService; private IStrategyService strategyService; private ITravelService travelService; private IDestinationService destinationService; private ElasticsearchTemplate template; //类比:select * from xxx where title like %#{keyword}% or subTitle like %#{keyword}% or summary like %#{keyword}% //关键字: keyword = 广州 //以title为例: //原始匹配: "有娃必看,广州长隆野生动物园全攻略" //高亮显示后:"有娃必看,<span style="color:red;">广州</span>长隆野生动物园全攻略" public <T> Page<T> searchWithHighlight(String index, String type, Class<T> clz, SearchQuery qo, String... fields) { String preTags = "<span style='color:red;'>"; String postTags = "</span>"; //须要进行高亮显示的字段对象, 他是一个数组, 个数由搜索的字段个数决定: fields 个数决定 //fields : title subTitle summary HighlightBuilder.Field[] fs = new HighlightBuilder.Field[fields.length]; for (int i = 0; i < fs.length; i++) { //最终查询结果: <span style="color:red;">广州</span> fs[i] = new HighlightBuilder.Field(fields[i]) .preTags(preTags) //拼接高亮显示关键字的开始的样式 <span style="color:red;"> .postTags(postTags);//拼接高亮显示关键字的结束的样式 </span> } NativeSearchQueryBuilder searchQuery = new NativeSearchQueryBuilder(); searchQuery.withIndices(index) //设置搜索索引 .withTypes(type); // 设置搜索类型 /*"query":{ "multi_match": { "query": "广州", "fields": ["title","subTitle","summary"] } },*/ searchQuery.withQuery(QueryBuilders.multiMatchQuery(qo.getKeyword(), fields)); //拼接查询条件 /** "from": 0, "size":3, */ searchQuery.withPageable(qo.getPageable()); //分页操做 //高亮显示 /** "highlight": { "fields" : { "title" : {}, "subTitle" : {}, "summary" : {} } } */ searchQuery.withHighlightFields(fs); //List<UserInfoEs> es = template.queryForList(searchQuery.build(), UserInfoEs.class); //调用template.queryForPage 实现结果处理 //参数1:DSL语句封装对象 //参数2:返回Page对象中list的泛型 //参数3:SearchResultMapper 全文搜索返回的结果处理对象 // 功能: 将DSL语句执行结果处理成Page 分页对象 return template.queryForPage(searchQuery.build(), clz, new SearchResultMapper() { ///mapResults 真正处理DSL语句返回结果 方法 //参数1: DSL语句查询结果 //参数2: 最终处理完以后, 返回Page对象中list的泛型 //参数3: 分页对象 public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { List<T> list = new ArrayList<>(); SearchHits hits = response.getHits(); //结果对象中hist 里面包含全文搜索结果集 SearchHit[] searchHits = hits.getHits();//结果对象中hist的hit 里面包含全文搜索结果集 for (SearchHit searchHit : searchHits) { T t = mapSearchHit(searchHit, clazz); //必须使用拥有高亮显示的效果字段替换原先的数据 //参数1: 原始对象(字段中没有高亮显示) //参数2:具备高亮显示效果字段, 他是一个map集合, key: 高亮显示字段名, value: 高亮显示字段值对象 //参数3:须要替换全部字段 Map<String, String> map = highlightFieldsCopy(searchHit.getHighlightFields(), fields); //BeanUtils.copyProperties(map, t); /*两个不一样包下BeanUtils工具类的区别: 1.springboot 框架中的BeanUtils类,若是参数是map集合,将没法进行属性的复制 copyProperties(源, 目标); 2.Apache 的BeanUtils类,能够对map进行属性的复制 copyProperties(目标, 源); */ try { BeanUtils.copyProperties(t, map); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } list.add(t); } //将结果集封装成分页对象Page : 参数1:查询数据, 参数2:分页数据, 参数3:查询到总记录数 AggregatedPage<T> result = new AggregatedPageImpl<>(list, pageable, response.getHits().getTotalHits()); return result; } public <T> T mapSearchHit(SearchHit searchHit, Class<T> clz) { String id = searchHit.getSourceAsMap().get("id").toString(); T t = null; if (clz == UserInfo.class) { t = (T) userInfoService.get(id); } else if (clz == Travel.class) { t = (T) travelService.get(id); } else if (clz == Strategy.class) { t = (T) strategyService.get(id); } else if (clz == Destination.class) { t = (T) destinationService.get(id); } else { t = null; } return t; } }); } //fields: title subTitle summary private Map<String, String> highlightFieldsCopy(Map<String, HighlightField> map, String... fields) { Map<String, String> mm = new HashMap<>(); //title: "<em>广州</em>小吃名店红黑榜:你仍是当年珠江畔那个老字号吗?" //subTitle: "<em>广州</em>小吃名店红黑榜" //summary: "企鹅吃喝指南|“城市指南“第4站-<em>广州</em> 小吃篇" //title subTitle summary for (String field : fields) { HighlightField hf = map.get(field); if (hf != null) { //获取高亮显示字段值, 由于是一个数组, 全部使用string拼接 Text[] fragments = hf.fragments(); String str = ""; for (Text text : fragments) { str += text; } mm.put(field, str); //使用map对象将全部能替换字段先缓存, 后续统一替换 //BeanUtils.setProperty(t,field, str); 识别一个替换一个 } } return mm; } }
小伙砸,欢迎再看分享给其余小伙伴!共同进步!
本文分享自微信公众号 - java学途(javaxty)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。