「架构师训练营 4 期」 第九周 - 001&2
作业一
请简述 JVM 垃圾回收原理。
设计一个秒杀系统,主要的挑战和问题有哪些?核心的架构方案或者思路有哪些?
作业二
根据当周学习情况,完成一篇学习总结
答案一
题目一
垃圾回收方法
jvm 的垃圾回收是指对于 jvm 堆之后不再使用的对象清理掉,释放内存资源。
问题来了,我们怎么找到哪些对象不再使用?
通过可达性算法,从从堆栈的局部变量或者方法区静态变量作为根目来触发,然后对于对象进行标记,对于对象进行进一步的标记,知道所有使用对象标记完毕,那么没有标记的就是可回收对象。在标记可回收对象之后,我们怎么回收呢?
可回收的对象分成以下三种回收方式:
清理:直接清理,会有碎片空间
压缩:压缩来避免碎片,拷贝存货对象到连续扩容
复制:通过复制方式来把所以可用对象复制到另一个空间中
jvm 分代垃圾回收
按照生命周期来看,在实际使用中很多对象的生命周期是很短的,通常来说都是一个方法内的内向的生存时间,为了快速回收这种较小生命周期的对象,所以分成新生代和老年代两种。
整体流程 : eden - from - to - 老年代
eden 区:eden 满的时候通过复制的方法把 eden 的对象复制到 from 或者 to 区,这个操作叫做 youngGC
from 区:from 满的情况下复制到 to 区
to 区:to 区满了的情况下复制到 from 区
老年代:多次拷贝未回收的对象复制到老年代,如果老年代也满了的情况下回触发老年代和新生代一起的垃圾回收叫做 fullGC
jvm 垃圾回收器算法
串行回收器:停止所有线程来进行垃圾回收,早起对于单核 cpu 的场景
并行回收器:多核的情况下,多个线程处理较多并行回收器,但是所有线程都被中止了
并发回收器 cms:只有初始标记和重标记的情况下会停止所有的线程
G1 回收器:更精细的内存区域管理,包括 eden、survivor,old 和 humongous 根据 GGpauseMills 时间来分块回收。
题目二
设计一套秒杀系统,从历史的经验看最大挑战是两个:
高并发挑战:单接口或者多接口的核心 qps 最大值
基础资源层面
服务器资源
图片带宽资源及 CDN 资源
业务层面
消息队列异步、消峰
分布式多级缓存,避免数据库压力
数据库读写分离、扩库分表
保证核心链路坐好熔断、降级
其他
安全放刷,基于请求特征的 waf
高可用 &多活
快速的扩容缩容
监控:metirc 监控、日志监控、全链路监控及活动大盘
数据一致性挑战
在高并发场景下,大概率会引入缓存或者多个缓存来实现支持更高的并发,这样子各层数据一致性是一个至关重要的问题(突出表现在库存一致性)
主从 db 一致性
半同步复制
强制读主
数据库中间件
缓存记录写 key
缓存和数据库一致性
缓存双淘汰
job 异步对账
冗余表数据一致性
服务同步写
服务异步写
job 线下异步写
锁
从案例这次经验看:
电商商详 &下单和支付操作分离
尽量在前端处理请求,避免服务端请求对于后端依赖
逐层降级来进行限流
CDN 缓存提高静态资源的并发
安全防刷
系统架构各个组件调优
作业二
数据库基本原理
数据库的基本架构
SQL - 连接器 - 语法分析器 - 语义分析与优化器 - 执行引擎
连接器:数据库的连机器作为数据库的核心通信组件,数据库为了提高处理能力对每个链接分配一块单独的内存用来管理会话上下文。日常应用的过程中,为了避免每次请求新建和释放链接,使用连接池在初始化的时候创建完毕链接,执行 sql 的时候在连接池中获取链接并执行即可。
语法分析器:根据已有语法分析器构建数据库的抽象语法树,进行 sql 的拆分。日常提示的语法错误都是在语法分析器的提示。
语义分析和优化:对于复杂的 sql 进行语义转换,得到几种有限的关系计算结构中,然后利用索引可以进一步优化
执行计划:产生的执行计划包含具体的执行情况。
数据库的存储
B+树
以 msql 为例,数据库都是通过链表 & 树的数据结构,B+树简单的理解的话是平衡 N 叉树,叶子节点就是链表,以图为例这个 N 就是 3
聚簇索引
以 B+树为例,记录着数据库的索引和数据的关系就是聚簇索引,下图为例 1 2 3 是主键,r1 r2 r3 就是行记录
非聚簇索引
为什么会有非聚簇索引?因为除了主键字段还会有其他索引需求,这种情况下我们 B+树的叶子节点不是简单的行记录,而是主键记录,然后通过主键索引找到行记录。问题是为什么不可以在非聚簇索引中保存行记录呢?这样子会导致记录保存多份,冗余增加,数据一致性维护难度增加这几个方面看不是很合理。
✅【任务】聚簇索引的数据结构有哪些缺点?是否有什么好的优化方式呢?
插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于 InnoDB 表,我们一般都会定义一个自增的 ID 列为主键
更新主键的代价很高,因为将会导致被更新的行移动。因此,对于 InnoDB 表,我们一般定义主键为不可更新。
二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。
✅【任务】MyISAM 索引实现
https://www.cnblogs.com/jiawen010/p/11805241.html
索引
索引可以提交查询效率,建议合理利用索引优化查询效率,但是不要盲目添加索引。
添加索引变更时间长,锁具库操作会出现堵塞,影响数据库性能
索引过多,变更记录革更新索引记录的 b+树增加
建议使用更小的数据类型创建索引
数据库事务
原子性:atomicity,或者全部完成或者全部取消,事务崩溃支持回滚
隔离性:lsotation,事务同时运行的情况下,最终结果相同,跟事务顺序无关,主要依赖各类锁来实现
持久性:Durability,一旦事务提交,需要持久化保存到数据库里面
一致性:consistency,只有合法的数据才能够写入。
数据库的事务一致性依赖的数据库日志来实现,如果是完成正常记录,如果是存在某条记录失败则全部回滚,最重要的是
UNDO 未作,即更新前的日志
REDO 重做,即本次更新的日志
JVM 虚拟机架构原理
JVM 组成架构
java 字节码文件
为什么叫做字节码?因为 java 一共 200 多个指令可以再一个字节中存储(256),对于 java 的执行来说,通常来说是字 cafebabe + 节码指令 + 操作数据,字节码执行流程,有点类似于内存的处理逻辑,通过 cache 来缓存。
java 字节码编译过程跟 sql 的执行过程基本一致的。
类加载器如何工作?分层加载,双亲委托模型。
堆 & 栈
✅【任务】队列、堆、栈和堆栈的区别?
队列:先进先出
堆栈:先进后出
java 栈是单独线程独自申请的运行数据,堆是整个 java 虚拟机共享的。
例如 new a = A 类 情况下,整个代码是放在方法区里面的字节码指令,new 的 class 是放在堆里面,变量 a 是存放在对应线程的变量 a 是存放在 java 栈里面。
方法区 & 程序计数器
方法区主要是针对于字节码文件,是一种特殊的堆。
程序计数寄存器,类似于 cpu 的 cpu 寄存器和程序计数器,记录当前的线程命令任务执行情况,执行到哪一行字节码
java 线程栈方法调用
通过先进后出的方式来保证线程隔离的安全性
线程工作内存 & volatile
这里的工作内存是 cpu cache,是每个 cpu core 的 cpu cache,是不共享的。
java 的运行环境
JVM 垃圾回收
jvm 的垃圾回收是指对于 jvm 堆之后不再使用的对象清理掉,释放内存资源。
通过可达性算法,从从堆栈的局部变量或者方法区静态变量作为根目来触发,然后对于对象进行标记,对于对象进行进一步的标记,知道所有使用对象标记完毕,那么没有标记的就是可回收对象,可回收的对象分成以下三种回收方式:
清理:直接清理,会有碎片空间
压缩:压缩来避免碎片,拷贝存货对象到连续扩容
复制:通过复制方式来把所以可用对象复制到另一个空间中
🔲【任务】是否有其他的垃圾回收算法?可达性算法之外。
jvm 分代垃圾回收
按照生命周期来看,在实际使用中很多对象的生命周期是很短的,通常来说都是一个方法内的内向的生存时间,为了快速回收这种较小生命周期的对象,所以分成新生代和老年代两种。
整体流程 : eden - from - to - 老年代
eden 区:eden 满的时候通过复制的方法把 eden 的对象复制到 from 或者 to 区,这个操作叫做 youngGC
from 区:from 满的情况下复制到 to 区
to 区:to 区满了的情况下复制到 from 区
老年代:多次拷贝未回收的对象复制到老年代,如果老年代也满了的情况下回触发老年代和新生代一起的垃圾回收叫做 fullGC
jvm 垃圾回收器算法
串行回收器:停止所有线程来进行垃圾回收,早起对于单核 cpu 的场景
并行回收器:多核的情况下,多个线程处理较多并行回收器,但是所有线程都被中止了
并发回收器 cms:只有初始标记和重标记的情况下会停止所有的线程
G1 回收器:更精细的内存区域管理,包括 eden、survivor,old 和 humongous 根据 GGpauseMills 时间来分块回收。
java 启动参数
JVM 性能诊断工具
基本工具:jps、jstat、jmap、jstack
集成工具:jconsole、JVisualVM
jstat
主要是针对于资源和性能的实时监控,对 heap 内存和垃圾回收情况,例如一直 fullgc 影响了 java 性能。
jmap
主要针对于内存泄露的分析
jstack
查看 jvm 的堆栈情况
java 代码优化
合理并谨慎的使用多线程
多少更合理?通过公式计算难度极大,压测是最好最大标准!
竞争调节和临界区(锁)
同一个程序的多个线程同时访问相同的资源的情况下下,如果对资源的访问顺序敏感会出现竞态条件,导致竞态条件发生的代码区被称作为临界区,需要通过适当的同步和锁来避免竞态条件。
线程安全
方法内的局部变量和对象都是安全的,主要是针对于堆共享的多线程同时更新的资源,需要通过加锁来保证线程安全,大家坐好基础的认知。
ThreadLocal
线程局部变量,即在堆中被大家共享,每个对象又是私有访问 threadlocal 变量,不会产生线程安全。
java 内存泄漏
java 常见的内存泄漏有哪些?最常见的是 java 人员开发自身使用问题而非 c++中的未释放的情况,主要有
长生命周期的对象
静态容器,list/map 等场景最为常见
本地缓存
栈帧中,对象设置为 null,堆中就不会有指针指向,可以用这个小技巧来做垃圾回收。
面试小问题:在 new 一个对象的时候,代码的执行步骤是什么?
静态代码段
静态成员变量
父类构造函数
子类构造函数
使用 buffer 来写入,或者是非阻塞异步 IO 来做。
尽量使用组合代替继承
版权声明: 本文为 InfoQ 作者【凯迪】的原创文章。
原文链接:【http://xie.infoq.cn/article/205cb86a0f7aecb7a5f464223】。文章转载请联系作者。
评论