java 内存区域、JMM、JAVA线程模型、硬件内存模型
- java内存区域分为共享区域(堆、方法区常量池)、私有内存区域(程序计数器、虚拟机栈、本地方法栈)
- java 内存堆
- 方法区常量池,方法区主要存储虚拟机加载类信息、常量、static变量、动态编译器编译的信息
- 程序技术器,记录方法循环、控制流程等计数信息
- 虚拟机栈,每个方法都一个栈帧包含本地变量、操作数表、动态链接方法、返回值、返回地址
- 本地方法栈,是用来记录本地方法
- JMM(java内存模型),JMM是一组内存分配规范,JVM为每一个线程都分配一块工作内存,JVM规定类中的成员变量存储在堆中,方法的本地变量存储在栈中,线程方法运行前将所有变量从主内存拷贝到工作内存,完成操作后,修改主内存中的变量
- JAVA线程模型,java中线程与内核中lwp(Light weight process)一一映射
graph TB
A[任务]-->D[Executors]
B[任务]-->D
C[任务]-->D
D-->E[Thread LWP]
D-->F[Thread LWP]
D-->G[Thread LWP]
E-->H[内核线程]
F-->I[内核线程]
G-->J[内核线程]
H-->K[OS kernel线程调度器]
I-->K
J-->K
K-->L[CPU]
K-->M[CPU]
K-->N[CPU]
- 硬件内存模型,分为cpu、寄存器、缓存、内存
java的对象创建及内存分配
对象创建
- jvm检查常量池里面是否有类加载标示,如果没有先加载类
- 内存分配方式是指针碰撞和空闲列表分配
- 指针碰撞主要是使用规则的内存块,根据指针和偏移长度分配,但是这种方式受垃圾回收器影响
- 空闲列表分配是jvm会存储空闲内存区域列表,根据空闲内存列表进行内存分配
- 对象的使用是栈来引用堆中的内存对象
内存分配
- java堆内存分为新生代和老年代
- 新生代包含EDEN和两个survivor区,内存分配默认是8:1:1
- 当eden区分配内存不足时,会进行minorGc,新生代会将存活的对象根据复制算法复制到另一个不使用的survivor区。
- 当survivor区内存不足时,会采用分配担保策略将对象移植到老年代
- fullGc是发生在老年代gc会触发stop the world停顿,所以需要避免发生full gc
- 对象的内存分配一般在新生代
- 对象的内存分配当EDEN区没有足够的连续内存会先触发一次Gc,然后在老年代中进行分配,因此不能进行大对象分配
volatile关键字
- 可见性
- 指令重排
多线程的重要特性
- 原子性
- 可见性
- 顺序性
- happens-before
- 传递性,
- 线程内执行顺序一致性
- 一个线程的start方法是所有的开始
- 一个锁的unlock必然在后续同一个锁的lock之前
- volatile,一个线程内变量读取必须从主内存读取,对变量的写操作会同步刷新到主内存
- 对象的构造方法必然早于finalize方法
- 线程的所有操作都早于线程的终止
- 对线程的interrupt方法调用先行发生于被中断线程检测到中断事件的发生
引用(软引用、弱引用、虚引用)
- 软引用softreference,主要用于内存敏感的高速缓存,当GC内存不足的时候才会回收
- 回收时首先将referent设置为null,不再引用堆中的对象
- 将堆中对象设置为finalizable
- 调用finalize方法将对象释放,引用会放入RefrenceQueue中
- 弱引用weakreference,GC会直接回收,过程类似软引用,使用场景就是使用但是不用关心回收的场景
- 虚引用phantomreference,虚引用直接把对象放入到referent中,只是把get返回结果置为null,然后把heap中的对象设置为finalizable,GC把phantomreference放到ReferenceQueue中,然后释放虚对象。使用场景是资源释放就可以把对象设置为虚引用。
synchronized关键字解析
使用场景
- 静态成员变量,将会锁当前class所有对象
- 实例方法,将会锁定当前实例方法
- 代码块,将会锁定指定实例对象或指定class
锁定原理
- 对象锁,对象分为对象头、对象实体、填充信息,锁的信息放在对象头markword里面,markword中锁标示指向对象监视器monitor;编译器在执行编译的时候,对synchronized对象前后增加monitorenter、monitorexit方法,当方法异常的时候也会执行monitorexit方法保证锁的释放;对象监视器为结构为waitset、entrylist、锁定标示,多线程获取锁进行entrylist,当前线程释放锁后修改锁定标示,然后从entrylist获取下一个线程信息,变更锁定标示为锁定状态,当线程进入wait状态会将线程写入waitset;
- 方法锁,主要依靠常量池方法的ACC_SYNCHRONIZED标示,标定是否是锁定状态
锁优化(jdk1.6)
- 偏向锁,对象锁markword锁标示默认是指向偏向锁的,当然一个线程多次获取同一个锁时,使用偏向锁,当多线程获取锁定对象时,膨胀为轻量锁
- 轻量锁,对象锁markword锁标示执行轻量锁,当多个线程交替获取锁时为轻量锁经验认为90%的情况是两个线程交替执行较多,当多个线程互相竞争获取锁时,锁膨胀为自旋锁
- 自旋锁,自旋锁,通过线程循环空转,尝试获取锁,一般次数空转次数为50-100次,仍然无法获取锁,则将调用系统将线程挂起等待,膨胀为重量锁
- 锁消除,当一结构在使用场景无多线程竞争,则删除锁
其他
- synchronized是通过编译器,修改该字节码进行锁定和释放的,因此是隐式锁;与之相对应的是ReentarentLock是通过AQS实现的,需要明确的进行锁的增加和释放
- synchronized与ReentrantLock都是重入锁,允许通过当前对象锁定线程多次对当前对象进行加锁
ReentrantLock实现原理
- ReentrantLock是基于AQS实现的,是重入锁
- ReentrantLock可以初始化为公平锁和非公平锁,默认为非公平锁,一般情况下非公平锁效率更高
- FairSync(公平锁)当获取锁的时候,首先会检查等待获取队列里面是否有线程,有线程则挂起线程加入队列不获取锁
- NonFairSync(非公平锁)当获取锁的时候不进行队列检查,直接获取
多线程常见问题
上下文切换
CPU给线程分配时间片,然后进行上下文切换
解决方案
- 无锁设计分段
- 采用CAS方法
- 合理创建线程
死锁
两个线程分别等待对方释放锁
解决方案
- 尽量一个线程只获取一个锁
- 一个线程只占用一个资源
- 尝试使用定时锁,最终锁会释放
资源限制
CAS原理(常用于无锁化设计)
- 对比原值是否相等,确定相等,然后进行更新操作
- Atomic调用unsafe类进行compareAndSet,unsafe调用操作系统的compare and exchange指令,多核时使用锁总线和锁缓存
- CAS容易遇到ABA问题,即其他线程已经将数据进行修改了B,然后又修改回了A,此时CAS命令无法处理,可以使用时间戳标示如AtomicStampedReference
JAVA并发包(java.util.concurrent)
- Executors:ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future、FutureTask、ForkJoinPool、ForkJoinTask
- Queue:LinkedBlockingQueue、ArrayBlockingQueue、SynchronizeQueue、PriorityBlockingQueue、DelayQueue
- Queue的默认方法为add(添加成功返回true失败抛出异常)、offer(添加成功返回true失败返回false)、put(添加队列满则阻塞)、remove(删除对象成功返回true失败返回false)、poll(取出队头数据,失败返回null)、take(取出队头数据队列空阻塞)、element(获取但不移除队列的头元素,没有元素抛出异常)、peek(获取但不移除队列的头元素,没有元素返回null)
- 结构区分:ArrayBlockingQueue底层基于数组是有界队列,LinkedBlockingQueue底层基于队列可以是有界也可以是无界
- 性能区分:ArrayBlockingQueue底层put、take使用一个ReentrantLock,LinkedBlockingQueue底层put、take分别使用两个ReentrantLock因此吞吐量大于ArrayBlockingQueue
- 空间区分:ArrayBlockingQueue基于数组因此队列的添加和删除不产生额外的数据,linkedBlockingQueue基于链表添加的时候会额外产生node对象,大批量增加的时候会产生大量数据对GC有影响
- SynchronizeQueue 是一对一阻塞队列
- Timing:TimeUnit
- Synchronizer:Semaphore、CountDownLatch、CyclicBarrier、Phaser、Exchanger
- Semaphore可以设置允许的多并发量
- Concurrent Collection:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList、CopyOnWriteArraySet
- Memory Consistency Properties:
- Locks(java.util.concurrent.locks):ReentrantLock、ReentrantReadWriteLock、StampedLock
- Atomic(java.util.concurrent.atomic):AtomicBoolean、AtomicInteger、AtomicIntegerArray、AtomicIntegerFieldUpdater
、AtomicLong、AtomicLongArray、AtomicLongFieldUpdater 、AtomicMarkableReference 、AtomicReference 、AtomicReferenceArray 、AtomicReferenceFieldUpdater 、AtomicStampedReference 、DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、
java中finally详解
- finally当在return不在try finally块中的时候finally不执行;当在try finally中执行system.exit的时候finally不执行
- finally块中和try块都有return语句,则finally中return先于try块中return执行;try中有return语句,finally中无return语句时,finally会在最后执行。
java中异常的层级结构
graph BT
A[IOException]-->B[Exception]
G[Error]-->F[Throwable]
B-->F[Throwable]
C[ClassNotFoundException]-->B
D[RuntimeException]-->B
E[CloneNotSupportException]-->B
H[FileNotFoundException]-->A
I[NullpointException]-->D
J[ClassCastException]-->D
k[EOFException]-->A
JVM定义内存区域划分
- 虚拟机是抽象化的计算机,运行在实际的计算机中,有完整的硬件、堆栈、指令系统。jvm通过虚拟机与操作系统交互,虚拟机只运行标准字节码,实现了跨平台不做修改的运行。
- JVM是一个普通的进程
- JVM包含类加载子系统、执行引擎子系统、垃圾收集子系统
- JVM类内存区域内存堆、虚拟机栈、方法区、常量池、本地方法区、程序计数器
JVM参数设置
- 推荐设置堆内存为full gc后老年代内存的3-4倍,-xms -xmx
- 推荐设置年轻代内存为full gc后老年代的1-1.5倍,-xmn
- 推荐设置老年代内存为full gc后老年代的2-3倍
- YGC设置大可以避免full gc,增大吞吐或者响应速度
- 增加survivor空间比例可以有效减少promotion failed
- 设置CMSFullGCBeforeCompaction=0可以开启对老年代的内存空间的整理
- 使用hashmap作为缓存的时候,缓存不应该无限长建议使用LRUMap
JAVA基础分析工具
- jps -m -l 查找java进程以及main方法,可以用ps -ef | grep xxx代替
- jstack pid 根据进程的堆栈信息,top -Hp pid 查找最耗性能的线程ID,然后根据线程ID(转为16进制)找到线程堆栈
- jmap 查看堆内存使用情况,也可以dump出来通过MAT查询
- jstat 查看jvm运行状况
classloader加载
- 类加载过程,加载(load)->验证(verify)->准备(Prepared)->解析(resolve)->初始化(initialize)
- 类加载器依次是Bootstrap加载器->扩展加载器(extend)->应用加载器(System)
- 类加载使用的双亲委托的模式
- 双亲委托的破坏者是线程上下文类加载器,可以逆向加载SPI核心
对象加载与反射
- 对象必须初始化的条件:
- new 或者读取设置一个类的静态字段(不包含编译常量)以及调用静态方法的时候,必须触发类的初始化过程.
- 使用反射包进行反射调用的时候,如果没有初始化必须进行初始化
- 初始化一个类的时候父类没有被初始化,先初始化父类
- 虚拟机启动时候的main方法类
- jdk1.7动态语言支持
- 反射是动态运行获取对象,包含Field\method方法
- 通过反射可以动态创建数组
enum类型
- 枚举的基础是final class extends Enum
- 反射无法动态创建枚举类
- 枚举扩展类包括EnumMap,EnumSet(位向量实现)
redis vs memcached
- redis支持多种的内存数据结构和丰富的内存操作,可以使大部分操作都像处理get/set一样高效
- 内存使用效率对比,普通的结构memcache更加高效,使用redis的hash结构内存的使用效率更加高效。
- 性能对比,redis只能使用单核而memcached可以使用多核。在小于100k的数据存储上,redis单核效率更高,而大于100K的数据结构memcache效率更高
redis数据结构详解
string
默认为是字符串sdc.h结构{int len,int free, char[] def},当使用incr decr时为数值类型
hash
redis hash当长度小于512且每个value均小于64kb时,hash结构为ziplist,ziplist可以使用紧凑的结构实现多个元素的连续存储,因此ziplist可以有更好的提高内存使用效率;当长度大于512时,hash结构为hashtable作为内部的实现方案。redis hash结构的resize使用了物理均摊策略,触发resize后会生成isRehasing标志,期间每一个hget和hset均进行相应的数据entry挪动,并且后台也有线程进行分批次迁移。迁移的过程中,get 先从old table读取,然后从 new table读取,让迁移完成后进行hash执行newtable,然后将isRehashing设置为true。
list
list内部结构默认是双向链表,支持反向查找和遍历,同时记录了链表内记录了链表的长度
set
set是一个value为null的hashmap,通过key的hash进行排重
sorted set
sorted set内部使用hashmap和skiplist进行数据存储,
hyperLogLog、Geo、PubSub、BloomFilter(Bitmap)、RedisSearch、Redis-ML
redis常问知识点
- 分布式锁,使用setnx+expire,setnx可以批量处理
- redis搜索,当数量巨大时可以使用不影响性能的scan
- redis异步队列,使用list做异步队列,消费者轮询lpop,也可以使用blpop不轮询阻塞读;如果要一个生产者多个消费者可以使用pub/sub,但是pubsub不会在消费者下线后存储消息,生产的消息丢失,因此要使用专业的mq;可以使用redis sortedset做延时队列,使用时间戳作为score,消费者使用zrangebyscore获取之前的数据处理
- 如果有大量的key设置同一时间过期,可能会引起redis的停顿,可以在时间戳上加上随机值,让redis过期更加均匀一些
- redis持久化通过rdb和aof进行,rdb是定时全量备份恢复时间短,aof按照一定的策略记录日志,但是恢复时间较长,redis会定期做aof重写;redis4.0使用混合模式将进行数据备份可以保证在数据较少丢失的情况下,快速恢复;
- pipeline 将多次io压缩成一次处理,对于前后没有关系的命令可以使用这个方法可以增加吞吐量
- redis主从同步机制,第一次主节点做一次bgsave,并同时将后续操作记录写入内存buffer,待到rdb文件全量同步到复制节点,复制节点将rdb文件全量加载到内存,加载完成后通知主节点将数据同步期间操作,同步到复制节点完成同步操作
- redis集群;redis sentinal强调高可用,master宕机后自动将slave提升到master继续操作;redis cluster强调扩展性,在单个redis内存不足时,使用redis cluster进行扩展分片存储,最多可以扩展到16384个簇。
- redis的最大内存策略失效策略,默认noeviction(不置换直接报错)、allkeys-lru 优先删除最近最不常使用的数据、volatile-lru 只从设置了失效时间的key里面按照lru删除、volatile-ttl只删除离失效时间最近的key、all-random 从所有key里面随机删除、volatile-random从expire set里面随机删除
redis keys * scan
- keys * 的缺点
- 没有offset、limit一次性输出全部的数据
- keys 是遍历key数据过大的时候会产生卡顿
- keys 匹配模式
- scan 的特点
- 游标不阻塞线程
- limit 限制返回的数据量
- 提供匹配模式
- 服务器端不保存游标数据,游标的状态是返回给客户端的数据
- 返回的结果可能有重复
- 遍历的过程中,有对数据的修改,新修改的数据是否能够轮询到不确定
- 单次返回的结果为空并不代表遍历结束,要看到返回的游标值是否为空
redis常见问题总结
- server端
- 内存规划,bgsave造成的内存溢出。通过优化内存最大值,更改linux参数设置
- 连接数超出。增大连接数限制和客户端优化
- AOF性能问题。关闭AOF或者使用async everysec
- client端
- 使用Pipline和批量命令。
- 线上发现任务执行缓慢问题
- ps -ef|grep java 找到进程id,或者jps -v
- jstack 导出日志,发现大量redis连接
- 代码中循环使用redis实时连接
- 错误使用数据类型和操作命令
- 使用string,存储数据,客户端查找;可以使用key:主键查找或者hash存储
- set客户端查找,判断是否包含存在直接客户端取出全部进行内存查找
- incr或hincr数据增长,客户端操作
- 不设置失效时间。导致内存暴涨
- 使用Pipline和批量命令。
netty线程模型
- reactor反应模型
- channelFuture是java.util.concurrent.Future的接口扩展实现
graph TB
A[serverSocketChannel]-->|多Channel|B[NioEventLoop,Selector]
A-->|多channel|C[NioEventLoop,Selector]
B-->|Channel,ByteBuf|D[channelHandlerPipline]
C-->|Channel,ByteBuf|E[channelHandlerPipline]
netty粘包和拆包
TCP包按照流的方式进行传输,根据数据大小、缓存区不同、TCP/IP报文最大大小、以太网payload帧大小不一致导致的TCP包的拆分和重组。
- 按照固定长度进行拆包和粘包
- 按照固定分隔符进行拆包和粘包
- 按照协议头和协议体进行拆包和粘包
- netty所有协议解析均继承自ByteToMessageDecoder、ChannelHandlerAdapter,目前netty常用的解析器为FixedLengthFrameDecoder、LineBasedFrameDecoder、DelimiterBasedFrameDecoder、HttpRequestDecoder、HttpResponseDecoder
netty zero-copy
netty的zero-copy是通过ByteBuf的操作实现数据的读取,避免了重新定义newarray
http 协议相关、get、post、tcp/ip
- http超文本传输协议
- http请求分为请求头和包体
- HTTP1.1默认为keep-alive,可以采用多个连接pipline方式通讯,严格限制请求和响应的顺序;可以采用ascII和二进制混合传输方式
- HTTP2建立到服务器的单链接,使用mulit方式通讯,请求和响应没有严格的顺序,提高了传输效率;http2采用二进制传输方式,且可以服务端推送消息
- get方式请求参数直接放在URI,通常用于查询操作,由于可以在浏览器端明文展示用户信息,且部分浏览器会对请求连接进行缓存,相对不够安全,在部分浏览器和服务器有请求长度的限制
- post方式请求参数放在http请求包里面,常用于数据传输、增加更新等操作,由于在浏览器端无法直接看到请求参数,且浏览器对于post参数默认不进行缓存,相对于get较安全,传输参数长度理论上无限制,只受限于服务端设置的大小
- tcp分为三次握手和四次挥手协议。
超时重发:发送者向接受者发送包、当达到超时时间没有回复就会重发;如果一次性发送三个包,如果收到了最后一个包的确认就默认三个包全部发送成功。
滑动窗口:通过多个包边发边接受用来提高效率。这种就是滑动窗口协议
三次握手
sequenceDiagram
client->>server: sync
Note left of client: SYNC_SEND
server-->>client: sync+ack
Note right of server: SYNC_RCVD
client->>server: ack
Note right of server: ESTABLISED
Note left of client: ESTABLISED
四次挥手
sequenceDiagram
client->>server: fin
Note left of client: FIN_WAIT-1
server-->>client: ack
Note right of server: CLOSE_WAIT
Note left of client: FIN_WAIT-2
server->>client: fin
client-->>server: ack
Note right of server: CLOSE
Note left of client: TIME_WAIT
ACID、CAP、BASE
- ACID事务的特性,原子性Atom、一致性Consistency、隔离性isolation(read uncommited未提交读、readcommited已提交读、repeate read可重复读、serialize串行化)、持久性durability
- CAP分布式系统的一致性consistency、有效性available、分区容错性partitionTolerance,分布式系统一般只能满足其中两个
- BASE是可用性Base available,软状态Soft state,最终一致性Eventual consistency
SQL优化、分库分表
- sql优化
- 负向查询不走索引
- 前导模糊不走索引
- 字段计算无法使用索引
- 符合索引最左前原则
- 只有一条语句直接limit 1
- join的字段类型要相同,才可以命中索引
- 字段的默认不要为null
- 数据区分不明显不建议创建索引
- 分库分表
- 水平分表
- 按ID取模、时间或固定行数进行分表
- 分表后查询语句变得复杂,可以按照多个语句进行查询然后在进行合并
- 垂直分表
- 当数据字段过多时将数据按照使用频次拆分为主表和扩展表
- 仍然建议分开查询
- 两段提交
- 保证最终一致性,A调用B,两个执行成功才算成功
- 方案为失败时B通过MQ将消息通知A,A再来回滚。前提是A的回滚操作是幂等的
- 水平分表
spring boot启动流程(SpringApplication.run(args))
- 初始化SpringApplication实例,然后调用run方法;初始化实例之前会进行几步操作
- 根据classpath的特征推断ApplictionContext类型是Web类型还是Standalone类型
- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer
- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener
- 推断并设置main方法的定义类
- SpringApplication初始化完成并完成设置后,就开始执行run方法;run方法首先遍历执行通过SpringFactoriesLoader查找并加载的SpringApplicationRunListener,并调用他们的start()方法
- 创建并配置当前SpringBoot将要使用的Environment(包括配置要使用的PropertySource以及Profile)
- 遍历调用所有SpringApplicationRunListener的environmentPrepared方法
- 根据设置showBanner
- 根据用户是否明确设置了applicationContextClass类型以及初始化推断结果,决定改为当前springBoot应用创建什么样类型的ApplicationContext并创建完成,判断是否添加shutdownHook,判断是否使用自定义的BeanNameGenerator,判断是否使用自定义的ResourceLoader,将准备好的Environment设置给ApplicationContext使用
- ApplicationContext创建好之后,会再次借助SpringFactoriesLoader,查找并加载所有可用的ApplicationContextInitializer,并调用其initialize方法对ApplicationContext进行进一步处理
- 遍历调用所有的SpringApplicationRunListener的contextPrepared方法
- 将@EnableAutoConfiguration获取的所有配置以及其他形式的IOC容器配置加载到ApplicationContext
- 遍历调用所有的SpringApplicationRunListener的contextLoaded方法
- 调用ApplicationContext的refresh方法,完成Ioc容器的最后一步工序
- 查找ApplicationContext中是否注册有CommandLineRuner,如果有遍历执行他们
- 正常情况下遍历执行SpringApplicationRunListener方法的finished方法,出现异常也会调用,只是会将异常信息一并传入处理
graph TB
A((开始))-->B[加载ApplicationContextInitializer和ApplicationListener]
B-->|SpringApplicationRunListener.start|C[load Environment]
C-->|SpringApplicationRunListener.environmentPrepared|D[init ApplicationContext,set Environment,load Property]
D-->|SpringApplicationRunListener.contextPrepared|E[ApplicationContext Load Other Ioc]
E-->|SpringApplicationRunListener.contextLoaded|F[refresh ApplicationContext]
F-->|Run CommandLineRuner,SpringApplicationRunListener.finished|G(结束)
Spring IOC与AOP,BEAN的生命周期,spring mvc处理流程
- IOC指的是IOC容器,IOC通过DI(依赖注入)和DL(依赖查找)进行工作
- IOC的作用是收集和注册Bean、分析和组装Bean
- AOP的底层原理是依赖于动态代理,基于接口实现动态字节码,生成新的类;
AOP也可以通过CGLib进行,是基于子类的,通过ASM生成动态字节码进行类的加载 - Spring Bean的生命周期
graph TB
A[Bean实例化]-->B[属性设置]
B-->C1[BeanNameAware.setBeanName]
C1-->C2[BeanFactoryAware.setBeanFactory]
C2-->C3[ApplicationContextAware.setAppliactionContext]
C3-->D[BeanPostProcess.setBeanPostBeforeInitialization]
O[continue]-->E[InitializingBean.afterPropertiesSet]
E-->F[init-method定制的初始化防范]
F-->H[BeanPostProcess.setBeanPostAfterInitialization]
H-->|Bean准备就绪|J[DisposeBean.destory]
J-->K[destory定制的销毁防范]
- Spring Mvc的处理流程
sequenceDiagram
Client->>DispatchServlet: request请求
DispatchServlet->>handlerMapping: 请求Handler
handlerMapping-->>DispatchServlet: 返回执行链
DispatchServlet->>handlerAdapter: 请求适配器执行handler
handlerAdapter->>handler: 执行handler
handler-->>handlerAdapter: 返回结果
handlerAdapter-->>DispatchServlet: 返回ModelAndView
DispatchServlet->>ViewResolver:请求视图解析器
ViewResolver-->>DispatchServlet: 返回view
DispatchServlet->>view视图jspfreemarker:视图渲染
DispatchServlet->>Client: response
- springMvc vs Struts
- Struts2是基于类拦截,spring mvc是基于方法拦截的
- Struts2拦截器是自己实现的,spring mvc是基于AOP的
- Struts2入口是filter,spring mvc是基于sevlet的
- Springmvc集成ajax,struts2需要第三方支持
- Spring mvc跟spring 更能无缝配合
缓存淘汰策略LRU\LFU\FIFO
- LRU最近最少使用,通常实现方式为hashmap+链表,使用通过hashmap查找,然后将当前值移至链表头部,删除的时候就直接删除链表的tail。
- LFU最近最小频率使用,常用实现方式为hashmap+数组,hashmap查找,数组记录数据和使用次数
- FIFO先进先出。使用hashmap+链表方式,hashmap查找,链表记录数据,删除tail数据。
zookeeper使用场景
- 配置中心
- 负载均衡(dubbo)
- 命名服务(naming service)
- 分布式通知协调
- 分布式锁
- 分布式队列
- 集群管理与Master选举
消息队列常见的问题
- 消费端重复消费,建立去重表
- 消费端消息丢失,消费完成才能确认,kafka中消费完成后返回offset
- 生产者重复发送,消费端根据去重表删除
- 生产者消息丢失
- 消息队列满了,进行阻塞重复发送
- 建立回调,消费完成后会进行回调
dubbo服务化分析
- 高性能RPC
- 自定义协议
- 基于netty的长连接
- 堆缓冲区
- 服务发现与治理
- 服务发现zookeeper、redis
- 路由、集群容错、限流(并发限流基于信号量限流)
- 负载均衡(权重分配、最少活跃优先、一致性hash、roundrobin)
- 服务动态配置
- 服务隔离
- 自定义熔断机制
- hystrix机制
- 启动与停机
- 延迟暴露
- 服务预热
- 优雅停机
日志分析理解
- 日志使用频率是低频的,要求准实时的,因此数据直接放入elk
- 日志随着时间越长价值越低,因此超过一定时间日志需要进行冷备份
- 日志需要提前明确日志的规范,可以方便查找与统计