题目来自牛客, 题的答案都是我个人理解的或网上的参考,仅仅只作为复习用.因个人水平有限,不合理的地方请多多指正.
9.21 有赞 二面
介绍实习情况
MySQL查询优化
建立合适的索引, 不要使用*来查询 . 使用limit来限制查找数量 避免索引失效的查找方式, 对联合索引使用最左匹配,. 尽量使用inner join等......
查询不走索引有哪些情况,为什么
1 in, 2个及以上参数 ,单列索引a, where a in (xxxx) ,如果参数是1个会用到索引,如果参数是2个及以上不会用到索引(包括int和varchar类型的字段)
2 使用%前缀模糊查询 like '%abc' 或者 like‘%abc%’
3 where子句中对字段进行函数操作 where num/2=100 或者 substring(a,1,3)='ab'或者age+10=30
4 where id !=2 或者 where id <> 2
5 where name is null
6 not in ,单列索引a ,where a not in (xxxxx) ,不管里面是一个还是多个参数都用不到a的索引(包括int和varchar类型的字段)
7 字符类型的字段与数字比较
8 多列索引中没有最左匹配
9 在 where 子句中使用 or 来连接条件
索引数据结构介绍,和B+树区别
数据库索引,是数据库管理系统中一个排序的数据结构,主要有
B树索引、Hash索引两种
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
哈希索引缺点 Hash索引只支持等值比较, Hash 索引无法被用来避免数据的排序操作,Hash 索引不支持多列联合索引的最左匹配规则,Hash索引在任何时候都不能避免表扫描
mysql中用的最多是B+树,
B+树的特征:
1.有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
2.所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3.所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
B+树的优势:
1.单一节点存储更多的元素,使得查询的IO次数更少。
2.所有查询都要查找到叶子节点,查询性能稳定。
3.所有叶子节点形成有序链表,便于范围查询。
什么情况下可以不回表查询
这先要从InnoDB的索引实现说起,InnoDB有两大类索引:
- 聚集索引(clustered index)
- 普通索引(secondary index)
**InnoDB聚集索引和普通索引有什么差异? **
InnoDB 聚集索引 的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:
(1)如果表定义了PK,则PK就是聚集索引;
(2)如果表没有定义PK,则第一个not NULL unique列是聚集索引;
(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;
InnoDB 普通索引 的叶子节点存储主键值。
mysql执行sql前会执行sql优化、索引选择等操作,mysql会预估各个索引所需要的查询代价以及不走索引所需要的查询代价,从中选择一个mysql认为代价最小的方式进行sql查询操作。而在回表数据量比较大时,经常会出现mysql对回表操作查询代价预估代价过大而导致索引使用错误的情况。
回表查询 ,需要扫码两遍索引树 ,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。
怎么避免? 不是必须的字段就不要出现在SELECT里面。或者b,c建联合索引。但具体情况要具体分析,索引字段多了,存储和插入数据时的消耗会更大。这是个平衡问题。
MySQL事务隔离级别
SQL 标准定义了四个隔离级别:
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
为什么选择Redis做缓存
由于redis访问速度块、支持的数据类型比较丰富,所以redis很适合用来存储数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作
数据库和缓存的数据一致性怎么保证
正常的缓存步骤是:
1、查询缓存数据是否存在
2、不存在即查询数据库
3、将数据添加到缓存同时返回结果,
4、下一次访问发现缓存存在即直接返回缓存数据。
先淘汰后写数据库vs先写数据库后淘汰
先写后淘汰,如果淘汰失败,cache里一直是脏数据
先淘汰后写,下次请求的时候缓存就会miss hit一次,这个代价是可以忽略的,(如果淘汰失败return false)
缓存淘汰级制保证一致性.先淘汰缓存再写数据库,下次请求直接从数据库取然后再写在缓存里。
Redis为什么是单线程的
- 单线程编程容易并且更容易维护;
- Redis 的性能瓶颈不再 CPU ,主要在内存和网络;
- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
- 虽然说 Redis 是单线程模型,但是, 实际上,Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。
- 引入多线程主要是为了提高网络 IO 读写性能
Redis数据结构
- string 数据结构是简单的 key-value 类型,一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
- list 即是 链表。链表是一种非常常见的数据结构, 应用场景: 发布与订阅或者说消息队列、慢查询。
- hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表) ,应用场景: 系统中对象数据的存储。
- set 类似于 Java 中的
HashSet
。Redis 中的 set 类型是一种无序集合, 应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景 - sorted se: 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列 ,应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,
Redis持久化策略
Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)
快照(snapshotting,RDB)
Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。快照持久化是 Redis 默认采用的持久化方式
AOF(append-only file)持久化
与快照持久化相比,AOF 持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
Redis集群模式
为了避免单台服务器故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
哨兵模式
第一种主从同步/复制的模式,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵挂了怎么办(所以哨兵应该也是集群)
哨兵是redis集群架构中非常重要的一个组件,主要功能如下:
- 集群监控:负责监控redis master和slave进程是否正常工作
- 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
- 故障转移:如果master node挂掉了,会自动转移到slave node上
- 配置中心:如果故障转移发生了,通知client客户端新的master地址
哨兵的核心知识
- 故障转移时,判断一个master node是宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题
- 哨兵至少需要3个实例,来保证自己的健壮性
- 哨兵 + redis主从的部署架构,是不会保证数据零丢失的,只能保证redis集群的高可用性
及时部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身就是单点,那么就不靠谱。
Redis集群选举master过程(这个不会,说了MySQL集群的选主过程)
当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
1.slave发现自己的master变为FAIL
2.将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
3.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
4.尝试failover的slave收集FAILOVER_AUTH_ACK
5.超过半数后变成新Master
6.广播Pong通知其他集群节点。
MQ是怎么防止消息丢失的
消息丢失分为三种情况 生产者丢失 mq自己丢失 消费则丢失
A:生产者丢失消息
①:可以选择使用rabbitmq提供是事物功能,就是生产者在发送数据之前开启事物,然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会受到异常报错,这时就可以回滚事物,然后尝试重新发送;如果收到了消息,那么就可以提交事物。
channel.txSelect();//开启事物
try{
//发送消息
}catch(Exection e){
channel.txRollback();//回滚事物
//重新提交
}
缺点:rabbitmq事物已开启,就会变为同步阻塞操作,生产者会阻塞等待是否发送成功,太耗性能会造成吞吐量的下降。
②:可以开启confirm模式。在生产者哪里设置开启了confirm模式之后,每次写的消息都会分配一个唯一的id,然后如何写入了rabbitmq之中,rabbitmq会给你回传一个ack消息,告诉你这个消息发送OK了;如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息失败了,你可以进行重试。
B:rabbitmq自己弄丢了数据
设置消息持久化到磁盘。设置持久化有两个步骤:
①创建queue的时候将其设置为持久化的,这样就可以保证rabbitmq持久化queue的元数据,但是不会持久化queue里面的数据。
②发送消息的时候讲消息的deliveryMode设置为2,这样消息就会被设为持久化方式,此时rabbitmq就会将消息持久化到磁盘上。
必须要同时开启这两个才可以。
而且持久化可以跟生产的confirm机制配合起来,只有消息持久化到了磁盘之后,才会通知生产者ack,这样就算是在持久化之前rabbitmq挂了,数据丢了,生产者收不到ack回调也会进行消息重发。
C:消费者弄丢了数据
使用rabbitmq提供的ack机制,首先关闭rabbitmq的自动ack,然后每次在确保处理完这个消息之后,在代码里手动调用ack。这样就可以避免消息还没有处理完就ack。
介绍一下线程池,线程池的线程数量取值应该怎么取,有使用过哪些线程池
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
线程数取值有两个方面:
1.CPU密集型
第一种是 CPU 密集型任务,比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。
最佳线程数 = CPU 核心数的 1~2 倍
2.IO密集型
第二种任务是耗时 IO 型,比如数据库、文件的读写,网络通信等任务,这种任务的特点是并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。对于这种情况任务最大线程数一般会大于 CPU 核心数很多倍
3.通用公式
线程数 = CPU 核心数 * (1+ IO 耗时/CPU 耗时)
目前用的最多的是ThreadPoolExecutor线程池
如果使用无界等待队列会有什么问题
当线程在执行任务时需要调用远程服务,当调用远程服务异常时,就会导致线程处理每个任务都需要等待很长的时间;
处理任务的速度慢,产生任务的速度快,而任务队列是没有边界的,就会导致队列变得越来越大,从而导致内存飙升,还可能导致OOM内存溢出。
介绍一下锁
锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源
1、同步锁
同一时刻,一个同步锁只能被一个线程访问。以对象为依据,通过synchronized关键字来进行同步,实现对竞争资源的互斥访问。
2、独占锁(可重入的互斥锁)
互斥,即在同一时间点,只能被一个线程持有;可重入,即可以被单个线程多次获取。什么意思呢?根据锁的获取机制,它分为“公平锁”和“非公平锁”。Java中通过ReentrantLock实现独占锁,默认为非公平锁。
3、公平锁
是按照通过CLH等待线程按照先来先得的规则,线程依次排队,公平的获取锁,是独占锁的一种。Java中,ReetrantLock中有一个Sync类型的成员变量sync,它的实例为FairSync类型的时候,ReetrantLock为公平锁。设置sync为FairSync类型,只需——Lock lock = new ReetrantLock(true)。
4、非公平锁
是当线程要获取锁时,它会无视CLH等待队列而直接获取锁。ReetrantLock默认为非公平锁,或——Lock lock = new ReetrantLock(false)。
5、共享锁
能被多个线程同时获取、共享的锁。即多个线程都可以获取该锁,对该锁对象进行处理。典型的就是读锁——ReentrantReadWriteLock.ReadLock。即多个线程都可以读它,而且不影响其他线程对它的读,但是大家都不能修改它。CyclicBarrier, CountDownLatch和Semaphore也都是共享锁。
6、读写锁
维护了一对相关的锁,“读取锁”用于只读操作,它是“共享锁”,能同时被多个线程获取。“写入锁”用于写入操作,它是“独占锁”,只能被一个线程锁获取。
介绍一下锁升级过程
synchronized锁的四种状态是在jdk1.6之后引入的,分别为:(无锁->偏向锁->轻量级锁->重量级锁 ) 这几个状态会随着竞争情况逐渐升级。
1:无锁
无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。也就是CAS(CAS是基于无锁机制实现的)。
2.偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。
3.轻量级锁
是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
4.重量级锁
在轻量级锁状态下,如果有第三个来访时,就会自动升级成重量级锁
介绍一下ReEntrantLock底层实现,介绍一下AQS
ReentrantLock的底层实现机制是AQS(Abstract Queued Synchronizer 抽象队列同步器)。AQS没有锁之类的概念,它有个state变量,是个int类型,为了好理解,可以把state当成锁,AQS围绕state提供两种基本操作“获取”和“释放”,有条双向队列存放阻塞的等待线程。AQS的功能可以分为独占和共享,ReentrantLock实现了独占功能(每次只能有一个线程能持有锁)。
AQS:AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏
Bean的生成过程
加载配置文件,将配置文件转换成Documebt。转换成BeanDefinition 然后转换成BeanWraper,通过反射或***生成实例,调用拦截器,再放入BeanFactory中
怎么样在Bean初始化完成后立即执行,而不是手动调方法
1:在Spring的配置文件中,添加注入:
2: 注解方式
使用注解只需要在方法名上添加@PostConstruct。
linux怎么查看磁盘剩余多少
使用df 命令
怎么查找一个文件里的某一个字符串的位置
不会
不得不说,有赞的面试题确实难度很大.对于我这个学生党来说,挺难了................