2020915知识图谱
2020-12-01 14:44:41 3 举报
AI智能生成
掌握这个图谱所有知识,算是个高级开发工程师了。达到阿里p6,b站2-1/2-2类似的级别。月薪大概18-24左右,年薪30左右
作者其他创作
大纲/内容
0基础知识
jvm
jvm内存
按照不同角度来看
运行时内存
堆
栈
栈帧
程序计数器(PC)
本地方法栈
元空间
class、常量池等
jvm内存模型
工作内存
主内存
gc分代
堆内存进行分代
溢出
堆和元空间都可能发生oom
-Xms
-Xmx
-MaxMetaspaceSize
-Xmx
-MaxMetaspaceSize
gc
GC基础
这些GC知识正在被渐渐地淘汰
这些GC知识正在被渐渐地淘汰
1、如何判断对象是否该被回收
1、引用计数器
2、根节点
判断一个对象是否跟gcroot相连接
判断一个对象是否跟gcroot相连接
2、不同的引用类型对gc的影响
强
软
弱
虚
3、堆内存的划分
按照对象的生存周期,划分为新生代、老年代。
是先划分代,然后根据代的特点选择合适的gc算法
是先划分代,然后根据代的特点选择合适的gc算法
4、gc算法
根据不同区域对象生命周期的特点,采用不同的回收算法
根据不同区域对象生命周期的特点,采用不同的回收算法
新生代
特点:98%的对象是朝生夕死的
特点:98%的对象是朝生夕死的
复制算法
原理:将内存分为两部分,先使用第一部分。gc时,将存活的对象复制到第二部分。但是造成内存的浪费
做法:将新生代内存划分为eden、两块survivor。每次只使用eden和一块survivor。gc时,将这两部分存活的对象复制到另外一块survivor
原理:将内存分为两部分,先使用第一部分。gc时,将存活的对象复制到第二部分。但是造成内存的浪费
做法:将新生代内存划分为eden、两块survivor。每次只使用eden和一块survivor。gc时,将这两部分存活的对象复制到另外一块survivor
如果另外一块survivor容量不足以放下所有的存活对象,那么将通过分配担保策略,将其放到老年代中
如果存活的对象比较多,那么每次就要复制很多对象,导致性能变差
老年代
特点:对象的生命周期比较长
不使用复制算法的原因:
1、没有其它内存为他做分配担保了,只能节省内存
2、即使有内存为他分配担保,因为存活的兑现多,那么每次就要复制大量对象,导致性能变差
特点:对象的生命周期比较长
不使用复制算法的原因:
1、没有其它内存为他做分配担保了,只能节省内存
2、即使有内存为他分配担保,因为存活的兑现多,那么每次就要复制大量对象,导致性能变差
标记-清除:
直接标记内存上需要被回收的对象,然后清理掉
直接标记内存上需要被回收的对象,然后清理掉
存在内存碎片,导致下次分配对象时,没有足够用的内存,又触发一次垃圾回收
标记-整理:
将所有存活的对象向内存一端移动,然后清理掉空出来部分
将所有存活的对象向内存一端移动,然后清理掉空出来部分
G1
设计目标:
在大内存、多处理器的服务器上,带来低停顿,保持高吞吐
在大内存、多处理器的服务器上,带来低停顿,保持高吞吐
内存划分:
1、把堆内存划分为区,每个区叫做eden、survivor、old、Humongous Region
1、把堆内存划分为区,每个区叫做eden、survivor、old、Humongous Region
youngGC和mixedGC
1、g1是STW的
2、youngGC只回收全部新生代。mixedGC回收全部新生代,和垃圾比较多的老年代
3、最坏情况可能触发fullGC,对整个堆。单线程收集
2、youngGC只回收全部新生代。mixedGC回收全部新生代,和垃圾比较多的老年代
3、最坏情况可能触发fullGC,对整个堆。单线程收集
触发条件
当新生代满,触发youngGC。
当老年代使用达到阈值,触发收集
当老年代使用达到阈值,触发收集
使用注意
jdk8中使用-XX:+UseG1GC启用。jdk9中默认g1
指定停顿时间,g1会自动减小扩大新生代大小。
不要指定(-XX:GCTimeRatio)新生代大小,这样暂停时间(-XX:MaxGCPauseMillis)就不起作用了
不要指定(-XX:GCTimeRatio)新生代大小,这样暂停时间(-XX:MaxGCPauseMillis)就不起作用了
g1自动调整堆大小
ZGC
不分代
停顿时间保持在10ms
所有阶段都是并发
class文件结构
类加载
加载类
通过磁盘、网络加载class,并转换为结构。
1、AppClassLoader借助于URLClassPath来获取资源
2、有多个相同类,只加载第一个
1、AppClassLoader借助于URLClassPath来获取资源
2、有多个相同类,只加载第一个
三个类加载器
AppClassLoader
ExtClassLoade
BootStrapClassLOader
加载的时机
默认是在运行期,当遇到new、调用类的静态方法、反射
通过jvm参数(-verbose)(-XX:+TraceClassLoading),能够看到在运行时,按需加载类的,而不是事先全加载
那么为啥slf4j注释说,在编译时绑定
验证
验证是否符合class文件接口、安全校验
准备
为类变量(被static修饰)分配零值
解析
将常量池中的符号解析为直接引用
初始化
为类变量赋值(我们指定的值),其实是执行clinit方法,收集了类变量和static中的语句
java探针
实现ClassFileTransformer,在启动jar时,指定启动参数-javaagent:
在类加载阶段对class进行处理
在类加载阶段对class进行处理
字节码执行引擎
1、基于栈的栈帧结构
局部变量表
方法的入参、和局部变量
操作数栈
操作数栈,顾名思义就是被操作的数字。不过这个‘数字’代表着任意的java类型。
它与字节码指令序列严格匹配
它与字节码指令序列严格匹配
动态链接
方法动态调用
方法返回地址
与方法调用有关。
‘返回地址’顾名思义,就是被调用方法正常返回后,应该返回到哪里。
‘哪里’一般使用pc计数器(就是程序计数器)来表示,表示调用者的pc计数器
‘返回地址’顾名思义,就是被调用方法正常返回后,应该返回到哪里。
‘哪里’一般使用pc计数器(就是程序计数器)来表示,表示调用者的pc计数器
2、方法调用
1、解析
2、分派(选择方法,而不是执行方法)
静态分派
发生在编译阶段,也就是在编译期就确定了方法版本。
根据变量的左侧类型(静态类型)来判断调用哪个方法,比如重载。为什么要这样做??
根据变量的左侧类型(静态类型)来判断调用哪个方法,比如重载。为什么要这样做??
动态分派
重写
静态分派根据左侧变量类型来判断,但是在方法实际调用时,还是要找到是实际类型
动态语言:运行时确定实际的变量类型。而静态语言在编译期就确定了类型
3、方法执行
基于栈和操作数栈的执行方式。
问题:如何把元空间中方法字节码拿出来执行?
问题:如何把元空间中方法字节码拿出来执行?
程序编译优化
try...catch...finally如何返回
回忆异常表。
记住以最后一个return为准
记住以最后一个return为准
对jvm进行调优
针对gc优化
运行期优化
逃逸分析
子主题
对象的内存布局
java工作内存模型
分为工作内存,主内存。但是书中未说明这些内存
并发
volitate
语义
1、可见性:
一个线程修改的值,另一个线程立马能够得知。
从字节码层面讲,要求read、load、user,和assign、store、write的顺序,且为原子性(要么都执行,要么都不执行),但是中间可以插入其它指令
从机器指令上讲,添加了lock指令,要求cpu将它的cache写入到内存(对应于store、write),同时其它cpu对这个值的cache无效
2、指令重排序(指的是机器层面的重排序)
一个线程修改的值,另一个线程立马能够得知。
从字节码层面讲,要求read、load、user,和assign、store、write的顺序,且为原子性(要么都执行,要么都不执行),但是中间可以插入其它指令
从机器指令上讲,添加了lock指令,要求cpu将它的cache写入到内存(对应于store、write),同时其它cpu对这个值的cache无效
2、指令重排序(指的是机器层面的重排序)
用法用途
happens-before
子主题
synchronized(可重入)
语义
1、可见性 2、原子性
分类
偏向锁(乐观锁)
轻量级锁(乐观锁)
重量级锁(悲观锁)
原理
对象的对象头中,保存了锁类别标识,和几个指针。
当锁类别为重量级锁时,指针指向一个monitor(管程),它借助操作系统的互斥量实现同步。
当锁类别为轻量级锁时,指针指向栈帧的一个锁记录。
当锁类别为偏向锁时,记录了持有锁的线程id
当锁类别为重量级锁时,指针指向一个monitor(管程),它借助操作系统的互斥量实现同步。
当锁类别为轻量级锁时,指针指向栈帧的一个锁记录。
当锁类别为偏向锁时,记录了持有锁的线程id
优化
自旋
如果获取不到就空转一会
偏向
第一次是A线程,在对象头里设置个线程id。这次比较一下,如果是就不加锁了
锁粗化
自动扩大锁的范围
锁消除
判断是否真正需要加锁。否则就把锁去掉
队列
阻塞队列
延迟队列
高级线程同步容器
线程池ThreadPoolExecutors
使用场景
保护资源
异步,前端体验
并发,提高效率
4个默认的线程池
newFixedThreadPool
newCachedThreadPool
newSingleThreadExecutor
newScheduledThreadPool
阻塞队列
ArrayBlockingQueue
LinkedBlockingQueue
DelayedWorkQueue
PriorityBlockingQueue
SynchronousQueue
4种拒绝策略
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy
原理
先说execute方法原理
①:判断当前活跃线程数是否小于corePoolSize,如果小于,则调用addWorker创建线程执行任务
②:如果不小于corePoolSize,则将任务添加到workQueue队列。
③:如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。
②:如果不小于corePoolSize,则将任务添加到workQueue队列。
③:如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。
内部使用worker包装了我们提交的runable。当前任务执行完毕,继续去阻塞队列中取任务执行
submit
会返回一个结果,是使用featureTask包装了我们的runabe
scheduleThreadPoolExecutor
主要是用DelayedWorkQueue实现。
比如我们设置核心线程数为1。先放一个延时30s的任务,再放一个延时1s的任务。那么这个核心线程会先取到30的,然后休眠30s,
才可以取出来。但是此时放进来一个1s(会把它放到队列第一位),会唤醒该线程,在循环中重新去取。
这次进取到了1s的。并且休眠1s后,取出来,去执行了
比如我们设置核心线程数为1。先放一个延时30s的任务,再放一个延时1s的任务。那么这个核心线程会先取到30的,然后休眠30s,
才可以取出来。但是此时放进来一个1s(会把它放到队列第一位),会唤醒该线程,在循环中重新去取。
这次进取到了1s的。并且休眠1s后,取出来,去执行了
x线程池是线程安全的。里面的共享变量通过cas修改的
最佳实践
阻塞队列设置合理容量
异常捕获
在我们的任务里使用tr...catch
自定义threadFactory,为线程设置uncatchExceptionHandler
使用submit提交任务,返回feature。使用feature.get来获取结果,或者获取异常
自定义子类,继承threadPoolExecutor,重写afterExecute方法。会传进来异常信息的
不同的场景使用不同的线程池,可以做到更合适,资源隔离
如何设置线程数
线程屏障CyclicBarrier
主要借助重入锁ReentrantLock和Condition实现
信号量Semaphore
控制有几个线程可以同时运行
问题
虚假唤醒
wait外面加while
锁
lock接口
ReentrantLock
特点
可重入
获取锁可超时取消、中断
Condition支持wait、notify操作
可实现公平锁、非公平锁
源码
1、静态内部类Sync继承AQS
静态内部类NonfairSync继承Sync
静态内部类FairSync继承Sync
静态内部类NonfairSync继承Sync
静态内部类FairSync继承Sync
2、以NonfairSync的lock为例,
reentrantLock源码
ReadWriteLock接口
ReentrantReadWriteLock
AQS
CLH队列
aqs作为jdk lock框架中一个比较底层的父类,主要负责定义了获取锁、释放锁、获取锁状态的流程框架。内部主要是实现了CLH队列的变种算法
clh的大致原理是,把每个竞争锁的线程封装为node节点。node节点有三个重要属性,next指针、pre指针、和它的状态。当前节点获取到锁以后,会根据next指针找到下一个节点,判断其状态,来唤醒它,去竞争锁。
然后获取锁、释放锁基本上就是针对这个clh队列做操作了。其中,还用到一个全局变量,stat。用它来记录当前锁状态,和重入次数
io
几个概念
1、几个概念
阻塞、非阻塞
同步、异步
2、组合
(非)阻塞是指线程是否被阻塞,(异)同步是指cpu是否始终参与io
同步阻塞:bio
同步非阻塞:nio
异步非阻塞:aio
bio使用read、write函数
阻塞、非阻塞
同步、异步
2、组合
(非)阻塞是指线程是否被阻塞,(异)同步是指cpu是否始终参与io
同步阻塞:bio
同步非阻塞:nio
异步非阻塞:aio
bio使用read、write函数
bio:同步阻塞,面向字节
bio
面向字节的stream
面向字符的reader、writer
nio
几个关键类
buffer
selector
channel
零拷贝
map
sendfile
netty简介
aio(nio2)
面试怎么说
零拷贝
加入一些数据从A channel拷贝到B channel,如果使用transfer,就避免从内核态拷贝到用户态。再从用户态拷贝到内核态
办法
map、sendfile两个底层方法
集合框架
threadlocal
collection
list
arraylist
set
treeset
借助TreeMap实现?
queue
优先级队列
最大(小)堆
根据比较器,构造一个最大堆或最小堆,root节点最大或最小。
每次删除root,或添加元素,需要上浮下浮该元素,以保持堆性质
每次删除root,或添加元素,需要上浮下浮该元素,以保持堆性质
延时队列
借助优先级队列排序,是一个最小堆。然后take方法死循环去取,循环体内休眠指定时间(root节点的值),再去尝试获取
阻塞队列
单纯的阻塞队列,队列满了,放元素就阻塞。队列空了,去元素就阻塞。take、pull、offer等
map
hashmap
put方法过程:
1、发现未初始化,resize初始化
2、发现槽位为空,则放进去
3、如果不为空,则顺着链表向下查找,直到找到next为null插入。
4、插入后,如果链表大于等于8,且容量达到64以上,就转换为红黑树(时间复杂度log n)。否则继续扩容。
5、判断当前元素值是否大于等于阈值,如果是则扩容
扩容过程:
1、发现未初始化,resize初始化
2、发现槽位为空,则放进去
3、如果不为空,则顺着链表向下查找,直到找到next为null插入。
4、插入后,如果链表大于等于8,且容量达到64以上,就转换为红黑树(时间复杂度log n)。否则继续扩容。
5、判断当前元素值是否大于等于阈值,如果是则扩容
扩容过程:
concurrentHashMap
put方法过程:
1、判断是否初始化:cas初始化
2、判断槽位是否为空,如果是则cas插入
3、如果不为空,或者插入失败,进行下次循环:如果不为空,则对该槽位加锁,然后遍历链表直到插入。
4、如果插入后,链表大于等于8,或者大于等于64,则转换为红黑树,否则扩容。
5、如果需要扩容,就去扩容
扩容过程https://www.jianshu.com/p/f6730d5784ad
1、判断是否初始化:cas初始化
2、判断槽位是否为空,如果是则cas插入
3、如果不为空,或者插入失败,进行下次循环:如果不为空,则对该槽位加锁,然后遍历链表直到插入。
4、如果插入后,链表大于等于8,或者大于等于64,则转换为红黑树,否则扩容。
5、如果需要扩容,就去扩容
扩容过程https://www.jianshu.com/p/f6730d5784ad
treeMap
treemap是一颗红黑树实现。它与hashmap同实现map接口。非线程安全
put流程:
1、从头节点开始,根据默认比较器,选择将新节点放在parent的左边还是右边。
如果默认比较器为空,那么需要key实现Comparable接口,据此来排序。
2、插入完成后,会平衡红黑树
put流程:
1、从头节点开始,根据默认比较器,选择将新节点放在parent的左边还是右边。
如果默认比较器为空,那么需要key实现Comparable接口,据此来排序。
2、插入完成后,会平衡红黑树
LinkedHashMap
顺序为插入的顺序:
存储、查找借助HashMap。但是维护了插入节点的after、before。遍历时就有序了
lru:
删除最不常用的数据
linkedHashMap头节点为最不常用的,尾结点为最近使用的。
hashmap在put方法中,会调用子类linkedHashMap的afterNodeInsertion方法,该方法内部调用removeEldestEntry判断删除策略。
我们重写removeEldestEntry即可。
注意:我们的类继承它,构造器中要指定true参数
存储、查找借助HashMap。但是维护了插入节点的after、before。遍历时就有序了
lru:
删除最不常用的数据
linkedHashMap头节点为最不常用的,尾结点为最近使用的。
hashmap在put方法中,会调用子类linkedHashMap的afterNodeInsertion方法,该方法内部调用removeEldestEntry判断删除策略。
我们重写removeEldestEntry即可。
注意:我们的类继承它,构造器中要指定true参数
网络
5层模型
应用层
运输层
网络层
数据链路层
物理层
tcp
三次握手
四次挥手
问题
http的keeyalive是针对tcp而言来设置的。
http请求完毕,必须响应。那么同一个客户端发起下一次http请求,复用的还是同样的tcp连接。
与dwr区别:dwr是抓住这个http不放。一旦有响应就返回,客户端立马再发起http请求。这样是没有复用tcp的
tomcat是有keepalive-timeout的限制,超时后会关闭这个tcp。
与tcp中keeplive区别是,这里用于检测对方是否还在线https://blog.csdn.net/weixin_37672169/article/details/80283935
http请求完毕,必须响应。那么同一个客户端发起下一次http请求,复用的还是同样的tcp连接。
与dwr区别:dwr是抓住这个http不放。一旦有响应就返回,客户端立马再发起http请求。这样是没有复用tcp的
tomcat是有keepalive-timeout的限制,超时后会关闭这个tcp。
与tcp中keeplive区别是,这里用于检测对方是否还在线https://blog.csdn.net/weixin_37672169/article/details/80283935
TCP 协议如何保证可靠传输
校验和checksum
每一个包分组的首部有checksum字段,来确认数据是否发生变化
流量控制
拥塞控制
慢开始
ARQ协议
收到确认机制
超时重传
当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段
udp
http
序列化
作用
把一个对象的状态保存起来,发送到另外的机器,能够恢复出来使用
框架
json
xml
java序列化:二进制
缺点:慢
hession:二进制
java序列化的原理
序列化
1、获取序列化对象的元信息,class、包名、序列化版本等
2、获取状态,对于java对象也就是全局作用域的字段值。会保存值和值的类型
3、按照定义好的二进制格式写入到字节数组
反序列化
1、解析字节数组,获取class并new一个实例出来。获取字段和值
2、利用unsafe类,直接利用字段内存偏移量,把值设进去
在二进制中,8种基本类型使用简短的字母来表示
Z:boolean
B:byte
C:char
S:short
I:int
F:float
J:long
D:double
Z:boolean
B:byte
C:char
S:short
I:int
F:float
J:long
D:double
自己实现一个序列化
方法1:
继承ObjectOutputStream、ObjectInputStream,覆写writeObjectOverride、readObjectOverride方法;
然后编写一个空参构造,调用super()。因为ObjectOutputStream的空参构造会指定参数,写出对象时,使用writeObjectOverride方法
继承ObjectOutputStream、ObjectInputStream,覆写writeObjectOverride、readObjectOverride方法;
然后编写一个空参构造,调用super()。因为ObjectOutputStream的空参构造会指定参数,写出对象时,使用writeObjectOverride方法
方法2:
自己从头实现
自己从头实现
注意点
效率:序列化后的数据尽可能少,优化网络传输
安全:
通用(跨平台):
效率:序列化后的数据尽可能少,优化网络传输
安全:
通用(跨平台):
string的序列化
1中间件
mysql
基础概念
引擎区别
innoDB
支持事务
支持行级锁(默认),表锁
支持崩溃后恢复
支持MVCC
MyISAM
不支持事物
仅支持表锁
不支持崩溃后恢复
记录表的行数
字符集、字符集校验规则
字符集就是把二进制转换为文字的规则
字符集校验规则指明如何排序
MySQL采用的是类似继承的方式指定字符集的默认值,每个数据库以及每张数据表都有自己的默认值,他们逐层继承。
比如:某个库中所有表的默认字符集将是该数据库所指定的字符集(这些表在没有指定字符集的情况下,才会采用默认字符集)
比如:某个库中所有表的默认字符集将是该数据库所指定的字符集(这些表在没有指定字符集的情况下,才会采用默认字符集)
数据类型优化
1、更小的通常更好
2、简单的更好
3、避免null
4、设置必要的限制条件,比如非null、整数无符号、
引擎
innoDb
索引
通用优缺点
优点
查找速度快
提前缓存数据
顺序磁盘io
避免排序
缺点
插入、更新、删除速度慢
以b+树索引为例,它是一颗平衡树,因为这些操作会破坏平衡,需要重新平衡,如果是聚簇索引,涉及到数据行的移动等
以b+树索引为例,它是一颗平衡树,因为这些操作会破坏平衡,需要重新平衡,如果是聚簇索引,涉及到数据行的移动等
占用磁盘或内存
explain
type字段:
index、range、ref、const、null,从左到右性能更好。
index、range、ref、const、null,从左到右性能更好。
类型
b+
针对innodb而言
针对innodb而言
基本结构:
1、扇区组成磁盘块。扇区是真实存在的,块是逻辑概念。mysql中,4个块组成页。一个块为4096B(字节)大小,一页大小16KB。
2、页是mysql基本存储结构。页与页之间可以组成双向链表。如果是聚簇索引,页中存储数据行,数据行之间形成单向链表
3、b+的结构见上图。非叶子节点存储了索引的值,如果要查找的值小于该值,就在他左边,否则在右边(二分法)
4、找到最后的叶子节点,如何找到数据,见‘数据组织方式’
1、扇区组成磁盘块。扇区是真实存在的,块是逻辑概念。mysql中,4个块组成页。一个块为4096B(字节)大小,一页大小16KB。
2、页是mysql基本存储结构。页与页之间可以组成双向链表。如果是聚簇索引,页中存储数据行,数据行之间形成单向链表
3、b+的结构见上图。非叶子节点存储了索引的值,如果要查找的值小于该值,就在他左边,否则在右边(二分法)
4、找到最后的叶子节点,如何找到数据,见‘数据组织方式’
数据组织方式
在innodb中,表就是聚簇索引
在innodb中,表就是聚簇索引
聚簇索引
innodb一定有聚簇索引,索引列的值和数据行都保存在叶子节点
innodb一定有聚簇索引,索引列的值和数据行都保存在叶子节点
注意:
叶子节点指向页,而不是一个具体的数据行。假如这一页数据主键id是1-100,是一个单链表。
那么把这些数据行分组,抽出来一个数组[1,20,40,60,80,100]。然后采用二分法查找
叶子节点指向页,而不是一个具体的数据行。假如这一页数据主键id是1-100,是一个单链表。
那么把这些数据行分组,抽出来一个数组[1,20,40,60,80,100]。然后采用二分法查找
二级索引(非聚簇索引、辅助索引)
叶子节点只保存索引列的值和主键值,通过索引列找到叶子节点后,再取出主键值,去聚簇索引查询。这叫做回表
叶子节点只保存索引列的值和主键值,通过索引列找到叶子节点后,再取出主键值,去聚簇索引查询。这叫做回表
覆盖索引
sql查询语句中查询的列,已经被包含在了索引列中,所以无需去查找数据行或回表
sql查询语句中查询的列,已经被包含在了索引列中,所以无需去查找数据行或回表
其他点
与红黑树区别
红黑树一个节点只存储一个值,而b+树存储多个值,会缓存这一页的数据,根据局部性原理,他们有可能会被用到
页分裂
因为b+有顺序。如果索引列的值无序,可能会造成页分裂。
即使序列值有序,可能造成插入的地方锁竞争严重
我怀疑一页存储的数据多,浪费内存,如果一页存储的数据少,那么会导致树变高。可能需要多次io了
hash
innodb不支持显式创建hash索引
innodb不支持显式创建hash索引
计算出每一行的索引列的hash值,并保存这一行的数据指针,时间复杂度为O(1)。
hash索引结构
缺点
无法利用索引排序
对于多列索引。不能只利用其中一列索引
不支持范围查询了
如果hash冲突比较大,需要去遍历这个槽位的链表
最左匹配原则
要按照索引定义的列顺序来匹配
比如有索引(a,b,c,d),那么a = 1 and b = 2 and c > 3 and d = 4,则会在每个节点依次命中a、b、c,无法命中d。(很简单:索引命中只能是相等的情况,不能是范围匹配)
如有索引(a, b, c, d),查询条件c > 3 and b = 2 and a = 1 and d < 4与a = 1 and c > 3 and b = 2 and d < 4等顺序都是可以的,MySQL会自动优化为a = 1 and b = 2 and c > 3 and d < 4,依次命中a、b、c。换句话说,会自动按照索引列顺序排列where条件
事物、隔离级别、锁
隔离级别其实讲的是有两个及以上的事物操作(增删改查)同一个范围内的数据时,产生的不同现象。针对这些现象,总结出四种隔离级别。
先说几种现象,分别是脏读、不可重复读、幻读。
然后四种隔离级别就是
读未提交:a事物读到了b事物尚未提交的内容,加入b又回滚了,或者b插入的数据有关联性,那么a就读到错误的数据了,脏数据。
读已提交:a只能读到b提交的事物。但是b在a事物中间多次修改,删除、插入。那么a事物多次读到的内容就不一致了。
可重复读:a多次读的内容是一直的。但数据行可能增多减少。
串行:
先说几种现象,分别是脏读、不可重复读、幻读。
然后四种隔离级别就是
读未提交:a事物读到了b事物尚未提交的内容,加入b又回滚了,或者b插入的数据有关联性,那么a就读到错误的数据了,脏数据。
读已提交:a只能读到b提交的事物。但是b在a事物中间多次修改,删除、插入。那么a事物多次读到的内容就不一致了。
可重复读:a多次读的内容是一直的。但数据行可能增多减少。
串行:
读未提交:一个事务可以读取到,另外一个事务尚未提交的变更
读已提交:一个事务提交后,其变更才会被另一个事务读取到。
可重复读:在一个事务执行的过程中所读取到的数据,和事务启动时所看到的一致。
串行
https://blog.csdn.net/weixin_43784914/article/details/99694860
谈谈隔离级别
其实隔离级别讲的是并发的事情,有多个线程同时对数据库同一范围的行进行增删改查。同时呢,每个线程希望自己的一系列操作符合事务acid的特性。所以就有了隔离级别的说法。它本质上是利用数据库的锁和行版本来控制并发(其实为多版本并发控制MVCC)。
如果不控制并发,那么数据就可能乱掉,我们总结出四种现象:脏读、不可重复读、幻读。
然后又总结出四种不同严格程度的并发控制,分别是读未提交、读已提交、可重复读、串行化。
其实隔离级别讲的是并发的事情,有多个线程同时对数据库同一范围的行进行增删改查。同时呢,每个线程希望自己的一系列操作符合事务acid的特性。所以就有了隔离级别的说法。它本质上是利用数据库的锁和行版本来控制并发(其实为多版本并发控制MVCC)。
如果不控制并发,那么数据就可能乱掉,我们总结出四种现象:脏读、不可重复读、幻读。
然后又总结出四种不同严格程度的并发控制,分别是读未提交、读已提交、可重复读、串行化。
事务隔离级别是并发控制的整体解决方案,其实际上是综合利用各种类型的锁和行版本控制来解决并发问题。
锁是数据库并发控制的内部机制,是基础
对用户来说,只有当事务隔离级别无法解决一些并发问题和需求时,才有必要在语句中手动设置锁,不恰当的设置锁可能导致严重的阻塞和死锁。建议在完全了解锁机制的情况下,才可以再语句中手动设置锁,否则应该使用事务隔离级别。
锁是数据库并发控制的内部机制,是基础
对用户来说,只有当事务隔离级别无法解决一些并发问题和需求时,才有必要在语句中手动设置锁,不恰当的设置锁可能导致严重的阻塞和死锁。建议在完全了解锁机制的情况下,才可以再语句中手动设置锁,否则应该使用事务隔离级别。
隔离级别和锁:https://blog.csdn.net/caohao0591/article/details/79887028
MVCC:https://www.jianshu.com/p/db334404d909
DATA_TRX_ID
用来标识最近一次对本行记录做修改(insert|update)的事务的标识符, 即最后一次修改(insert|update)本行记录的事务id。
DATA_ROLL_PTR
指写入回滚段(rollback segment)的 undo log record (撤销日志记录记录)。如果一行记录被更新, 则 undo log record 包含 '重建该行记录被更新之前内容' 所必须的信息。
可重复读
加的锁是next-key锁
myISAM
memory
语法
查询任务并终止
show processlist
kill
kill
性能优化
业务优化
比如我们的定位数据,只允许查询2个月
三年以外的贴在,仅支持查询。不能回复
索引优化
根据业务,合理建立主键索引、二级索引、多列索引
sql优化
尽量符合覆盖索引、避免分页查询过多数据再删掉
避免回表
假如一个二级索引有三列,那么查询所有字段,就不能直接从该索引上查出返回,还要根据主键,去聚簇索引上查询
水平分片(借用sharding里面的概念)
垂直拆分
将宽表拆分为单表
垂直分片(借用sharding里面的概念)
读写分离
问题
分为多表、多库以后。如何保证唯一主键
redis
从单一数据库取自增序列
存在单个机器性能问题。可能有自增序列锁竞争激烈问题
每个库的表设置初始值和步长
节点是股固定的,如果新增mysql机器了,不容易扩展
雪花算法
如何保证分库分表的数据均匀,一个表数据多一个表数据少
分片框架sharding
https://shardingsphere.apache.org/document/current/cn/features/sharding/
https://www.jianshu.com/p/952108f777a3
问题
一条sql在mysql中是如何执行
mysql大体分为server和引擎两部分。
其中server包括连接器、查询缓存、分析器、优化器、执行器等。而且所有与引擎具体实现无关的东西也在这里:存储过程、触发器、视图,函数等、binlog
其中server包括连接器、查询缓存、分析器、优化器、执行器等。而且所有与引擎具体实现无关的东西也在这里:存储过程、触发器、视图,函数等、binlog
查询语句
1、经过连接器,进行权限校验
2、查询缓存,如果命中则返回。否则进入分析器,进行sql的词法分析
3、进入优化器,选择一个执行方案
4、交给执行器去执行
2、查询缓存,如果命中则返回。否则进入分析器,进行sql的词法分析
3、进入优化器,选择一个执行方案
4、交给执行器去执行
更新语句
1、查询张三这条数据
2、执行器调用引擎,把这条修改的数据写入到内存,记录redo log。此时redolog进入prepare状态。告诉执行器,我执行完了,可以提交
3、执行器收到后,记录binlog。调用引擎,将redo logo改为提交状态
2、执行器调用引擎,把这条修改的数据写入到内存,记录redo log。此时redolog进入prepare状态。告诉执行器,我执行完了,可以提交
3、执行器收到后,记录binlog。调用引擎,将redo logo改为提交状态
binlog:是mysql服务层自带的通用日历记录器,用于记录操作
redolog:是innoDb自有的日志,用它实现了崩溃后恢复
redolog:是innoDb自有的日志,用它实现了崩溃后恢复
为什么先把redolog置为prepare,在写binlog,再将redolog置为已提交呢?
假如先记录binlog,再记录redolog为已提交
假如先记录redolog为已提交,再记录binlog
假如写完binlog后,挂了,那么redolog还是prepare状态,怎么办?
判断binlog是否完整,如果是,则提交redolog
判断redolog是否完整。如果完整就提交
sql慢怎么解决
最佳实践
1、公认的sql规范
2、避开一些mysql问题
3、
高可用
spring framework
IOC
我主要讲两个点,一是ioc容器的启动流程,二是ioc的原理
我主要讲两个点,一是ioc容器的启动流程,二是ioc的原理
1、框架设计
BeanFactory为基本的容器接口。ApplicationContext是比较高级的容器是因为它继承了Resource、Event等接口,做了很多扩展
1、ioc容器启动流程,主要从refresh方法入手
(1)准备启动,设置一些状态
(2)创建DefaultListableBeanFactory,并加载bean定义
(3)bena后置处理器
(4)发布事件、监听器
(5)处理单利bean,可能发生第一次依赖注入
(6)完成ioc容器创建,清理资源
(1)准备启动,设置一些状态
(2)创建DefaultListableBeanFactory,并加载bean定义
(3)bena后置处理器
(4)发布事件、监听器
(5)处理单利bean,可能发生第一次依赖注入
(6)完成ioc容器创建,清理资源
2、ioc原理,主要从getBean方法入手
(1)尝试从单例缓存中获取,如果获取到则返回,否则尝试从父容器中获取。这里已经有父子容器的概念了,在WebApplicationContext那里体现的尤其明显
(2)如果获取不到,则先处理depend-on属性指定的bean。然后继续处理当前bean
(3)如果当前bean是单利,则去创建它的实例。创建过程或根据是否有复写方法等因素,判断是采用cglib,还是jdk反射。
(2)创建完成实例后,会发生依赖注入。注入完成后,将其缓存起来,这个bean就创建完毕。
(1)尝试从单例缓存中获取,如果获取到则返回,否则尝试从父容器中获取。这里已经有父子容器的概念了,在WebApplicationContext那里体现的尤其明显
(2)如果获取不到,则先处理depend-on属性指定的bean。然后继续处理当前bean
(3)如果当前bean是单利,则去创建它的实例。创建过程或根据是否有复写方法等因素,判断是采用cglib,还是jdk反射。
(2)创建完成实例后,会发生依赖注入。注入完成后,将其缓存起来,这个bean就创建完毕。
3、循环依赖
(1)依靠三级缓存,三个map类singletonObjects、singletonFactories、earlySingletonObjects。
(2)假设a依赖b,b依赖a。先创建a,创建a之前,会标记a为正在创建中。然后实例化出a的对象,
并把a的工厂对象也缓存到singletonFactories(其实a的工厂返回的就是上面的a对象,是同一个对象)。
(3)对a进行注入,递归去创建b。创建b的过程与a相同,只是在注入b的a属性时,
会从缓存中获取a(此时a尚未填充完毕属性;从a工厂获取a对象,并删除工厂),设置到属性上。
此时b的创建就完毕了,最后把b缓存到singletonObjects。
(4)回到创建a的过程,得到了b对象,把b设置到属性中,a的创建也完成了。最后把a放到singletonObjects。
(5)回忆吴鹏出的题,其实我们只用到一个缓存就可以了,为啥要弄出singletonFactories、earlySingletonObjects。
(1)依靠三级缓存,三个map类singletonObjects、singletonFactories、earlySingletonObjects。
(2)假设a依赖b,b依赖a。先创建a,创建a之前,会标记a为正在创建中。然后实例化出a的对象,
并把a的工厂对象也缓存到singletonFactories(其实a的工厂返回的就是上面的a对象,是同一个对象)。
(3)对a进行注入,递归去创建b。创建b的过程与a相同,只是在注入b的a属性时,
会从缓存中获取a(此时a尚未填充完毕属性;从a工厂获取a对象,并删除工厂),设置到属性上。
此时b的创建就完毕了,最后把b缓存到singletonObjects。
(4)回到创建a的过程,得到了b对象,把b设置到属性中,a的创建也完成了。最后把a放到singletonObjects。
(5)回忆吴鹏出的题,其实我们只用到一个缓存就可以了,为啥要弄出singletonFactories、earlySingletonObjects。
bean的生命周期
实例化
注入属性
实现各种接口:bean后置处理器、bean后置处理器,能设置进去bean名称、bean工厂等
applicationContextAware
environmentAware
使用
实现了distoryBean,销毁
aop
aop解决的问题
1、不要自己做重复工作
2、将功能性需求与非功能性需求分离
好处
集中处理某一关注点
很方便地添加、修改关注点
侵入性小,ze增强代码可维护性、可读性
应用
事务
缓存cacheable注解
日志监控
异常处理
aop分类
aop原理
原理就是代理模式
按动态静态
静态代理
一个类a包含另外一个类b,调用a的方法实际上调用的是b。
在外围对b方法返回值做了一些修改。
回忆马老板的代理
在外围对b方法返回值做了一些修改。
回忆马老板的代理
动态代理
jdk proxy
基于接口,所以只能对public方法进行代理
cglib
基于继承,所以不能对final修饰的类代理,不能对private的方法代理
aspactj
按织入时机
编译期织入
aspactj
运行期织入
jdk proxy
cglib
spring aop实现
对jdk-proxy和cglib的选择
aop链式调用
责任链模式
主要流程,类(需要去看看源码)
怎么用的(需要看源码)
事务
缓存
restTemplate
坑
在一个bean类中,a方法调用b方法。a没有加事务注解,而b加了。那么事务aop不会起作用。因为a调用b时,是this.b()。不是代理对象.b()。
所以:使用Context获取该类bean,再调用就可以了(为什么获取的bean就是代理对象呢?难道原本的对象没有成为bean?)
所以:使用Context获取该类bean,再调用就可以了(为什么获取的bean就是代理对象呢?难道原本的对象没有成为bean?)
事务管理
回答思路
回答思路
1、设计思路
2、三个接口
3、aop流程
spring cloud
服务治理
思考
有哪些步骤、环节和方法论
理解
治理的是什么:
我们治理的是一个SOA系统,以便让他正常工作,比如高可用,熔断降级等。
就像治理国际一样,让他高效、安全、不出问题地运转
我们治理的是一个SOA系统,以便让他正常工作,比如高可用,熔断降级等。
就像治理国际一样,让他高效、安全、不出问题地运转
springcloud是一套微服务的治理框架,整体目标是通过一个动态的管理,使整个系统对外提供良好的服务。
按照用途,cloud里面的框架可以分为几类:
第一类是信息发现,他是基础,比如eureka、consul、sleuth。
第二类是治理:比如ribbon、feign、hytrix、config
第三类是门户,比兔zuulgateway,对外起到一个统一门户,封装了内部的复杂性
按照用途,cloud里面的框架可以分为几类:
第一类是信息发现,他是基础,比如eureka、consul、sleuth。
第二类是治理:比如ribbon、feign、hytrix、config
第三类是门户,比兔zuulgateway,对外起到一个统一门户,封装了内部的复杂性
按照用途区分框架
全局唯一id:雪花算法,long类型。但可能出现全局不是递增的情况
eureka
说说eureka中储存了什么信息
二级缓存
全量信息存储在register中
如果有注册、更新状态等操作,把readwritecatch缓存中对应的key删掉。
并且readwritecatch每过n秒会主动清理所有key
并且readwritecatch每过n秒会主动清理所有key
客户端先读readonlycatch,如果读不到就去readwritecatch,再读不到,就去register
第二级缓存怎么把读写分离隔离开了?不要一级缓存也可以啊
缩短服务发现时间
调短缓存刷新时间
禁用二级缓存
调整客户端拉去注册表时间
https://blog.csdn.net/u012394095/article/details/80894140
https://blog.csdn.net/u012394095/article/details/80882684
gateway
静态路由
在yml文件中配置
代码写死
动态路由
基于erurka的动态路由,在yml中配合开启即可
流程
ribbon
根据指定注解注入
如果有三个bean,两个加上@Qualifier,并且在注入的list上也加上@Qualifier,那么只有两个会注入进去。
上面的图片中,扩展了@Qualifier注解,可以支持按条件注入不同的bean到不同的list。如何扩展@Qualifier见左侧图片
上面的图片中,扩展了@Qualifier注解,可以支持按条件注入不同的bean到不同的list。如何扩展@Qualifier见左侧图片
总结来说,就是为resttemplate添加注解,注解中根据实例id来替换为实际的ip和端口。
选择ip时,会受到负载均衡策略的影响,内置的策略有线性轮训、加权、随机。
我们可以自定义负载策略
选择ip时,会受到负载均衡策略的影响,内置的策略有线性轮训、加权、随机。
我们可以自定义负载策略
自定义负载策略
Hystrix
原理
使用命令模式包装我们的逻辑调用。可以利用线程池或者信号量隔离不同的调用
提供不同的策略,来打开断路器,返回fallback
提供了监控:dashbord
命令模式
命令:调大声音
命令接收者:电视机
调用者:遥控器
客户端:看电视的人
命令对象中,包含了命令接收着,
熔断器策略
//错误率:当出错率超过50%后熔断器启动
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
/**
* 设置在一个滚动窗口中,打开断路器的最少请求数。
比如:如果值是20,在一个窗口内(比如10秒),收到19个请求,即使这19个请求都失败了,断路器也不会打开。
*/
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "3"),
* 设置在一个滚动窗口中,打开断路器的最少请求数。
比如:如果值是20,在一个窗口内(比如10秒),收到19个请求,即使这19个请求都失败了,断路器也不会打开。
*/
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "3"),
// 熔断器工作时间,超过这个时间,先放一个请求进去,成功的话就关闭熔断,失败就再等一段时间
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
隔离策略
线程池隔离
可以通过设置组名称。相同的名称使用同一个线程池
信号量
redis
1、底层的基本数据结构
思路
redis实现了这些结构,一是能够利用对应的算法,二是动态字符串能够带来其他的优势,比如减少内存的分配次数
1、动态字符串
2、链表
3、字典
渐进rehash
hash冲突解决
4、跳跃表
5、压缩列表ziplist
6、整数列表
2、对象系统
实现思路
对象系统基于几种基本数据结构,构建了5种对象。
这五种对象包含其它字段,比如底层数据结构类型、最后被访问时间等,用于扩展和优化
每种结构可以由一种以上的基本结构来实现,在不同场景下,对同一个对象选择合适的结构,能够达到内存性能的最优
这五种对象包含其它字段,比如底层数据结构类型、最后被访问时间等,用于扩展和优化
每种结构可以由一种以上的基本结构来实现,在不同场景下,对同一个对象选择合适的结构,能够达到内存性能的最优
5种对外对象
1、字符串string
2、列表list
3、hash
4、集合set
5、有序集合sorted set
优化点
每个对象中,保存有引用计数器数值。用于内存回收
内存共享。redis在初始化是,会预先创建好0-9999的数字,用于共享,避免重复创建
对象最后一次被访问时间,用于回收策略
因为一个对象底层能够用不同的结构来实现,所以redis能够根据不同场景选择合适的结构,达到提高效率,节省内存的目的
3、数据库设计
数据存储
比如0号数据库,所有键值对存储在一个字典(map)中。这个叫做键空间
所有设置了过期时间的key,存储在另外一个map中,key指向键空间中的key。value是时间戳,到啥时候过期字典
持久化
持久化的意思是同步到硬盘的策略
持久化的意思是同步到硬盘的策略
RDB
save 900 1
save 300 10
save 60 10000
save 300 10
save 60 10000
900秒内有至少1个key发生变化,则获取此时内存数据快照
AOF
appendfsync的值
appendfsync的值
always
将buffer中内容写入aof文件,并同步到磁盘
everysec
将buffer中内容写入aof文件。如果距离上次同步磁盘过去了1s,则再次同步
no
将buffer中内容写入aof文件,但是不同步。由操作系统来决定
混合持久化
先使用rdb把全量数据写到aof文件,然后再增量写入aof命令。
写的文件还是aof文件,它的前部分是rdb格式,后部分是aof格式。
写的文件还是aof文件,它的前部分是rdb格式,后部分是aof格式。
问题
redis能否保证100%不丢失数据
不能
不能
redis服务器进程就是一个事件循环。一进来会把缓冲区内容写到磁盘。
然后开始处理文件时间、时间事件,把写操作保存在缓冲区,等待下一次循环,把它写到硬盘
然后开始处理文件时间、时间事件,把写操作保存在缓冲区,等待下一次循环,把它写到硬盘
过期键
过期键删除策略
定期随机删除
惰性删除
过期键对aof、rdb的影响
当内存耗尽,如何处理key
volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
allkeys-lfu**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
volatile-lfu**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。默认
allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰
allkeys-lru**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
问题
过期key策略,如果某个key始终没有被选中清除,且我们也不再访问它了,那么就发生了内存泄漏
事件
时间事件在文件时间后执行。都处于一个大循环内。
如果向客户端返回数据大小,超过了预设值,会在下一个循环中再继续写出
时间事件也会在子进程中做耗时的操作
时间事件在文件时间后执行。都处于一个大循环内。
如果向客户端返回数据大小,超过了预设值,会在下一个循环中再继续写出
时间事件也会在子进程中做耗时的操作
文件时间
就是selector那一套
时间事件
集群
从简单到复杂,分别为以下
1主多从:
(1)手动故障转移
(2)可以使用Lettuce客户端做读写分离,但是一般不用
(1)手动故障转移
(2)可以使用Lettuce客户端做读写分离,但是一般不用
主从(master-slave)架构
方案
主从架构模型
一主多从
主负责写,并且将数据复制到其它的 slave 节点
从节点负责读,所有的读请求全部走从节点
优势
方便水平扩容
支撑读高并发
主从数据同步的完整过程
完整文字版
当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
原则
初次连接master 全量复制
以后只复制缺少的
简化版完整过程
1、启动 slave node,发送 PSYNC 命令给 master node
2、slave 初次连接 master,触发 full resynchronization 全量复制
3、master 启动后台线程,生成 RDB 快照文件,并将所有写命令缓存在内存中
4、RDB 文件生成完毕,发送给 slave,slave 写入本地磁盘,再加载到内存中
5、master 将内存中缓存的写命令发送给 slave,slave 进行同步
6、网络故障断开连接,slave 自动重连,重连后仅复制缺少的数据
过期 key 处理
slave 不会主动过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave
heartbeat
主从节点互相都会发送 heartbeat 信息
master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat
实现
复制过程特性
redis 采用异步方式复制数据到 slave 节点
slave node 也可以连接其他的 slave node
slave node 做复制的时候,不会阻塞 master node 的正常工作
slave node 在做复制的时候,也不会 block 对自己的查询操作
它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了
完整复制过程
注意
如果采用了主从架构,那么建议必须开启 master node 的持久化、master node 的备份
如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。
另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。
哨兵模式:
高可用,基于主从结构,自动故障切换
高可用,基于主从结构,自动故障切换
如何做横向扩展?突破单机限制
redis cluster:
推荐的高可用方案
推荐的高可用方案
流言协议
分布式锁
基于数据库
基于zookeeper
基于redis
要解决三个问题
独占
每个客户端去获取锁,检查key,如果存在key则获取锁成功,否则失败
无死锁
key设置有过期时间,当持有锁的客户端宕机,其它客户端仍有机会获取锁
容错
在一个
其它问题
业务时间内不释放锁
锁续约。以Redison的watchdog为例,自动续约10秒
a持有的锁,只能由a释放
为这个key设置value,为客户端唯一标示
不同机器的时间是不同的
不同用自己机器的时间作为key过期的判断依据
思路
使用lua加锁,带有客户端标识,过期时间
在业务执行期间,不断去续约
好文章
https://blog.csdn.net/yb223731/article/details/90349502
https://blog.csdn.net/ice24for/article/details/86177152
子主题
子主题
子主题
高可用方案
主从+keepalive
不支持读写分离
主从-哨兵
如何做横向扩展?
采用客户端分片
采用客户端分片
redis cluster
分为槽位,客户端计算key的hash,放置在不同槽上
至少六个节点
问题
雪崩、击穿、穿透
雪崩
同一时间大量缓存失效,请求落在数据库,将数据库打死
同一时间大量缓存失效,请求落在数据库,将数据库打死
根本解决
将过期时间分散开
选择合适的内存淘汰策略
如果集群时,避免宕机
预防
做好服务降级
利用redis的持久化机制尽快恢复
注意加锁
穿透
大量访问不存在的key
大量访问不存在的key
第一次启动系统,缓存中没有,数据库有
缓存预热
定期半夜更新
恶意伪造key,在缓存和数据库中都不存在
布隆过滤器,guava的BloomFilter
业务校验
缓存无效的key,设置过期时间
击穿
一个热点数据突然失效
程序控制更新、失效时间
数据库,缓存一致性
https://zhuanlan.zhihu.com/p/59167071
给缓存设置过期时间兜底
注意事项
不用keys,用scan
4.0异步操作
高可用一些技术
keepalive
采用VIP实现
阿里云slb(负载均衡)
作用
负载
健康检查
负载
各种负载策略,再搭配上健康检查
健康检查
业务可用
http层的接口检查
机器可用
tcp、udp层检查
检查规则为:每隔n秒检测一次。如果连续几次超时、无响应、响应错误,那么则判断其坏了,不再向其转发流量
热备冷备
热备:以mysql为例,采用主从。主挂了,立马切换到从,从提供服务。其实像哨兵也就是热备了
冷备:两台mysql,一台挂了。立马把数据文件拷贝到第二台,启动第二台提供服务。
现在高可用都是热备吧
冷备:两台mysql,一台挂了。立马把数据文件拷贝到第二台,启动第二台提供服务。
现在高可用都是热备吧
Tengine
由nginx改造
rocketmq
2软件理论
CAP
一致性(Consistency)
可用性(Availability)
分区容错性(Partition tolerance)
BASE
设计模式
六大原则
24种设计模式其实是对六大原则的详细说明
1、单一职责
一个类(接口)只做一件事情。约束的是类和方法
反例:数据输出模块,单个写与批量写
2、开闭原则
对扩展开放,对修改关闭
反例:在数据探索曾使用简单工厂模式(根据参数在内部使用if判断创建哪个对象),但违背了开闭原则
3、依赖倒置
高层模块不应该依赖低层模块,二者都应该依赖其抽象
4、接口隔离原则
意思是接口职责单一,如果一个接口包含很多方法,那么类就必须要实现它不相关的方法
5、迪米特原则
意思是高内聚低耦合,一个类应该与另外一个类知道的越少越好,耦合越低越好
做法:
发邮件模块-功能比较简单,我就把所有功能点封装在一个类中,对外只提供了发送邮件方法。内部自己处理发送、排队、失败等问题
发邮件模块-功能比较简单,我就把所有功能点封装在一个类中,对外只提供了发送邮件方法。内部自己处理发送、排队、失败等问题
6、里式替换
程序中凡是引用父类的变量,如果替换为子类,那么程序的表现与之前一致。
24设计模式
模板模式
工厂模式
简单工厂
工厂
单例模式
代理模式
命令模式
hystrix
责任链
spring aop
对一个事件有多个处理者
策略模式
UML
面向对象编程的建模工具。现在浅显地理解uml是为了表示类的组成和类之间的关系
关联关系
单向关联
双向关联
自关联
聚合
组合
接口实现
依赖
继承
3系统设计
设计一个秒杀系统
高并发
商品超卖
性能
思路
1、抽象出秒杀系统的通用问题
2、针对系统的各个部分(前端、服务端、数据库、缓存)来做措施
3、结合微服务治理
回答
1、首先,抛开具体的业务问题,我总结出秒杀系统的通用问题。
(1)既然提出秒杀概念,那么就会伴随着高并发。这是最根本的问题。
(2)由高并发带来的性能下降、服务不可用甚至崩溃是第二个问题。
(3)第三个问题是高并发下对独享资源的访问,比如超卖,或者我们公司预约嘉宾见面。
2、我先从单个系统的各个构成部分来分析下。
(1)首先是业务,比如应该根据业务来拦住没有资格的用户,这样避免了一部分流量
(2)然后是前端,将静态资源部署在OSS上。据我所知,oss其实会有cdn的分发。这样将部分流量分散到系统外部。
(3)然后流量进入我们的服务。我们应该有一个前端系统,做一些简单的事情。比如根据前期的一些活动规则,筛选掉一部分流量,处理排队信息,将秒杀请求入mq。这里使用mq来做一个削峰。
(4)当然下游系统要尽力来消费数据。业务逻辑处理成功,就是入库。因为数据库io的关系,可能在这里成为瓶颈。所以考虑先入缓存,再异步入库
3、我们的服务机虽然可以采用集群部署,虽然解决了高并发问题,但是实际上是没有保证高可用。
(1)前端高可用由oss来保证
(2)业务系统的高可用,我们现在是采用微服务架构,那么服务降级必然是非常重要的。要做好服务降级,(降级策略)避免服务不可用或性能很差
(3)数据库的高可用。
(1)既然提出秒杀概念,那么就会伴随着高并发。这是最根本的问题。
(2)由高并发带来的性能下降、服务不可用甚至崩溃是第二个问题。
(3)第三个问题是高并发下对独享资源的访问,比如超卖,或者我们公司预约嘉宾见面。
2、我先从单个系统的各个构成部分来分析下。
(1)首先是业务,比如应该根据业务来拦住没有资格的用户,这样避免了一部分流量
(2)然后是前端,将静态资源部署在OSS上。据我所知,oss其实会有cdn的分发。这样将部分流量分散到系统外部。
(3)然后流量进入我们的服务。我们应该有一个前端系统,做一些简单的事情。比如根据前期的一些活动规则,筛选掉一部分流量,处理排队信息,将秒杀请求入mq。这里使用mq来做一个削峰。
(4)当然下游系统要尽力来消费数据。业务逻辑处理成功,就是入库。因为数据库io的关系,可能在这里成为瓶颈。所以考虑先入缓存,再异步入库
3、我们的服务机虽然可以采用集群部署,虽然解决了高并发问题,但是实际上是没有保证高可用。
(1)前端高可用由oss来保证
(2)业务系统的高可用,我们现在是采用微服务架构,那么服务降级必然是非常重要的。要做好服务降级,(降级策略)避免服务不可用或性能很差
(3)数据库的高可用。
定位引擎设计思路
设计的原则
合适
扩展
稳定
我设计定位引擎的思考步骤
1、需求分析
这个需求分析指的是,我通过对公司内很多定位项目的观察、分析。
来发现其中存在的问题,来将这些项目中定制化的需求剥离,留下相同的、稳定的东西
来发现其中存在的问题,来将这些项目中定制化的需求剥离,留下相同的、稳定的东西
主要问题
重复造轮子
代码质量参差不齐
缺少一套规范的流程
与客户沟通时,不能拿出成熟的输出格式,需要再次定义(其实每次定义的输出格式都差不多)
系统目标
要解决什么问题,解决到什么程度,取得什么成果
比如定位精度我不会去解决
需求功能和流程
数据接入的方式、定位的场景、数据输出的格式
边界
只做定位,不负责其它:区域判定等
不能说系统的需求没完了
未来的可能扩展,留作以后的版本迭代完成
观察系统负载,发现场馆人数多时,压力剧增
单机性能瓶颈-横向扩展
可能新增的需求
2、概要设计和详细设计
1、模块划分
主要按照功能来切分模块。会考虑模块的边界,会遵循一些设计原则,比如迪米特原则等
讲一讲边界划分的问题
1、考虑把数据转换拆出来,作为一个模块,但是考虑到合适、演进的原则,我就暂时没拆分
2、把数据模块划分到定位模块,还是接入模块。涉及到模块边界
2、把数据模块划分到定位模块,还是接入模块。涉及到模块边界
2、细化每个模块中,每个实现类的流程
3、编写代码
1、编写各个模块的实现类
2、使用了条件化注解
问题
数据输出模块发生了单一职责
4、反思
横向扩展
基于http、udp、tcp的横向扩展很方便,只需加上nginx负载均衡,无需改动代码即可
但是基于mq的比较难实现
mq的消息格式是一个蓝牙网关和它扫描到的手机list。而我们定位的对象是手机,需要的数据格式是手机和扫描到它的蓝牙网关list。
所以,我就需要获取连续n条的mq message,对它们进行一个格式转换,来符合我们的要求。
所以,我构思了1种解决方案:
定位引擎节点采用集群消费模式,从mq拉数据,从一条消息中解析出手机mac,并以它为key,存储有网关mac的list为value,存放到redis中。
然后有一个定时任务,每隔5s从redis取出key,进行计算。
现在我需要把redis的key分散到不同的实例上计算,以分散压力。
所以我对每台实例从0开始编号,单调递增。并才用一个hash算法,对redis key计算出一个hash值,然后对实例数取模,得到的余数如果符合
该节点的编号,则可以计算并删除该key。
他存在的问题是,hash不均匀,导致余数频繁落在某一台机器,可能就导致某一台机器负载过大,而其他机器资源空闲。
所以,我就需要获取连续n条的mq message,对它们进行一个格式转换,来符合我们的要求。
所以,我构思了1种解决方案:
定位引擎节点采用集群消费模式,从mq拉数据,从一条消息中解析出手机mac,并以它为key,存储有网关mac的list为value,存放到redis中。
然后有一个定时任务,每隔5s从redis取出key,进行计算。
现在我需要把redis的key分散到不同的实例上计算,以分散压力。
所以我对每台实例从0开始编号,单调递增。并才用一个hash算法,对redis key计算出一个hash值,然后对实例数取模,得到的余数如果符合
该节点的编号,则可以计算并删除该key。
他存在的问题是,hash不均匀,导致余数频繁落在某一台机器,可能就导致某一台机器负载过大,而其他机器资源空闲。
针对上述问题,我可以将hash算法修改为一致性hash,这样就解决了不均匀问题。
但仍存在的问题是,每个实例保存了实力数量,这样其实就是有状态了,这对扩展来说很不友好。
所以我就想把这个状态移到外面。我就想到了redis或者eureka。
但仍存在的问题是,每个实例保存了实力数量,这样其实就是有状态了,这对扩展来说很不友好。
所以我就想把这个状态移到外面。我就想到了redis或者eureka。
第三方开发者需要关注系统流程
第三方开发者扩展时,需要配制参数、加上条件化注解,还是比较麻烦
架构师意见:条件化注解使用的比较乱
高并发场景
缓存一致性
https://zhuanlan.zhihu.com/p/59167071
问题
设计系统,拿到需求,大概知道有几块。怎么划分他们的界限,又如何耦合起来呢
一个技术的选型
架构设计原则
各种集群模式的优缺点
需求分析得工具
脑图
系统流程图
写文档
4业务知识
总体围绕着,你的系统如何产生价值,为用户更好地服务。
落实到细节上,就是一些关键设计怎么处理,你的产出如何保障准确等等
落实到细节上,就是一些关键设计怎么处理,你的产出如何保障准确等等
0、你如何训练你的模型?
1、如何保证数据的准确性
看起来这一块很重要,越高端的岗位越会问这些
安全
sql注入
mybatis怎么防止注入
xss
注入脚本
csrf
伪造用户请求
ddos
盗链
检查http refered地址
跨域
后端设置响应头Access-Control-Allow-Origin
权限校验
JWT
5面试
21天突击
技术广度:
包括java基础(集合、并发等),
和中间件(用法、原理、优缺点)
包括java基础(集合、并发等),
和中间件(用法、原理、优缺点)
mq
各个mq比较
rabbitmq
社区活跃(快速解决问题)。性能不行。
但为啥很多公司用?因为他们就没有什么很高的并发
但为啥很多公司用?因为他们就没有什么很高的并发
rocketmq
性能强劲,社区一般,资料匮乏
kafka
火得很,性能也强劲
mq作用:解耦、异步、削峰
耦合:
cindy通知各个bu的系统。来拉去数据,自动切图。
体现出解耦。如果cindy直接去调用各个系统,让他们拉取数据,那么cindy就高度与其它系统耦合。
今天新来一个系统,需要cindy调用一下。明天一个系统又不需要cindy调用。cindy需要频繁修改自己的代码。
而且cindy还要考虑如果其它系统超时怎么办等情况,需要做一个重试机制。。。
cindy通知各个bu的系统。来拉去数据,自动切图。
体现出解耦。如果cindy直接去调用各个系统,让他们拉取数据,那么cindy就高度与其它系统耦合。
今天新来一个系统,需要cindy调用一下。明天一个系统又不需要cindy调用。cindy需要频繁修改自己的代码。
而且cindy还要考虑如果其它系统超时怎么办等情况,需要做一个重试机制。。。
异步化:
1、a系统完成自己的事情,然后把消息发送到mq,就直接返回前端成功(很短的响应时间,用户高兴)。下游系统接收到消息后自己处理自己的。
疑问:
为啥不用基于http接口,和多线程的异步化?因为这样违背了第一条,a系统与其它系统耦合了
1、a系统完成自己的事情,然后把消息发送到mq,就直接返回前端成功(很短的响应时间,用户高兴)。下游系统接收到消息后自己处理自己的。
疑问:
为啥不用基于http接口,和多线程的异步化?因为这样违背了第一条,a系统与其它系统耦合了
削峰:
硬件数据发送到mq,定位引擎拉取数据定位。因为高峰期有很多场馆的wifi大量发送数据到定位引擎,
下游系统来不及处理。
该系统不保证消息的可靠传输(幂等、丢失)。因为热力图只要求实时,点位动起来
硬件数据发送到mq,定位引擎拉取数据定位。因为高峰期有很多场馆的wifi大量发送数据到定位引擎,
下游系统来不及处理。
该系统不保证消息的可靠传输(幂等、丢失)。因为热力图只要求实时,点位动起来
削峰
将系统来不及处理的消息积压在mq中,从而保证下游服务可用
配置详解
brokerRole
如果一个 Broker组有 Master和 Slave, 消息需要从 Master复制到 Slave上。
同步复制方式:
Master 和 Slave 均写成功 后才反馈给客户端写成功状态
异步复制方式:
只要 Master 写成功即可反馈给客户端写成功状态 。
注意:
1.复制方式是通过 Broker 配置文件里的 brokerRole 参数进行设置:
ASYNC MASTER 异步复制
SYNC MASTER 同步复制
SLAVE 无影响
2.通常情况下,应该把
刷盘方式配置成 ASYNC_FLUSH
主从复制方式配置成 SYNC_MASTER
这样即使有一台机器出故障,仍然能保证数据不丢。
同步复制方式:
Master 和 Slave 均写成功 后才反馈给客户端写成功状态
异步复制方式:
只要 Master 写成功即可反馈给客户端写成功状态 。
注意:
1.复制方式是通过 Broker 配置文件里的 brokerRole 参数进行设置:
ASYNC MASTER 异步复制
SYNC MASTER 同步复制
SLAVE 无影响
2.通常情况下,应该把
刷盘方式配置成 ASYNC_FLUSH
主从复制方式配置成 SYNC_MASTER
这样即使有一台机器出故障,仍然能保证数据不丢。
flushDiskType
flushDiskType=ASYNC_FLUSH(异步)SYNC_FLUSH(同步)
同步刷盘方式 :
在返回写成功状态时,消息已经被写入磁盘 。 消息写入内存的 PAGECACHE 后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态 。
异步刷盘方式 :
在返回写成功状态时 ,消息可能只是被写入了内存的 PAGECACHE,写操作的返回快,吞吐量大 ;
当内存里的消息量积累到一定程度时,统一触发 写磁盘动作,快速写人
同步刷盘方式 :
在返回写成功状态时,消息已经被写入磁盘 。 消息写入内存的 PAGECACHE 后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态 。
异步刷盘方式 :
在返回写成功状态时 ,消息可能只是被写入了内存的 PAGECACHE,写操作的返回快,吞吐量大 ;
当内存里的消息量积累到一定程度时,统一触发 写磁盘动作,快速写人
brokerId
等于0为主节点,其它值为从节点
deleteWhen=04
每天凌晨四点清理磁盘
fileReservedTime=48
消息文件在硬盘上保留时间,48小时
brokerIP1
brokerIP1:当前broker监听的IP
brokerIP2:存在broker主从时,在broker主节点上配置了brokerIP2的话,
broker从节点会连接主节点配置的brokerIP2来同步
brokerIP2:存在broker主从时,在broker主节点上配置了brokerIP2的话,
broker从节点会连接主节点配置的brokerIP2来同步
配置多主(即只分片)
集群名称一致,brokerId=0 相当于(主),brokerName不一致
缺点
引入高复杂度,我们需要支持它高可用
分布式架构:多主多从、多主无从
消费端高可用:
master繁忙,或者不可用,从从节点拉数据
发送端高可用:
只要有一个master可用。就可以发送消息
消费端高可用:
master繁忙,或者不可用,从从节点拉数据
发送端高可用:
只要有一个master可用。就可以发送消息
重复消费
业务自己来保证,例如一个消息有一个唯一id(雪花算法)
数据(事务)不一致?但最终一致?
乱序
同一个订单的消息发送到同一个队列。客户端只能从一个分区的一个队列中取数据。
这里要回忆:
这里要回忆:
消息丢失
1、生产者到mq阶段:
开启事务。同步阻塞
回调。异步
2、mq自己处理阶段:
持久化到磁盘后再给消费者ack确认;
或者同步到从节点再ack确认
这里要回忆几种策略:同步刷盘、异步刷盘;同步传输数据到从节点、异步传输数据到从节点
这些策略带来的后果,是否会丢失数据
或者同步到从节点再ack确认
这里要回忆几种策略:同步刷盘、异步刷盘;同步传输数据到从节点、异步传输数据到从节点
这些策略带来的后果,是否会丢失数据
3:、mq到消费者阶段:
4、消费者自己处理阶段:
4、消费者自己处理阶段:
业务处理完毕后再手动确认ack。
防止你一接收到消息就ack,但是业务又处理失败了的情况,这就相当于消费失败嘛
防止你一接收到消息就ack,但是业务又处理失败了的情况,这就相当于消费失败嘛
redis
为什么单线程还那么快
(主要原因1)io多路复用:有三种技术:select、poll、epoll,性能依次增强。
io多路,指的是多个网络连接
epoll被设计用来处理有多个网络连接,但是大多数是空闲的
io多路,指的是多个网络连接
epoll被设计用来处理有多个网络连接,但是大多数是空闲的
基于内存操作
单线程避免了过度上下文切换(鸡肋,可以补充说它很勉强)
(主要原因2)优秀的数据结构。
回忆有哪些数据结构,什么作用
回忆有哪些数据结构,什么作用
数据结构
string
set
ordered set
hash
list
string
set
ordered set
hash
list
缓存问题
击穿
一个很热的缓存失效,大量请求到达数据库
永久缓存,用我们的代码来控制刷新机制
加锁取数据库取数据,填充缓存
雪崩
大量缓存同时过期
将过期时间分散开
穿透
恶意请求不存在的缓存,或者null值
代码也判断不可能存在的缓存key。必要时缓存null等特殊值
集群
codis
删除
过期策略:定期删除
每过一段时间就检查其中一部分的key,如果过期了就删除
过期策略:惰性删除
客户端操作这个key,检查是否过期,如果过期就删除
淘汰策略
如果每次没有检查到这一部分过期key,又没有主动去访问,那怎么办
lru
lru
项目经验
系统设计
数据探索平台
模型中各个节点如何关联
通过model_node表,只保存了简单的关联关系
遇到的问题,如何解决
跑出的结果存储问题
上下游系统不稳定问题
结果准确性问题
判断数据好的标准是什么
跑出的结果多?
跑出的结果准确性高?
是否符合业务、术语
智博会
问题
redis频繁超时
因为存放了大value
拆分为map、小key,小value。不能拆分的放到本地缓存
数据库连接不上
每个服务连接的数量过多,减小即可
cpu爆满
频繁创建大对象,list频繁扩容。arrays.copy
微服务怎么划分的
数据层(迁库)
基础服务(路算、poi搜索、发短信邮件)
业务服务(统一登录)
业务
我最近做的一个项目是重庆智博会导航项目。主要是为参展观众提供室内导航,包括路算、地图poi搜索、迁移数据、大屏BI等。我主要是负责大屏,埋点数据采用分表方式。然后要查询这些表,我们采用了t(交易日)+1
按照简历来表达,引导面试官,说自己想说的,让面试官问自己想要他问的。
但是一定要说的精彩,要突出什么情况发生了什么事情,我们如何解决,学到了什么。
我在项目中的贡献。我有什么与众不同
但是一定要说的精彩,要突出什么情况发生了什么事情,我们如何解决,学到了什么。
我在项目中的贡献。我有什么与众不同
回答问题的思路
1、遇到什么问题,怎么分析出来、定位到的
2、如何解决,思路
3、这样解决的优缺点
问过的问题
判断循环依赖
1、假如a依赖b,b依赖a。那么创建a时,把a放入缓存。
2、这时依赖注入b。把b放进缓存。
3、这时依赖注入a,先去缓存查找,如果找到了,说明发生循环依赖
2、这时依赖注入b。把b放进缓存。
3、这时依赖注入a,先去缓存查找,如果找到了,说明发生循环依赖
concurrenthashmap的size方法
使用mappingSize替代。size方法如果超过INTEGER.MAX_VALUE会返回INTEGER.MAX_VALUE。
而mappingSize会返回long类型
而mappingSize会返回long类型
可能是不准确的
addCount方法中,会先cas对baseCount进行累加。如果cas失败,则保存到counterCells数组中。
size方法中,对这两部分累加返回
size方法中,对这两部分累加返回
隔离级别加锁
juc包里面用过哪些类
锁
ReentrantLock
ReentrantReadWriteLock
高级并发容器
阻塞队列
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
线程池
ThreadPoolExecutor
ScheduledThreadPoolExecutor
Callable
Feature
FutureTask
Executors
Runable
你在项目中贡献
技术上的贡献
善于发现细微之处,察觉到它变坏的趋势,并在以后逐步解决
我们有很多老项目,日志文件会一只保存。我就会经常观察硬盘的使用情况。如果硬盘快满了,我去手动清理。防止硬盘满导致服务不可用的后果。那么经过几次这种情况,我发现人工检查不及时,有遗漏。所以我就向上反映,最终我们使用阿里云监控,实时监控硬盘、内存、cpu变化。
实际上是提高了我们服务的可用性
实际上是提高了我们服务的可用性
善于观察总结,利用技术来改善现有产品的效率、功能
定引擎项目。我事先总结了一些问题,借鉴了其它部门遇到的问题
产品
为产品提建议
参考竞品的功能点,对照我们类似的功能点
验证码位数
参考主流产品的验证码位数,大部分都是四位
结合自己的体验,四位容易记忆。而且在上方提示框中正好能显示出来。而六位就需要站开看,且记不住
短信提示内容
我参考了脉脉、陌陌等软件的提醒短信,发现短信内容是模糊的,只提醒有这么一件事情,
但是详细内容却半显半隐,诱导用户到app里面去查看,来正增加产品的流量
但是详细内容却半显半隐,诱导用户到app里面去查看,来正增加产品的流量
团队
主动承担责任,推动项目前进
数据探索
团队调整,后端主要开发离职,几乎没人知道流程。我就站出来,加班两周,
将系统核心代码看完。给同事分享,出问题该找负责人的找,该上报的上报。
我成为一个实际上的项目负责人
将系统核心代码看完。给同事分享,出问题该找负责人的找,该上报的上报。
我成为一个实际上的项目负责人
感悟
为什么阿里人这么喜欢造轮子
符合这种业务场景
标准化流程,对外提供统一的格式
避免大家重复工作
mybatis如何防止注入
使用#代替$。
底层原理:preparedStatement会把select * from table where a = ? 作为一个语句预编译。执行时,只需传入参数,不会编译传参数。
这样,及时参数中包含'drop table xx',也会被当做a的参数,不会去执行的。
底层原理:preparedStatement会把select * from table where a = ? 作为一个语句预编译。执行时,只需传入参数,不会编译传参数。
这样,及时参数中包含'drop table xx',也会被当做a的参数,不会去执行的。
mybatis 默认情况下,将对所有的 sql 进行预编译。mybatis底层使用PreparedStatement,过程是先将带有占位符(即”?”)的sql模板发送至mysql服务器,由服务器对此无参数的sql进行编译后,将编译结果缓存,然后直接执行带有真实参数的sql。核心是通过#{ } 实现的。
在预编译之前,#{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符?
在预编译之前,#{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符?
eureka实现了cap中的哪两个,为什么
实现了ap。因为eureka专注于服务注册发现,而服务注册发现不需要很强的一致性,且提升了性能
redis能保证数据不丢失吗。如果要不丢失怎么做
肯定会丢失。回忆持久化策略,想要不丢失,别用redis
inner jion和outing join区别
put和post的区别
put是幂等的,post不是。
所以put一般用于需改资源,对同一个资源的多次修改,结果都是一样的。而post一般用于新增资源
(注意:重点是幂等和非幂等)
所以put一般用于需改资源,对同一个资源的多次修改,结果都是一样的。而post一般用于新增资源
(注意:重点是幂等和非幂等)
eureka中存实例的什么信息
1、连接信息
2、状态
3、健康
for update
如果有主键
数据行不存在
无锁
数据行存在
行级锁
主键不明确,模糊匹配
表锁
无主键
表锁
说说你印象最深的项目
重构小程序
阿里p7一面
分库分表,数据准确性
遇到问题,你是怎么解决的
数据库数据的四个隔级别
从工程角度来分析怎么保证bi准确性,不要从算法、硬件角度出发
怎么做mq数据的保证
豪猪配制
命令模式
谈谈aop
spring事务管理
工作中的困难
团队调整
数据探索遇到的困难,团队主要开发离职,逻辑混乱。
河豚参展产品被叫停,整个节奏被打
技术困难
对一个表的记录按照条件查询。其中一个条件是按照年月份查找。我之前是用一个定时任务,计算每个记录对应的月份。保存到一个关联表中。
版本
springboot版本
2.0.3
redis版本
4
mysql版本
8
pg版本
10
不停机拆分表
双写机制
数据根据key分到不同的库里。a为原来的库,b为新库。
数据根据key分到不同的库里。a为原来的库,b为新库。
你们高保怎么做的
系统
集群
redis
没做高保。回忆几种方式
mysql
1、不同的服务使用不同的库
bi的库与其它服务的库分开
2、我们对读的库做了集群部署,slb tcp负载均衡+健康检查
3、读写分离
在同一个下服务中,使用多个数据源。写功能使用单独的数据源
pg与mysql的区别
pg没有自增,使用序列。而mysql有自增主键
pg对gis支持非常好
支持r树、gist索引
pg支持单个存储引擎。而mysql使用插件式的引擎:innodb、myisam等
pg支持很复杂的查询处理。而mysql支持不太好
mysql是按照索引组织表的,所以对主键要求短,而且递增。pg不存在该问题
pg是进程,而mysql是线程
pg在可重复读级别下,实现了乐观锁。而mysql没有
分布式事务
cap、base
2PC两阶段提交
tcc补偿型事务
子主题
子主题
java序列化
讲讲策略模式
eureka脑裂(zk与eureka区别之一)
eureka没有脑裂现象。因为它不选举主节点
数据探索平台,关于数据的问题
如何判断结果准确性
数据量的多少
对于公安行业,不能以数据量多少来评判
准确比率
与前科犯罪人员库碰撞,计算准确比率
那真实数据,和测试数据来做判断
如何提高结果准确性
1、基础:提高数据治理的质量
前期把治安问题划分到了刑事犯罪
2、优化模型参数
参考浙江的成功模型案例参数
找上海有经验的警察来输入参数
3、与第三方系统,智能标签对接
给一个人打标签,可以再次作为模型输入,经过筛选
大量的数据如何存储
需要业务上配合
默认保存3天。其余删除。允许其导出结果集
结果集不大。允许长期保存
为什么把es改为sql
1、es使用全文索引。比如搜索国博,会被自动拆开,搜索出博学路这些不相关的。
2、es要分词,我们就需要对每场展会进行分词,确实比较麻烦
3、es占用较大内存和硬盘。他本身又是集群部署。我们这小公司这规模不合适
4、我们数据库水平拆分后,单表也就几百到小几千的数据量。然后也完全满足搜索的需求,完全可以支持SQL、连表查询
最主要原因:Es对我们业务来说比较重量级
业务特点
搜索的东西也不复杂
我们的搜索并发量没有那么大
公司服务器资源紧张
es特点
es支持高级功能,我们几乎用不到
es部署起来,如果做集群,无疑会增加系统的异构性。而且很吃机器资源
选择sql
数据经过了分表,数量很小
sql查询无性能问题,做连表查询,全模糊都可以的
讲讲分布式锁
多个服务之间调用,如何确定一个唯一标识
如果没有spring,如何做到自动注入。日志门面的使用场景
sharding原理
长事务
长事务就是开启一个事务,长时间不提交。可能造成阻塞,或者锁超时
information_schema
是一个视图,所以维护在mysql服务层
数据库信息
事务信息
字符集信息
划分微服务的标准
稳定
方便复用
基础下沉
可扩展
可替换
java性能优化
纯代码优化
为容器设置合理容量
使用线程池时考虑到实际的制约因素
围绕jvm优化
gc
配制垃圾回收器
根据硬件选择回收器
单核用串行回收器,多核用并行回收器
根据业务选择回收器
关注cpu吞吐量,使用Parallel Scavenge。适用于后台计算密集型,如定位引擎
关注低停顿,使用CMS。适用于与用户交互,如智博会
指定gc的行为,比如指定gc时间,指定是否整理老年代内存
调整合适堆大小
选择合适的堆大小。堆过大导致管理堆成本过大,过小导致频扩容
在大内存机器上部署要避免fullGC,因为进行一次停顿时间比较久
避免创建过大对象,进入老年代
对象生存周期就可能短,不要进入老年代
定时半夜触发fullg
长期监控优化
打印gc信息,分析gc频率和原因
Full GC (Metadata GC Threshold)
GC (Allocation Failure)
jstat -gc
观察系统正常运行时的gc频率
发生oom自动导出堆栈
运行期优化
逃逸分析,栈上分配
指定JIT参数
运行模式
代码符合规范
https rsa加密如何保证高性能
交换证书使用非对称
传输数据使用对称
https://www.jianshu.com/p/14cd2c9d2cd2
热加载类时,如何确保之前的这个类没有正在被使用
没办法保证
r树索引
存储空间索引,便于距离内兴趣点查找等
如果一个redis key有百万次的访问怎么做
可能的问题
limit性能优化
jvm异常表
keys与scan
排序
分布式缓存、本地缓存区别
数据库乐观锁
pg在可重复读级别下,实现了乐观锁。而mysql没有
b与b+区别
b树非叶子节点保存数据,而b+非叶子节点不保存数据。所以b+更矮,每个页上存储的值更多,减少磁盘io
b+的叶子节点使用链表关联,方便范围查询
子主题
AQS
1、维护了CLH的队列
包括前置节点,下一个节点的状态
微服务相比单体服务的好处和坏处
功能分开,易于协同开发,采用不同的技术
功能以服务来区分,易于资源配置
因为服务众多,需要协调。需要服务治理:注册中心、链路跟踪等
比如每个微服务持有一个连接池,资源不能统一分配
gc可达性算法
栈引用的
元空间常量引用 的
类的静态属性引用的
负载均衡
策略
随机
线性
加权
架构设计
客户端负载 or 服务器反向代理 or中间层负载
高可用
失效转移
集群
是否强一致
扩展
slb原理
四层负载
LVS(Linux Virtual Server)+ keepalived
到达ecs
七层负载
LVS(Linux Virtual Server)+ keepalived
tengine
ecs
lvs
linux四层负载
keepalive
基于vip的热备机制。保证lvs存活
如何确定redis中一个key的最佳大小
基准测试
智博会得到的东西
对各种场景的解决经验
对微服务的思考
基础服务下沉
对高可用的机器部署原理
在定位引擎中实践
增强了eureka的理解
happen-before
几个原则
项目细节
如何确定线程池数量,依据
你为什么要把最大连接数改为300
如何确定实例的最小连接数
反射是否有性能问题
看怎么使用反射
cookie与session
cookie
在客户端记录信息确定用户身份
一定不能挎顶级域名。一般情况下不能挎二级域名,但是可以设置domain属性
缺点优点
session
在服务器端记录信息确定用户身份。
但是需要借助cookie存储sessionid。
但是需要借助cookie存储sessionid。
如果禁用cookie,使用url重写http://xx/xx.html;sessionid=xxxx?p1=p
缺点优点
select poll epoll
nio具体使用哪个,与环境相关。linux下使用epoll
7学习方向
引子:
1、几个月前,豪哥跟我说放一放java基础知识,多看看中间件、框架。当时没太理解
2、面试阿里,面试官问我,你最近这份工作是注重什么。只是应付了一句注重模块的设计
1、几个月前,豪哥跟我说放一放java基础知识,多看看中间件、框架。当时没太理解
2、面试阿里,面试官问我,你最近这份工作是注重什么。只是应付了一句注重模块的设计
疫情期间想了想,学到了这个阶段,只一闷着头学习java基础知识,已经进步太慢了。当然不是说不学,而是要转移学习中心。
多看看中间件,看人家的原理,怎么使用基础技术的。
学学如何设计系统,设计架构
多看看中间件,看人家的原理,怎么使用基础技术的。
学学如何设计系统,设计架构
分支主题
select * 可以预编译
select时要指定具体的列,便于mysql预编译后缓存,下次使用时不用编译
子查询与内连接的区别
执行子查询时,MYSQL需要创建临时表,查询完毕后再删除这些临时表,所以,子查询的速度会受到一定的影响,这里多了一个创建和销毁临时表的过程
收藏
0 条评论
下一页