- 内存模型以及分区
JVM分为虚拟机栈、堆、方法区、本地方法区
堆
,用来存放实例化对象、非static成员变量,属于线程共享
的,并发处理的主要就是
堆中的共享虚拟机栈
,由栈帧
组成,每个方法运行打包成一个栈帧,当前正在执行的方法就是
虚拟机栈顶的栈 帧,栈帧缺省大小1M,可通过-Xss
256k进行设置,栈帧由局部变量表
、
操作栈
、动态连接
、返回地址
方法区
,用来存放类信息、静态常量编译后的字节码文件等,本地方法区
,用来存放native方法服务程序计数器
,记录程序执行的行号
- 堆的内存区域划分和特点
分为
新生代
、老年代
、永久代
(永久代1.8及以后替换为metaspcace元空间
)
- 新生代,包含Eden区和
survivor
from
、to
区,分配比例为8:1:1,新建对象存放在Eden区,当Eden区空间
不足会触发GC进行回收,幸存者进入survivor区,由于回收率较高一般采用复制算法
- 老年代,经过
N次GC后幸存的对象
会进入老年代,大对象
创建会直接放入老年代,当老年代 空间不足
时触发full GC
- GC 的判定方法
引用计数法
,当对象被引用时计数器+1,被释放时,计数器-1,当为0则会被回收,会存在相互
引用的问题可达性分析法
,是通过计算GC roots的对象向下搜索,不能达到的对象进行回收,GC roots
对象有以下几种
①虚拟机栈中引用的对象
②方法区类静态属性引用的对象
③方法区常量池引用的对象
④本地方法栈 JNI 引用的对象
- GC有哪些内存回收算法
复制算法
,会浪费一半内存
,内存移动,需要修改引用标记清除
,产生大量不连续的碎片内存
,大内存分配不便标记整理
,通过标记、整理、删除,优点是内存连续但耗时长
- GC收集器有哪些,特点是什么
serial
是新生代收
集器,单线程
独占,速度快,会阻塞,采用复制算法
ParNew
是新生代
收集器,多线程
版serial,采用复制算法
Parallel Scavenge
,吞吐量优先
的新生代收
集器,采用复制算法
,多线程
回收,适合后台运算,不需要太多交互任务Serial Old
,老年代
收集器,采用单线程
,采用标记整理
算法Parallel
,老年代
收集器,多线程
,注重吞吐量(执行时长/(执行时长+垃圾回收时间) 即使用率),采用标记整理
CMS
是并行与并发
的收集器,收集时间短,适合快速响应,采用标记清除
算法,JVM可以设置N次FGC
后进行一次标
记整理,执行过程如下
一、
初始
标记,短暂STW,标记GC roots可达的对象引用
二、并发
标记,GC roots Tracing(GC roots 追踪)
三、重新
标记,短暂STW,修整标记期间变动记录
四、并发
清理。两次STW时间都比较短,整体来算是并发的。
G1
,跨越新老年代,将内存切分为N个region局部块
,每块都有新老年代,整体采用标记整理算法,
只对部分region区域进行增量清理,所以G1的STW时间短,整体执行过程如下
- 新生代GC,Eden清空,幸存进入survivor
- 并发清除周期,具体步骤如下,
a.根区域扫描,survivor能进入olden的
b.并发标记
c.重新标记
d.独占标记
e.并发清除
- 混合回收,含有垃圾比例高的region进行回收
- 可能的fullGC
ZGC
,内存划分为一个个region区,没有分代概念,每次清理所有的region,采用分区复制进行清理
,执行过程如下,
- Pause Mark Start 初始停顿标记
- Concurrent Mark并发标记,递归标记其他对象
- Relocate移动对象
- Remap - 修正指针
- 简述JVM垃圾回收机制
JVM采用了一个
垃圾回收的守护线程
,当虚拟机空闲或堆内存不足
时,才会触发执行,扫描
未被引用的对象,把它们添加到要回收的集合中,进行回收
- java内存模型是什么
java 内存模型(JMM)是
线程间通信的控制机制
,JMM 定义了主内存和线程之间抽象关系。
线程之间的共享变量存储在主内存(main memory)
中,每个线程都有一个私有的
本地内存(local memory)
,本地内存中存储了该线程以读/写共享变量的副本
。
本地内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,
寄存器以及其他的硬 件和编译器优化。
- 类的加载过程
加载
,首先加载二进制字节流
,并转换成方法区或元空间的运行时结构
,在内存中生成class 对象
作为方法区或元空间的入口
连接
,连接分为三步,
1.验证,验证文件格式(即千4字节魔数)、元数据、字节码等等
2.准备,类变量进行初始化和分配内存
3.解析,将常量池中符号引用替换为直接引用
初始化
,clinit指令
对构造方法执行变量的赋值,只能有一个线程
能执行这个类的clinit,
多线程环境中被正确地加锁、同步,耗时很长的操作(大对象),可能造成多个进程阻塞- 使用
- 卸载
- 类加载器双亲委派模型机制是什么
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其
委派给父类
,由父类 去加载,
如果此时父类不能加载,反馈给子类
,由子类去完成类的加载。类加载器主要有如下四种
- 启动加载器(Bootstrap),用来加载java核心类库,如:jre/lib/ext
- 扩展类加载器(Extension),用来加载java的扩展库
- 系统类加载器(system class loader)根据 Java 应用的类路径(CLASSPATH) 来加载 Java 类
- 应用加载器(Application),用户自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现。
- Java 类加载器(ClassLoader)的实际使用场景有哪些?
- 解决依赖冲突,通过自定义类加载器,为每个中间件自定义一个加载器,这些加载器之间的关系是平行的,彼此没有依赖关系。这样每个中间件的classloader就可以加载各自版本的jar包
热加载
- 热部署
- 加密保护,打包加密,自定义classloader加载class时解密
- 性能优化
性能优化按结构分可分为3类
- 前端
- 客户端减少请求
- CDN加速加载资源
- 反向代理缓存(静态资源存放nginx)
- web组件分离(js/css/图片放在不同服务,加大浏览器并发数)
- 服务端优化
- 缓存(优先第一考虑原则),缓存离用户越近越好,减少多余请求,如:redis、memcach
- 异步处理(可以有效避免阻塞),如:serverlet异步、多线程、消息队列
- 集群(更多机器,提供更高的吞吐量)
- 程序
- 合适的数据结构(集合给定容器长度,减少扩容)
- 更优的算法/更少的代码
- 并发编程(充分利用CPU资源,需要避免线程安全)
- 减少锁竞争(轻量锁、偏向锁如CAS、读写锁)
- 单例模式
- 池化技术
- Synchronized原理
Syn是由JVM实现的一种互斥同步的方式,通过指令
monitorenter
和monitorexit
两个字节码
指令控制,执行menter指令时,如果这个对象没有锁定或者当前线程已经锁定这个对象,会把锁计数器
+1,当执行mexit时,会-1,当计数器为0时,锁就被释放了
- Syn锁定的对象
Syn可以锁定
对象
、锁定代码块
、锁定方法
- jvm对java原生锁进行哪些优化
- 进行短时间自旋
减少了用户态和到内核态的切换
,- 通过
偏向锁(在锁头标记线程ID)
、轻量锁(CAS)
、重量锁
的升级、降级
减少锁的竞争开销
- 为什么Syn是非公平锁
非公平主要表现在获取锁的行为上,并非是
按照申请锁的时间
前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,
这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿
现象。
- 什么是锁消除和锁粗化
- 锁消除是JVM在编译时检测到
不可能存在锁竞争
的情况,进行锁消除,主要根据逃逸分析- 锁粗化指减少
没必要的重复加锁和解锁
,增大锁的作用域
- 为什么syn是悲观锁,乐观锁是什么,CAS原理和特性是什么,
- syn不管是否有竞争,任何数据都必须进行
加锁、用户核心态转换、维护锁计数器、检查阻塞
等情况- 乐观锁的核心算法是
CAS
(compare and swap),通过volatile
在内存中公开一个对象
,
当且仅当内存值
为预期值
时,才赋予新值
,否则自旋继续进行非阻塞式同步,- CAS优缺点:提高了并发性能,但只能保证
共享一个变量
的原子操作,存在ABA问题,需要用版本戳,长期自旋产生性能消耗
- Syn和显示锁Lock
显示锁具有更高的
灵活性
,提供中断、获取超时时间等操作方法,实现基于AQS
实现,
Syn竞争不激烈的情况下效率高于ReetrantLock
ReetrantLock为公平锁,
- AQS是什么
AQS一种实现
阻塞锁
和一系列依赖FIFO
等待队列的同步器
的框架,
- 定义了一个volatile int
state
变量,表示同步状态,=0时表示没有锁,加锁对state进行+1,释放锁进行-1
- 构建
Node双向链表
锁队列,进行锁排队(so是个公平锁)
一. 进入
acquire
可以看到addWaiter()
将本次aquire请求打包为一个node添加到链表中
二.自旋
尝试获取锁,当前驱结点为头结点
,参与锁竞争
三.否则需要会被LockSupport.park
(this) 进行阻塞
四.当release的时候,LockSupport.unpark
(s.thread)释放当前节点的next节点
- 常用的JUC并发工具
CountDownLatch、 CyclicBarrier、 Semaphore 等
同步工具
ConcurrentHashMap、有序的ConcunrrentSkipListMap、CopyOnWriteArrayList等安全容器
ArrayBlockingQueue、SynchorousQueue等阻塞队列
强大的 Executor 框架,各种线程池
- ReadWriteLock 和 StampedLock
StampedLock是1.8提供的读写锁,支持读优化,
validate()
方法可以确认是否进如了读模式,避免重复锁开销
- 如何让线程彼此同步,有哪些同步器
CountDownLatch
,一个/多个线程通过await进入等待,在通过countDown进行释放,当计数器变为0后可执行CyclicBarrirer
,一组线程全部到达await()屏障后放开屏障执行Semaphore
构建固定连接池,进行acquire池中没有连接进入等待,当release释放会唤醒等待,通常用在流量控制
- java中线程池是如何实现的
通过构建
一组worker线程
来执行BlockingQueue中
的Runnable/Callable
通常需要corePoolSize、最大线程数、超过核心线程数的线程存活时间、工作队列
常用线程池有以下这些
- SingleThreadExecutor,单线程串行工作
- FixedThreadPool,固定大小线程池
- ScheduledThreadPool,执行一些周期性任务线程池
- volatile的特点
volatile是jvm提供的
轻量同步机制
,volatile修饰的变量对所有线程都是可见
的,能够阻止 JVM进行重排序
优化,但不保证代码执行顺序
(遵循happen before 原则
)
- ThreadLocal 怎么解决并发安全
java提供的一种
线程私有信息机制
,线程为key
,使用需要注意remove,否则会一直伴随着
线程共生,造成OOM问题
- 影响线程性能有哪些因素,怎么优化
上下文切换
(线程之间的切换)内存同步
(锁指令,需要刷新缓存)阻塞
(导致挂起,唤醒,重新排队,导致多余上下文切换)
优化方案
- 减小
锁粒度
- 减小
锁的范围
(如:锁代码块)锁分段
(如:CurrentHashMap)- 替换
独占锁
(根据实际情况采用CAS、读写锁、并发容器等工具)
- 线程之间怎么通信
共享内存
消息传递
java中通常通过 wait()和notify()进行传递消息
- 线程的基础信息
启动/终止线程
- Thread/Runnable/Callable
- 安全中断线程
- 优先使用
interrupt中断
处理InterruptException进行线程中断全局变量控制中断线程
状态
- 状态(就绪(start)、阻塞(sleep/wait)、运行(running)、结束(安全中断线程))
线程协作
- 等待和通知机制,
wait/notify/notfyAll
(Object方法),必须要锁,等待和通知的标准范式
,notify/notfyAll,notify只能唤醒一次,一般用notifyAll,避免信号丢失join
方法(等待前一个线程执行完毕)yield
(让出CPU进入就绪,不放锁)sleep
/wait都会进入阻塞,sleep不放锁,wait/notify使用需要锁住同一对象
-
Foke/Join
机制
分而治之算法是将一个大的
任务拆分
为N个任务并发执行
,最终在进行汇总
的一种算法思想,
提高计算效率和充分利用CPU的计算能力
JDK提供了一套标准的Foke/Join算法模型,
- FokeJoinPool、task(invokeAll()添加子任务、join()合并子任务))
- 分而治之,分治法(阈值N区分进行大任务拆分,递归拆分任务,直到任务可执行)
- 工作密取/工作窃取(从头执行,从尾窃取,做完扔回尾部)
- Mysql有什么数据引擎
InnoDB
,支持行级锁、表级锁、事务、索引、独立表空间,其他引擎均不支持事务
Myisam
,数据可进行压缩,采用表级锁,查询快,适用于:只读应用、
非事务应用(数据仓库、报表、日志数据)、空间类应用,会存储自增主键CVS
,所有列not null,不支持索引,数据可直接用Excel编辑Active
,以zlib对数据压缩,磁盘I/O更少,只支持insert、select
只能在自增上创建索引,常用于日志采集Memory
,基于内存的引擎,数据都存在内存中Ferderated
,远程访问引擎,表结构在本地,数据在远程服务器
- 事务的ACID是什么
- 原子性 Atomicity,事务是每一次执行的都是原子操作
- 一致性 Consistency,事务前后数据的完整性必须保持一致
- 隔离性 Isolation,多个并发事务之间要相互隔离(MySQL默认隔离级别)
- 持久性 Durability,数据永久保存在数据库中
- 隔离级别是什么
- 未提交读(READ-UNCOMMITED),会产生脏读
- 提交读(READ-COMMITED),解决脏读,通常优先使用提交读级别
- 重复读(REPEATABLE READ)重复读出数据不会随事务提交而改变,解决重复读
- 可串行化(Serializable),进行锁表操作,解决幻读
- 数据库三大范式
- 字段原子性,不可分割
- 有唯一主键
- 除却主键不包含关联数据的其他字段,为了快速查询数据,也会存在反范式结构
- mysql数据库索引的工作机制是什么
mysql索引实现通常用B树及B+树,
B+树是脱胎于平衡二叉树的多路搜索树
,在节点空间大小
一定的情况下,每个节点可以存储更多的内结点
所有数据存储在叶子节点上
所有叶子节点由指针连接
- 索引的优化
- 尽量全值匹配索引
- 最佳左前缀匹配法则
- 不再索引列上做操作,减少索引表的变更
- 范围条件放在最后,会影响前面条件,导致索引失效
- 使用覆盖索引,减少select *
- 不等于要慎用,not/null,使用覆盖索引
- 字段非空,is null 会导致索引失效,字段可为空则不会
- like要注意,左匹配不会失效
- 字符类型+引号,防止类型不一致索引失效同时减少转换
- or 改union
- mysql 优化实践方式
- 通过explain分析执行计划,优化查询
- 常用搜索字段建立索引
- 查询单行添加limit 1,可以有效停止多余搜索
- 有限且固定长度的字段用enum替换varchar
- 避免 SELECT *
- 永远为每张表设置一个 ID
- 所有字段为固定长度的的表会更快,
- 垂直分割把一张大表变为多张小表
- 拆分大的 DELETE 或 INSERT 语句
- 越小的列会越快
- 选择正确的存储引擎
- mysql 锁
- 表级锁,开销小,加锁快;不会出现死锁;锁定粒度大,
发生锁冲突的概率最高,并发度最低。- 行级锁,Innodb独有,只有在对存在索引的操作才会使用行级锁,
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- timestamp 与 datetime 区别
- timestamp,有时区,4字节,只能存1970~2037
- datetime,无时区,8字节,1000~9999
- B+Tree
数据只存储在叶子节点上,非叶子节点只保存索引信息;
非叶子节点(索引节点)存储的只是一个 Flag,不保存实际数据记录;
索引节点指示该节点的左子树比这个 Flag 小,而右子树大于等于这个 Flag
叶子节点本身按照数据的升序排序进行链接(串联起来);
叶子节点中的数据在 物理存储上是无序 的,仅仅是在 逻辑上有序 (通过指针串在一 起); - 索引
索引是高效获取数据的数据结构
- eq_ref 唯一索引
- ref 非唯一索引
- range 范围扫描,between and、> 、< 等
- index 虽然全表扫描,但只查询索引库
- mybatis简介,(所有相关回答均可从此开始起手式)
MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架,
- 高版本的JDBC已采用
SPI加载驱动
Cache采用装饰器模式
封装了如FIFO、LRU、Schedule等缓存器,利于针对cache扩展增强日志Logging采用适配器模式
扩展Excutor采用模版模式
进行扩展不同的Excutor
- mybatis的缓存
MyBatis 的缓存分为
一级缓存
和二级缓存
,一级缓存放在session
里面,在select上通过flushCache
开启和关闭,默认为true,
二级缓存放在它的命名空间
里,使用二级缓存属性类需要实现
序列化,可在它的映射文件中配置<cache/>
,当进行Insert、update、delete操作会清除对应缓存
- mybatis如何进行分页
mybatis使用
RowBounds
进行分页,也可以使用sqllimit
进行分页,或使用分页插件
- mybatis的插件运行原理
- Mybatis 仅可以编写针对
ParameterHandler、ResultSetHandler、StatementHandler、 Executor
这 4 种接口的插件,Mybatis 通过动态代理,为需要拦截的接口生成代理对象
以实 现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,
具体就是 InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦
截的方法- 实现 Mybatis 的
Interceptor 接口并复写 intercept()
方法,然后在给插件编写注解,
指定 要拦截哪一个接口的哪些方法即可
- Mybatis 动态 sql 是做什么的?都有哪些动态 sql?
Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标
签的形式编写动态 sql
,
完成逻辑判断和动态拼接 sql 的功能,提供了9种动态sql标签
- trim、where、set、foreach、if、choose、when、otherwise、bind
-
{}和${}的区别是什么?
{}是
预编译
处理,可以防止sql注入,${}是字符串替换
- Mybatis 的延迟加载是什么
Mybatis 仅支持 association 一对一关联对象和 collection 一对多关联集合对象的延迟加载
。可以通过lazyLoadingEnabled
=true|false来配置是否启用延迟加载
它是使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,在单独发送
事先保存好的查询给对象赋值
- MyBatis 的好处是什么?
- 独立出来的xml编写sql,更加的便利和灵活,
- 封装了底层 JDBC API 的调用细节,并能自动将结果集转换成 Java Bean 对象,
大大简化了 Java 数据库编程的重复工作
- MyBatis 实现一对一有几种方式?
有
联合查询
和嵌套查询
- 联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面 配置 association
节点配置一对一的类就可以完成,关联查询适合数据少- 嵌套查询是先查一个表,根据这个表里面 的结果的外键 id,去再另外一个表里面查询数据,
也是通过 association 配置,但另外一个表的 查询通过 select 属性配置。嵌套查询又分为
嵌套结果
和嵌套查询
- 嵌套结果是使用嵌套结果映射(另一个resultMap)来处理重复的联合结果的子集
- 嵌套查询是通过执行另外一个 SQL 映射语句来返回预期的复杂类型
- Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
可以使用<resultMap>标签或者直接写查询字段
- 当实体类中的属性名和表中的字段名不一样,如果将查询的结果封装到指定 pojo
可以采用sql别名方式或者resultmap中进行映射
- Mybatis 都有哪些 Executor 执行器
Mybatis 有三种基本的 Executor 执行器,
SimpleExecutor
、ReuseExecutor
、
BatchExecutor
,可在配置文件中指定Executor或创建session时指定ExecutorType.xxx
- SimpleExecutor:每执行一次 update 或 select,就开启
一个 Statement
对象,
用完立刻关闭 Statement 对象- ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,
存在就使用,不存在就创建,用完后,不关闭 Statement 对象
, 而是放置于Map- BatchExecutor是完成批处理
- resultType resultMap 的区别?
- 类的名字和数据库相同时,可以直接设置 resultType 参数为 Pojo 类
- 若不同,需要设置 resultMap 将结果名字和 Pojo 名字进行转换
- mbatis 源码全过程
//level1:整个创建过程包含二进制流文件解析
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//level2:(加载配置,初始化)
new XMLConfigBuilder(inputStream, environment, properties);
//level3:读取xml字节流转为document可操作对象
new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()
//level3:将configuration内容解析为一个Configration
//含 settings(先加载默认,在进行覆盖)、properties、typeAliases、plugins、
//(objectFactory、objectWrapperFactory、reflectorFactory可以自定义覆盖)、
//environments(解析datasource)、
XMLConfigBuilder.parse()
//level4:XmlMapperBuilder解析mapper文件最终注册到MapperRegistry中
SqlSession session = sqlSessionFactory.openSession();
//通过解析注册到MapperRegistry获取MapperProxyFactory
//通过反射获取一个Mapper实例MapperProxy代理Mapper
CscLineMapper mapper = session.getMapper(CscLineMapper.class);
//通过MapperMethod方法执行(此处mapperMethod做了缓存)
//MapperMethod.execute 通过操作类型(增删改查)执行了sqlsession对应的方法
//sqlsession通过Executor模版执行操作(Spring的SqlSessionTemplate也是通过对SqlSession的实现)
//从BaseExecutor能看到增、删、改都会清空缓存,查询会优先查询缓存
//Executor操作通过
//StatementHandler处理操作
//ParameterHandler处理参数
//ResultSetHandler处理查询结果
CscLine cscLine = mapper.selectByPrimaryKey(1000463l);
- 什么是redis
redis是一个基于内存操作,通过C实现的单线程key-value数据库,
- 执行异步操作定时刷新数据到硬盘持久化,
- 有String、Hash、List、Set、Soredset五种数据结构内容,支持超时expire设置,
- 因为基于内存操作、单线程、C实现以及RESP协议的简单易读,是目前最快的Key-Value数据库,
受限于内存,只能试用于较小数据量的高性能操作和运算上,- 常用于会话缓存、排行榜计数器、队列、发布订阅。
- redis有哪几种数据淘汰策略
- no-enviction(驱逐):禁止驱逐数据
- 基于所有key的策略
- allkey-lru,最少使用淘汰
- allkey-random,随机淘汰
- 基于设置了超时时间的策略
- volatile-ttl,最快要超时的淘汰
- volitile-lru,最少使用淘汰
- volitile-random,随机淘汰
- 一个字符串最大容量
512M
- redis的集群方案有哪些
- 主从模式,
- 有一主一从、一主多从、星行主从等拓扑形式,主节点挂了不会自动切换主节点,不满足高可用
- 通过配置slaveof host:port为主节点设置一个从节点
- slaveof no one,脱离主节点,
- info replication,查看状态信息,
- 哨兵模式(sentinel)
- 哨兵服务心跳检测主节点,故障时通知其他哨兵,超过半数哨兵发现故障则进行选举
以3个哨兵和3个redis服务为例,哨兵监听主节点,从主节点获取从节点最新信息,
通过心跳检测主节点是否正常,当一个哨兵发现主节点ping不通,会通知其他2个哨兵,
如果还有个哨兵也ping不通(设定2票为通过)则认定主节点已挂,发现的哨兵会发起选举,
经过几轮raft选举出leader,当上一个主节点恢复,则变为从节点
- redis哈希槽
- redis有16384个哈希槽,每个redis负责一部分的槽,不是闭环的,有利于节点的变更,增加、删除节点进行变更负责的槽位,
- 一致性哈希算法,是一个闭合的哈希环,一个个节点分布在hash环上,依次寻找所在的位置
- 集群选择数据库
- 只能默认0,无法select其他数据库
- redis管道,
把多次指令放入管道种一次发送,减少网络开销,需要避免一次缓存数据过大,占用尽内存,
请求不满管道需要flush进行强制发送
- Jedis 与 Redisson 对比有什么优缺点
- Jedis是java客户端,提供了较全面的API支持
- Redisson实现了分布式和可扩展的 Java 数据结构,不支持字符串操作,排序、事务、管道、分区等 Redis 特性
- redis的事物,
redis通过MULTI开启事物,EXEC提交事物,DISCARD中断事物,WATCH监听事物,
只有在语法错误才会一次性拒绝,部分异常不会阻止其他正确指令的执行,是弱事务
- redis内存的优化
减少命名长度,使用hash结构优化,减少空间占用
- redis回收进程如何工作
回收进程在内存大于maxmemory的限制时,根据设置的淘汰策略进行内存的释放
- redis分布式锁,缺点
利用 SETNX key value 的原子性来加锁,在利用lua脚本的原子性来释放锁
master如果宕机丢失了锁数据,会产生脏数据Redlock 算法,使用redlock算法,多个redis实例,向超过半数的节点发送加锁/释放锁
指令,只有当超过半数的成功才算加锁或释放锁成功,but性能会有损失
- 缓存击穿、缓存雪崩产生的原因和解决方案
- 缓存击穿是对一个缓存为空的key发送大量请求,导致db压力很大或崩溃造成缓存击穿,
通过不存的缓存null减少不必要查询,可以通过布隆过滤器,一个BitMap来存储所有可能
存在的key,避免大量访问数据库- 缓存雪崩是在同一时间大量缓存失效,涌进大量请求,导致数据库崩溃,可以采用加锁、
随机失效时间来避免同时失效来解决
- redis和memcached
- redis采用单线程,支持RDB和AOF持久化数据,支持多种数据结构存储,天然的支持集群、主从
- memcached采用多线程,可缓存图片、视频,适用于纯KV,数据量大的业务
- redis 主从复制如何实现的,redis 的集群模式如何实现?redis 的 key 是如何寻址的
- 主从节点复制:主节点将自己内存中的数据生成一份快照发送给从节点用来恢复到内存,之后
有变更,会以二进制的形式发送给从节点执行同步- redis采用将hash槽分割为N个片段,每个master负责一部分槽,Master 节点维护着一个
16384/8 字节的位序列,Master 节点用 bit 来标识对于某个槽自己是否拥有。比如:
对于编号为 1 的槽,Master 只要判断序列的第二位(索引从 0 开始)是不是为 1 即可。
这种结构很容易添加或者删除节点。
- redis的持久化机制
- RDB,通过bgsave定期存储到硬盘,耗时耗性能,数据容易丢失,恢复快
- AOF,存储操作指令到aof日志中,产生的体积大,恢复慢,不容易丢失数据
- redis的过期策略,LRU算法
惰性过期,在使用的时候判定是否过期;定时过期,定时任务进行过期扫描
LRU最近最少使用算法
- 缓存与数据库不一致怎么解决
数据库执行增删改清除缓存(mybatis的缓存即是如此处理),从库更新数据缓存中的数据也更新
- redis常见性能问题和解决方案
- master最好不做持久化工作
- 如果数据比较重要,在某个slave开启AOF备份,策略1s同步一次
- 为了主从复制的速度和链接的稳定,master和slave最好在一个局域网
- 尽量避免在压力大的主库上添加从库
- 主从复制,尽量使线性结构,避免节点压力大 master<-slave1<-slave2
- spring简介以及优点好处
spring是一个非侵入式低耦合的一个框架,IOC控制管理bean,AOP增强bean,
声明式事务处理,以及Mybatis、Quartz、JMS等优秀框架的集合,大大方便的系统的开发
测试
- Spring中的单例bean的线程安全问题
多个线程对非静态变量进行写操作会存在线程安全问题
- 实例中不存在可变更修改的非静态变量是安全的
- 用ThreadLocal存储可变变量,解决线程安全问题
- bean的生命周期
- Aware接口设置相关属性
- BeanPostProcessor的postProcessBeforeInitialization()
- InitializingBean接口方法
- 指定的initMethod
- BeanPostProcessor的postProcessAfterInitialization()
- DisposableBean接口方法
- 指定的destroyMethod
- spring中用到的设计模式
- BeanFactory 工厂模式
- AOP的代理模式
- bean的创建单例模式
- xxxxTemplate的模板模式
- Advisor的适配器模式
- ApplicationListener的观察者模式
- @Component和@Bean的区别是什么
- Component注解类,自动扫包装配实例
- Bean注解方法,返回实例,灵活性强
- Spring事务管理的方式有几种?
- 编程式事务,在业务逻辑中写入事务,灵活性差,耦合严重
- 声明式事务,基于AOP方式,可注解或XML中灵活声明事务
- spring的事务隔离级别
- 采用后端数据库的隔离级别或自定义隔离性事务的4个级别
- BeanFactory 和 ApplicationContext 有什么区别?
- BeanFactory 提供bean的创建和生命周期的控制
- ApplicationContext具有BeanFactory的功能,还提供国际化、统一资源读取方式等其他额外功能
- bean的作用域
@Scope("prototype")
,实例范围
- prototype多实例;
- singleton 默认,单利;
- request,一次请求;
- session 同一个session创建
- spring的自动装配模式
- byName模式
- byType模式
- 构造函数模式
- 默认模式
- 无
- 如何开启基于注解的自动装配?
注册AutowiredAnnotationBeanPostProcessor实例
- 请举例解释@Required 注解?
- 注册RequiredAnnotationBeanPostProcesso 来验证 bean 属性
- Spring 框架中有哪些不同类型的事件?
Spring 的 ApplicationContext 提供了支持事件和代码中监听器的功能。
提供了5种监听事件,可通过实现一个ApplicationListener 接口并注入到容器中进行监听
- 上下文更新事件(
ContextRefreshedEvent
):该事件会在ApplicationContext被初始化或者 更新时发布。也可以在调用 ConfigurableApplicationContext 接口中的 refresh()方法时被触 发。- 上下文开始事件(
ContextStartedEvent
):当容器调用ConfigurableApplicationContext的 Start()方法开始/重新开始容器时触发该事件。- 上下文停止事件(
ContextStoppedEvent
):当容器调用ConfigurableApplicationContext的 Stop()方法停止容器时触发该事件。- 上下文关闭事件(
ContextClosedEvent
):当ApplicationContext被关闭时触发该事件。容器 被关闭时,其管理的所有单例 Bean 都被销毁。- 请求处理事件(
RequestHandledEvent
):在Web应用中,当一个http请求(request)结束 触发该事件。
- FileSystemResource 和 ClassPathResource 有何区别?
- ClassPathResource 在环境变量中读取配置文件
- FileSystemResource 在加载的配置文件 中读取配置文件
- spring容器的初始化过程
- 1.
prepareRefresh
(prepare 准备)校验环境、属性、数据正确性- 2.
obtainFreshBeanFactory
(obtain (尤指经努力) 获得,赢得; 存在; 流行; 沿袭)
获取BeanFactory实例,- 3.
prepareBeanFactory
,再准备BeanFactory的后续工作,包含注册classLoader类加载器
resource环境 Aware接口等- 4.postProcessBeanFactory(spring预留自定义增强BeanFactory接口)
- 5.
invokeBeanFactoryPostProcessors
(invokeBFPP 调用执行BeanFactory的增强器,
包括用户自定义扩展的增强器)
- 6.
registerBeanPostProcessors
(registBPP 注册其他bean增强器,包括AOP
,
Transaction
等都在此注册,注册按照PriorityOrdered、Ordered、None进行注册,)- 7.initMessageSource(国际化,初始化信息源,注册
messageSource
的bean实例可实现自定义国际化)- 8initApplicationEventMulticaster(初始化事件派发器,
注册applicationEventMulticaster实例可实现自定义事件派发器)- 9.onrefresh(spring预留自定义扩展接口)
- 10.registerListeners(给容器中将所有项目里面的ApplicationListener注册进来)
- 11.
finishBeanFactoryInitialization
(finish init 加载bean,单实例非懒加载bean
,将在此初始化,详细情况如下:)
- 先获取
RootBeanDefinition
bean实例定义- 获取一个BeanWrapper,包装bean
- 正式实例化前
- initializeBean进行bean实例化
- Aware接口调用
- BeanPostProcessor 前置增强调用,如果此时获取到实例bean则返回实例
- bean实例化前被调用,如:InitializingBean接口、指定的init-method
- BeanPostProcessor后置增强调用,如:AOP、Transaction等advisor在此注册
- getAdvicesAndAdvisorsForBean,获取所有Advisors增强到bean上,供方法增强
时使用
- 12.finishRefresh(finish 结束)
- SpringMvc的优点
- 清晰的角色划分:控制器(controller)、验证器(validator)、命令对象(command obect)、
表单对象(form object)、模型对象(model object)、Servlet分发器(DispatcherServlet)、
处理器映射(handler mapping)、试图解析器(view resoler)等等。
每一个角色都可以由一个专门的对象来实现
- SpringMvc 的控制器是不是单例模式
是,存在线程安全,
- 不存在可变的成员变量,即状态不可变,是安全的
- 通过ThreadLocal来存储状态可变的变量
- SpringMvc和struts2区别
- sp入口是一个servlet,st是一个filter
- sp针对方法开发,可单例、多例,st针对类作为action开发,只能多例,所以sp性能高于st
- SpringMVC 怎么样设定重定向和转发的
- 转发在返回前加forward:,如:forward:user.do?name=method4
- 重定向返回前加 redirect:,如:redirect:http://www.baidu.com
- Springnmvc 能返回的数据类型
- String、Object,通过@ResponseBody 可直接返回数据
- ModeAndView,返回一个view页面
- Model、ModelMap、ModelAndView
- Model是用于给View传递数据的模型
- ModeMap是用于服务给页面传递数据的,无需自己创建,容器自动注入
- ModeAndView,需要自行创建定义返回的视图和数据模型
- Filter和Interceptor的区别
- Filter是基于函数回调(doFilter()方法)的,而Interceptor则是基于Java反射的(AOP思想)。
- Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器。
- Filter对几乎所有的请求起作用,而Interceptor只能对action请求起作用。
- Interceptor可以访问Action的上下文,值栈里的对象,而Filter不能。
- 在action的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
- spring MVC工作原理
- 客户端(浏览器)发送请求,直接请求到
DispatcherServlet
。- DispatcherServlet根据请求信息调用
HandlerMapping
,解析请求对应的Handler。- 解析到对应的
Handler
(也就是我们平常说的Controller控制器)。HandlerAdapter
会根据Handler来调用真正的处理器来处理请求和执行相对应的业务逻辑。- 处理器处理完业务后,会返回一个
ModelAndView
对象,Model是返回的数据对象,View是逻辑上的View。ViewResolver
会根据逻辑View去查找实际的View。- DispatcherServlet把返回的Model传给View(视图渲染)。
- 把
View
返回给请求者(浏览器)
- mq简介
利用高效可靠的异步消息传递机制对分布式系统中的其余各个子系统进行集成
特点
- 分布式系统之间消息的传递
- 提高分布式系统与其他子系统的伸缩性和扩展性
- 低耦合,降低应用之间的耦合性,有解耦作用
- 异步通信,进行异步处理业务
- 缓冲能力,可用来削峰
- 系统可用性降低、系统复杂性提高、存在一致性问题
与RPC区别
- RPC是模块与模块之间的调用,存在依赖性,适用于快速响应、同步处理的业务。
- MQ是多系统之间的消息传递,系统间存在队列管理消息,适用于不需要响应,或实时性不高的场景
- 都是分布式方式下通信
- JMS简介
JMS是java关于消息中间件的规范
JMS 模型
- 连接工厂,ConnectionFactory
- JMS连接,Connection,conn.start();
- JMS会话,Session
- JMS目的,Destination
- JMS生产者/消费者Productoer/Consumer
- 消息发送/注册消息监听
JMS消息模型
- P2P(点对点)
- Topic/主题(发布和订阅)
- 什么是 ActiveMQ?
实现了JMS1.1规范的MQ框架
- ActiveMQ宕机、丢消息怎么处理?
- 对消息做持久化,p2p消息会自动持久化,需要处理的是topic消息的持久化
- 开启事务对消息进行应答确认处理
- 消息的不均匀消费
根据不同的服务设置合适的prefetch,预取数量,避免消息分配不均
- 死信队列是什么
用来保存处理失败或过期的消息的队列,本质也是一个队列,发生以下情况会进入死信队列:
- 事物会话被回滚
- 事物会话提交之前关闭
- 客户端手动确认模式,调用session.recover
- 自动应答失败
7.ActiveMQ的消息重发机制
- 消费者接收到消息处理后未进行响应,会对这条消息进行重发
- 预取模式下,消费者未对本次接收的消息进行响应,生产者会对这些消息全部进行重发
- session事务模式下,未进行确认,会重新发送本次事务的消息
- 分布式MQ下单场景方案
- 下单后先保存本地数据和MQ消息表,这时候消息的状态是发送中,如果本地事务失败,那么下单失败,事务回滚。
- 下单成功,直接返回客户端成功,异步发送MQ消息
- MQ回调通知消息发送结果,对应更新数据库MQ发送状态
- JOB轮询超过一定时间(时间根据业务配置)还未发送成功的消息去重试
在监控平台配置或者JOB程序处理超过一定次数一直发送不成功的消息,告警,人工介入。
8.秒杀
秒杀的业务场景分3个阶段,
1.准备预热阶段,用户不断的刷新页面查看秒杀是否开始
2.秒杀阶段,限时、限价、限量的销售,会产生瞬时的高并发流量
3.结算阶段,完成秒杀的数据工作,如:数据一致性、异常处理、商品回仓处理
准备阶段可以将商品、人员、库存 等数据进行redis热点缓存,页面静态化,尽量减少动态元素,
秒杀阶段的高并发通过限流、削峰、异步消息处理,限流方面,客户端进行限时提交、防止重复提交处理,
服务端的话,由于负载均衡层处理并发能力要强于应用层不少,像Nginx、redis分别能处理10万+、5万+的并发,而服务器如tomacat和数据库分别只有800、1000的并发,而像瞬时高并发流量并且大多数请求都属于无效请求,去扩展服务成本比较高,所以在负载均衡层进行库存限流比较好,在预热时redis缓存库存,
在nginx中执行lua脚本做库存预减校验,库存小于0直接返回秒杀结束,校验通过的发送MQ消息,再异步处理MQ消息,校验活动时间,是否黑名单,扣减缓存的秒杀库存,生成有效token,
结算阶段,客户端通过短轮询查询是否获得了有效token,即秒杀资格,获取到秒杀资格进行秒杀结算,结算验证token加入秒杀购物车,如果token失效,回滚缓存中的秒杀商品库存,最后提交订单,删除token,秒杀完成
- AMQP简介
AMQP(Advanced Message Queuing Protocol,高级消息队列协议)
是一个进程间传递异步消息的网络协议,主要实现为RabbitMQ。
AMQP模型
- 生产者:消息的创建者,发送消息到rabbitmq;
- 消费者:连接到rabbitmq,订阅到队列上进行消费消息,持续订阅(basicConsumer)和单条订阅(basicGet).
- 消息:包含有效载荷和标签,有效载荷指要传输的数据,,标签描述了有效载荷,
并且rabbitmq用它来决定谁获得消息,消费者只能拿到有效载荷,并不知道生产者是谁信道
:生产者与消费者均通过信道与MQ通信,一个TCP连接有成百上千个信道来达到多线程
处理,交换器、队列、绑定、路由键
,队列
通过路由键
(routing key,某种确定的规则)绑定
到交换器
,
生产者
将消息发布到交换器
,交换器
根据绑定
的路由键
将消息
路由到特定队列
,然后由订阅这个队列的消费者
进行接收
- RabbitMQ 中的 Broker 是指什么?Cluster 又是指什么?
- broker 是指一个或多个 erlang node 的逻辑分组,且 node 上运行着 RabbitMQ 应用程序
- cluster 是在 broker 的基础之上,增加了 node 之间共享元数据的约束
- 什么是元数据?元数据分为哪些类型?包括哪些内容
在非 Cluster 模式下,元数据主要分为:
- Queue 元数据(queue 的名字和属性等),具有自己的 erlang 进程
- Exchange 元数据(exchange 名字、类型和属性等),内部实现为保存 binding 关系的查找表
- Binding 元数据(存放路由关系的查找表)
- Vhost 元数据(vhost 范围内针对前三者的名字空间约束和安全属性设置),拥有独立的权限,
可用来做范围控制。
- 如何确保消息正确地发送至 RabbitMQ?
RabbitMQ 使用发送方确认模式,确保消息正确地发送到 RabbitMQ
当采用channel.confirmSelect();开启确认者发送模式后,可采用以下方法确认消息,
- channel.addConfirmListener()添加异步监听发送方确认模式
- channel.waitForConfirms()普通发送方确认模式;消息到达交换器,就会返回true
- channel.waitForConfirmsOrDie()批量确认模式;只要有一个未确认就会IOException
- 常用的交换器有哪些
- direct是精准匹配,对于匹配符合的消费者都推送消息
- fanout是广播式发送,所有消费者均能收到消息
- topic,通过"."分割路由键,"*"进行1个匹配,"#"N个匹配,
- headers(通direct差不多)
- metadata的存储方式 RAM node和disk node
- RAM node,将queue、exchange 和 binding等 RabbitMQ基础构件存储在内存中,
- disk node,不但保存到内存中,还会保存到磁盘上,一个cluster至少有一个disk node用来持久化
- 在单 node 系统和多 node 构成的 cluster 系统中声明 queue、exchange ,以及 进行 binding 会有什么不同?
- 单 node 上声明 queue 时,只要
该 node
上相关元数据进行了变更,你就会 得到
Queue.Declare-ok 回应- cluster 上声明 queue ,则要求 cluster 上的
全部 node
都要进行元数据成功更新,
才会得到 Queue.Declare-ok 回应。另外,若 node 类型 为 RAM node 则变更的数据仅
保存在内存中,若类型为 disk node 则还要变更保存在磁盘 上的数据。
- cluster 中拥有某个 queue 的 owner node 失效了,其他node能否重新声明
- durable持久化的queue只能重新恢复才能使用该queue
- 非durable的可以重新声明
- 能够在地理上分开的不同数据中心使用 RabbitMQ cluster 么
- 无法控制所创建的 queue 实际分布在 cluster 里的哪个 node 上(一 般使用
HAProxy + cluster 模型时都是这样),这可能会导致各种跨地域访问时的常见问题- Erlang 的 OTP 通信框架对延迟的容忍度有限,这可能会触发各种超时,导致 业务疲于处理
- 在广域网上的连接失效问题将导致经典的“脑裂”问题,而 RabbitMQ 目前无法处理
- routing_key 和 binding_key 的最大长度是多少
255字节
- 向不存在的 exchange 发 publish 消息会发生什么
都会收到 Channel.Close 信令告之不存在(内含原因 404 NOT_FOUND)
- 什么情况下 producer 不主动创建 queue 是安全的?
- message 是允许丢失的
- 实现了针对未处理消息的 republish 功能(例如采用 Publisher Confirm 机制)。
- 死信交换器DLX(
一个普通交换器,在队列声明参数 x-dead-letter-exchange 开启,x-dead-letter-routing-key,后
当发生以下情况,消息会进入死信队列:
- 消息被拒绝,requeue参数为false
- 消息过期
- 队列达到最长长度
死信和备用的区别
- 备用是路由失败的消息,死信是路由成功拒绝或未响应的消息
- 备用是在队列申明,死信是在交换器申明
- 什么情况下会出现 blackholed 黑洞问题?
blackholed 问题是指,向 exchange 投递了 message ,而由于各种原因导致该
message 丢失,但发送者却不知道。可以通过 mandatory=true,来接收路由失败的消息
可导致 blackholed 的情况:
- 向未绑定 queue 的 exchange 发送 message
- exchange 以 binding_key key_A 绑定了一个 queue queue_A,但向 该
exchange 发送 message 使用的 routing_key 却是 key_B。
- Consumer Cancellation Notification 取消机制用于什么场景?
用于保证当镜像 queue 中 master 挂掉时,连接到 slave 上的 consumer 可以收到
自身 consume 被取消的通知,进而可以重新执行 consume 动作从新选出的 master 处获得
消息。若不采用该机制,连接到 slave 上的 consumer 将不会感知 master 挂掉这个事
情,导致后续无法再收到新 master 广播出来的 message 。另外,因为在镜像 queue 模式
下,存在将 message 进行 requeue 的可能,所以实现 consumer 的逻辑时需要能够正确
处理出现重复 message 的情况
- 消息的拒绝
消息消费异常,拒绝回mq,进行重新发送给其他消费者消费
- reject 拒绝单条消息channel.basicReject(envelope.getDeliveryTag(),false);
- Nack 多条拒绝
- 为什么不应该对所有的 message 都使用持久化机制?
仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈
- 性能影响
- queue持久化会导致无法重新声明,而丢失消息
- Dubbo是什么
Dubbo是一个分布式、高性能、透明化的RPC服务框架,提供服务自动注册、自动发现等高效服务治理方案,可以和Spring框架无缝集成
- 主要的应用场景
- 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需要简单的配置,没有任何API侵入
- 软负载均衡及容错机制,可以在内网替代F5等硬件负载均衡器,降低成本,减少单点
- 服务的自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址
并且能够平滑添加和删除服务提供者
- Dubbo的核心功能
主要有以下3个核心功能
- Remoting,网络通信框架,提供多种NIO框架抽象封装,包含"同步转异步"和"请求-响应"模式的信息交换方式
- Cluster,服务框架,提供基于接口方法的透明远程过程调用,包含多协议支持,以及软负载均衡,失败容错,
地址路由,动态配置等集群支持- Registry,服务注册,基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供者可以
平滑的添加和删除服务提供者
- Dubbo的核心组件
- Provider,暴露服务的服务提供方
- Consumer,调用远程服务的服务消费方
- Registry,服务注册与发现的注册中心
- Monitor,统计服务的调用次数和调用时间的监控中心
- Container,服务运行容器
- Dubbo服务注册与发现流程
- 0服务容器Container负责启动,加载,运行服务提供者。
- 1服务提供者Provider在启动时,向注册中心Registry注册自己提供的服务。
- 2服务消费者Consumer在启动时,向注册中心Registry订阅自己所需的服务。
- 3注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 4服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 5服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。
- Dubbo的设计思路原因
- Consumer与Provider解耦,双方都可以横向增减节点
- 注册中心对本身可做对等集群,可动态增加节点,并且任意一台宕机,将自动切换到另一台
- 去中心化,双方不直接依赖注册中心,即使注册中心全部宕机短时间内也不会影响服务的调用
- 服务提供者无状态,任意一台宕机,不影响使用
- Dubbo的十层架构设计
Dubbo 总体架构分为10层,左蓝是消费者使用的接口,右绿为服务提供方使用接口,中轴线是双防均用的接口
- 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应
的接口和实现。- 配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,
也可以通过spring解析配置生成配置类。- 服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,
以ServiceProxy为中心,扩展接口为ProxyFactory。- 服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、
Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。- 集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,
扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,
实现对服务消费方来透明,只需要与一个服务提供方进行交互。- 监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、
Monitor和MonitorService。- 远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker
和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。
Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,
可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。- 信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,
扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。- 网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、
Transporter、Client、Server和Codec。- 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、
ObjectOutput和ThreadPool
- Dubbo 支持哪些协议
- dubbo协议,单一长连接和NIO异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者
,传输协议TCP,异步,Hessian序列化- RMI协议,采用JDK标准的rmi协议失效,传输参数和返回参数对象需要实现序列化,使用ava标准序列化机制,
阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP多个短连接
,同步传输,适用常规的远程调用和rmi互操作,在依赖低版本的Common-Collections包,java序列化存在安全漏洞- webservice协议,基于webservice的远程调用协议,集成CXF实现,提供和原生webservice的互操作。
多个短连接,基于http传输,同步传输,适用于系统集成和跨语言调用- http协议,基于http表单提交的远程调用协议,适用Spring的HttpInvoke实现,多个短连接,传输协议http,传输参数大小混合,
提供者个数多于消费者,需要给应用程序和浏览器JS调用- hessian协议,集成Hessian服务,基于http通讯,采用Servlet暴露服务,Dubbo内嵌jetty做为服务器默认实现,提供与Hessian服务
互操作,多个短连接,同步http传输,Hessian序列化,传入参数较大,提供者大于消费者,提供者压力大,可传输文件- memcache,基于memcache实现的RPC协议
- redis,基于redis实现的RPC协议
- Dubbo有哪些注册中心
- Multicast注册中心,不需要任何中心节点,只要广播地址,就能进行服务注册与发现,基于网络
中组播传输实现- zk,基于分布式协调系统zk实现,采用zk的watch机制实现数据变更
- redis,基于redis实现,采用可以key/Map实现,
- Simple 注册中心
- 为什么需要服务治理
- 过多的url篇日志困难
- 负载均衡分配节点压力过大的情况下也需要部署句群
- 服务依赖混乱,启动顺序不清晰
- 过多服务导致性能指标分析难度过大,需要监控
- Dubbo与spring的关系
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring
加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载
- Dubbo使用什么通信框架
默认使用NIO Netty框架
- Dubbo集群提供了哪些负载策略
- Random LoadBalance随机选取提供策略,有利于动态调整提供者权重,截面碰撞概率,调用
次数越多,分布越均匀,默认策略
- RoundRobin LoadBalance,轮询选取提供者策略,平均分布,但是存在请求累计的问题
- LeastActive LoadBalance,最少活跃调用策略,解决慢提供者接收更少的请求
- ConstantHash LoadBalance,一致性Hash策略,使相同参数请求总是发到统一提供者,一台
机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动
- Dubbo 的集群容错方案有哪些
通过<dubbo:servce 或 <dubbo:reference cluster="faildback" 进行配置
- Failover Cluster,失败自动切换,重试其他服务器,通常用于读操作,但重试会带来更长的延迟,默认方案
- FailFast Cluster,快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作,
如新增记录- Failsafe Cluster,失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作
- FaildBack Cluster,失败能自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作
- Forking Cluster,并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的
读操作,但需要浪费更多的服务资源,可通过forks="2" 来设置最大并发数- Broadcast Cluster 广播调用所有提供者,逐个调用,任何一台服务报错则报错,通常用
与通知所有提供者更新换错或日志等本地资源信息
- Dubbo支持的序列化方式
默认Hessian序列化,还有Dubbo、FastJson、java自带序列化
- Dubbo超时时间怎么设置
可在provider或consumer设置超时时间,consumer端优先级更高,
服务调用超时不成功,默认是会重试两次的
- Dubbo在安全机制方面是如何解决的
Dubbo 通过Token令牌防止用户绕过注册中心直连,然后在注册中心上管理授权,Dubbo
还提供服务黑白名单,来控制服务所允许的调用方
- Dubbo和Dubbox之间的区别
dubbox 基于Dubbo上做了一些扩展,如加了服务科restful调用,更新了开源组件等
- Dubbo和Spring Cloud的关系
Dubbo是SOA时代的产物,他的关注点主要在于服务的调用,流量分发、流量监控和熔断,
而spring cloud 诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了
Spring、Springboot的优势之上,两个框架的开始目标就不一致,Dubbo定位服务治理,
Spring Cloud 是一个生态
- Dubbo的SPI
SPI 是service provider interface ,是一种服务发现机制,SPI 的本质是将接口实现类的全限定名配置在文件中,
并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。Dubbo SPI对java原生的SPI做了增强
- getExtensionLoader 获取一个 ExtensionLoader 实例
- getExtension 方法获取拓展类对象,检查缓存,缓存未命中则创建拓展对象,
通过Holder 获取持有对象,缓存不存在则创建新的Holder,Holder没有持有实例创建持有实例- createExtension(name);创建拓展对象
- getExtensionClasses().get(name);从配置文件(META-INF/dubbo、META-INF/dubbo/internal/、META-INF/service/、)中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表,
通过反射创建实例,向实例中注入依赖
- loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件
- loadResource 加载解析配置的实现健值类、loadClass 根据解析出来的包名通过反射加载类
- injectExtension(instance); 向实例中注入依赖,利用SpiExtensionFactory、SpringExtensionFactory
AdaptiveExtensionFactory来完成IOC注入- 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过 加载实现类。这样可以在运行时,动态为接口替换实现类。Dubbo SPI对java原生的SPI做了增强
- getExtensionLoader 获取一个 ExtensionLoader 实例
- getExtension 方法获取拓展类对象,检查缓存,缓存未命中则创建拓展对象,
通过Holder 获取持有对象,缓存不存在则创建新的Holder,Holder没有持有实例创建持有实例- createExtension(name);创建拓展对象
- getExtensionClasses().get(name);从配置文件(META-INF/dubbo、META-INF/dubbo/internal/、META-INF/service/、)中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表,
通过反射创建实例,向实例中注入依赖
- loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件
- loadResource 加载解析配置的实现健值类、loadClass 根据解析出来的包名通过反射加载类
- injectExtension(instance); 向实例中注入依赖,利用SpiExtensionFactory、SpringExtensionFactory
AdaptiveExtensionFactory来完成IOC注入- 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 insta反射创建 Wrapper 实例。
然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
- BIO、NIO 和 AIO 的区别?
- BIO,同步阻塞式IO,面向流,一个连接来创建一个线程进行处理
- 伪异步IO,线程池管理线程进行处理
- NIO,同步非阻塞式IO,面向缓冲区,一个连接一个线程,通过Selector多路复用器
轮询所有操作状态的改变来管理N个连接和事件,Channel进行读写- AIO,异步非阻塞IO,无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,
系统会通知对应的线程来处理
- NIO的组成
- Selector,多路复用器,负责管理channel的所有事件
- Buffer,缓冲区,数据流入、流出所必须的区域
- Channel,通道,用来进行读写
- netty的特点
- netty是一个采用多线程Reactor模式的高性能、异步事件驱动的NIO框架,提供了对TCP、UDP、文件传输的支持。
- 简化了NIO的使用,封装了多种编解码器、粘包/半包处理、心跳检测、http/ssl等开箱即用的处理器
- 缓冲区通过内存池的方式循环利用,ByteBuf通过引用计数器及时申请释放不再引用的对象,降低了 GC 频率
- 大量使用了 volitale、使用了 CAS 和原子类、线程安全类的使用、读写锁的使用
- netty的线程模型
netty可以选择构建两个线程池,boss线程池和worker线程池
- boss线程池用于mainReactor线程负责建立连接,subReactor线程负责监听事件
- worker线程负责handller的处理
- 什么是粘包半包,怎么处理
导致粘包/半包的原因如下
- 由于TCP采用nagel算法为了减少网络开销,每一次都在MISS大小想尽可能嘴大的发送数据包
,会对数据进行拆分发送- 接收数据的缓冲区在大小不足时会进行拆包处理
netty提供了4个已经封装好的处理器解决粘包/半包问题
- FixedLengthFrameDecoder(int frameLength),固定长度拆包,适用于数据包长度固定
- LineBasedFrameDecoder(int maxLength),按行拆包,数据("\r\n")结尾
- DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter), 固定分隔符拆包,数据必须以分隔符结尾
- LengthFieldBasedFrameDecoder,基于长度域拆包
- 了解哪些序列化协议
数据包需要以二进制形式在网络传输中传输,所以数据需要编码为二进制流传输,接收在进行
解码使用影响序列化的几个因素有
- 序列化后的码流大小,影响网络开销
- 序列化的性能,影响CPU的资源占用
- 是否支持跨语言,影响开发语言的切换和平台的对接
常见的序列化方式有以下几种
- java自带序列化,无法跨语言、码流太大、序列化的性能差
- xml,可读性好,不能序列化方法,文件大,当做配置文件存储数据,实时数据转换。
- JSON,兼容性高、格式简单、码流小、扩展性好,描述性差,额外空间开销大,适用于
跨防火墙访问、可调式性要求高、基于 Web browser 的 Ajax 请求、传输数据量相对小,实时性要求相对低(例如秒级别)的服务- FastJSON,简单易用,速度快,适用场景:协议交互、Web 输出、Android 客户端
- Thrift,不仅是序列化协议,还是一个 RPC 框架,体积小, 速度快、支持多种语言和
丰富的数据类型、对于数据字段的增删具有较强的兼容性、支持二进制压缩编 码。
缺点:使用者较少、跨防火墙访问时,不安全、不具有可读性,调试代码时相对困难、
不能与其他传输层协议共同使用(例如 HTTP)、无法支持向持久层直接读写数据,即:
不适合做数据持久化序列化协议。适用场景:分布式系统的 RPC 解决方案- Protobuf,将数据结构以.proto 文件进行描述,序列化后码流小,性能高、
结构化数据存 储格式(XML JSON 等)、通过标识字段的顺序,可以实现协议的前向兼容、
结构化的文档 更容易管理和维护。缺点:需要依赖于工具生成代码、支持的语言相对较少,
官方只支持 Java 、C++ 、python。适用场景:对性能要求高的RPC调用、
具有良好的跨防火墙的访问 属性、适合应用层对象的持久化- protostuff 基于 protobuf 协议,但不需要配置 proto 文件,直接导包即可
- Hessian 采用二进制协议的轻量级 remoting onhttp 工具
- MessagePack
- Netty 的零拷贝实现
Netty 接收或发送Bytebuf采用DirectMemory,不要拷贝到localMemory中,减少堆拷贝的过程。
- CHannelConfig创建ByteBufAllocator默认使用Drect Buffer
- CompositeByteBuf可以合并多个ByteBuf为一个写入/写出,内部个个buf是独立的
- FileRegion包装的FileChannel.tranferTo可直接将文件缓存区中的数据发送到指定Channel,
避免循环write导致的内存拷贝
- netty的高性能表现在哪些方面
- 心跳,服务端用来定时清除闲置会话,客户端用来检测测会话是否断 开,是否重来,检测网络延迟
- 串行无锁化设计,避免线程的竞争、上下文切换
- 可靠性,读写超时处理、内存池重复使用、优雅停机、资源释放
- 安全性,支持的安全协议:SSL V2和V3,TLS,SSL单向认证、双向认证和第三方CA 认证
- TCP 参数配置
NIOEventLoopGroup 源码
Zookeeper是个啥
ZooKeeper 是为分布式系统中各个服务主机提供高可用、高性能、具备严格顺序访问控制的协调服务,
- 提供一个
多层级的节点命名空间
znode,采用watch机制
监听znode,当znode节点数据变更时通知
client做出对应业务上的变更- 节点属性zxid的递增,保证了事务顺序的一致性,
- znode的类型
- PERSISTENT 持久化节点
- PERSISTENT_SEQUENTIAL 持久化有序节点
- EPHEMERAL 临时节点,临时节点断开会删除
- EPHEMERAL_SEQUENTIAL 临时有序节点
- zk可以用来做什么
负载均衡
,客户端从zk获取服务信息时,按照负载策略返回地址master选举
(recipes下有demo),通过ZAB协议利用节点信息投票,选取leader节点集群管理
,临时有序节点存储broker信息,通过心跳检测,当断开连接临时节点删除,新的服务
启动,注册到指定临时节点中分布式锁
(recipes下有demo)
- 利用节点不可重复创建来实现锁,创建成功获取锁,失败retry,直到获取到锁
- 高性能的分布式锁采用一个有序节点来做一个锁队列,进行锁分配,头节点(即序号最小)为自己时获取锁成功,具体同AQS原理
配置管理
,利用节点存储配置信息,watch节点进行配置变更处理分布式队列
(recipes下有demo),持久化有序节点按序消费数据发布和订阅
,对节点进行watch命名服务
,利用节点存储服务信息 通过指定的名字来获取资源或者服务的地址
- zk的数据复制
复制能提高容错性、系统扩展能力、性能
容错
:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;提高系统的扩展能力
:把负载分布到多个节点上,或者增加节点来提高系统的负载能力;提高性能
:让客户端本地访问就近的节点,提高用户访问速度
可以采用写主和写任意模式,zk采用基于ZAB理论实现数据复制和master选举
- 写主(WriteMaster) 方式,对数据的修改提交给指定的节点。
- 写任意(Write Any)方式对数据的修改可提交给任意的节点
- zk的工作原理
zk通过zab协议来保证server之间的同步,zab协议分为恢复模式和广播模式
恢复模式
是master服务挂了后进行master选举广播模式
是数据的同步
- zk下server工作状态
每个 Server 在工作过程中有三种状态:
- LOOKING:当前 Server 不知道 leader 是谁,正在搜寻
- LEADING:当前 Server 即为选举出来的 leader
- FOLLOWING:leader 已经选举出来,当前 Server 与之同步
- zk 如何选取leader
当 leader 崩溃或者 leader 失去大多数的 follower,这时 zk 进入恢复模式,
- 基于
basic paxos
算法,
- 发起询问zxid,收到返回计算出最大zxid
- 与最大zxid一致的server参与选举,当某个server得到票数超过一半,则选举为leader,否则继续
- 基于
fast paxos
算法,
- 发起选举提议,其他服务解决 epoch 和 zxid 的冲突,并接受对方的提议,当超过半数的server
返回接收提议则选举成功,否则继续下一轮
- zk同步流程
选完 Leader 以后,zk 就进入状态同步过程。
- Leader 等待 server 连接;
- Follower 连接 leader,将最大的 zxid 发送给 leader;
- Leader 根据 follower 的 zxid 确定同步点;
- 完成同步后通知 follower 已经成为 uptodate 状态;
- Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。
- 为什么要有leader
其他server可以共享结果,减少重复计算,提高性能
- zk节点宕机如何处理
如果是leader宕机,则进行选举leader,follower集群继续使用,当超过半数宕机,
则将停止服务,所以follower宕机咋办,赶紧检查宕机原因,起来服务
- zk和Nginx的负载区别
zk 的负载均衡是可以调控,
nginx 只是能调权重,其他需要可控的都需要自己写插件;但是 nginx 的吞吐量比 zk 大很多,应该说按业务选择用哪种方式}{
- zk的wath机制
- zk的watch是一次性的,只会出发一次
- Zookeeper 只能保证最终的一致性, 而无法保证强一致性
- 通过getData、exists、getChildren注册watch
- create、delete、setData 可出发watch
- 服务连接可以watch,bu丢失连接没法watch,so需要心跳检测,就会存在弱一致性
- 2PC、3PC
都存在 单点问题、同步阻塞、数据不一致、容错不好
2PC(two-phase-commit两阶段提交)简单容易实现
- 第一阶段 prepare 双方进行确认,会进行加锁
- 第二阶段 commit 双方进行确认提交
3PC(相比较减少了单点故障,效率也更高)
- cancommit,不锁表,避免等待响应时锁表阻塞
- precommit,锁表
- docommit,
- CAP、BASE、ZAB
CAP理论(三者不可同时兼得,需要做的是保证分区容错性的前提下,均衡一致性与可用性)
- 一致性(强一致性、弱一致性)---需要考虑保证 最终一致性
- 可用性(一直处于可用)---需要考虑保证基本可用,降级处理
- 分区容错性(不出现网络分区现象)
BASE理论
- Basically available 基本可用
- soft state 软状态,即三态的中间态,数据同步过程存在延迟,不影响系统可用性(消息队列排队)
- eventually consistent 最终一致性,数据经过一定时间同步,能够达到最终一致的状态
数据库事物
ACID
- 原子性,每一个操作都是原子操作
- 隔离性(isolation)
show variable like '%tx_isolation%'
set session transaction isolation level read uncommit
- 未提交读(READ-UNCOMMITED)
- 提交读(READ-COMMITED),解决脏读,优先
- 可重复读(REPEATABLE-READ)解决不可重复读
- 串行读(SERIALIZABLE)解决幻读,锁表
- 一致性
- 持久性
锁的本质
- 锁要解决的问题是 ------- 资源数据会不一致
- 锁要达成的目标是 ------- 让资源使用起来,像原子性一样
- 锁达成目标的手段 ------- 让使用者访问资源时,只能排队,一个一个地去访问资源
分布式环境下,如何协调资源达到原子性的操作?
- sychronized / lock 这些java天然的实现,无法跨JVM发挥作用
- 只得去寻求分布式环境里,大家都公认的服务来做见证人,以协调资源
- 常见的公证人 ------》 mysql/zk/file/redis
- 目标 ----- 通过公证人发出信号,来协调分布式的访问者,排队访问资源
- 条件 ----- 任何一个能够提供【是/否】信号量的事物,都可以来做公证人
- 陷阱 ----- 发出锁信号量的动作,本身必须是原子性的
- mysql来充当公证人,利用的是一条sql语句执行的成功/失败,是原子的,
- redis来充当公证人,利用的其 setnx指令的成功/失败,是原子的
为了防止线程宕机,造成锁死在那里挡道,需要给锁认定一个有效期限,
此期限的自动失效解锁,与线程的主动解锁之间,会存在冲突,reids的解锁流程必须考虑动作原子性,
需要改为lua脚本来执行
- lua 脚本为什么是原子性的
redis是单线程执行指令的,因此内部不存在线程竞争
- 一、服务器A依次发送了ab指令到redis
- 二、服务器B依次发送了cd指令到redis
- 三、两台机器同向redis发送的四条指令,最终在指令队列里顺序是:acbd
- 四、可以看到,服务器A发送的ab两条指令,中间穿插了c指令,破坏了其完整性,因此,ab两条指令不是原子的
- 五、lua脚本,被放进队列时,ab指令是放在一起的,因为ab会顺序一起被执行,成为了原子性动作
- 锁的问题是多对一的问题,是多个线程同时访问同一个资源,造成资源状态不一致
- 事务的问题是一对多的问题,是一个线程进数据库,操作多条sql,其中,某条sql的失败,致使整个业务失去意义
事物的操作过程
- 编程式事务:使用 TransactionTemplate 或者直接使用底层的 TransactionManager 来操作事务 commit 或者 rollback。
- 声明式事务:建立在 AOP 基础上,通过对方法前后进行拦截,加入编程式事务里的流程控制逻辑。使用的时候只需要在方法前
面加上@Transactional
BASE 理论
往往在分布式系统中无法实现完全一致性,于是有了 BASE 理论,它是对 CAP 定律的进一步扩充
BASE 指的是:
- Basically Available(基本可用) : 分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用
- Soft state(软状态) : 允许系统中存在中间状态,这个状态不影响系统可用性
- Eventually consistent(最终一致性) : 经过一段时间后,所有节点数据都将会达到一致
BASE 和 ACID 是相反的,它完全不同于 ACID 的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时
间内是不一致的,但最终达到一致状态。
分布式事物
分布式事务,是指多台数据库的执行sql,也想要达到一致性的标准,即:多台一起commit或rollback
- 基于可靠消息的最终一致性方案概述
基于XA协议的2pc,3pc,存在单点问题、同步阻塞、数据不一致
- 3pc 在阶段三,可能会因为网络导致参与者无法收到 doCommit 请求或者 abort 请求,
针对这种情况,参与者都会在等待超时之后,继续进行事务提交
必须要使用支持XA协议的datasource数据源- 2pc因为要同时锁定两个数据库的数据,事务锁定时间大大延长
- TCC事务补偿型方案
其将整个业务逻辑的每个分支显式的分成了 Try、Confirm、Cancel 三个操作。Try 部分完成业务的准备工作,confirm 部分完成业务的提交,cancel
部分完成事务的回滚。
- MQ事物
目前,仅阿里云的 RocketMQ 支持事务消息。帮助用户实现类似 X/Open XA 的分布事务功能,通过 MQ 事务消息能达到分布式事务的最终一致。
- 发送方向 MQ 服务端发送消息
- MQ Server 将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息
- 发送方开始执行本地事务逻辑
- 发送方根据本地事务执行结果向 MQ Server 提交二次确认(Commit 或是 Rollback),MQ Server 收到 Commit 状态则将半消息标记
为可投递,订阅方最终将收到该消息;MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息- 在断网或者是应用重启的特殊情况下,上述步骤 4 提交的二次确认最终未到达 MQ Server,经过固定时间后 MQ Server 将对该消息发
起消息回查- 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果
- 发送方根据检查得到的本地事务的最终状态再次提交二次确认,MQ Server 仍按照步骤 4 对半消息进行操作
- Lcn事物
锁定事务单元(lock)、确认事务模块状态(confirm)、通知事务(notify)
- TxClient 作为模块的依赖框架,提供 TX-LCN 的标准支持,
- TxManager 作为分布式事务的控制方。事务发起方或者参与方都由TxClient 端来控制。
- 创建事务组
是指在事务发起方开始执行业务代码之前先调用 TxManager 创建事务组对象,然后拿到事务标示 GroupId 的过程。
- 加入事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给 TxManager 的操作。
- 通知事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给 TxManager,TxManager 将根据事务最终状态和事务组的信息
来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方
- Seata 事务
六大基础原则 单一职责(dao/controller/service,服务分开)
- 里氏替换(子类可以扩展父类功能,但不改变父类功能)
- 依赖倒置(面向接口编程)
- 接口隔离(设计接口功能尽量细粒度,最小功能原则)
- 迪米特(降低耦合,局部变量中不引入新的类)
- 开闭原则(对扩展开放,对修改关闭,不修改原有代码扩展功能)
- 解耦,对象创建和使用过程分离
- 对象创建包含init方法的调用,黑盒创建过程
- 面向接口编程,使用者只管使用,只知其接口,不知其实现类
对象创建型模式(create)
- 单例模式(single)
饿汉式/懒汉式(线程安全:双重校验/静态内部类懒汉加载)
- 简单工厂(simple factory)
一个工厂什么都创建,要什么工厂创建什么(对象少使用,方便,对象一多,修改查看使用麻烦)
- 工厂模式(factory)
产品分类,一个工厂(interface)创建一系列水果(实例),有新的种类,直接扩展工厂
- 抽象工厂(abstract factory)
统一类工厂合并(苹果工厂/苹果箱包装)Furute/Package,spring通过DI注入实现。
- 建造者模式(build)
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示,
水果套餐组合/电脑拼装组合,说到底就是组合构建,简化复杂的创建过程
- 原型模式
创建对象的种类,并且通过拷贝这些原型创建新的对象
1、性能提高。 2、逃避构造函数的约束。
创建模型总结
类型 | 说明 | |
---|---|---|
简单工厂 | 数量少使用 | |
工厂模式 | 数量多分类使用 | |
抽象工厂 | 简单组合使用 | |
建造者 | 复杂组合使用 | |
原型 | 性能提高。逃避构造函数的约束 | 消耗资源多多,new繁琐 |
#简单工厂(数量少使用)-->工厂模式(数量多分类使用)-->抽象工厂(简单组合使用)-->建造者模式(复杂组合使用) #从单个接口-->多个接口-->组合接口的使用
结构型模式(structure)
适配和桥接都是把对象传入组合起来使用,桥接的目标是分离,适配的目标是合并
- 适配器模式(adapter)
1)接口适配,A类适配(实现)AB接口,对外提供AB接口方法
2)类适配,构造注入,调用适配方法
- 匹配不匹配,通过转接头适配(2)3))
- 桥接模式(bridge)
- 通过提供抽象化和实现化之间的桥接结构,解耦
- 构造注入,组合N种结果(桥A通向A1 A2 A3多地,桥B通向B1 B2 B3 多地,A引入B接口/抽象类,A就能与B组出从An-->Bn的N种组合)
- 装饰器模式(decorator)
io缓冲流的实现思路,通过装饰原有类,扩展功能,装饰类也会实现同一接口,动态增加功能,动态撤销。
- 代理模式(proxy)
为其他对象提供一种代理以控制对这个对象的访问。
- 组合模式(composite)
如tree,通过组合包含,组织结构
将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 外观模式(facade)
当访问流程比较复杂时,将流程封装成一个接口,提供给外部使用
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
行为设计模式(action)
#模板和策略都是
- 模板方法模式(template)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的
结构即可重定义该算法的某些特定步骤
如: spring种的Template
- 策略模式(strategy)
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
根据不同的策略来处理不同算法分支
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
- 责任链模式(chain)
- 一个责任链client,注册多给责任类,链条传递执行(多个优惠卷传递执行)
- 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,
并且沿着这条链传递请求,直到有对象处理它为止
- 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,
无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
如:netty的handler,spring的filter
- 观察者模式(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
- 订阅/发布,JDK提供了Observeable/Obsever 来实现观察者模式
- 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
- 命令模式(command)
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化