叮咚买菜
hashmap的扩容过程
单向链表如何判断有环
HashSet遍历和快慢指针
如何找出一个数组重复元素最多的前K个元素
TopK问题,堆排序,时间复杂度nLogK
MySQL什么情况下会发生死锁?如何解决?
举个例子:事务A按照先后顺序去更新id=1和id=2的记录,事务B同一时间按照先后顺序去更新id=2和id=1的记录,因为行锁是两阶段协议,A获得id=1的行锁不会马上释放,此时B会等待A释放id=1的行锁,而A会等待B释放id=2的行锁,这样就形成了死锁。
如何解决:- 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置。
- 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑。
ArrayList的contains方法和HashSet的contains方法的时间复杂度各是多少
O(n)和O(1)
2个线程,A生产数据,B消费数据,有哪几种实现形式(使用LinkedList实现有哪些需要注意的,线程安全问题,如何解决:加锁)
一个抽奖系统,奖品1的概率是10%,奖品2的概率是20%,不中奖的概率是70%,算法如何实现?
随机数和区间
缓存一致性如何保证?
最好的解决方案是先更新数据库再删除缓存(在极端情况下也会有问题,比如说B线程更新数据的节点,缓存正好失效,此时线程A读取到了旧值,随后B操作数据库完成更新并且删除缓存之后,A再把旧值写入缓存,此时再去请求拿到的会是脏数据,这种概率非常低,基本可以忽略)
Dubbo IO线程池和业务线程池有什么区别?默认是哪种线程池(Fixed)
使用Dubbo有没有遇到什么坑?
- 父子类有相同属性时值丢失:hessian序列化问题
- 自定义异常被包装成RuntimeException
- Data length too large
- 线程耗尽:Provider默认是fixed线程池,且线程数为200
网关是用来做什么的?为什么要用网关?
乐观锁和悲观锁有什么区别?乐观锁有哪些实现方式?(版本号机制和CAS算法)
版本号机制和CAS算法
线程池主要有哪些东西?线程池中如果没有任务,会是什么状态?
项目中遇到的技术难题,是如何解决的?
分库分表一些连表问题和条件查询是如何解决的?
分两次查询,第一次把联表的id查出来,通过这些id再做一次查询
东方头条
悲观锁和乐观锁的使用场景
读多写少的场景用乐观锁,读少写多的场景用悲观锁
redis线上使用应该注意什么?哪些命令会导致堵塞
keys,避免使用keys,用scan替代,scan有个问题就是可能会查到重复的key,我们在内存里去重就好了
设计一个程序,将1s内访问超过三次的IP禁掉
盒马
如何避免死锁
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout) 来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库链接里,否则会出现解锁失败的情况。
CHM(ConcurrentHashMap)1.7版本segment数组长度是多少(最小16,根据不同型号CPU可能有所不同)
序列化原理是什么?为什么要序列化
JVM如何给对象分配内存
当创建一个对象时,需要给新生对象分配内存,而分配内存 就是在堆上进行分配。在堆上进行分配的时候,可能在新生代的Eden区上,也可能在老年代中分配,具体的分配策略需要参考一些内存分配的规则:
- 优先在eden区分配:大多数情况下,新生对象都在新生代的Eden区进行内存分配,当新生代Eden区没足够空间的时候,会触发一次Minor GC
- 大对象直接进入老年代:当一个新生对象需要大量连续空间并且对象所需空间大于-XX:PretenureSizeThreshold参数值的时候,这个对象将在老年代分配内存空间,这样可以避免新生代发生大量的内存复制。
- 年龄大的存活对象进入老年代:在新生代每经过一次Minor GC,存活对象的年龄都会增加一岁,当年龄超过-XX:MaxTenuringThreshold参数值的时候,这个对象将进入老年代
动态对象年龄判断:如果新生代Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄等于或超过该年龄的对象将直接进入老年代,不再等待年龄超过-XX:MaxTenuringThreshold参数值的条件进入老年代。
如何解决超卖问题
平安金服
SQL语句的执行顺序
SQL的执行顺序:from—where–group by—having—select—order by
掌门一对一
进程和线程的区别
- 进程是操作系统资源分配的最小单位,线程是CPU任务调度的最小单位。一个进程可以包含多个线程,所以进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。
- 不同进程间数据很难共享,同一进程下不同线程间数据很易共享。
- 每个进程都有独立的代码和数据空间,进程要比线程消耗更多的计算机资源。线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉。
- 系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
SpringBean的生命周期
SpringBean 生命周期简单概括为4个阶段:
- 实例化,创建一个Bean对象
- 填充属性,为属性赋值
- 初始化
- 如果实现了
xxxAware
接口,通过不同类型的Aware接口拿到Spring容器的资源 - 如果实现了BeanPostProcessor接口,则会回调该接口的
postProcessBeforeInitialzation
和postProcessAfterInitialization
方法 - 如果配置了
init-method
方法,则会执行init-method
配置的方法
- 如果实现了
- 销毁
- 容器关闭后,如果Bean实现了
DisposableBean
接口,则会回调该接口的destroy
方法 - 如果配置了
destroy-method
方法,则会执行destroy-method
配置的方法
- 容器关闭后,如果Bean实现了
微盟
redis哨兵模式和cluster模式的区别以及原理?
Redis集群一般使用哪一种方案(cluser),cluser如何做到水平拓展的?
redis分布式锁如果加锁失败或者释放锁失败了怎么办?(如何保证加锁的安全性)
一致性哈希原理
redis单线程的理解,为什么选用单线程
- 使用单线程模型能带来更好的可维护性,方便开发和调试;
- 使用单线程模型也能并发的处理客户端的请求(I/O多路复用);
- Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU,主要在内存和网络;
redis单线程为什么这么快?
- 纯内存操作:读取不需要进行磁盘 I/O,所以比传统数据库要快上不少;*(但不要有误区说磁盘就一定慢,例如 Kafka 就是使用磁盘顺序读取但仍然较快)*
- 单线程,无锁竞争:这保证了没有线程的上下文切换,不会因为多线程的一些操作而降低性能;
- 多路 I/O 复用模型,非阻塞 I/O:采用多路 I/O 复用技术可以让单个线程高效的处理多个网络连接请求(尽量减少网络 IO 的时间消耗);
- 高效的数据结构,加上底层做了大量优化:Redis 对于底层的数据结构和内存占用做了大量的优化,例如不同长度的字符串使用不同的结构体表示,HyperLogLog 的密集型存储结构等等..
聊聊对Redis I/O多路复用的理解
Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据 套接字目前执行的任务来为套接字关联不同的事件处理器。
当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。
缓存穿透是什么?如何解决?
拿一些缓存和数据库中都没有的非法参数进行请求;设置null值或者布隆过滤器。
布隆过滤器使用场景和原理是什么?为什么会存在一定的误差?
应用场景主要有:
- 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
- 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。
布隆过滤器的原理是创建一个位数组,位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb 的空间。当一个元素加入布隆过滤器中的时候,会进行如下操作:
- 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
- 根据得到的哈希值,在位数组中把对应下标的值置为 1。
不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。
综上,我们可以得出:布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
Redis布隆过滤器命令是什么?
BF.ADD
、BF.MADD
、BF.EXISTS
、BF.MEXISTS
、BF.RESERVE
MySQL行锁的原理,锁的到底是什么?(索引)
对MVCC的理解,MVCC到底解决了什么问题?(乐观锁实现,提高性能读写冲突不加锁)
MVCC是乐观锁的一种实现,读写冲突不加锁,提高了性能
MVCC能解决幻读吗?
MVCC不能彻底解决幻读,只能解决读情况下的幻读,写的幻读是通过加锁来解决。
分布式事务如何解决?基于CAP理论和2PC,TCC是什么
MySQL深分页会有什么问题?(比如LIMIT 1000,100)如何解决?
深分页为什么会慢:举个例子,select * from where status = 1 limit 10000,20,即使知道前10000个会丢掉,mysql也会去聚集索引上查一遍数据(回表),自然速度就非常慢,这里是因为limit offset只能作用于引擎层返回的结果集。换句话说,引擎层也很无辜,他并不知道这10000个是要扔掉的。具体参考点击这里
如何解决:
- 根据实际业务需求,看是否能替换成下一页,上一页这种,这里是说,把limit,offset替换为>辅助索引(即搜索条件)id的方式。该id再调用时,需要返回给前端。
- 覆盖索引:当辅助索引查询的数据,只有id和辅助索引本身,那么就不必再去回表查询。思路如下:select * from t wherer id in (select id from t where second_index = xxx limit 10000,20),这句话是说,先从条件查询中,查找数据对应的id,因为主键在辅助索引上就有,所以不用会回表查询。再通过这些已经被limit出来的id,去查聚集索引,这样只会20次随机IO。(或者用JOIN去优化,原理类似select * from t a join (select id from t where second_index = xxx limit 10000,20) b on a.id=b.id)
kafka内部是如何保证消息的幂等性?
消息语义,精准一次性,消息高水位待补充
如何保证kafka消息的全局的顺序性(设置key可以保证分区顺序性)
设置相同的key可以保证分区的顺序性,全局顺序性可以设置一个 topic,一个 partition,一个 consumer
kakfa ISR机制的原理
哲学家进餐问题
微盟二面
dubbo有哪些组件(分层)?
从大的范围来说,dubbo分为三层,business业务逻辑层由我们自己来提供接口和实现还有一些配置信息,RPC层就是真正的RPC调用的核心层,封装整个RPC的调用过程、负载均衡、集群容错、代理,remoting则是对网络传输协议和数据转换的封装。
划分到更细的层面,就是图中的10层模式,整个分层依赖由上至下,除开business业务逻辑之外,其他的几层都是SPI机制。
简单讲讲dubbo服务注册与发现(服务暴露)的过程
在容器启动的时候,通过ServiceConfig解析标签,创建dubbo标签解析器来解析dubbo的标签,容器创建完成之后,触发ContextRefreshEvent事件回调开始暴露服务
通过ProxyFactory获取到invoker,invoker包含了需要执行的方法的对象信息和具体的URL地址
再通过DubboProtocol的实现把包装后的invoker转换成exporter,然后启动服务器server,监听端口
最后RegistryProtocol保存URL地址和invoker的映射关系,同时注册到服务中心
讲讲缓存穿透(布隆过滤器的原理)
JVM内存区域
- 程序计数器(线程私有):指向当前线程正在执行的字节码指令
- 虚拟机栈(线程私有):虚拟机栈描述的是Java方法执行的线程内存模型:每个方法执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量、操作数栈、动态连接、方法出口等信息。
- 本地方法栈(线程私有):调用本地native的内存模型
- 堆(线程共享):Java对象存储的地方
(1)Java堆是虚拟机管理的内存中最大的一块
(2)Java堆是所有线程共享的区域
(3)在虚拟机启动时创建
(4)此内存区域的唯一目的就是存放对象实例,几乎所有对象实例都在这里分配内存。存放new生成的对象和数组
(5)Java堆是垃圾收集器管理的内存区域,因此很多时候称为“GC堆” - 方法区(线程共享):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
运行时常量池:
A、是方法区的一部分
B、存放编译期生成的各种字面量和符号引用
C、Class文件中除了存有类的版本、字段、方法、接口等描述信息,还有一项是常量池,存有这个类的 编译期生成的各种字面量和符号引用,这部分内容将在类加载后,存放到方法区的运行时常量池中。
分库分表之后多条件查询如何处理?(宽表或者ES)
MySQL最左匹配原则的原理
介绍下AQS
用过哪些JUC包下的类,场景各是什么?
docker之间是如何通信的
携程
excel大文件上传OOM的问题如何解决
分库分表之后的JOIN如何处理,分页如何处理
TODO待补充
MySQL是如何保证ACID的
A(原子性):
undo log
记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。C(一致性):从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。
I(隔离性):MVCC保证了读的隔离性;排它锁保证写的隔离性。
D(持久性):使用了WAL(Write-Ahead Logging,先写日志再写磁盘)。Redo log就是一种WAL的应用。当数据库忽然掉电,再重新启动时,MySQL可以通过Redo log还原数据。
怪兽充电
几种分布式id生成方案,各有什么问题
- UUID,性能较差,MySQL官方不推荐,会引起磁盘数据位置频繁变动
SNOWFLAKE
(雪花算法)是默认使用的主键生成方案,生成一个 64bit的长整型(Long
)数据。sharding-jdbc
中雪花算法生成的主键主要由 4部分组成,1bit
符号位、41bit
时间戳位、10bit
工作进程位以及12bit
序列号位。- Tinyid
快货运
分布式锁(或其他互斥锁)如何保证安全的前提下同时提高性能
使用分段锁:比如1000个库存分成10份,每个请求hash到不同的key上,最后再汇总
一药网
dubbo的线程模型
spring事务失效的场景
类加载过程
类加载过程分为三个阶段:Load、Link和Init,即加载、链接、初始化
- 第一步,Load阶段读取类文件产生二进制流,并转化为特定的数据结构,初步校验cafe babe魔法数、常量池、文件长度、是否有父类等,然后创建对应的java.lang.Class实例
- 第二步,Link阶段包括验证、准备、解析三个步骤。验证是更详细的校验,比如final是否合规、类型是否正确、静态变量是否合理等;准备阶段是为静态变量分配内存,并设定默认值,解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局。
- 第三步,Init阶段执行雷构造器<clint>方法,如果赋值运算是通过其他类的静态方法来完成的,那么会马上解析另外一个类,在虚拟机栈中执行完毕后通过返回值进行赋值。
包名和类名一样的两个类冲突如何解决
喜马拉雅
JVM是基于栈还是寄存器进行解释的(栈)
new一个对象,实际发生了什么?(操作系统层面和JVM层面)
创建一个线程,对于操作系统来说到底发生了什么(CPU和内存方面)
线程切换有什么损耗?
线程切换的时候还要执行内存换页,CPU 的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性
volatile保证的内存可见性是指什么?禁止重排序的原理
JMM的工作内存是在哪里?(CPU高速缓存,逻辑上部分属于栈)
操作系统线程和JVM线程的区别
leetcode98
货拉拉
- 类的加载过程
- 类的卸载
- JVM内存区域
- 常用垃圾回收器和GC算法,三色标记
- 垃圾回收的过程
- 设计一个高并发系统,例如秒杀系统
货拉拉二面
强引用、软应用、弱引用、虚应用的区别以及使用场景
强引用,即Strong Reference,最为常见。如Object = new Object();这样的变量声明和定义就会产生对该对象的强引用。只要对象有强引用指向,并且GC Roots可达,那么Java内存回收时,也不会回收该对象。
软引用,即Soft Reference,引用力度弱于”强引用“,是用在非必需对象的场景。在即将OOM之前,垃圾回收器会把这些软引用指向的对象加入回收范围,以获得更多的内存空间,让程序能够继续健康运行。主要用来缓存服务器中间计算结果及不需要实时保存的用户行为等。
弱引用,即Weak Reference,引用强度较前两者更弱,也是用来描述非必需对象的。如果弱引用指向的对象只存在弱引用这一条线路,则在下一次YGC时会被回收。由于YGC时间的不确定性,弱引用何时被回收也具有不确定性。弱引用主要用于指向某个易消失的对象,在强引用断开后,此引用不会劫持对象。调用WeakReference.get()可能会返回null,要注意空指针异常。
虚引用,即Phantom Reference,是极弱的一种引用关系,定义完成后,就无法通过该引用获取指向的对象。为一个对象设置虚引用的唯一目的就是希望能在这个对象被回收时收到一个系统通知。虚引用必须与引用队列联合使用,当垃圾回收时,如果发现存在虚引用,就会在回收对象内存前,把这个虚引用加入与之关联的引用队列中。
caffeine和guava区别,为什么选caffeine,caffeine的优势
涂鸦智能
spring拓展点用过吗?(前置后置处理器,bean生命周期相关)
MySQL事务的原子性是如何保证的?
通过undolog来保证原子性
Kafka架构设计
Redis单线程为什么这么快
Redis单线程的理解
dubbo接口如果超时了,问题排查的思路
蔚来
- 介绍下cas算法
- redis缓存雪崩如何解决
- redis哨兵模式如何保证高可用
- 分库分表之后联表如果是两次查询的话性能会很差吗
阿里(企业智能)
dubbo的原理是什么(工作原理)
- 服务启动的时候,provider和consumer根据配置信息,连接到注册中心register,分别向注册中心注册和订阅服务
- register根据服务订阅关系,返回provider信息到consumer,同时consumer会把provider信息缓存到本地。如果信息有变更,consumer会收到来自register的推送
- consumer生成代理对象,同时根据负载均衡策略,选择一台provider,同时定时向monitor记录接口的调用次数和时间信息
- 拿到代理对象之后,consumer通过代理对象发起接口调用
- provider收到请求后对数据进行反序列化,然后通过代理调用具体的接口实现
zookeeper的选举机制以及如何保证整个注册中心的稳定性
选举算法和流程:FastLeaderElection(默认提供的选举算法):
目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
- 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。
- 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
- 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为leader,服务器1,2成为follower。
- 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。
- 服务器5启动,后面的逻辑同服务器4成为follower。
动态代理的实现方式,AOP的实现方式,有什么区别?
- JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
- CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
ThreadLocal的原理
Redis持久化机制
一致性哈希原理
缓存击穿和缓存雪崩是什么?如何解决
击穿:1.热点数据永不失效;2.从数据库加载缓存数据的时候加互斥锁
雪崩:
针对 Redis 服务不可用的情况:- 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
- 限流,避免同时处理大量的请求。
针对热点缓存失效的情况:
- 设置不同的失效时间比如随机设置缓存的失效时间。
- 缓存永不失效。
Java CPU负载100%排查技巧
- top命令按照CPU排序找到占用高的java进程pid比如
9702
- top -H -p pid查看这个进程中,消耗CPU最多的线程比如
10007
(top -Hbp 9702 | awk '/java/ && $9>50'
这个命令表示查出CPU占用率超过某个值的所有线程,例如超过50%) - 线程id转换为十六进制
printf "%x\n" 10007
16进制id为2717
- 使用jstack吧线程信息打印出来:
jstack $pid | grep 2717
- top命令按照CPU排序找到占用高的java进程pid比如
死锁如何检测
jsp -l 得到进程pid,然后jstack -l pid
有赞
异步更新缓存,如果该缓存是基于多张表算出来的话,应该如何更新
Kafka和RocketMQ的区别(分层设计和存储的区别)
MQ的顺序消息、事务消息、延迟消息是什么
Dubbo SPI机制和Java SPI机制的区别
Redis跳表的原理
Redis缓存穿透和缓存击穿以及如何解决
MySQL MVCC原理介绍一下
ThreadLocal的原理,ThreadLocalMap的key是什么?
ThreadLocalMap的key是ThreadLocal的实例,因为一个线程可以有多个ThreadLocal变量
synchronized和Lock的区别
Future原理
netty原理
京东
DDD的理解
如何保证系统的高可用
系统上游挂了怎么办,降级的话如何降级
流量突增的情况下,在不修改代码的情况下,如何应对这种场景
Redis持久化配置
- 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
- 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
- Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
Redis删除策略
缓存穿透、缓存雪崩(雪崩的话可以异步去刷新缓存时间)
Redis大key会有什么问题?大key如何优化
大key的风险:
1.读写大key会导致超时严重,甚至阻塞服务。2.如果删除大key,DEL命令可能阻塞Redis进程数十秒,使得其他请求阻塞,对应用程序和Redis集群可用性造成严重的影响。
redis使用会出现大key的场景:
1.单个简单key的存储的value过大;
2.hash、set、zset、list中存储过多的元素。
解决问题:
单个简单key的存储的value过大的解决方案:
- 将大key拆分成对个key-value,使用multiGet方法获得值,这样的拆分主要是为了减少单台操作的压力,而是将压力平摊到集群各个实例中,降低单台机器的IO操作。
hash、set、zset、list中存储过多的元素的解决方案:
类似于第一种场景,使用第一种方案拆分;
以hash为例,将原先的hget、hset方法改成(加入固定一个hash桶的数量为10000),先计算field- 的hash值模取10000,确定该field在哪一个key上。
Redis基础数据结构
MySQL INNODB引擎没有设置主键会有什么问题
- 使用不了主键索引,查询会进行全表扫描
- 影响数据插入性能,插入数据需要生成ROW_ID,而生成的ROW_ID是全局共享的,并发会导致锁竞争,影响性能
B+Tree索引和Hash索引的优劣
MySQL可重复读隔离级别下读和写是如何加锁的?
行锁是加在什么上面?(索引)
JVM优化的思路(保持jvm的稳定,避免频繁gc)
策略 1:将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
策略 2:大对象进入老年代,虽然大部分情况下,将对象分配在新生代是合理的。但是对于大对象这种做法却值得商榷,大对象如果首次在新生代分配可能会出现空间不足导致很多年龄不够的小对象被分配的老年代,破坏新生代的对象结构,可能会出现频繁的 full gc。因此,对于大对象,可以设置直接进入老年代(当然短命的大对象对于垃圾回收来说简直就是噩梦)。
-XX:PretenureSizeThreshold
可以设置直接进入老年代的对象大小。策略 3:合理设置进入老年代对象的年龄,
-XX:MaxTenuringThreshold
设置对象进入老年代的年龄大小,减少老年代的内存占用,降低 full gc 发生的频率。策略 4:设置稳定的堆大小,堆大小设置有两个参数:
-Xms
初始化堆大小,-Xmx
最大堆大小。策略5:注意: 如果满足下面的指标,则一般不需要进行 GC 优化:
MinorGC 执行时间不到50ms; Minor GC 执行不频繁,约10秒一次; Full GC 执行时间不到1s; Full GC 执行频率不算频繁,不低于10分钟1次。
发现虚拟机频繁full GC时应该怎么办(full GC指的是清理整个堆空间,包括年轻代和永久代)
- 首先用命令查看触发GC的原因是什么 jstat –gccause 进程id
- 如果是System.gc(),则看下代码哪里调用了这个方法(可以通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc)
- 如果有perm gen的话(jdk1.8就没了),要给perm gen分配空间,但没有足够的空间时,会触发full gc。所以看看是不是perm gen区的值设置得太小了。
- 如果是一次fullgc之后,剩余对象不多。那么说明你的eden区设置太小,导致生命周期的对象进入了老年代。
- 如果一次fullgc之后,老年代回收率不大,那么说明老年代太小。
- 如果是heap inspection(内存检查),可能是哪里执行jmap –histo[:live]命令
- 如果是GC locker,可能是程序依赖的JNI库的原因
垃圾回收算法
Mark-Sweep(标记-清除算法):
(1)思想:标记清除算法分为两个阶段,标记阶段和清除阶段。标记阶段任务是标记出所有需要回收的对象,清除阶段就是清除被标记对象的空间。
(2)优缺点:实现简单,容易产生内存碎片,导致需要分配一个较大连续空间时容易触发FGC。Copying(复制清除算法):
(1)思想:将可用内存划分为大小相等的两块,每次只使用其中的一块。当进行垃圾回收的时候了,把其中存活对象全部复制到另外一块中,然后把已使用的内存空间一次清空掉。
(2)优缺点:不容易产生内存碎片;可用内存空间少;存活对象多的话,效率低下。Mark-Compact(标记-整理算法):
(1)思想:先标记存活对象,然后把存活对象向一边移动,然后清理掉端边界以外的内存。
(2)优缺点:不容易产生内存碎片;内存利用率高;存活对象多并且分散的时候,移动次数多,效率低下分代收集算法:(目前大部分JVM的垃圾收集器所采用的算法,思想:把堆分成新生代和老年代,永久代指的是方法区):
(1) 因为新生代每次垃圾回收都要回收大部分对象,所以新生代采用Copying算法。新生代里面分成一份较大的Eden空间和两份较小的Survivor空间。每次只使用Eden和其中一块Survivor空间,然后垃圾回收的时候,把存活对象放到未使用的Survivor(划分出from、to)空间中,清空Eden和刚才使用过的Survivor空间。
(2) 由于老年代每次只回收少量的对象,因此采用mark-compact算法。
(3) 在堆区外有一个永久代。对永久代的回收主要是无效的类和常量CMS是怎么进行垃圾回收的?(相关:G1垃圾回收过程)
CMS收集器基于“标记-清除”算法:
优点有:并发收集、低停顿
缺点:对CPU资源非常敏感,导致程序变慢,CMS无法处理浮动垃圾,容易 产生大量空间碎片。
垃圾回收过程分为四个步骤:初始标记、并发标记 、重新标记 、并发清除:
G1 收集器基于“标记-整理”算法:
优点有:
- 不产生内存碎片
- 可以非常精确的控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收
G1工作过程分为几个步骤:初始标记、并发标记、最终标记、筛选回收。
生产环境遇到JVM问题如何去解决?解决的步骤有哪些
并发编程线程安全三大问题:可见性、有序性、原子性分别如何去保证?
内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。
可见性:除了
volatile
之外,Java中的synchronized
和final
两个关键字也可以实现可见性有序性:在Java中,可以使用
synchronized
和volatile
来保证多线程之间操作的有序性。实现方式有所区别:volatile
关键字会禁止指令重排。synchronized
关键字保证同一时刻只允许一条线程操作。原子性:在Java中,为了保证原子性,提供了两个高级的字节码指令
monitorenter
和monitorexit
。这两个字节码,在Java中对应的关键字就是synchronized
。因此,在Java中可以使用
synchronized
来保证方法和代码块内的操作是原子性的。
volatile如何保证可见性
如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
volatile两条实现原则:
- Lock前缀指令会引起处理器缓存回写到内存。LOCK#信号确保在声言该信号期间,处理器可以独占共享内存(通过锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存)。但在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,锁总线开销比较大。在P6和目前的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确定修改的原子性,此操作被称为“缓存锁定”,缓存一致性会阻止同时修改由两个以上处理器缓存的内存区域数据。
- 一个处理器的缓存会写到内存会导致其他处理器的缓存无效。部分处理器使用MESI(修改、独占、共享、无效)控制协议去维护内部缓存和其他处理器缓存的一致性。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。
volatile如何保证有序性
它是通过内存屏障来实现的。
什么是内存屏障?硬件层面,内存屏障分两种:读屏障(Load Barrier)和写屏障(Store Barrier)。内存屏障有两个作用:
- 阻止屏障两侧的指令重排序;
- 强制把写缓冲区/高速缓存中的脏数据等写回主内存,或者让缓存中相应的数据失效。
volatile能否保证原子性?
不能。Num++这种操作无法保证原子性,Num++可以分为三步:读取i的值,将i的值+1,写入最新的i值。
举个例子:当线程1执行Num++语句时,先是读入Num的值为0,倘若此时让出CPU执行权,线程2获得执行,线程2会重新从主内存中,读入Num的值还是0,然后线程2执行+1操作,最后把Num=1刷新到主内存中; 线程2执行完后,线程1由开始执行,但之前已经读取的Num的值0,所以它还是在0的基础上执行+1操作,也就是还是等于1,并刷新到主内存中。 所以最后输出结果可能是1也可能是2。
synchronized如何保证可见性
Synchronized能够实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
synchronized如何保证有序性
synchronized的有序性由”一个变量在同一个时刻只允许一个线程对其进行lock操作“这条规则获得的,这个规则决定了一个锁的两个同步代码块只能串行地进入。
synchronized锁升级的过程
无锁→偏向锁→轻量级锁→重量级锁
使用线程池有什么好处?
合理的使用线程池能够带来三个好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
线程创建的基本开销和线程切换开销
线程创建开销:
- 为线程堆栈分配并初始化一大块内存
- 需要进行系统调用以在主机OS中创建/注册本机线程
- 创建初始化描述符并将其添加到JVM内部数据结构中
线程切换开销:
线程切换的时候还要执行内存换页,CPU 的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。【分配内存、列入调度、内存换页、清空缓存和重新读取】
创建线程的开销
https://qastack.cn/programming/5483047/why-is-creating-a-thread-said-to-be-expensive
Java线程的创建非常昂贵,因为其中涉及大量工作:
- 必须为线程堆栈分配并初始化一大块内存。
- 需要进行系统调用以在主机OS中创建/注册本机线程。
- 需要创建,初始化描述符并将其添加到JVM内部数据结构中。
https://zhuanlan.zhihu.com/p/161628226
TODO待补充
对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。【分配内存、列入调度、内存换页、清空缓存和重新读取】
关于内存开销
Java线程的线程栈区别于堆,它是不受Java程序控制的,只受系统资源限制。默认一个线程的线程栈大小是1M,别小看这1M的空间,如果每个用户请求都新建线程的话,1024个用户光线程就占用了1个G的内存,如果系统比较大的话,一下子系统资源就不够用了,最后程序就崩溃了。
【创建一个线程默认需要消耗1M的内存,如果每个用户请求都创建一个线程,那么1024个用户就是1G了,并发量一大就扛不住了】
线程是JVM创建还是操作系统去创建的?
Redis分布式锁获取锁成功,释放锁之前宕机,如何优雅的解决这个问题?
京东二面
HashMap扩容、缩容过程细节(HashMap没有缩容操作,因为反复横跳没太大意义)
什么是线程安全?
引用《并发编程实战》:
当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么就称这个对象是线程安全的。HashMap与CHM区别在哪里?CHM为了保证线程安全,在性能上做了哪些牺牲
Java内存区域有哪些?
HashMap与CHM区别在哪里?CHM为了保证线程安全,在性能上做了哪些牺牲
GCRoots有哪些?为什么选择这些对象作为GCRoots?
参考《深入理解Java虚拟机第三版》第三章,固定可作为GC Roots的对象包括以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
- Java虚拟机内部的引用,如基本数据类型所对应的Class对象,一些常驻的异常对象(比如NullPointException、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(synchronize关键字)持有的对象。
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
滴滴
编程题:找出一个数组中出现次数为奇数的元素,并输出这些元素
快排时间复杂度是多少,快排是否稳定?稳定指的是什么?
快排时间复杂度:最差n^2,平均nLogn
快排是不稳定的,因为是原地排序
稳定:如果 a 原本在 b 前面,而 a=b,排序之后 a 仍然在 b 的前面。
不稳定:如果 a 原本在 b 的前面,而 a=b,排序之后 a 可能会出现在 b 的后面。kafka为什么支持高吞吐
kafka如何做到水平拓展
一个http请求过来,SpringMVC的工作流程
Spring拦截器和filter的区别
线程池工作原理,线程多了会有什么问题?
如何设置核心线程数以及其他参数?
JUC底下用过哪些包,可重入锁的底层实现(AQS)
分库分表除了sharding jdbc还有哪些不需要引入三方jar包的
INNODB引擎有哪些锁,这些锁基于什么加的?
表锁、行锁、乐观锁(MVCC);行锁基于索引加锁。
索引的数据结构
索引的种类
聚集索引和非聚集索引的区别
字节跳动
- HashMap的底层数据结构
- HashMap rehash是啥,为什么要rehash
- redis热点key是啥?如何去发现热点key(proxy)
- 什么是可重复读?什么是联合索引
- MySQL如何保证数据的强一致性
- MQ如何保证消息不丢失
- tcp与udp的区别,tcp如何保证可靠性
- 内存泄露最终会导致什么
- Linux查看一个目录最大的文件(du -sh *)
B站
- SpringBoot的启动流程
- kafka rebalance机制
- kafka架构设计,如何保证消息不丢失
- 抽奖场景:奖品扣库存如何保证高并发
- 优惠券发放场景:假设上游接口全部可靠,如何保证发放优惠券的可靠性(延迟消息)
- 如何保证redis加锁和释放锁的安全性(什么情况下会出现释放锁不安全的问题)
- 分布式事务解决方案
- ShardingJDBC原理,怎么做的,会有什么问题
- 数据分库分表之后数据如何不停机迁移
百度
- 对DDD的理解
- DDD中业务域如何划分
- 聚合根、实体和值对象的区别
- 工厂模式在实际中的使用
- 手写一个策略模式
滴滴
- JVM调优
- Dubbo和SpringCloud的区别
- 分布式事务如何处理
58安居客
分库分表如果用非分表的字段去查询,怎么办(比如name字段,可以用name建索引,name和sharding-key建立kv)
做mapping表,name和分片key做映射
分库分表批量操作极端情况,如果三次操作在三个不同的库,如何保证数据一致性(在相同的本地库,生成一条prepare记录,使其成为本地事务,保证单库事务成功,单库是没有数据一致性问题,最后再一个个commit,如果一个失败,则依次回滚)
ddd相关:充血模型和贫血模型的区别;ddd在项目中是如何实施的?
Zookeeper如何保证CP?Eureka如何保证AP?
Zookeeper保证CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka保证AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
hasmap具体为什么不安全?(不安全的表现是对象丢失,例如两个线程都执行到同一个hash桶目前为空,都创建一个entry,会有一个entry被覆盖掉;不安全是因为不可见,为什么不可见?)
数据库缓存一致性问题
redis SDS优化点
redis跳表相对于平衡树有什么区别?
redis IO多路复用原理
JMM工作内存对应到计算机硬件层面是什么?(高速缓存?或者内存?)
异步事件驱动的好处和坏处(坏处就是代码不直观,无法直接找到消费者;这种情况又如何优化)
分库分表场景:如何同时通过买家和卖家id去查询订单
订单表,比如以买家id分片,做卖家id和买家id的映射表,做两次查询
本地事务执行成功,MQ发送失败,本地事务如何回滚(发送回调?)
ES和MySQL的区别,ES这么强大,为什么还需要有MySQL(MySQL为什么不能被替代)
可重入锁原理
读写锁AQS用法的区别(是否共享模式)
为什么要加锁(因为内存不可见),为什么内存不可见
CMS垃圾回收器有什么问题?为什么?怎么解决?
问题:CMS在重新标记(Remark)的过程中会浮动垃圾,容易产生大量空间碎片。
原因:CMS垃圾回收器是基于“标记-清除”算法;Remark过程标记活着的对象,从GCRoot的可达性判断对象活着,但无法标记“死亡”的对象。
如果在初始阶段被标记为活着,并发运行过程中“死亡”,remark过程中无法纠正,因此变为浮动垃圾,需等待下次GC的到来。
如何解决:可以通过
-XX:+UseCMSCompactAtFullCollection
参数,强制JVM在FGC完成后对老年代进行压缩,执行一次空间碎片整理,但是会引发STW。为了减少STW次数,CMS还可以通过配置-XX:+CMSFullGCsBeforeCompaction=n
参数,在执行了n次FGC后,JVM再在老年代执行空间碎片整理。
百度(ACG互联网)一面
- 什么是DDD?和传统开发模式的区别
- 同一个topic只有四个分区的情况下如何提高消费吞吐,假设有同样分区数量的消费者(不考虑消费者本地多线程消费)(将原topic按照一定规则分片,转发到其他topic,再进行消费)
- Leetcode15. 三数之和
百度(ACG互联网)二面
docer原理,如何做隔离
负载均衡机制有哪几种:4层和7层的有什么区别
各种锁机制(内置锁、分布式锁)
商汤科技
- 一致性哈希分库分表方案如何动态感知节点的增减,这种分库分表如何扩容
- 类加载机过程(从class文件到就绪)
- SpringBoot启动流程
- dubbo服务注册流程
- 微服务链路追踪是怎么做的
- Redis主从复制过程
- AQS原理,可重入锁是如何做到可重入的;读写锁的原理
- Kafka如何做到高可用的
- 如何设计一个注册中心
- kafka partition与consumer工作机制
- 负载均衡机制
- redis排它锁(分布式锁)
- 观察者模式
滴滴
分布式事务是如何做的
kafka如何保证消息顺序性
kafka消息是如何存储的
MySQL索引原理:B树和B+树的区别以及各自的优缺点
Rpc(dubbo)和Http调用的区别(http原生调用是十进制、dubbo是二进制,dubbo性能好一点)
数据库缓存一致性性
Redis为什么这么快
跳表和红黑树的区别
跳表最差是O(N),平均O(LogN);红黑树相对跳表比较稳定,是O(LogN),跳表有一定的运气成分
小红书(2021/4/23)
- 分布式事务如何处理
- MySQL事务原理以及MVCC原理
- MySQL聚集索引和非聚集索引区别
- Redis热点key问题
- Redis集群原理
- Spring生命周期
- Spring如何解决循环依赖(三级缓存)
滴滴
MySQL B+树 500万节点,树高是多少?如何配置
为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。那么,我们就不应该使用二叉树,而是要使用“N叉”树。这里,“N叉”树中的“N”取决于数据块的大小。
以InnoDB的一个整数字段索引为例,这个N差不多是1200。这棵树高是4的时候,就可以存1200的3次方个值,这已经17亿了。考虑到树根的数据块总是在内存中的,一个10亿行的表上一个整数字段的索引,查找一个值最多只需要访问3次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了。
https
堆排序是否稳定,以及原理
不稳定,构建大根堆或者小根堆
积分转账到一个第三方服务如何设计
分为转出-转账中-转账成功三步:
转出:本地扣减,插入本地消息表,包含本地事务id,支付账户、接收人、积分数量、状态等
转账中:轮询本地消息表,调用第三方RPC(如果失败,一直重试,有个重试上限)
转账成功:调用成功之后,更新本地消息表状态为成功
关键点:幂等和重试,如果重试一定次数后,回滚扣减的积分,将本地事务表状态设置为失败