B树和B+树的区别,为什么Mysql使用B+树

B树的特点:

  1. 节点排序
  2. 一个节点了可以存多个元素,多个元素也升序排序了
    B+树的特点:
  3. 拥有B树的特点
  4. 2.叶子节点之间有指针
  5. 非叶子节点上的元素在叶子节点上都冗余了,也就是叶子节点中存储了所有的元素,并且排好顺序
    Mysql索引使用的是B+树,因为索引是用来加快查询的,而B+树通过对数据进行排序所以是可以提高查询速度的,然后通过一个节点中可以存储多个元素,从而可以使得B+树的高度不会太高。在MysqI中一个Innodb页就是一个B+树节点,一个nnodb页默认16kb,所以一般情况下一颗两层的B+树可以存2000万行左右的数据,然后通过利用B+树叶子节点存储了所有数据并且进行了排序,并且叶子节点之间有指针,可以很好的支持全表扫描。范围查找等SQL语句。

Explain语句结果中各个字段分表表示什么

列名 描述
id 查询语句中每出现一个SELECT关键字,MySQL就会为它分配一个唯一的id值,某些子查询会被优化为join查询,那么出现的id会一样
select_type SELECT关键字对应的那个查询的类型
table 表名
partitions 匹配的分区信息
type 针对单表的查询方式(全表扫描、索引)
possible_keys 可能用到的索引
key 实际上使用的素引
key_len 实际使用到的索引长度
ref 当使用索引列等伯查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比
Extra —些额外的信息,比如排序等

lnnodb是如何实现事务的

Innodb通过Buffer Pool,LogBuffer,Redo Log,Undo Log来实现事务,以一个update语句为例:

  1. Innodb在收到一个update语句后,会先根据条件找到数据所在的页,并将该页缓存在Buffer Pool中
  2. 执行update语句,修改Buffer Pool中的数据,也就是内存中的数据
  3. 针对update语句生成—个RedoLog对象,并存入LogBuffer中
  4. 针对update语句生成undolog日志,用于事务回滚
  5. 如果事务提交,那么则把RedoLog对象进行持久化,后续还有其他机制将Buffer Pool中所修改的数据页持久化到磁盘中
  6. 如果事务回滚。则利用undolog日志进行回滚

五、MySQL的索引结构是什么样的?聚簇索引和非聚簇索引又是什么?

二叉树-》AVL树-》红黑树-》B-树-》B+树
二叉树:每个节点最多只有两个子节点,左边的子节点都比当前节点小,右边的子节点都比当前节点大。
AVL树:树中任意节点的两个子树的高度差最大为1
红黑树:

  1. 每个节点都是红色或者黑色。
  2. 根节点是黑色。
  3. 每个叶子节点都是黑色的空节点。
  4. 红色节点的父子节点都必须是褐色。
  5. 从任一节点到其每个叶子节点的所有路径都包含相同的黑色节点。
    B-树:
  6. B-树的每个非叶子节点的子节点个数都不会超过D(这个D就是B-树的阶)
  7. 所有的叶子节点都在同一层。
  8. 所有节点关键字都是按照递增顺序排列。
    B+树:
  9. 非叶子节点不存储数据,只进行数据索引。
  10. 所有数据都存储在叶子节点当中。
  11. 每个叶子节点都存有相邻叶子节点的指针。
  12. 叶子节点按照本身关键字从小到大排序。

聚簇索引就是数据和索引是在一起的。
MyISAM使用的是非聚簇索引,树的子节点上的data不是数据本身,而是数据存放的地址。InnoDB采用的是聚簇索引,树的叶子节点上的data就是数据本身。
聚簇索引的数据物理存放顺序和索引顺序是一致的,所以一个表当中只能有一个聚簇索引,而非聚簇索引可以有多个。
InnoDB中,如果表定义了PK,那PK就是聚簇索引。如果没有PK,就会找第一个非空的unique列作为聚簇索引。否则,lnnoDB会创建一个隐藏的row-id作为聚簇索引。

MySQL的覆盖索引和回表
如果只需要在一颗索引树上就可以获取SQL所需要的所有列,就不需要再回表查询,这样查询速度就可以更快。
实现索引覆盖最简单的方式就是将要查询的字段,全部建立到联合索引当中。
user (PK id , name ,sex)
select count(name) from user ;->在name字段上建立一个索引。
select id , name ,sex from user; ->将name上的索引升级成为(name,sex)的联合索引。

MySQL的锁有哪些?什么是间隙锁?

从锁的粒度来区分
1、行锁:加锁粒度小,但是加锁资源开销比较大。InnDB支持。
共享锁:读锁。多个事务可以对同一个数据共享同一把锁。持有锁的事务都可以访问数据,但是只能读不能修改。selectxxx LOCK IN SHARE MODE。
排他锁:写锁。只有一个事务能够获得排他锁,其他事务都不能获取该行的锁。InnoDB会对update\delete\insert语句自动添加排他锁。SELECT xxx FOR UPDATE。
自增锁:通常是针对MySQL当中的自增字段。如果有事务回滚这种情况,数据会回滚,但是自增序列不会回滚。
2、表锁:加锁粒度大,加锁资源开销比较小。MylSAM和InnoDB都支持。
表共享读锁
表排他写锁
意向锁:是InnoDB自动添加的一种锁,不需要用户干预。
3、全局锁: Flush tables with read lock。加锁之后整个数据库实例都处于只读状态。所有的数据变更操作都会被挂起。一般用于全库备份的时候。
常见的锁算法: user. userid ( 1,4.9)update user set xxx where userid=5;REPEATABLE READ间隙锁锁住(5,9)
1、记录锁:锁一条具体的数据。
2、间隙锁:RR隔离级别下,会加间隙锁。锁一定的范围,而不锁具体的记录。是为了防止产生幻读。(-x,1)(1,4)(4.,9)(9,xxx)
3、Next-key :间隙锁+右记录锁。(-xx,1](1,4](4.9](9,xxx)

MySQL的集群是如何搭建的?读写分离是怎么做的?

code

  • MySQL通过将主节点的Binlog同步给从节点完成主从之间的数据同步。
  • MySQL的主从集群只会将binlog从主节点同步到从节点,而不会反过来同步。由此也就引申出了读写分离的问题。
  • 因为要保证主从之间的数据一致,写数据的操作只能在主节点完成,而读数据的操作,可以在主节点或者从节点上完成。

Mysql慢查询该如何优化?

  1. 检查是否走了索引,如果没有则优化SQL利用索引
  2. 检查所利用的索引,是否是最优索引
  3. 检查所查字段是否都是必须的,是否查询了过多字段,查出了多余数据
  4. 检查表中数据是否过多,是否应该进行分库分表了
  5. 检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资瀛

mysql聚簇和非聚簇索引的区别

都是B+树的数据结构

  • 聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的
  • 非聚簇索引:叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。

InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引,则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值

MyISM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键』辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。

如果涉及到大数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。

什么是MVCC

多版本并发控制:读取数据时通过一种类似玦照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事session会看到自己特定版本的数据,版本链

MVCC只在READ COMMITTED和REPEATABLEREAD两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE贝会对所有读取的行都加锁。

聚簇索引记录中有两个必要的隐藏列:

  • trx_id:用来存储每次对某条聚簇索引记录进行修改的时候的事务id。
  • roll_pointer: 每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)

已提交读和可重复读的区别就在于它们生成ReadView的策略不同。

  • 开始事务时创建readview,readView维护当前活动的事务id,即未提交的事务id,排序生成一个数组
  • 访问数据,获取数据中的事务id(获取的是事务id最大的记录),对比readview:
  • 如果在readview的左边(比readview都小),可以访问(在左边意味着该事务已经提交)
  • 如果在readview的右边(比readview都大)或者就在readview中,不可以访问,获取roll_pointer,取上一版本重新对比(在右边意味着,该事务在readview生成之后出现,在readview中意味着该事务还未提交)
  • 已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个Readview,之后的读都复用之前的Readview。
  • 这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView生成策略的不同实现不同的隔离级别。

什么是脏读、幻读、不可重复读?要怎么处理?

这些问题都是MySQL进行事务并发控制时经常遇到的问题。

  • 脏读:在事务进行过程中,读到了其他事务未提交的数据。
  • 不可重复读:在一个事务过程中,多次查询的结果不一致。
  • 幻读:在一个事务过程中,用同样的操作查询数据,得到的记录数不相同。

处理的方式有很多种:加锁、事务隔离、Mvcc
加锁:

  1. 脏读:在修改时加排他锁,直到事务提交才释放。读取时加共享锁,读完释放锁。
  2. 不可重复读:读数据时加共享锁,写数据时加排他锁。
  3. 幻读:加范围锁。

索引的基本原理

索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理:就是把无序的数据变成有序的查询

  1. 把创建了索引的列的内容进行排序
  2. 对排序结果生成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

在业务系统中,除了使用主键进行的查询其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

Redis单线程为什么这么快

Redis基于Reactor模式开发了网络事件处理器、哀件事件处理器file event handler。它是单线程的,所以Redis才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis内部的线程模型的简单性。

文件事件处理器的结构包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。

多个Socket 可能并发的产生不同的事件,IO多路复用程序会监听多个Socket,会将Socket放入一个队列中排队,每次从队列中有序、同步取出一个Socket给事件分派器,事件分派器把Socket给对应的事件处理器。然后一个Socket的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket给事件分派器。文件事件分派器会根据每个Socket当前产生的事件,来选择对应的事件处理器来处理。

  1. Redis启动初始化时,将连接应答处理器跟AE_READABLE事件关联。
  2. 若一个客户端发起连接,会产生一个AE_READABLE事件,然后由连接应答处理器负责和客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件和命令请求处理器关联,使得客户端可以向主服务器发送命令请求。
  3. 当客户端向Redis发请求时(不管读还是写请求),客户端socket都会产生一个AE_READABLE事件,触发命令请求处理器。处理器读取客户端的命令内容,然后传给相关程序执行。
  4. 当Redis服务器准备好给客户端的响应数据后,会将socket的AE_WRITABLE事件和命令回复处理器关联,当客户端准备好读取响应数据时,会在socket产生一个AE_WRITABLE事件,由对应命令回复处理器处理,即将准备好的响应数据写入socket,供客户端读取。
  5. 命令回复处理器全部写完到socket后,就会删除该socket的AE_WRITABLE事件和命令回复处理器的映射。

单线程快的原因:

  1. 纯内存操作
  2. 核心是基于非阻塞的IO多路复用机制
  3. 单线程反而避免了多线程的频繁上下文切换带来的性能问题

redis的持久化机制

RDB: Redis DataBase 将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘。
手动触发:

  • save命令,使Redis处于阻塞状态,直到RDB持久化完成,才会响应其他客户端发来的命令,所以在生产环境—定要慎用
  • bgsave命令,fork出一个子进程执行持久化,主进程只在fork过程中有短暂的阻塞,子进程创建之后,主进程就可以响应客户端请求了
    自动触发:
  • save m n :在m秒内,如果有n个键发生改变,则自动触发持久化,通过bgsave执行,如果设置多个、只要满足其一就会触发,配置文件有默认配置(可以注释掉)
  • flushall:用于清空redis所有的数据库,flushdb清空当前redis所在库数据(默认是0号数据库),会清空RDB文件,同时也会生成dump.rdb、内容为空
  • 主从同步:全量同步时会自动触发bgsave命令,生成rdb发送给从节点
    优点:
  1. 整个Redis数据库将只包含一个文件dump.rdb,方便持久化。
  2. 容灾性好,方便备份。
  3. 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis 的高性能
  4. 相对于数据集大时,比AOF的启动效率更高。
    缺点:
  5. 数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
  6. 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百亳秒,甚至是1秒钟。会占用cpu
    AOF:Append Only File 以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录,调操作系统命令进程刷盘。
    当不小心使用flushall命令后,如果开启了AOF功能,可以不用跑路,因为flushall不会清理AOF日志
  7. 所有的写命令会追加到AOF缓冲中。
  8. AOF缓冲区根据对应的策略向硬盘进行同步操作。
  9. 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。4、当Redis重启时,可以加载AOF文件进行数据恢复。
    同步策略:
  • 每秒同步:异步完成,效率非常高,一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失
  • 每修改同步:同步持久化,每次发生的数据变化都会被立即记录到磁盘中,最多丢一条
  • 不同步:由操作系统控制,可能丢失较多数据
    优点:
  1. 数据安全
  2. 通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof工具解决数据一致性问题。
  3. AOF机制的rewrite模式。定期对AOF文件进行重写,以达到压缩的目的
    缺点:
  4. AOF文件比RDB文件大,且恢复速度慢。
  5. 数据集大的时候,比rdb 启动效率低。
  6. 运行效率没有RDB高

Redis的过期键的删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
    (expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
    Redis中同时使用了惰性过期和定期过期两种过期策略。

Redis分布式锁底层是如何实现的?

  1. 首先利用setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
  2. 然后还要利用lua脚本来保证多个redis操作的原子性
  3. 同时还要考虑到锁过期,所以需要额外的一个看门狗定时任务来监听锁是否需要续约
  4. 同时还要考虑到redis节点挂掉后的情况,所以需要采用红锁的方式来同时向N/2+1个节点申请锁,都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到

Redis和Mysql如何保证数据一致

  1. 先更新Mysql,再更新Redis,如果更新Redis失败,可能仍然不一致
  2. 先删涂Redis缓存数据,再更新Mysql,再次查询的时候在将数据添加到缓存中,这种方案能解决1方案的问题,但是在高并发下性能较低,而且仍然会出现数据不一致的问题,比如线程1删除了Redis缓存数据,正在更新Mysql,此时另外一个查询再查询,那么就会把Mysql中老数据又查到Redis中
  3. 延时双删,步骤是:先删除Redis缓存数据,再更新Mysql,延迟几百毫秒再删除Redis缓存数据,这样就算在更新Mysql时,有其他线程读了Mysql,把老数据读到了Redis中,那么也会被删除掉,从而把数据保持一致

redis集群方案

主从
哨兵模式:
sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控redis master和slave 进程是否正常工作。
  • 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果master node挂掉了,会自动转移到slave node 上。
  • 配置中心:如果故障转移发生了,通知client客户端新的master地址。

哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

  • 故障转移时,判断一个master node是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
  • 哨兵通常需要3个实例,来保证自己的健壮性。
  • 哨兵+ redis 主从的部署架构,是不保证数据零丢失的,只能保证redis集群的高可用性。
  • 对于哨兵+redis主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
    Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行

方案说明

  • 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384个槽位
  • 每份数据分片会存储在多个互为主从的多节点上
  • 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
  • 同一分片多个节点间的数据不保持强一致性
  • 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
  • 扩容时需要需要把旧节点的数据迁移一部分到新节点
    在redis cluster架构下,每个redis要放开两个端口号,比如一个是6379,另外一个就是加1w的端口号,比如16379。

16379端口号是用来进行节点间通信的,也就是cluster bus的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带

优点

  • 无中心架构,支持动态扩容,对业务透明
  • 具备Sentinel的监控和自动Failover(故障转移)能力
  • 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • 高性能,客户端直连redis服务,免去了proxy代理的损耗
    缺点
  • 运维也很复杂,数据迁移需要人工干预
  • 只能使用0号数据库
  • 不支持批量操作(pipeline管道操作)
  • 分布式逻辑和存储模块耦合等

Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。Java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool
优点
优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强
缺点
由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。

客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化

Redis如何配置Key的过期时间?他的实现原理是什么?

redis设置key的过期时间:

  1. EXPIRE
  2. SETEX
    实现原理:
  3. 定期删除:每隔一段时间,执行一次删除过期key的操作。
  4. 懒汉式删除:当使用get、getset等指令去获取数据时,判断key是否过期。过期后,就先把key删除,再执行后面的操作。
    Redis是将两种方式结合来使用。
    懒汉式删除
    定期删除:平衡执行频率和执行时长。
    定期删除时会遍历每个database(默认16个),检查当前库中指定个数的key(默认是20个)。随机抽查这些key,如果有过期的,就删除。
    程序中有一个全局变量记录到秒到了哪个数据库。

布隆过滤器原理,优缺点

位图: int[10],每个int类型的整数是4*8=32个bit,则int[10]—共有320 bit,每个bit非o即1,初始化时都是0

添加数据时,将数据进行hash得到hash值,对应到bit位,将该bit改为1,hash函数可以定义多个,则一个数据添加会将多个(hash函数个数) bit改为1,多个hash函数的目的是减少hash碰撞的概率

查询数据: hash函数计算得到hash值,对应到bit中,如果有一个为0,则说明数据不在bit中,如果都为1,则该数据可能在bit中
优点:

  • 占用内存小
  • 增加和查询元素的时间复杂度为:O(K),(K为哈希函数的个数,一般比较小),与数据量大小无关
  • 哈希函数相互之间没有关系,方便硬件并行运算
  • 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  • 数据量很大时,布隆过滤器可以表示全集
  • 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
    缺点:
  • 误判率,即存在假阳性(False Position),不能准确判断元素是否在集合中
  • 不能获取元素本身
  • 一般情况下不能从布隆过滤器中删除元素

缓存过期都有哪些策略?

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量

  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,但是很消耗内存、许多的过期数据都还存在内存中。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key (是随机的),并清除其中已过期的key。该策略是定时过期和惰性过期的折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

缓存雪崩、缓存穿透、缓存击穿

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请东而崩掉。可能来源于大量缓存同时过期或缓存重启
解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
  • 缓存预热
  • 互斥锁

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。可能来源于攻击
解决方案:

  • 接口层增加校验,如用户鉴权校验,id做基础校验,id<=O的直接拦截;
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:

  • 设置热点数据永远不过期
  • 加互斥锁

redis分布式锁实现

setnx+setex:存在设置超时时间失败的情况,导致死锁一
set(key,value,nx,px):将setnx+setex变成原子操作问题:

  • 任务超时,锁自动释放,导致并发问题。使用redisson解决(看门狗监听,自动续期)
  • 以及加锁和释放锁不是同一个线程的问题。在value中存入uuid(线程唯一标识),删除锁时判断该标识(使用lua保证原子操作)
  • 不可重入,使用redisson解浏(实现机制类似AQS,计数)·异步复制可能造成锁丢失,使用redLock解决
  1. 顺序向五个节点请求加锁
  2. 根据一定的超时时间来推断是不是跳过该节点
  3. 三个节点加锁成功并且花费时间小于锁的有效期
  4. 认定加锁成功

简述redis九大数据结构

  • string:字符串
  • List:列表
  • Hash:哈希表
  • Set:无序集合
  • Sorted Set:有序集合
  • bitmap:布隆过滤器
  • GeoHash:坐标,借助Sorted Set实现,通过zset的score进行排序就可以得到坐标附近的其它元素,通过将score还原成坐标值就可以得到元素的原始坐标
  • HyperLogLog:统计不重复数据,用于大数据基数统计
  • Streams:内存版的kafka

#{}和${}的区别是什么

{}是预编译处理、是占位符,${}是字符串替换、是拼接符;

Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值,调用Statement来赋值;

{}的变量替换是在DBMS中、变量替换后,#{}对应的变量自动加上单引号;

${}的变量替换是在DBMS外、变量替换后,${}对应的变量不会加上单引号;
使用#{}可以有效的防止SQL注入,提高系统安全性。

ApplicationContext与BeanFactory的区别

BeanFactory是Spring中非常核心的组件,表示Bean工厂,可以生成Bean,维护Bean,而ApplicationContext继承了BeanFactory,所以ApplicationContext拥有BeanFactory所有的特点,也是一个Bean工厂,但是ApplicationContext除开继承了BeanFactory之外,还继承了诸如
EnvironmentCapable、MessageSource、ApplicationEventPublisher等接口,从而ApplicationContext还有获取系统环境变量、国际化、事件发布等功能,这是BeanFactory所不具备的
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能:

  1. 继承MessageSource,因此支持国际化。
  2. 统一的资源文件访问方式。
  3. 提供在监听器中注册bean的事件。
  4. 同时加载多个配置文件。
  5. 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
  • BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
  • ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
  • 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader
  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

Spring Boot、Spring MVC和Spring有什么区别

  • spring是一个IOC容器,用来管理bean的,使用依赖注入(DI)的方式来控制翻转, 可以方便控制整合各种框架,同时利用AOP机制优化了OOP所带来的代码重复问题,将不同类不同方法的共同处理抽取成切面,自动注入给方法执行,例如日志,异常等。
  • springmvc是spring对web框架应用的解决方案,通过一个总的前端控制器servlet用来接受请求,定义了一套路由策略(从url到handle的映射)以及适配执行handle,将handle结果使用视图解析技术生成视图展示给前端。
  • springboot是spring和springmvc整合到一起的快速开发工具包,能让开发人员更加便捷的开发spring+springmvc应用,采用约定大于配置,摆脱了springmvc开发中繁多的配置难题,同时整合了一系列的解决方案(通过starter机制),redis、mybatis等在pom中注册依赖即可使用。

SpringBoot是如何启动Tomcat的

  1. 首先,SpringBoot在启动时会先创建一个Spring容器
  2. 在创建Spring容器过程中,会利用@ConditionalOnClass技术来判断当前classpath中是否存在Tomcat依赖,如果存在则会生成一个启动Tomcat的Bean
  3. Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端口等,然后启动Tomcat

SpringBoot中常用注解及其底层实现

  1. @SpringBootApplication注解:这个注解标识了一个SpringBoot工程,它实际上是另外三个注解的组合,这三个注解是:
    • @SpringBootConfiguration:这个注解实际就是一个@Configuration,表示启动类也是一个配置类
    • @EnableAutoConfiguration:向Spring容器中导入了一个Selector,用来加载ClassPath下SpringFactories中所定义的自动配置类,将这些自动加载为配置Bean
    • @ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫描的路径是启动类所在的当前目录
  2. @Bean注解:用来定义Bean,类似于XML中的cbean>标签,Spring在启动时,会对加了@Bean注解的方法进行解析,将方法的名字做为beanName,并通过执行方法得到bean对象
  3. @Controller、@Service、@ResponseBody、@Autowired都可以说

SpringBoot中配置文件的加载顺序是怎样的?

优先级从高到低,高优先级的配置覆盖低优先级的配置,所有配置会形成互补配置。

  1. 命令行参数。所有的配置都可以在命令行上进行指定;
  2. Java系统属性(System.getProperties0) ;
  3. 操作系统环境变量;
  4. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
  5. jar包内部的application-[profile}.properties或application.yml(带spring.profile)配置文件再来加载不带profile
  6. jar包外部的application.properties或application.yml(不带spring.profile)配置文件
  7. jar包内部的application.properties或application.ymI(不带spring.profile)配置文件
  8. @Configuration注解类上的@PropertySource

Spring Boot自动配置原理

@import +@Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到META-INF/spring.factories下使用Spring spi扫描META-INF/spring.factories下的配置类
使用@import导入自动配置类
code

SpringMVC的底层工作流程

  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping 处理器映射器。
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter 处理器适配器。
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
  6. Controller 执行完成返回ModelAndView.
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  9. ViewReslover解析后返回具体View。
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。

Spring MVC的主要组件

Handler:也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler

  1. HandlerMapping
    initHandlerMappings(context),处理器映射器,根据用户请求的资源uri来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行,这就是HandlerMapping需要做的事。
  2. HandlerAdapter
    initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
  3. HandlerExceptionResolver
    initHandlerExceptionResolvers(context),其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
  4. ViewResolver
    initViewResolvers(context),ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
  5. RequestToViewNameTranslator
    initRequestToViewNameTranslator(context),ViewResolver是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
  6. LocaleResolver
    initLocaleResolver(context),解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
  7. ThemeResolver
    initThemeResolver(context),用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。
  8. MultipartResolver
    initMultipartResolver(context),用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
  9. FlashMapManager
    initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

SpringMVC中的控制器是不是单例模式?如果是,如何保证线程安全?

控制器是单例模式。
单例模式下就会有线程安全问题。
Spring中保证线程安全的方法

  1. 将scop设置成非singleton。prototype, request。
  2. 最好的方式是将控制器设计成无状态模式。在控制器中,不要携带数据。但是可以引用无状态的service和dao。

spring事务传播机制

多个事务方法相互调用时,事务如何在这些方法间传播

方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法Al事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

  • REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
  • SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
  • MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
  • REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。
  • NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
  • NEVER:不使用事务,如果当前事务存在,则抛出异常
  • NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

Spring框架中Bean的创建过程是怎样的?

首先,简单来说,Spring框架中的Bean经过四个阶段:实例化-》属性赋值-》初始化-》销毁
然后:具体来说,Spring中Bean经过了以下几个步骤:

  1. 实例化: new xxx();两个时机:
    1. 当客户端向容器申请一个Bean时;
    2. 当容器在初始化一个Bean时发现还需要依赖另一个Bean。BeanDefinition对象保存。
  2. 设置对象属性(依赖注入): Spring通过BeanDefinition找到对象依赖的其他对象,并将这些对象赋予当前对象。
  3. 处理Aware接口: Spring会检测对象是否实现了xxxAware接口,如果实现了,就会调用对应的方法。BeanNameAware、BeanClassLoaderAware、BeanFactoryAware、ApplicationContextAware
  4. BeanPostProcessor前置处理:调用BeanPostProcessor的postProcessBeforelnitialization方法
  5. InitializingBean: Spring检测对象如果实现了这个接口,就会执行他的afterPropertiesSet()方法,定制初始化逻辑。
  6. init-method: 如果Spring发现Bean配置了这个属性,就会调用他的配置方法,执行初始化逻辑。@PostConstruct
  7. BeanPostProcessor后置处理:调用BeanPostProcessor的postProcessAfterInitialization方法

    到这里,这个Bean的创建过程就完成了,Bean已经可以正常使用了

  8. DisposableBean: 当Bean实现了这个接口,在对象销毁前就会调用destory()方法。
  9. destroy-method:@PreDestroy