java知识点思维导图
2019-01-24 17:16:18 811 举报
AI智能生成
学习路线
作者其他创作
大纲/内容
JDK
基本类型
装箱:集合类型添加基本类型时发生。拆箱:运算一个基本类型和引用类型时发生。
集合
iterator
list
arrayList
linkedList
copyOnWriteArrayList
适合读大于写
在copy时多个线程可能读到不同的数据
map
HashMap
数组长度的特点
2的指数次幂,使得 hash%cap = hash&(cap-1),与运算更快
装载因子
超过这个值就会扩容。通过数学推导出来的0.75,太小了会频繁扩容,太大了会很少扩容造成更多的hash碰撞
死锁
1.7使用的链表在扩容时两个线程会打乱链表顺序,导致元素循环引用,在扩容时永远遍历不完链表,cpu飙升
1.8已经修复这个问题
一个槽内元素小于8时用单向链表存储
数组长度大于等于64时,且槽内链表元素数量大于8时转成红黑树存储
null值只能存一份,存在下标0
TreeMap
红黑树维护key值,默认字典排序,可以定制comparator
ConcurrentHashMap
分段算法
LinkedHashMap
比HashMap多一个链表维护写顺序
accessOrder=true时每次访问元素都会把元素放到尾部
lru:重写removeEldestEntry:{return size>maxSize}
set
HashSet
底层是用hashMap实现的,只用了key
TreeSet
同上
LinkedHashSet
同上
工具类
Arrays
copyValueOf,valueOf,fill,toArray
Collections
排序,交集并集
fail fast
exceptModifyCount != modifyCount, 并发写会或者for里面删除、添加元素导致modifyCount++而另一个没变两者不等,抛出ConcurrentModifyException。解决方案是用iterator遍历里面去修改list,iterator的api会同步这两个值。
为什么平时添加删除元素后for遍历不会抛异常?因为for底层用了while 和 iterator,创建迭代器的时候把exceptModifyCount改为了modifyCount
fail safe
解决并发写就是安全的,比如copyOnWriteArrayList,每次写都是写副本,不会有并发问题
JVM
内存模型
运行时数据区
栈
栈帧
局部变量表
程序计数器
本地方法栈
native方法
jni调用
堆
新生代
Eden
Survivor1
Survivor2
老年代
字符串常量池
基本类型常量池
本地内存
方法区
运行时常量池
字面量和常量
JIT hot code cache
类与方法
类的初始化
加载
读取class文件为流,在方法区保存class信息叫做运行时数据结构,在堆创建一个class对象作为运行时数据结构的入口。实例对象头会包含class对象的引用,该结构包含class常量池。
链接
验证
校验class文件的合法性,方法、参数、字段是否符合jvm规范,该步骤是和加载并行的。
准备
给静态变量初始化为默认值(0,null),给final常量初始化常量值
解析
将class常量池内的符号引用替换成直接引用,在访问变量,方法调用时使用直接引用
初始化
只有在new、getputstatic读写静态变量时会调用,初始化静态变量的值
卸载
没有任何地方引用class对象
没有任何对象是该class的
加载该class的class loader被卸载了
类加载器
双亲委派
子加载器如果有父加载器就必须用父加载器加载类,保证了安全性
Bootstrap
系统类加载器,加载jre/lib下的核心类,rt.jar、resource.jar等
Extension
扩展类加载器,也是加载jre/lib下的类
App
加载classpath下的jar
自定义加载器
findClass 重写该方法
Tomcat commonClassLoader, 父加载器是App classLoader
Catalina Classloader,加载tomcat的核心类
Shared ClassLoader,加载应用共享的jar,shared lib 下的包
WebAppA Classloader 只加载A应用的class
WebAppB Classloader 只加载B应用的class
Class.forname,使用加载了当前类的加载器加载
创建对象
分配内存
指针碰撞,规整内存块使用,par new使用。 cms也可以用,在整理后对某块空闲区域可以做碰撞。
空闲列表
内存碎片化的区域使用,cms使用,未进行整理的老年代只能用这种方式
初始化0值
为字段开辟空间,将字段初始化为0、null
设置对象头
mark word,哈希码,gc年龄=0,线程id=0,偏向锁标志=0,锁标志0,等待列表空
引用赋值
创建引用关联该对象,并把引用返回给用户
tlab- Thread Local Allocation Buffer
eden区
年代提升
分配担保
GC
标记复制
Parrallel new
标记清除
CMS
整理碎片
线程模型
原子操作
lock
锁定主存上一个变量,标记为线程独占,其他线程无法修改和读取。
unlock
释放主存上被锁定的一个变量,其他线程才可以修改或读取它。
read
从主存上读取一个变量
load
将read读取的变量保存到线程的工作内存
use
使用该变量的副本让jvm引擎执行
assign
把从执行引擎获取到的变量复制到线程的工作内存
store
将工作内存中的变量传输到主内存
write
把store操作后传送到主内存上的变量写入到主内存
主内存
内存条,通过消息总线、io与cpu通信
工作内存
线程私有的内存,实际上是cpu L1L2缓存,保存主内存上变量的副本。
volatile
缓存行64b,超过64b的变量无法使用MESI
多核处理器读取统一变量有并发问题
消息总线加锁
不使用了,性能很低,每次读写都要加锁
MESI 缓存一致性协议
保证同一时间只有一个核心可以通过总线写变量
缓存行状态
Modified 被修改
Exclusive 独占
Shared 共享
Invalid 无效
修改变量流程
A线程 S-M-E-S, A修改变量,先通知总线其他core嗅探后把缓存行改为I,S改为M,unlock后改为E,其他core嗅探后改为S。
B线程 S-I-S
可见性
写变量立即刷新到主存,读变量必须用主存中的变量
有序性
jit会对代码优化,考虑到性能优化,将后面的代码先执行,在单线程下是没有问题的,多线程下会有问题。
happen before
原子性
不保证多条字节码指令的原子性,volatile只保证读能看见写的,但是两个核心的写操作还是会相互覆盖,举例体现在++实际值会比预计的小。
保证long 和 double 等64位数据读写的原子性
线程生命周期
新建
调用pThread函数创建线程,new,之后要准备等待cpu调度
运行
获取到cpu时间片,开始执行
等待
wait、sleep、park、join主线程等待子线程结束
time waiting 带超时的等待
阻塞
synchronized
对象头里面有Mark Word和类型指针,Mark Word 用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
结束
线程销毁
并发编程
锁
锁类别与场景
自旋锁
持有锁的时间极短的情况下,占用cpu比切换上下文的开销更小。
实现:unsafe
读写锁
内存黑名单,读大于写但是又不想让读写互斥,采用读写分开加锁的方式,效率更高。
公平锁
FIFO模式,会导致饥饿
非公平锁
可能导致第一个进来的线程最后一个获得锁
悲观锁
假设每次都会产生竞争,因此每次都加锁
乐观锁
假设很少情况下会产生竞争,因此每次都不加锁,但是竞争情况下只有某一次能成功,这取决于你的实现。
偏向锁
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此只让该线程获取锁,若突然有其他线程获取锁则发生锁升级。
实现:mark word 内存偏向锁线程id
分段锁
分区,对不同的分区加不同的锁,比如ConcurrentHashMap的实现。
轻量级锁
偏向锁升级成自旋锁。
重量级锁
最低性能的锁,线程每次访问加锁区域都会获取锁。
锁粗化/锁消除
粗化:JVM探测到在某个循环中重复加锁,就会把锁粗化到更高的范围,比如提到循环外面。
消除:JVM探测到某个加锁块不可能被其他线程访问到,就消除锁。
单例
枚举
懒加载,双重判断,volatile防止指令重排序
并发框架juc
ConcurrentHashMap
同步器
CAS
多线程cas 自旋 unsafe.compareAndSwapInt
AQS
由一个等待队列workers、锁状态state、线程id构成
线程aquire获得锁将state改为1(锁被获取),线程id设置为当前线程。如果另一个线程未获取到锁就放到workers里面,然后自旋LockSupport.park等待锁,获取锁的线程释放锁后弹出队头元素调用unpark方法通知解锁。
LockSupport
AQS用来等待锁等方法,不要用wait
自旋
假设很快就能获取到锁就不用阻塞让出锁了,直接自旋保证当前线程还有用cpu时间片
线程池
coreSize
核心线程数
默认一值存在不会被回收
maxSize
最大数量
keepalive
如果线程数量超过核心线程数,空闲超过这个时间的线程就被回收
queue
等待队列
达到最大数量后会放到这个队列
allowCoreThreadTimeOut
开启后核心线程超过keepalive后也会被回收
ThreadFactory
创建线程的工厂,可以自定义创建策略
AbortPolicy
队列也满了之后的拒绝策略
分布式
一致性算法
基于复制日志的复制状态机
通过同步日志的方式使集群达到一致
raft强一致性
线性一致性:读写都走raft日志提交,永远只会读到已经提交的数据。顺序一致性:写走raft日志提交,读可以随意读取任意节点的状态机,因此会读到未提交的老数据。
特性,也是实现raft的总结
选举安全性(election safety),在不增删改成员的情况下,要求过半选票的机制确保每个任期最多选出一个leader。
领导人只附加原则(leader append-only),领导人绝对不会删除或者修改自己的日志,只会增加。
日志匹配原则(log matching),如果不同机器上的两个日志项具有相同索引位置和任期号,那么我们就认为这个日志从头到这个索引位置之间全部完全相同。因为不会删除已提交的,提交日志是会比对之前一位的日志是否有相同index和term。
领导完整性(leader completeness)如果某个日志条目在某个任期号中已经被提交(注意leader只会提交当前任期产生的日志,之前任期的日志及时被同步到半数以上节点也不算提交,会被后面的leader覆盖),那么这个条目必然出现在更大任期号的所有领导人中。
状态机安全(state machine safety)如果一个领导人已经将给定的索引值位置的日志条目应用到状态机中,那么其他任何的服务器在这个索引位置不会应用一个不同的日志
选举
与matser间心跳timeout(随机值保证不会永远选不出)后变成candidator,给自己term+1投票给自己,然后发起选举请求给其他节点,其他节点处理请求:term小的拒绝,已经投过票了拒绝、最后一条日志的任期小的拒绝,相同时日志少的拒绝,其他通过。
过半机制保证不会脑裂
日志
存放在内存,超过一定大小会压缩保存到本地
install
节点动态扩容
gossip弱一致性、最终一致性
用来广播故障、成员关系等事件
hashicorp/serf
一致性hash
精髓是每次增删节点只要改变最多一个节点的数据,其他节点数据不变。主要解决动态增删节点的问题,使用一个排序的首尾相连的hash表,在其内部填充虚拟node保证数据分散平衡,每个节点都有hash值。读写数据时对key hash找到对应的hash槽,每次添加节点只要同步比它hash小一点的那个节点的数据,每次删除节点只要把数据同步到比他大一点的hash槽。
CAP
Mysql是CA系统,没有分区容忍性P,若出现网络分区,则一段时间内数据无法同步一致C,如果等待一致则放弃了一段时间内的可用性A。
C (一致性):对某个指定的客户端来说,读操作能返回最新的写操作。
A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
可用性的两个关键一个是合理的时间,一个是合理的响应。
可用性的两个关键一个是合理的时间,一个是合理的响应。
P (分区容错性):当出现网络分区后,系统能够继续工作。
打个比方,这里集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
打个比方,这里集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
CP系统,放弃可用性,也就是一段时间内无响应,比如zookeeper。
AP系统,放弃强一致性,选择最终一致性。
BASE
基本可用(Basically Available)
软状态(Soft State)
最终一致性(Eventually Consistent
软状态(Soft State)
最终一致性(Eventually Consistent
分布式锁
redis+lua
锁的key是业务细分的id,value用hash结构存储对象,对象包含了持有锁的线程的唯一id、锁的有效期。先exists判断key是否被他人持有,没就自己持有,更新锁持有人id,锁失效时间。如果已经被持有,判断所持有者id是否是自己,是的话就加1并续费,每次释放锁都减1。
线性扩展锁性能
分段锁,将key再细分成多个段。
锁超时而业务未完成怎么解决?
参考redission给出的实现,lock成功后开启一个后台线程在锁快要失效的时候去给他续费,增加失效时间。
zk
节点类型
持久
保存持久化数据
临时
客户端连接断开后就会被删除,一个典型的场景是服务注册到zk,其他服务想实时监听到它的状态
顺序
一个典型的用途是公平的分布式锁,每个客户端都创建一个顺序的临时节点,如果自己是否是最小的那个就获取锁,每个未获取锁的客户端都监听之前一位的节点并使用park等待,当它被删除的时候客户端就unpart执行下面的代码,执行完毕后删除节点。临时节点保证业务挂了后锁自动释放。
acl权限
用的比较少,一般业务上控制,使用ZooDef.Ids.Open_ACL_Unsafe
watch
监听器接口,用来监听znode的变化、集群机器状态变化
临时性,防止无休止的监听,用完一次后需要主动调用api,watch参数=true才可以再次生效
callback
getChildren获取到数据后执行的接口
节点状态
leading
使用相当于raft的term的 zxid 进行选举
following
提供读服务,写服务转发到leader,leader走zab一致性协议提交
looking
相当于raft的candidate,正在选举中
observing
一个不会looking的节点,用来分散读压力
顺序一致性
zab使提交的请求被大多数副本接收,但是还有一部分未接收,zk允许读follower,这就导致了客户端可能读到一份旧的数据。但是一旦读到新的数据就不会再读到老的数据了,这就叫顺序一致性。
IO
协议
upd
面向包
tcp
客户端
sync send、established、fin wait1、fin wait2、 time wait、close
服务端
listen、sync recv、established、close wait、 last ack、close
三次握手
isn(Initial Sequence Number)、syn(synchronize sequence number)、ack (acknowladge numer)
客户端生成isn=x ,发送syn seq=x给服务端,服务端生成服务端isn=y,返回ack x+1 seq=y 给客户端, 客户端返回ack=y+1
3次握手已经满足需求不需要4次,2次握手会导致重拾连接延迟过来的syn被接收到然后返回ack建立一个假的过时的连接,3次握手因为会对ack携带的syn做确认所以不会建立假连接。
四次挥手
fin(finish)
因为服务端收到客户端的fin后可能还有数据没发完,所以不能直接返回fin,要先返回一个ack,待数据传输完毕后再发送fin,客户端返回确认ack后进入time wait等待2msl(60s)才算完全断开。time wait 用于服务端收不到客户端ack的情况下重试。还可以使这次连接中被延迟发送的包清除,保证不影响到下一个新连接。
避免拥堵要确认ack才继续发送,超时重传、慢启动、快速恢复
http
应用层协议,报文格式:method + url + version + \n\n + body
响应码
401
402
403
restful
https
非对称加密。。。
零拷贝
零拷贝不是说真的只有0次拷贝数据,而是尽可能将多余的拷贝次数减少为0。正常情况下需要经过4次用户态内核态间的上下文切换与4~5次数据拷贝。1:程序调用system call 的read函数调用DMA(direct memory access)将磁盘数据拷贝到内核态缓冲区。2:再切换到用户态,将内核态数据拷贝到用户态缓冲区,java若用直接缓冲区的话是一次,用堆内存的话要两次,要先拷贝到堆外再拷贝到堆内。3:发起系统调用write将程序内缓存数据拷贝到socket缓冲区,再切回用户态。4:DMA异步拷贝socket内数据到网卡缓冲区。
mmap
直接将page cache 映射到 应用程序缓冲区,减少了从kernel space拷贝到 user space 的工作。api:MappedByteBuffer
sendfile
适用于大文件,网络框架tomcat、netty用的比较多。通过filechannel的transferTo方法用DMA将磁盘数据拷贝到socket缓冲区,减少了中间两次的数据拷贝和两次上下文切换。
BIO
面向字节
system call read -》 kernel space -〉 user space -》app space,每次读取一个页大小的数据到jvm,调api时按字节读
面向字符
同上,只不过可以读一个char或者readline
面向缓冲
字节和字符流的包装,用一块堆内存做缓冲区。
连接池
initial size
min Idle
最少要保证有这么多空闲的连接,少于就自动创建
max Idle
最多有这么多空闲连接,超过就回收
maxActive
最多有这么多活跃的连接,超过就尝试获取,超时maxWait就跑出异常
removeAbandoned
是否要回收(包括活跃的)
removeAbandonedTImeOut
存活多久的要回收
timeBetweenEvictionRunsMillis
每隔这么久检查一下空闲的线程空闲时间超过minEvictableIdleTimeMillis
minEvictableIdleTimeMillis
相当于keepalive time,超过这个时间的空闲线程会被回收
大数据
spark
hadoop
hbase
hdfs
消息队列
kafka
broker
物理上将kafka集群分为多个服务节点,一个服务节点就叫做一个broker
topic
消息的主题,逻辑概念,可以存储在多个节点,一个模块的生产消费者往一个topic中读写数据
partition
分区,物理概念,一个topic可以分为多个partition,一个partition存储在一个broker,而且对他的写入是顺序写,保证了磁盘io的速度
controller
集群控制器,用于协调集群,比如leader和follower,选举之类的
producer
生产者往指定topic生产数据,可以指定分区,也可以用默认对k哈希分区的方式,消息可以是kv形式的。
consumer
消费者从指定topic消费数据,同一时间只能被一个消费者消费,但是可以被多个消费者组消费,因为kafka维护的消费offset是以消费者组为维度的。
存储
元数据比如topic信息存储在zk,消息体存放在kafka文件系统。
性能
顺序写、零拷贝。
zk
作为kakfa的注册中心,使用kafka的分布式锁进行选举。
Java Web 框架
Servlet3.0
servletContextInitializer.startup()启动web容器
生命周期
init
service
destory
Filter
servlet规范,webFilter filterRegistration
fileChain,责任链模式
Listener
servlet规范,webListener listenerRegistration
生命周期
init
destory
Spring
Ioc
di的实现
生命周期
beanDefinition
beanFactoryPostProcessor
beanFactory getBean
实例化
循环依赖
创建步骤
判断A是否正在创建中,如果正在创建就抛出循环依赖错误,如果不在就创建对象A,然后放入beanFactory中,最后再给字段赋值初始化B,B又依赖A,如果B能从beanFactory找到A则完成,如果找不到就要去创建A。
检测方法
每次创建对象前都会将beanName放入一个map容器:singletonsCurrentlyInCreation ,创建对象前如果beanName已经存在于singletonsCurrentlyInCreation中,说明有循环依赖,抛出异常BeanCurrentlyInCreationException。
构造器注入
构造函数调用前将beanName存入map,未执行完就会去创建注入的对象,注入的对象又会去beanFactory找主对象,此时是找不到的,就会去创建,而创建时候又会发现主对象已经在创建中,抛出异常。
字段或set注入
属于懒加载,spring在创建完所有bean后才会反射去给字段赋值,不存在循环依赖。
prototype
因为每次get对象都会创建内部依赖的对象,这就又会发现在‘正在创建对象池’中,抛出异常。
Aop
使用场景:扩展类的能力。
日志记录,跟踪,优化和监控。
事务的处理,dao查询数据库。
系统统一的认证、权限管理等。
应用系统的异常捕捉及处理。
针对具体行业应用的横切行为。
事务的处理,dao查询数据库。
系统统一的认证、权限管理等。
应用系统的异常捕捉及处理。
针对具体行业应用的横切行为。
springAop
aop的实现
开启@EnableAspectJAutoProxy,postProcessBeforeInitialization时候创建代理对象
jdk动态代理
Proxy.newInstance生成一个class字节流给classloader加载,最后生成一个代理对象
aspectJ
aop的实现
切面
通知+切点
切点
被增强的方法
连接点
方法前后、抛异常处
通知
增强的功能
代理对象
原始对象
织入
增强对象的过程
注解
AutoWired,Resource
Controller、Service、RequestMapping、RequestParam,Resource、Value、RequestBody、ResponseBody。
SpringbootApplication,PropertySource,Configuration,MapperScan
ImportResource,ImportSelector,ImportBeanDeifinitionRegister
事务传播属性
REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
启动流程
tomcat遵循servlet3.0规范启动servletContextInitializer -》springContainerInitializer(注解handleTypes=WebApplicationInitializer)-》WebApplicationInitializer的 startUp方法,创建父容器将ContextLoaderListener注册到根容器,创建子容器,把DispatcherServlet注册到子容器。
contextLoaderListener注册到父容器引用父容器,负责Component,server,dao的初始化
dispatcherServlet注册到子容器引用子容器,负责Controller层bean的初始化
整合mybatis
在beanPostProcess时将beanDefinition中dao接口的替换成了MapperFactoryBean,将MapperFactoryBean的构造函数参数设置为dao接口的类名,因此在beanFactory生成的bean其实是MapperFactoryBean,它里面包含了dao的代理对象,getObject时获取的是它内部的dao代理对象。MapperFactoryBean内部还有SqlSessionTemplate执行sql语句用的代理dao对象(内部包含SqlSessionFactory用来管理数据库连接session的),它们都不是autowire的,是由注入模式0调用set方法注入进去的,这个点的精妙之处在于使用了spring管理对象而不依赖spring的代码。
自动装配
@SpringbootApplication包含@Configuration、@ComponentScan、@EnableAutoConfiguration包含@AutoConfigurationImportSelector调用importSelector方法扫描factories下自动装配类的全限定名
spring在importSelector时扫描在meta-inf下面spring.factories里面key是EnableAutoConfiguration的类,不用的会在启动时被过滤掉
如何过滤?根据自动装配类上面的@ConditionOnClass或者ConditionOnMissing去找classpath下面的依赖,选择过滤。一般我们会自定义一些实现,比如数据库的sqlSesstionFactory或者redis的template类,这时候@ConditionOnMissing就起作用了过滤掉了spring默认的实现类。
SpringMvc
拦截器,interceptor
filter是servlet规范的,interceptor是spring定义的,调用在filter之后
请求通过web容器转发到dispatcher,由它找handlermapping,找到对应的handler处理器也就是controller,序列化入参调用controller,执行业务返回结果反序列化给用户,视图解析则用viewResolver
默认可以访问静态资源 static,meta-info/resources等目录下,也可以用javaconfig方式通过addResourceHandlers(ResourceHandlerRegistry )配置
Mybatis
#{} sql预处理,防注入。${},无预处理,原始sql,可以注入。
Mybatis扫描对应目录下的xml文件,读取内部的class接口全名,使用动态代理生成dao对象,并使用sql语句作为前方法执行的逻辑。
- Mybatis拦截器,可以在sql执行前后扩展自己的逻辑,比如分页和日志。
连接池:minActive、maxActive、minIdle等
设计模式
创建型
工厂
定义创建接口,不同的字类创建不同的对象
抽象工厂
不同的字类创建不同的工厂对象,也叫做工厂的工厂
单例
创建唯一的对象实例
建造者
隐藏复杂的构造方法,提供一个简单的构造接口。
理解装饰器模式
包装一下提供更丰富功能,比如BufferedInputStream就是InputStream的封装,内部用一个堆内存缓冲区使inputstream 可以反复读取 mark reset。
享元模式
重用资源,比如线程池
迭代器模式
使不同的对象具备遍历的能力
外观模式
比如controller,封装一层外观层
代理模式
通过代理对象去调用原始对象,比如jdk动态代理
责任链
netty的handler,filterChain
模版模式
子类实现抽象父类接口
桥接模式
将多个独立变化的领域模型组合起来使用
linux
shell
vim、less、awk、sed、ln、scp、rsync、ssh、chmod、chown
jps、top、print "%x/n" id、jstack | grep id -A100, jcmd -v VM.heap_info
useradd、userdel、password、su、groupadd、groupdel
top、iostat、ifconfig、du --max-depth=1、df -h
nginx
HA
niginx upstream
主备
vip
设置虚拟ip(网卡0上添加一个别名)ifconfig eth0:0 166.111.69.100 netmask 255.255.255.0 up
keepalived
vrrp
自由主题
存储
Redis
线程模型
reactor
reactor
数据结构
sds动态字符串
set、 get、 append、getrange
字典
哈希表
hset k field、hget k field、hgetall、hincr、hdncr by
ziplist
列表
链表
lpush、rpush、lpop、rpop、lrange k start end、lindex k i、ltrim k start end、lset k i、lrem k v
ziplist
集合
哈希表
无序集合set,不能存储相同元素
sadd、smembers、scard
ziplist
有序集合
维护一个score用来排序
zadd、zrange、zcount k min max、zscore k member、 zincr、zdecr、zcard
跳表
操作
setex key val 5
setxx k v
setnx key val
incr incrby decr decrby
keys
禁用
scan cursor pattern count, 返回游标
备份恢复
rdb
几秒内有多少个写操作就全量保存内存中的数据到dump.rbd文件。太频繁会导致io开销大,太少会导致丢数据,数据量大时候每次写文件都要花很久时间。
aof
将用户的写增量写到appendonly.aof文件,一般使用1s调一次fsync的方式刷脏页到磁盘,同样也是比较io开销过大。因为保存了所有的写操作,所以几乎不会丢数据,但是在数据量很大的情况下重启预热会很慢。预留一半的机器内存,防止oom。
混合模式
在rbd,aof的基础上,redis后台线程使用bgrewriteaof方法将aof文件中的历史数据替换成rdb快照,减少了aof文件大小,同时也保留了rdb不丢数据的有点。默认在aof文件大于64m且每次增长超过原有大小时重写日志。开启no-appendfsync-on-rewrite会丢数据,关闭会和影响fsync的io。
选举
多master-多slave架构,不需要选举
水平扩展
添加一个集群(master+slave),client选择槽位重新分配到新的集群
高可用
主从,master+slave,当master挂了后slave编程master接收请求
数据一致性
主从间是最终一致性
集群通信
gossip
找最近的n个节点发送ping、pong交换信息,收到的节点和传染病一样继续传给下几个节点,注意过滤重复请求(根据seqId)防止网络风暴。
ping可能一时半会到不了某个节点,就pull一下附近的节点交换一下信息。
存储
16384个hash槽,请求由master重定向client到对应槽的服务器
缓存穿透
db查不到数据导致redis也查不到数据,压力全打到db,解决方案是把查不到数据的key存到redis,value可以设置一个特殊的常数
缓存击穿
缓存数据过期导致直接访问db
缓存雪崩
缓存大面积失效,缓存服务扛不住压力挂掉。解决方案:1.高可用redis主从。2.本地ehcache能抗一会。上下游业务限流。3.集群重启、缓存重建
多级缓存
存在缓存一致性问题,可以用aqs接口封装一个并发类将读写请求序列化执行,过滤重复读请求。
Mysql
索引
B树
非叶子结点存储数据,叶子结点无双向指针。一个节点存储大小为16k,因为包含数据,所以存储的索引值比B+树少,因此在同样多的索引数量,B树要比B+树更高,查询一个数据需要更多的io。
hash表
无法进行范围查询,无hash碰撞时O(1)的查询速度。
B+树
B树的变种,非叶子结点不存储数据了,因此一个节点能存储更多的索引值,相对的更矮了,2000W数据只需要1~3次io。叶子结点有双向指针串联,适合范围查找,跨多个叶子结点的查询需要多次IO查询叶子结点。
按索引大小来算,一个节点大约可存储1000个索引,2000w数据的b+深度也才3
组合索引
index(a,b,c)代表建立了1个索引:(a-b-c)
最左匹配原则,where条件必须包含索引的第一个字段。where字段可乱序,优化器会优化。
范围查询比较特殊,以where a= 1 and b <2 and c= 3为例,a走索引,b走索引,但是c只有当b确定时候才有序,所以它还得排序一下。
聚簇索引
innodb
主键作是聚簇索引,B+tree索引,叶子结点会保存整行数据,若没有主键,innodb会使用一个隐式的字段作为主键。
非主键索引为非聚簇索引,B+tree存储,叶子结点只会保存主键。
除主键外的索引都是二级索引,二级索引叶子节点包含主键Id,为了查聚簇索引
聚簇索引上存着两个隐藏列:trx_id, roll_ptr。
覆盖索引
使用非主键索引查询字段时可能会发生回表,回表就是说这个查询会查到主键,并用主键再去查字段。
索引下推,mysql5.6之后,引擎直接匹配索引字段过滤出数据,无需mysql筛选全表数据回表,比如select x from a where x like %s% and age >10,引擎直接过滤出age>10的数据后返回mysql过滤,若x也是索引就更简单了,引擎直接走覆盖索引筛选。一级索引(主键)因为包含了所有数据,所以直接返回mysql筛选。
优化
禁止3张表以上的联查,on字段要加索引,IN代替关联
延迟关联
select p.x1,p.x2 from p where i=j limint x,y 该sql若走了usingwhere 全表扫描就会回表,这个开销比较大,无视limit将所有符合where的数据查出来再进行limit筛选。解决方式是先走覆盖索引分页查出主键,再用主键查出数据。
覆盖索引
回表:sql没有走using index,走了 using where。因为非主键索引叶子结点只保存主键,如果查了别的字段,就还要用主键去查一次聚簇索引。根据业务情况,将要查询的字段建立到联合索引解决回表。。
主键使用自增
uuid存储空间大,作为索引比较大小时候要一个一个字符比较asc码,作为主键写数据是随机io。自增id就没这些问题,写是顺序写。
explain指令
type
All
全表扫描
index
全索引扫描
range
范围索引扫描
ref
返回索引到的多个行
ref-eq
返回索引到的1个行
const
where里面只有主键
null
最高效的,未扫描索引和表就返回结果。
extra
using index表示全索引扫描
using where表示全表扫描
事务
原子性
undolog
一致性
隔离性
同时运行的事务不应相互干扰。例如,如果某个事务进行多次写入,
则另一个事务应该观察到的是其全部完成(或者一个都没完成)的结果,而不应该看到中间的部分结果。
级别
读已提交 rc
描述
只能读到其他事务已经提交的数据
实现
和rr差不多,区别是read_view是每次执行语句时创建的
可重复读 rr
描述
一个事务哪不会读到其他事务已经提交了的数据
实现
mvcc,read view是开启事务时创建的
串行化 serialize
描述
所有事务完全串行化
实现
对同一批对象的事务加锁,让他们一个个串行化地执行
mvcc
每个tx开始后的第一条读sql创建一个read_view、一条undo_log快照,包含up_limit_txid, low_limit_txid,max_txid,之后的更新删除都是基于快照的。事务提交后不会立刻删除undo_log,而是标记删除后交给purge线程,待没有其他事务会使用它时候删除。
回滚的话是找事务自己的最新log做反向操作,通过undo_log的指针查找每一条要回滚的语句
回滚的话是找事务自己的最新log做反向操作,通过undo_log的指针查找每一条要回滚的语句
违反隔离性
脏读
描述:
客户端读到了其他客户端尚未提交的写人。若A回滚了则B就是读了脏数据。
解决方案:
rc以及更强的隔离级别可以防止脏读 。
脏写
描述:
客户端覆盖了另一个客户端尚未提交的写入。
解决方案:
使用数据库的行级锁
更新丢失
描述:
更新的是快照,但是最后会合并快照到聚簇索引上的数据,这时候会发生覆盖。
解决方案1:
乐观锁,比如cas version字段。
解决方案3:
悲观锁,比如 for update加锁。
写偏差,幻读
描述:
之前读的值好像是假的一样。值班系统要求至少有一人值班,A、B查询到目前已有2人在值班,同时点下班,导致2人都下班了。
解决方案:
加锁序列化。
undo log
回滚日志,引擎层控制,用于实现多版本并发控制快照隔离级别。和间隙锁、行锁、read_view一起实现了rr隔离级别快照读、rc。
聚簇索引上存着两个隐藏列:trx_id, roll_ptr。roll_ptr就是undo log的地址,多条undo_log组成的链表叫做版本链
事务中的每个update delete语句会插入一条undo_log 到 版本链中
更新普通索引
插入一条反向的日志
更新主键索引
删除数据行,再写一条新的
持久性
redo log
引擎层控制,确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘。在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性
lsn 逻辑序列号
数据中的lsn < redolog中的lsn说明数据丢失
binlog
mysql服务层控制
1:主从同步,2:灾备。
模式
row
记录所有被修改、添加行的值,导致文件较大,但是同步较statement模式简单。
statement
只记录写sql,文件比row模式小,但是同步起来比较麻烦。
mixed
每条写sql,mysql自动选择以上两种模式中的一种。
生成规则
当前Binlog超过500m时候,滚动创建一个新的Binlog
清理规则
配置一个失效时间,防止Binlog占用太多磁盘资源。
分布式事务
2pc,prepare commit/rollback, atomikos框架支持,jta java transaction api, prepare保证commit基本是可用的,比如prepare锁定库存,保证commit不会因为库存不够回滚,比如prepare可以发现db报错。这种模式可能会锁表或行,导致另一个事务卡住,而tcc不会。
可靠消息最终一致性
异步的最终一致性,不适用实时场景。
系统模块
下单模块(prepare 业务逻辑(下单)confirm)-》可靠消息服务-〉mq-》消费者
prepare confirm
这两步都是往可靠消息服务发送消息,只要都发送成功就算一致,消息中间件会保证后续的数据一致。
业务逻辑失败怎么办
回滚业务,prepare锁定的资源回滚
消息发送失败
可见发送prepare和confirm都会失败,prepare失败无所谓因为业务还未执行。confirm失败一段时候后消息服务可以查询业务执行状态,如果查到失败就删除消息,如果查到成功就将消息改为成功并且发送给mq。
消息丢失
producer发送失败,注册回调函数,如果结果失败就重发
mq丢失,以kafka为例,mq开启持久化,分片因子replication.factor设为3,isr节点至少一个min.insync.replicas=1,ack=all保证副本必须同步才算commit消息。
消费者丢失,关闭自动提交,执行完业务逻辑主动ack消息
消费失败
消费者完成消费后会ack给mq和消息服务,可靠服务一段时间后收不到ack就会重发消息到mq
重复消费
消费失败会导致消息重发,因此消费者必须保证幂等性,可以用唯一id判断(雪花snowflake)
人工补偿
消费者有bug导致一直不能ack,这种情况人工处理
柔性事务tcc try confirm cancle
业务模块需要开发三个接口:预留资源try,确认资源confirm,取消资源cancle,需要保证接口的幂等性
如果调接口超时可以查成功没,如果失败了可以重试,实在不行可以人工改数据库。
锁
行共享锁
xx in share mode ,可以并发读,不能并发写
行排他锁
for update, 不能并发读写
写
没有命中索引会对所有行加锁,再解锁
意向共享锁,表级
行共享锁之前加,为了提升性能,没有它的话其他事务想锁表就得去找每一行数据有没有锁。
意向排他锁,表级
行排他锁之前加,同上
谓词锁
给一个范围内的(可以理解为where 筛选出来的)数据加锁,gap_lock,net_key_lock
建表规范
流水表的时间唯独要加冗余字段-年/月/日/周/时
建索引前,要先考虑是否合适
建表/加字段时要不能仅仅考虑到添加和修改,还要考虑到查询的sql是否简单
b+索引一个节点16K,3000W左右的数据磁盘io大概在3次,月流水少于1000万的或月表小于1G的不要分表,更不要分区
ES
召回
索引
倒排索引(term:doc_id),segment
keyword
只支持全匹配
text
支持分词后建立term
建好之后不能再修改,主要是考虑到重新建立索引带来的性能问题
字段修改类型,不影响老数据
数据结构
term index
fst finite state transducers 有限状态转换器
posting list
FOR
frame of reference
1~255这种稠密的doc_id
RBM
roaring bit map
稀疏的doc_id
3种container
array container
bit container
run container
除2^16得商和余数
用两个2^16bit数组保存int值
水平扩容
加机器,机器越多,每个机器加载的数据量越少
高可用
sharding机制,一个索引有多个shard,由不同的node加载,保证了高可用
一致性
有协调者请求master去做数据同步,master会把数据写到shard所在的node上,如果哪个写入失败就会把那个shard给踢掉,所以是最终一致的
选举
类似raft的
压缩
variable byte
存比特,比如int(32位)数组,有很多元素小于2^7,可以用1个字节存储
差值 variable byte
如果是时序数据,后-前肯定是小数字,就比较适合了
集群
协调者、master、数据节点
网络
5层架构
应用层
http
https
tls
用非对称加密算法加密 “对称加密算法的密钥和算法”,在客户端和服务端之间传输他们
用ca证书保证非对称加密公钥的安全性
websocket
使用http协议建立连接,之后通过ws协议通信
传输层
封包-port
网络层
封包-ip
数据链路层
封包mac
物理层
交换机协议
安全
对称加密
AES
摘要、盐、key
非对称加密
RSA
摘要、公钥、私钥、盐
盐
用户id,防止被猜出一个后影响到所有人
oauth2.0
authorization code
用户请求后端,控制权限
client credentials
微服务间请求
netty
reactor
注册自己感兴趣的事件,事件分发器(selector)等待感兴趣的时间发生,交给事件处理器
同步非阻塞,同步是说轮询是同步的,非阻塞是说应用程序发起读写请求后立刻就能返回。
single reactor
只有一个selector监听所有channel的所有事件
multi reactor
多个selector分别注册channel的一些事件,netty就是这么做的,多个boss,多个worker,boss只处理连接,连接后打开的通道交给worker处理。worker也有多个,可以很好的分散压力到多个worker线程。
proactor
异步非阻塞,非阻塞是说不需要同步轮询事件了
reactor的加强版,reactor需要事件处理器去读写数据(从磁盘拷贝到用户程序缓冲区),而proactor直接就帮你读写完了,完了之后再异步通知你,应用程序就能直接使用用户态缓冲区的数据了。
channel
通道
pipeline
由HandlerContext组成双向链表,每个节点对应一个handler,InBoundHandler负责入方向数据处理,OutBoundHandler负责出方向数据处理
boss group
负则轮询 serverSocket accept的请求,accept的socket channel通道交给workGroup处理
worker group
负责监听socket channel的状态,
EventLoopGroup(多个线程)
一个线程管理多个EventLoop
多路复用,管理多个channel
NLP
bert
用了transformer的encoder
bert-base 1亿参数量
gpt2
用了transformer的decoder
去掉了gpt的fine tuning任务
15亿参数量
faq
召回
建索引
ES 天然分布式
Annoy 本地内存,还需要考虑高可用
排序
bert
微服务
springboot
eureka
zuul
ribbon
随机、权重、轮询
最小连接数、最大剩余连接数
根据压测结果、机器配置、响应时间等选择自定义算法
feign
hystrix、sentinal
限流
滑窗
固定窗口大小,客户端和服务端共同维护窗口。
漏桶
固定桶大小,故处理请求速度不能大于桶大小,不能处理突发增加的流量。
令牌桶
固定生产速度,固定桶大小,可以处理突发流量。
熔断
保护服务不被报错请求拖垮
收藏
收藏
0 条评论
下一页