目录
对整理的面试题总结一些简略的回答,深入的分析还是需要自己去研究
微服务的限流方案,和服务降级方案
分布式微服务架构下,某一个节点出现故障,会导致雪崩效应。分布式总体限流方案:利用redis,记录调用量,服务降级,限流降级:把非重点的服务关闭。故障降级:设置默认值,采用兜底数据,开关预置、配置中心
Synchronized原理:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权
Java并发编程:Synchronized及其实现原理 - liuxiaopeng - 博客园
乐观锁和悲观锁区别
乐观锁,先进行业务操作,在最后一步更新数据时再加锁,实现为每一条数据加一个版本号,查询出版本号,然后更新的时候以此版本号为依据,如果不一致则报错或者回滚事物
悲观锁:查询的时候就加锁 select for update 仅适用于innodb,且必须在事物块中才生效
redis
redis集群三种模式:
1主从模式,一个master,一个slave,slave启动后,向master发送sync命令,master接收到命令后先保存快照(RDB持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和命令发给slave,slave接收到以后加载快照文件和执行缓存命令,初始化完成后,master每次接收到的写命令都会同步发送给slave,保证主从一致性
1. slave发送sync命令到master
2. master收到sync之后,执⾏bgsave,⽣成RDB全量⽂件
3. master把slave的写命令记录到缓存
4. bgsave执⾏完毕之后,发送RDB⽂件到slave,slave执⾏
5. master发送缓存中的写命令到slave,slave执⾏
2.8版本开始提供了psync命令,可以进行部分复制,具体原理是:主服务器维护了一个复制积压缓冲区,同时主从服务器都维护了一个复制偏移量,初次同步时主会将自己的runid发送给从服务器,当断线重连时,从服务器执行psync,如果主从的两个master run id相同,并且指定的偏移量在内存缓冲区中还有效,那么复制就会从上次中断的点开始继续,如果有一个条件不满足(runid,复制偏移量在内存缓冲区中是否有效)
2 Sentinel模式,哨兵模式,实现了自动故障转移,集群监控,消息通知等功能
Sentinel可以监控主从状态,当主下线以后,哨兵会标记成主观下线,然后当多个哨兵认为主观下线,则节点状态变为客观下线 ,开始进行故障转移,这里根据配置来决定。
#监控的IP 端口号 名称 sentinel通过投票后认为mater宕机的数量,此处为至少2个
sentinel monitor mymaster 192.168.14.101 6379 2
一个Sentinel可以监控多个主从redis,多个Sentinel也可以监控同一个主从redis
手动命令:判断节点状态 ping, 节点信息 info,设置主从节点 slaveof,其他哨兵节点是否在线 sentinel is-master-down-by addr
过程:首先哨兵选举出一个leader节点(通过raft协议实现)超过半数同意则为leader,leader选出来以后,首选选出一个master,主要根据以下条件:
1、首先去掉断线或者下线的节点,然后去掉一个ping请求过去后,响应时间大于5s的
2、选择slave-priority(slave节点优先级)最高的slave节点,存在则返回,如果有多个相等则继续下一步判断
3、选择复制偏移量最大的slave节点,如果存在则返回,如果还有多个,则继续下一步判断
4、选择runId最小的节点(启动最早的节点)
但是master只有一台,数据量大时就不能满足需求了,此时可以用集群(cluster)模式
3、Cluster模式,多台机器,将redis服务分片,cluster模式自带的选举机制类似哨兵选举,但不存在哨兵,当master宕掉后,由该master的一个或多个slave节点来选举出新的master
使用集群,只需要将redis配置文件中的cluster-enable配置打开即可
数据分区规则采用虚拟槽方式(16384个槽),每个节点负责一部分槽和相关数据,实现数据和请求的负载均衡,
具体步骤:对每个key进行 CRC16 hash,然后hash结果对16383进行取余,根据余数发送到对应节点,如果连接的不是对应节点,由于节点之间会共享消息,因此会把数据发给正确的节点
集群扩容或者缩容时,会对槽及槽中数据进行迁移:两种异常:moved异常,槽已经迁移 ask异常,槽还在迁移中
相关文章:https://www.cnblogs.com/williamjie/p/11132211.html
redis的几种数据存储结构
redis数据存储结构有五种,string(字符串)list(列表)hash(字典)set(集合)zset(有序集合)依据存储数据的类型和大小等场景来分别使用
1、简单动态字符串 SDS(string)
没有用c语言的字符串,而是自定义了名为SDS的数据结构,有点类似于java中的ArrayList,有一个字符数组,同时保存了字符长度和剩余空间free
解决了缓冲区溢出/内存泄漏问题,预分配了空间(free)减少了内存分配次数,获取字符串长度复杂度为常数
2、双向链表(list)有点类似于java中的LinkedList,可以通过LPUSH 和RPUSH分别向list的左边(头部)和右边(尾部)添加新元素,可以实现队列和栈
3、字典(hash)Redis的Hash,为单链表结构,和java中的HashMap类似
就是在数组+链表的基础上,进行了一些rehash优化等。即扩容的时候会一点点扩,渐进式hash
4 跳跃表skipList
跳跃表类似于多级索引,可以根据索引快速查找到对应数据,效率类似于二分查找
skiplist,指的就是除了最下面第1层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针故意跳过了一些节点(而且越高层的链表跳过的节点越多)。这就使得我们在查找数据的时候能够先在高层的链表中进行查找,然后逐层降低,最终降到第1层链表来精确地确定数据位置。在这个过程中,我们跳过了一些节点,从而也就加快了查找速度。
如果严格按照上下相邻两层链表个数2:1的关系,那么插入数据的时候,就需要把新插入的数据和后面所有节点重新进行调整,这会让时间复杂度重新变为O(n),因此skiplist插入节点通过一个随机数算法来计算层数,redis中最大层数为32
在同⼀个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯⼀的,节点按照分值⼤⼩排序,如果分值相同,则按照成员对象的⼤⼩排序,所以redis的skiplist实际上根据score和对象值来进行排序
redis zset底层实现原理_redis的zset的底层原理-CSDN博客
每个节点中维护多个指向其他节点的指针,节点是有序的 ,和平衡树很相似,为什么不用平衡树呢
skipList & AVL 之间的选择
-
从算法实现难度上来比较,skiplist比平衡树要简单得多。
-
平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
-
查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当。
-
在做范围查找的时候,平衡树比skiplist操作要复杂。
-
skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的。
5 整数集合(intset)
对int存储做了优化,根据属性分配不同大小,如果放入更大的,则进行encoding升级
6 压缩列表(ziplist)ziplist是redis为了节约内存而开发的顺序型数据结构。它被用在列表键和哈希键中。一般用于小数据存储。
ZSet(sorted set-有序集合)在redis中有两种实现,skipList和zipList,在这基础上加上了score分值的概念,通过score和index来进行排序,相当于是一个进行了排序的链表
Redis数据结构底层实现_redis中zest结构-CSDN博客
6、redis的几种数据结构:
String 字符串类型,场景:redis做缓存层,mysql做持久层,降低mysql读写压力
Hash:常用命令 hget hset 场景:比较直观,相比string更节省空间,比如用户信息,视频信息等
链表 list:命令:lpush+lpop =栈 lpush+rpop=队列 lpush+brpop = mq(消息队列)消息队列还有多种实现方式,(LPUSH+LRANGE+RPOP ,LPUSH+BRPOPLPUSH+LREM)
具体可看【你不了解的Redis】基于Redis实现消息队列的6种方案之方案简述(上)基于List的LPUSH+/RPOP/BRPOP/BRPOPLPUSH的实现-CSDN博客
实战场景:时间线,比如微博的时间轴,有人发布微博,则lpush加入时间轴
集合set:set和list不同的地方在于 1、没有重复的元素 2、元素是无序的,不能通过索引下标获取元素 3、支持集合间的操作
命令:s开头 sset srem scard smembers返回集合中所有成员
场景:点赞或者收藏
有序集合zset 和set的区别就是可以排序,给每个元素设置了一个分数 zadd zrange zcore
场景:排行榜,榜单可以根据用户关注数,更新时间,字数等打分,然后做排行
mysql
mysql索引的数据结构
索引就是排好序的数据结构,可以帮我们快速找到数据,mysql用的B+树
使用的Innodb存储引擎,表相关文件只有两个同样.frm文件是存放表结构数据,.ibd存放的数据和索引。
查找和比较速度最快的是二叉树,但是数据量大的时候二叉树会很深,而数据库数据是保存在外部磁盘上,每次读取一个磁盘页,因此如果用二叉树磁盘io会很多,降低查找速度。而B+树则减少了磁盘深度,尽量将数据放在同一层节点。
B+树中,叶子节点同时存储了上层节点的数据(最大或者最小数据),同时每一个节点存储了指向相邻节点的指针,形成有序链表
B树和B+树的区别:B+树只在叶子节点上存储数据,其他节点只存储索引,因此同样大小的树B+树存储的更多数据,同时由于B树每个节点下都有data域,B树查询不是稳定的,同时范围查询B树只能用中序遍历
-
B-Tree 中,所有节点都会带有指向具体记录的指针;B+Tree 中只有叶子结点会带有指向具体记录的指针。
-
B-Tree 中不同的叶子之间没有连在一起;B+Tree 中所有的叶子结点通过指针连接在一起。
-
B-Tree 中可能在非叶子结点就拿到了指向具体记录的指针,搜索效率不稳定;B+Tree 中,一定要到叶子结点中才可以获取到具体记录的指针,搜索效率稳定。
B+树更适合外部存储(磁盘),因为非叶子节点不存储数据,所以一个节点可以存储更多的内节点,每个节点能索引的范围更大更准确,因此B+树单次磁盘io的信息量比B树更大,I/O效率更高
综合起来,B+树相比B-树的优势有三个:
1.单一节点存储更多的元素,使得查询的IO次数更少。
2.所有查询都要查找到叶子节点,查询 稳定。
3.所有叶子节点形成有序链表,便于范围查询。
B树和红黑树的区别,在数据较小,可以完全放到内存中时,红黑树的时间复杂度比B树低。
反之,数据量较大,外存中占主要部分时,B树因其读磁盘次数少,而具有更快的速度
聚簇索引:mysql用的innodb引擎,用的是聚簇索引, 即主键索引,因此每张表只能由一个聚簇索引,通过主键来聚集数据,
主键索引的叶子节点放的是具体的数据。也称为数据页。如果没有主键,则选择一个非空的唯一索引代替,如果没有这样的索引,innodb会隐式的定义一个主键索引。
最好用主键索引,因为如果用其他索引,在插入数据的时候容易造成页分裂,即要插入的节点个数超出,因此需要重新进行调整
在主键索引之上创建的索引称为辅助索引,辅助索引都需要二次查找,叶子节点放的是主键值
相关链接:Mysql索引数据结构_mysqlsql查询数据结构-CSDN博客
B+树层数估算:
一个 B+Tree 可以存多少条数据呢?以主键索引的 B+Tree 为例(二级索引存储数据量的计算原理类似,但是叶子节点和非叶子节点上存储的数据格式略有差异),我们可以简单算一下。
计算机在存储数据的时候,最小存储单元是扇区,一个扇区的大小是 512 字节,而文件系统(例如 XFS/EXT4)最小单元是块,一个块的大小是 4KB。InnoDB 引擎存储数据的时候,是以页为单位的,每个数据页的大小默认是 16KB,即四个块。
基于这样的知识储备,我们可以大致算一下一个 B+Tree 能存多少数据。
假设数据库中一条记录是 1KB,那么一个页就可以存 16 条数据(叶子结点);对于非叶子结点存储的则是主键值+指针,在 InnoDB 中,一个指针的大小是 6 个字节,假设我们的主键是 bigint ,那么主键占 8 个字节,当然还有其他一些头信息也会占用字节我们这里就不考虑了,我们大概算一下,小伙伴们心里有数即可:
16*1024/(8+6)=1170
即一个非叶子结点可以指向 1170 个页,那么一个三层的 B+Tree 可以存储的数据量为:
1170*1170*16=21902400
可以存储 2100万 条数据。
在 InnoDB 存储引擎中,B+Tree 的高度一般为 2-4 层,这就可以满足千万级的数据的存储,查找数据的时候,一次页的查找代表一次 IO,那我们通过主键索引查询的时候,其实最多只需要 2-4 次 IO 操作就可以了。
联合索引的结构:联合索引的结构和普通索引不同,普通索引节点存放单条数据,而联合索引存放的是联合索引对应的数据,即有多条,由于最左列已经按顺序排了,所以后续列跟着排,并不是有序的,只有最左列相同时,第二列才会按序排列,因此只有使用到最左列的时候,才能用到索引,即最左匹配原则,详见:
索引失效的情况
https://www.cnblogs.com/wdss/p/11186411.html
explain 字段内容:possible_keys 可能选中的索引, key 此次查询中确定使用到的索引,ref 哪个字段或常数与key一起被使用
1、like以%开头,索引无效,如果是前缀没有%,后缀有%时,则索引有效(索引的最左匹配)
原因是b+树是按照从左到右的顺序来建立搜索数的,最左边的就是第一个比较因子,所以如果没有最左边的数据,b+树就不知道下一步该查哪个节点
2、or语句前后没有同时使用索引,当or左右查询字段只有一个使用索引,则索引无效,只有or左右查询字段都为索引时,索引有效
3、组合索引,不是使用第一列索引,索引失效,当查询字段中有最左索引时,索引有效,不存在最左索引时,都失效
4、数据类型出现隐式转化,如varchar不加单引号的话可能就会自动转换为int型,使索引无效,产生全表扫描
5、在索引列上使用 IS NULL或IS NOT NULL 操作,索引是不索引空值的,所以索引失效,可以用其他办法,比如如果是数字类型则可以判断大于0,或者设置一个默认值
6、!=操作符无法使用索引
7、对索引字段进行计算操作,索引字段上使用函数,则无法走索引
8、当全表扫描速度比索引速度快时,mysql会使用全表扫描
mysql数据库事务的隔离级别:
事务的特性:原子性,一致性,隔离性和持续性
ACID是如何保证的:
A原⼦性由undo log⽇志保证,它记录了需要回滚的⽇志信息,事务回滚时撤销已经执⾏成功的sql
C⼀致性⼀般由代码层⾯来保证
I隔离性由MVCC来保证
D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复
四种隔离级别:
read uncommitted 未提交读 事务中的修改即使没有提交,对其他事务也可见,事务读取未提交的数据,也称为脏读
read committed 提交读 大多数数据库的隔离级别(mysql不是),也叫不可重复读,因为事务中执行两次同样的查询,可能得到不一样的结果
repeatable read 可重复读,确保了同一个事务的多个实例在并发读取数据时,会看到同样的数据行,不过可能会产生幻读,即某个事务在读取某一范围内数据时,另外一个事务又在该范围内插入了数据,当之前的事务再次读取该范围记录时,会产生幻行(即多出来的或者少的那一行叫幻行),innoDb通过多版本并发控制(MVCC)解决了幻读的问题
快照读(通过MVCC解决)简单的select操作,原理其实是乐观锁,保存了数据在某一时刻的快照,在事务提交时判断版本号是否一致。只适用于读已提交和可重复读
读已提交(不可重复读)和可重复读的MVCC区别就是版本的生成时机,可重复读的情况下事务只在第一次select时生成版本,后续查询都在这个版本上进行,从而实现可重复读
详细原理:MVCC叫做多版本并发控制,实际上就是保存了数据在某个时间节点的快照。我们每⾏数实际上隐藏了两列,创建时间版本号,过期(删除)时间版本号,每开始⼀个新的事务,版本号都会⾃动递增。而MVCC的原理是查找创建版本⼩于或等于当前事务版本,删除版本为空或者⼤于当前事务版本,这样就保证了事务读取的数据是在事务开始前就已经存在的,要么是事务⾃⼰插⼊或者修改的。
当前读(通过next-key锁解决)插入更新删除操作 需要加锁 select * from table where ? lock in share mode; select * from table where ? for update;
serializable 可串行化 事务最高的隔离级别,通过强制事务串行执行,即加行锁解决了幻读问题,不过可能导致大量的超时和锁争用,所以实际很少使用
mysql锁机制和加锁原理
锁的粒度:行锁,表锁,页锁,innodb的锁都是加在索引上的
使用方式:共享锁、排他锁(悲观锁的一种实现)
行锁类型:Record Lock行锁、 Gap Lock 间隙锁、 Next-key Lock 临键锁(Record Lock + Gap Lock)
行锁开销比较大,是最细粒度的一种锁,只能针对索引加锁,如果不是索引,那么会在每一个聚簇索引后面加锁,类似表锁,但不一样
行锁 共享锁:(S锁 读锁)select ... lock in share mode; 允许多个线程同时获取一个锁,一个锁可以同时被多个线程拥有,可以读,但是不能修改,只有当S锁释放才可以修改
行锁 排它锁:(X锁,写锁)select ... for update; 也称为独占锁,在某一时刻只能被一个线程占有,其他线程必须等锁释放以后才能获取锁,如果事务T对数据对象A加了X锁,那么T可以读也可以修改A,其他事务不能再对A加任何锁,保证了事务在释放锁之前数据不被修改
表锁开销比较小,但是容易发送锁冲突,也分为共享锁(LOCK TABLE table_name [ AS alias_name ] READ )排它锁(
LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE )
gap锁:即在索引的间隙加上锁,可以防止幻读,只需要在事务进行当前读的时候,保证其他事务不可以在满足当前读条件的范围内进行数据操作
间隙锁加锁有以下特性:
- 1.加锁的基本单位是(next-key lock),他是前开后闭原则
- 2.插叙过程中访问的对象会增加锁
- 3.索引上的等值查询--给唯一索引加锁的时候,next-key lock升级为行锁
- 4.索引上的等值查询--向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁
- 5.唯一索引上的范围查询会访问到不满足条件的第一个值为止
next-key lock 即行锁+gap锁的结合,即不仅锁住当前行,还对索引两边的间隙加上锁,可以防止幻读
innodb造成死锁的原因:因为在innodb中,不是锁记录,而是锁索引,如果一个sql操作了主键索引,mysql就会锁定这个索引,而另一个语句操作了非主键索引,mysql会先锁定该非主键索引,然后再锁定相关的主键索引,如果这两个同时执行,一个锁了主键索引,在等待其他索引,另一个锁定了非主键索引,在等待主键索引,就会发生死锁
间隙锁就可能造成死锁,因为间隙锁之间不是互斥的
select 。。。for update (排它锁)
意向锁
innodb支持多粒度锁,允许行级锁和表级锁共存,而意向锁就是一种表锁,他不与行级锁冲突
可以分为意向共享锁和意向排他锁,意向锁是数据引擎自己维护的,在为数据行加共享锁之前,innodb会先获取该数据行所在数据表的对应的意向锁
意向锁之间(包括意向共享锁和意向排他锁)是兼容的,同时和行级的共享锁和排他锁也是兼容的,只有和表级的共享锁和排他锁之间是互斥的
意向锁要解决的问题:主要为了解决表级共享锁和排他锁的问题,如果没有意向锁,则在对表加共享锁的时候,需要保证:
-
当前没有其他事务持有 users 表的排他锁。
-
当前没有其他事务持有 users 表中任意一行的排他锁 。
为了满足第二个条件,事务B必须在确保没有表级排他锁的前提下,去检查表中每一行是否存在排他锁,这样效率是很差的,而有了意向锁,则只需要判断是否存在意向排他锁即可。
意向锁的并发性:刚才也说了意向锁不会与行级锁互斥,因此在添加行级排他锁以后,存在意向排他锁,仍然可以继续添加排他锁,只要不和之前的排他锁冲突即可。
不会影响多个事务对不同数据行加排他锁时的并发性
总结
-
InnoDB 支持
多粒度锁
,特定场景下,行级锁可以与表级锁共存。 -
意向锁之间互不排斥,但除了 IS 与 S 兼容外,
意向锁会与 共享锁 / 排他锁 互斥
。 -
IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。
-
意向锁在保证并发性的前提下,实现了
行锁和表锁共存
且满足事务隔离性
的要求。
mysql优化步骤
1、通过慢查日志,定位执行效率较低的sql语句
2、explain 分析SQL的执行计划
需要重点关注type、rows、filtered、extra。
type由上至下,效率越来越高
- ALL 全表扫描
- index 索引全扫描
- range 索引范围扫描,常用语<,<=,>=,between,in等操作
- ref 使用非唯一索引扫描或唯一索引前缀扫描,返回单条记录,常出现在关联查询中
- eq_ref 类似ref,区别在于使用的是唯一索引,使用主键的关联查询
- const/system 单条记录,系统会把匹配行中的其他列作为常数处理,如主键或唯一索引查询
- null MySQL不访问任何表或索引,直接返回结果
虽然上至下,效率越来越高,但是根据cost模型,假设有两个索引idx1(a, b, c),idx2(a, c),SQL为"select * from t where a = 1 and b in (1, 2) order by c";如果走idx1,那么是type为range,如果走idx2,那么type是ref;当需要扫描的行数,使用idx2大约是idx1的5倍以上时,会用idx1,否则会用idx2
Extra
- Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行。通过根据联接类型浏览所有行并为所有匹配WHERE子句的行保存排序关键字和行的指针来完成排序。然后关键字被排序,并按排序顺序检索行。
- Using temporary:使用了临时表保存中间结果,性能特别差,需要重点优化
- Using index:表示相应的 select 操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!如果同时出现 using where,意味着无法直接通过索引查找来查询到符合条件的数据。
- Using index condition:MySQL5.6之后新增的ICP,using index condtion就是使用了ICP(索引下推),在存储引擎层进行数据过滤,而不是在服务层过滤,利用索引现有的数据减少回表的数据。
mysql 和myisam的区别与优缺点
链接:https://www.zhihu.com/question/20596402/answer/211492971
1. InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
3. InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
4. InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
5. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
如何选择:
1. 是否要支持事务,如果要请选择 InnoDB,如果不需要可以考虑 MyISAM;
2. 如果表中绝大多数都只是读查询,可以考虑 MyISAM,如果既有读写也挺频繁,请使用InnoDB。
3. 系统奔溃后,MyISAM恢复起来更困难,能否接受,不能接受就选 InnoDB;
4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的。如果你不知道用什么存储引擎,那就用InnoDB,至少不会差。