第 9 周作业 2
数据库的基本原理
PrepareStatement 预编译
statement
preparestatement
数据库架构
SQL -> 连接器 -> 语法分析器 -> 语义分析与优化器 -> 执行引擎
连接器
为每个连接请求分配一块专用的内存空间用于会话上下文管理。建立连接对数据库而言比较重,需要花费一定的时间,因此大多数使用数据库连接池,这样当处理外部请求执行 SQL 操作的时候,就不需要花费时间建立连接了。
建立连接通常要几百毫秒。
语法分析器
构建成抽象语法树
语义分析与优化
将各种复杂嵌套的 SQL 进行语义等价转化,得到有限几种关系代数计算结构,并利用索引等信息进一步优化。
生成执行计划
为什么 PrepareStatement 更好
会预先提交带占位符的 SQL 到数据库进行预处理,提前生成执行计划,当给定占位符,真正执行 SQL 的时候,执行引擎可以直接执行,效率更好一点
可以防止 SQL 注入攻击
数据记录使用 B+ 树 记录
聚簇索引:数据库的记录和索引存储在一起。
MySQL 数据库的主键就是聚簇索引,主键 ID 和所在的记录行存储在一个 B+ 树中。
非聚簇索引:叶子节点记录的不是数据行记录,而是聚簇索引,也就是主键。
通过非聚簇索引找到主键索引,再通过主键索引找到行记录的过程称为回表。
为什么非聚簇索引不记录行记录?
因为数据库需要构建其他字段的索引,存放多个数据无法保证数据一致性(不应该考虑分布式的一致性)
添加“必要”的索引优化 SQL 查询性能
有索引没索引查询效率很大不同
频繁查询的字段才有必要添加
合理使用索引
添加索引的 alter 操作会消耗较长时间(分钟级别)
Alter 操作期间,数据库的增删改查全部阻塞,对应用而言,连接不能释放
删除不必要的索引,避免不必要的增删开销
使用更小的的数据类型创建索引
数据库事务(ACID)
原子性(Atomicity):全部完成,或者全部取消。靠数据库事务日志,记录更新前和更新后的数据。
隔离性(Isolation):如果两个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1 和 T2 的执行顺序,隔离性主要依靠锁实现。
持久性(Durability):一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。
一致性(Consistency):只有合法的数据才能写入数据库。
数据库事务日志
进行事务操作时,事务日志文件会记录更新前的数据记录,然后再更新数据库中的记录,如果全部更新成功,那么事务正常结束,如果过程中某条更新失败,那么整个事务全部回滚,已经更新的使用事务日志中记录的数据进行恢复,这样全部数据都恢复到事务提交前的状态,任然保持数据一致性。
LSN:一个按时间顺序分配的唯一事务记录日志序列号。
TransID:产生操作的事务 ID。
PageID:被修改的数据在磁盘的位置。
PrevLSN:同一个事务产生的上一条日志记录的指针。
UNDO:取消本次操作的方法,按照此方法回滚。
REDO:重复本次操作的方法。
JVM 虚拟机架构原理
JVM 组成架构
Java 是一种跨平台的语言,JVM 屏蔽了底层系统的不同,为 Java 字节码文件构造了一个统一的运行环境。
JVM 类似一个实际的机器,有内存、有 CPU 操作,所以叫做虚拟机。
java org.apache.catalina.startup.Bootstrap "$@" start
java 命令启动一个虚拟机线程
通过命令后面的参数,类加载器找到需要执行的类。类中需要有 main 的入口方法
加载后,给执行类启动一个主线程。每个主线程中有自己单独的 Java 栈、程序计数器;运行的方法代码放在方法区中,运行中生成的对象放到堆内存中。
执行引擎根据编译后的字节码翻译成本地的机器码执行相关命令。
Java 字节码文件
解决 Java 代码可以在不同操作系统、不同硬件平台执行。
原理是执行引擎将字节码转换为不同平台的机器指令。
在代码执行过程中,JVM 将字节码解释执行,屏蔽对底层操作系统的依赖,JVM 也可以将字节码编译执行,如果是热点代码,会通过 JIT 动态地编译为机器码,提高执行效率。
Java 字节码文件编译过程(与 MySQL 中的 SQL 执行结构相同)
Java 源文件 -> 词法解析 -> 语法解析 -> 语义分析 -> 生成字节码 -> 字节码
类加载器的双亲委托模型
Bootstrap ClassLoader 加载 jre/lib/rt.jar 等
Platform ClassLoader 加载 jre/lib/ext/\*.jar
Application ClassLoader 加载 ClassPath 中的 jar
自定义类加载器
隔离加载类:同一个 JVM 中不同组件加载同一个类的不同版本
扩展加载源:从网络、数据库中加载字节码
字节码加密:记载自定义的加密字节码,在 ClassLoader 中解密
堆 & 栈
堆:所有线程共享堆数据,存放程序运行中创建的所有类实例或数组。
堆栈:JVM 为每个新创建的线程都分配一个堆栈。对于一个 Java 程序来说,它的运行就是通过对堆栈的操作来完成的。
方法区 & 程序计数器
方法区主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆中。
线程执行到哪一行字节码指令,这个信息被存放在程序计数寄存器中。
Java 线程栈
所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的。
栈顶总是程序现在在执行的栈帧。
线程工作内存 & volatile
线程操作内存变量时,需要通过线程独有的工作内存拷贝主内存变量副本来进行。
一个共享变量被 volatile 修饰后,保证了不同线程对这个变量进行操作的可见性,即一个线程修改了某变量的值,这新值对其他线程来说是立即可见的。
JVM 垃圾回收性能分析
JVM 垃圾回收就是将 JVM 堆中的已经不再被使用的对象清理掉,释放宝贵的内存空间。
JVM 通过一种可达性分析算法进行垃圾对象的识别,具体过程是:
从线程栈帧中的局部变量,或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没有被标记的对象就是可回收垃圾对象了。
标记完成后,JVM 对垃圾对象占用的内存空间进行回收,主要有 3 种方法
清理:将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找到一段空闲内存分配给这个新对象。
压缩:从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其他的空间就是连续的空闲空间。
复制:将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。
JVM 分代垃圾回收
新生代:Eden 区、From 区、To 区
新生代中创建,开始分配到 Eden 区,比较小,方便回收,
老年代
Young GC
FullGC
JVM 垃圾回收器算法
串行回收器:垃圾回收线程只有 1 个,垃圾回收时阻塞其他任务 Stop the word。
并行回收器:多线程回收
并发回收器 CMS:回收阶段分细。初始化标记、并发标记、重标记、并发清理。只有初始化标记、重标记会 stop the word ,减少了 stop the word 时间。其他阶段垃圾回收线程与其他线程并行执行。大部分时间垃圾回收线程与任务线程并行执行,对用户线程、服务响应时间影响比较小。
G1 回收器:默认分成 2000 个区域,针对小块进行回收。
Java 启动参数
标准参数,所有的 JVM 实现都必须实现这些参数的功能,而且向后兼容
运行模式 -server, -client
类加载路径 -cp, -classpath
运行调试 -verbose
系统变量 -D
非标准参数,默认 jvm 实现这些参数,但不保证所有 JVM 实现都实现了,且不保证向后兼容
-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 新生代大小
-Xss 线程堆栈大小
非 Stable 参数,此类参数各 jvm 实现会有所不同,将来可能会随时取消
-XX:+UseConcMarkSweepGC 启用 CMS 垃圾回收算法
JVM 性能诊断工具
JPS 查看 host 上所有运行的 java 进程 pid
常用的几个参数
-l 输出 Java 应用程序的 main class 完整包
-m 输出传递给 main 方法的参数
-v 输出传递给 JVM 的参数
JSTAT 查看 Java 应用程序的资源和性能
JMAP 输出所有内存中对象的工具,将 VM 中的 heap 以二进制输出成文本
jstack 查看堆栈信息
JConsole 可视化工具
JVisualVM
Java 代码优化技巧及原理
合理并谨慎使用多线程
使用场景:I/O 阻塞、多 CPU 并发、多用户并发
资源争用与同步问题
java.util.concurrent
启动线程数=[任务执行时间/(任务执行时间 - IO 等待时间)] * CPU 内核数
最佳启动线程数和 CPU 内核数量成正比,和 IO 阻塞时间成反比。
如果任务都是 CPU 计算密集型任务,那么线程数最多不超过 CPU 核心数,因为启动再多的线程,CPU 也来不及调度;
如果是需要等待磁盘操作、网络响应,那么启动多线程有助于提高任务并发度,提高系统吞吐能力,改善系统性能。
竞态条件与临界区
问题在于多个线程访问了相同的资源
两个线程竞争同一资源时,如果任务线程对资源访问的顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。
在临界区中使用适当的同步就可以避免竞态条件。
Java 线程安全
允许被多个线程安全执行的代码称作线程安全代码。
安全的:
方法局部变量。局部变量存储在线程自己的栈中,永远不会被多个线程共享,所以是线程安全的
方法局部的对象引用。如果方法中的对象不会逃逸出该方法,那么也是线程安全的
不安全的:
对象成员变量。对象成员变量存储在堆上,如果两个线程同时更新同一个对象的同一个成员,那么代码就不是线程安全的。
ThreadLocal
即使线程共享又是线程安全的
Java 内存泄露
Java 内存泄露是由于开发人员的错误引起的。
如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存。
长生命周期对象
静态容器。如静态的 map 或者 list,put 后需要 remove、delete
缓存
合理使用线程池和对象池
复用线程或对象资源,避免在程序的生命周期中创建和删除大量对象
池管理算法(记录哪些对象是空闲的,哪些对象正在使用)
对象内容清除(ThreadLocal 的清空)
使用合适的 JDK 容器类(顺序表、链表、Hash)
LinkList 和 ArrayList 区别及适用场景
HashMap 的算法实现及应用场景
使用 concurrent 包,ConcurrentHashMap 和 HashMap 的线程安全特性有什么不同?
ConcurrentHashMap 采用分段式锁
缩短对象生命周期,加速垃圾回收
减少对象驻留内存的时间
在使用时创建对象,用完释放
创建对象的步骤(静态代码段 - 静态成员变量 - 父类构造函数 - 子类构造函数)
使用 I/O buffer 及 NIO
延迟写与提前读策略
异步无阻塞 IO 通信
使用组合代替继承
减少对象耦和
避免太深的继承层次带来的对象创建性能损失
合理的使用单例模式
无状态对象
线程安全
系统性能优化案例:秒杀系统
秒杀系统是一种典型的性能要求高的系统。
有限的商品,在极低的价格在限定时间销售。
高并发下存在的风险
网络带宽耗尽
服务器 Load 飙高,停止响应
数据库瘫痪
把事情理清楚、隔离出来
服务器和网络准备
style 服务器
图片服务器
静态服务器
交易服务器
带宽准备
图片出口带宽上线
CDN 准备
设计原则
静态化
并发控制,防止秒杀器
简化流程
前端优化
版权声明: 本文为 InfoQ 作者【Yangjing】的原创文章。
原文链接:【http://xie.infoq.cn/article/ead1c195e378fbe5426f640b3】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论