记美团一面,凉凉~
\01. 自我介绍
2. Oauth2原理
OAuth 的核心就是向第三方应用颁发令牌
四种认证流程#
授权码(authorization-code)
隐藏式(implicit)
密码式(password):
客户端凭证(client credentials)
3. Redis数据结构
常见五种数据结构:字符串、哈希、列表、集合、有序集合
4. Redis集群模式及区别
主从复制是为了数据备份,哨兵是为了高可用,Redis主服务器挂了哨兵可以切换,集群则是因为单实例能力有限,搞多个分散压力,简短总结如下:
主从模式:备份数据、负载均衡,一个Master可以有多个Slaves。
sentinel发现master挂了后,就会从slave中重新选举一个master。
cluster是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器。
sentinel着眼于高可用,Cluster提高并发量。
- 主从模式:读写分离,备份,一个Master可以有多个Slaves。
- 哨兵sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。
- 集群:为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。
5.消息队列如何保证数据不丢失
rabbitmq: 发给生产者不丢失 消费者发送不丢失
kafka: ACK 机制 、 设置分区、关闭 unclean leader 选举等等
调整存储时间
rocketmq 事务消息 分布式事务
6. 集合
常用的集合有ArrayList、LinkedList、HashSet、HashMap、LinkedHashMap
7. ArrayList与LinkedList区别
1.是否保证线程安全:ArrayList和LinkedList 都是不同步的,也就是不保证线程安全;
2.底层数据结构:ArrayList底层使用的是Object数组;LinkedList底层使用的数双向链表 数据结构;
3.插入和删除是否受元素位置的影响:ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。LinkedList采用链表存储,所以,如果在头尾插入或者删除元素不受元素位置的影响。在指定位置插入和删除元素,时间复杂度为O(n),因为需要先移动到指定位置再插入。
4.是否支持随机访问:LinkedList不支持高效的随机元素访问,而ArrayList支持。
5.内存空间占用:ArrayList的空间浪费主要体现在list列表的结尾会预留一定的空间容量,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为需要存放直接后继和直接前驱以及数据)。
8. ArrayList扩容机制
ArrayList默认初始化是会申请10个长度的空间,如果超过这个长度则需要进行扩容。这主要包括以下步骤:
1.判断长度充足;ensureCapacityInternal(size + 1);
2.当判断长度不足是,则通过扩大函数,进行扩容;java.util.ArrayList#grow(minCapacity);
3.扩容的长度计算;int newCapacity = oldCapacity + (oldCapacity >> 1);旧容量+旧容量右移1位,这相当于扩容了原来的(int) 3/2。
4.当扩容完以后,就需要进行把数组中的数据拷贝到新数组中,这个过程会用到 Arrays.copyOf(elementData, newCapacity);但是它底层使用的是System.arraycopy
9. 如何使用线程安全的集合
1.使用线程安全的集合类:
使用ConcurrentHashMap替换HashMap、
使用CopyOnWriteArrayList替换ArrayList、
使用CopyOnWriteArraySet替换HashSet
2.使用静态方法创建集合
java.util.Collections#synchronizedMap、java.util.Collections#synchronizedList
java.util.Collections#synchronizedSet
10. 分布式锁
概念:分布式项目开发中用到的锁,可以用来控制分布式系统之间同步访问共享资源,分布式锁需要满足的特性有这么几点:
1.互斥性:在任何时刻,对于同一条数据,只有一台应用可以获取到分布式锁;
2.高可用性:在分布式场景下,一部分服务器宕机不影响正常使用,这种情况就需要提供分布式锁的服务以集群的方式部署;
3.防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁;
4.独占性:加锁解锁必须由同一台服务器进行,也就是锁的持有者才可以释放锁,不能出现你加的锁,别人给你解锁了;
11. 分布式锁锁超时
描述:分布式锁设置了过期时间,锁的方法执行执行很长会出现什么问题
客户端1得到了锁,因为锁的方法执行时间很长,导致业务程序还没执行完锁就过期了,这时候客户端2也能正常拿到锁,可能会导致线程安全的问题。
解决方案:
1.使用Redisson,在分布式锁快要超时时续期
12. JVM分为几大区域
总共分为五大区域:
程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区
程序计数器:是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。
Java虚拟机栈:线程私有,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
本地方法栈:本地方法栈与虚拟机栈发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地方法服务。
Java堆:是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。
方法区:与堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类型信息,常量、静态变量、即时编译器编译后的代码缓存等数据。
13. JVM垃圾回收器区别
Serial 收集器:单线程垃圾回收器,收集时必须暂停其他所有的工作线程("Stop The World"),
新生代采用标记-复制算法,老年代采用标记-整理算法。
ParNew 收集器: ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Parallel Scavenge 收集器:Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)
新生代采用标记-复制算法,老年代采用标记-整理算法。
Serial Old 收集器: Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
Parallel Old 收集器: Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS 收集器: CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
G1 收集器: G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。
ZGC 收集器: 与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。在 ZGC 中出现 Stop The World 的情况会更少!
jdk1.8 默认垃圾回收器
jdk1.8 默认垃圾回收器是 -XX:+UseParallelGC
java -XX:+PrintCommandLineFlags -version
ParallelGC 默认的是 Parallel Scavenge(新生代)+ Parallel Old(老年代)
在JVM中是+XX配置实现的搭配组合:
UseSerialGC 表示 “Serial” + "Serial Old"组合
UseParNewGC 表示 “ParNew” + “Serial Old”
UseConcMarkSweepGC 表示 “ParNew” + “CMS”. 组合,“CMS” 是针对旧生代使用最多的
UseParallelGC 表示 “Parallel Scavenge” + "Parallel Old"组合
UseParallelOldGC 表示 “Parallel Scavenge” + "Parallel Old"组合
在实践中使用UseConcMarkSweepGC 表示 “ParNew” + “CMS” 的组合是经常使用的
14. 垃圾回收算法
标记-清除算法
标记-复制算法
标记-整理算法
分代收集算法
15. Kafka原理
16. Kafka生产者组与消费者组
17. 线程池各个属性的含义
首先来看线程池的通用构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
可以看出,构造一个线程池需要以下7个参数:
1.corePoolSize :核心线程数。可以看作稳定的工作线程数量,当阻塞队列还未满载时,线程池将保持核心线程数。
2.maximumPoolSize :最大线程数。可以看作弹性的工作线程数量,当阻塞队列满载时,线程池将在核心线程数的基础上创建新线程来处理任务,直到最大线程数。
3.keepAliveTime :工作线程空闲时则保持存活的时间。
4.unit:keepAliveTime 参数的时间单位
5.workQueue:BlockingQueue类型接口,用来存储积压任务的阻塞队列。
6.threadFactory:线程工厂,用户可以自定义创建线程的工厂。
7.handler:拒绝策略,当workQueue满载时将会触发。
18. 请求伪造(CSRF)
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作
解决方案: 1.检查Referer字段(防盗链) 2.添加校验token
19. RPC有没有使用过
20. Redis缓存穿透、缓存击穿、缓存雪崩
缓存穿透
描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
总结:1.验证拦截 2.缓存空数据 3.使用布隆过滤器
缓存击穿
描述: 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案: 1.设置热点数据永远不过期。2.加互斥锁
缓存雪崩
描述: 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3.设置热点数据永远不过期。
21.分布式事务
分布式事务和常见的分布式事务包括 2PC、3PC、TCC、本地消息表、消息事务、最大努力通知
本地消息表
本地消息表其实就是利用了 各系统本地的事务来实现分布式事务。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。
可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。
22.做一道算法题
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!