目录
中间件部分
Redis部分
讲一下你理解的Redis,为什么Redis很快
你常用的Redis的数据存储结构有哪些,他们的使用场景分别是什么
Redis每种存储结构说 4 个命令吧
你一般是怎么用Redis的?
怎么防止Redis宕机数据丢失问题
Redis持久化是什么?有几种方式
Redis内存不够了怎么办?
你们Redis用在哪些业务上?用的什么存储结构?
Redis淘汰策略有哪些?你用的哪种?
Redis事务和Mysql事务的区别
使用Redis如何实现消息广播
为什么要使用Redis做缓存
你知道缓存的执行流程吗?
如何保证Redis和Mysql的一致性?
SpringCache常用注解
了解缓存击穿,穿透,雪崩吗?怎么处理?
Redis的主从有什么优点,和缺点?
解释一下Redis的哨兵模式,这种模式有什么不足?
Redis的cluster集群怎么存储数据的?
什么情况下Redis集群不可用?
Redis存储结构底层有没有了解?什么是SDS
Redis如何模拟队列和栈,用什么命令?
Redis存储单个对象怎么存,存储对象集合怎么存?
你们Redis用来做什么?使用的什么结构?
RabbitMQ
RabbitMQ的使用场景
RabbitMQ如何防止消息丢失
RabbitMQ的交换机有哪几种
消息是如何从发送者到达消费者的(RabbitMQ工作流程)
如何防止消息重复消费
RabbitMQ消息投递失败,你们怎么处理
ElasticSearch
Lucene创建索引原理
ES的keyword和text区别
ES的优势:
Lucene/ES为什么那么快(ES用到什么数据结构)
ES的分层结构,index下面是什么
你知道ES中的查询对象吗?比如TermQuery
简单描述一下DSL语法
说一下 match和term的区别?
你使用过ES的哪些聚合查询?
ES高亮怎么做的?
你们ES和数据库的数据一致性怎么做的
了解ES分片机制吗
描述一下ES添加文档的过程
数据节点存储数据详细流程:
详细描述一下ES获取文档的过程
详细描述一下Elasticsearch搜索过程
详细描述一下Elasticsearch更新和删除文档的过程
ES有几种节点类型?他们的作用分别是什么
ES集群的三种颜色代表什么
Redis是一种高性能的,开源的,C语言编写的非关系型数据库,可以对关系型数据库起到补充作用,同时支持持久化,可以将数据同步保存到磁盘
说Redis很快是相对于关系型数据库如mysql来说的,主要有以下因素
第一,数据结构简单,所以速度快
第二,直接在内存中读写数据,所以速度快
第三,采用多路IO复用模型,减少网络IO的时间消耗,避免大量的无用操作,所以速度快
第四,单线程避免了线程切换和上下文切换产生的消耗,所以速度快
Redis存储形式是键值对,支持value形式包括String,List,Set,ZSet,Hash。
String可以用作缓存,计数器,防攻击,验证码、登录过期等,List可以用来做队列,秒杀等,Set可以用来去重
1.String
set key value 设置值
get key 取值
mset key value key value... 设置多个值
mget key key 获取多个值
incr key 将key中的值自增1
decre key 将key中的值自减1
2.List
lpush key value value... 从最左边设置值
rpush key value value... 从最右边设置值
lrange key start stop 查询key中指定区间的元素
lpop key 移出并返回key中最左边的元素
rpop key 移出并返回key中最右边的元素
3.Set
sadd key value value 添加元素
smembers key 返回集合key中的所有元素
srem key member 删除集合key中member元素
scard key 查询集合key中的元素数量
4.ZSet
zadd key score value (score value)... 添加元素
zcard key 查询集合key中元素数量
zcount key min max 返回有序集合key中score 在min和max之间的元素
zrange key start stop 返回有序集合key中索引在start和stop之间的元素
5.Hash
hset key field value 添加元素
hget key field 获取key集合中field键对应的值
hmset key field value (field value)... 添加元素并批量添加子键值对
hmget key field field 获取key集合中所有的子键值对
使用的是Springboot整合的redis,主要用来解决前后端分离后前后端会话问题,以及验证码的问题
通过对Redis持久化,把内存中的数据和命令,保存一份到磁盘中做备份,当Redis发生宕机,重启服务器的时候,会从磁盘重新加载备份的数据,从而解决数据丢失问题
将内存中的数据备份到磁盘的过程,就叫作持久化
Redis持久化主要有两种方式,RDB和AOF,可以通过修改redis.conf进行配置
RDB是记录数据快照,而AOF是记录写命令的
Redis有了AOF持久化为什么还要RDB?
AOF和RDB各有所长
RDB是记录数据快照,它的优点是只产生一个持久化文件,体积相对较小,启动恢复速度快,备份方便,它的缺点是没办法做到数据百分百不丢失,因为它是每隔一定时间保存一次
AOF是记录写命令,它的优点是格式清晰,容易理解,数据更安全,采用append模式即使持久化过程中宕机,也不影响已经保存的数据,它的缺点是文件体积较大,恢复速度慢
根据实际需要来选择,通常二者可以结合来使用
方式一:增加物理内存
方式二:使用淘汰策略,删掉一些老旧数据
方式三:集群
主要用做缓存,比如:验证码,分类缓存,数据字典缓存,权限数据缓存,登录信息缓存等。
String类型的存储结构用的比较多,并且使用了Json格式进行序列化。
volatile-lru :从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
allkeys-random:从数据集中任意选择数据淘汰
no-enviction:不使用淘汰
Mysql的事务是基于日志,记录修改数据前后的状态来实现的,而Redis的事务是基于队列实现的
Mysql中的事务满足原子性:即一组操作要么同时成功,要么同时失败,
Redis中的事务不满足原子性,即一组操作中某些命令执行失败了,其他操作不会回滚
因此对于比较重要的数据,应该存放在mysql中
Redis是使用发布订阅来实现广播的
订阅者通过 SUBSCRIBE channel命令订阅某个频道 , 发布者通过 PUBLISH channel message向该频道发布消息,该频道的所有订阅者都可以收到消息
因为他速度快。
缓存它指的是将数据库的数据同步到内存中,客户端获取数据直接从内存中获取。由于内存读写速度大于磁盘,而使用缓存能减少磁盘读取,大大提高查询性能。
我们一般会将经常查询的,不会经常改变的热点数据,保存到缓存中,提高响应速度
1.客户端发起查询请求
2.判断缓存中是否有数据
如果有,直接返回
如果没有,就从数据库查询,再把数据同步到缓存
3.返回数据给客户端
我们在代码中控制,如果数据库做是写操作,直接把redis中的对应数据删除,下次查询数据会重新写入缓存。
我们的业务对一致性要求不是很高,因此采用了先操作mysql,后删除redis。在写数据库和删除缓存行代码之间如果有查询请求依然会查询到Redis中的老数据,但是这种情况非常极端,而且我们的业务也能容忍这种短暂的脏数据。
我还知道其他方案,比如延迟双删 , 监听Mysql事务日志自动同步Redis等。
@EnableCaching:打在主启动类上,开启缓存功能
@Cacheable:打在方法上,表示该方法会开启缓存,打在类上,表示类中所有的方法都开启缓存,方法的返回值会自动写入缓存。如果缓存中已经有数据,方法将不会被调用,而是拿着缓存数据直接返回给客户端。
@CacheEvict:搭载类或者方法上,会将缓存清除
@CachePut:更新缓存
@Caching:组合操作,要应用于方法的多个缓存操作
@CacheConfig:打在类上,共享的一些常见缓存设置
缓存击穿:缓存中没有,数据库中有的数据,由于某种原因比如缓存过期了,同时并发用户特别多,一时间都往数据库中读取数据
解决方案:加互斥锁,只能允许一个线程访问数据库,然后其他线程就可以往内存中拿
缓存穿透:客户端频繁请求一个缓存和数据库中都没有数据,导致数据库压力大。
解决方案:布隆过滤器来判断数据库中有没有这个key
缓存雪崩:缓存重启,或者大量key失效,导致大量并发打到数据库
解决方案:为key设置不同的过期时间
优点是读写分离,分担了读的压力,同时能起到备份作用,防止数据丢失
缺点是不能分担写的压力,主的单点故障没有解决,存储没有得到扩容
当主服务器中断服务后,可以将一个从服务器升级为主服务器 ,以便继续提供服务
哨兵就是用来监控主从服务器,实现故障恢复功能的。它会不断的检查主服务器和从服务器的健康状态,当某个服务器出现问题时,可以向管理员发起通知。如果主服务器不可用时,会自动选择一个从服务器作为新的主服务器,并让其他的从服务器从新的主服务器复制数据
哨兵也是主从模式,没有解决写的压力,只减轻了读的压力,而且存储也得不到扩容
Redis Cluster集群采用哈希槽 (hash slot)的方式来分配的。它默认分配了16384个槽位,当我们set一个key 时,会用CRC16算法得到所属的槽位,然后将这个key 分到对应区间的节点上
Redis Cluster有一个容错机制,如果半数以上的主节点与故障节点通信都超时了,就会认为该节点故障了,自动触发故障转移操作,故障节点对应的从节点升级为主节点。
但是如果某个主节点挂了,又没有从节点可以使用,那么整个Redis集群就不可用了、
简单动态字符串,是Redis自己封装的字符串结构。它记录了字节数组buf,字节数组中用到的字节数len,以及未使用的字节数free。
为了解决二进制安全问题,定义了len来表示已有字符串长度
为了防止缓冲区溢出,在分配内存的时候做了预留空间free
内存惰性释放,多余的内存加入free做预留,优化了内存频繁分配
针对不同的String长度定制了不同的SDS结构
list控制同一边进,同一边出就是栈;
list控制一边进,另一边出就是队列
单个对象可以使用String,也可以使用hash
集合对象可以使用hash,以便可以快速的通过field来取值
登录信息login,使用的是String结构存储
手机验证码code,使用的是String结构
课程分类course_type ,使用的是String结构
购物车保存,使用的是Hash结构
rabbitMQ消息队列可以用来:
做任务的异步处理,提高程序的相应时间
提高系统稳定性,通过手动确认机制,当消费者出现故障,只要没有确认签收,请求的数据都不会丢失可以再次处理
服务解耦,生产者通过MQ与消费者交互
消除峰值,通过异步处理,消息传到MQ直接返回,接着等待排队处理,避免了线路拥堵
首先,RabbitMQ的消息确认机制,默认是自动签收,也就是说消息一旦被消费者接收,就自动签收,消息就从队列里清除了。因此对于重要的消息,不容丢失的数据,我们需要设置在消费完成后手动签收
其次,我们可以将消息持久化,避免消息在消费前MQ宕机,网络问题等造成的消息丢失
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合指定routing key的队列
Topic:通配符,把消息交给符合routing pattern的队列
分为消息发送和消息接收两个步骤
消息发送:生产者和Broker建立TCP连接,创建信道。通过信道将消息发送给Broker,由Exchange将消息进行转发到指定的队列
消息接收:消费者和Broker建立TCP连接 ,创建信道 ,然后监听指定的队列,当有消息到达队列时,Broker默认将消息推送给消费者,消费者就能接收到消息
重复消费,一般时由于消费者消费成功后,在给MQ确认签收的时候出现了网络波动,MQ没有接到确认,就会继续给消费者投递之前的消息,造成消费者接收到了两条一样的消息。
我们可以通过实现消息的幂等性来避免这种情况,比如说让生产者给每个消息携带一个唯一的id,消费者获取消息后根据这个id去查询数据库,如果不存在就正常消费,如果存在了就证明该消息被消费过,直接丢弃
我们可以设置confirm回调和 returned 回调
比如说,可以在发送消息的时候,把消息详情包括交换机名,路由键,都保存到一个表中,状态设置为发送中,如果在confirm方法中ack为false,代表发送到交换机失败 ,就把这个记录状态修改为发送失败
然后我们创建一个定时任务定时扫表,去读取发送失败的数据并重新发送,为了优化性能,我们设置重试次数3次,如果3次都失败了,我们可以采取人工干预
Lucene是基于倒排索引原理来实现的
首先,将原文档进行分词处理,形成一个个单独的单词,
然后取出标点符号以及停词,形成词元,
再将词元做一些语言相关的处理,比如变成小写,转换时态,单复数形式等等,
将得到的词创建一个字典,按照字母顺序排序,合并相同的词,最终生成一个倒排索引文档
keyword:不分词,直接建立索引,支持模糊查询,精确查询,聚合查询
text:分词后建立索引,支持模糊查询,精确查询,不支持聚合查询
keyword通常用于通常用于存储年龄,性别,邮编,邮箱号码等等,直接将完整数据保存的场景
text通常存储全文搜索的数据,例如地址,文章内容的保存
ES是基于Lucene的开源搜索引擎,它解决了原生Lucene使用的不足,优化了Lucene的调用方式
分布式的实时文件存储,每个字段都被索引并可被搜索
支持实时分析搜索
可以扩展到上百台服务器,处理PB级结构化或非结构化数据
通过简单的 RESTful API、可以跟各种语言的客户端甚至命令行进行交互
上手非常容易,只需很少的学习就可以在生产环境中使用
传统搜索比如mysql的like关键字查询,它的搜索方式就是全文扫表,查询性能很低
ES是基于Lucene的全文检索引擎,它采用的是倒排索引结构,在存储时先对文档进行分词,再做一些标点符号去除,大小写时态转换等优化处理,最后按照字母顺序去重排序,形成一个倒排索引文档,我们在检索时,就可以通过二分查找的方式找到目标值
Index:索引库,包含有一堆相似结构的文档数据,类比Mysql中的数据库
Type:类型,它是index中的一个逻辑数据分类,类比Mysql中的表
Document:文档:是ES中的最小数据单元,通常用json结构标识,类比Mysql中的一行数据
Field:字段:类比Mysql中的一个列
从ES7.0开始,Type被干掉了,从此库表合一即一个Index中只有一个默认的Type
TermQuery:匹配关键字查询(关键词不分词)
MatchQuery:匹配关键字查询(关键字分词后)
BooleanQuery:按条件查询
matchAllQuery:匹配所有文档查询
rangeQuery:查询指定范围内的数据
DSL是一种以json形式标识的,由ES提供的一种查询语言,它由两部分组成,DSL查询和DSL过滤。
DSL过滤类似于模糊查询,DSL查询类似于精确查询
term:不会对搜索词进行分词处理,而是作为一个整体与目标字段进行匹配,若完全匹配,则可查询到
match:会将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到
指标聚合,比如求和,求最大值,最小值,平均数
数量统计聚合,计算满足条件数据的总条数,相当于sql中的count
去重聚合,它会计算非重复的数据个数,相当于sql中的distinct
桶聚合,它会将某个field的每个唯一值当成一个桶,并计算每个桶内的文档个数,相当于sql中的group by
最高权值聚合,它会匹配每组前n条数据,相当于sql中的group by后取出前n条
使用HighlightBuilder对关键字作高亮处理,由于我们项目使用的是SpringBoot整合ES的jar包,结果没有进行高亮处理,我们使用ElasticsearchTemplate的queryForPage方法来获取结果,再手动进行分页封装返回前台
代码控制的,数据库做了写操作,直接更新ES中的数据,我知道可以通过 Logstash 中数据和ES的数据自动同步。
ES的索引库由多个分片 shard组成,shard分为primary shard主shad和replica shard 副本,主shard承担写请求,replica副本的数据从primary复制而来,同时分担读请求,primary shard的数量设定了就不能修改,replica数量可以修改。
(1) 客户端请求一个协调节点coordinating node
(2) 协调节点根据算法选择一个primary shard: 算法 hash(document_id) % (num_of_primary_shards)
(3) 对应的primary shard 所在节点保存完数据后,将数据同步到replica node。
(4) 协调节点coordinating node 发现 primary node 和所有 replica node 都搞定之后返回结果给客户端
(1) 当分片所在的节点接收到来自协调节点的请求后,会将请求写入到Memory Buffer,然后定时(默认是每隔1秒)写入到Filesystem Cache,这个从Momery Buffer到Filesystem Cache的过程就叫做refresh
(2) 当然在某些情况下,存在Momery Buffer和Filesystem Cache的数据可能会丢失,ES是通过translog的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到translog中,当Filesystem cache中的数据写入到磁盘中时,才会清除掉,这个过程叫做flush;
(3)在flush过程中,内存中的缓冲将被清除,内容被写入一个新段,段的fsync将创建一个新的提交点,并将内容刷新到磁盘,旧的translog将被删除并开始一个新的translog。 flush触发的时机是定时触发(默认30分钟)或者translog变得太大(默认为512M)时;
(1) 客户端请求一个协调节点coordinating node
(2) coordinate node 根据算法hash(document_id) % (num_of_primary_shards),将请求转发到对应的 node,此时会使用 round-robin随机轮询算法,在 primary shard 以及其所有 replica 中随机选择一个,让读请求负载均衡
(3) 接收到请求的 node 返回 document 给调节点 coordinate node。
(4) coordinate node 返回 document 给客户端。
搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;
(1) 在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。
(2) 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询Filesystem Cache的,但是有部分数据还在Memory Buffer,所以搜索是近实时的。
(3) 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,协调节点它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
(4) 接下来就是 取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。
删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更; 磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。
在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
分为主节点,node.master =true , 数据节点node.data =true , 负载均衡节点(node.data =false,node.master=false),
node.master=true,代表该节点有成为主资格 ,主节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。一般会把主节点和数据节点分开
node.data=true,数据节点主要是存储索引数据的节点,主要对文档进行增删改查操作,聚合操作等,数据节点对CPU,IO,内存要求较高,优化节点的时候需要做状态监控,资源不够时要做节点扩充
当主节点和数据节点配置都设置为false的时候,该节点只能处理路由请求,处理搜索,分发索引操作等,从本质上来说该客户节点表现为智能负载平衡器。配置:mode.master=false,mode.data=false
绿色,黄色,红色,绿色代表集群健康,所有的主备分片都得到分配,如果有备分片没有node去分配,集群是黄色,黄色和绿色都是可用状态,如果有主分片的节点down机,集群不可写数据,呈现红色,代表集群不健康。