Interview
2018-01-06- 20210214 V2 更新
-
20180106 V1
- 写在最前面: 问题主要从这几个方面来讲what?how?why?是什么?怎么用说一下使用场景?为什么这么样用?对比其它方案是否有更好的方案?
- 总结:多思考什么场景适合用什么技术,以及是否有更好的技术方案。做过的事情需要总结成一句话来说,心得!!!!
自我介绍 5min
- 项目简介:1.核心业务流程 2.技术架构 3.技术难点亮点,解决过哪些系统难点问题? 如何发现,解决的,收获是什么? 现有系统是否还能进一步优化?
-
简历中的核心点多聊聊关于技术的。系统技术框架是怎么样的?为何采用这种技术方案? 是否有更好的技术方案?
- 计算机专业,写了点儿博客记录学习历程以及技术的积累
- 架构采用RPC通信, 站点: 参数及帖子校验header,refer等,IP拉黑,动态验证码…反作弊,反爬虫策略,简称流量过滤.
服务层: 号码资源管理模块(分配/绑定/释放)号码资源隔离以及降级策略; 运营商服务故障监测模块; 业务/性能监控模块; 查询模块;
JOB,ThreadPoll, HttpClient, ESB, 分布式锁,线程池子隔离措施.灵活配置等,做了服务重构的事,开发测试效率提升约20%;缓存,IP名单,锁..
- 故障检测: 类心跳监测,下号 + 短信/邮件告警, 上号码 做系统高可用
- 监控告警,有误报率?比如话单量下降20%的告警?节假日如何处理的?
- 系统遇到的难点:
- 网络超时造成A,B系统号码状态不一致问题.
- 反爬虫,观测,分析,对症下药,比如不返回错误,而是返回假号码,让对方以为一切正常…等
- 慢SQL优化,分析找到问题SQL,优化,效果验证?
- 个人总结:
- 技术选型时适合就好, 变化与不变性的东西隔离开,
- 不相信任何第三方服务做好容错降级处理,系统做到小步快跑,做好测试用例方便今后的扩展
- 提高内存使用率,减少IO, 指数级调用减少常量级调用
- 流量激增增加副本, 数据激增分库分表或者引入大数据HDFS
项目介绍
框架
- 框架分层
- 框架思考
- 框架总结
数据层面
- 数据沉淀
监控告警能力
计算机基础
HTTP
TCP/IP
Java
Java语言基础类原理
- equals vs ==
- Arraylist
- Volitatie
- transient vs serialization vs Externalizable
- 1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
- 2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
- 3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
- 对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关
- Synchronized
- HashMap
- https://juejin.cn/post/6927211419918319630?utm_source=gold_browser_extension
- https://www.cnblogs.com/williamjie/p/9099141.html
- https://blog.csdn.net/ns_code/article/details/36034955
- ConcurrentHashMap
- sleep vs wait,join
- notify vs notifyAll
- 等待池,锁池?
- Semphore
- CountDownLatch(以下简称 CDL)
- CyclicBarrier https://sa.sogou.com/sgsearch/sgs_tc_news.php?req=hobzKjkKLynRWJ4khJJLjdhp2-ixRXMKcc56sdMEWvE=
- ReentrantLock
- AQS
- 公平锁?非公平锁?
- CAS
- ThreadLocal
- 线程池。https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
- ThreadPoolExecutor
- 原理
- 参数如何设置
- CPU密集,IO密集
- 如何进行监控
- Callerrunspolicy风险?
- 锁,锁的原理,synchronized 和 reentrantlock的区别,偏向锁/轻量级锁/重量级锁的原理,能否从偏向锁直接升级成重量级锁
- 偏向锁,轻量锁,重量锁,锁升级过程
- 锁粗化?锁消除?
- 可重入锁
- TODO
- AQS框架
- 并发
- 单机
- 集群处理
JVM
- 对象分配?
- 栈帧?
- 如何将对象直接初始化到老年代?
- 大对象?年轻代配置大小。
- 类加载原理?
- JMM内存模型?
- https://juejin.cn/post/6926715555760046087
- JVM常用命令?
- https://my.oschina.net/feichexia/blog/196575
- GC,算法
- 为什么年轻代用复制算法,年老代用标记-清除/压缩算法?
- GCRoot 对象?
- 常量,静态对象。。。
- 标记清除
- 复制
- 标记压缩
- JVM调优
- 项目中JVM调优都调整那些参数?回收器?内存大小?CMS分代比例?日志打印信息?压缩针?
- JVM垃圾回收机制有几种?工作原理是什么?CMS清理步骤?G1?
- JVM内存模型类加载机制?为什么这么设计?
- 堆栈,对象分析?
- Serial 与 Parallel GC对别?
- CMS?
- 框架
Spring
- 事务传播机制
- 原理
- 回滚机制?
- Spring中用到的设计模式
- Interceptor,filter各自的使用场景
- Spring启动流程
- https://www.cnblogs.com/shamo89/p/8184960.html
- Spring注解声明和xml方式声明的区别?
- AOP的实现原理?
- 2种动态代理模式?
- 如何解决循环依赖问题?如果是构造方法注入能解决循环引用吗?
- 3级缓存?
- 大事务优化?
Mybatis
- $ vs # 区别?
- https://www.cnblogs.com/williamjie/p/11188716.html
Netty
- java资料
- https://mp.weixin.qq.com/s?__biz=MzkwMDE1MzkwNQ==&mid=2247495973&idx=1&sn=a18538bc3d9a3e92db729b4d347efb84&chksm=c04ae67bf73d6f6d6381cd21f164f5c9192215040f91f7403e20acb8d54976bc033e7c5dda05&token=549878447&lang=zh_CN#rd
- https://www.cnblogs.com/williamjie/p/11139302.html
工程能力
服务稳定性保障?
- 巡检?
git
- git rebase
- git
服务稳定性保障?
- Maven-jar包冲突如何处理?
- 子pom > 父pom
- 浅层依赖 > 深层依赖
- 声明前 > 声明后
线上问题排查?
- CPU负载高如何排查? 服务注册与发现 + 服务器本身可能性能不足导致
- CPU负载定义是什么?哪些因素可能导致CPU负载变高?
- 如何找到CPU占用最高的线程?
- https://juejin.cn/post/6927291610732429325
- top 得到cpu使用率最高的进程 pid
- top -H -p$pid 在进程下找到使用率比较高的线程号
- top -Hp pid shift +p 按cpu使用情况排序
- shift +m 按照内存使用情况排序
- Printf %x $tid 输出 十六进制 线程id
-
jstack $pid grep $tip 十六进制数据,ps:如果线上没有权限需要申请sudo 权限 - 如果使用dump内存
- jmap -dump:format=b,file=dumpFileName pid
- 举例子:jmap -dump:format=b,file=/tmp/dump.dat 21711
- 内存泄漏问题?
- 资料
- https://juejin.cn/post/6911624328472133646
其他坑
- https://juejin.cn/post/6911624328472133646
- 服务迁移的坑?服务注册与发现Zookeeper目前只发现到 service,不到方法层面,导致服务打错服务调用自己的服务了。
中间价
- 持久化
ES
- 原理
MySQL
- https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg==&mid=2247491894&idx=1&sn=05e52c044f20aeda15d6e0d046ffda4d&chksm=9b8671cbacf1f8ddffd0759eb69f7fe39e466273b1c895ac1620eada69c9fafcf46c8cb344f7&scene=132#wechat_redirect
- https://mp.weixin.qq.com/s?__biz=MzU3MTAzNTMzMQ==&mid=2247485938&idx=1&sn=1a3525cb38e97f67f513dbbbf6cbd732&chksm=fce7125ecb909b48135cf8fc0be669cbbaedddb00965fd2aac17c18533a516d4004f26efba62&token=1066766011&lang=zh_CN#rd
- http://blog.codinglabs.org/articles/theory-of-mysql-index.html
- https://www.cnblogs.com/williamjie/p/11081592.html
- https://www.cnblogs.com/williamjie/p/11080893.html
- https://www.cnblogs.com/williamjie/p/11081081.html
- https://baijiahao.baidu.com/s?id=1598257553176708891&wfr=spider&for=pc
- http://neoremind.com/2020/01/inside_innodb_file/
- https://www.cnblogs.com/yyjie/p/7486975.html
- 原理
- 索引
- 聚集索引 vs 非聚聚索引?
- filesort?
- 索引数据结构?
- B+Tree
- 为什么这么设计?
- 索引失效举例?
- update加锁过程?
- 事务隔离
- 默认什么隔离级别?
- RR
- 锁
- 锁种类?
- 记录锁
- 间隙锁
- 表锁
- 意向锁?
- 死锁
- 间隙锁插入意向锁?
- 死锁分析?死锁解决处理?
- 场景举例?
- 2PL,两段锁
- https://blog.csdn.net/qq4165498/article/details/76855139
- https://segmentfault.com/a/1190000012513286
- SQL优化
- 慢SLQ分析步骤?
- 慢SQL优化案例?
- mysql InnoDB为什选择自增主键做主键?
- 聚簇索引每个页面装载因子(innodb 默认为 15/16)
- 双写机制?
- redolog,undolog,binlog这几个文件都有什么用?
- 面试问题
- RR,每一种隔离级别为解决什么问题?
- MVCC
- 当前读
- 快照读
- grap锁
- explain 参数
- 资料
- https://zhuanlan.zhihu.com/p/76494612
- https://tech.meituan.com/
Redis
- 数据一致性问题
- 监听binlong
- 缓存,穿透,击穿,雪崩?
- 雪崩?不过期,随机,降级
- 击穿:更新DB加互斥锁,其他线程等待。
- redis持久化?
- RDB
- AOF
- redis扩容机制?
- Redis数据结构和使用场景
- 分布式锁实现,有什么问题?是否有更好的解决方案?
- https://www.cnblogs.com/williamjie/p/9395659.html
- setnx
- px
- 集群?
- 哨兵
- 集群
- 公司集群方式?几主几丛?
- 主从同步机制?
- 缓存击穿应急方案?
- 原子命令?
- 布隆过滤器?
- zset?
- 资料
-
- https://www.cnblogs.com/williamjie/p/11080889.html
消息队列
- https://www.cnblogs.com/williamjie/p/11080889.html
- MQ如何实现延迟队列?
- Kafka
- https://www.cnblogs.com/williamjie/p/11102282.html
- 框架
- 顺序消息是如何实现的?局部顺序?全局顺序?
- 死信的使用场景?
- 消费挤压如何处理?
- 消费失败处理,消息挤压问题处理?
- https://honeypps.com/mq/deep-interpretation-of-kafka-data-reliability/
- https://www.infoq.cn/article/depth-interpretation-of-kafka-data-reliability
- Mtrace
- LeafId
- RPC框架
- 框架
- RPC原理?
- 负载均衡?
- 容错策略?
- Pigeon
- Dubbo
- GRPC
- 序列化
- thirft
分库分表
- 如何进行?
- 解决什么问题?
- 怎么改造的?
- Zebra
Zookeeper
分布式
- 架构能力,分布式
- 分布式系统设计开发
- 分布式原理
- BASE
- CAP
- 一致性Hash
- Paxos
- Raft
- 2PC
- 3PC
- DDD理解
- 并发
- 单机并发
- 集群并发
- 高并发流量处理
- 读多写少
- 读少写多
- 设计模式
- 抽象思维
- 可扩展性
- 设计模式举例子?
- 缓存,选型
- 微服务架构
- 服务治理
- 服务性能度量和优化思考
- 常见架构问题
- 常见限流算法和优缺点?
- https://xie.infoq.cn/article/32606ec229eb96f3bb4b295ee
- 服务出现大量接口超市的问题排查和处理?
- 分布式锁问题?分布式锁怎么实现的?锁的各种使用场景?需要注意什么问题?
- ZK VS Redis
- 常见ES问题?
- ES设计时,索引如何设计的?索引的存储和查询原理和步骤?如何进行优化慢查询?深度分页问题如何解决?
- 常见分库分表问题?
- 分库分表使用场景,解决什么问题?
- 如何进行分库分表
- 分库分表一般都是跟随者业务量来进行的,改造过程会有什么坑吗?
- 同步 VS 异步 ;阻塞 VS 非阻塞
- https://www.zhihu.com/question/19732473
- https://michaelygzhang.github.io/architecture/2020/10/11/concept.html
- 服务注册中心?
- zookeeper
- eureka
- nacos
- 布隆过滤器
- 原理?优缺点?解决什么问题?
- 幂等问题场景?
- 系统高可用
- 系统高性能
- 系统可扩展
- 系统可伸缩
相关资料
- https://juejin.cn/post/6922447992452710414?utm_source=gold_browser_extension
- https://www.xuxueli.com/page/projects.html
大数据
- Hadoop
- Hive
- Spark
DevOps
- Docker
- Kubernetes
- jekines
算法
- 二分查找
- https://blog.csdn.net/ym123456677/article/details/112260079
面试资料
- https://www.cnblogs.com/williamjie/category/1485042.html
- https://www.cnblogs.com/williamjie/p/12532685.html
- https://www.cnblogs.com/williamjie/p/11139302.html
其他资料
Java Collections Framework(JCF)
- http://java-performance.com/
- https://docs.oracle.com/javase/8/docs/technotes/guides/collections/overview.html
ArrayList
- 实现List接口, 底层实现Object[],非线程安全,多线程安全考虑使用Vector/Collections.synchronizedList(List l)
- 实现RandomAccess接口,可高效进行foreach(:){}
- 构造集合时若不设置initialCapacity则第一次add操作时将开辟DEFAULT_CAPACITY = 10;的内存数组空间
- 当前数组集合大小若不足时将进行1.5倍的扩容,数组最大值为MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;即
2^32-1
; 注意扩容将调用public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);性能消耗点. - 迭代时fail-fast; 如有修改 the iterator will throw a ConcurrentModificationException.
API简介
- public boolean add(E e) {};最后一位数组下标插入;
- public void add(int index, E element) {};指定下表插入需移动index之后的元素,当size比较大时index约小,需要移动数组的元素越多,性能越低. 最坏情况是1. 先扩容 2. 调用System.arraycopy(….)移动元素
- public E set(int index, E element) {}; 替换指定位置元素
- public E remove(int index) {}; 移除指定位置元素,index约小需要前移动的元素越多,性能消耗越大.注意elementData[–size] = null; // clear to let GC do its work
- public boolean remove(Object o) {}; 1. 找到元素下标 2.移动元素 3.elementData[–size] = null; // clear to let GC do its work
LikedList
- 实现List
, Deque 双端队列,非线程安全, `插入/删除`高效,`遍历`性能低, 消耗更多的内存,产生更多对象,增加GC的次数/时间; - Collections.synchronizedList(new LinkedList(…));可做到线程安全
- 实现List
-
HashSet
无序,不重复,采用散列的存储方法,所以没有顺序; 其实就是一个hashmap,只是在在添加元素的时候对应的put(k,object),k就是要添加的值,而参数v就是一个final类型的object对象。 此处需要注意的是:由于map允许有一个key为null的键值对,所以set也就允许有一个为null的对象,唯一的一个。 -
LinkedHashSet
LinkedHashSet是HashSet的一个子类,只是HashSet底层用的HashMap,而LinkedHashSet底层用的LinkedHashMap; 元素有序. -
HashMap
数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。哈希表结合了两者的优点。 哈希表有多种不同的实现方法,可以理解将此理解为“链表的数组”;哈希表是由数组+链表组成;HashMap是基于AbstractMap; HashMap可以允许存在一个为null的key和任意个为null的value; HashMap仅支持Iterator的遍历方式; -
TreeMap
对象必须实现equals方法和Comparable/Comparator Hashtable
线程安全; HashTable基于Dictionary类,HashTable中的key和value都不允许为null,HashMap仅支持Iterator的遍历方式;
并发集合
CopyOnWriteArrayList:list
ConcurrentLinkedDeque / ConcurrentLinkedQueue
ConcurrentHashMap JDK1.7 Segment[] + HashEntry[] + HashEntry单链
其中Segment在实现上继承了ReentrantLock,这样就自带了锁的功能。 数组大小默认16,0.75增长因子,2^n次方大小.size计算方式:1不加锁连续计算元素个数最多3次,如果前后2次一样,则返回;否则给每个Segment进行加锁计算一次. ;
- 当执行put方法插入数据时,根据key的hash值,在Segment数组中找到相应的位置,如果相应位置的Segment还未初始化,则通过CAS进行赋值,接着执行Segment对象的put方法通过加锁机制插入数据,实现如下:
场景:线程A和线程B同时执行相同Segment对象的put方法
- 线程A执行tryLock()方法成功获取锁,则把HashEntry对象插入到相应的位置;
- 线程B获取锁失败,则执行scanAndLockForPut()方法,在scanAndLockForPut方法中,会通过重复执行tryLock()方法尝试获取锁,在多处理器环境下,重复次数为64,单处理器重复次数为1,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程B;
- 当线程A执行完插入操作时,会通过unlock()方法释放锁,接着唤醒线程B继续执行;
ConcurrentHashMap JDK1.8 Node数组 + CAS + Synchronized | 数组+链表+红黑树
volatile类型的变量baseCount计算size值,因为元素个数保存baseCount中,部分元素的变化个数保存在CounterCell数组中,通过累加baseCount和CounterCell数组中的数量,即可得到元素的总个数;
-
扩容时优先扩容数组(<64时),2倍数组进行扩容; 然后才是当单链>8时转化红黑树;
ConcurrentSkipListMap
基于跳跃列表(Skip List)的ConcurrentNavigableMap实现。本质上这种集合可以当做一种TreeMap的线程安全版本来使用。- ConcurrentSkipListSet:使用 ConcurrentSkipListMap来存储的线程安全的Set。
JDK集合总结:
- 选择合适的集合,使用泛型避免出现ClassCastException
- 数组查询高效,插入删除移动元素,每次add到最后一个下标,容量不够则扩容复制元素
System.arraycopy()
; - 链表每次新建Entry对象需要更多的内存空间,增加GC,插入删除高效遍历耗时.
- 重写equals & hashcode方法,注意:equals相等必定hashcode相同; 若hashcode相同equals不等则造成hash冲突,导致单链/rehash等操作
- 可以设置初始容量来避免重新计算hash值或者是扩容,Map的key尽量采用不可变对象比如String
- tree相关Comparable/Comparator
- 实现了RandomAccess接口的类可以通过foreach遍历高效,否则采用Iterator迭代器遍历,Iterator可以进行remove操作.
- 编程的时候接口优于实现,底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回 null。
- 集合
- 集合面试题
- Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
- 集合相关runoob
- JCF-github
- final/static?TODO
AQS (AbstractQueuedSynchronizer)
队列同步器AQS是用来构建锁或其他同步组件的基础框架,内部使用一个int成员变量表示同步状态, 通过内置的FIFO队列来完成资源获取线程的排队工作,其中内部状态state,等待队列的头节点head和尾节点head, 都是通过volatile修饰,保证了多线程之间的可见。
- static final int CANCELLED = 1;SIGNAL = -1等待触发状态;CONDITION = -2等待条件状态;PROPAGATE = -3状态需要向后传播;
- 子类重写tryAcquire和tryRelease方法通过CAS指令修改状态变量state。
- AQS详解
- 线程A执行CAS执行成功,state值被修改并返回true,线程A继续执行。
- 线程A执行CAS指令失败,说明线程B也在执行CAS指令且成功,这种情况下线程A会执行步骤3。
- 生成新Node节点node,并通过CAS指令插入到等待队列的队尾(同一时刻可能会有多个Node节点插入到等待队列中), 如果tail节点为空,则将head节点指向一个空节点(代表线程B)
- node插入到队尾后,该线程不会立马挂起,会进行自旋操作。因为在node的插入过程,线程B(即之前没有阻塞的线程)
可能已经执行完成,所以要判断该node的前一个节点pred是否为head节点(代表线程B),如果pred == head,
表明当前节点是队列中第一个“有效的”节点,因此再次尝试tryAcquire获取锁
- 如果成功获取到锁,表明线程B已经执行完成,线程A不需要挂起
- 如果获取失败,表示线程B还未完成,至少还未修改state值。进行步骤5
- 前面我们已经说过只有前一个节点pred的线程状态为SIGNAL时,当前节点的线程才能被挂起。
- 如果pred的waitStatus == 0,则通过CAS指令修改waitStatus为Node.SIGNAL。
- 如果pred的waitStatus > 0,表明pred的线程状态CANCELLED,需从队列中删除。
- 如果pred的waitStatus为Node.SIGNAL,则通过LockSupport.park()方法把线程A挂起,并等待被唤醒,被唤醒后进入步骤6。
- 线程每次被唤醒时,都要进行中断检测,如果发现当前线程被中断,那么抛出InterruptedException并退出循环。 从无限循环的代码可以看出,并不是被唤醒的线程一定能获得锁,必须调用tryAccquire重新竞争, 因为锁是非公平的,有可能被新加入的线程获得,从而导致刚被唤醒的线程再次被阻塞,这个细节充分体现了“非公平”的精髓。
- 释放锁过程:
- 如果头结点head的waitStatus值为-1,则用CAS指令重置为0;
- 找到waitStatus值小于0的节点s,通过LockSupport.unpark(s.thread)唤醒线程
CAS(Compare and Swap)
- CAS三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。
- CAS存在一个很明显的问题,即ABA问题。AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。
- CAS 可以复用缓存
synchronized
- synchronized可以保证方法或代码块在运行时,同一时刻只有一个线程可以进入到临界区(互斥性),同时它还保证了共享变量的内存可见性
- monitorenter/monitorexit
- synchronized资料
ReentrantLock Synchronized
线程池 资料 场景?优势/缺点是什么?
- ThreadLocal 内存何时情况有可能发生内存泄漏? 如何解决?答得是remove? 待核实!!threadlocal使用线程局部变量,注意使用后释放?static修饰?
- 线程安全 与 JVM内存模型之间的关系?
- 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
- 并发集合容器,线程安全的非线程安全的分别都有哪些?
- 如何看当前线程是否是线程安全的? 特征是什么? 【资料](https://yq.aliyun.com/articles/75403)
IO/BIO/NIO/AIO Netty
- NIO最底层与操作系统是如何交互的? 最底层 ! 或者是AIO 最底层是与操作系统交互的??
- IBM-NIO
- NIO-1
- Netty 框架模块. NIO ?Netty-1
Netty长链接和短链接 :
基本思路:netty服务端通过一个Map保存所有连接上来的客户端SocketChannel,客户端的Id作为Map的key。每次服务器端如果要向某个客户端发送消息,只需根据ClientId取出对应的SocketChannel,往里面写入message即可。心跳检测通过IdleEvent事件,定时向服务端放送Ping消息,检测SocketChannel是否终断。Netty自带心跳检测功能,IdleStateHandler,客户端在写空闲时主动发起心跳请求,服务器接受到心跳请求后给出一个心跳响应。当客户端在一定时间范围内不能够给出响应则断开链接。 心跳 机制. 心跳机制的工作原理是: 在服务器和客户端之间一定时间内没有数据交互时, 即处于 idle 状态时, 客户端或服务器会发送一个特殊的数据包给对方, 当接收方收到这个数据报文后, 也立即发送一个特殊的数据报文, 回应发送方, 此即一个 PING-PONG 交互. 自然地, 当某一端收到心跳消息后, 就知道了对方仍然在线, 这就确保 TCP 连接的有效性. 在 Netty 中, 实现心跳机制的关键是 IdleStateHandler,
- 使用 Netty 实现心跳机制的关键就是利用 IdleStateHandler 来产生对应的 idle 事件.
- 一般是客户端负责发送心跳的 PING 消息, 因此客户端注意关注 ALL_IDLE 事件, 在这个事件触发后, 客户端需要向服务器发送 PING 消息, 告诉服务器”我还存活着”.
- 服务器是接收客户端的 PING 消息的, 因此服务器关注的是 READER_IDLE 事件, 并且服务器的 READER_IDLE 间隔需要比客户端的 ALL_IDLE 事件间隔大(例如客户端ALL_IDLE 是5s 没有读写时触发, 因此服务器的 READER_IDLE 可以设置为10s)
- 当服务器收到客户端的 PING 消息时, 会发送一个 PONG 消息作为回复. 一个 PING-PONG 消息对就是一个心跳交互.
- 资料
- 资料IO
线程是如何通信的?
wait()、notify()
还是阻塞队列? 还是主内存共享?CSP?Java的线程通信与Gorutine最根本的区别是什么? 线程通信
- CountDownLatch 适用于一个线程去等待多个线程的情况。
-
为了实现线程间互相等待这种需求,我们可以利用 CyclicBarrier
- 线程有哪些状态,继承thread,创建多个线程,是分别执行自己的任务。实现runnable,创建多个线程是多个线程对某一共同任务的执行。区别。
字符串不变性的好处/不变性编程 常量池
- 谈到了不可变类和String,大意就是 他会更倾向于使用不可变类,它能够缓存结果,当你在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,这个问题不存在的。如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。
- 迫使String类设计成不可变的另一个原因是安全,当你在调用其他方法,比如调用一些系统级操作之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,其内部的值被改变了,可能引起严重的系统崩溃问题,这是迫使String类设计成不可变类的重要原因。
- 效率高,字符串池可复用节约内存高效
- 安全性(因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。)
- 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
- hashcode在字符串生成时就计算好了比如作为map的key值更高效
- 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
- 异常处理,主要在异常时最后关闭回收资源,定期分析异常日志找到问题处理问题,具体明确是那类异常提早抛出延迟捕获?
HttpClient
- HttpClient 连接池实现?连接池原理,设计时的注意事项?连接池中连接都是在发起请求的时候建立,并且都是长连接
- 降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗,别小看这几次握手,本人经过测试发现,基本上3倍的时间延迟
- 支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接
- 连接池中连接都是在发起请求的时候建立,并且都是长连接;
- 连接池内的连接数其实就是可以同时创建多少个 tcp 连接,httpClient 维护着两个 Set,leased(被占用的连接集合) 和 avaliabled(可用的连接集合) 两个集合,释放连接就是将被占用连接放到可用连接里面。
异步的httpclient?使用场景?
- 多线程与并发编程: 进程: 一个计算机的运行实例,有自己独立的地址空间,包含程序内容和数据,不同进程间相互隔离,拥有各自的各种资源和状态信息,包括打开的文件,子进程和信号处理等。 线程: 程序的执行流程,CPU调度的基本单位,线程拥有自己的程序计数器,寄存器,栈帧,同一进程中的线程拥有相同的地址空间,同时共享进程中的各种资源
- excutorServer接口,继承自executor;提供了对任务的管理:submit(),可以吧Callable和Runnable作为任务提交,得到一个Future作为返回,可以获取任务结果或取消任务。 提供批量执行:invokeAll()和invokeAny(),同时提交多个Callable;invokeAll(),会等待所有任务都执行完成,返回一个包含每个任务对应Future的列表; invokeAny(),任何一个任务成功完成,即返回该任务结果。超过时限后,任何尚未完成的任务都会被取消。
- 多线程开发中应该优先使用高层API,如果无法满足,使用java.util.concurrent.atomic和java.util.concurrent.locks包提供的中层API, 而synchronized和volatile,以及wait,notify和notifyAll等低层API 应该最后考虑。
框架
- springmvc,boot,mybatis框架
- tomcat与spring容器关联的点? web.xml?
- spring容器启动原理? 资料
- Spring AOP两种实现方式?
- SprintBoot与SpringMVC的区别?
JVM
JVM 类加载的整个过程
加载-> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 - > 卸载
双亲委派 -> 验证:文件格式/魔术开头.. -> 准备: 为类的静态变量分配内存,并将其`初始化为默认值`
-> 解析: 把类中的符号引用转换为直接引用 -> 初始化: 为类的静态变量`赋予正确的初始值`
验证,准备阶段,静态变量初始化,解析,符合引用变直接引用。
初始化,双亲委派机制,主动被动引用
,使用,卸载。
- heap, java stack, native method stack, PC register, method area; 资料
ClassLoader?比如根ClassLoader?JVM级别的ClassLoader?系统扩展级别的ClassLoader?要非常具体和详细的描述?
- Bootstrap class loader(负责加载虚拟机的核心类库,如 java.lang.* ,JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。)C/C++编写 -> Extension class loader:这个加载器加载出了基本 API 之外的一些拓展类,JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。 -> AppClass Loader: 应用/程序自定义类加载器,负责加载用户路径(classpath)上的类库。
- 双亲委派机制: 系统类防止内存中出现多份同样的字节码; 保证Java程序安全稳定运行;
Class.forName()和ClassLoader.loadClass()区别
- Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
- ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
- Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。
ClassNotFoundException 与 NoClassDeFoundError 的区别
问题排除 jvm内存溢出?CPU100%?堆内存溢出?栈异常死锁?涉及哪些Linux命令?机器负载高,怎么办,用那些命令可以解决。
g1垃圾为啥时间可控的?
- 资料
- 资料-2
- 可以有计划地避免在Java堆的进行全区域的垃圾收集;G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;
每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);这就保证了在有限的时间内可以获取尽可能高的收集效率;
GC算法?JVM优化手段?常见的优化场景?
- 资料-Java优化
Tomcat/Netty Server相关
- TODO
中间件
Redis VS Memcached 优缺点? 如何选型?
- redisVSMemcached
redis高效点?redis数据结构?Redis为什么高效,原理是什么,为什么使用跳表结构存储?持久化实现方式?
- redis源码
分布式锁都用哪些? Memcached ? Redis ? Zookeeper?
优缺点是什么?什么场景下用那种,还有底层内部实现是什么? 具体实现锁调用的API是什么?
Google Protocol Buffer 高性能原因?
- 资料-protobuf-1
- 资料-protobuf-2
- 资料-protobuf-3
zookeeper节点类型,选主机制? 主从怎么做?有何问题?
- 资料
mq如何保证幂等
- a,mq,落地db,b。a生成一个业务相关全局唯一biz_id,a发给mq,mq落地db,返回给a说成功了,若失败a重试,mq生成内部一个唯一msg_id业务无关的,这样保证b接受的幂等,会有定时删除重复的数据。b接受消息时,根据a生成的id,判断以此保证b幂等,mq重复策略,可以是1s,3s,5s重复发送机制。
环行队列实现延迟消息,可以很好避免定时任务的扫库的效率低的问题? RingBuffer?
- 资料-cache
DB
mysql的存储数据结构,b树与b+数的区别,mysql是否indexia索引?有哪些索引方式?
大数据量如何做分布式存储?分库分表?如何分怎么设计?还是利用HDFS?
mysql优化?MySQL数据存储?MySQL数据结构存储方式以及读写锁?
分库分表查询,业务拼装,多线程查询,热点数据放一起,冗余数据,防止攻击接口幂等,分库分表事务?
比如找第200页数据每页5条,三个库,怎么查?如果查询维度很多该怎么办?
1 order by time offset x limit y 改写为offset 0 limit x+y 内存中排序,少可以多性能差
2 禁止跳页,只能顺序翻页,记录maxtime, order by time where time
> maxtime, limit y 每次只返回一页数据
3 二次查询法
offset x\n limit y ,n 数据个数,
找到mintime,order by time betwwen mintime and maxtime,第一次取出的,设置虚拟mintime,找到全局offset,因为本来就按时间排序,得到全局offset,
DB事务隔离级别?
悲观锁:
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系
统不会修改数据)。例如:
select * from table_name where id = ‘xxx’ for update;
这样查询出来的这一行数据就被锁定了,在这个update事务提交之前其他外界是不能修改这条数据的,但是这种处理方式效率比较低,一般不推荐使用。
Mysql 乐观锁,共享锁,悲观锁,排他锁?
分布式服务架构设计
设计一个RPC框架需要关注哪些? 比如像Dubbo?
- 资料-DongFangHongRpc
序列化?都有哪些对象的序列化方式以及优缺点是什么?
RPC通信,Netty? 怎么做通信的?
编码器,解码器? 什么来实现? 那种更好些?
服务的注册与发现?用什么实现,那种实现方式更好?Zookeeper, Eruk…?
设计一个全局ID生成器,怎么设计?性能/有序性/?
- http://vesta.cloudate.net/vesta/doc/%E7%BB%9F%E4%B8%80%E5%8F%91%E5%8F%B7%E5%99%A8(Vesta)%20-%20%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1.html
接口幂等事如何实现的?
幂等:
Bool withdraw(account_id, amount)
——>
int create_ticket(); //创建唯一id
bool idempotent_withdraw(ticket_id, account_id, amount);
幂等设计:服务端确保生成唯一标识符。
怎样理解微服务?
设计时考虑如果当前业务出现错误如何更好恢复?自动恢复能力?
如何做到系统高可用?
服务降级,容错,号码运营商降级,服务本身降级,如果所有运营商都出现问题,反真实号码,imc出现问题,走本地缓存,最后是服务恢复。
隔离,幂等,异步,超时,服务降级?
- 高可用服务,请求限流器表每分钟每个用户的请求量,并发请求限流器,抛弃非关键api,处理关键api机制,
令牌算法,注意可随时调控。限流器不应该影响到正常的业务,被限流的请求注意文案,可开启或关闭限流器。
重试时注意重试次数?并发量与QPS之间的关联关系?
分布式事务
2PC,3PC,阿里的TCC?是否还有?核心思想如何实现的?分布式事务中的关键点有哪些?
2PC,3PC,TCC分别都有哪些优缺点? 底层如何实现?看源码? 是否还有更优秀的分布式事务框架?
数据一致性问题?分布式注意的点,prosx原理?raft?Paxos原理,2PC,3PC原理
服务容器化
Docker底层框架架构?
CS
TODO 复习数据结构
B树与B+树区别?
跳表?
其他
开源代码阅读过哪些框架,Spring是否阅读过?JDK核心的有哪些比较了解?
想问的问题?
你有哪些优点?有哪些缺点? 有哪些这次面试过程中没表现出来的?
对公司是否有些诉求?比如期待未来进入什么样的团队?
优化题:
规则:
1, A,B,C
2, C,D
3, E,F
....
输入: ACD -> true; ACEF -> true; 任意命中一个规则返回true/false;
TODO
算法
笔试题
- 请用jdk7实现以下shell脚本的功能, 注意异常处理及输出格式(alibaba)
cat /home/admin/logs/webx.log | grep "login" | awk '{print $6}' | sort | uniq -c | sort -k 2r
------------------
902 www.taobao.com
20 s.taobao.com
9 i.taobao.com
- 二分查找(dd)
// 二分查找递归实现
public static int binSearch(int srcArray[], int start, int end, int key) {
int mid = (end - start) / 2 + start;
if (srcArray[mid] == key) {
return mid;
}
if (start >= end) {
return -1;
} else if (key > srcArray[mid]) {
return binSearch(srcArray, mid + 1, end, key);
} else if (key < srcArray[mid]) {
return binSearch(srcArray, start, mid - 1, key);
}
return -1;
}
// 二分查找普通循环实现
// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
- 二叉树的遍历,深度/广度/查找
static class TreeNode{
Object val = null;
TreeNode left = null;
TreeNode right = null;
public TreeNode(Object val) {
this.val = val;
}
public TreeNode(TreeNode left, TreeNode right, Object val) {
this.left = left;
this.right = right;
this.val = val;
}
}
/**
* 🌲结构
* A
* B C
* D E
* F
* G H
*/
static TreeNode builderTree(){
TreeNode G = new TreeNode(null, null, 'G');
TreeNode H = new TreeNode(null, null, 'H');
TreeNode F = new TreeNode(G, H, 'F');
TreeNode D = new TreeNode(null, F, 'D');
TreeNode E = new TreeNode(null, null, 'E');
TreeNode B = new TreeNode(D, E, 'B');
TreeNode C = new TreeNode(null, null, 'C');
TreeNode A = new TreeNode(B, C, 'A');
return A;
}
/**
* ->A->B->D->F->G->H->E->C
*/
static void 深度优先遍历(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();//为什么用栈?先进后出特点
if (root != null){
stack.push(root);
} else {
return;
}
while (!stack.empty()) {
TreeNode treeNode = stack.pop();
System.out.print("->"+ treeNode.val);
if (treeNode.right != null) { //注意是先右边,为什么?因为栈先进后出
stack.push(treeNode.right);
}
if (treeNode.left != null) {
stack.push(treeNode.left);
}
}
}
/**
* ->A->B->C->D->E->F->G->H
*/
static void 广度优先遍历(TreeNode root) {
Queue<TreeNode> queue = new ArrayBlockingQueue<TreeNode>(10);//先进先出特点
if (root != null) {
queue.add(root);
} else {
return;
}
while (!queue.isEmpty()) {
TreeNode treeNode = queue.poll();
System.out.print("->"+ treeNode.val);
if (treeNode.left != null) {
queue.add(treeNode.left);
}
if (treeNode.right != null) {
queue.add(treeNode.right);
}
}
}
//二叉树查询就很简单了,在遍历的基础上,加判断条件就可以了,比如:
static TreeNode 深度优先遍历_查询(TreeNode root, Object target) {
Stack<TreeNode> stack = new Stack<>();//为什么用栈?先进后出特点
if (root != null){
stack.push(root);
} else {
return null;
}
while (!stack.empty()) {
TreeNode treeNode = stack.pop();
if (treeNode.val.equals(target)) {
System.out.println("找到了!!");
return treeNode;
}
if (treeNode.right != null) { //注意是先右边,为什么?因为栈先进后出
stack.push(treeNode.right);
}
if (treeNode.left != null) {
stack.push(treeNode.left);
}
}
return null;
}
- 写一个死锁的例子。
- 实现令牌限流/ 另一种方式限流{漏桶,令牌桶算法(Guava中的Ratelimiter来实现控制速率),信号量(Semaphore)};
- wait/notify 实现生产者/消费之模式
- Linux命令查询, 比如 nginx日志,过滤 xxx.do请求的前十名的IP倒序排列
统计xx.log某字符串出现前10名输出到testfile中
sdate=2017-09-20 23:59:32&txt=com.ford.fordmobile&client_id=x
切割后:
sdate=2017-09-20 23:59:32&
com.ford.fordmobile
_id=x