目录
14. 事务的隔离级别?怎么实现的(加了什么锁)?MySQL默认的隔离级别是什么?
22. count(*)、count(1)、count(列名)有什么区别?
23. 频繁的增删数据量某个表,数据库最终数据只有几万或者更少,为什么查询会变慢
7. 有2000万mysql数据,redis只能存储20w数据,怎么保证redis存储的都是热点数据?
11. redis集群模式(redis cluster)的工作原理知道吗?
15. 假设redis中有一亿数据,有10w个前缀都是一样的,如何把他们找出来?
2. MySQL和MongoDB和redis的特点、区别、使用场景?
一. MySQL
1. 数据库三大范式是什么?
- 第一范式:每一列都不可拆分;
- 第二范式:在第一范式基础上,非主键完全依赖主键,而非依赖主键的一部分;
- 第三范式:在第二范式基础上,非主键只依赖主键,不依赖其他非主键。
2. MyISAM和InnoDB存储引擎的区别?
- 从功能上来说,InnoDB支持事务和行级锁,MyISAM不支持事务和行级锁,只支持表级锁;
- 从索引来说,InnoDB的索引是聚簇索引,MyISAM是非聚簇索引。InnoDB主键索引的叶子节点存放的是行数据,因此用主键查询非常快;非主键索引的叶子节点存放的是主键索引和其他非主键索引,因此索引覆盖查询非常快。MyISAM索引的叶子节点存放的是行数据地址。
什么是存储引擎?
存储引擎定义了如何存储数据、如何为存储的数据建立索引、如何查询更新数据等技术的查询方法。
如何选择存储引擎?
- 当插入和查询比较多时,且对事务,并发性要求不高时使用MyISAM,因为MyISAM查询比较快。比如博客系统,新闻门户网站。
- 当需要用到事务,行级锁时,都是用InnoDB存储引擎。
为什么MyISAM查询比InnoDB快?
从功能上来说,InnoDB支持事务,会有一个MVVC(多版本并发控制)比较,会消耗性能,以及行级锁也会有性能开销。
从索引上来说,InnoDB的非主键索引查询数据会有一个回表的过程。先从非主键索引查到主键索引,然后再定位到行数据。而MyISAM直接能用索引定位到行数据。
3. 什么是MVCC?有什么作用?怎么实现的?
详情讲解链接:华为云讲解MVCC
MVCC(Multi-Version Concurrency Control)是多版本并发控制,主要是为了实现读-写并发下的安全读。它是一种除了加锁之外的并发控制方案,能够代替行级锁,并发控制时系统性能消耗更少。
当我们执行普通的select语句,如果发生了读写并发,mysql如何保证安全读数据呢?当然可以加锁,但是mysql使用了更高性能的方案,就是MVCC。MVCC是用两个隐藏的列和undoLog实现的。两个隐藏的列分别是 当前事务的版本号 和 回滚指针(指向undolog中的版本记录)。
MVCC多版本控制的过程:
事务执行更新操作时,会先给行数据加排它锁,然后把当前数据快照存在undolog中,拷贝完毕后,再修改行数据,并把回滚指针指向undolog中的版本记录。事务执行期间只需要读undolog中的版本记录即可。
undolog:保存了事务发生前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读。主要用于回滚和MVCC的快照读。
redolog:主要用于故障恢复。数据库操作数据的时候会先写入缓存,然后再写入数据库。但是如果写入缓存成功,但是数据库宕机,数据就丢失了。因此有了redolog,每次写入缓存后必须在redolog中记录该操作(对 XXX表空间中的XXX数据页XXX偏移量的地方做了XXX更新),这样即使数据库宕机,等数据库恢复后,仍然能把数据从redolog刷盘到数据库。主要用于数据恢复刷盘
快照读和当前读
- 快照读:普通的select * from table就是快照读,利用MVCC保存的undolog快照实现读写并发下的安全读
- 当前读:读取最新数据,会对读取的数据加锁
SELECT * FROM xx_table LOCK IN SHARE MODE; //共享锁 SELECT * FROM xx_table FOR UPDATE; //排他锁 INSERT INTO xx_table ... //排他锁 DELETE FROM xx_table ... //排他锁 UPDATE xx_table ... //排他锁
4. 什么字段适合作为索引?
频繁使用的字段、区分度高的字段、外键
5. 索引的结构有哪些?
Hash、BTree(Blance Tree)、B+Tree。在MySQL的InnoDB存储引擎中,主要使用的是B+树。
B+树讲解:什么是B+树_石楠烟斗的雾的博客-CSDN博客
6. 为什么数据库主要使用B+树?B树和B+树有什么区别?
- B树的每个节点都存储卫星数据,而B+树中间节点只存储索引,所以一个磁盘也可以存更多的节点,就能以更少的IO次数遍历完整个数。叶子节点包含了全量的元素,并存储了卫星数据(索引指向的数据记录,比如数据库的某一行)
- B+树的叶子节点形成了有序链表,每个节点都有指向下一个节点的指针,范围查询更方便。但B树范围查询就要遍历整个树。
- B+树每次查询都查询到叶子节点,更稳定。
磁盘页:假设一次IO能读8K的数据,那么8K就是一个磁盘页
需要指出的是:聚簇索引的叶子节点存储了卫星数据,非聚簇索引存储的是指向卫星数据的指针。
B树:
B+树:
7. hash存储结构和B+树存储结构有什么优劣?
- hash存储底层是Hash表,等值查询比较快,但无法进行范围查询,因为用hash算法构建的索引不能和原顺序保持一致;B+树叶子节点的链表天然有序,因此支持范围查询。
- hash索引在某个键大量重复时,会出现hash冲突,此时效率极差;B+树都是从根节点到叶子节点,查询效率稳定。
因此一般情况下,用B+树就能获得稳定快速的查询
8. B+树的具体实现是什么样的?
- 每个中间节点都只存储索引,叶子节点包含全量的元素,并且是天然有序的双向链表
- 主键索引的B+树结构: 叶子节点存储的是卫星数据; 非主键索引的B+树结构: 叶子节点存储的是主键索引和其他非主键索引(再通过主键索引回表查询到卫星数据)。
9. 联合索引在B+树中怎么存储?
联合索引主要是用索引第一列构建的树,因此第一列是有序的,其他列是无序的。但是当第一列确定时,其他列相对有序。最左匹配原则也由此而来。
10. 最左匹配原则
以最左边为起点,连续的索引都能匹配上,遇到范围查询(> < between like)就会终止。
例如:建立联合索引(a,b,c,d),where a=1 and b=1 and c>1 and d=1 则d用不到索引,因为c是范围查询。
最左匹配原则原理:
索引的底层是B+树,联合索引也是。但是联合索引是用最左边的字段来构建B+树,因此最左边的字段是有序的,后边的字段都是无序的。但是当最左边字段确定时,后边字段相对有序。
11. 百万级的数据怎么删除
删除数据需要的时间和索引数量成正比,因为维护索引需要成本。
因此删除百万级数据的时候,应该先删除索引,然后删除不用的数据,最后重新建立索引。这样绝对比直接删除快的多。
12. 什么是数据库事务?事务的四大特性ACID介绍一下
事务是一组操作,要么都执行,要么都不执行。
- 原子性:要么都执行,要么都不执行
- 一致性:数据前后一致,比如转账操作,转账前两个账户共2000元,转完后也应是2000元
- 隔离性:各个事务之间互不影响
- 持久性:事务一旦提交,改变就是永久性的
13. 什么是脏读、不可重复读、幻读?
- 脏读:读到了未提交的数据
- 不可重复读:一个事务中,前后两次读到的数据不一致
- 幻读:一个事务中,前后两次读到的行数不一致
14. 事务的隔离级别?怎么实现的(加了什么锁)?MySQL默认的隔离级别是什么?
- 读未提交:可以读没有提交的数据。没有加锁。
- 读已提交:可以读已经提交的数据,解决了脏读。使用了MVCC,在事务执行过程中,每次读操作都会创建一个数据快照,并读取这个数据快照。
- 可重复读:一个事务前后两次读到的数据必然相同,解决了不可重复读。使用了MVCC,在事务执行时生成一个数据快照,整个事务期间都读取这个数据快照。
- 可串行化:最高隔离级别,各个事务之间依次执行,不可能相互影响,解决了幻读。事务中读操作的时候给整个范围内的数据都加共享锁,其他事务只能读不能写;写操作的时候加排他锁,其他事务不能读也不能写,事务执行完毕才释放锁。(间隙锁)
MySQL默认的隔离级别是 可重复读;Oracle是 读已提交。
15. 数据库的行锁和表锁是怎么实现的?
行锁是对索引进行加锁,没有索引不能用行锁,会升级到表锁。并且如果两条数据的索引相同,另一条数据也会被锁住。
例如:select * from table where id = 1 for update;如果id是索引,则加的是行锁;如果不是,则加的是表锁。
行锁和表锁如何使用?
行锁:
数据库会默认给UPDATE、DELETE、INSERT加排它锁,SELECT语句不会加任何锁,但是用户可以通过如下语句自行加锁
- 加共享锁:select * from table where xxx LOCK IN SHARE MODE;
- 加排它锁:select * from table where xxx FOR UPDATE;
表锁:
表锁必须手动释放,但是在事务提交前不要释放锁,因为UNLOCK TABLE隐含提交事务。如果要对t1写,对t2读,则按如下操作:
LOCK TABLES t1 WRITE, t2 READ; dosomething...... COMMIT; UNLOCK TABLES;
间隙锁
间隙锁就是对数据之间的间隙加锁,用于多个事务时阻止其他事务往已存在的数据行间隙中插入输入
SELECT * FROM employees WHERE age BETWEEN 30 AND 40 FOR UPDATE;
16. 如何定位sql语句的性能问题?如何解决?
定位性能问题用explain命令查看执行计划。有几个关键的字段:
- possible_keys:可能会用到的索引
- key:用到的索引
- type:访问类型(如:ALL-全表扫描、index-遍历索引、ref-使用非唯一索引查询、range-索引范围查询、fulltext-全文索引)
- rows:估算的结果集数目
- Extra:额外信息(如:Using index-使用索引覆盖、Using where-使用where、Using filesort-使用文件排序、Using temporary-使用临时表)
优化:
- 看是否加载了额外的行或列,尽量缩小查询范围
- 看是否能调整sql命中索引,或添加索引
- 如果是长难sql,看是否能拆开查询,然后在内存中聚合处理
17. 超大分页怎么处理?
比如:select * from table where age>20 limit 1000000,10; 这样相当于把前一百万条数据抛弃了,取了后续10条,这样非常慢。
【优化方案一】数据库角度:
- 如果有自增长的id,则可以优化为:select * from table where id>1000000 limit 10; 记录上一次查询的最大id,下一次查询直接从该id开始
- 如果没有自增长id,可以用子查询索引覆盖:select * from table where id in ( select id from table where age > 20 limit 1000000,10); 子查询索引覆盖,会快很多。
【优化方案二】需求角度:尽量不做这样的需求,问业务方这是强需求吗?是什么样的场景导致一定要这样做?是否可以限制用户的查询轨迹,只允许逐页查看之类的。
18. 怎么排查慢sql?对于慢sql怎么优化?
可以登录mysql配置开启慢查询日志,当有慢查询时,慢查询日志就会记录该sql。但是一般这个功能都是公司DBA做的,业务方可以在平台上查看慢sql耗时。
配置开启慢查询日志:set slow_query_log = on
配置临界时间:set long_query_time = 0.5
慢sql优化:
- 看是否加载了额外的行或列,尽量缩小查询范围
- 看是否能调整sql命中索引,或添加索引
- 如果是长难sql,看是否能拆开查询,然后在内存中聚合处理
LEFT JOIN优化
- 用大表去关联小表,也就是小表left join大表
- 关联字段建立索引
- 把关联查询拆成单个查询,然后在内存中做聚合
//原sql
select user.uid, userinfo.uname from user
left join userinfo on user.uid=userinfo.uid
where user.country='c'
//拆成
select uid,uname from user where country='c'
select uid,add from userinfo where uid in(32,34,23,23)
19. MySQL数据库CPU飙升到500%怎么处理?
看一下有没有慢sql,用explain命令分析并优化慢sql
20. 千万数据的表,CRUD特别慢,怎么优化?
- 缩小查询范围(行和列),比如只查近一个月的订单
- 命中索引
- 把热点数据放在缓存中
- 分库分表
【分库分表】
垂直分表:把表按照某个维度(比如使用频率)垂直拆分开
- 优点:一个数据页能存储更多的数据,能减少IO次数
- 缺点:需要管理冗余列,有时候需要联表查询
水平分表:当数据超过200万行时,CRUD就会很慢,这时就需要用某种策略将数据水平分片
- 优点:能够支撑大量的数据
- 缺点:分布式事务难以处理、跨库的join、跨库的count orderby groupby等函数不好使用。后两个问题可以通过在应用程序中进行拼装来解决
21. 数据库是怎么进行主从复制的?
- 主库把数据更改记录到binlog中
- 通过IO把binlog传输到从库
- 从库重新执行binlog中的语句就能实现主从复制了
读写分离造成数据不一致怎么处理?
如果是对强一致性要求不高的业务,那在主从同步期间读到旧数据也没有关系。如果是一致性要求很高的业务,有三种方案:
(1)数据库的主从同步方案一般是异步,我们可以将其改为同步,主从同步完成,主库的写才能返回。缺点是写请求延迟会增加,吞吐量会降低。一般在线业务都无法接受。
(2)对于强一致的场景,我们可以将其读写请求都发往主库,就没有不一致的情况了
(3)缓存路由大发:写请求路由到主库,同时缓存记录操作的key(比如主键),缓存的失效时间设置为主从的延时;读请求先判读缓存是否存在,存在则读主库,否则读从库。
22. count(*)、count(1)、count(列名)有什么区别?
执行结果上:count(*)、count(1)没有区别,count(列名)不统计为null的记录。
执行效果上:mysql会自动优化count(*),因此当有主键的时候count(*)比count(1)快;没有主键的时候,count(1)比count(*)快。如果列名是主键,则count(列名)最快。
23. 频繁的增删数据量某个表,数据库最终数据只有几万或者更少,为什么查询会变慢
原因是该表的空间大了,查询起来很慢。解决的方法是把该表所占用的表空间缩小,或者说释放表空间。
alter table XXXX move; 这样处理后就释放了表空间了。
但是释放表空间后,表的行号rowid会发生变化,而基于rowid的索引则会变成无效。因此该操作后必须重建索引。
否则会 提示“ORA-01502: 索引'SMP.ITEMLOG_MID_IDX'或这类索引的分区处于不可用状态”
而重建索引的方法当然可以先drop掉再create ,但是这样太麻烦了,
用alter index XXX rebuild 这样最快了,不会改变原来的索引结构
24. mysql执行sql流程?
1、连接器:客户端先和MySQL服务器建立连接,并发送查询sql脚本给服务器
2、查询缓存:服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。(MySQL8.0删掉了这个功能)否则进入下一阶段。
3、分析器:服务器端SQL解析语句,判断SQL语法是否正确,查询的表、字段是否存在
4、优化器:对sql语句进行优化,并生成对应的执行计划。
5、执行器:根据执行计划调用存储引擎的API来执行查询,并把结果返回给客户端
25. 什么情况下会索引失效?
- like左边包含%(like '张%' 能用到索引 like '%张' 用不到)
- 使用了or 关键字(如果使用了
or
关键字,那么它前面和后面的字段都要加索引,不然所有的索引都会失效)- 使用了not in 或 not exist
- is null走索引,is not null不走
- 索引列使用了函数
- 不满足最左匹配原则(联合索引abc,查询条件where b=? and c=? 则用不到联合索引)
26. 分库分表的方案?
概念:以某个字段为依据,按照一定策略,将一个表中的数据拆分到多个表中。
场景:单表的数据量太多,影响了SQL效率工具:Sharding-JDBC
- 选择分片键
- 选择分片策略:取模、范围(Between AND)
27. 数据库使用自增ID和UUID作为主键有什么不同
- 自增ID有序,生产速度快,存储空间小,数据库可以具有更好的性能,缺点是只在当前表中唯一,而不是全局唯一,而且自增ID会暴露表中数据规模;
- UUID在分布式场景下依然全局唯一
- 因此,UUID更适合数据量大的分布式场景,自增ID更适合小数据量追求性能的场景
28. 如何防止SQL注入?
- 使用预编译,比如用#{}代替${}
- 对用户输入进行严格的验证和过滤,只允许预期的输入格式和字符,拒绝特殊字符
- 为数据库用户分配最小权限,限制数据库的访问范围和操作权限
- 使用ORM框架,比如Mybatis,能够自动执行参数化查询防止sql注入
二. Redis
1. redis有哪些数据类型?
- String:字符串。set get mset(批量set) mget
- Hash:包含键值对的无序散列表。hmset key filed1 value1 filed2 value2; hget key filed1;
- List:有序列表。lpush key value; rpush key value; lrange key 0 3;
- set:无序不重复集合。sadd key value; smembers key;
- zset:有序不重复集合,每个元素都关联分数,通过分数来排序。zset key score value; zrangebyscore 0 3;
redis中String底层的数据结构?
Redis六种底层数据结构_星空是梦想的博客-CSDN博客_redis 底层数据结构、图解redis五种数据结构底层实现(动图哦)
是简单动态字符串(SDS)。用char[]数组的形式保存String中每个字符,还有len属性保存字符串的长度,以及free属性保存未使用的字节数。
为什么要这样设计?
- 高效的修改操作。可以直接在原字符串基础上修改,而不是像Java中String不可变,修改时需要频繁内存分配和拷贝。
- 空间预分配。redis会根据当前字符串长度和剩余空间进行预分配,适应字符串长度变化
- 杜绝空间溢出。字符串如果没有重新分配足够空间,直接修改字符串,可能会造成空间溢出。SDS会先判断空间是否够用,不够的话会扩展至所需大小。
- 惰性空间释放。如果字符串变短,不会直接回收,而是存到free中,防止频繁内存释放
Java中String和StringBuffer String不可变,修改String实际上是创建了新的String对象。设计为不可变主要是为了线程安全,既然不可变,那么多线程操作也无需担心线程安全问题。 StringBuffer可变,也是用char[]存储数据,如果容量足够则直接修改。不够的话,创建新的char[],将原有数据复制过来并修改,再把指针指向新char[]
redis中hash的底层数据结构?
hash的底层数据结构是哈希表(数组加链表的结构)或ziplist(压缩列表)。当存储的数据量较小时会使用ziplist(元素个数小于512,且长度小于64字节)。
- 数据结构:ziplist并不是以某种压缩算法来进行压缩存储数据,而是标识一段连续的内存空间存储数据。ziplist的数据结构有点类似于数组,但实际上是个特殊的双向链表,它不存储指向前节点或后节点的指针,存的是上一个节点长度和当前节点长度,由于内存连续因此通过偏移量就能完成前后节点访问
- 优缺点:ziplist是以空间换时间的思路,它的优势在于空间紧凑,有效减少内存占用;缺点是读写性能不如链表,而且添加或删除数据都需要对内存进行扩展和减小。然而链表占用的空间通常比ziplist更多,因为每个元素都需要额外分配内存存储指针。
zipList的数据结构?
- ziplist包括zip header、zip entry、zip end三个模块。
- zip entry由prevlen、encoding&length、value三部分组成。
- prevlen主要是指前面zipEntry的长度,coding&length是指编码字段长度和实际- 存储value的长度,value是指真正的内容。
- 每个key/value存储结果中key用一个zipEntry存储,value用一个zipEntry存储。
本质上是个字节数组,把所有元素按照特定的编码格式编码后紧凑的排列在一起
redis中list的底层数据结构?
redis中list的底层数据结构是一个双向链表或ziplist。
当Hash、List、Zset存储的数据较小时,会优先使用ZipList
redis中set的数据结构?
redis中set是一个intset或者hashtable的结构。当元素全为整数且元素数量不超过512时用intset存储,否则用hashtable的key保存数据。
intset底层实际上是一个int数组。
zset底层的数据结构是什么?
漫画:什么是 “跳表”?、跳跃表及底层实现原理解析_Slayer_Zhao的博客-CSDN博客_跳跃表原理和实现
zset底层的存储结构包括跳表(skiplist)或压缩列表(ziplist),数据量小时使用ziplist(元素个数小于128且长度小于64字节),其他时候使用skiplist
压缩列表:
当ziplist作为zset的底层存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。
跳表:
当skiplist作为zset的底层存储结构的时候,使用skiplist按序保存元素及分值,使用dict来保存元素和分值的映射关系。
跳表的结构如下图所示,跳表是用来加速查询、插入和删除的,它是在原始链表的基础上加了多层索引链表,索引链表的节点指向原始列表的节点,使得查询数据的效率更高。
插入:
2. redis的适用场景?项目中哪里用过redis?
- 缓存热点数据:如缓存标的列表
- 计数器:一个月内优惠券一万张,发一张就减一
- 分布式锁:比如一个人在交易完成前不能再进行第二次操作,用分布式锁来保证。再比如抢标时标的金额递减,也用分布式锁来保证。用setnx实现的
3. redis为什么快?
- redis基于内存,所以高性能
- redis是单线程,不需要多线程竞争CPU资源,也不需要切换上下文,减少了很多性能损耗
- redis使用了IO多路复用的模型,用一个选择器监听多个通道,当通道有事件发生时,线程就处理事件
4. redis为什么单线程还快?
因为redis使用了IO多路复用的模型,用一个选择器监听多个通道,当通道有事件发生时,线程就处理事件。相当于可以处理大量并发IO。
IO多路复用和多线程有什么区别?
- IO多路复用适合处理IO密集型,IO等待的时间比较长,不需要太多CPU计算,单线程就处理计算逻辑了
- 多线程适合处理CPU密集型,需要多线程使用多核CPU处理计算逻辑。
redis完全是单线程的吗?
不是。redis 6.0 后读写命令是依然是单线程的,但是处理网络IO是多线程的,因为redis的瓶颈主要是网络IO,所以 处理网络IO 用多线程可以解决这个问题,并且不会造成数据并发读写。
5. redis的持久化机制是什么?优缺点是什么?
RDB:按照一定的时间间隔把内存快照保存到磁盘中
- 优点:性能好,保存内存快照是子进程进行的,不需要占用主进程IO;而且恢复数据快
- 缺点:如果在保存间隔内redis宕机,会丢失数据
AOF:把所有的写操作记录到磁盘日志中
- 优点:保存数据完整
- 缺点:数据集大的时候,恢复数据慢
项目中使用的是RDB还是AOF?
- 用 AOF 来保证数据不丢失,作为数据恢复的第一选择;
- 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复
总的来说:都用,AOF 作为第一恢复方式,RDB 后补
6. redis过期键的删除策略?
- 定时删除:给每个有过期时间的key都创建定时器,到过期时间就会清除;
- 惰性删除:访问到key的时候,才判断有没有到过期时间,过期则清除;
- 定期删除:每间隔一段时间,会扫描所有的key,清除已过期的key
redis使用:惰性删除 + 定期删除
7. 有2000万mysql数据,redis只能存储20w数据,怎么保证redis存储的都是热点数据?
当数据集上升到一定程度,要么对redis扩容,要么使用淘汰策略。
淘汰策略用于处理 内存不足时需要申请额外空间的数据,有三种策略:
- 随机移除
- 移除使用最少的key
- 移除更早过期的key
当内存用完的时候,写操作会返回错误,或者你可以配置内存淘汰策略
8. redis支持事务吗?
支持,redis的事务能保证一系列指令按顺序执行,中间不会插入其他语句。但是redis事务只支持隔离性,不支持原子性,不支持回滚。
当中间有语句执行失败时,会继续执行其他语句。
9. redis事务的命令有哪些?
- MULTI:开启事务
- EXEC:执行事务
- WATCH:监控一个或多个键,一旦键被修改,会通知客户端,事务就不会执行。本质是一个乐观锁,为事务提供CAS(compare and set)行为。用到的是发布订阅模式。
- DISCARD:清空事务队列,并放弃执行事务
10. redis集群模式(redis cluster)的工作原理知道吗?
redis集群使用了哈希槽的方式来进行数据存储和读取。
假设redis集群有3个分片,数据将分别存储在这三个分片上,三个分片上被均分了16384个哈希槽。数据具体存储在哪个分片上,是由哈希槽算法决定的。CRC16(key) % 16384就可以得知数据要被存储在哪个槽上了。
如果是服务端路由的方式,客户端把请求发送到任意分片节点,接收到请求的节点都会把请求转发到正确的节点上执行。
redis为什么不使用一致性哈希,而使用哈希槽?
因为哈希槽好扩容,如果要增加节点,只需要把其他分片上的哈希槽迁移一部分到新节点即可。
一致性哈希(哈希环)
- 普通做法:如果我们要确定一个key要放在ABC哪台服务器上,普通做法是 key的hash值%3(3台服务器),但是如果要再加一台服务器,所有图片的位置就都需要改变。
- 一致性哈希:一致性哈希也叫哈希环,这个环分成了2^32个小块,ABC服务器则是环上三个点,然后用key的 hash值 对 2^32 取模,key的hash值必然落在弧形区间内,归为下一个最近的服务器。
- 一致性哈希缺点:可能会数据分布不均匀,导致哈希倾斜。解法:把ABC服务器,多取几个hash值(A1A2A3)作为虚拟节点,虚拟节点越多,哈希倾斜带来的影响就越小。
什么是redis集群模式下的脑裂?如何解决脑裂?
脑裂是因为网络原因,导致master、slave、sentinel不在一个网络分区。此时sentinel感应不到master,就会从slave中重新选举一个master,两个master看起来就像master分裂成了两个。
集群脑裂问题中,如果客户端还继续往旧的master写数据,当网络恢复后,sentinel就会把之前master降为slave,此时再从新的master同步数据就会丢失大量数据。
解决方案:
redis有两个配置如下,如果slave节点少于3个,或者slave超过10秒都没连接到master,master就拒绝写请求
//配置slave节点最少有3个 min-slaves-to-write 3 //配置slave连接到master的最大延时为10秒 min-slaves-max-lag 10
12. redis是如何进行主从复制的?
- 当一个slave启动的时候,会发送一个同步命令(PSYNC)给master
- master生成一个RDB文件发给slave,并把期间收到的写命令缓存起来,然后把RDB文件和缓存的写命令发送给slave
- slave把RDB文件先写到磁盘,然后写到缓存中,再执行写命令
- 之后master收到的写命令都会同步给slave,从而保证数据的一致性
这样的主从复制模式有一个缺点,就是所有的复制同步都由master处理,导致master压力太大,可以使用主从从模式,slave同步到slave
Redis主从模式和集群模式的区别?
- 主从模式,master可以读写,slave只能读,master会向slave同步数据
- 集群模式,多个节点都可以读写,支持更高的并发
13. redis哨兵机制是什么?
sentinel,哨兵,用于保证redis主从集群的高可用。它有四个作用:
- 集群监控:监控redis master 和 slave 的可用性
- 消息通知:如果master宕机,会报警通知运维管理员。(判断master宕机,需要一半以上的sentinel同意才行,涉及到分布式选举)
- 故障转移:如果master故障,会从slave中选举出一个新的master
- 配置中心:管理配置信息,如主从节点的连接信息
哨兵机制不保证数据不丢失,只能保证redis集群高可用
13. 缓存异常
【缓存雪崩】:同一时间内大面积缓存失效,导致请求全都打到服务器上。
解决方案:缓存预热,在服务器启动前把热点数据提前刷到缓存中,并且过期时间尽量设置的随机一些,避免同一时间失效
【缓存击穿】:缓存中没有数据库中有的数据(一般是缓存时间到期)
解决方案:一般有两种解决方案。第一是让热点数据用不过期,但是轻易不使用这种方式。第二种是用双重判空的方式,先去缓存中拿,缓存中没有再加锁,再判断缓存中有没有,没有的话再到数据库拿。(加锁是为了保证高并发下只有一个请求到数据库拿数据,减轻压力;双重判空是因为加锁后,有可能其他线程已经往缓存中放了数据)
【缓存穿透】:缓存和数据库中都没有的数据
解决方案:可以在缓存中设置一个key,value是null,这样也就直接能从缓存拿到了
14. 如何用redis实现分布式锁?
- 获取锁:SETNX(set if Not Exist), 设置成功返回1,设置失败返回0
- 释放锁:DEL
如何获取分布式锁?怎么保证性能?
- 可以用setnx()获取锁成功返回1,获取失败返回0;如果是获取成功则可以继续处理下边的业务,如果获取失败自旋5次,尝试去获取锁。
如果要设置过期时间,用jedisClient.set(key, value, "NX"
,
"EX", expireSecond);//NX是不存在时才set,XX是存在时才set,EX是秒,PX是毫秒
自旋比较消耗性能,如果不采用自旋的方式呢?
使用阻塞队列的blpop。
redis和zk获取分布式锁有什么区别?
zk获取分布式锁是通过创建临时节点的方式,如果创建临时节点成功就获取锁,释放锁就是删除临时节点。
但是一般都不使用zk做分布式锁,很客观的原因是要用zk做分布式锁就得有zk集群,但是一般公司都没有zk集群,但是都有redis的集群,所以都用redis做分布式锁。
15. 假设redis中有一亿数据,有10w个前缀都是一样的,如何把他们找出来?
- 使用keys指令,keys [前缀]*。缺点:redis是单线程的,keys指令会使线程阻塞一段时间,导致线上会停顿,直到该指令执行完毕。
- 使用scan指令,scan指令是一个基于游标的迭代器。每次调用之后,都会返回一个新游标。用户下次可以使用这个游标继续迭代。
KEYS aaa*; //模糊匹配,aaa是key的前缀
SCAN 0 MATCH aaa* COUNT 10; //0是游标
16. 有10万条数据,要插入到redis中,如何操作?
- 可以使用管道,分批提交数据。比一个一个提交快,是因为省去了多次网络IO的时间。
- 可以使用AOF的方式,把10万条数据变成set key value,set ket value的形式,加载到redis中
17. 如何使用redis做异步队列?
使用redis的list数据结构,blpush生产消息,brpop消费消息,brpop是阻塞的,没有消息就一直等待,直到消息到来。
18. 如何使用redis做延迟队列?
用redis的zset做延迟队列,消息作为value,消息要发送的时间戳作为score
用zrangebyscore以 0 < score <= 当前时间戳 来获取要执行的任务。
19. redis的回收线程是怎么工作的?
当redis已使用内存达到阈值之上,如果有新请求进来,就会按照配置的淘汰策略进行回收。
20. redis回收使用的是什么算法?
LRU算法,淘汰最少使用的数据。
LRU算法具体实现?
LRU算法的核心思想是:
如果数据最近被访问过,那么将来被访问的几率也更高。
实现:
- 新加入数据添加到链表头部
- 被访问的数据也添加移动到链表头部
- 当链表满时从底部淘汰数据
代价:命中需要遍历链表,找到命中的数据,并移动到链表头部
21. Redisson看门狗机制
看门狗(WatchDog)是一个后台线程,它的主要作用是确保分布式锁续期和异常释放。
- 它会启动一个定时任务,对于已经持有分布式锁,但是还没执行完业务流程的客户端,它会更新分布式锁的过期时间,进行锁续约,确保业务正常执行完。
- 看门狗还会定期检查持有锁的客户端是否活跃(监控客户端和redis的心跳),如果客户端一段时间内没有响应(比如网络故障或客户端进行崩溃),看门狗将自动释放锁,确保其他客户端可以获取到锁资源。
22. 如何查看redis的内存和CPU?
- 查看内存的命令:INFO memory
- 查看CPU的命令:INFO CPU
如果内存突然满了,需要排查下是否是短时间内写入了大量数据。
解决方案一般是:
- 设置key的过期时间自动清理不再需要的key,或手动删除不再需要的key
- 对redis进行扩容,或者增加节点
三.MongoDB
1. 你们项目中MongoDB中存储是什么数据?
费率、保单的扩展字段。
由于费率计算复杂,每个保司计算费率的条件也都不一样,这时候如果用数据库存储,就没办法设计表,因此采用MongoDB存储,可以灵活的存储非结构化数据。
2. MySQL和MongoDB和redis的特点、区别、使用场景?
- MySQL是关系型数据库,常用于存储结构化数据,并且支持事务。
- MongoDB是文档型数据库,如果不确定的数据结构,就可以使用MongoDB,便于扩展字段。
- redis当做缓存使用,查询非常快速,比如可以存储热点数据,缩小给前端的返回时间。