天眼查-面试题
一面
1.项目中的难点
- orgagent的线程隔离
- 机构的流程配置化
2.MySQL
MySQL的索引结构?
B+树
B树和B+树的区别
B树的每个结点都存储了key和data,B+树的data存储在叶子节点上
节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少
树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录
由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好
描述下可重复读和不可能重复读
同一个事务中发起多次查询,每次读取到的数据相
同的,无论其他事务有没有对数据进行修改,当不影响当前事务的读取
描述下MVCC
MVCC是多版本并发控制,是为了解决InnoDB中的不可重复读现象
通过把undo log,将所有事务的修改串联成一个版本链,当读取数据时根据read view来判断哪个版本的数据是自己可以读取的
3.GC
项目中使用的垃圾回收器
CMS + ParNew
CMS的四个阶段
- 初始标记(STW)
- 并发标记
- 重新标记(STW)
- 并发清除
4.Redis
- redis在项目中的应用
- 分布式锁是如何实现的
- setnx 为什么要设置超时时间
5.RPC
RPC中都有哪些组件
基础功能
通信协议:协议头、协议体、可变协议
序列化:JDK、JSON、Hession、Protobuf
网络IO模型:阻塞IO、IO多路复用
动态代理:JDK动态代理、CGLIB
服务相关
服务发现:服务注册、服务发现
健康检查:检查服务状态、心跳机制、客户端请求成功率
路由策略:做灰度、全链路灰度等
服务分组:服务之间进行隔离,彼此不影响
负载均衡:随机权重、轮询权重
异常重试:客户端根据指定的异常进行充实
优雅起停:停止由外到内,启动由内到外
熔断限流:服务端限流、降级,客户端熔断
高级优化
异步RPC:压榨服务的吞吐量
链路追踪:全局链路跟踪
动态分组:
泛化调用:
6.TCP
滑动窗口
7.算法
1 | [3,3,4,4,5,5,7,1,1,2,2] |
二面
1.API机构线程隔离
- 机构间Http互相影响
2.API机构差异配置
- 流程配置
- 组件配置
3.MySQL
- redo log
- bin log
4.Redis
- 热key的发现与处理
- 大量key过期导致redis服务卡顿
5.JMM
- 并发问题的产生原因
6.Java
- 线程池创建核心线程的流程
- 项目中线程池的配置,拒绝策略使用什么
7.JVM
- 用的什么收集器
- 卡表
- JVM层级的监控有没有(老年代比例、GC次数等参数监控)
8.限流算法
- 漏斗
- 令牌桶(实现简单的令牌桶)
三面
1.实现一个服务治理框架,初期的选型工作都要做哪些?
2.使用的框架出现了难题,找不到好的解决方案怎么办?
3.Kakfa
- 如何做到高吞吐量
- 顺序写
- SSD还有必要顺序写吗
- 零拷贝
- mmap和sendfile
- 批量发送
- 客户端如何做到批量发送
- Reactor
- 顺序写
- 如何做到低延迟
4.Redis
- key过期策略
- 25%是静态参数,还是动态调整的
- redis主从切换
- 主从切换的过程
- 客观下线和主观下线的区别
5.如何保证服务稳定性,四九9,一个5,SLA
- 资源监控
- 服务监控
- 业务监控
6.十个人十把椅子
有1-10号椅子,1-10号人,第N个人坐第N把椅子的概率是多少
理想汽车-面试题
一面
1.SQL索引优化有哪些?
- 最左前缀
- 联合索引
- 索引下推优化
2.InnoDB数据页大小,一页可以放多少数据?
16k
bigint类型可以放千万级别
3.varchar类型建索引,可以设置的最大长度是多少?
768
4.B-树和B+树的区别?为什么InnoDB索引使用B+树?
是否只在叶子结点存储数据
B+树优化磁盘IO和范围查询
5.项目中流程配置是什么样的?
6.分布式锁使用什么?为什么用lua脚本?为什么setnx不用lua?setnx从什么版本支持原子操作的?
setnx + lua
解锁操作不是一个原子指令,涉及到get、equals、del
setnx在2.6.12版本之后,官方支持原子操作,可以set key value nx ex
7.zookeeper分布式锁是如何实现的?
创建一个临时有序节点,并监听上一个节点的状态
8.线程池中,线程数到达最大线程数怎么办?
配置拒绝策略
9.orgagent线程隔离为什么不用hystrix?
10.MQ中间件了解哪些?
二面
1.RabbitMQ都有哪些组件?
2.公司内部RPC框架有哪些设计的优秀的地方?
3.Netty的线程模型在源码中是如何控制的?
4.介绍下GC的分代回收以及什么情况下对象进入老年代?
5.redis的数据结构及其对应的基本数据结构?
6.事务隔离级别?事务传播机制?
7.聚簇索引是什么?
8.说一些工作中用到的设计模式
9.spring源码看过哪些?
10.CountDownLatch的原理?
三面
1.ElasticSearch的索引的原理?
2.ElsaticSearch的数据写入流程?
3.说下整体的业务流程?
4.流程配置是怎么用的?
5.线程池隔离的具体方案(代码怎么写的)?
6.动态代理都有哪些类型?性能有何区别?
当当-面试题
Java基础
1 CAS原理及实现
2 AQS原理及实现?JUC下的应用?
3 线程池参数?流程描述?
4 JDK7 HashMap成环的原因
5 垃圾回收机制
- GC Roots 可达性分析
- 分代算法
- 标记清除
- 标记整理
- CMS
- G1
6 引用类型
- 强引用
- 弱引用
- 软引用
- 虚引用
Redis
1 Redis数据类型
1.1 8种数据类型
1.2 string类型 底层实现
2 持久化
- RDB
- AOF
3 Redis集群复制原理
4 Redis集群方案对比
- 主从模式
- 哨兵模式
- Redis Cluster
5 缓存雪崩 缓存穿透 原理及解决方法?
5.1 布隆过滤器
6 分布式锁
7 缓存淘汰机制
RocketMQ
1 简单介绍技术架构
2 顺序消费
3 事务消息
4 消息重投
5 消息重试
6 死信队列
MySQL
1 InnoDB和MyISAM的区别
2 ACID
3 三大范式
4 MVCC
5 表锁 行锁 页级锁 间隙琐
6 B+树和B树的区别
7 为什么选择B+树,而不是哈希表,B树?
8 索引失效
9 索引优化
- 熟悉explain的各字段的含义
10 组合索引的数据结构
- (a,b) 叶子节点排列顺序应该是先按照a进行排序,排序完成后再按照b进行排序,所以应该是a是全局有序,b是a中有序,所以可满足a,(a,b)为条件的查询
分布式
1 CAP理论
2 dubbo和feign的对比
3 描述一下dubbo的工作中流程
- 服务注册
- 服务发现
- 服务调用
- 服务降级
- 负载均衡
4 eureka nacos zookeeper
- eureka集群 AP
- zookeeper集群 CP
- nacos集群 可AP,可CP
5 Hystrix 服务降级
- 依赖隔离策略
- 线程池隔离
- 信号量隔离
6 服务熔断
7 Eureka
- 服务注册
- 服务同步
- 服务续约
- 服务启动
- 服务下线
- 服务发现
- 服务失效剔除
8 分布式事务
- 2PC
- 3PC
- TCC
并发编程的基础知识
并发编程的基础知识
Java中的CAS操作
在Java中,锁在并发处理中占据了一席之地,但是使用锁有一个不好的地方,就是当一个线程没有获取到锁时会被阻塞挂起,这会导致线程上下文的切换和重新调度的开销。
Java提供了非阻塞的volatile关键字来解决共享变量的可见性问题,这在一定程度上弥补了锁带来的开销问题,不能解决读-改-写等的原子性问题。CAS即Compare And Swap,
是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。
compareAndSwap*
JDK的Unsafe类提供了一系列的compareAndSwap*方法,下面以compareAndSwapLong为例 进行简单介绍。
- boolean compareAndSwapLong(Object obj, long valueOffset, long except, long update)
CAS有四个操作数,分别是:对象内存位置,对象中的变量的偏移量,变量的预期值,更新之后的值。其操作含义:如果对象obj中内存偏移量为valueOffset的变量值为except,
则使用新的值update替换旧的值except。这是处理器提供的一个原子性指令。
CAS自旋等待
CAS自旋,一直尝试,直到成功。
1 | public final long getAndAddLong(Object obj, long valueOffset, long add) { |
ABA问题
假如线程I使用CAS修改初值值的变量X,那么线程I会首先去获取当前变量X的值(为A),然后使用CAS操作尝试修改X的值为B,如果使用CAS操作成功可,那么此时程序运行不一定正确。
可能在线程I获取变量X的值A后,在执行CAS前,线程II使用CAS修改了变量X的值为B,然后又使用CAS修改了变量X的值为A。虽然线程I执行CAS时的值是A,但是这个A已经不是线程I获
取时的A了。
ABA问题的产生是因为变量的状态值产生了环形转换,A-B-A。JDK中的AtomicStampedReference类为每个变量的状态值都配备了一个时间戳,从而避免ABA问题的产生。
什么是AQS
AQS(Abstract Queued Synchronizer)是一个抽象的队列同步器,通过维护一个共享资源状态(Volatile Int state)和一个先进先出(FIFO)的线程等待队列来实现一个多线程
访问共享资源的同步框架。
AQS原理
AQS为每个共享资源都设置了一个共享资源锁,线程在需要访问共享资源时首先要获取共享资源锁,如果获取到了共享资源锁,便可以在当前线程中使用该共享资源,如果获取不到,则将该线程
放入线程等待队列,等待下一次资源调度。许多同步类的实现都依赖于AQS,比如常用的ReentrantLock、countDownLatch。
state: 状态
Abstract Queued Synchronizer维护了一个volatile int类型的变量,用于表示当前的同步状态。volatile虽然不能保证操作的原子性,但是能保证当前变量state的可见性。
1 | // 返回共享资源状态,此操作的内存语义为volatile修饰的原子读操作 |
AQS共享资源的方式:共享式和独占式
- 共享式:多个线程可以同时执行,具体的实现Semaphore和CountDownLatch。
- 独占式:只有一个线程能执行,具体的实现ReentrantLock。
AQS只是定义一个框架,只定义了一个接口,具体资源的获取、释放都交由自定义同步器去实现。不同的自定义同步器争用贡献资源的方式也不同,自定义同步器在实现时只需要实现共享
资源state的获取与释放方式即可,至于具体线程等待队列的维护,如获取资源失败入队、唤醒出队等,AQS已经在顶层实现好,不需要具体的同步器再做处理。
方法名 | 资源共享方式 | 说明 |
---|---|---|
isHeldExclusively() | 查询该线程是否正在独占资源,只有用到condition才需要去实现它 | |
tryAcquire(int) | 独占方式 | 尝试获取资源:成功返回true,失败返回false |
tryRelease(int) | 独占方式 | 尝试释放资源:成功返回true,失败返回false |
tryAcquireShared(int) | 共享方式 | 尝试获取资源:负数表示失败;0表示成功,但没有剩余资源可用;正数表示成功,且有剩余资源 |
tryReleaseShared(int) | 共享方式 | 尝试释放资源:如果释放资源后允许唤醒后续等待线程,则返回true,否则返回false |
一般来说,自定义同步器要么采用独占方式,要么采用共享方式,实现类只需实现tryAcquire、tryRelease或tryAcquireShared、tryReleaseShared中的一组即可。但AQS也支持
自定义同步器同时实现独占和共享两种方式,例如ReentrantReadWriteLock在读取时采用共享方式,在写入时采用独占方式。
ReentrantLock
ReentrantLock中的state初始值为0,表示无锁状态。在线程执行tryAcquire()获取该锁后,ReentrantLock中的state+1,这时该线程独占ReentrantLock锁,其他线程在通过
tryAcquire()获取锁时均会失败,直到该线程释放锁后,state再次为0,其他线程才有机会获取该锁。该线程在释放锁之前可以重复获取此锁,每获取一次便会执行一次state+1,因此
ReentrantLock也属于可重入锁。但是获取多少次锁就要释放多少次锁,这样才能保证state最终为0。
CountDownLatch
CountDownLatch将任务分为N个子线程去执行,将state也初始化为N,N与线程的个数一致,N个子线程是并行执行,每个子线程都在执行完后countDown()一次,state会执行CAS操作
并减1。在所有子线程都执行完后(state = 0)时会unpark()主线程,然后主线程会从await()返回,继续执行后续的动作。
秒杀设计
秒杀设计
场景
假设商城策划了一期秒杀活动,活动在每天的00:00开始,仅限前200名,那么秒杀即将开始时,后台会显示用户正在疯狂地刷新APP或者浏览器来保证自己能够尽量早的看到商品。
分析
- 用户查询的是少量的商品数据,属于查询的热点数据。可采用缓存策略,将请求尽量挡在上层的缓存。
80/20原则 - 能静态化的数据尽量静态化,提高CDN的命中率。
减少web服务器的访问压力和带宽负担 - Nginx直接访问分布式缓存(redis)。
减少tomcat的压力
类消息队列在系统设计中的使用
- 在Java线程池中我们就会使用一个队列来暂时存储提交的任务,等待有空闲的线程处理这些任务
- 操作系统中,中断的下半部分也会使用工作队列来实现延后执行
- 实现一个RPC框架时,也会将从网络上接收到的请求写到队列里,再启动若干个工作线程来处理
消息队列在秒杀中的使用
- 将秒杀请求暂存在消息队列中,然后业务服务器会响应用户“秒杀结果正在计算中”,释放了系统资源之后再处理其它用户的请求
- 后台启动若干个消费者处理消息队列中的消息,执行库存检验、下单等逻辑
- 因为若干个消费者在执行,所以数据库的并发请求也是有限的
- 消息队列中的请求是可以被堆积的,当库存被消耗完之后,消息队列中堆积的请求就可以被丢弃了
- 通过异步处理,简化秒杀过程中的异步处理
以上作用就是消息队列最主要的作用:流量削峰。可以削平短暂的流量高峰。虽说堆积会造成请求被短暂延迟处理,但是只要我们时刻监控消息队列中的堆积长度,在堆积量超过一定量时,
增加一定数量的消费者即可。
本项目中的SQL语句逻辑整理
需求
记录一下本项目中的SQL语句逻辑。
文章模块
- 查看文章的推荐文章
1
2
3
4
5
6
7
8
9
10
public interface ArticleDao extends BaseMapper<Article> {
/**
* 查看文章的推荐文章
*
* @param articleId 文章id
* @return 文章列表
*/
List<ArticleRecommendDTO> listRecommendArticles(; Integer articleId)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<select id="listRecommendArticles" resultType="com.minzheng.blog.dto.ArticleRecommendDTO">
SELECT
id,
article_title,
article_cover,
create_time
FROM
(
SELECT DISTINCT
article_id
FROM
( SELECT tag_id FROM tb_article_tag WHERE article_id = #{articleId} ) t
JOIN tb_article_tag t1 ON t.tag_id = t1.tag_id
WHERE
article_id != #{articleId}
) t2
JOIN tb_article a ON t2.article_id = a.id
WHERE a.is_delete = 0
ORDER BY
is_top DESC,id DESC
LIMIT 6
</select>SQL语句逻辑:
- 先查询文章标签表,得到当前文章的所有标签id
- 再次连接文章标签表,得到与当前文章同标签的所有文章id(排除掉当前文章)
- 通过文章id,内连接文章表 获取文章详细信息(排除已删除的文章)(按是否置顶和id排序,取前6条)
角色模块
- 查询路由角色列表(查询出所有页面能被哪些角色访问)
1
2
3
4
5
6
7
8
9
10
11
public interface RoleDao extends BaseMapper<Role> {
/**
* 查询路由角色列表
*
* @return 角色标签
*/
List<ResourceRoleDTO> listResourceRoles();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<select id="listResourceRoles" resultMap="RolePermissionMap">
SELECT
re.id,
url,
request_method,
role_label
FROM tb_resource re
LEFT JOIN tb_role_resource rep
on re.id = rep.resource_id
LEFT JOIN tb_role r
on rep.role_id = r.id
WHERE
parent_id is NOT NULL
AND is_anonymous = 0
</select>SQL语句逻辑:
- 查询资源表,连接角色资源中间表
- 用角色资源中间表,连接角色表,显示出角色信息
- 过滤掉非页面的资源(菜单组)(只查询页面),过滤掉不需要登录即可访问的资源(只查询需要登录才可以访问的资源)
策略模式介绍以及简单示例
需求
一个方法,有多种具体的实现方法,而在使用时,可以根据需要,灵活调用对应的实现方法。也可以通过修改配置文件,读取配置,调用配置的实现。
举例:
上传图片:可以用阿里云oss上传,可以直接上传存入到自己服务器。
用户登录:可以qq登录,可以微博登录,可以邮箱登录。
策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
介绍
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
实现
我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。
StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。
步骤 1
创建一个接口。
1 | public interface Strategy { |
步骤 2
创建实现接口的实体类。
1 | public class OperationAdd implements Strategy{ |
1 | public class OperationSubtract implements Strategy{ |
1 | public class OperationMultiply implements Strategy{ |
步骤 3
创建 Context 类。
1 | public class Context { |
步骤 4
使用 Context 来查看当它改变策略 Strategy 时的行为变化。
1 | public class StrategyPatternDemo { |
步骤 5
执行程序,输出结果:
1 | 10 + 5 = 15 |
自定义注解实现接口限流(接口防刷)
需求
💡 Tips:注解使用起来简单快速,配置传入2个参数即可,每个方法可设置不同值。
● 防止同一个ip地址,短时间内多次请求接口。
● 用到自定义注解、HandlerInterceptor
、Redis
、IpUtils
,实现接口限流。
创建自定义注解
💡 Tips:全忘完了。
● @interface
先声明一个自定义注解AccessLimit。
● 2个属性:senconds
秒、maxCount
最大请求次数
1 |
|
实现HandlerInterceptor
接口,拦截请求
● 编写WebSecurityHandler
方法,实现HandlerInterceptor
接口。
● 重写preHandle
方法,在方法调用前进行拦截。
● 从方法中获取我们自定义的@AccessLimit注解,如果获取到了,则进行逻辑处理。
● 从注解中获取配置的值:秒数 以及最大次数;从httpServletRequest
中获取ip地址和方法名。
● 将ip地址+方法名
做为key
值,值为1
(自增1),过期时间为seconds
存入redis
中。
● 将自增后的结果返回,与注解中的maxCount比较大小,如果大则拦截,反之则放行。
1 |
|
Redis
的工具类
每次请求使当前key
中的value
值自增1
,并将value
结果返回。
1 |
|