跳到主要内容

Mybatis与数据库相关面试题

什么是MyBatis?

  • Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
  • MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录。

  • SQL语句的编写工作量较大。

  • SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

ORM是什么?

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单来说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

JDBC编程有哪些不足之处,MyBatis是如何解决的?

  • 1、数据连接创建、释放频繁造成系统资源浪费从而影响系统性能

    • 解决:在mybatis-config.xml中配置数据链接池,使用连接池统一管理数据库连接。
  • 2、sql语句写在代码中造成代码不易维护

    • 解决:将sql语句配置在XXXXmapper.xml文件中与java代码分离。
  • 3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

    • 解决:Mybatis自动将java对象映射至sql语句。
  • 4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

    • 解决:Mybatis自动将sql执行结果映射至java对象。

Hibernate 和 MyBatis 有什么区别?

  • 都是对jdbc的封装,都是应用于持久层的框架。

  • MyBatis 是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单

    Mybatis在查询关联对象或关联集合对象时,需要手动编写SQL来完成

  • Hibernate 是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取

MyBatis使用过程

  1. 创建SqlSessionFactory:可以从配置或者直接编码来创建SqlSessionFactory
  2. 通过SqlSessionFactory创建SqlSession:SqlSession(会话)可以理解为程序和数据库之间的桥梁
  3. 通过sqlsession执行数据库操作 :可以通过 SqlSession 实例来直接执行已映射的 SQL 语句或先获取Mapper(映射),然后再执行SQL语句
  4. 调用session.commit()提交事务
  5. 调用session.close()关闭会话

Mybatis生命周期

一般MyBatis生命周期就是这些组件的生命周期

  • SqlSessionFactoryBuilder

    一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的生命周期只存在于方法的内部。

  • SqlSessionFactory

    SqlSessionFactory 是用来创建SqlSession的,相当于一个数据库连接池,每次创建SqlSessionFactory都会使用数据库资源,多次创建和销毁是对资源的浪费。所以SqlSessionFactory是应用级的生命周期,而且应该是单例的。

  • SqlSession

    SqlSession相当于JDBC中的Connection,SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的生命周期是一次请求或一个方法。

  • Mapper

    映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的,它的生命周期在sqlsession事务方法之内,一般会控制在方法级。

MyBatis通常也是和Spring集成使用,Spring可以帮助我们创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到我们的 bean 中。

在mapper中如何传递多个参数?

  1. 顺序传参法 :{}里面的数字代表传入参数的顺序,不建议使用,容易顺序搞混
  2. @Param注解传参:{}里面的名称对应的是注解@Param括号里面修饰的名称。适用于参数少的情况
  3. Map传参:{}里面的名称对应的是Map里面的key名称,适合传递多个参数
  4. java bean传参:{}里面的名称对应的是User类里面的成员属性,这种方法直观 ,代码可读性强

实体类属性名和表中字段名不一样

  1. 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
  2. 通过resultMap来映射字段名和实体类属性名的一一对应的关系。

Mybatis映射Enum枚举类

Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。

TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。

#{}和${}的区别?

#{}是占位符,Mybatis在处理#{}时,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来预编译处理。${}是拼接符,字符串替换,没有预编译处理。

#{}可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入

#{}的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外。

模糊查询like语句该怎么写?

  1. ’%${question}%’ 可能引起SQL注入,不推荐
  2. "%"#{question}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
  3. CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,(推荐)

Mybatis一对一、一对多、多对多关联查询吗?

一对一使用实体类一一对应

一对多使用collection 集合一一对应

多对一、多对多也 使用association 和collection处理

Mybatis是否支持延迟加载?原理?

  • Mybatis支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
  • 它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。

如何获取生成的主键?

新增标签中添加:keyProperty=" ID " 即可。

MyBatis支持动态SQL吗?

MyBatis中有一些支持动态SQL的标签,它们的原理是使用OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。

  1. 根据条件来组成where子句
  2. choose (when, otherwise),类似Java 中的 switch 语句有点像
  3. trim (where, set)
  4. foreach 可以对集合进行遍历,执行批量操作

MyBatis如何执行批量操作?

foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach标签的属性主要有item,index,collection,open,separator,close。

  • item   表示集合中每一个元素进行迭代时的别名,随便起的变量名;
  • index   指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
  • open   表示该语句以什么开始,常用“(”;
  • separator 表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
  • close   表示以什么结束,常用“)”。

使用ExecutorType.BATCH

Mybatis内置的ExecutorType有3种,默认为simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql;而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优;但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,在某些情况下不符合业务的需求。

Mybatis的一级、二级缓存?

一级缓存: 基于 PrepertualCache 的 HashMap 本地缓存,其存储作用域为SqlSession,各个SqlSession之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis默认打开一级缓存。

二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同之处在于其存储作用域为 Mapper(Namespace),可以在多个SqlSession之间共享,并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置。

MyBatis的工作原理

生成会话工厂会话运行

Mybatis都有哪些Executor执行器?

  • SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
  • ReuseExecutor:就是重复使用Statement对象。
  • BatchExecutor:,它缓存了多个Statement对象,与JDBC批处理相同。

Mybatis中如何指定使用哪一种Executor执行器?

  • 在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,
  • 手动创建,sqlSessionFactory.openSession(ExecutorType.BATCH);

Mybatis的插件运行原理

Mybatis会话的运行需要ParameterHandler、ResultSetHandler、StatementHandler、Executor这四大对象的配合,插件的原理就是在这四大对象调度的时候,插入一些我我们自己的代码。

使用Plugin生成代理对象,代理对象在调用方法的时候,就会进入invoke方法,在invoke方法中,如果存在签名的拦截方法,插件的intercept方法就会在这里被我们调用,然后就返回结果。如果不存在签名方法,那么将直接反射调用我们要执行的方法。

如何编写一个插件?

实现Mybatis的Interceptor接口并重写intercept()方法

再给插件编写注解,确定要拦截的方法和对象

最后,在MyBatis配置文件里面配置插件:指定拦截器类名

MyBatis是如何分页的

MyBatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页。

可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的原理是什么?

  • 使用Mybatis提供的插件接口,实现自定义插件,拦截Executor的query方法

  • 在执行查询的时候,拦截并重写待执行的sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

  • 举例:select from student,拦截sql后重写为:select t. from (select * from student) t limit 0, 10

char和varchar的区别

char设置多少长度就是多少长度,varchar可以改变长度,所以char的空间利用率不如varchar的空间利用率高。

因为长度固定,所以存取速度要比varchar快。

char适用于固定长度的字符串,比如身份证号、手机号等,varchar适用于不固定的字符串。

数据库的三大范式

第一范式(1NF): 保证字段不可再分,保证原子性。

第二范式(2NF): 满足1NF前提下,表的每一列都必须和主键有关系。消除部分依赖关系。

第三范式(3NF): 满足2NF前提下,表的每一列比必须和主键有直接关系,不能是间接关系。消除传递依赖关系。

索引是什么

是一种高效获取数据的数据结构,相当于目录,更快的找到数据,是一个文件,占用物理空间。

索引的优点和缺点

优点:
提高检索的速度。
索引列对数据排序,降低排序成本,有利于调优。
缺点:
索引也是一个文件,所以会占用空间。
降低更新的速度,因为不光要更新数据,还要更新索引。

索引的类型

  1. 普通索引: 基本索引类型,允许定义索引的字段为空值和重复值。
  2. 唯一索引: 索引的值必须唯一,允许定义索引的字段为空值。
  3. 主键索引: 索引的值必须唯一,不可以为空。
  4. 复合索引:多个字段加索引,遵守最左前缀匹配规则。
  5. 全文索引:关键字是fulltext。全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较,它更像是一个搜索引擎,基于相似度的查询,而不是简单的where语句的参数匹配。
  6. 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、 POINT、LINESTRING、POLYGON。

索引怎么设计(优化)

  1. 选择唯一性索引:值是唯一的,查询的更快。
  2. 经常作为查询条件的字段加索引。
  3. 为经常需要排序、分组和联合操作的字段建立索引:order by、group by、union(联合)、distinct(去重)等
  4. 限制索引个数:索引数量多,需要的磁盘空间就越多,更新表时,对索引的重构和更新就很费劲。
  5. 表数据少的不建议使用索引(百万级以内):数据过少,有可能查询的速度,比遍历索引的速度都快。
  6. 删除不常用和不再使用的索引。

怎么避免索引失效

①某列使用范围查询(>、<、like、between and)时, 右边的所有列索引也会失效。
②不要对索引字段进行运算。
字符串不加单引号,造成索引失效。

索引的原理

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上

评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。

  • hash算法

优点:通过字段的值计算的hash值,定位数据非常快。

缺点:不能进行范围查找,因为散列表中的值是无序的,无法进行大小的比较。

  • B+tree

MyISAM引擎使用B+Tree作为索引结构, 叶节点的data域存放的是数据记录的地址

InnoDB的叶节点的data域存放的是数据,相比MyISAM效率要高一些,但是比较占硬盘内存大

二叉查找树、B树、B+树

  1. 二叉查找树:查询次数和比较次数都是最小的,但是索引是存在磁盘的,当数据量过大的时候,不能直接把整个索引文件加载到内存,需要分多次IO。

  2. B树: 是一种多路查询树,每个节点包含K个子节点,K是B树的树高。虽然比较的次数比较多,但是B树IO的次数要比二叉查少,因为B树的高度可以更低。

  3. B+树: B树的升级版,只有叶子节点储存的是索引元素指向数据库的数据。

    ①减少了磁盘IO: 因为B+树非叶子节点不会存放数据,只有关键字,所以磁盘页存的数据就会多了,读入的关键字多了,IO次数就少了。
    ②B+树适合范围查找: 这才是关键,因为数据库大部分都是范围查找,B+树的叶子节点是有序链表,直接遍历就行,而B树的范围查找可能两个节点距离很远,只能通过中序遍历去查找,所以使用B+树更合适。

MylSAM和InnoDB的区别

MylSAM:mysql5.5之前的存储引擎,是表锁(悲观锁)级别的,不支持事务和外键。
InnoDB:mysql5.5之后的存储引擎,是行锁(乐观锁)级别的,支持事务和外键。

Memory: 所有数据置于内存的存储引擎,拥有极高的插入,更新和查询效率。但是会占用和数据量成正比的内存空间。并且其内容会在MYSQL重新启动是会丢失。

Archive :非常适合存储大量的独立的,作为历史记录的数据。因为它们不经常被读取。Archive 拥有高效的插入速度,但其对查询的支持相对较差。

事务的四个特性

  1. 原子性(Atomicity ):事务内的所有操作是一个整体,要么整体失败,要么整体成功。
  2. 一致性(Consistency):事务提交前后都是正确的状态。从一个正确的状态迁移到另一个正确的状态。
  3. 隔离性(Isolation):事务之间互相分离,事务提交前对其他事务不可见。
  4. 持久性(Durability):事务一旦提交,则其结果是永久的。

事务的隔离级别

  1. 读未提交(Read uncommitted )

    一个事务会读取到另一个事务未提交的数据,会造成脏读

  2. 读已提交(Read committed)

    一个事务要等另一个事务提交之后才能读取数据,会造成不可重复读

  3. 可重复读(Repeatable read))

    开始读取数据时(事务开启),不再允许更新操作,可避免脏读和不可重复读。但是会造成幻读

  4. 序列化(Serializable)

    最高的事务隔离级别,在该级别下,事务串行化执行,可以避免脏读、不可重复读、幻读,但是这种级别性能很低。

常用的聚合函数

  1. sum(列名) 求和     
  2. max(列名) 最大值     
  3. min(列名) 最小值     
  4. avg(列名) 平均值     
  5. first(列名) 第一条记录
  6. last(列名) 最后一条记录
  7. count(列名) 统计记录数不包含null值 count(*)包含null值。

字符串函数:CONCAT、CONCAT_WS(第一个参数是分隔符)

日期函数:CURDATE current_date

窗口函数:MYSQL8新特性:比如使用ROW_NUMBER 函数,对每个部门的员工按照薪资排序,并给出排名

关联查询

内连接(inner join): 查询两个表匹配数据。

左连接(left join): 查询左表全部行以及右表匹配的行。

右连接(right join): 查询右表全部行以及左表匹配的行。

in和exists的区别

in(): 适合子表(子查询)比主表数据小的情况。
exists(): 适合子表(子查询)比主表数据大的情况。

drop、truncate、delete的区别

  1. 速度: drop > truncate > delete。
  2. 回滚: delete支持,truncate和drop不支持。
  3. 删除内容: delete表结构还在,删除部分或全部数据,不释放空间。truncate表结构还在,删除全部数据,释放空间。drop表结构和数据不在,包括索引和权限,释放空间。
  4. 高水位重置:truncate操作会重置高水位线,数据库容量也会被重置,之后再进行DML操作速度也会有提升。
  5. truncate语句执行以后,id标识列还是按顺序排列,保持连续;delete语句执行后,ID标识列不连续

MYSQL的存储过程

delimiter -- 自定义结束符号
create procedure 储存名([in,out,inout]) -- 参数名 数据类型
begin
    sql语句  -- sql语句需要加分号
end 
自定义结束符号 delimiter;

类似于java中的方法,SQL语言的代码封装和重用。

  1. 有输入输出、变量控制语句
  2. 模块化,封装,代码复用
  3. 速度快,只有首次执行需要经过编译和优化步骤,后续可以直接执行

MYSQL的游标

游标(cursor)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用光标对结果集进行循环的处理。光
标的使用包括光标的声明、OPEN、FETCH 和 CLOSE.

触发器

  1. 触发器,就是一种特殊的存储过程。触发器和存储过程一样是一个能够完成特定功能、存储在数据库服务器上SQL片段,但是触发器无需调用,当对数据库表中的数据执行DML操作时自动触发这个SQL片段的执行,无需手动调用。
  2. 在增删改前后触发。
  3. 触发器定义在表上,附着在表上。

触发器注意事项

  1. MYSQL中触发器中不能对本表进行 insert ,update ,delete 操作,以免递归循环触发
  2. 尽量少使用触发器,假设触发器触发每次执行1s,insert table 500条数据,那么就需要触发500次触发器,光是触发器执行的时间就花费了500s,而insert 500条数据一共是1s,那么这个insert的效率就非常低了。
  3. 触发器是针对每一行的;对增删改非常频繁的表上切记不要使用触发器,因为它会非常消耗资源。

MYSQL的锁机制

从对数据操作的粒度分 :

  1. 表锁:操作时,会锁定整个表。
  2. 行锁:操作时,会锁定当前操作行。

    从对数据操作的类型分:

  3. 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。

  4. 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁。

不同的存储引擎支持不同的锁机制。表级锁MyISAM、InnoDB、MEMORY、BDB都支持,行级锁InnoDB支持。

InnoDB行锁

支持事务

(1)行锁模式

nnoDB 实现了以下两种类型的行锁。

  1. 共享锁(S):又称为读锁,简称S锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
  2. 排他锁(X):又称为写锁,简称X锁,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
  3. 对于UPDATE、DELETE和INSERT语句,InnoDB会自动给数据集加排他锁(X);
  4. 对于普通SELECT语句,InnoDB不会加任何锁;

MYSQL性能优化

  1. 从设计上优化
  2. 从查询上优化
  3. 从索引上优化
  4. 从存储上优化

定位低效率的执行SQL

慢查询日志、explain执行计划

避免索引失效

  1. 全值匹配
  2. 最左原则:如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
  3. 范围查询右边的列,不能使用索引
  4. 不要在索引列上进行运算操作, 索引将失效。
  5. 尽量使用覆盖索引,避免select
  6. 用or分割开的条件, 那么涉及的索引都不会被用到。

优化insert语句

  1. 如果需要同时对一张表插入很多行数据时,应该尽量使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗。使得效率比分开执行的单个insert语句快。
  2. 在事务中进行数据插入

优化order by语句

第一种是通过对返回数据进行排序,也就是通常说的 filesort 排序,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。

第二种通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高

(1)Filesort 的优化

通过创建合适的索引,能够减少 Filesort 的出现

加快 Filesort的排序操作 ,MySQL 有两种Filesort 排序算法 :

  • 两次扫描算法 :MySQL4.1 之前,使用该方式排序。首先根据条件取出排序字段和行指针信息,然后在排序区 sort buffer 中排序,如果 sort buffer不够,则在临时表 temporary table 中存储排序结果。完成排序之后,再根据行指针回表读取记录,该操作可能会导致大量随机I/O 操作。
  • 一次扫描算法:一次性取出满足条件的所有字段,然后在排序区 sort buffer 中排序后直接输出结果集。排序时内存开销较大,但是排序 效率比两次扫描算法要高。 MySQL 通过比较系统变量max_length_for_sort_data 的大小和Query语句取出的字段总大小, 来判定是否那种排序算法,如果 max_length_for_sort_data 更大,那么使用第二种优化之后的算法;否则使用第一种。

可以适当提高 sort_buffer_size 和 max_length_for_sort_data 系统变量,来增大排序区的大小,提高排序的效率。

优化group by语句

可以执行order by null 禁止排序。

能使用连接语句不使用子查询语句。

连接(Join)查询之所以更有效率一些 ,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。

redis是什么

是由C语言开发的高性能的非关系型数据库。

redis的存储结构

  1. String: 键值对。应用场景:缓存、短信验证码等。

    基于简单动态字符串(SDS)实现,存储上限为512mb。

  2. List: 相当于链表。应用场景:消息队列等。

    在3.2版本之前,Redis采用ZipList和LinkedList来实现List,当元素数量小于512并且元素大小小于64字节时采用ZipList编码,超过则采用LinkedList编码。

    在3.2版本之后,Redis统一采用QuickList来实现List:

    • LinkedList :普通链表,可以从双端访问,内存占用较高,内存碎片较多
    • ZipList :压缩列表,可以从双端访问,内存占用低,存储上限低
    • QuickList:LinkedList + ZipList,可以从双端访问,内存占用较低,包含多个ZipList,存储上限高
  3. Hash: 键值对,可以存放多个键值对,适合存储对象。应用场景:缓存等,比String更节省空间。

    底层使用压缩列表ziplist 或者 字典dict

    • Dict由三部分组成,分别是:哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict)
  4. Set: 键值对,无序,不可重复。应用场景:共同好友等。

    为了查询效率和唯一性,set采用Dict实现。Dict中的key用来存储元素,value统一为null。

  5. Zset: 键值对,不可重复,有一个权重参数,按这个排序。应用场景:排行榜。

为什么要用redis和redis为什么那么快

  1. 存内存操作,速度快。TPS可以达到十万级。
  2. 支持事务和持久化。
  3. 采用IO多路复用:单线程处理多个连接请求。

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

  1. 缓存雪崩: 缓存中的数据大量过期,然后大量请求去访问数据库,导致数据库压力过大或者down机。
    解决:
    ①使缓存过期时间分布比较均匀。
    ②设置高可用集群。
  2. 缓存穿透: 缓存和数据库都没有要的数据,然后会一直请求,给数据库压力。
    解决:
    ①给缓存设置一个空值或者默认值。
    ②使用布隆过滤器。先通过过滤器判断数据是否存在,存在再继续向下查。

  3. 缓存击穿: 某个key过期,恰巧有大量请求访问这个key,然后导致数据库压力过大。
    解决:
    ①使用互斥锁,只让一个请求去访问,然后把数据带到缓存,然后剩余请求再去访问请求。
    ②设置缓存不过期。

redis实现分布式锁

分布式锁: 是控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁。redis可以当分布式锁。

我们利用redis 的setNx 方法,当有多个线程进入时,我们就利用该方法,第一个线程进入时,redis 中就有这个key 了,返回了1,如果结果是1,则表示他抢到了锁,那么他去执行业务,然后再删除锁,退出锁逻辑,没有抢到锁的哥们,等待一定时间后重试即可

分布式锁的特征

  1. 互斥性: 任意时刻,只有一个客户端能持有锁
  2. 锁超时释放: 持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁
  3. 可重入性: 一个线程如果获取了锁之后,可以再次对其请求加锁
  4. 高性能和高可用: 加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效
  5. 安全性: 锁只能被持有的客户端删除,不能被其他客户端删除

redis的应用场景

①缓存 ②数据库 ③排行榜 ④计数器 ⑤消息队列 ⑥分布式锁 ⑦共享Session 、秒杀下单、

UV(独立访客量)统计:Hyperloglog(HLL)是从Loglog算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值,Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,有小于0.81%的误差。

redis的持久机制

RDB(默认): 一定时间内将内存数据以快照形式保存到硬盘中。某个时间点把数据写到临时文件,然后替换上次持久化的文件。

优点: ①恢复大的数据集时,比AOF效率更高。 ②因为只有一个文件,所以容灾性好。 缺点: 不安全,数据丢失。

AOF: 会把每次写的命令记录到日志文件(同一个日志文件,不会替换),redis重启会将持久化的日志文件恢复。如果两种持久化都开启,优先恢复AOF。

优点: 安全,几乎不会丢失数据。 缺点: ①AOF文件比RDB文件大,且恢复速度慢。 ②数据集大的时候,恢复效率比RDB慢。

redis的过期策略

定时删除: 每个key都需要创建一个定时器,到时间就会清除key,所以对内存很友好,但是会占用cpu的大量资源去处理过期。
惰性删除: 用到key的时候再去判断过没过期,过期就清除,可以节省cpu的资源,但是对内存不友好。可能出现大量过期的key没有清除。
定期删除: 定时删除和惰性删除的结合体,每隔一段时间抽取设置过期的key检测是否过期(默认是1s执行10次清除,每次抽取5个检测),过期就清除。

redis的淘汰策略

①volatile-lru: 针对设置了过期时间的key,使用lru算法(最近最少使用的key:根据时间)进行淘汰。
②allkeys-lru: 针对所有key使用lru算法进行淘汰。
③volatile-lfu: 针对设置了过期时间的key,使用lfu算法(最近最少使用:根据计数器)进行淘汰。
④allkeys-lfu: 针对所有key使用lfu算法进行淘汰。
⑤volatile-random: 从所有设置了过期时间的key中使用随机淘汰机制进行淘汰。
⑥allkeys-random: 针对所有的key使用随机淘汰机制进行淘汰。
⑦volatile-ttl: 删除生存时间最近的一个键。
⑧noeviction(默认): 不删除键,值返回错误。

redis怎么设置高可用或者集群

  1. 主从复制: 一个主,一个或多个从,从节点在主节点复制数据。主节点负责写,从节点负责读。可以更好的分担主节点的压力,但是如果主节点宕机了,会导致部分数据不同步。
  2. 哨兵模式(重点): 也是之中主从模式,哨兵定时去查询主机,如果主机太长时间没有相应,多个哨兵就投票选出新的主机。提高了可用性,但是在选举新的主节点期间,还是不能工作。
  3. Cluster集群模式: 采用多主多从(一般都是三主三从),按照规则进行分片,每台redis节点储存的数据不一样,解决了单机储存的问题。还提供了复制和故障转移功能。配置比较麻烦。

什么是多级缓存

多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能:

  • 浏览器访问静态资源时,优先读取浏览器本地缓存
  • 访问非静态资源(ajax查询数据)时,访问服务端
  • 请求到达Nginx后,优先读取Nginx本地缓存
  • 如果Nginx本地缓存未命中,则去直接查询Redis(不经过Tomcat)
  • 如果Redis查询未命中,则查询Tomcat
  • 请求进入Tomcat后,优先查询JVM进程缓存
  • 如果JVM进程缓存未命中,则查询数据库

数据同步策略

  1. 设置有效期:给缓存设置有效期,到期后自动删除。再次查询时更新
  2. 同步双写:在修改数据库的同时,直接修改缓存
  3. 异步通知:修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据

1、kafka的消费者是pull(拉)还是push(推)模式,这种模式有什么好处?

Kafka 遵循了一种大部分消息系统共同的传统的设计:producer 将消息推送到 broker,consumer 从broker 拉取消息。
优点:pull模式消费者自主决定是否批量从broker拉取数据,而push模式在无法知道消费者消费能力情况下,不易控制推送速度,太快可能造成消费者奔溃,太慢又可能造成浪费。
缺点:如果 broker 没有可供消费的消息,将导致 consumer 不断在循环中轮询,直到新消息到到达。为了避免这点,Kafka 有个参数可以让 consumer阻塞直到新消息到达(当然也可以阻塞直到消息的数量达到某个特定的量这样就可以批量发送)。

2、kafka维护消息状态的跟踪方法

Kafka中的Topic 被分成了若干分区,每个分区在同一时间只被一个 consumer 消费。然后再通过offset进行消息位置标记,通过位置偏移来跟踪消费状态。同时也无需维护消息的状态,不用加锁,提高了吞吐量。

3、zookeeper对于kafka的作用是什么?

Zookeeper 主要用于在集群中不同节点之间进行通信,在 Kafka 中,它被用于提交偏移量,因此如果节点在任何情况下都失败了,它都可以从之前提交的偏移量中获取,除此之外,它还执行其他活动,如: leader 检测、分布式同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。

4、kafka判断一个节点还活着的有那两个条件?

(1)节点必须维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接

(2)如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久

5、讲一讲 kafka ack 的三种机制

0:生产者不会等待 broker 的 ack,这个延迟最低但是存储的保证最弱当 server 挂掉的时候就会丢数据。
1:生产者会等待 ack 值, leader 副本确认接收到消息后发送 ack ,但是如果 leader挂掉后他不确保是否复制完成,新 leader 也会导致数据丢失。
-1(all):生产者会等所有的 follower 的副本收到数据后才会受到 leader 发出的ack,这样数据不会丢失。

6、kafka 分布式(不是单机)的情况下,如何保证消息的顺序消费?

不同 partition 之间不能保证顺序。因此你可以指定 partition,将相应的消息发往同 1个 partition,并且在消费端,Kafka 保证1 个 partition 只能被1 个 consumer 消费,就可以实现这些消息的顺序消费。

7、kafka 如何不消费重复数据?比如扣款,我们不能重复的扣。

指定位移提交数据,同步提交位移。

  1. 位移提交

    对于Kafka中的分区而言,它的每条消息都有唯一的offset,用来表示消息在分区中的位置。

    当我们调用poll()时,该方法会返回我们没有消费的消息。当消息从broker返回给消费者时,broker并不

    跟踪这些消息是否被消费者接收到;Kafka让消费者自身来管理消费的位移,并向消费者提供更新位移

    的接口,这种更新位移方式称为提交(commit)。

    带来两个问题

    重复消费:同一个消费组中,两个消费者A和B,B消费之后未提交,造成A重复消费

    消息丢失:同一个消费组中,一个消费者拉取消息后,提交了位移,但是没有读取,此时宕机了,消息丢失

  2. 提交方式

    自动提交:enable.auto.commit设置为true 。那么消费者会在poll方法调用后每隔5秒(由auto.commit.interval.ms指定)提交一次位移

    注意:这种方式可能会导致消息重复消费。假如,某个消费者poll消息后,应用正在处理消息,

    在3秒后Kafka进行了重平衡,那么由于没有更新位移导致重平衡后这部分消息重复消费。

    手动提交。手动提交有一个缺点,那就是当发起提交调用时应用会阻塞。当然我们可以减少手动提交的频率,但这个会增加消息重复的概率(和自动提交一样)。

    异步提交。也会出现重复消费,当消费者A提交后,同时B也提交了,但A失败了,那么B就有可能覆盖掉A

  3. 指定位移消费。

    seek()方法:追踪以前的消费或者回溯消费。可以指定offset位置开始消费,也可以从末尾段开始消费。

8、讲一下kafka集群的组成?

Kafka集群通常由多个代理组成以保持负载平衡。 Kafka代理是无状态的,所以他们使用ZooKeeper来维护它们的集群状态。 一个Kafka代理实例可以每秒处理数十万次读取和写入,每个Broker可以处理TB的消息,而没有性能影响。 Kafka经纪人领导选举可以由ZooKeeper完成。

ZooKeeper用于管理和协调Kafka代理。 ZooKeeper服务主要用于通知生产者和消费者Kafka系统中存在任何新代理或Kafka系统中代理失败。 根据Zookeeper接收到关于代理的存在或失败的通知,然后生产者和消费者采取决定并开始与某些其他代理协调他们的任务。

生产者将数据推送给经纪人。 当新代理启动时,所有生产者搜索它并自动向该新代理发送消息。 Kafka生产者不等待来自代理的确认,并且发送消息的速度与代理可以处理的一样快。

因为Kafka代理是无状态的,这意味着消费者必须通过使用分区偏移来维护已经消耗了多少消息。 如果消费者确认特定的消息偏移,则意味着消费者已经消费了所有先前的消息。 消费者向代理发出异步拉取请求,以具有准备好消耗的字节缓冲区。 消费者可以简单地通过提供偏移值来快退或跳到分区中的任何点。 消费者偏移值由ZooKeeper通知。

10、partition的数据文件

partition中的每条Message包含了以下三个属性: offset,MessageSize,data,其中offset表示Message在这个partition中的偏移量,offset不是该Message在partition数据文件中的实际存储位置,而是逻辑上一个值,它唯一确定了partition中的一条Message,可以认为offset是partition中Message的 id; MessageSize表示消息内容data的大小;data为Message的具体内容。

11、kafka如何实现数据的高效读取?

Kafka为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为index。 index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。

Kafka中大量使用页缓存,这页缓存是Kafka实现高吞吐的重要因素之一。Kafka还使用了零拷贝技术来进一步提升性能。磁盘文件的数据复制到页面缓存中一次,然后将数据从页面缓存直接发送到网络中(发送给不同的订阅

者时,都可以使用同一个页面缓存),避免了重复复制操作。

12、Kafka 消费者端的 Rebalance 操作什么时候发生?

  • 同一个 consumer 消费者组 group.id 中,新增了消费者进来,会执行 Rebalance 操作
  • 消费者离开当期所属的 consumer group组。比如宕机
  • 分区数量发生变化时(即 topic 的分区数量发生变化时)
  • 消费者主动取消订阅

13、Kafka 中的ISR(InSyncRepli)、OSR(OutSyncRepli)、AR(AllRepli)代表什么?

答:kafka中与leader副本保持一定同步程度的副本(包括leader)组成ISR。与leader滞后太多的副本组成OSR。分区中所有的副本通称为AR。

14、Kafka中的HW、LEO等分别代表什么?

HW:高水位,指消费者只能拉取到这个offset之前的数据

LEO:标识当前日志文件中下一条待写入的消息的offset,大小等于当前日志文件最后一条消息的offset+1.

16、分区Leader选举策略有几种?

1、 OfflinePartition Leader选举:每当有分区上线时,就需要执行Leader选举。所谓的分区上线,可能是创建了新分区,也可能是之前的下线分区重新上线。这是最常见的分区Leader选举场景。

2、 ReassignPartition Leader选举:当你手动运行Kafka-reassign-partitions命令,或者是调用Admin的alterPartitionReassignments方法执行分区副本重分配时,可能触发此类选举。假设原来的AR是[1,2,3],Leader是1,当执行副本重分配后,副本集合AR被设置成[4,5,6],显然,Leader必须要变更,此时会发生Reassign Partition Leader选举。

3、 PreferredReplicaPartition Leader选举:当你手动运行Kafka-preferred-replica-election命令,或自动触发了Preferred Leader选举时,该类策略被激活。所谓的Preferred Leader,指的是AR中的第一个副本。比如AR是[3,2,1],那么,Preferred Leader就是3。

4、 ControlledShutdownPartition Leader选举:当Broker正常关闭时,该Broker上的所有Leader副本都会下线,因此,需要为受影响的分区执行相应的Leader选举。

这4类选举策略的大致思想是类似的,即从AR中挑选首个在ISR中的副本,作为新Leader。

17、请简述下你在哪些场景下会选择 Kafka

•日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、HBase、Solr等。
•消息系统:解耦和生产者和消费者、缓存消息等。
•流式处理:比如spark streaming和 Flink

18、请谈一谈 Kafka 数据一致性原理

假设分区的副本为3,其中副本0是 Leader,副本1和副本2是 follower,并且在 ISR 列表里面。虽然副本0已经写入了 Message4,但是 Consumer 只能读取到 Message2。因为所有的 ISR 都同步了 Message2,只有 High Water Mark 以上的消息才支持 Consumer 读取,而 High Water Mark 取决于 ISR 列表里面偏移量最小的分区,对应于上图的副本2,这个很类似于木桶原理。

19、生产者如何保证消息不漏发,不重复

幂等性加事务。

幂等性:对接口的多次调用和调用一次的效果是一致的。幂等性只能保证单个会话、单个分区有效,所以需要加入事务来保证多个分区写入操作的原子性。

20、可靠性保证

确保系统在各种不同的环境下能够发生一致的行为。

  • 保证分区消息的顺序
  • 只有当消息被写入分区的所有同步副本时(文件系统缓存),它才被认为是已提交
  • 只要还有一个副本是活跃的,那么已提交的消息就不会丢失
  • 消费者只能读取已经提交的消息

21、一致性保证

  1. 在leader宕机后,只能从ISR列表中选取新的leader,无论ISR中哪个副本被选为新的leader,它都知道HW之前的数据,可以保证在切换了leader后,消费者可以继续看到HW之前已经提交的数据。

  2. HW的截断机制:选出了新的leader,而新的leader并不能保证已经完全同步了之前leader的所有 数据,只能保证HW之前的数据是同步过的,此时所有的follower都要将数据截断到HW的位置, 再和新的leader同步数据,来保证数据一致。 当宕机的leader恢复,发现新的leader中的数据和 自己持有的数据不一致,此时宕机的leader会将自己的数据截断到宕机之前的hw位置,然后同步新leader的数据。宕机的leader活过来也像follower一样同步数据,来保证数据的一致性。

数据丢失场景

leader A宕机,followerB选举为Leader,B的HW之前的数据丢失。

数据不一致场景

leader A 和follower B都宕机,B先于A恢复,B选举为leader,此时B的消息少于A的消息,此时写入一条消息,AB不一致。

所谓leader epoch实际上是一对值:(epoch,offffset)。epoch表示leader的版本号,从0开始,当

leader变更过1次时epoch就会+1,而offffset则对应于该epoch版本的leader写入第一条消息的位移。

Elasticsearch是什么

Elasticsearch是由 Java语言开发基于Lucene的一款开源的搜索、聚合分析和存储引擎。也是一种非关系型文档数据库。

  1. 天生分布式、高性能、高可用、易扩展、易维护。
  2. 跨语言、跨平台:几乎支持所有主流编程语言,并且支持在“Linux、Windows、MacOS”多平台部署

  3. 支持结构化、非结构化、地理位置搜索等

  4. 海量数据的全文检索,搜索引擎、垂直搜索、站内搜索。

  5. 数据分析和聚合查询

ES数据类型

ES中的mapping有点类似与RDB中“表结构”的概念。在Mapping里包含了一些属性,比如字段名称、类型、字段使用的分词器、是否评分、是否创建索引等属性,并且在ES中一个字段可以有多个类型。

  1. 数字类型:long integer short byte double float half_float scaled_float unsigned_long

  2. Keywords :适用于索引结构化的字段,可以用于过滤、排序、聚合

  3. dates(时间类型)

  4. alias :为现有字段定义别名

  5. text:当一个字段是要被全文搜索的,比如Email内容、产品描述,这些字段应该使用text类型

  6. object:用于单个JSON对象

  7. nested:用于JSON对象数组

  8. join:为同一索引中的文档定义父/子关系。

结构化类型

  1. geo-point:纬度/经度积分
  2. geo-shape:用于多边形等复杂形状
  3. point:笛卡尔坐标点
  4. shape:笛卡尔任意几何图形

自动映射和手工映射

整数、浮点数、布尔、日期、数组、对象、字符串都可以自动映射,其他类型必须手动映射。

映射参数

  1. ignore_above:超过长度将被忽略
  2. ignore_malformed:忽略类型错误
  3. dynamic:控制是否可以动态添加新字段
  4. index:是否当前字段创建倒排索引,默认true

ES支持哪些类型的查询

脚本查询、聚合查询、SQL查询等。

全文检索:match

精确查找:term

模糊查询:suggester

什么是全文检索

对非结构化数据顺序扫描很慢,对结构化数据的搜索却相对较快,那么把我们的非结构化数据想办法弄得有一定结构不就行了吗?这就是全文检索的基本思路,也就是将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。

term、match、keyword的有何区别,你还知道哪些检索类型

term:对搜索词不分词,不影响源数据,

match:对搜索词分词,不影响源数据

term:检索类型

keyword:字段类型

为什么MySQL(B+Trees)不适合做全文检索?

  1. 索引往往字段很长,如果使用B+trees,树可能很深,IO很可怕
  2. 性能无法保证并且索引会失效
  3. 精准度差(相关度低),并且无法和其他属性产生相关性

倒排索引的基本原理

倒排索引:即关键词到文档id的映射。 不是由记录来确定属性值,而是由属性值来确定记录的位置。

在一个二维表格中,有三列Term Index(词项索引)、Term Dictionary(词项字典)、Posting List(倒排表)

将目标字段切分成若干个词项(term),按照字典序生成一个词项字典(Term Dictionary) ,此项字段存储的是去重之后的所有词项 ,倒排表(Posting List)保存的就是所有包含当前词项的元数据的id的有序int数组 ,

此时对于Term Dictionary的查询由原本的模糊查询编程了精准查询 ,Term Index就是词项字典索引,可以大大加快倒排索引的查询效率

什么是字典树

  1. 根节点不包含字符,除了根节点每个节点都只包含一个字符。root节点不含字符这样做的目的是为了能够包括所有字符串。
  2. 从根节点到某一个节点,路过字符串起来就是该节点对应的字符串。
  3. 每个节点的子节点字符不同,也就是找到对应单词、字符是唯一的。
  4. 利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。