思维导图详见https://www.processon.com/view/link/62b2e08f5653bb0724fd7359
网络协议
网络分层模型
- OSI七层体系
- TCP/IP五层
TCP
- 面向连接、可靠、基于字节流、传输层通信协议
- 三次握手
- 四次挥手
- 数据传输过程
UDP
- 面向连接、不保证可靠、基于报文、无阻塞的传输层协议
HTTP
- 无状态、无连接、数据格式灵活、简单快速、应用层的超文本传输协议
- 报文格式:报文首部(请求行、多个请求体/状态行、多个响应头)、空行、报文主体
- 请求类型:GET/POST/HEAD/PUT/DELETE/TRACE/OPTIONS/CONNECT
- 状态码及含义
- 1xx 请求处理中
- 2xx 请求成功处理
- 3xx 重定向
- 301:Moved Permanently 永久重定向
- 302:Found 临时重定向
- 4xx 客户端错误,请求不合法
- 400 Bad Request 请求语法错误
- 401 Unauthorized 未经授权
- 403 Forbidden 拒绝提供服务
- 404 Not Found 资源不存在
- 5xx 服务端错误
- 500 Internal Server Error 不可预期错误
- 503 Server Unavailable 服务不能处理请求不可用
- 支持B/S、C/S架构
- Cookies与Session
- Session由服务端生成,可存放于文件、数据库、内存
- cookies在客户端,客户端请求服务端会生成对应sessionid,并返回客户端,通过setCookies将其放入,后续请求发送cookie可查验sessionid保证其状态
- 完整的http请求过程
1、DNS域名解析
2、建立socket连接,发起TCP三次握手
3、客户端给服务端发送请求命令(请求头和数据)
4、服务端返回响应头与数据
5、关闭socket连接,TCP四次挥手
6、客户端根据返回渲染数据
HTTPS
- HTTP+加密+认证+完整性保护=HTTPS
- 在TCP与HTTP之间加入SSL/TLS为上层安全保驾护航
- 单向认证
- 双向认证
- WEB攻击
- DOS攻击、SQL注入、中间人攻击、OS命令等
JVM
内存模型
- 程序计数器
- 虚拟机栈
- 本地方法栈
- Java堆
- 方法区
- 运行时常量池
- 直接内存
对象分配与创建过程
OOM、内存溢出与内存泄漏常见场景
虚拟机性能监控、故障处理工具
- jps -l:查询虚拟机进程
- jstat -gc/-gcutil:监控虚拟机运行状态
- jinfo -flag xxx:实时查看虚拟机参数
- jmap -dump:format=b,file=eclipse.bin PID:生成对应堆转储快照
- -XX:+HeapDumpOnOutOfMemoryError/-XX:+HeapDumpOnCtrlBreak:在对应条件下生成堆转储快照
- jhat:堆转储快照分析工具
- jstack -l PID:堆栈跟踪工具
对象死亡判断
- 引用计数法
- 可达性分析
- GCRoots对象:
- 1、虚拟机栈中引用的对象,被调用方法的参数、局部变量、临时变量等
- 2、方法区的类静态属性引用的对象
- 3、方法区中的常量引用的对象
- 4、本地方法栈JNI引用的对象
垃圾回收算法
- 标记-清除
- 内存碎片、导致内存不连续、使用率不高导致无法分配触发GC
- 效率随对象数量增长而降低
- 标记-复制
- 只使用一半内存,不过没有内存碎片
- 复制在存活对象较多时复制效率低
- 标记--整理
- 没有内存 碎片
- 标记整理效率不高
垃圾回收器
- ParNew+CMS
- 初始标记与重新标记会STW
- G1
- ZGC
常用的调优命令
- Xms=Xmx、PermSize=MaxPermSize,避免内存扩展
- 子主题
类加载过程
- 加载-验证-准备-解析-初始化-使用-卸载
双亲委派机制
- 类加载过程中选择类加载器过程源码判断
- 破坏双亲委派机制
- JavaSPI与Dubbo 增强SPI6以前的Thead Context Classloader和之后的ServiceLoader,父类加载器请求子类加载器加载类
- OSGi模块化热部署
- 为什么优先父类加载?
- 避免重复加载,父类加载后,子类无需加载
- 安全性,避免用户自定义类替换掉Java核心类
Java基础
String为何选用31做hashcode方法的乘数?
- 31是个不大不小的质数,是优选乘数
- 31可被jvm优化为31*i=(i<<5)-i
- 质数太小,hash区分度不够,容易冲突质数太大,hash结果太大,int类型表示不了
Integer缓存
- -128~127位缓存数据,超出该范围才会new Integer
类实例化顺序
- 父类静态变量-父类静态代码块-子类静态变量-子类静态代码块-父类非静态变量-父类构造方法-子类非静态变量-子类构造方法
ArrayList扩容
- size>>!+size 1.5倍扩容
- 初始值为10,最大值为integer最大值
HashMap
- 扩容获取比当前容量大的第一个2的指数
- 因为get()通过hash&(n-1)来获取
- hash函数:key.hashcode()^(h>>>16)
- 通过低位使得hash更加分散,保留高位信息
- 链表树化 Treeify_Threshold=8,元素最少64
- 理想情况,负载因为为0.75,链表个数出现概率服从泊松分布,长度为8概率为0.00000006,不足千万分之一
- 红黑树退化为链表 UnTreeify_Threshold=6
- 以8、7为退化阈值,会因为树化阈值为8,而反复在临界附近树化退化影响性能
- HashMap与HashTable
Java并发
多线程开发良好实践
- 给线程、线程池命名,方便问题追踪
- 最小化同步范围
- 优先使用volatile,而不是synchronized
- 尽量使用更高层次并发工具,而非notify-wait实现线程通信
- 优先使用并发容器
- 考虑使用线程池
提高锁性能
- 减少锁持有时间,降低锁冲突可能
- 将可能冲突代码往后放,提高并发能力
- 减小锁粒度,分割数据结构分段加锁
- ConcurrentHashMap
- 读写锁替换独占锁
- 功能点分割
- 锁分离
- 插入数据和取出数据分离,阻塞队列
- 锁粗化
- 多个锁统一资源,一个整个锁
JVM对锁优化
- 锁偏向
- 之前获取锁的线程再次获取锁,无需任何同步操作
- 轻量级锁
- 自旋锁
- 偏向锁-失败-获取轻量级锁-失败-膨胀为重量级锁-自旋-失败-线程挂起
- 锁消除
- 去除不可能存在竞争的锁,逃逸分析
volatile
- JMM内存模型
- 只保证可见性,不保证原子性
- 避免指令重排序
CAS
- rt.jar的sun.misc.Unsafe
- CAS与wait应使用while做条件判断,避免虚假唤醒
Atomic类
- AtomicInteger
- AtomicReference
- AtomicStampedReference
ThreadLocal
- Thread类维护threadlocalmap<TheadLocal,Object>,其中ThreadLocal为弱引用,可能出现内存泄漏
- 业务完成后finally块中手动threadLocal.remove(),防止内存泄漏
- inheritaleThreadLocal可访问父线程变量,在thread中也维护对应的map
CopyOnWriteArrayList
- 写时复制COW
- 增删改使用独占锁,复制array副本,在副本做操作,完成后赋值给array
- 获取与遍历取得为快照,存在弱一致性
LockSupport
Semapore
- acquire、release 等待达到对应数量要求后进行后续逻辑,可重复使用
ContDownLatch
- countDown、await 火箭发射,一次性使用,主线程等待所有检查完成
CyclicBarrier
- await public CyclicBarrier(int parties,Runnable barrierAction)
Guava的RateLimiter
- 限流漏桶算法令牌桶算法
AQS AbstractQueuedSynchronizer
- exclusiveOwnerThread:锁持有线程
- head tail:Node双端队列,AQS队列
- newCondition(),返回conditionObject对象,维护条件队列
- tryAcquire与tryRelease需要子类实现,修改对应state状态并返回
- lock方法拿不到锁,进入AQS队列等待;trylock拿不到直接返回false
- xxxShared:共享锁实现
- xxxInteruptibly:对中断进行响应,中断后抛出InterrupterException
ReentrantLock
- state:可重入次数
- 实现newCondition()
- 实现AQS要求的各种方法
- 内部有Sync的子类FairSync与NonFairSync,构造方法有是否公平参数
ReentrantReadWriteLock
- state:高16位为读锁状态,低16位为写锁状态
- 读读不阻塞,其余都阻塞
- ReadLock不支持condition
队列
- ConcurrentLinkedQueue 无界非阻塞队列 单链表+volatile+CAS
- LinkedBlockingQueue 有界阻塞队列 单链表+reentrantlock
- ArrayBlockingQueue 有界阻塞队列 数组+独占锁
- PriorityBlockingQueue 优先级无界阻塞队列 最小堆实现有序
- DelayQueue 无界阻塞延时队列
JDK并发容器
- ConcurrentHashMap
- CopyOnWriteArrayList
- ConcurrentLinkedQueue
- BlockingQueue
- ConcurrentSkipListMap
- Vector
线程池选择
- 高三位表示线程池状态,低29位表示线程数
- 构造器各参数含义
线程池大小设置
- CPU密集型
- N+1
- CPU使用率高,开启过多线程,产生上下文切换开销
- IO密集型
- 2N+1
- CPU使用率不高,在IO等待时处理别的任务
- 混合型
- 分别使用各自线程池
- (线程等待时间/线程CPU时间+1)*CPU期望使用率*CPU核数
过期时间
- 1s内任务数*单线程单个任务时间*80%(八二原则)
队列大小
- 1s线程可处理任务数
自定义threadFactory的name前缀,方便问题追踪
拒绝策略
AbortPolicy(抛出异常)
CallerRunsPolicy(调用线程执行任务)
DiscardPolicy(丢弃,不抛异常)
DiscardOldestPolicy(丢弃最老任务,执行当前任务)
线程池任务提交过程
1、任务为空,抛出NPE
2、当前线程数是否小于核心线程数如小于开启新线程运行
3、否则判断线程池状态是否为Running,如果是添加任务到阻塞队列再次判断如果不是Running,从阻塞队列中删除,并执行拒绝策略否则如果线程池为空,新建线程执行
4、如果队列满,则新增线程执行,新增失败执行拒绝策略
Fork-Join框架
- RecursiveTask
- RecusiveAction
生产者-消费者模式
CompletableFuture
MySql
架构
Server层
- 连接器:管理连接,权限校验
- 分析器:词法语法分析
- 优化器:执行计划生成,索引选择等
- 执行器:操作引擎,返回结果
- 缓存:8.0之后删除
引擎层
- InnoDb
- 支持事务
- 有crash-safe能力(redo log)
- 支持并发行锁
- MyISAM
- 不支持事务
- 只支持表锁
- Memory等
长链接内存耗用严重
- Mysql执行过程中使用的内存都是在连接对象中的,断开连接后才会释放连接累积会导致内存占用严重,OOM,Mysql异常重启
- 定期断开长连接
- 5.7之后,执行mysql_reset_connection重新初始化连接,过程中不需要鉴权或重连
日志模块
- redo log
- 属于InnDB,通过它保证crash-safe能力
- 内存结构类似于RingBuffer,环形链表大小固定,满了会将头部记录刷入库后擦除
- 是物理日志,记录在某个数据页中做了什么修改
- binlog
- 属于Server层,所有引擎都可用
- 文件记录,可追加写入不会覆盖
- 记录逻辑操作,分为statement、row、mix等格式
- 两阶段提交
- redo log写入会分为prepare和commit阶段
- 更新操作到内存后,写入redo log此时为prepare状态写入binlog成功后,redo log为commit状态,提交事务
- 如果没有两阶段提交,redo log与binlog任何一个写入失败,binlog与redolog数据都不一致,会导致从库同步或者异常恢复导致数据不一致
ACID及隔离级别
- 原子性、一致性、隔离性、持久性
- 读未提交
- 脏读:未提交变更其他事务可见
- 读提交
- 不可重复读:提交变更其他事务可见
- 可重复读
- 幻读:前后读取数量不一致
- 串行化
- 以上问题都解决,但并发性能差
- 一致性视图及MVCC、undolog
索引
常见索引实现方式
- Hash表
- 只有等值查询场景,没有范围查询,Nosql引擎使用
- 有序数组
- 不适合更新,只适用静态存储引擎
- 搜索树(N叉树)
- 即可等值查询,也可范围查询
InnoDB实现
- B+树
- 主键索引叶子节点存储整行数据,非主键索引存储的主键(回表,到主键索引查询整行数据)
- 叶子节点会存储链指针,实现快速区间查询
- B+树为N叉树,一般去N为1200左右,树高为4时已到十亿级别,减少IO次数
- 分类
- 主键索引(聚簇索引)
- 普通索引(非聚簇索引)
- 唯一索引
- 联合索引
联合索引创建原则
- 调整顺序可减少索引,便优先考虑这个顺序
- 高频查询选择交换顺序,争取占用内存最小
- 最左前缀原则
覆盖索引
- 索引覆盖了查询需求,减少回表,提升性能
索引下推
- 索引遍历过程中,对索引包含字段先做判断,过滤不满足条件的
普通索引与唯一索引
- 查询过程
- 普通索引会查询到第一个不满足条件的记录为止
- 查询到满足条件记录即停止
- 更新过程
- 如果记录数据页在内存中
- 普通索引查询到对应记录,更新,执行结束
- 唯一索引查询到对应记录,判断唯一性(需要回表查询整行),更新,执行结束
- 如果记录数据页不在内存中
- 普通索引将更新记录在changebuffer中,执行结束
- 唯一索引将数据页读入内存,判断没有冲突,更新,执行结束
- changebuffer
- 只适用于普通索引
- 适用于读少写多的场景,页面写完后马上被访问的概率小
- 减少磁盘访问
- 唯一索引更新操作需要判断唯一性,读取数据页到内存,无法使用changebuffer
- changebuffer与redolog
- redo log 主要节省随机写磁盘的IO操作,转为一定数量的顺序写
- changebuffer主要节省随机读磁盘的IO消耗
- changebuffer merge过程
1、从磁盘读入数据页到内存
2、从changebuffer中找出该数据页的记录,一次应用,得到新版数据页(脏页)
3、写redo log 该redo log包含数据变更和changebuffer变更
锁
- 全局锁
- flush tables with read lock 全库做逻辑备份
- 表级锁
- lock tables ... read/write
- 行锁
- 两阶段锁协议行锁是在需要的时候加上,并不是不需要立即释放,等事务结束才会释放
- next-key lock
- 行锁+间隙锁(可重复读条件下生效)实现
- 解决幻读
- 加锁规则
- 原则1:加锁基本单位next-key lock,前开后闭
- 原则2:查找过程中访问到的都加锁
- 优化1:索引上的等值查询,给唯一索引加锁时,next-key lock退化为行锁
- 优化2:索引上的等值查询。向右遍历且最后一值不满足条件,next-key lock退化为间隙锁
- 一个bug:唯一索引上范围查询会访问到第一个不满足条件的值为止
count(*)
- 遍历哪个索引树结果都一样,会找最小的索引树遍历
- InnDB不记录表中记录数,需读出计数
- count(字段)<count(主键id)<count(1)约等于count(*)
SQL逻辑相同性能差距巨大
- 对索引字段做函数操作,可能破坏索引值得有序性,优化器决定放弃索引
- 条件字段函数操作
- select count(*) from tablea where month(t_modified) = 7;
- 隐式类型转换
- select * from tablea where tradeid = 110717=select * from tablea where CAST(tradeid as signed int) = 110717Mysql是将字符串转数字
- 隐式字符编码转换
- 两表字符集不同为utf-8和utf-8m64,两表字段做表关联,utf-8表做主表,关联条件变为CONVERT(tradeid USING utf-8m64)
慢sql分析
- 配置慢sql条件收集慢sql
- 使用explain分析索引使用情况,是否排序、临时表等
- 分析索引创建是否合理且足够
- 查询infomation-schema.optimize_trace,看优化器索引选择情况
- 排除执行期间,是否有MDL、flush操作、大量循环执行sql等情况
分库分表
方式
- 垂直拆分
- 专库专用,单表拆分主子平行表
- 水平拆分
- hash取模
- 某字段范围(id、时间戳等)
问题
- 查询非分区键子弹,需要携带分区键查询
- 维护查询字段与分区键的映射关系
- 跨库join与count
- 跨库join有分页数据不会太多,业务代码中处理
- count使用redis或count表存储
- group-range
迁移
- 停机部署
- 双写部署,基于业务
- 双写部署,基于binlog
ID算法
- UUID
- SnowFlake
- 1bit-41bit-10bit-12bit保留-时间戳-workerID(进程、机房id)-序列号(如超过4096序列号,sleep(1s)获取下一秒的)
sharding-sphere
数据结构与算法
Mybatis
Spring
Spring AOP
定义
- 连接点:join point
- 切点:point cut
- execution匹配表达式
execution
within
bean
@annotation
表达式组合
- 增强:advice
- before advice
- after returning advice
- after throwing advice
- after finally advice
- around advice
- AOP proxy
- 一个类被AOP织入advice,生成包含原类和增强逻辑的代理类
JDK动态代理
Cglib动态代理(为一个未实现任何接口的类进行代理)
使用场景
1、HTTP接口鉴权
创建切面,获取request.cookie,进行鉴权处理
2、方法调用日志
before after returnning throwing advice打印对应日志
3、方法耗时统计
around advice 记录proceedingJoinPoint.proceed前后时间差
4、统一异常处理
@ControllerAdvice+@ExceptionHandler
AOP织入方式
- 编译器织入
- 类装载期织入
- 动态代理织入
AspectJ
- 采用编译器织入和类装载期织入,Spring采用动态代理
- 还支持属性级别增强,Spring仅支持方法级增强
- 使用方式
- @EnableAspectJAutoProxy
- <aop:aspectj-autoproxy/>
Spring Transaction
分类
- 声明式事务
- 编程式事务
- 手动控制事务相关操作,开启提交回滚等
Spring如何与多种数据持久层做集成
1、Spring事务通过platformTransactionManager进行管理
2、PlatformTransactionManager的抽象子类AbstractPlatformTransactionManager有多种实现:JDBC、DatasourceTransactionManager
事务的相爱难相关属性
- 传播级别:propagation
- 常用Required、Required-New
- 超时:timeout
- 只读:readonly
- 回滚:rollbackfor
- 隔离属性:isolation
使用
- 注解
- @EnableTransactionManagement
- @Transactional(rollbackfor=xxx.class,propagation=Propagation.Required,readonly=true,timeout=30,isolation=Isolation.Default})
- xml
- <tx:annotation-driven><bean id = "transactionManager" class = "xxx.DatasourceTransactionManager"> <property name = "dataSource" ref = "xxxDataSource"></bean>
- <tx:advice = "mainTxAdvice" transaction-manager="transactionManager" <tx:attributes> <tx:method name = "query*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> </tx:attributes></tx:advice><aop:config> <aop:pointcut id = "businessServiceMethods" expression = "execution(* com.xxx.xxx.*.*(..))"/> <aop:advisor advice-ref="mainTxAdvice" pointcut-ref = "businessServiceMethods"/></aop:config>
Spring IOC
实现机制
- 工厂模式+反射
SpringBean生命周期
- org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)加载对应资源类并注册beanDefination到容器(添加到beanDefinationMap与beanDefinationNames属性中)
- beanFactory获取从缓存中获取bean
- 三级缓存
- 如何解决循环依赖
- 两级缓存就可以解决循环依赖为什么设置三级缓存
- 按对应scope进行bean实例化
- 根据是否是单例、是否允许循环依赖、是否在创建中等条件判断是否放入三级缓存
- 进行属性填充,处理循环依赖
- 初始化
- 调用对应aware接口的setxxx方法
- 调用org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization
- 调用初始化方法,init-method/afterPropertiesSet
- 调用org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(AOP在这里获取工厂生产的代理类)
- 注册到IOC
- 新创建的bean放入一级缓存并清除二三级缓存,调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
- 销毁
- 容器关闭调用destory-method/afterPropertiesSet
AbstractApplicationContext.refresh(),刷新Spring应用上下文
- 加锁startupShutdownMonitor
- prepareRefresh() 时间参数等属性的初始化
- obtainFreshBeanFactory() 旧BeanFactory销毁,创建新BeanFactory并加载beanDefination
- prepareBeanFactory(beanFactory)
- postProcessBeanFactory(beanFactory) 允许子类实现对应BeanFactoryPostProcessor增强逻辑
- invokeBeanFactoryPostProcessors(beanFactory) 激活beanFactoryPostProcessor
- registerBeanPostProcessors(beanFactory) 激活BeanPostProcessors
- initMessageSource() 初始化上下文资源文件,如国际化
- initApplicationEventMulticaster() 初始化广播器
- onRefresh() 子类扩展初始化其他bean
- registerListeners() 注册监听器,并发布前期事件
- finishBeanFactoryInitialization(beanFactory) 初始化剩余单例bean
- finishRefresh() 完成刷新并发布对应事件
SpringBoot
- 构造在Spring之上的Boot启动器
- 编码、配置、部署、监控变简单,未提供注册中心、微服务相关的解决方案
统一引用版本
- 继承spring-boot-starter-parent项目
- 导入spring-boot-dependencies项目依赖<type>pom</type><scope>import</scope>
不同环境部署
- spring.profiles.active=xxx/ymal文件中spring.profiles:xxx+---分隔符
- spring.profiles.active=xxx+application-{profiles}.propertites
- 使用配置中心
- maven profiles标签
配置加载顺序
- 命令行参数
- 命令行spring.application.json=xxx
- JNDI参数
- java系统变量
- 操作系统环境变量
- 外部带profile的application配置 application-{profile}.propertites/yaml
- 内部带profile的application配置application-{profile}.propertites/yaml
- 外部不带profile的application配置application.propertites/yaml
- 内部不带profile的application配置application.propertites/yaml
- 自定义@Configuration中的@PropertySource
配置方式
- xml
- 注解
- Java Config
自动装配
- @EnableAutoConfiguration@Import(AutoConfigurationImportSelector.class)org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
- 获取@EnableAutoConfiguration注解元信息
- 通过SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,getBeanClassLoader())获取META-INF/spring.factories资源内的实现类
- 移除重复的实现类
- 通过注解的exclude、excluedename、spring.autoconfiguration.exclude获取需要排除的实现类集合
- 检查排除类的合法性
- 移除排除类
- 通过SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader)获取过滤实现类,并执行过滤逻辑OnBeanConditionOnClassConditionOnWebApplicationCondition
- 触发自动装配事件,AutoConfigurationImportEvent
读取配置方式
- @Value
- @ConfigurationPropertites(prefix="xxx")
- @PropertySource(value="config/db-config.propertites")+@Value
- @PropertySource(value="config/db-config.propertites")+@CondigurationPropertites
- @Autowiredprivate Enviroment env
启动时运行代码
- 实现ApplicationRunner/CommandLineRunner接口,并注册bean
自定义Starter
- 命名规则:{module-name}-spring-boot-starter
1、添加spring-boot-starter基础依赖
<optional>true</optional>,不传递依赖,不与引包项目版本冲突
2、创建对应配置类xxxAutoconfiguration,并添加@Configuration
3、将其全类名添加到META-INF/spring.factories文件的EnableAutoConfiguration下的实现类
4、打包构建,项目添加xxx-spring-boot-starter依赖,可以正常获取自动加载相关bean
Redis
优点
- 有丰富的数据类型
- 支持数据持久化
- 支持实施恢复
- 支持集群cluster
- 使用单线程的IO多路复用
- 支持发布订阅、lua脚本、事务等
- 删除策略同时使用惰性删除和定期删除
为什么使用redis实现缓存
- 高性能
- 内存访问速度快
- 高并发
- 单机redis支持QPS远高于mysql
常用数据结构
- String 动态字符串,可保存文本、二进制、int等,常用于计数器
- list 链表/压缩列表,双向链表实现
- hash hashtable/压缩列表,适合存储档案信息
- set intset/hashtable 存储不可重复对象,支持并集、交集、差集计算,常用于统计共同关注共同喜欢等
- zset ziplist/skiplist 有序排列,常用于排行榜、在线用户、弹幕等
- bitmap bit表示某元素状态,常用于用户签到、活跃用户、行为统计等
单线程模型
- 基于Reactor模式开发的文件事件处理器,通过IO多路复用监听客户端的大量连接
- 大量socket-IO多路复用-任务队列-文件事件分排气-分发给连接应答处理器、命令请求处理器、命令回复处理器
持久化机制
快照持久化RDB
- 类似binlog,save 60 1000 60s1000个key发生变化触发BGSAVE生成快照
AOF
- 类似redolog,只追加文件,appendonly yes appendonly everysec 每秒同步一次
混合持久化
- aof-use-rdb-permble
- RDB以一定频率进行,两次之间使用AOF记录增量记录
AOF重写
- 压缩aof文件
- 后台子线程bgwriteof完成,避免阻塞主线程
- fork线程子线程是需要拷贝处理大量数据,未完成前是阻塞的
- fork操作本身也是阻塞主线程的
- RDB是二进制,AOF是命令,磁盘IORDB效率高,从库恢复,RDB效率高于AOF
redis事务
- MULTI EXEC DISCARD WATCH
- EXEC之前,会将所有命令放入队列,一起执行
- Redis不满足回滚即原子性和持久性
问题
缓存穿透
- 热点数据缓存失效
- 对于频繁访问的任店数据。不设置过期时间
缓存击穿
- 大量请求key不存在,直接访问数据库
- 缓存物料key,避免数据库压力过大
- 布隆过滤器
- 多个hash函数,结果存于二进制位上,后续字符串进行相同多个hash,如返回结果位数据都为1,则存在
- 不存在一定不存在,存在不一定存在
缓存雪崩
- 同一时间缓存大面积失效,请求直接访问数据库
- 采用redis集群,避免单机流量过大不可用
- 限流,非核心业务返回降级信息,核心业务查询数据库
- 设置不同的失效时间,加随机数
- 缓存永不失效
保证缓存与数据库数据一致
旁路缓存模式
- 先更新DB,删除缓存
- 如缓存删除失败,添加对应的重试机制,重试后仍不可用存入队列,待服务可用时删除
延时双删
- 先删缓存,更新DB,sleep线程读取数据写入缓存的最大时间后再删除缓存
主从同步
- 建议使用主从从结构,将生成RDB的压力分散到一级从库上
哨兵机制
监控
- 周期性给所有主从发送ping命令,看是否在线,如未返回,则认为主观下线
- 超过quorum值哨兵认为主观下线,则标记为客观下线
选主
- 一定的筛选条件和规则打分,得分最高的为主库
- 筛选条件:主库的运行状态,及以往的网络连接状态,如从库多次与主库断联超过一定阈值则排除
- 规则:
1、优先级高的从库得分高,slave_priority
2、和旧主库同步程度最接近的得分高
3、id小的得分高
通知
- 基于Pub/Sub机制建立哨兵集群
- 主库上"__sentinel__:info"频道
- 基于INFO命令返回从库列表,哨兵建立与所有从库连接
- 基于Pub/Sub实现客户端与哨兵、哨兵与哨兵、哨兵与从库的连接
- 哨兵通过leader选举出一个哨兵执行主从切换
- 超过quorum并超过半数才能当选,不满足永远选不出
哨兵服务配置应保持一致
集群与数据切片
- RedisCluster
- CRC32%1024获取slot编号
- Codis
- CRC16%16384获取槽编号
GEO
实现消息队列
- list实现
- streams实现
Redis秒杀场景
要求:瞬时并发访问量高,读多写少
阶段
- 活动前
- 商品详情页静态化资源缓存
- 活动开始
- 库存查验、库存扣减(保证原子性,在redis处理避免请求压力大,更新不及时导致超售)
- 订单处理(数据库处理,真正下单请求压力小,添加重试机制保证成功)
- 结束(请求压力小,订单追踪等)
关键环节
- 请求拦截流控,恶意攻击黑名单、流控等
- 库存信息过期时间处理,防止缓存击穿不设置过期时间
- 订单异常处理,添加重试机制
- 建议:秒杀商品存量信息、分布式锁信息单独实例保存,与日常业务区分,避免相互影响
库存查验、库存扣减原子性保证
- 原子操作实现
- 单个redis保存商品信息hash,lua脚本实现库存查验和扣减
- 分布式锁
- 子主题
无锁的原子性操作
- redis多个命令实现为一个操作:Incr/Decr
- 多个命令写入Lua脚本,原子性方式执行(避免不需要并发的操作写入,通过参数传递)
- 实现分布式锁
- 加锁使用set key value NX PX/EX expiretime
- 解锁通过Lua命令获取唯一id并判断相等后释放锁
- 基于多节点实现:RedLock
Dubbo
架构图
- Consumer/Provider/Registry/Monitor
- 分层架构
- Service/Config/Proxy/Registry/Cluster/Monitor/Protcol等
增强SPI
- ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(DubboProtocol.NAME);Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension()动态编译生成对应的适配器类,并根据@SPI选择对应的实现类
zk目录结构
- Root-Service-Type-URLDubbo-com.xxx.xxx.xxxService-Providers-127.0.0.1:20881
zk实现分布式锁
- 临时顺序节点
各属性的含义及使用
SpringCloud
- Eureka
- Zuul
- Hystrix
- Ribbon
- Feign
- Config
- Bus
分布式事务
- sagas
- tcc
- 其它