架构师训练营 1 期 - 第九周总结(vaik)
本周概述
第一课,主要讲解的是 Mysql 数据库的基本架构,连接器的作用与优化,语法分析器的作用,语义分析与优化器,执行引擎,表使用 B+树作为索引的原理,聚簇索引,非聚簇索引的概念,事务 ACID 的机制原理。
第二课,主要讲解 JVM 的基本框架和原理,类加载,方法区,堆,Java 堆栈,程序计数器
第三课,主要讲解 JAVA 的垃圾回收机制, 不同的垃圾回器和垃圾回收算法相关的机制原理
第四课,主要讲 Java 开发过程,合理使用多线程,避免内存泄漏,优化性能相关的技巧
第五课第六课,详解秒杀系统的设计难点与解决方案
Mysql 数据库的架构原理
PrepareStatement 预编译
能预防 SQL 注入,保证数据库安全性
提升性能
数据库基本架构
SQL-》连接器 -》 语法分析器-》语义分析和优化器-》执行引擎
连接器
使用连接池复用连接,提升性能,节省系统资源开销
语法分析器
创建一个 SQL 语法树
语义分析与优化器
分析复杂的多层嵌套的 SQL,转化为等的 SQL,得到有限的几种代数结构关系
执行计划 EXPLAIN
使用 explain 来分析 SQL 执行的过程,是全表扫描,还是利用了索引
数据库索引
mysql 默认使用 B+树建立索引
聚簇索引:主键会默认创建聚簇索引,且一张表只允许存在一个聚簇索引,叶子节点就是数据节点
非聚簇索引:叶子节点仍然是索引节点。
回表:能过非聚簇索引,找到聚簇索引的数据节点的过程
合理使用索引:数据库添加索引的 ALTER 操作会耗时较长,ALTER 期间所有数据库的增删改查全部阻塞
删除无用索引
使用更小数据类型创建索引
数据库事务
事务特性 ACID
原子性(Automicty):事务要么全部完成,要么全部取消。事务失败,状态回滚到事务发生前。
隔离性(Isolation):使用锁实现隔离,保证并发情况下的数据安全性。
持久性(Durability):一旦事务提交,不管理发生什么(崩溃或出错),数据要保存在数据库。
一致性(Consistency):只有合法的数据(依照关系约束和函数约束)才能写入数据库。
数据库事务日志
JVM 虚拟机框架
JVM 组成架构
类加载器:加载各种来源(磁盘,内存,网络的)类文件
运行期数据区:线程共享区(方法区,堆)线程独享区(Java 栈,程序计数寄存器)
执行引擎:将字节码转化为本地机器码,并执行
字节码执行流程
超过阈值就会成为热点代码,会缓存起来
Java 字节码文件编译过程
双亲委托模型
BootstrapClassLoader(启动类加载器)
c++
编写,加载java
核心库 java.*
,构造ExtClassLoader
和AppClassLoader
。开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
ExtClassLoader (标准扩展类加载器)
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者
java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。
AppClassLoader(系统类加载器)
java
编写,加载程序所在的目录,如user.dir
所在的位置的class
CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
类加载过程
在代码编译后,就会生成 JVM(Java 虚拟机)能够识别的二进制字节流文件(*.class)。而 JVM 把 Class 文件中的类描述数据从文件加载到内存,并对数据进行校验、转换解析、初始化,使这些数据最终成为可以被 JVM 直接使用的 Java 类型,这个说来简单但实际复杂的过程叫做 JVM 的类加载机制。
一、类的加载
1、通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class 文件)。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。这个 Class 对象并没有规定是在 Java 堆内存中,它比较特殊,虽为对象,但存放在方法区中。
二、类的连接
1、验证
验证被加载后的类是否有正确的结构,类数据是否会符合虚拟机的要求,确保不会危害虚拟机安全。
2、准备
为类的静态变量(static filed)在方法区分配内存,并赋默认初值(0 值或 null 值)。
3、解析
将类的二进制数据中的符号引用换为直接引用。
三、类的初始化
类的初始化的主要工作是为静态变量赋程序设定的初值。
Java 虚拟机规范中严格规定了有且只有五种情况必须对类进行初始化:
使用 new 字节码指令创建类的实例,或者使用 getstatic、putstatic 读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行过初始化。
通过 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要首先进行初始化。
当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化。
当虚拟机启动时,用户需要指定一个主类(包含 main()方法的类),虚拟机会首先初始化这个类。
使用 jdk1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REFgetStatic、REFputStatic、RE_invokeStatic 的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。
JAVA 堆 &栈
堆:每个 JVM 实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放
在这个堆中,并由应用所有的线程共享。
堆栈:每个 JVM 为每个新创建的线程分配一个堆栈。
所有对像的存储空间都在堆中分配的,每个对像的引用是在堆栈中分配。
方法区和程序计数器
方法区就是用来存放已被加载的类信息,常量,静态变量,编译后的代码的运行时内存区域
当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器
线程工作内存 &volatile
每个线程都有自己的执行空间(即工作内存)
线程执行的时候用到某变量,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作:读取,修改,赋值等,这些均在工作内存完成,操作完成后再将变量写回主内存;
一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,保证了不同
线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他
线程来说是立即可见的
又
Java 运行环境
JAVA 垃圾回收机制
Java 的垃圾回收就是将 JVM 堆中已经不再使用的对象清理掉。
JVM 通过可达性分析算法进行垃圾对象识别。
三种垃圾回收算法
标记-清除算法
标记-复制算法
标记-整理算法
JVM分代垃圾回收
绝大多数的对象都是“朝生夕灭”的,既创建不久即可消亡;
熬过越多此垃圾回收过程的对象就越难以消亡;
G1 垃圾回收内存管理机制
G1 GC 由 Young Generation 和 Old Generation 组成。G1 将 Java 堆空间分割成了若干个 Region
G1 GC 深度原理
G1 把整个 Java 堆划分为若干个区间(Regions)。每个 Region 大小为 2 的倍数,范围在 1MB-32MB 之间,可能为 1,2,4,8,16,32MB。所有的 Region 有一样的大小,JVM 生命周期内不会改变。例如-Xmx16g –Xms16g,设置 16GB 的堆大小,2000 个 Regions,则每个 Region=16GB/2000=8MB。如果堆大小很大,而每个 Region 的大小很小,则 Region 数量可能会超过 2000 个。同样地,很小的堆大小会导致 Region 数量很少。
Region 类型
Available Region=可用的空闲 Region
Eden Region = 年轻代 Eden 空间
Suivivor Region=年轻代 Survivor 空间
所有 Eden 和 Survivor 的集合=整个年轻代
Humongous Region=大对象 Region
Humongous Region
大对象是指占用大小超过一个 Region50%空间的对象,这个大小包含了 Java 对象头。对象头大小在 32 位和 64 位 HotSpot VM 之间有差异,可以使用 Java Object Layout 工具确定头大小,简称 JOL。
Java 启动参数
JVM 性能诊断工具
基本工具:JPS,JSTAT,JMAP,JSTATCK
集成工具:JConsole,JVisualVM
JAVA 开发技巧与优化
合理并谨慎使用多线程
启动线程数 = [任务执行时间 / (任务执行时间 - IO 等待时间)] * CPU 内核数
竞态条件与临界区
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞
态条件发生的代码区称作临界区。
在临界区中使用适当的同步就可以避免竞态条件
Java 线程安全
允许被多个线程安全执行的代码称作线程安全的代码。
方法局部变量,局部变量存储在线程的栈中,永远不会被多个线程共享,所以是线程安全的
方法局部的对象引用,如果方法中创建的对像不会逃逸出此方法,那么它是线程安全的
对像成员变量,对像成员存储在堆上,如果多个线程能同时更新对象的一个成员,那这个代码就不是安全的。
ThreadLocal
在堆上创建的两层 map,第一层 map 保存的线程对象作为 Key 的 map, 第二层 map 保存的是查找的 key 对象,
所以每次查找 key 时,都是先根据当前线程对旬找到线程对应的 map,再在这个 map 中找到 key 对应的对象。
所以是线程安全的。
Java 的内存泄漏
Java 的内存汇漏是由于开发人员的错误引起的。
如果程序保留永远不再使用的对象,这些对象将会占用并耗尽内存
长生命周期对象
静态容器
缓存
合理使用线程池和对象池
复用线程池对象资源,避免在程序的生命期中创建和删除大量对象
池管理算法(记录哪些对象是空闲的,哪些对象正在使用)
对象内容清除(ThreadLocal 的清空)
使用合适的 JDK 空器类(顺序表,链表,Hash)
缩短对象生命周期,加速垃圾回收
减少对象驻留内存的时间
在使用时创建对象,用完释放
创建对象的步骤(静态代码段-静态成员变量-》父类构造函数-》子类构造函数)
使用 I/O buffer 及 NIO
使用组合代替继承
合理使用单例模式
计算机的任何问题都可以通过虚拟层(或者中间层)解决
面向接口编程
7 层网络协议
JVM
编程框架
一致性 hash 算法的虚拟化实现
秒杀系统案例剖析
要解决的核心问题
秒杀系统如何应对百倍甚至几百倍的并发爆增?
如何不影响旧的业务系统?
预防刷数据和自动化并发下单等安全防患。
基本思路和解决方案
单独部署,与原来系统物理隔离,下单成功后共用支付系统相关的逻辑。
资源静态化,通过 CDN 缓存加速,优化前端减少请示次数,节省网络资源消耗。
提升带宽,升级服务器配置。
并发控制,设置阀门,只放最前面一部分人进入秒杀系统,三道阀门。
4.1 限制进入秒杀页面
4.2 限制进入下单页面
4.3 限制进入支付系统
减少不必要的数据库读写,后端生成静态页面。
5.1 生成的静态页面的 URL 随机
5.2 秒杀前 2 秒写入随机的 URL 至请求页面
5.2 订单 ID 随机
5.3 不能直接跳过秒杀 Detail 页面进
5.4 如果秒杀过,直接进入秒杀结束页面
评论