本篇记录一下自己面试的一些中大厂的 1~3年Java开发的面试题以及自己对题目理解的答案(结合网上查的资料)部分可能更新的不及时,有问题可以评论区讨论。
面试题不分先后
- 1、hashmap rehash 过程 扩容几倍,扩容时候插入数据会怎样,扩容时候取数据怎样
- 2、 aqs 每个节点存储的数据格式是什么,怎么唤醒阻塞的队列
- 3、 redis reactor模型
- 4、 redis 跳表数据结构,新增元素会有什么效果。
- 5、 Redis怎么用的,MySQL和Redis一致性怎么保证。
- 6、 MySQL的幻读怎么避免的
- 7、MySQL中B和B+树区别
- 8、项目中比较难的设计
- 9、定时任务框架有了解吗
- 10、分布式锁了解吗
- 11. ReentrantLock 怎么实现的
- 12、查日志一个时间段带有error行的日志
- 13. 查所有TCP状态是Listen的进程
- 14. 怎么区分一个终端向另一个server发送的TCP连接包
- 15. 冒泡排序,选择排序实现逻辑
- 16、数据库索引设计优化
- 17、SQL:找student表 course表,求每个学生得分平均分及格的学号和平均分
- 18、postgresql和MySQL有啥区别
- 19. 为啥有的地方用消息队列有的地方用Redis的发布订阅模式
- 20. 用到什么监控系统了吗
- 21. 如果想动态查千万级数据怎么办
- 22. 树的最大高度,广度优先和深度优先遍历
- 23. 用栈怎么实现队列
- 24. git使用
- 25. hashmap put的返回值是什么
- 26. SQL:or走索引吗,in查找走索引吗
- 27. docker cmd 和 entrypoint 区别
- 28. docker add和copy的区别
- 29. dockerfile 编写有什么需要注意的
- 30、hashmap的红黑树和链表什么时候转换,为啥设置这么个值8/6
- 31. hashmap的hash算法
- 32. 重写hashcode和重写equals的区别和关系
- 33. 红黑树插入的时间复杂度
- 34. ElasticSearch 为什么能做到实时大数据搜索?
- 35. kafka partition与topic之间的关系。三个partition用几个多线程。再加多线程有效果吗,多个partition能保证顺序消费吗
- 36. redis都有什么数据结构,说了下sds和skiplist
- 37. MySQL主键索引和唯一索引哪个快
- 38. MySQL主键索引和普通索引有啥区别,怎么进行查找
- 39. spring和springboot的区别
- 40. 微服务了解吗,微服务怎么拆分
- 44. 怎么控制topic的分区配置和消费配置
- 45. 看你写了项目使用了openresty 介绍一下这个干嘛的,为啥要用
- 46.虚拟内存介绍一下
- 47. 进程把内存用光了,再起新的进程怎么办?
- 48. 线程的状态都有哪些?主线程中创建了一个线程去调用一个接口,此时这个被创建的线程是什么状态,什么时候被唤醒。怎么被唤醒的?
- 49. 看你会golang,项目中一般用来做什么。说说跟Java的不同,golang的协程介绍一下。
- 50.按时间搜索快的数据库有哪个?
- 51.日志数据量大(千万~亿),现在采用定时任务+汇总表,如果要想实时查询该怎么办?
- 52.流式处理的中间件了解哪些?
- 53.rpc框架了解哪些?
- 54.grpc 的底层序列化协议是什么? 为啥用protobuf做序列化协议,优势是什么,不足是什么
- 55. 介绍一下分布式系统都有什么特征
- 56. 零拷贝介绍一下
- 57. 介绍一下微服务框架springcloud
- 58. kafka了解什么特征
- 59. Java的内存模型是什么样的
- 60.cdn实现原理是什么?
- 61. Java中的垃圾回收算法说说,垃圾回收的分区都有什么,什么时候会从一个区到另一个区。
- 62. CMS收集器用什么垃圾回收算法了,G1用什么垃圾回收算法了,为什么。
- 63.数据库的sharding分库分表有哪些中间件实现了解吗?(balabala,一个订单表分成1024个库)
- 64.分布式事务了解吗?怎么实现?
- 65.eureka 底层使用了什么机制?
- 66.对跨平台的理解?
- 67. MySQL更新一行记录会触发表级锁还是行级锁
- 68、关于聚簇索引说一说
- 69、MySQL更新记录是锁的行还是锁的索引
- 70、docker 底层实现原理是什么
- 71、docker-compose了解吗,简单说说干啥的
- 72、判断是同一个类的方法?
- 73、hashmap中的链表是头部插入还是尾部插入? 为什么?
- 74、redis 字符串有用了sds,默认是什么数据结构,直接就是sds吗
- 75、mvcc加的两列都是什么
- 76、MySQL B+树一次io多少字节
- 77、epoll与select区别
- 78、锁升级
- 79、Java怎么实现单例模式,为什么用volatile
- 80、线程池参数都有哪些?
- 算法题
1、hashmap rehash 过程 扩容几倍,扩容时候插入数据会怎样,扩容时候取数据怎样
- 扩容两倍。 hashmap多线程不安全,扩容时多个线程操作可能会导致死循环,循环链表。所以如果不是在单线程环境使用时,用concurrentHashmap 这个是synchronized和cas乐观锁的线程安全的map数据结构。
- get取数据时是算hash,hash算法:
return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
然后用first = tab[n - 1 & hash]
取元素(如果哈希冲突什么的就红黑树和链表遍历了) 所以多线程扩容时取数据看你扩容过程中这个数据有没有被resize映射完,映射完了就能正常获取了。 - put 操作当下面的场景出现就会触发resize扩容。 可以看到是当前线程会调resize方法(同步调用),而resize实际上操作就是创建一个新的数组,然后将老的数据重新算一遍hash再加到新的数组里面。可见如果一个线程在往新数组放值的时候,另一个线程put和get操作都是被影响的。可以debug看看
main:
Map<String,String> map = new HashMap<>(32);
for (int i = 0; i < 24; i++) {
map.put(""+i,"1");
}
map.put("a","b");
map:
if (++this.size > this.threshold) {
this.resize();
}
2、 aqs 每个节点存储的数据格式是什么,怎么唤醒阻塞的队列
- aqs维护线程的状态,维护一个CLH队列,也是使用cas乐观锁的机制去维护waitStatus
//volatile int waitStatus //节点状态
//volatile Node prev //当前节点/线程的前驱节点
//volatile Node next; //当前节点/线程的后继节点
//volatile Thread thread;//加入同步队列的线程引用
//Node nextWaiter;//等待队列中的下一个节点
//节点的状态如下表示
//int CANCELLED = 1//节点从同步队列中取消
//int SIGNAL = -1//后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行;
//int CONDITION = -2//当前节点进入等待队列中
//int PROPAGATE = -3//表示下一次共享式同步状态获取将会无条件传播下去
- 当要唤醒或者阻塞一个线程时都是用的LockSupport 这个工具类来完成的。底层也是调用了unsafe包和native方法。
3、 redis reactor模型
- 这个我之前有总结过:https://blog.csdn.net/finalheart/article/details/107881548
- reactor模型类似Java的nio一个线程负责注册和转发请求,其他线程负责处理请求、
4、 redis 跳表数据结构,新增元素会有什么效果。
- 这个首先要了解跳表的数据结构,是由多层的链表组成,再插入时会根据算法生成高层的索引以便快速搜索,而存储时是按顺序存储的,zset的数据结构就用到了链表。插入和查找类似,用类似二分法查找的方式进行查找,最后锁定要插入的位置。
- 而插入就会按原有的顺序插入到目标位置,根据算法生成更高层的映射索引。(偷张图)
5、 Redis怎么用的,MySQL和Redis一致性怎么保证。
- redis存一些缓存的数据,以免每次请求都查库。 还有其他的用法,记录历史时间戳等等…
- 先update MySQL再删除Redis,Redis删除失败进行重试,重试还是失败加到队列中,人工介入
6、 MySQL的幻读怎么避免的
mvcc以及next-key锁(行锁+gap锁) 推荐看极客的《MySQL实战45讲》
先记录一下MySQL中的几种锁
-
记录锁:对索引记录的锁定。必须是唯一索引才生效,行锁衍生出来的
记录锁只锁定索引,如果没有建立索引,innodb也会创建一个隐藏的聚簇索引。
比如对id=1的行开启 for update排它锁 其他事务对id=1的行进行写就阻塞住了。 -
间隙锁(gap)x锁:间隙锁作用在索引记录之间的间隔,又或者作用在第一个索引之前,最后一个索引之后的间隙。不包括索引本身。非唯一索引
where子句查询条件是唯一键且指定了值时,next-key退化成行锁
如果where语句指定了范围,next-key锁 -
间隙锁又称之为区间锁,每次锁定都是锁定一个区间,隶属行锁。既然间隙锁隶属行锁,那么,间隙锁的触发条件必然是命中索引的,当我们查询数据用范围查询而不是相等条件查询时,查询条件命中索引,并且没有查询到符合条件的记录,此时就会将查询条件中的范围数据进行锁定
-
间隙锁只会出现在可重复读的事务隔离级别中,mysql5.7默认就是可重复读。
-
间隙锁锁的是一个区间范围例如
第一个事务执行但不提交。
update xxx where id > 1 and id<3
第二个事务执行
update xxx where id = 1
insert xxx id = 4
update xxx where id = 3
那么此时第二个事务中的id=3的更新将会失败,原因是间隙锁,锁住了(1,3]
- 临键锁也是next-key锁。临键锁锁定区间和查询范围后匹配值很重要,如果后匹配值存在,则只锁定查询区间,否则锁定查询区间和后匹配值与它的下一个值的区间。
例如如果数据库有 id = 1,5,7的索引
第一个事务
update xxx where id > 1 and id<8 -----上的临键锁是 (1,正无穷)
第二个事务执行
update xxx where id = 1 成功
insert xxx id = 4 失败
update xxx where id = 9 失败
如果检索的条件后匹配是没在索引中存在的,则会锁到 正无穷!!!
上面那个id<8换成 id<7后 由于7是存在的索引,就会锁 (1,7]
- 自增锁:表级别的锁,如果一个事务正在向表中插入值,则任何其他事务必须等待,以便第一个事务插入的行接收连续的主键值。
间隙锁和临键锁就是MySQL在RR防止幻读的解决办法
MVCC可以防止并发加锁问题
- MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
- 对于读来说,会将当前事务之前的数据行(没删)的读出来;
- 对于update操作来说,插一行新的,删一行旧的
- 也叫快照读。 读当前事务之前版本的数据 这样就不会有幻读了。
- 对于已提交读和可重复读两个状态下, mvcc还不太一样。 已提交读每次读取数据前都会生成一个readView 而可重读只在第一次读的时候生成readView
7、MySQL中B和B+树区别
- B和B+树都是矮胖的数据结构,为了每行多存储数据,减少磁盘IO。innodb的实现用的是b+树
- 区别在于 B+树(聚簇索引使用的数据结构)非叶子节点只存储索引值(例如主键id是索引就存主键id,没有主键就是第一个唯一列,否则是默认生成的隐藏列)因为主键是几乎是建表不可少的,所以就用主键id为例,叶子节点存储的才是全部数据。并且按照顺序存储。
- 而B树则是非叶子节点也存储数据。因为磁盘IO每次取的数据容量是固定的,所以叶子节点存储所占容量大了,IO也就多了。这也是使用B+树的原因。
- 顺便说一下:现在MySQL用的都是B+树了,如果是一个辅助索引,索引的name字段,会根据索引值(name)进行查找,找到主键之后回表聚簇索引取全部的数据,如果只是查主键ID则不需要再回表了。(这里举例以ID为主键的聚簇索引为前提)
8、项目中比较难的设计
- 这个题是最头疼的,一定要提前准备!
9、定时任务框架有了解吗
- 这个我并没有使用过,定时任务是通过crontab调用golang的程序来完成的。golang本身也有一些GitHub的开源库实现了定时任务。
- 如果Java栈的话,查了一下可以用@Scheduled注解,先在springboot的启动类上加@EnableScheduling然后创建下面类就行了。
package com.zy.integrate.util;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @Author zhangyong
*/
@Component
public class ScheduledTask {
@Scheduled(fixedRate = 3000)
public void scheduledTask() {
System.out.println("任务执行时间:" + LocalDateTime.now());
}
//任务执行时间:2020-11-16T11:44:06.213024
//11:44:06.215 logback [main] INFO org.apache.coyote.http11.Http11NioProtocol - 173 - Starting ProtocolHandler ["http-nio-8088"]
//11:44:06.246 logback [main] INFO o.s.boot.web.embedded.tomcat.TomcatWebServer - 204 - Tomcat started on port(s): 8088 (http) with context path ''
//11:44:06.251 logback [main] INFO com.zy.integrate.IntegrateApplication - 59 - Started IntegrateApplication in 2.514 seconds (JVM running for 8.11)
//任务执行时间:2020-11-16T11:44:09.214557
//任务执行时间:2020-11-16T11:44:12.214634
//任务执行时间:2020-11-16T11:44:15.215797
}
- 其他的定时任务框架有一些第三方的,例如 xxl-job 看了一下功能挺全面的。如果是高并发比较复杂的业务可以使用elastic-job也是不错的选择,如果常规业务用 xxl-job就可以了。都是分布式定时任务框架。
10、分布式锁了解吗
- 可以用Redis MySQL zookeeper MySQL的话就建立个唯一索引,加锁就插入,解锁就删除,可见效率多低。
- 用Redis实现分布式锁,Java栈有个redisson框架是基于lua脚本的 https://blog.csdn.net/w372426096/article/details/103761286
11. ReentrantLock 怎么实现的
- 实现了抽象类aqs,基于cas和synchronized实现的。维护state字段。底层用的aqs然后把这个问题引到aqs了。
12、查日志一个时间段带有error行的日志
- 用下面的命令,其中日期根据不同日期格式做调整。
sed -n '/20201115 13:52:47/','/20201115 14:02:47/p' ent.log |grep error
13. 查所有TCP状态是Listen的进程
netstat -tunlp|grep LISTEN
14. 怎么区分一个终端向另一个server发送的TCP连接包
这个问题先理解一下三次握手和四次挥手。可以参考下这篇:https://juejin.im/post/6844904070000410631#comment
- 对于三次握手,其实三次是因为三次完全可以完成建立双全工通信了,而4,5次也是OK的。
- 三次包括,客户端先发送建立连接请求,此时客户端处于syn_send状态;服务端确认请求,服务端进入syn_rcvd状态;然后客户端再次发送包确认,此时客户端进入established状态,服务端接收到后也进入established状态。
- 整体的流程其实可以理解为,客户端确认服务端能发能接收,服务端确认客户端能发能接收。粗略理解仅此而已、第一步服务端确认客户端能发,第二步客户端确认服务端能发能接收,第三步服务端确认客户端能接收。
- 四次挥手则是:第一步客户端向服务端(服务端也可以向客户端发送FIN断开连接请求)发送FIN断开连接请求进入FIN_WAIT1状态,表示客户端已经发送完毕;第二步服务端接收到FIN后,发送确认ACK包,发送完服务端进入CLOSE_WAIT状态,客户端接收到后进入FIN_WAIT2状态。第三步等服务端发送完数据后也向客户端发送一个FIN关闭请求,发送完毕后服务端进入LAST_ACK状态。客户端接收到后进入TIME_AWAIT状态,第四步客户端向服务端发送确认包,服务端收到后CLOSED。客户端等待2MSL时间仍没有收到回复证明server关闭了,此时客户端也关闭CLOSED
- 下面是我抓的包
[root@test logs]# tcpdump -n -t -S -i ens192 port 8080
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens192, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [S], seq 734879327, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1932192608 ecr 0,sackOK,eol], length 0
IP 10.44.202.9.webcache > 10.95.110.166.57445: Flags [S.], seq 3867084604, ack 734879328, win 28960, options [mss 1460,sackOK,TS val 1465971641 ecr 1932192608,nop,wscale 7], length 0
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [.], ack 3867084605, win 2058, options [nop,nop,TS val 1932192609 ecr 1465971641], length 0
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [P.], seq 734879328:734880026, ack 3867084605, win 2058, options [nop,nop,TS val 1932192609 ecr 1465971641], length 698: HTTP: GET /updatelogs?edate=2020-11-16&limit=16&sdate=2020-11-07&start=0&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MDU1MTA1MTYsInFpZCI6IjVmODVmMWFhNjU2ODllMTIwMDhiNDU2NyIsImV4cCI6MTYwNTUxNDExNiwiandpIjoiNlpJVkF2V3hrRU1vMGhPUiJ9.vOi-megYtsRdcue0L9bbQFMgJfiieVJGNhoKiNT481U HTTP/1.1
IP 10.44.202.9.webcache > 10.95.110.166.57445: Flags [.], ack 734880026, win 238, options [nop,nop,TS val 1465971642 ecr 1932192609], length 0
IP 10.44.202.9.webcache > 10.95.110.166.57445: Flags [P.], seq 3867084605:3867086643, ack 734880026, win 238, options [nop,nop,TS val 1465971660 ecr 1932192609], length 2038: HTTP: HTTP/1.1 200 OK
IP 10.44.202.9.webcache > 10.95.110.166.57445: Flags [P.], seq 3867086643:3867086648, ack 734880026, win 238, options [nop,nop,TS val 1465971660 ecr 1932192609], length 5: HTTP
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [.], ack 3867086643, win 2027, options [nop,nop,TS val 1932192627 ecr 1465971660], length 0
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [.], ack 3867086648, win 2026, options [nop,nop,TS val 1932192627 ecr 1465971660], length 0
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [P.], seq 734880026:734880724, ack 3867086648, win 2048, options [nop,nop,TS val 1932193085 ecr 1465971660], length 698: HTTP: GET /updatelogs?edate=2020-11-16&limit=16&sdate=2020-11-07&start=0&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MDU1MTA1MTYsInFpZCI6IjVmODVmMWFhNjU2ODllMTIwMDhiNDU2NyIsImV4cCI6MTYwNTUxNDExNiwiandpIjoiNlpJVkF2V3hrRU1vMGhPUiJ9.vOi-megYtsRdcue0L9bbQFMgJfiieVJGNhoKiNT481U HTTP/1.1
IP 10.44.202.9.webcache > 10.95.110.166.57445: Flags [P.], seq 3867086648:3867088686, ack 734880724, win 249, options [nop,nop,TS val 1465972132 ecr 1932193085], length 2038: HTTP: HTTP/1.1 200 OK
IP 10.44.202.9.webcache > 10.95.110.166.57445: Flags [P.], seq 3867088686:3867088691, ack 734880724, win 249, options [nop,nop,TS val 1465972132 ecr 1932193085], length 5: HTTP
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [.], ack 3867088686, win 2038, options [nop,nop,TS val 1932193096 ecr 1465972132], length 0
IP 10.95.110.166.57445 > 10.44.202.9.webcache: Flags [.], ack 3867088691, win 2038, options [nop,nop,TS val 1932193096 ecr 1465972132], length 0
^C
14 packets captured
14 packets received by filter
0 packets dropped by kernel
- 回到原问题,如果一个端口向另一个端口发送TCP报文,怎么区分出多次发送的请求并给予对应的回复呢?
- 根据上面的抓包可以分析出来,最开始Flags是S S. . 是建立连接的过程,P 表示交给上层处理。后面就是请求和响应的处理了,我对一个接口发了两次请求。可见是通过seq和ack结合length保证收发包的顺序的。客户端和服务端收发通过seq和ack进行通信。如果出现丢包的请求,根据seq和ack可以判断出是那部分的包丢了,进行重发。
15. 冒泡排序,选择排序实现逻辑
- 这个就不说了。。
public class Bubble {
public int[] bubbleSort(int[] a,int n){
for (int i=0;i<n;i++){
boolean flag = false;
for (int j=0;j<n-1;j++){
if (a[j]>a[j+1]){
int tmp = a[j+1];
a[j+1] = a[j];
a[j] = tmp;
flag = true;
}
}
//已经排好了,就不需要再循环了
if (!flag) break;
}
return a;
}
public static void main(String[] args) {
Bubble b = new Bubble();
int[] ints = b.bubbleSort(new int[]{3, 1, 2, 4, 5, 1}, 6);
for (int i = 0; i < ints.length; i++) {
System.out.println(ints[i]);
}
}
}
public class Choose {
public int[] chooseSort(int[] a,int n){
for (int i = 0; i < n; i++) {
int tmp = i;
for (int j = i; j < n-1; j++) {
if (a[tmp]>a[j+1]){
tmp = j+1;
}
}
int s = a[i];
a[i] = a[tmp];
a[tmp] = s;
}
return a;
}
public static void main(String[] args) {
Choose a = new Choose();
int[] ints = a.chooseSort(new int[]{4, 5, 1, 3, 2, 6, 5}, 7);
for (int i = 0; i < ints.length; i++) {
System.out.println(ints[i]);
}
}
}
16、数据库索引设计优化
- 主键索引,唯一索引,联合索引
- 范围查询可以使用索引
- group 的字段可以用索引
- and的联合索引生效 or联合索引不生效 联合索引有顺序的。不能 abc的联合索引,只出现ac 可以出现ab 等等这个问题就简单回答了下。没多问
17、SQL:找student表 course表,求每个学生得分平均分及格的学号和平均分
select student.code, avg(course.score) as res from student left join course on student.code = course.code group by course.code having res > 60
18、postgresql和MySQL有啥区别
- pg有一些自身的特性和语法比较好,比如with 字句,递归查询等等。这个简单说了带过下。
19. 为啥有的地方用消息队列有的地方用Redis的发布订阅模式
- Redis的发布订阅不会持久化,对于不需要存储数据的地方,只是通知的地方用到了发布订阅,且用的时候也有机制是多久自动刷,所以丢数据过一会也是好的、
- 消息队列是掉电重启之后还能继续消费没消费的任务。
20. 用到什么监控系统了吗
- 这个是个开放性的问题。然而并没有用过。查有一些监控的框架例如:Prometheus
21. 如果想动态查千万级数据怎么办
- 可以用一些大数据nosql组件例如:elasticsearch 、hbase (hdfs)
22. 树的最大高度,广度优先和深度优先遍历
- todo
23. 用栈怎么实现队列
- 用两个栈可以实现,但是流式的话会有一段时间被阻塞。
24. git使用
- git clone git pull git push git stash git blame git merge git rebase git reset git reflog
25. hashmap put的返回值是什么
- 如果之前有存在值就返回旧值,如果之前没有key对应的value就返回null
26. SQL:or走索引吗,in查找走索引吗
- 分情况,如果or连接的字段都创建索引了就走,否则不走索引,全表扫描。
- in也分情况,如果in的值少就走,多的话就不走全表扫描,取决于MySQL的执行优化器。
27. docker cmd 和 entrypoint 区别
- cmd给出的是一个容器的默认的可执行体。也就是容器启动以后,默认的执行的命令。
- enterpoint 容器启动以后的执行体ENTRY
- 用entrypoint的中括号形式作为docker 容器启动以后的默认执行命令,里面放的是不变的部分,可变部分比如命令参数可以使用cmd的形式提供默认版本,也就是run里面没有任何参数时使用的默认参数。entrypoint一般启动个脚本,然后docker run的时候指定参数,cmd就是默认的参数了。
FROM centos
CMD ["hello cmd"]
ENTRYPOINT ["echo"]
//
[root@test docker]# docker run --rm --name test test:01 "hello run"
hello run
[root@test docker]# docker run --rm --name test test:01
hello cmd
28. docker add和copy的区别
- add 可以解压,还可以通过URL下载,而copy就是将文件加到工作区。 为了清晰明了不犯错,建议使用copy因为add下载的时候命令不当容易出错,且add还会让镜像构建缓存失效。
29. dockerfile 编写有什么需要注意的
- 每个命令都会创建一层layer ,用 \ 结合run命令,减少镜像的层数
- 选最简洁的FROM镜像
- 将不用的安装包删除
- 把变化最少的部分放在Dockerfile的前面,这样可以充分利用镜像缓存。缓存失效的命令:WORKDIR、CMD、ENV、ADD
30、hashmap的红黑树和链表什么时候转换,为啥设置这么个值8/6
- 链超过8转成红黑树,链减到6转成链表。避免频发的插入和删除
31. hashmap的hash算法
return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
32. 重写hashcode和重写equals的区别和关系
- 同时重写hashcode和equals之后才是绝对的等、
- 会先计算hashcode,当发生哈希冲突(hashcode相同)时,再对比equals方法是否相等。
33. 红黑树插入的时间复杂度
- 红黑树能够以O(log n) 的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。
34. ElasticSearch 为什么能做到实时大数据搜索?
- 倒排索引数据结构比较快。https://blog.csdn.net/finalheart/article/details/105151513
- Elasticsearch和磁盘之间还有一层称为FileSystem Cache的系统缓存,正是由于这层cache的存在才使得es能够拥有更快搜索响应能力。
35. kafka partition与topic之间的关系。三个partition用几个多线程。再加多线程有效果吗,多个partition能保证顺序消费吗
- 一个topic有多个partition,一个partition对应一个线程,三个partition超过3个线程消费也没用,只能三个线程同时工作。
- 多个partition不能保证顺序消费,单个partition可以保证顺序。
36. redis都有什么数据结构,说了下sds和skiplist
- string、list、hash、set、zset
37. MySQL主键索引和唯一索引哪个快
- 主键索引快,主键索引是聚簇索引(默认),唯一索引查完也得回表才能取全部数据(假设不是覆盖索引查询,覆盖索引查询就不用回表)
38. MySQL主键索引和普通索引有啥区别,怎么进行查找
- 普通索引查完,只能得到id(假设以id为聚簇索引),要想得到除了id和当前索引字段外的其他值还需回表到聚簇索引、
39. spring和springboot的区别
- SpringBoot是 Spring本身的扩展,使开发,测试和部署更加方便。简化了jar包的导入,内置了Tomcat等容器
- 自动装配更是简化了xml文件的配置。不用配一堆xml文件,写一个application.yml然后利用主键就OK了。
40. 微服务了解吗,微服务怎么拆分
- 按业务拆分,按功能拆分。比如订单服务,用户服务。管理员服务、文件服务。分发服务。接入服务,公共服务。
44. 怎么控制topic的分区配置和消费配置
- 根据业务需求来,进行压测。从而估算多少个消费者合适。生产消息的返回是replication都写入还是leader写入就返回。等等都是根据业务需要来的。
- 且kafka性能高,吞吐量高在于多partition 多消费进程。因此如果对顺序要求严格,mq的技术选型可以选别的。kafka单partition有序,但是对于一些down机以及加partition等等情况也有可能出问题。如果单partition了,kafka的高性能也就浪费掉了。
45. 看你写了项目使用了openresty 介绍一下这个干嘛的,为啥要用
- 这个是lua的框架。OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块
- 相当于在NGINX加了个buff 我们终端侧就是用的这个openresty。对于文件下载分片的实现比较方便。
46.虚拟内存介绍一下
- 由于操作系统的进程与进程之间是共享 CPU 和内存资源的,因此需要一套完善的内存管理机制防止进程之间内存泄漏的问题。为了更加有效地管理内存并减少出错,现代操作系统提供了一种对主存的抽象概念,即是虚拟内存(Virtual Memory)。虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉。
- 而实际上,虚拟内存通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来。
47. 进程把内存用光了,再起新的进程怎么办?
- 虚拟内存在操作系统上解决多个进程对内存操作的冲突问题。通过虚拟内存机制,每个进程都以为自己占用了全部内存,进程访问内存时,操作系统都会把进程提供的虚拟内存地址转换为物理地址,再去对应的物理地址上获取数据。CPU 中有一种硬件,内存管理单元 MMU(Memory Management Unit)专门用来将翻译虚拟内存地址。
- 使用虚拟内存。以Linux为例,Linux 中可以使用 SWAP 分区,在分配物理内存,但可用内存不足时,将暂时不用的内存数据先放到磁盘上,让有需要的进程先使用,等进程再需要使用这些数据时,再将这些数据加载到内存中,通过这种”交换”技术,Linux 可以让进程使用更多的内存。
48. 线程的状态都有哪些?主线程中创建了一个线程去调用一个接口,此时这个被创建的线程是什么状态,什么时候被唤醒。怎么被唤醒的?
public static enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
private State() {
}
}
发送IO线程是进入等待被唤醒的状态,但是在jvm中Java的表示是RUNNABLE状态。我模拟了一个http服务
public static void main(String[] args) throws Exception {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
sendGet(); //请求别的接口,接口里面有sleep10秒才返回。
}
});
t.start();
Thread.sleep(1000);
System.out.println(t.getState()); //RUNNABLE
}
-
New(新建状态) 线程刚被创建 但是并未启动 还没调用start方法
-
Runnable(可运行状态)线程可以在java虚拟机中运行的状态 可能正在运行自己的代码 也可能没有
-
Blocked(锁阻塞状态) 当一个线程试图获取一个对象锁 而该对象锁被其他的线程持有 则该线程进入Blocked状态,当该线程持有锁时 该线程将变成Runnable状态
-
Waiting(无限等待状态) 一个线程在等待另一个线程执行一个(唤醒)动作时 该线程进入Waiting状态,进入这个状态后是不能自动唤醒的 必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
-
TimedWaiting(计时等待状态) 同waiting状态 有几个方法有超时参数 调用他们将进入Timed Waiting状态,这一状态将一直保持到超时期满或者接收到唤醒通知 带有超时参数的常用方法有Thread.sleep()和Object.wait()
-
Teminated(终止状态) 因为run方法正常退出而死亡 或者因为没有捕获的异常终止了run方法而死亡
-
线程如果进入 TIMED_WAITING、WAITING 是等待其他线程唤醒的。 例如 wait(long)是TIMED_WAITING、wait()是WAITING等待唤醒。
-
java中的RUNNABLE 包括操作系统层的等待CPU的ready状态 等待io的 io wait状态。在Java的枚举中就是 RUNNABLE状态
-
Java中的LockSupport#park(),线程就将会变为 WATTING 状态。如果需要唤醒线程就需要调用 LockSupport#unpark,然后线程状态重新变为 RUNNABLE。具体可以参考下AQS的队列唤醒。
回到问题,怎么被唤醒的呢? ---- https://juejin.im/post/6867151896818221063
- linux内核将线程当做一个进程进行cpu调度,内核维护了一个可运行的进程队列,所有处于 TASK_RUNNING状态的进程都会被放入队列中,本质是用双向链表将tast_struct连接起来,排队使用cpu时间片,时间片用完重新调度cpu。
调度就是在可运行进程列表中选择一个进程,再次cpu列表中选择一个可用的cpu,将进程的上下文恢复到这个cpu的寄存器中,然后执行进程上下文指定的下一条指令。 - 线程在加入等待队列的同时,向内核注册了回调函数,通知内核在等待这个socket上的数据,如果数据来了就唤醒我。在网卡接收到数据时,产生硬件中断,内核再通过调用回调函数唤醒进程。
- 唤醒的过程是将进程的task_struct从等待队列中移到运行队列中,并将task_struct的状态设置为task_running,这样进程就有机会重新获取时间片。
这个过程,内核还将数据从内核空间拷贝到用户空间的堆上。
49. 看你会golang,项目中一般用来做什么。说说跟Java的不同,golang的协程介绍一下。
- golang写一些小工具,消费kafka,往Redis写数据等等。Goroutine 是go比较好用的。
- go大佬的文章:https://draveness.me/golang/concurrency/golang-goroutine.html
50.按时间搜索快的数据库有哪个?
- hdfs hbase elasticsearch
51.日志数据量大(千万~亿),现在采用定时任务+汇总表,如果要想实时查询该怎么办?
- 可以介入大数据平台 hdfs hbase 用elasticsearch搜索。
52.流式处理的中间件了解哪些?
- storm flink(流式) spark(批处理)
53.rpc框架了解哪些?
- springcloud dubbo grpc
54.grpc 的底层序列化协议是什么? 为啥用protobuf做序列化协议,优势是什么,不足是什么
- protobuf ----https://zhuanlan.zhihu.com/p/141415216
55. 介绍一下分布式系统都有什么特征
- 分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统 —https://blog.csdn.net/qq_32297447/article/details/79081112
- 增大系统容量。我们的业务量越来越大,而要能应对越来越大的业务量,一台机器的性能已经无法满足了,我们需要多台机器才能应对大规模的应用场景。所以,我们需要垂直或是水平拆分业务系统,让其变成一个分布式的架构。
- 加强系统可用。我们的业务越来越关键,需要提高整个系统架构的可用性,这就意味着架构中不能存在单点故障。这样,整个系统不会因为一台机器出故障而导致整体不可用。所以,需要通过分布式架构来冗余系统以消除单点故障,从而提高系统的可用性。
- 因为模块化,所以系统模块重用度更高
- 因为软件服务模块被拆分,开发和发布速度可以并行而变得更快
- 系统扩展性更高
- 团队协作流程也会得到改善
56. 零拷贝介绍一下
零拷贝指CPU的拷贝次数和上下文切换时间的减少。
-
大佬文章总结的很全面:https://juejin.im/post/6844903949359644680
-
linux分为内核空间和用户空间,与网络设备直接交互的是内核空间,从网络设备读取数据到内核空间,在将数据搬运到用户空间进程。
-
零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现 CPU 的零参与,彻底消除 CPU 在这方面的负载。实现零拷贝用到的最主要技术是 DMA 数据传输技术和内存区域映射技术。
-
零拷贝机制可以减少数据在内核缓冲区和用户进程缓冲区之间反复的 I/O 拷贝操作。
-
零拷贝机制可以减少用户进程地址空间和内核地址空间之间因为上下文切换而带来的 CPU 开销。
-
操作系统中CPU执行的拷贝共两次,1.是内核进程和用户进程之间的拷贝一共两次。2.是硬件设备到内核空间的DMA拷贝两次。
-
零拷贝就是减少上面两个地方。其中DMA省去了CPU的参与,但是DMA的拷贝不能消除。而内核与用户进程之间的拷贝也用到了CPU这个是零拷贝需要消减的。
-
DMA 的全称叫直接内存存取(Direct Memory Access),是一种允许外围设备(硬件子系统)直接访问系统主内存的机制。
-
DMA控制器将数据从主存或硬盘拷贝到内核空间,DMA为了防止CPU与主存/磁盘进行读写而准备。
零拷贝的方式
- 内存映射。
- mmap 的目的是将内核中读缓冲区(read buffer)的地址与用户空间的缓冲区(user buffer)进行映射。从而减少一次CPU的数据拷贝。
Java中的MappedByteBuffer就是这个机制。
- mmap 的目的是将内核中读缓冲区(read buffer)的地址与用户空间的缓冲区(user buffer)进行映射。从而减少一次CPU的数据拷贝。
- sendfile系统调用
- 通过 sendfile 系统调用,数据可以直接在内核空间内部进行 I/O 传输,从而省去了数据在用户空间和内核空间之间的来回拷贝。
- sendfile 存在的问题是用户程序不能对数据进行修改,而只是单纯地完成了一次数据传输过程。
- kafka有的部分使用了这种。
- splice 系统调用建立管道
- splice 系统调用可以在内核空间的读缓冲区(read buffer)和网络缓冲区(socket buffer)之间建立管道(pipeline),从而避免了两者之间的 CPU 拷贝操作。
- RocketMQ 选择了 mmap + write 这种零拷贝方式,适用于业务级消息这种小块文件的数据持久化和传输;而 Kafka 采用的是 sendfile 这种零拷贝方式,适用于系统日志消息这种高吞吐量的大块文件的数据持久化和传输。但是值得注意的一点是,Kafka 的索引文件使用的是 mmap + write 方式,数据文件使用的是 sendfile 方式。
57. 介绍一下微服务框架springcloud
就简单说了下springcloud有啥组件,都是干啥的。
- euraka:服务注册与发现
- feign:服务调用,http调用、
- ribbon:负载均衡
- zuul:网关
- Hystrix:Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延
- 配置中心可以用 Apollo等等、
58. kafka了解什么特征
就简单说了一些。
- 逻辑上有topic的概念,物理上有partition的概念,一个topic有多个partition。一个partition的存储对应一个日志目录。{topicName}-{partitionid}/,在目录下面会对应多个日志分段(LogSegment)。LogSegment文件由两部分组成,分别为“.index”文件和“.log”文件、
索引文件使用稀疏索引的方式,避免对日志每条数据建索引,节省存储空间。隔一定位置建立索引,稀疏索引。 - 使用page cache顺序读文件,操作系统可以预读数据到 page cache。同时,使用mmap直接将日志文件映射到虚拟地址空间。
- kafka使用零拷贝,避免消息在内核态和用户态间的来回拷贝
59. Java的内存模型是什么样的
- 保证多线程之间操作共享变量的正确性。
- 包括程序计数器、本地方法栈、线程栈、jvm堆、方法区、常量池。
- 可以参考了解-----https://zhuanlan.zhihu.com/p/29881777
60.cdn实现原理是什么?
-
CDN是将源站内容分发至全国所有的节点,从而缩短用户查看对象的延迟,提高用户访问网站的响应速度与网站的可用性的技术。它能够有效解决网络带宽小、用户访问量大、网点分布不均等问题。
-
CDN可以粗略的理解为,是一个源端的缓存,这个缓存在物理位置离你更进了,让传输更快了。
61. Java中的垃圾回收算法说说,垃圾回收的分区都有什么,什么时候会从一个区到另一个区。
-
建议刷刷:《深入理解Java虚拟机》 找到一篇文章总结的还挺多的 本题答案引自----https://juejin.cn/post/6844903666432868365
-
jvm的堆分为新生代(eden,s1,s2)、老年代、以及永久代。
-
对象优先在eden区分配、
-
大对象直接进入老年代(为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。)、
-
长期存活的对象将进入老年代。
-
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1.
-
对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。
-
对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
-
目前主流的垃圾收集器都会采用分代回收算法、根据各个年代的特点选择合适的垃圾收集算法。
-
新生代GC(MinorGC): 新生代的垃圾回收,非常频繁,回收速度一般比较快。
-
老年代GC(MajorGC/FullGC): 老年代GC,速度比较慢(MinorGC的十分之一甚至更慢),回收没有那么频繁。
-
java根据可达性分析算法,区分哪个是可回收的对象。GC Roots是根,跟根不沾边的就可回收,那么什么可以作为GC Roots呢?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中的类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(即一般说的Native方法)中引用的对象
对于垃圾回收算法有:
-
标记-清除法。
- 算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
- 但是他会造成大量的内存碎片。
-
复制算法。
- 为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。(这么大范围的在新生代比较好用)
-
标记-整理算法。
- 根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一段移动,然后直接清理掉端边界以外的内存。
- 根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一段移动,然后直接清理掉端边界以外的内存。
-
分代收集算法。
- 比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集
62. CMS收集器用什么垃圾回收算法了,G1用什么垃圾回收算法了,为什么。
-
当时这个具体什么收集器用什么回收算法,我记不清了,感觉这个一定要记得清楚扎实。
-
CMS (Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,实现了让垃圾收集线程与用户线程同时工作。
-
CMS收集器是一种 “标记-清除”算法实现的
-
它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
- 对CPU资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
- G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
- G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率。
63.数据库的sharding分库分表有哪些中间件实现了解吗?(balabala,一个订单表分成1024个库)
-
分库是因为并发大,一般超过qps1000的就可以考虑了。
-
分表是因为数据量大,保持单表百万级别数据就行了。
-
目前比较好的分库分表中间件有mycat/Sharding-jdbc
-
拆分的话可以根据日期或什么 range一下。不过这种不能抵抗活动日。还可以hash路由,这种要是扩容就麻烦了。(id/1024)
-
mycat一共三个配置文件server.xml(服务自身) scheme.xml(数据库相关) rule.xml(分库策略)
-
对应id来说,mycat可以多种方式生成唯一id这个放心。server.xml设置sequnceHandlerType
-
这有个demo:https://juejin.cn/post/6844903978287759373
64.分布式事务了解吗?怎么实现?
- TODO
65.eureka 底层使用了什么机制?
-
每个服务持有一个eureka客户端,在服务启动时会向eureka服务端注册,并保持一定时间的心跳来维持关系。
-
对于down掉的服务,正常down会向eureka服务端发送下线通知,然后server广播到各个服务。
-
对于非正常down掉的服务,eureka server默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务从注册中心剔除。
-
eureka有自我保护机制。(时间和比例都是可以配置的。)
- 自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,
此时会出现以下几种情况:- 1)Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
- 2)Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
- 3)当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中
- 自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,
-
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。 CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
-
Eureka Server 收到客户端的注册、下线、心跳请求时,通过 PeerEurekaNode 向其余的服务器进行消息广播,如果广播失败则重试,直到任务过期后取消任务。通过广播来达到最终一致性。
66.对跨平台的理解?
- 跨平台由于底层CPU使用的芯片和平台不同从而使用的指令集不同,C语言为例,使用编译器将C语言程序翻译成汇编程序,汇编程序再使用汇编器编译成0和1,当CPU解释二进制数据时(以c语言为例)根据不同指令集的解析是不同的结果,从而达不到跨平台的效果。(指令集比如 + - * / 等等表示成不同的二进制)
- cpu的指令集不同, 不同平台编译出来的结果格式都不同,那么我们可以在各个平台上运行虚拟机,然后我们制定某种编译结果的输出格式,我们的输出了某种格式的结果,直接在虚拟机上运行,这是Java跨平台的解决方式。
- java使用 .class文件作为虚拟机可运行的字节码文件 javac A.java ===> A.class A.class则是虚拟机需要解释的。里面存储的二进制数据。 javap -c A.class 可以反编译出Java的更高一层面的代码。
- 顺便提一下,Java虚拟机关注的是 .class文件,至于是不是从 .java文件编译过来的,虚拟机并不关心
67. MySQL更新一行记录会触发表级锁还是行级锁
- 锁的是索引。更新索引字段触发行级锁和gap锁,多个事务对唯一key进行插入只有一个插入成功,其他事务会报冲突,行锁是锁一行或者多行,如果更新的是非索引字段,那么在主键索引上扫描到的行都会被锁住,因为全表扫描。 因此别的update都会被阻塞。
- 通俗易懂:https://zhuanlan.zhihu.com/p/52678870
68、关于聚簇索引说一说
聚簇:术语,表示数据行和相邻的键值紧凑的存储在一起。
那么,分开来看,
聚簇索引 == 索引 + 数据存储方式
索引
以 InnoDB 为例,其为 B-Tree 索引
数据存储方式
即,聚簇索引 不光有 索引 ,还有存放在叶子页的数据行
另外
同一个表中,聚簇索引 只能有一个,因为 无法同时把数据行放在两个不同的地方
PRIMARY KEY 是一种典型的 聚簇索引 ,InnoDB 的 聚簇索引 具体选择策略如下:
如果你的表中定义了 PRIMARY KEY,那么,这就是此表的 聚簇索引;
如果你的表中没有定义 PRIMARY KEY,但是定义了 非空唯一索引,那么,InnoDB 会选择第一个 非空唯一索引 作为此表的 聚簇索引;
如果不满足上述两个条件,即表中没有 PRIMARY KEY 也没有合适的 UNIQUE,则 InnoDB 会自动生成一个隐藏的聚簇索引,此索引包含一个单点递增的 ID 列。MyisAM 引擎没有聚簇索引。
对于如下的SQL 语句, 直接根据主键查询 获取 所有字段数据,此时主键 是 聚簇索引,因为主键对应的索引叶子节点存储了id=1 的所有字段的值 :
select * from lhrdb.student where id = 1;
对于如下的SQL 语句, 根据编号 no 查询编号和名称,编号本身是一个唯一索引,但查询的列包含了学生编号和学生名称,当命中编号索引时,该索引的节点的数据存储的是主键ID ,需要根据主键 ID 重新查询一次
select no,name from student where no = 'test';
对于如下的SQL 语句, 根据编号查询编号,这种查询命中编号索引时,直接返回编号,因为所需要的数据就是该索引,不需要回表查询
select no from student where no = 'test';
69、MySQL更新记录是锁的行还是锁的索引
- 锁的索引
70、docker 底层实现原理是什么
https://juejin.im/entry/6844903648330252295#comment
- 通过Linux的命名空间 namespace进行隔离。 命名空间(namespaces)是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。
- 在默认情况下,每一个容器在创建时都会创建一对虚拟网卡,两个虚拟网卡组成了数据的通道,其中一个会放在创建的容器中,另一个会加入到名为 docker0 网桥中。docker0 会为每一个容器分配一个新的 IP 地址并将 docker0 的 IP 地址设置为默认的网关。网桥 docker0 通过 iptables 中的配置与宿主机器上的网卡相连,所有符合条件的请求都会通过 iptables 转发到 docker0 并由网桥分发给对应的容器。
- Docker 通过 Linux 的命名空间实现了网络的隔离,又通过 iptables 进行数据包转发
- docker中的文件系统也是通过命名空间进行隔离的
- 使用 CGroup 来隔离容器可使用的系统资源。CGroups 全称control group,用来限定一个进程的资源使用,由Linux 内核支持,可以限制和隔离Linux进程组 (process groups) 所使用的物理资源 ,比如cpu,内存,磁盘和网络IO,是Linux container技术的物理基础。
- Docker 镜像其实本质就是一个压缩包。使用 UnionFS联合文件系统来将多个物理位置不同的文件联合在一起。以系统内核为底层核心。
- 当镜像被 docker run 命令创建时就会在镜像的最上层添加一个可写的层,也就是容器层,所有对于运行时容器的修改其实都是对这个容器读写层的修改。
- 容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。
71、docker-compose了解吗,简单说说干啥的
- 编排 docker项目的,如果项目使用多个技术组件,将每个技术组件放到一个镜像里,然后将映射、镜像、命令、参数、网络、等等都配置在docker-compose文件中,这样就从变成白盒的了。
72、判断是同一个类的方法?
全限定类名相同且类加载器相同。扩展一下就是,不同类加载器的类能用synchronized锁住class吗,答案是不行的,synchronized锁的类对象,一定要是相同的类才有效。
73、hashmap中的链表是头部插入还是尾部插入? 为什么?
jdk1.8之前是头插,1.8变成了尾部插入,防止链表成环
74、redis 字符串有用了sds,默认是什么数据结构,直接就是sds吗
我们可能以为redis在内部存储string都是用sds的数据结构实现的,其实在整个redis的数据存储过程中为了提高性能,内部做了很多优化。整体选择顺序应该是:
1.整数,存储字符串长度小于21且能够转化为整数的字符串。
2、EmbeddedString,存储字符串长度小于39的字符串(REDIS_ENCODING_EMBSTR_SIZE_LIMIT)。
3、SDS,剩余情况使用sds进行存储。
embstr和sds的区别在于内存的申请和回收
- embstr的创建只需分配一次内存,而raw为两次(一次为sds分配对象,另一次为redisObject分配对象,embstr省去了第一次)。相对地,释放内存的次数也由两次变为一次。
- embstr的redisObject和sds放在一起,更好地利用缓存带来的优势
- 缺点:redis并未提供任何修改embstr的方式,即embstr是只读的形式。对embstr的修改实际上是先转换为raw再进行修改。
75、mvcc加的两列都是什么
行创建的事务号和行删除的事务编号
76、MySQL B+树一次io多少字节
操作系统一般以 4kb 为一个数据页读取数据,而 MySQL 一般为 16kb 作为一个数据块
77、epoll与select区别
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
78、锁升级
最开始是无锁状态,当有一个线程访问同步块的时候升级成偏向锁。
当有锁竞争时由偏向锁升级成轻量级锁。
当自旋十次失败(可配置)锁膨胀时升级成重量级锁。
79、Java怎么实现单例模式,为什么用volatile
public class Singleton {
private volatile static Singleton a;
private Singleton() {
}
public Singleton getInstance() {
if (null == a) {
synchronized (Singleton.class) {
if (null == a) {
a = new Singleton();
}
}
}
return a;
}
}
因为只有最开始会竞争后续创建完不会竞争,所以最开始有个if的判断,然后加锁,顺序获取锁,里面还有个判断是为了防止两个线程在竞争锁的时候都是判断的a == null 所以加了个if
对于volatile 关键字是因为如果不加这个关键字, a = new Singleton(); 这个是分为三步
1、配内存空间
2、初始化对象
3、将对象指向刚分配的内存空间
如果编译器优化将其中的顺序变了2和3,可能会导致a没有完成初始化,就指向分配的内存空间,从而导致访问一个未初始化的对象。
80、线程池参数都有哪些?
ThreadpoolExecutor就是Java里面线程池的实现。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
if (corePoolSize >= 0 && maximumPoolSize > 0 && maximumPoolSize >= corePoolSize && keepAliveTime >= 0L) {
if (workQueue != null && threadFactory != null && handler != null) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
构造方法中的字段含义如下:
-
corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行以下判断:
- 如果运行的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
- 如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务;
- 如果设置的corePoolSize 和 maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;
- 如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务;
所以,任务提交时,判断的顺序为 corePoolSize –> workQueue –> maximumPoolSize。
-
maximumPoolSize:最大线程数量;
-
workQueue:等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
-
workQueue:保存等待执行的任务的阻塞队列,当提交一个新的任务到线程池以后, 线程池会根据当前线程池中正在运行着的线程的数量来决定对该任务的处理方式,主要有以下几种处理方式:
- 直接切换:这种方式常用的队列是SynchronousQueue,但现在还没有研究过该队列,这里暂时还没法介绍;
- 使用无界队列:一般使用基于链表的阻塞队列LinkedBlockingQueue。如果使用这种方式,那么线程池中能够创建的最大线程数就是corePoolSize,而maximumPoolSize就不会起作用了(后面也会说到)。当线程池中所有的核心线程都是RUNNING状态时,这时一个新的任务提交就会放入等待队列中。
- 使用有界队列:一般使用ArrayBlockingQueue。使用该方式可以将线程池的最大线程数量限制为maximumPoolSize,这样能够降低资源的消耗,但同时这种方式也使得线程池对线程的调度变得更困难,因为线程池和队列的容量都是有限的值,所以要想使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度相对简单,并且还要尽可能的降低线程池对资源的消耗,就需要合理的设置这两个数量。
- 如果要想降低系统资源的消耗(包括CPU的使用率,操作系统资源的消耗,上下文环境切换的开销等), 可以设置较大的队列容量和较小的线程池容量, 但这样也会降低线程处理任务的吞吐量。
- 如果提交的任务经常发生阻塞,那么可以考虑通过调用 setMaximumPoolSize() 方法来重新设定线程池的容量。
- 如果队列的容量设置的较小,通常需要将线程池的容量设置大一点,这样CPU的使用率会相对的高一些。但如果线程池的容量设置的过大,则在提交的任务数量太多的情况下,并发量会增加,那么线程之间的调度就是一个要考虑的问题,因为这样反而有可能降低处理任务的吞吐量。
-
keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
-
threadFactory:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
-
handler:它是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:
- AbortPolicy:直接抛出异常,这是默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
ExecutorService es = Executors.newFixedThreadPool(10);
ExecutorService默认是LinkedBlockingQueue 无界队列,使用拒绝策略AbortPolicy
关于那个 ThreadFactory就是一个接口,里面有一个newThread方法,用来创建线程的。可以给线程一些自定义操作。
更详细可以看下下面这篇文章,本段摘抄自下面引用、
http://www.ideabuffer.cn/2017/04/04/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%EF%BC%9AThreadPoolExecutor
算法题
接下来是经历的算法题。能在LeetCode找到的我就放LeetCode链接了
- https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
- https://leetcode-cn.com/problems/permutations/
- 给一个字符串数组,将其按照排列分组 [“ate”,“eta”,“eat”,“asd”,“qwe”,“das”] 分组成三组 前三个一组,倒数第二个一组剩下两个一组。
- 多个数组按列合并
//[1,2,3]
//[4,5]
//[7,8,9] 合并成
//[1,4,7,2,5,8,3,9] - 树的广度优先遍历
- 棋子有0、1 给一个二维数组表示期盘,二维数组的每个位置是0或者1 求所有上下左右算连接,连接的最大面积,如下最大面积是5
0 | 1 | 0 | 1
1 | 1 | 1 | 0
0 | 1 | 0 | 0