一、面试概况
今天是之前面美团java研发凉之后的,美团后端捞起面试,我之前一直是觉得自我感觉良好的,【感觉技术面以及四门专业课=====从来不虚】,结果今天感觉非常的知足,不是说的笔试和面试怎么杨吧,就是我在今天这场面试中,积攒到了很多经验,找到了自身的不足,应该思考的地方,面试的要点,让我很是受益,所以抓紧做一个笔记。
二、面试-笔试
两个算法:
一个是二叉树的先序遍历【做出来了】
合并两个有序数组【也做出来了】
三、技术面
3.1 Redis【一直在干redis连环炮】
3.2 说一下redis为什么快?
我说基于内存,基于单线程,然后网络请求有IO多路复用所以很快。
1、完全基于内存
2、数据结构简单
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路 I/O 复用模型,非阻塞 IO;
3.3 redis如何保证数据的一致性?
我说了个集群 分区数据同步一致性,因为真的想不到了
3.4 redis的锁机制
1.Redis中可以使用SETNX命令实现分布式锁
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
2.RedLock
安全特性:互斥访问,即永远只有一个 client 能拿到锁
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
容错性:只要大部分 Redis 节点存活就可以正常提供服务
3.redission
Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
3.5 redis的事务【redis 事务不保证原子性,不支持回滚】
Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序 串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中
1.单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
2.没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行
3.不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
1.事务开始 MULTI
2.命令入队
3.事务执行 EXEC
3.6 redis的五大数据类型
String,List,Set,Zset,Hash
3.7 redis的5大数据类型的使用场景
1.String 进行自增自减运算,从而实现计数器功能,很适合存储频繁读写的计数量
2.list: 可以通过lpush/rpush rpop/lpop 读取和写入消息,实现阻塞队列
3.Set 可以实现交集、并集等操作,从而实现共同好友,推荐等等功能
4.ZSet 可以实现有序性操作,从而实现排行榜等功能。
3.8 redis的list 与 set的区别?
3.9 redis的String底层怎么实现的?list、set,hash的底层怎么实现?
1.String的数据类型是由SDS【简单动态字符串】实现的,C语言的实现方式有专门用于保存字符串长度的变量,所以可以在O(1)时间内获得
2.链表是list的实现方式之一。当list包含了数量较多的元素,或者列表中包含的元素都是比较长的字符串时,Redis会使用链表作为实现List的底层实现。此链表是双向链表。
链表结构的特点是可以快速的在表头和表尾插入和删除元素,但查找复杂度高,是列表的底层实现之一
3.Hash:数组 + 链表(采用头插法解决冲突,不会像java语言的map一样转化为红黑树),redis会把kay、value封装成一个dictEntry的结构体(dictEntry的key为string类型,value是一个指针,指向一个redisObject对象(不像java语言一样把hashmap的key、value的具体值封装在一起))
3.list底层实现:底层是链表,有两个list,一个是ziplist,字面意是压缩列表,另一个是quicklist,字面意是快速列表,在redis中直接使用的是quicklist.
4.set底层实现: Set是一个特殊的value为空的Hash
5.zset底层实现:Zset底层是一种跳表SkipList数据结构(跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找,实质就是一种可以进行二分查找的
其实跳表主要是来替代平衡二叉树的,比起平衡树来说,跳表的实现要简单直观的多。
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速查找访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操作都可以在O(logn)期望时间下完成
3.10 在使用场景中怎么区别 list 与 set ?
2.list: 可以通过lpush/rpush rpop/lpop 读取和写入消息,实现阻塞队列
3.Set 可以实现交集、并集等操作,从而实现共同好友,推荐等等功能
4.ZSet 可以实现有序性操作,从而实现排行榜等功能。
3.11 Redis的内存用完了?
1.Redis支持运行时通过命令动态修改内存大小
2.Redis的内存淘汰
3.12 redis的那些命令是原子性的?
3.13 RDB && AOF
RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候redis.conf
AOF:将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在append操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当server需要数据恢复时,可以直接replay此日志文件,即可还原所有的操作过程。AOF相对可靠,它和mysql中bin.log、apache.log、zookeeper中txn-log简直异曲同工。AOF文件内容是字符串,非常容易阅读和解析。
AOF默认关闭,开启方法,修改配置文件reds.conf: appendonly yes
优点:可以保持更高的数据完整性,如果设置追加file的时间是1s,如果redis发生故障,最多会丢失1s的数据;且如果日志写入不完整支持redis-check-aof来进行日志修复;。AOF的特性决定了它相对比较安全,如果你期望数据更少的丢失,那么可以采用AOF模式
缺点:AOF文件比RDB文件大,且恢复速度慢。
四、Mysql
4.1 说一下Innodb引擎
简单
4.2 说一下mysql的锁机制
悲观锁
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)
如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
优点和缺点
优点:安全
缺点:悲观锁实际上是采取了“先取锁在访问”的策略,为数据的处理安全提供了保证,但是在效率方面,由于额外的加锁机制产生了额外的开销,并且增加了死锁的机会。并且降低了并发性;当一个事物所以一行数据的时候,其他事物必须等待该事务提交之后,才能操作这行数据。
乐观锁
相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本
乐观锁的优点和缺点
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
4.3 说一下如何排查慢SQL
1.mysql默认不开启慢SQL日志,检查是否开启了 慢查询日志 : show variables like '%slow_query_log%' ; 2.开启慢SQL /etc/my.cnf 中追加配置:vi /etc/my.cnf [mysqld] slow_query_log=1 slow_query_log_file=/var/lib/mysql/localhost-slow.log 3.查询超过阀值的SQL show global status like '%slow_queries%' ; 4.慢查询的sql被记录在了日志中,因此可以通过日志 查看具体的慢SQL。 cat /var/lib/mysql/localhost-slow.log 5.通过explain分析sql语句,例: explain select name from tablex where a = '1';
4.4 说一下mysql悲观锁与乐观锁的使用场景?
悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。
五、JVM
5.1 说一下在什么情况下,对象会进入老年代?
1.大对象
2.长期存活的对象 s0 s1 15岁以后【只答出来这个】
5.2 说一下为什么 s0区 = s1区
没答出来
5.3 如果java程序CPU过高,如何排查????
进程====》定位线程
一般java应用cpu过高基本上是因为
1.程序计算比较密集
2.程序死循环
3.程序逻请求堵塞
4.IO读写太高
1、先通过top命令找到消耗cpu很高的进程id假设是123 2、执行top -p 123单独监控该进程 3、在第2步的监控界面输入H,获取当前进程下的所有线程信息 4、找到消耗cpu特别高的线程编号,假设是123 5、执行jstack 123456对当前的进程做 jmap -dump,输出所有的线程信息 6 将第4步得到的线程编号11354转成16进制是0x7b 7 根据第6步得到的0x7b在第5步的线程信息里面去找对应线程内容 8 解读线程信息,定位具体代码位置
定位到cpu过高是IO读写太高 ,接下来就是找开发人员确认这段代码是否可以优化
5.4 Java OOM问题如何排查????
1.查看java 进程/线程对系统的占用情况,如 top -pid 2.jstack pid > file.log 通过jstack 把该进程的所有线程堆栈打印到file.log中 3.jmap -heap 进程/线程号 显示堆内存信息 4.jmap -dump 生成堆内存转储快照 5.加载对比 堆文件,发现 对象增多,初步定位到问题
六、Java
在我非常擅长的java这一块,只问了一个volitize关键字
我说,是一个关键字,只能作用在变量上,保证可见性,防止指令重排,不保证原子性
面试官反问:除了可见性,还能保证什么????
七、反问
我说我自己今天表现不是很好,问问面试官对我有什么地方改进?
面试官:说对我期望特别高,看到我简历说我会的技术栈很全,所以问我的时候并没有刁难我,不要为了面试而面试,而是选择有思考的让我去考虑问题。
最后感谢了面试官,因为真的收获很大,也知道了在学习时候如何如避免形成刻板思路,多思考!!!