写点什么

第 9 周作业 2

用户头像
Yangjing
关注
发布于: 2020 年 11 月 22 日

数据库的基本原理

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 实现都必须实现这些参数的功能,而且向后兼容

  1. 运行模式 -server, -client

  2. 类加载路径 -cp, -classpath

  3. 运行调试 -verbose

  4. 系统变量 -D


  • 非标准参数,默认 jvm 实现这些参数,但不保证所有 JVM 实现都实现了,且不保证向后兼容

  1. -Xms 初始堆大小

  2. -Xmx 最大堆大小

  3. -Xmn 新生代大小

  4. -Xss 线程堆栈大小


  • 非 Stable 参数,此类参数各 jvm 实现会有所不同,将来可能会随时取消

  1. -XX:+UseConcMarkSweepGC 启用 CMS 垃圾回收算法


JVM 性能诊断工具

  • JPS 查看 host 上所有运行的 java 进程 pid

常用的几个参数

  1. -l 输出 Java 应用程序的 main class 完整包

  2. -m 输出传递给 main 方法的参数

  3. -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 线程安全

允许被多个线程安全执行的代码称作线程安全代码。


安全的:

  1. 方法局部变量。局部变量存储在线程自己的栈中,永远不会被多个线程共享,所以是线程安全的

  2. 方法局部的对象引用。如果方法中的对象不会逃逸出该方法,那么也是线程安全的

不安全的:

  1. 对象成员变量。对象成员变量存储在堆上,如果两个线程同时更新同一个对象的同一个成员,那么代码就不是线程安全的。


ThreadLocal

即使线程共享又是线程安全的


Java 内存泄露

Java 内存泄露是由于开发人员的错误引起的。

如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存。

  • 长生命周期对象

  • 静态容器。如静态的 map 或者 list,put 后需要 remove、delete

  • 缓存


合理使用线程池和对象池

  1. 复用线程或对象资源,避免在程序的生命周期中创建和删除大量对象

  2. 池管理算法(记录哪些对象是空闲的,哪些对象正在使用)

  3. 对象内容清除(ThreadLocal 的清空)


使用合适的 JDK 容器类(顺序表、链表、Hash)

  1. LinkList 和 ArrayList 区别及适用场景

  2. HashMap 的算法实现及应用场景

  3. 使用 concurrent 包,ConcurrentHashMap 和 HashMap 的线程安全特性有什么不同?

ConcurrentHashMap 采用分段式锁


缩短对象生命周期,加速垃圾回收

  1. 减少对象驻留内存的时间

  2. 在使用时创建对象,用完释放

  3. 创建对象的步骤(静态代码段 - 静态成员变量 - 父类构造函数 - 子类构造函数)


使用 I/O buffer 及 NIO

  1. 延迟写与提前读策略

  2. 异步无阻塞 IO 通信


使用组合代替继承

  1. 减少对象耦和

  2. 避免太深的继承层次带来的对象创建性能损失


合理的使用单例模式

  1. 无状态对象

  2. 线程安全


系统性能优化案例:秒杀系统

秒杀系统是一种典型的性能要求高的系统。

有限的商品,在极低的价格在限定时间销售。


高并发下存在的风险

  1. 网络带宽耗尽

  2. 服务器 Load 飙高,停止响应

  3. 数据库瘫痪


把事情理清楚、隔离出来


服务器和网络准备

  • style 服务器

  • 图片服务器

  • 静态服务器

  • 交易服务器

带宽准备

  • 图片出口带宽上线

  • CDN 准备


设计原则

  1. 静态化

  2. 并发控制,防止秒杀器

  3. 简化流程

  4. 前端优化


发布于: 2020 年 11 月 22 日阅读数: 22
用户头像

Yangjing

关注

还未添加个人签名 2017.11.09 加入

还未添加个人简介

评论

发布
暂无评论
第9周作业2