常问问题
# 总结
JVM 、mysql、并发、redis、nettry、zookeeper、设计模式、微服务、spring源码、网络基础、项目实战。
JVM、mysql、并发、redis、kafka、spring-boot、微服务、项目、设计模式、网络(nettry)
# JavaSE
# String
- String的引用的指向不变,最终将输出变化? 利用反射修改String的value属性
- String对象的intern用法? ⾸先会检查字符串常量池中是否存在,如果存在则返回该字符串引 ⽤, 如果不存在,则添加到字符串常量池中,并返回该字符串常量的引用。
- String、StringBuffer、StringBuilder的区别 ? 不变对象;对象拼接的使用;线程安全的使用-》jvm的锁消除和锁粗化;
# ==和equals
- Integer和其他包装类的缓存,和自动拆箱和装箱。
- String的字面量和引用地址的判断。
- 对"abc",或者Integer i = 1 加锁能锁住吗? 能,都是同一对象。
# 集合
# ArrayList和LinkedList区别
- 他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的
- 由于底层数据结构不同,他们所适用的场景也不同,ArrayList更适合随机查找,LinkedList更适合 删除和添加,查询、添加、删除的时间复杂度不同。
- 另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以 LinkedList还可以当做队列来使用
- 他们的add、remove、insert方法的源码,可知他们不一定谁快。
| 方法 | ArrayList | LinkedList |
|---|---|---|
| get | 数组下标肯定是快的 | 距离中间的节点,就是遍历几次 |
| remove | 最后一个节点是快的直接把最后设为null,但是其他位置需要复制数组元素,不一定了 | 瓶颈是查询,如果是头节点的话是比array快的 |
| add(E e) | 瓶颈是扩容,容量够的话,比Link快 | 无瓶颈 |
| add(int index, E element) | 瓶颈是扩容+复制数组元素(一定) | 先查再插,瓶颈是查找。所以插入0的话,Link会快. |
| 实现接⼝ | List | List、Deque可以作为队列 |
| 数据结构 | 数组 | node双端链表,且记有头节点和尾节点 |
| 总结 | 无参构造,第一次调用add是要扩容的。所以使用时最好给一个初始值 get肯定是快的 remove 主要是移动的慢,但是操作最后一个元素是快的。 add 不一定比链表慢,主要看是不是要扩容。 数组对空间有连续性要求 add(int index, E element) ,操作最后还是会调用数组复制操作,所以如果是尾插别用他 | 插入和查询会先判断从头还是从尾开始。 删除是先查再操作节点。 |
# CopyOnWriteArrayList
- ⾸先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素 时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上进⾏
- 并且,写操作会ReentrantLock加锁,防⽌出现并发写⼊丢失数据的问题
- 写操作结束之后会把原数组指向新数组
- CopyOnWriteArrayList允许在写操作时来读取数据,⼤⼤提⾼了读的性能,因此适合读多写少的应 ⽤场景,但是CopyOnWriteArrayList会⽐较占内存,同时可能读到的数据不是实时最新的数据,所 以不适合实时性要求很⾼的场景
# HashMap
# 构造方法
- 设为2的次幂来给HashMap进行扩容,如果是7的话就是4
- 利用了多次位运算,来实现上面的算法。
# 扩容机制原理
# 1.7版本
- 先⽣成新数组
- 遍历⽼数组中的每个位置上的链表上的每个元素
- 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标
- 将元素添加到新数组中去
- 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
- 而且是采用的头插法。
# 1.8版本
前面一样的操作,数组的情况,table属性也是都是一样。
如果是红⿊树,
- 先遍历红⿊树并计算出红⿊树中每个元素对应在新数组中的下标位置;
- 统计每个下标位置的元素个数;
- 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对应位置
- 如果该位置下的元素个数没有超过8,那么则⽣成⼀个链表,并将链表的头节点添加到新数组 的对应位置
# ConcurrentHashMap
# 扩容机制
# 1.7版本
- 基于Segment分段实现的
- 每个Segment相对于⼀个⼩型的HashMap,通过锁Segment,来减少细粒度。
- 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
- 先⽣成新的数组,然后转移元素到新数组中
- 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
# 1.8版本
1.8版本的ConcurrentHashMap不再基于Segment实现
- 当某个线程进⾏put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进⾏扩容 ,ConcurrentHashMap是⽀持多个线程同时扩容的。
- 如果某个线程put时,发现没有正在进⾏扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进⾏扩容
- 扩容之前也先⽣成⼀个新的数组
- 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或多组的元素转移⼯作
- 他锁的是synchronized 头节点,而不是Segment对象了,粒度更细。
- 重复的key通过cas替换。
# ThreadLocal
# 底层原理
- ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对 象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
- 里面的Entry继承了软引用,但是还是会出现内存泄露的问题。 比如: 如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该要把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收, Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿ 动调⽤ThreadLocal的remove⽅法,⼿动清楚Entry对象
- ThreadLocal经典的应⽤场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅ 法之间进⾏传递,线程之间不共享同⼀个连接)
# 并发
# MESI
- M 修改 (Modified)
- E 独享、互斥 (Exclusive)
- S 共享 (Shared)
- I 无效 (Invalid)
- E -> S -> I ; S -> M
# JMM(java内存模型)
# 背景
简化多线程编程、屏蔽掉不同操作系统/硬件的底层细节和差异。
# 工作流程图
将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再不定时的将变量写回主内存。
# JAVA如何开启线程?
- 继承Thread类,重写run方法。
- 实现Runnable接口,实现 run方法。 给Thread构造方法传过去。
- 实现Callable接口,实现call方法。通过FutureTask创建一个线程,获取到线程执行的返回值。
- 通过线程池来开启线程。
- 本质就一个Thread类,实现类和构造方法。
# 线程间通信?怎么保证线程安全?
volatile、等待通知机制( wait和notify )、Thread.join
锁机制(LockSupport;Lock;Sychronized)
juc一些其他的工具类(Blobckqueue阻塞队列;Semaphore 信号量;CountDownLatch 倒计时锁;CyclicBarrier 篱栅)
# volatile
在并发领域中,存在三⼤特性:原⼦性、有序性、可⻅性。volatile关键字⽤来修饰对象的属性,在并发环境下可以保证这个属性的可⻅性,会直接将CPU⾼级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从⽽保证了可⻅性,底层是通过操作系统的内存屏障来实现的,由于使⽤了内存屏障,所以会禁⽌指令重排,所以同时 也就保证了有序性,在很多并发场景下,如果⽤好volatile关键字可以很好的提⾼执⾏效率。
但是它不能保证线程安全,只能保证线程可见性, 不能保证原子性。
Volatile防止指令重排。在DCL(双重检测锁)中必须要加上,可以防止高并发情况下,指令重排造成的线程安全问题。
# Sychronized
# 底层原理
JAVA的锁就是在对象头信息的Markword中记录一个锁状态。基于Monitor机制实现,依赖底层操作系统的互斥原语Mutex(互斥量),被阻塞的线程会被挂起、等待重新调度,会导致上下文切换,对性能有较大影响。1.5之后做了锁升级、锁粗化、锁消除相关的优化。内置锁的并发性能已经基本与Lock持平了。
# 锁的膨胀升级
无锁状态(没加锁)、偏向锁(同一线程)、轻量级锁(几个线程交替执行偶尔竞争)、适应性自旋锁(自适应: 对象刚刚的一次自旋操作成功过,他自己默认就多自旋一下)、重量级锁(Mutex,上下文切换)
# java对象头的信息(1次)
hash码,对象所属的gc年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度
# AQS
# CAS (比较并替换)
- 优点 : 无锁的原子操作
- 他的规则是:当需要更新一个变量的值的时候,只有当变量的预期值A和内存地址V中的实际值相同的时候,才会把内存地址V对应的值替换成B。 不同就不替换
- 三个问题/缺点: ABA问题(加版本号解决)、自旋消耗、只能针对一个共享变量
# 简介
juc下的大多数的同步器都是基于AQS框架来实现。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
# 源码实现
volatile int state、同步等待双向链表队列、条件等待队列、cas、LockSupport.park、Condition
# 可重入的实现
state就用来表示加锁的次数。0标识无锁,每加一次 锁,state就加1。释放锁state就减1。
# ReentrantLock
# 公平锁和⾮公平锁实现
不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线 程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。
# tryLock()和lock()⽅法的区别
- tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁则返回 true,没有加到则返回false
- lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也没有返回值
# Sychronized和ReentrantLock的区别
- sychronized是⼀个关键字,ReentrantLock是⼀个类
- sychronized会⾃动的加锁与释放锁,ReentrantLock需要再finally⼿动加锁与释放锁
- sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁,支持多种扩展,如分布式锁等等。
- sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁
- sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识 来标识锁的状态 (这个可以不说)
- sychronized底层有⼀个锁升级的过程,ReentrantLock没有。
- ReentrantLock 支持Condition实现多个条件等待队列。而sychronized只有一个wait和notiy
# CountDownLatch、Semaphore、CyclicBarrier的区别和底层原理
Semaphore表示信号量,表示同时允许最多多少个线程使⽤。
CountDownLatch表示计数器,当计数器值到达0时,所有的线程才都会开始执行。只能使用一次
CyclicBarrier表示栅栏屏障,让一组线程都到达一个屏障点时,所有的线程才都会开始执行。可以使用reset()方法重置,从而复用。
# 线程池
# 构造参数解释
核心线程数、最大线程数、非核心线程的空闲时间、时间单位、阻塞队列、threadFactory、拒绝策略
# ⼯作流程原理
- 如果此时线程池中的线程数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建 新的线程来处理被添加的任务
- 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊ 缓冲队列。
- 如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maximumPoolSize,建新的线程来处理被添加的任务。并且会先被执行。
- 非核心线程数、核心线程数,所有的线程,执行完任务都会去执行队列里面的任务。
- 如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等 于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
- 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被 终⽌。这样,线程池可以动态的调整池中的线程数。
# 非核心线程源码
所有的任务,都被Worker包装成线程执行。在他们的getTask方法,里面核心线程是通过对比当前工作线程数量确定的,所以可能核心线程是会变化的。
非核心线程通过workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)。 来实现等待一段时间没有获得任务就跳出循环释放线程。
核心线程通过workQueue.take();一直阻塞获得。
# 其他问题
如何对一个字符串快速进行排序? Fork/Join框架,分治算法。还可以用于值累加、快速排序、归并排序、桶排序等等分治场景。
有A,B,C三个线程,如何保证三个线程同 时执行?如何在并发情况下保证三个线程依次 执行?如何保证三个线程有序交错进行?
CountDownLatch, 栅栏CylicBarrier, Semaphore,ReentrantLock以及3个Condition。
如果是A、B二个线程的话就还可以用Sychronized实现。
Volatile和Synchronized的区别 ???
Synchronized关键字,主要用来加锁,保证原子性。
Volatile只是保持变量的线程可见性和禁止重排序。通常适用于一个线程写,多个线程读的场景
# JVM
# 哪些是线程共享区
堆区(1/3新生代,2/3老年代)和⽅法区(元空间 默认是21MB)是所有线程共享的,栈、本地⽅法栈、程序计数器是每个线程独有的
# 对象的生命周期
创建、使用、收集阶段(finalize)、回收。
创建一个对象 》 到方法区去找对象的类型信息 》堆中实例化一个对象 》 会先存在新生代的Eden 》 老年代 》 引用被除掉,标记为垃圾 》GC线程清理掉(finalize) 》 回收
# gc root对象
栈中的本地变量、⽅法区中的静态变量、正在运⾏的线程
# 内存分配机制
对象栈上分配、存入Survior空间(大对象直接进入老年代)、长期存活的对象将进入老年代,对象动态年龄,老年代担保机制、full gc、OOM.
# 垃圾收集算法
分代收集理论、标记-复制算法、标记-清除算法、标记-整理算法
STW: Stop-The-World。是在垃圾回收算法执行过程当中,需要将JVM内存冻结的 一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native 方法可以执行,但是,不能与JVM交互。
# 收集器
- 新生代收集器 : Serial、ParNew、Parallel
- 老年代收集器 : Serial Old、CMS、Parallel Old
- 新型垃圾收集器 : G1(jdk9)、ZGC(jdk11)
# CMS收集器的过程
初始标记(STW),并发标记,重新标记(STW),并发清理,并发重置。
注意: 执行过程中,也许没回收完就再次触发full gc,这次gc将用serial old来回收了,变成了单线程。
# 排查JVM问题
- 可以使⽤jmap来查看JVM中各个区域的使⽤情况
- 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁
- 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏调优了
- 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析
- ⾸先,初步猜测频繁发送fullgc的原因,如果频繁发⽣fullgc但是⼜⼀直没有出现内存溢出,那么表 示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免 这些对象进⼊到⽼年代,对于这种情况,就要考虑这些存活时间不⻓的对象是不是⽐较⼤,导致年 轻代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤⼩,如果改完之后,fullgc减少,则证明 修改有效
- 同时,还可以找到占⽤CPU最多的线程,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免 某些对象的创建,从⽽节省内存
# OOM问题
- ⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件(- XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base)
- 我们可以利⽤jsisualvm等⼯具来分析dump⽂件
- 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码
- 然后再进⾏详细的分析和调试
# JVM调优
-server 开启服务模式
‐Xms3072M 初始堆大小 (memory startup)
‐Xmx3072M 最大堆大小 (memory maximum)
‐Xmn2048M 年轻代大小 (memory new)
‐Xss1M 栈大小 (stack size)
‐XX:MetaspaceSize=256M 元空间初始大小
‐XX:MaxMetaspaceSize=256M 元空间最大大小
‐XX:SurvivorRatio=8
‐XX:MaxTenuringThreshold=5
‐XX:PretenureSizeThreshold=1M
‐XX:+UseParNewGC ParNewu收集器
‐XX:+UseConcMarkSweepGC cms收集器
‐XX:CMSInitiatingOccupancyFraction=92
‐XX:+UseCMSCompactAtFullCollection
‐XX:CMSFullGCsBeforeCompaction=0
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base 发⽣了OOM时,自动⽣成当时的dump⽂件
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 双亲委派模型
作用: 保护JAVA的层的类不会被应用 程序覆盖
过程: 向上委托查找,向下委托加载
# Tomcat⾃定义类加载器
# Mysql
# Explain各个字段表示什么
| 列名 | 描述 |
|---|---|
| id | 查询语句中每出现⼀个SELECT关键字,MySQL 就会为它分配⼀个唯⼀的id值 |
| select_type | SELECT关键字对应的那个查询的类型,是简单还是复杂的查询 |
| table | 表名 |
| partitions | 匹配的分区信息 |
| type | 针对单表的查询⽅式(全表扫描、索引) 决定如何查找表中的行,null >system > const > eq_ref > ref > range > index > ALL |
| possible_keys | 可能⽤到的索引 |
| key | 实际上使⽤的索引 |
| key_len | 实际使⽤到的索引⻓度, 联合索引失效,定位. |
| ref | 当使⽤索引列等值查询时,与索引列进⾏等值匹 配的对象信息 |
| rows | 预估的需要读取的记录条数 |
| filtered | 某个表经过搜索条件过滤后剩余记录条数的百分⽐ |
| Extra | ⼀些额外的信息,⽐如排序等 |
# 为什么使⽤B+树
- 拥有B树的特点
- 非叶子节点不存储data,只存储索引(冗余)和下层指针,以便放更多的索引
- 叶子节点包含所有索引字段 ,和数据,并且排好顺序
- 叶子节点用双指针连接,提高区间访问的性能
# 索引覆盖和回表
回表 : 查到数据所在的地址,再通过地址去找到具体的数据。
索引覆盖 : 直接放回,不再需要通过数据地址找到具体的数据再返回。
所需要的字段都在当前索引的叶⼦节点上存在,可以直接作为结果返回了,不⽤再回表。
# 聚簇索引和非聚簇索引
聚簇索引就是数据和索引是在一起的。 主要为了减少回表的操作。
MyISAM使用的是非聚簇索引。 InnoDB采用的是聚簇索引,聚簇索引的数据物理存放顺序和索引顺序是一致的,所以一个表当中只能有一个聚 簇索引,而非聚簇索引可以有多个。
InnoDB中,如果表定义了PK,那PK就是聚簇索引。 如果没有PK,就会找第一个 非空的unique列作为聚簇索引。否则,InnoDB会创建一个隐藏的row-id作为聚簇索引。
# 最左前缀原则
# Mysql慢查询该如何优化
- sql语句的优化,Order by(group by)、limit 大分页、Join优化。
- 检查是否⾛了索引,如果没有则优化SQL利⽤索引
- 检查所利⽤的索引,是否是最优索引
- 检查所查字段是否都是必须的,是否查询了过多字段,查出了多余数据
- 检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资源
- 检查表中数据是否过多,是否应该进⾏分库分表了
# Mysql锁
控制事务的并发访问。
- 从性能上 : 乐观锁(业务实现)、悲观锁(写锁)
- 从对数据库操作 : 读锁(共享锁:lock in share mode) 、写锁(排它锁:for update,DML语句)
- 从对数据操作的粒度 : 全局锁、表锁、 间隙锁(只在可重复读的隔离级别中存在)、行锁
锁优化建议:
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
- 尽可能减少检索条件范围,避免间隙锁。 (insert 和 delete 同时操作一段间隙,就会导致一个等锁。解决: 存在才删除、修改删除状态)
- 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
# MySQL数据存储引擎和区别?
| MyISAM | InnoDB | Archive | MEMORY | |
|---|---|---|---|---|
| 特点 | 表锁、索引/数据文件分离 | 事务、行级锁、外键约束、savePoints、XA事务 | 只支持select 和 insert语句 | 基于内存的 |
| 文件 | MYD和MYISAM文件,非聚簇索引 | InnDB每个表只有一个文件idb,聚簇索引 | 一个文件,不支持索引 | 无 |
# Innodb实现事务原理
# 事务和隔离级别
四大特性: 原子性A、一致性C、隔离性I、持久性D
事务隔离级别: 读未提交、读已提交(不可重复读)、可重复读、串行化。 级别越高,事务的安全性是更高的,但是,事务的并性能也就会越低。
| 脏读 | 不可重读 | 幻读 | 脏写 | |
|---|---|---|---|---|
| 定义 | 读到未提交的数据 | 不同时刻读出的结果不一致 | 读到新增的数据 | 更新丢失 |
| 隔离级别解决方案 | 读已提交以上 | 可重复读以上 | 串行化 | 串行化 |
| 加锁解决方案 | 在修改时加排他锁,直到事务提交才释放。 读取时加共享锁,读完释放锁。 | 读数据时加共享锁,写数据时加排他锁 | 加范围锁(间隙锁) |
# BufferPool缓存机制流程
加载命中的一整页记录到Buffer pool
写入更新前的旧数据到undo日志版本链中
更新Buffer pool 数据
写入redo log Buffer 中
准备提交事务,刷新 redo 日志写入磁盘,第一次提交
写入binlog日志
写入commit标识到redo日志中,第二次提交
这里也就保证binlog和redo日志一致,2阶段提交。
不定时的更新整页修改的数据,到磁盘
# MVCC
通过read-view机制与undo日志上的版本链进行比对实现的。
读已提交隔离级别在每次执行查询都生成新的read-view。
而可重复读事务结束之前是不会变化的。
# 分库分表
多大数据量需要进行分库分表? 一个表的数据量超过500W或者数据文件超过2G
分库分表的方式? 垂直分片按业务角度拆分到不同的库中。 水平分片从数据角度拆分。
分片策略由哪些? 取模、按时间、按枚举值、时间加取模
分库分表后SQL语句的执行流程是怎样的 ? sql解析 -> 查询优化 -> sql路由 -> sql改写 -> sql执行 -> 结果归并
分库分表的问题 ? 垮库查询、跨库排序、分布式事务、公共表、主键重复
# 集群是如何搭建的?读写分离是怎么做的?(0次)
用途: 数据安全、应用做读写分离、高可用 (如果数据库的访问压力没有那么大,读写分离不一定是必须要做的,但是主从架构是高可用架构则是必须要搭建的。)
主从集群原理 :将主节点的Binlog同步给从节点完成主从之间的数据同步。
如何做读写分离 : 要保证主从之间的数据一致,写数据的操作只能在主节点完成, 而读数据的操作。
主从数据不一致问题? 从库配置使用的普通用户为只读
主从读写延迟的问题?
主服务开启半同步。
如果主库写频繁,5.7版本后,从服务开启多线程并行复制binlog数据。
如果一主多从,可以采用MGR架构,保证半数以上同步。
具体配置细节(可以说百度)
主节点 : 开启binlog、指定severId、重启
从节点 : 配置server-id、relay-log、log-bin;启动mysqls;设置主节点ip/port/user/password等等;开启slave;重启
# Redis
# 哪些数据结构和应⽤场景?
- 字符串:单值缓存、对象缓存、分布式锁、计数器、分布式 ID
- 哈希表:对象缓存、购物车。 集群无法分片要避免大key。
- 列表:栈,队列、消息流、阻塞队列
- 集合:抽奖,集合可以进⾏交集、并集、差集操作, 从⽽实现我和某⼈共同关注的⼈、朋友圈点赞、商品筛选等功能。
- 有序集合:集合是⽆序的,有序集合可以设置顺序,可以⽤来实现排⾏榜功能
- BitMap : 海量数据统计、布尔过滤器、用户上线次数统计、连续7日活跃用户
- HyperLogLog : 计算日活、7日活、月活数据
- GEO : 地理位置
# Redis底层数据结构实现原理 (opens new window)
# 分布式锁底层
- ⾸先利⽤setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
- 然后还要利⽤lua脚本来保证多个redis操作的原⼦性
- 同时还要考虑到锁过期,所以需要额外的⼀个看⻔狗定时任务来监听锁是否需要续约
- 同时还要考虑到redis节点挂掉后的情况,所以需要采⽤红锁的⽅式来同时向N/2+1个节点申请锁, 都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到。 redis可以设置同步几个节点才能返回,就不需要用红锁实现了。
# 持久化
- RDB : N 秒内数据集至少有 M 个改动,保存成快照一次
- AOF: 将修改的每一条指令记录进文件
- 混合模式: AOF在重写时,先把内存做RDB快照处理,之后生成的命令是aof
# 淘汰策略
- 被动删除: 当读/写时才删除过期的key
- 主动删除: 定期主动淘汰过期的key
- 超过maxmemory(设置的最大内存) : 不处理(报错);针对所有的key处理;针对过期的key处理
# 主从复制
- slave发送PSYNC 命令给master请求复制数据
- master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件
- 继续接收客户端的请求,它会把数据集的请求以aof格式,缓存在内存中
- 给slave发送rdb数据
- slave清空旧数据,并加载rdb
- master发送 buff的aof指令
- slave 执行aof的指令
- 重复6/7步骤
# 场景问题
- 缓存穿透:访问数据库不存在的数据。 利用空对象、布隆过滤器可以解决
- 缓存击穿:某个热点key突然失效。利用热点key不设置过期时间、加锁减少并发。
- 缓存雪崩:redis挂了或者某⼀时刻⼤批热点数据同时过期。利用再他们批量的过期时间上增加⼀点随机值、做二级缓存、缓存层高可用、限流。
- 热点缓存重建 : 冷数据变热数据、瞬间有大量线程访问DB。 加锁。
- 缓存与数据库双写不一致 : 删除redis再操作数据库、延迟双删(推荐)、内存队列排队、设置过期时间、读写锁来保证实时一致性且高并发、canal监听binlog实现同步。
# 集群架构
# 槽位
16384个槽位 ;
2次取模获得具体的定位;
采用槽位使得数据迁移更少(同时对比hash、和一致性hash的优势);
槽位配置信息存在客户端,不一致时触发重定向。
# 选举流程
gossip协议 : meet 、ping、pong、fail。 去中心话、且端口号默认加 1W
- slave发现自己的master变为FAIL,延迟一段时间后发起选举
- 将自己记录的集群currentEpoch(选举周期)加1,并广播信息通知其他节点
- 其他节点收到该信息,判断请求者的合法性,并发送ACK,对每一个 epoch(周期)只发送一次ack
- 各个slave收到超过半数master的ack后变成新Master,Ack相同继续前面的选举步骤
- slave变成master并广播Pong消息通知其他集群节点,停止选举
# 其他问题
海量数据下,如何快速查找一条记录?
- 使用布隆过滤器,快速过滤不存在的记录 2. 在Redis中建立数据缓存。 3. Redis、数据库查询优化,槽位分配。
# MQ
优点: 异步、解耦、削峰
缺点: 系统可用性降低、系统的复杂度提高、数据一致性
| pulsar | Kafka | RocketMQ | |
|---|---|---|---|
| 特点 | 在运维和扩展上更为简单,功能更多支持多租户 | 吞吐量大 | 事务/延迟/过滤/消息,消息轨迹 |
| 共性 | 消息不丢失、消息顺序、积压消息都是公共问题 | ||
| 缺点 | 生态系统不够完善 | 功能少 | 功能丰富 |
# 使用MQ如何保证分布式事务的最终一致性?
- 生产者要保证100%的消息投递。 事务消息机制
- 消费者这一端需要保证幂等消费。 唯一ID + 业务自己实现幂等 (exactly once 有且仅仅消费一次)
# Kafka
# Pull和Push分别有什么优缺点
- pull表示消费者主动拉取,可以批量拉取,也可以单条拉取,所以pull可以由消费者⾃⼰控制,根据 ⾃⼰的消息处理能⼒来进⾏控制,但是消费者不能及时知道是否有消息,可能会拉到的消息为空。
- push表示Broker主动给消费者推送消息,所以肯定是有消息时才会推送,但是消费者不能按⾃⼰的 能⼒来消费消息,推过来多少消息,消费者就得消费多少消息,所以可能会造成⽹络堵塞,消费者压⼒⼤等问题。
# 设计原理
message、Broker、Topic、Producer、Consumer、ConsumerGroup、Partition、replica(副本) 、Leader、Follower、Offset、ISR、ACK 。
- broker: 一个Kafka物理节点就是一个broker
- Controller : 某一个broker为控制器;用于监听管理broker、topic、partition、ISR、leader相关的变化;通过zk的监听机制和临时节点实现选举算法;
- Partition : 一个topic可以分为多个partition,用于分散给不同的broker,每个partition内部消息是有序的。
- replica : 实现高可用,保证一个分区保存指定的副本数。
- ISR : 保持同步的副本列表,副本选举Leader就取ISR的第一个broker。
- ACK : 0不需要等待,1等待leader将数据写入log,-1等待ISR列表所有副本都写入log。
- Rebalance 重平衡: 期间不对外提供服务;一定要避免和容错,上线后不要轻易增加分区,运维时间放到晚上。
# 场景问题
- 消息丢失: 客户端设置重试、ack=-1 、try/catch做业务容错;kafka配置最小副本数防止ISR列表为空;消费端设置手动提交
- 重复消费: 生产者配置发送PID和Sequence来实现避免重试机制的幂等性;消费端做幂等处理 如: 业务ID、去重表、插入或者更新。
- 顺序消费: 发送端设置同步发送、且都发送到一个分区; 或者通过其他业务处理: 如时间戳、顺序标识、链表结构。
- 消息积压: 1. 临时其他转发到死信队列(其他的topic中)处理 2. 临时存数据库表,之后定时处理。
- 死信队列的实现: 消费失败后转发到其他topic中。
- 延时队列 : 先把消息按照不同的延迟时间段发送到指定的队列,定时器进行轮训消费这些topic到期就发送到具体业务处理的topic,并提交offsert。
- 消息回溯: 可以用consumer的offsetsForTimes、seek等API方法指定从某个offset偏移的消息开始消费
- kafka的事务 : 保障一次发送多条消息的事务一致性;而与业务的事务,还是采用存表+定时发送的本地方法表来实现分布式事务。
# 上线规划
网卡
硬盘,默认7日日志留存和存储的副本数
CPU和内存要大一点
如: 存7天数据,副本为2,平均一条消息10KB,3000左右的QPS. 一秒 30MB 左右,一天大概产生 2.5TB。 5台16核32G,每台7.5T磁盘 NAS, 60MB的带宽.
# 用G1可以设置GC最大停顿时间,给操作系统的缓存留出几个G ‐Xmx25G ‐Xms25G ‐Xmn20G ‐XX:MetaspaceSize=256M ‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=50 ‐XX:G1He apRegionSize=40M1
2
3
# Spring
# Spring为啥怎么强大,开发都离不开他?
什么是Spring?谈谈你对IOC和AOP的理解?
IOC就是控制反转,指创建对象的控制权转移给Spring来进行管理。
DI机制将对象之间的关系交由框架处理,减少组件的耦合。
AOP技术,低侵入设计。
同时提供了许多扩展接口,以便和其他框架整合。 已达到spring-boot简化配置、搭建环境脚手架、提高开发效率。
# Spring中Bean是线程安全的吗
SpringMVC中的控制器是不是单例模式?如果是,如何保证线程安全? socpe为prototype, request 或者 设计为无状态的。
Spring本身并没有针对Bean做线程安全的处理,还是得看这个Bean对象本身。如是否加锁、是否无状态。
可能用socpe作用域,来设置原型或者请求中,但是spring还是没有对线程安全做控制。
# Bean创建的⽣命周期
我从看到的源码开始说吧: bean的生命周期其实就是getBean方法源码。
- 获得真实的beanName,如别名、&等
- 从单例池获得对象,有就判断是不是FactoryBean,没有就判断Scope走不同的分支处理。
- 核心createBean方法:先加载类 》 实例化前扩展点 》推断构造方法扩展点》合并BeanDefinition扩展点》添加ObjectFactory的三级缓存》实例化后扩展点》根据BY_NAME/BY_TYPE填充PropertyValues》修饰PropertyValues扩展点(@Autowired,@Value,@Resource @Inject都是在这里处理)》如果出现了循环依赖调用getEarlyBeanReference扩展点,执行后放到二级缓存中 》循环PropertyValues填充属性 》 初始化前扩展点(Aware的操作) 》执行初始化方法(实现InitializingBean接口、指定init方法、@PostConstruct) 》 初始化后扩展点 》给scope注册销毁回调方法。
# Spring循环依赖问题
一种是使用@Lazy注解: 解决构造方法造成的循环依赖问题
一种是spring内部使用三级缓存解决的:
对象分别为: 一级缓存单例池对象 ,二级缓存初始化的对象 ,三级缓存ObjectFactory对象
在getBean过程中都会先实例化ObjectFactory对象,再设置PropertyValues过程中,发现有循环依赖,就会调用getEarlyBeanReference扩展点,执行后放到二级缓存中(再AOP模板抽象类 AbstractAutoProxyCreator 内部存放的,该类实现了 实例化前扩展点、前面的那个扩展点、初始化后扩展点)。
# ApplicationContext和BeanFactory有什么区别
BeanFactory是Spring中⾮常核⼼的组件,表示Bean⼯⼚,可以⽣成Bean,维护Bean。
⽽ ApplicationContext继承了BeanFactory,拥有BeanFactory所有的特点,还有获取系统环境变量、国际化、事件发布等功能,这是BeanFactory所不具备的,并且它是IOC容器的启动主流程。
# Spring容器启动流程
初始化Conditional解析器、环境变量、扫描器并且设置@Component、注册手工设置的BeanDefinition、注册默认的BeanDefinition: ConfigurationClassPostProcessor(扫描并注册BeanDefinition、@Import、@Bean)、AutowiredAnnotationBeanPostProcessor(处理@Autowired),CommonAnnotationBeanPostProcessor(处理@PostConstruct@PreDestroy@Resource)
调用refresh()入口方法,里面有12个主线方法,有⼀些模板⽅法,让⼦类来实现。
- 准备刷新方法: 初始化并校验必须的环境变量、发布早期事件
- 获得新Bean工厂方法: 校验是否可以重复刷新、和销毁之前的bean等等。
- 准备Bean工厂方法 : 设置类加载器、SpringEL表达式解析器、类型转化注册器;添加一些BeanPostProcessor(Aware接口注入);注册一些环境相关的单例。
- 后置处理Bean工厂的空方法
- 调用Bean工厂后置处理器方法 : 4种方式排序的递归的执行处理BeanDefinitionRegistry的方法用于扫描并注册BeanDefinition并存在⼀个Map中,4种方式排序的递归的执行操作BeanFactory的方法用于注册单例。
- 注册Bean后置处理器方法 : 扫描前面所有属于BeanPostProcessor的BeanDefinition实例化放到单例池中。
- 初始化国际化方法
- 初始化事件广播者
- onRefresh 空方法
- 注册事件监听器 方法: 并且广播earlyApplicationEvents的事件。
- 完成BeanFactory初始化方法 : 冻结BeanDefinitionNames,实例化非懒加载的单例Bean()
- 完成刷新方法: 清除ASM信息、初始化并执行生命周期处理器的start方法、广播完成事件
# Spring中什么时候@Transactional会失效
Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时,那么这个注解才会⽣效,所以如果是被代理对象来调⽤这个⽅法,那么@Transactional是不会失效的。 如this.XXX(); 等等。
底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类final⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效
还有再源码扫描中做只会对pulic方法做代理。
# @Transactional情况异常
默认情况下@Transactional ,Spring会对unchecked异常进行事务回滚;如果是checked异常则不回滚
java里面将派生于Error或者RuntimeException(比如空指针,1/0)的异常称为unchecked异常,其他继承自java.lang.Exception得异常统称为Checked Exception,如IOException、TimeoutException等
可以这样解决@Transactional(rollbackFor={Exception.class,Exception1.class})
# Spring中的事务是如何实现的
支持编程式事务管理 (TransactionTemplate) 和声明式事务管理(@Transactional)
- Spring声明式事务底层是基于数据库事务和AOP机制的
- 对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean
- 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解
- 如果加了,那么则利⽤事务管理器创建⼀个数据库连接
- 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交
- 然后执⾏当前⽅法,⽅法中会执⾏sql
- 执⾏完当前⽅法后,如果没有出现异常就直接提交事务
- 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
- Spring事务的隔离级别对应的就是数据库的隔离级别
- Spring事务的传播机制是Spring事务⾃⼰实现的,也是Spring事务中最复杂的 ,它是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为需要新开⼀个事务,那么实际上就是先建⽴⼀个数据库连接,在此新数据库连接上执⾏sql
TransactionAttribute 事务属性
就是@Transactional中的一些配置。
TransactionSynchronizationManager 事务同步管理器
事务相关的ThreadLocal统一管理的一些api,比如resources(map<obj,obj> 一般为key为DataSource对象,value为ConnectionHolder对象)、synchronizations、currentTransactionName、currentTransactionReadOnly、currentTransactionIsolationLevel、actualTransactionActive
第三方整合事务也经常用到这个,但是不是直接用它,而是用spring提供的DataSourceUtils.getConnection(); 可以见mybatis-spring的事务整合。
TransactionAspectSupport 事务处理支持
他管理 ThreaLocal
,并且控制代理对象的主流程。 TransactionInfo 事务信息: TransactionInfo 主要是持有事务的状态,以及上一个TransactionInfo 的一个引用,并与当前线程进行绑定。内部拥有TransactionStatus
TransactionStatus 事务状态: 主要描述当前事务的状态,比如:是否有事务,是否是新事物,事务是否只读;回滚点相关操作等等。这些相关的属性在后面会影响事务的提交。 事务传播机制主要的实现。
看完源码知道他有一些高级的用法:
- 事务传播机制 : @Transactional(propagation = Propagation.REQUIRES_NEW)
- 强制回滚 :TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
- 动态数据源 : TransactionSynchronizationManager.setCurrentTransactionName
- 事务同步 (opens new window): TransactionSynchronizationManager.registerSynchronization、@TransactionEventListener
# spring和mybatis整合原理(1次)
# 管理Mapper接口的动态代理的
- @MapperScan 导入了 MapperScannerRegistrar(实现了ImportBeanDefinitionRegistrar接口).
- new ClassPathMapperScanner 重写了判断BeanDefintion方法isCandidateComponent。 判断只有是接口才可以扫描到的。
- 重写了doScan方法,责任链调用super.doScan,再处理扫描收集到的BeanDefinitions集合。
- 把BeanDefinition的属性BeanClass修改为MapperFactoryBean , 添加一个构造参数为原始的Class,把AutowireMode修改为byType
- MapperFactoryBean 实现了2个重要接口 FactoryBean和InitializingBean接口。
- InitializingBean 再实例化时会给configuration.addMapper 往mybatis的configuration注册Mapper.
- FactoryBean : 中就是 getSqlSession().getMapper()
- MapperFactoryBean的AutowireMode为byType,所以Spring会自动调用set方法,有两个set方法,一个setSqlSessionFactory,一个setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以Spring容器中要提前存在SqlSessionFactory类型的bean或者SqlSessionTemplate类型的bean。如果你定义的是一个SqlSessionFactory(SqlSessionFactoryBean)类型的bean,那么最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性
- 而在SqlSessionTemplate类中就存在一个getMapper方法,这个方法中就产生一个Mapper接口代理对象,之后就是Mybatis的流程。
# 如何整合spring事务
SpringManagedTransactionFactory implements TransactionFactory (mybatis的)
方法里面 new SpringManagedTransaction()
spring的工具类: DataSourceUtils.getConnection(this.dataSource); > TransactionSynchronizationManager.getConnection > 通过dataSource找到ConnectionHolder再获得Connection
2
3
# 如何解决线程不安全的问题
SqlSessionTemplate -> sqlSessionProxy -> SqlSessionInterceptor -> DefaultSqlSession
// 就会调用上面SpringManagedTransactionFactory.newTransaction获得 SpringManagedTransaction 再走spring的事务。
# 总结
程序员定义SqlSessionFactoryBean , 他将构建SqlSessionFactory,整合spring的事务。
ClassPathMapperScanner : 重写判断bean办法,和修改扫描出来的BeanDefinition的BeanClass为MapperFactoryBean
MapperFactoryBean : 从上面spring 获得 SqlSession再包装成为sqlSessionTemplate,解决线程安全和整合spring事务的问题。
# SpringMVC
# ⼯作流程
DispatcherServlet 》 遍历所有HandlerMapping解析request获得该Handler和对应的拦截器,以HandlerExecutionChain对象返回
》 循环调用HandlerAdapter的方法找到合适的HandlerAdapter 》 调用前置拦截器方法 》 HandlerAdapter.handle方法 提取Request中的模型数据,填充Handler入参,开始执行Handler,HttpMessageConveter的数据转换, 数据验证,处理业务,获得业务返回,并封装成ModelAndView 》 调用后置拦截器方法 》 异常判断并封装处理成ModelAndView 》 找到合适的ViewResolver 》 结合Model和View,来渲染视图 》 调用拦截器完成方法
# 启动流程(0次)
JDK的SPI 到 Servlet的SCI接口 => 业务设置的ServletInitializer类,实现父子容器的初始化 -> @EnableWebMvc 注册相关的bean。
servlert启动后,才回调spring的ApplicationContextInitializer接口,再他里面执行父子容器refresh方法。
# SpringBoot
# 常⽤注解及其底层实现
\1. @SpringBootApplication注解:这个注解标识了⼀个SpringBoot⼯程,它实际上是另外三个注解 的组合,这三个注解是: a. @SpringBootConfiguration:这个注解实际就是⼀个@Configuration,表示启动类也是⼀个 配置类 b. @EnableAutoConfiguration:向Spring容器中导⼊了⼀个Selector,⽤来加载ClassPath下 SpringFactories中所定义的⾃动配置类,将这些⾃动加载为配置Bean c. @ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫 描的路径是启动类所在的当前⽬录 2. @Bean注解:⽤来定义Bean,类似于XML中的标签,Spring在启动时,会对加了@Bean注 解的⽅法进⾏解析,将⽅法的名字做为beanName,并通过执⾏⽅法得到bean对象 3. @Controller、@Service、@ResponseBody、@Autowired都可以说
# 配置⽂件的加载顺序
优先级从⾼到低,⾼优先级的配置覆盖低优先级的配置,所有配置会形成互补配置。 1. 命令⾏参数。所有的配置都可以在命令⾏上进⾏指定; 2. Java系统属性(System.getProperties()); 3. 操作系统环境变量 ; 4. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置⽂件 5. jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置⽂件 再来加 载不带profile 6. jar包外部的application.properties或application.yml(不带spring.profile)配置⽂件 7. jar包内部的application.properties或application.yml(不带spring.profile)配置⽂件 8. @Configuration注解类上的@PropertySource
# 如何打War
# 如何启动Tomcat
- ⾸先,SpringBoot在启动时会先创建⼀个Spring容器 2. 在创建Spring容器过程中,会利⽤@ConditionalOnClass技术来判断当前classpath中是否存在 Tomcat依赖,如果存在则会⽣成⼀个启动Tomcat的Bean 3. Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端⼝等,然后 启动Tomcat
# 自动装配原理
- @EnableAutoConfiguration实现了DeferredImportSelector接口(用于操作@OnBeanCondition)
- 获得注解上面属性信息,加载并获得所有jar的里面spring.factories文件中的EnableAutoConfiguration对应的配置类集合
- 移除重复、注解里面定义和Environment设置的需要排除的类。
- 加载spring.factories中的AutoConfigurationImportFilter实现类,并且循环调用方法过滤不需要的类。
- 排除spring-autoconfigure-metadata.properties配置中的里面的过滤类。
- 实例化监听器用于记录装配成功的内容,发布spring的自动配置导入事件。
# Mybatis
# 优点和缺点
优点:
- 基于 SQL 语句编程,相当灵活,不会对应⽤程序或者数据库的现有设计造成任何影响,SQL单独 写,解除 sql 与程序代码的耦合,便于统⼀管理。
- 与 JDBC 相⽐,减少了 50%以上的代码量,消除了 JDBC ⼤量冗余的代码,不需要⼿动开关连 接;
- 很好的与各种数据库兼容( 因为 MyBatis 使⽤ JDBC 来连接数据库,所以只要JDBC ⽀持的数据 库MyBatis 都⽀持)。
- 能够与 Spring 很好的集成;
- 提供映射标签, ⽀持对象与数据库的 ORM 字段关系映射; 提供对象关系映射标签, ⽀持对象关 系组件维护。
缺点:
- SQL 语句的编写⼯作量较⼤, 尤其当字段多、关联表多时, 对开发⼈员编写SQL 语句的功底有⼀定要求。
- SQL 语句依赖于数据库, 导致数据库移植性差, 不能随意更换数据库
# #{}和${}的区别是什么?
| #{} | ${} | |
|---|---|---|
| 机制 | 预编译处理、是占位符 | 字符串替换、是拼接符 |
| 实现 | 会将 sql 中的#{}替换为?号,调⽤ PreparedStatement 来赋值 | 就是把${}替换成变量的值,调⽤ Statement 来赋值 |
| 用途 | 防⽌SQL注⼊ | 常用于动态返回字段上 |
# 插件编写与原理
MyBatis四大核心对象 ParameterHandler:处理SQL的参数对象 ResultSetHandler:处理SQL的返回结果集 StatementHandler:数据库的处理对象,用于执行SQL语句 Executor: MyBatis的执行器,用于执行增删改查操作
插件编写: 实现Interceptor接口,并加上@Intercepts说明拦截对象的哪些方法,重写接口方法,把spring-boot用@Bean实例化。
# 微服务
# 微服务优缺点
| 优点 | 缺点 |
|---|---|
| 服务部署更灵活 | 服务调用的复杂性提高 |
| 技术更新灵活 | 分布式事务 |
| 应用的性能得到提高 | 测试的难度提升 |
| 更容易组合专门的团队 | 运维难度提升 |
| 代码复用 | 架构更复杂 |
# CAP理论
C(Consistency)表示强⼀致性, A(Availability)表示可⽤性,P(Partition Tolerance)表示分区容错性。
CA数据库,AP代表Redis,CP代表ZK.
# BASE理论
BA表示基本可⽤,S表示软状态,E表示最终⼀致性
# RPC
表示远程过程调⽤。在Java中,我们可以通过直接使⽤某个服务接⼝的代理对象来执⾏⽅法如open-fegin。协议可以是tcp也可以是http.
# 分布式ID
- uuid ,会影响存储空间和性能一般不使用。(数据的毫无顺序会导致数据分布散乱,索引需要大量的重建,数据会有碎片,同时一些BufferPool的缓存也会失效,频繁的刷盘。)
- 利⽤redis、zookeeper的特性来⽣成id。推荐
- 雪花算法,数据结构为时间戳+机器码+数字自增,但是他只能保证趋势递增。
# 分布式锁
- 数据库for update 来实现。
- zookeeper 的 临时顺序节点 + watch机制可以实现。
- redis : setnx、lua脚本、消费订阅、定时续命。 还有红锁,但是也可以采用redis的配置解决锁丢失的情况。
# 分布式事务
- 本地消息表 : 先存入到同一个数据库的一张表中,再用定时轮询调用第三方系统的处理、如MQ/Redis/RestApi/等等,再更新表记录的状态。 再有其他的处理不同状态情况,如失败、错误编码等等。
- 最大努力通知 : 如银行通知、 商户通知、支付宝支付等。 发起拉起APP,待第三方回调编写的接口通知我们处理结果,再补偿性对账
- 消息队列带业务的实现 : 当前业务操作表 + 消息表,保证了ACID。定时任务扫描日志消息表,往MQ里面发消息,各个消费者做事务。
- 消息队列无业务实现: 直接放到消息队列中,保证各个消费者做事务,就行了。
- Rocketmq事务消息 : Producer发送事务消息,Server回应消息发送成功,Producer执行本地事务并做commit操作,编写事务回查防止Producer挂了。
- JTA/XA规范 : 需要数据库支持XA规范,功能比AT弱,没有自动补偿机制。
- Seata的AT模式 : 只能用于关系型数据库,几乎不需要修改业务代码,有自动补偿机制。需要再业务库中添加undo-log数据表
- TCC: 需要业务做3阶段的硬编码prepare、commit 、rollback ,还需要注意处理空回滚、防空悬挂控制、幂等控制。
- Saga: 通过流程图控制事件驱动,正向服务和补偿服务都由业务开发,无锁、高性能、可以整合其他公司的系统。
# 分布式session
在负载均衡中,ip到同一台服务器中。
session复制 : tomcat 开启集群同步session
session共享 : 代理模式,代理把数据放到redis中存放。
# Spring Cloud有哪些常⽤组件
- 注册中⼼: Eureka、Nacos、Consul、Zookeeper、etcd
- 配置中⼼: Spring Cloud Config、Nacos、Consul、Apollo、Zookeeper、etcd
- 负载均衡: Ribbon
- RPC调⽤:Feign、OpenFeign 、 Dubbo
- 服务⽹关:Zuul、Gateway、Kong、APISIX
- 服务熔断:Hystrix、Sentinel
- 链路追踪: Sleuth、Zipkin、skywking
- 分布式事务 : Seata
SpringCloud NetFlix : Eureka、Spring Cloud Config、Ribbon 、Feign、Zuul、Hystrix
SpringCloud Alibaba : Nacos、Nacos、Ribbon 、Feign/OpenFeign (Dubbo)、Gateway、Sentinel
# SpringCloud Alibaba组件原理篇
考察重点是微服务:::::
# gateWay的IO模型
WebFlux 异步非阻塞IO模型,
# fegin的工作原理
- 程序启动后,会进行包扫描,扫描所有的@FeignClient 的注解的类,通过扩展把@RequestMapping解析MethodMetadata,并将这些信息生成代理对象注入IoC容器中。
- 当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象。
- 根据RequestTemplate再生成Http请求的Request对象。
- Request 对象交给Client去处理,其中Client的网络请求框架可以是(默认的组件)Apache HttpClient、JDK HttpURLConnection、HttpClient和OkHttp。
- 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
- spring实现了fegin提供的接口,并定制自己的一套OpenFegin规范。
# nacos的协议:
distro协议是为了注册中心而创造出的协议; 客户端与服务端有两个重要的交互,服务注册与心跳发送; 客户端以服务为维度向服务端注册,注册后每隔一段时间向服务端发送一次心跳,心跳包需要带上注册服务的全部信息,在客户端看来,服务端节点对等,所以请求的节点是随机的; 客户端请求失败则换一个节点重新发送请求; 服务端节点都存储所有数据,但每个节点只负责其中一部分服务,在接收到客户端的“写“(注册、心跳、下线等)请求后,服务端节点判断请求的服务是否为自己负责,如果是,则处理,否则交由负责的节点处理; 每个服务端节点主动发送健康检查到其他节点,响应的节点被该节点视为健康节点; 服务端在接收到客户端的服务心跳后,如果该服务不存在,则将该心跳请求当做注册请求来处理; 服务端如果长时间未收到客户端心跳,则下线该服务; 负责的节点在接收到服务注册、服务心跳等写请求后将数据写入后即返回,后台异步地将数据同步给其他节点; 节点在收到读请求后直接从本机获取后返回,无论数据是否为最新。
# Sentinel的原理:
从这个架构图可以发现,整个调用链中最核心的就是 StatisticSlot(用于记录、统计不同纬度的 runtime 指标监控信息) 以及FlowSlot(根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制).
Chain是链条的意思,从build的方法可看出,ProcessorSlotChain是一个链表,里面添加了很多个Slot。具体的实现需要到DefaultProcessorSlotChain中去看。 SlotChain 的链路执行。 FlowRule 循环匹配资源进行限流过滤。
# Spring Cloud和Dubbo有哪些区别
Spring Cloud是⼀个微服务框架,提供了微服务领域中的很多功能组件,Dubbo⼀开始是⼀个RPC调⽤ 框架,核⼼是解决服务调⽤间的问题,Spring Cloud是⼀个⼤⽽全的框架,Dubbo则更侧重于服务调 ⽤,所以Dubbo所提供的功能没有Spring Cloud全⾯,但是Dubbo的服务调⽤性能⽐Spring Cloud⾼, Dubbo使用tcp更快性能更高同时也支持http协议,同时dobbo现在也在增加cloud的功能了。,不过Spring Cloud和Dubbo并不是对⽴的,是可以结合起来⼀起使⽤的。
# SOA、分布式、微服务有何异同?
- 分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和微服务基 本上都是分布式架构的
- SOA是⼀种⾯向服务的架构,系统的所有服务都注册在总线上,当调⽤服务时,从总线上查找服务 信息,然后调⽤
- 微服务是⼀种更彻底的⾯向服务的架构,将系统中各个功能个体抽成⼀个个⼩的应⽤程序,基本保 持⼀个应⽤对应的⼀个服务的架构
# 服务雪崩、限流、熔断、降级有何异同?
- 一个服务挂了,影响其他服务调用,进而一直扩大影响整个系统,这就是服务雪崩,解决⽅式就是服务限流、服务熔断和服务降级。
- 服务限流是指在⾼并发请求下,为了保护系统,可以对访问服务的请求进⾏数量上的限制,从⽽防 ⽌系统不被⼤量请求压垮,在秒杀中,限流是⾮常重要的。
- 服务熔断是指,上游服务A为了保证⾃⼰不受影响,从⽽不再调⽤下游服务,直接返回⼀个结果,上游服务的压⼒,直到下游服务恢复继续又改用调用下游服务。
- 服务降级是指,当发现系统压⼒过载时,可以通过关闭某些非核心的服务,或限流某个服务来减轻系统压⼒,这就是服务降级。
# 其他问题
怎么拆分微服务? DDD领域驱动设计?
微服务之间尽量不要有业务交叉。只能通过接口进行服务调用,而不能绕过接口直接访问对方的数据。高内聚,低耦合。
高内聚低耦合 : 同步的接口调用 和 异步的事件驱动
DDD : 贫血/充血模型 (贫血失忆症) ; 防腐层(适配器模式);领域对象、领域服务;限界上下文(划分边界);
中台和微服务有什么关系?
中台可以分为三类 业务中台、数据中台和技术中台。 就是把共性的东西给剥离出来,如sdk、脚手架、单个的服务。
你的项目中是怎么保证微服务敏捷开发的?
开发运维一体化。 技能要求提高了
每个月固定发布新版本,以分支的形式保存到代码仓库中。快速入职。任务面板、 站立会议。团队人员灵活流动,同时形成各个专家代表。测试环境- 生产环境 -》 开 发测试环境SIT、集成测试环境、压测环境STR、预投产环境、生产环境PRD。文档优先。晨会、周会、需求拆分会。
微服务的链路追踪、持续集成、AB发布要 怎么做?
链路追踪: skywking 或者 filebeat-logstash-Elasticsearch采集
持续集成:SpringBoot maven pom -> build -> shell -> Jenkins。
AB发布:1、蓝绿发布、红黑发布。 老版本和新版本是同时存在的。2、灰度发布、 金丝雀发布
# 设计模式
# 创建型
- 建造者 : 有builder,做入口解析、链式调用代替set方法。
- 工厂方法 : 封装new细节。
# 结构型
- 适配器 : 用接口+引用+封装, 做第三方的整合进入现有系统。
- 装饰器 : 用向上循环委托 , 做显式的功能增强
- 代理 : 用静态委托、jdk/字节码动态代理,做隐式的功能增强
- 组合 : 用循环所有,做树状结构的逻辑处理。
- 外观/门面 : 用多种委托,做所有的入口操作。
# 行为型
- 策略, 用不同实现类 ,代替switch、if-else(说成switch更类似)
- 组合 , 用循环+跳出条件 ,代替if-else
- 状态 , 用对象和上下文入参 , 代替if-else + 状态流转
- 模板 , 用抽象类,定义主体流程。
- 责任链 , 用循环或者递归,代替对上下文的流程化加工
- 观察者, 用循环+回调, 实现发布/订阅的业务解耦。
Spring 的AOP用了什么设计模式: 模板模式(AbstractAutoProxyCreator)、适配模式(AdvisorAdapter )、包装模式(把bean包装成TargetSource)、代理模式。
# Spring⽤到了哪些设计模式
- 工厂模式: BeanFactory、FactoryBean
- 适配器模式: AdvisorAdapter 不同对Advisor做适配
- 装饰器模式: BeanWarpper
- 代理模式: AOP
- 观察者模式 : 事件监听机制
- 策略模式: InstantitionStrategy 不同情况实例化
- 模板模式: JdbcTemplate等等各种Template、还有Abstract****
- 责任链模式 : BeanPostProcessor
- 委托模式: BeanDefinitionParserDelegate 解析器
- 访问者模式: PropertAccessor 属性访问器接口
# Netty
# 和Tomcat有什么区别?
Netty是⼀个基于NIO的异步⽹络通信框架,性能⾼,封装了原⽣NIO编码的复杂度,开发者可以直接使 ⽤Netty来开发⾼效率的各种⽹络服务器,并且编码简单。
Tomcat是⼀个Web服务器,是⼀个Servlet容器,基本上Tomcat内部只会运⾏Servlet程序,并处理 HTTP请求,⽽Netty封装的是底层IO模型,关注的是⽹络数据的传输,⽽不关⼼具体的协议,可定制性更⾼,也有。
# BIO、NIO、AIO分别是什么
- BIO:同步阻塞IO,使⽤BIO读取数据时,线程会阻塞住,并且需要线程主动去查询是否有数据可读,并且需要处理完⼀个Socket之后才能处理下⼀个Socket
- NIO:同步⾮阻塞IO,使⽤NIO读取数据时,线程不会阻塞,但需要线程主动的去查询是否有IO事件
- AIO:也叫做NIO 2.0,异步⾮阻塞IO,使⽤AIO读取数据时,线程不会阻塞,并且当有数据可读时 会通知给线程,不需要线程主动去查询
# NIO
核心组件 : Selector(多路复用器)、Channel(通道)、Buffer(缓冲区)、操作系统的非阻塞定义
| select | poll | epoll | |
|---|---|---|---|
| 数据结构 | 数组 | 链表 | 哈希表 |
| 最大连接数 | 有上限,受限于内核 | 无上限 | 无上限 |
| 查找方式 | 遍历 | 遍历 | 观察者回调模式 |
| 时间复杂度 | 轮询所有的sockchannel,时间复杂度O(n) | 轮询所有的sockchannel,时间复杂度O(n) | 事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1) |
| 用户态和内核态切换 | 从用户态拷贝到内核态,在从内核态拷贝回用户态,总共两次 | 同select,总共两次 | 从用户态拷贝到内核态,只有一次 |
epoll :
使用mmap加速内核与用户空间的消息传递 ; LT (水平触发模式) 和 ET(边缘触发模式)
epoll 的 api :
epoll_create 创建一个epoll的句柄
epoll_ctl 事件注册函数
epoll_wait 等待事件的到来
# 高级功能
- 编码解码器 : 他们都实现了ChannelInboundHadnler或者ChannelOutboundHandler接口,如StringEncoder/StringDecoder
- 粘包拆包 : 原因: TCP是一个流协议。解决方案: 消息定长度,尾部添加特殊分隔符,发送长度(http协议实现)
- 心跳检测机制 : 添加IdleStateHandler,再在后面其他自定义的handle中重写userEventTriggered做读写空闲的主动发送心跳内容如ping。
- 断线自动重连 : 启动时利用Listener做递归连接; 运行中时重写InboundHandler的channelInactive方法做引用属性的连接方法。
# 零拷⻉
用户空间和内核空间:操作系统为了保护系统安全,将内核划分为两个部分,一个是用户空间,一个是内核空间。用户空间不能直接访问底层的硬件设备,必须通过内核空间。
传统的拷贝过程是 磁盘 -> 内核 -> JVM -> 修改后JVM -> 内核 -> 网卡(磁盘) ;
零拷⻉是指: 应⽤程序在需要把内核中的⼀块区域数据转移到另外⼀块内核区域去时,不需要先经过复制到JVM⽤户空间,再转移到⽬标内核区域去了,⽽直接实现转移,从而减少了2次拷贝。
# 两种方式
| mmap | transfile | |
|---|---|---|
| API | MappedByteBuffer | FileChannel |
| 场景 | 适合比较小的文件,1.5G ~2G | 没有文件大小限制 |
# Reactor线程模型
Netty同时⽀持Reactor单线程模型(boss和worker都是一个)、Reactor多线程模型(一个boss、多个worker)和Reactor主从多线程模型(多个boss、多个worker),⽤户可根 据启动参数配置在这三种模型之间切换。
服务端启动时,通常会创建两个NioEventLoopGroup实例,对应了两个独⽴的Reactor线程池, bossGroup负责处理客户端的连接请求,workerGroup负责处理I/O相关的操作,执⾏系统Task、定时任务Task等。可根据服务端引导类ServerBootstrap配置参数选择Reactor线程模型,进⽽最⼤限度 地满⾜⽤户的定制化需求。
# ⾼性能体现在哪些⽅⾯
主从Reactor线程模型
把连接和处理线程分开,避免阻塞。
NIO多路复用非阻塞
NIO的多路复用就是一种无锁串行化的设计思想(理解下Redis和Netty的线程模型)
无锁串行化设计思想
为了尽可能提升性能,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降。表面上看,串行化设计似乎CPU利用率不高,并发程度不够。但是,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。
Netty的NioEventLoop读取到消息之后,直接调用ChannelPipeline的fireChannelRead(Object msg),只要用户不主动切换线程,一直会由NioEventLoop调用到用户的Handler,期间不进行线程切换,这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。
支持高性能序列化协议(这个nio也可以做到.)
零拷贝(直接内存的使用)
ByteBuf内存池设计
因为bytebuffer分配比再jvm堆上慢,但是读写比jvm快,所以bytebuffer做了池化,还有其他的优化。
灵活的TCP参数配置能力
合理设置TCP参数在某些场景下对于性能的提升可以起到显著的效果,例如接收缓冲区SO_RCVBUF和发送缓冲区SO_SNDBUF。如果设置不当,对性能的影响是非常大的。通常建议值为128K或者256K。
Netty在启动辅助类ChannelOption中可以灵活的配置TCP参数,满足不同的用户场景。
并发优化
- volatile的大量、正确使用;
- CAS和原子类的广泛使用;
- 线程安全容器的使用;
- 通过读写锁提升并发性能。
# 网络
# 浏览器发出⼀个请求到收到响应经历了哪些步骤?
重点host和dns的ip解析。
# 跨域请求
浏览器会发送options请求去检查协议、域名、端⼝和当前⽹⻚是否⼀致,进行校验。
解决方案: response添加header、 jsonp、后台的反向代理。
# TCP的三次握⼿四次挥⼿过程
在建⽴TCP连接时,需要通过三次握⼿来建⽴,过程是:
- 客户端向服务端发送⼀个SYN
- 服务端接收到SYN后 +n ,并给客户端发送⼀个SYN_ACK
- 客户端接收到SYN_ACK后,再给服务端发送⼀个ACK
在断开TCP连接时,需要通过四次挥⼿来断开,过程是:
- 客户端向服务端发送FIN
- 服务端接收FIN后,向客户端发送ACK,表示接收断开连接的请求,客户端不发数据了,不过服务端这边可能还有数据正在处理
- 服务端处理完所有数据后,向客户端发送FIN,表示服务端现在可以断开连接
- 客户端收到服务端的FIN,向服务端发送ACK,表示客户端也会断开连接了
# TCP和UDP有什么区别?
# HTTP和HTTPS的区别
HTTP: 是互联网上应用最为广泛的一种网络通信协议,基于TCP,可以使浏览器工 作更为高效,减少网络传输。
HTTPS: 是HTTP的加强版,可以认为是HTTP+SSL(Secure Socket Layer)。在 HTTP的基础上增加了一系列的安全机制。一方面保证数据传输安全,另一位方面对 访问者增加了验证机制。是目前现行架构下,最为安全的解决方案。
主要区别:
1、HTTP的连接是简单无状态的,HTTPS的数据传输是经过证书加密的,安全性 更高。
2、HTTP是免费的, 而HTTPS需要申请证书,而证书通常是需要收费的,并且费 用一般不低。
3、他们的传输协议不通过,所以他们使用的端口也是不一样的, HTTP默认是80 端口,而HTTPS默认是443端口。
HTTPS的缺点:
1、HTTPS的握手协议比较费时,所以会影响服务的响应速度以及吞吐量。
2、HTTPS也并不是完全安全的。他的证书体系其实并不是完全安全的。并且 HTTPS在面对DDOS这样的攻击时,几乎起不到任何作用。
3、证书需要费钱,并且功能越强大的证书费用越高。
# Zookeeper
# 数据结构和功能、使用
数据结构: 持久化目录节点、持久化顺序编号目录节点、临时节点、临时顺序编号节点。 监听通知机制。 Curator工具包使用 : 分布式配置中心、分布式注册中心、集群选举、发布/订阅、分布式队列、计数器、缓存、分布式锁(非公平锁:的羊群效应;公平锁怎么解决的?)
# ZAB协议 (一致性协议)
为分布式协调服务 专门设计的一种支持 崩溃恢复 和 原子广播(数据同步) 的协议
2 个原则 : 类似于2阶段. 确保丢弃没有提交的事务; 确保生效已经在 Leader 提交的事务,且最终会被所有服务器提交
流程: 先广播写文件,等待ack过半,再commit同步到内存(到内存才算生效)。
- Leader将请求封装成一个事务Proposal,将其发送给所有Follwer;写自身的数据文件;并给自己发ack;
- 各个Follwer,写本地数据文件; 返回ack给Leader;
- Leader收到一半以上的ack,给Follwer发送commit; 发送消息让observer存储消息;自身commit写自身的内存数据;
- 回发节点数据变动通知给客户端,触发客户端的监听事件;返回客户端命令操作结果;
- 各自的Follwer和observer,commit数据,写内存数据。 细节:
- Leader 在收到客户端请求之后,会将这个请求封装成一个事务,并给这个事务分配一个全局递增的唯一ID,称为事务 ID(ZXID),ZAB 协议需要保证事务的顺序,因此必须将每一个事务按照 ZXID 进行先后排序然后处理,主要通过消息队列实现。
- 在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,解除同步阻塞。
- zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是 Leader 服务器接受写请求,即使是 Follower 服务器 接受到客户端的写请求,也会转发到 Leader 服务器进行处理,Follower只能处理读请求。
- ZAB协议规定了如果一个事务在一台机器上被处理(commit)成功,那么应该在所有的机器上都被处理成功,哪怕机器出现故障崩溃。
# Leader选举流程
- 第一轮都是观望looking,给自己投票。
- 第二轮都给接受到的票数中,给最大的myid,zxid投票。(优先选择zxid大的,代表数据最新。zxid一样就选myid大的为leader)
- 超过半数票的节点,为leader。
- 通知其他节点leader节点的情况,并停止选举,变成leader。
- 新进来的节点发现leader有了,也直接变成follower。
# ES(1次)
# 什么是倒排索引?
索引: 从ID到内容。
倒排索引: 从内容到ID。好处: 比较适合做关键字检索。 可以控制索引数据的总量。 提高查询效率。
文章 -》 term ->排序 term dictionary -> term index -》 Posting List -> [文章 ID ,[在文章中出现的偏移量],权重 ]TFIDF
将文章分词,对分词排序,创建索引,索引内容有(文章id、文章的偏移量、权重)。
# 一些使用细节
DSL语句、数据结构模型怎么使用。
# 安全验证(0次)
# 加密算法有哪些?
对称加密算法(一个秘钥,更快): DES、TDEA、AES
非对称加密算法(私钥和公钥,更安全): RSA、ECC
混合加密 : 对称+非对称加密 https中的ssl
不可逆加密: md5
# 什么是认证和授权?如何设计一个权限认证框架?
认证: 就是对系统访问者的身份进行确认。 用户名密码登录、 二维码登录、手机短信登录。
授权: 就是对系统访问者的行为进行控制。后台接口访问权限、前台控件的访问权限。
RBAC模型: 主体 -》 角色 -》 资源 -》访问系统的行为
shiro 框架和 spring security
# Cookie和Session有什么区别、联系?
Cookie会有一个sessionId,一个在客户端,一个在服务端。
如果没有客户端的Cookie,Session是无法进行身份验证的。
# CSRF攻击和防范
CSRF: Cross Site Requst Forgery 跨站请求伪造。
一个正常的请求会将合法用户的session id保存到浏览器的cookie,其他的tab页也会共享cookie。从而再钓鱼网站做侵入。
防范手段: 尽量使用POST请求、将cookie设置为HttpOnly、增加token (Spring Security)
# OAuth2.0协议,认证方式,JWT令牌,普通令牌
OAuth2.0是一个开放标准,允许用户授权第三方应用程序访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。 从而保证用户的第三方账号密码不被泄密。
四种认证: 授权码模式(微信code码模式)、简化模式(减少一个code的申请)
| 授权码模式 | 简化模式 | 密码模式 | 客户端模式 | |
|---|---|---|---|---|
| 流程 | 发起 -> 用户同意 -> 返回code码 -> 用code申请 -> 返回令牌 | 发起 -> 用户同意 -> 返回令牌 | 利用账号密码 -> 申请 -> 返回令牌 | 申请 -> 放回令牌 |
| 应用场景 | 微信code码模式 | 支付宝 | 公司内部系统(SSO单点登录) | 内网安全系统 |
| 优缺点 | 安全等级高,接入流程麻烦 | 安全等级较高,接入简单 | 安全等级一般,接入简单 | 安全等级低,接入简单 |
普通令牌只是一个随机的字符串,没有特殊的意义。
JWT令牌的本质就是一个加密的字符串,里面包含了设置的信息,客户端可以自己解密获得。
# SSO和OAuth2.0异同
| SSO单点登录 | OAuth2.0 联合登录 | |
|---|---|---|
| 应用场景 | 一处登录,多处同时登录 | 一处注册,多处使用 |
| 关键实现 | 将Session集中存储,或者一个公共数据的认证系统 | 向第三方申请令牌,以获得用户相关信息。 |
| 重点 | 重复登录问题 | 数据共享和账号安全问题。 |
他们可以混合实现和使用,不是对立的。
# 如何设计一个开放授权平台
开放授权平台也可以按照认证和授权两个方向来梳理。
分别为APP预留接口供小程序使用和统一门户
# APP预留接口
1、认证: 就可以按照OAuth2.0协议来规划认证的过程。
2、授权: token使用再某些情况下可以回调APP的接口, 如果用户统一,就返回令牌,再返回具体用户信息给第三方。 如微信再小程序中获得手机号。流程: 进入小程序情况下,利用公钥/appid,调用获得手机号接口,微信显示同意和拒绝授权,用户同意后返回code码,再利用code码调用真实的API返回手机号相关信息。
3、 缺点: 请求就把真实信息同步返回了,所以为了安全起见是需要有个推送地址的,只把数据发送到这个地址。 还有请求和响应推送内容加密。
# 统一门户
1、认证: 就可以按照OAuth2.0协议来规划认证的过程。
2、授权: 首先需要待接入的第三方应用在开放授权平台进行注册,注册需要提供几个必要的信息 clintID, 消息推送地址,2对密钥(一对公私钥,私钥由授权平台自己保存,公钥分发给第三方应用)。
然后,第三方应用引导客户发起请求时,采用公钥进行参数加密,授权开放平台使用对应的私钥解密。
接下来:授权开放平台同步响应第三方应用的只是消息是否处理成功的结果。而 真正的业务数据由授权开放平台异步推送且用另一对秘钥加密解密给第三方应用预留的推送地址。
3、缺点: 流程更加麻烦
# 项目
# 线上中间件规划
一条接口消息10KB,3000左右的QPS。 一天2.56亿次接口调用,大概产生 2.5TB。 存7天数据,副本为2
Kafka : 5台专机专用,每台16核32G,每台7.5T磁盘 NAS,60MB的带宽。 集群 , 一天2.5TB,2.56亿条数据。
mogodb : 6台专机专用,每台16核32G,每台10T磁盘 NAS , 50MB带宽。 (2个分片都做了,一主二从,一个从做了延迟数据处理,另一个从做了olap统计查询标签处理) 一天4千万数据左右
redis : 3台专机专用, 每台8核16G,每台就1TNAS,主的80MB带宽,其他副的弹性带宽。 哨兵
如: 存7天数据,副本为2,平均一条消息10KB,3000左右的QPS. 一秒 30MB 左右,一天大概产生 2.5TB。
# 用G1可以设置GC最大停顿时间,给操作系统的缓存留出几个G
‐Xmx25G ‐Xms25G ‐Xmn20G ‐XX:MetaspaceSize=256M ‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=50 ‐XX:G1He
apRegionSize=40M
2
3
# 其他问题
# ASM
在大3上半学期的时候参加了老师和学长带队的ACM预选比赛,但是没有获得排名。 3个人,1台电脑,5个小时,要答7道题,错了比较多时间扣没了。 收获: 更加注重方案的质量、不断寻求最优解决方案、注重代码的边界条件、深度思考问题的能力。
# 场景设计类
如: 你会如何设计一个MQ?
两个误区: 1、 放飞自我,漫无边际。 2、纠结技术细节。
好的方式: 1、 从整体到细节 2、从业务场景到技术实现。 3、以现有产品Kafka为基础
答题思路 : MQ作用、项目大概的样子、核心技术点、再一些具体功能点的实现。
# 软技能,学习和修炼; 职业规划是什么?
学习能力: 编码和阅读源码,对技术保有积极的渴望,只是一个技术人的基本素质。
要提高个人在团队影响力(领导力):推动业务的影响力,用技术解决实际问题,推动一些有利于团队的正能量事情(比如:开站会同步信息、不做加班秀、内卷同事)。
招人减人的合理理由,管理的逻辑和团队构架的能力。
保持好奇心: 提高发现问题能力,比解决问题能力,更好,QA质量监管扼杀再摇篮中,想更好的解决问题方案。
团队的动手能力 : 协同增效、为业务和技术搭建沟通的桥梁、碰撞更多的方案和实现。
目标: 指挥选型、管理带领团队冲锋、解决核心问题、做好承上启下的作用(跨部门沟通,方案定稿沟通n次)、发扬企业文化
技术人的成长之路
技术(业务)、商业、市场。 技术和业务是分不开的,技术包括业务。 但是业务不包括、商业和市场。
技术专家: 架构师、研究员、科学家。 只关注技术和业务。
产品专家: 产品运营和规划。 (pass,不能说,可能自己)
项目管理专家: 包工头,解决问题,完成产品的迭代周期。为结果而战。
技术管理专家: 统领技术线的leader, 技术和管理平衡,人和事的平衡, 技术总监、技术经理、CTO、技术VP。
综合管理专家: CEO,对结果、公司收入、股东、员工等等负责。
#
技术: 不能停留在基础,微服务的原理。 项目架构层面: 应该有些架构思想,好多是交付之后部署的问题。 沟通: 有些不该说。简要直接
微服务组件: spring-cloud-kubernetes、