写点什么

第九周 - 总结

用户头像
jizhi7
关注
发布于: 2020 年 12 月 21 日

一、数据库:

1、数据库架构:

连接器->语法分析器->语义分析与优化器->执行引擎

1. 连接器:

数据库会为每一个连接请求分配一块专用的内存空间用于会话上下文的管理。所以建立连接对数据库而言相对比较重,需要花费一定时间(通常几百毫秒),因此现在都是在程序启动的时候,就建立了一些连接放在数据库连接池里面。

2. 语法分析器:

进行语法解析,构建抽象语法树。在构建过程就能识别出来语法错误,因为语法有问题,抽象语法树就构建不成功。

3. 语义分析与优化器:

将各种复杂嵌套 SQL 进行语义等价转换,并利用索引进一步优化。得到一个执行计划。

4. 执行器:执行执行计划。

2、数据存储:

1. 使用 B+树,叶子节点存放真正的一条完整记录数据。

2. 索引:

1) 聚簇索引:该索引树的叶子节点存放着完整的记录数据。主键就是一种聚簇索引。

2) 非聚簇索引:叶子节点存放的不是行记录数据,而是聚簇索引。通过非聚簇索引找到主键索引,在通过主键索引树找到行记录成为回表。

3、数据库优化手段:

1. 添加必要的索引,优化查询性能。

注意:

1)不要盲目添加索引,尤其是在生产环境中,因为添加索引时的 alter 操作耗时比较长(分钟级别),添加时会阻塞所有的操作。

2)删除不必要的索引,避免不必要的开销。索引多了,索引树也就会多,插入或修改数据时,都要修改对应的所有 B+索引树,就会比较久。也比较耗空间。

3)使用更小的数据类型创建索引。

4、数据库的事务:

1. 事务特性 ACID:

1) 原子性(Atomicity):要么全部完成,要么全部取消。

2) 隔离性(Isolation):事务与事务之间是相互隔离的。

3) 持久性(Durability):一旦事务提交,不管发生什么,都不会变更。

4) 一致性(Consistency):只有合法的数据,才能写入数据库。

2. 数据库事务日志:

进行事务操作的时候,事务日志会记录更新前的数据记录,然后再更新数据库中的记录,如果全部更新成功,那么事务正常结束,如果更新失败,就会根据日志记录回滚数据。

LSN:一个按时间顺序分配的唯一事务记录日志序列号。

TransID:产生操作的事务 ID。

PageID:被修改的数据在磁盘上的位置。

PrevLSN:同一个事务产生的上一条日志记录指针。

Undo:取消本次操作的方法,按此回滚。

Redo:重复本次操作的方法。

5、问题:

1. 为什么 PrepareStatement 会比 Statement 更好?

1) 数据库会对这条带占位符的 SQL 语句进行预处理(要先提交),执行的时候直接执行,性能会更好。(有些数据库不一定会实现写这些预处理)

2) 可以防止 SQL 注入,在执行的时候,已经根据带占位符的 SQL 把抽象语法树给生成了,执行的时候会直接读取参数执行,就不会把参数当成 SQL 关键字了。

二、JVM 虚拟机:

1、JVM 组成架构:

类文件->类加载器->运行期数据区(方法区、堆、Java 栈、程序计数器)->执行引擎

2、启动过程:

Java 启动一个带 Main 函数的 java 类的时候->会启动一个 JVM 进程->通过类加载器加载这个 java 类到 JVM 中,放在运行期数据区中的方法区->然后 JVM 会为这个带有 Main 函数的第一个类创建一个主线程执行,并分配 Java 栈,程序计数器->开始根据程序计数器的指向,从方法区中拿到相应代码->交给执行引擎,执行用户代码,执行引擎会将 java 字节码指令编译成为本地的操作系统的指令,执行。

3、Java 字节码文件:

1. Java 所有的指令有 200 多个,一个字节 8 位,可以表示 256 中不同的指令,一个这样的子节称为字节码。JVM 可以将字节码编译执行,如果是热点代码,会通过 JIT 动态的编译为机器码,提高执行效率。

2. 字节码文件前 4 个子节都是 cafe babe。

3. 字节码执行流程:

方法调用->已经编译过的->执行编译后的机器码

 ->没有编译过->方法调用计数器+1 ->技术是否超过阈值->超过,提交编译请求->编译器编译->放到 Code Cache 中              

->超过没 超过,都会进行,解释方法执行

4. 字节码文件编译过程:

Java 源文件->词法解析->语法解析->语义分析->生成字节码->字节码文件

5. 类加载器:

Bootstrap ClassLoader:加载 jre/lib/rt.jar

Extension ClassLoader:加载 jre/lib/ext/*.jar

Application ClassLoader:加载 classpath 底下的 jar 包文件

自定义的 ClassLoader(继承至 ApplicationClassLoader)

6. 自定义类加载器:

隔离加载类:同一个 JVM 中不同组件加载一个类的不同版本。(Tomcat 中就是为每 一个 war 包,每一个应用上下文,都分派一个 ClassLoader)

扩展加载:从网络、数据库等处加载字节码。

字节码加密:加载自定义的加密字节码,在 ClassLoader 中解密。

4、运行期数据区:

1.方法区:存放从磁盘加载进来的类字节码,静态变量,静态方法,常量池。

2.堆:每个 JVM 实例唯一对应一个堆。应用程序所创建的所有类实实例或数组都存放在这,并由应用程序的所有线程共享。

3. Java 栈:每个线程都分配一个栈。程序创建的对象引用存放在栈中,指向堆中的内存。每调用一个方法,压入一个栈帧。

4.程序计数器:当前线程执行到哪一行字节码指令。

5、Volatile 关键字:

当多个线程访问同一个内存中的数据时,一个 CPU 线程修改了数据,实际上只修改了 CPU Cache 里的数据,另一个 CPU 线程要读取这个数据时会从主内存中读取,这就会导致读到的数据不是修改后的数据,就会导致数据不一致。

加了 volatile 关键字之后,修改了该关键字修饰的变量了之后,会立即将修改的数据从 CPU Cache 刷回到主内存中,而其他线程要读取的变量值有 volatile 关键字修饰的变量后,一定要从主内存中读取该变量值,而不是用当前 CPU Cache 里面的变量值。

三、JVM 垃圾回收:

JVM 垃圾回收:是将 JVM 堆(包括方法区)中已经不再被使用的对象清理掉,释放出内存资源。

1、标记:

JVM 通过可达性分析算法进行垃圾对象的识别,具体过程:从线程栈帧中的局部变量,或者方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些对象是否引用了其他对象,继续标记。最后没有被标记的对象就是垃圾对象了。

2、清理:

1. 清理:将垃圾对象占据的空间清理掉,标记为空闲。碎片化严重。

2. 压缩:将存活的对象拷贝到堆头的一段连续空间中,其余的就是空闲空间了。

3. 复制:将堆空间分为两部分,只在一个部分创建对象,当这个部分用完后,将存活 的对象拷贝到另一个部分中。

3、分代垃圾回收:

1. 新生代:

1) Eden 区:

2) From 区:

3) To 区:

2. 老年代

4、JVM 垃圾回收器算法:

1. 串行回收器:进垃圾回收的时候,停止所有用户线程 STW,启动一个垃圾回收线程(单核心 CPU),先标记,再回收垃圾。

2. 并行回收器:垃圾回收的时候,也是停止所有的用户线程 STW,启动多个垃圾回收线程(多核心 CPU),进行垃圾回收。

3. 并发回收器 CMS:初始标记 STW,时间很短,并发标记,和用户线程一起执行,重新标记 STW,多线程执行,并发清理,和用户线程并发执行。

4. G1 回收器:

5、Java 启动参数:

1. 标准参数:所有的 JVM 都实现了的,且向后兼容。

1) 运行模式:-server,-client

2) 类加载路径:-cp,-classpath

3) 运行调试:-verbose

4) 系统变量:-D

2. 非标准参数:默认 JVM 都实现,但不保证,也不保证向后兼容。

1) -Xms 初始堆大小

2) -Xmx 最大堆大小

3) -Xmn 新生代大小

4) -Xss 线程栈大小

3. 不稳定的参数:各个 JVM 实现会有所不同,将要有可能会取消。

1) -XX:-UseConcMarkSweepGC 启用 CMS 垃圾回收

6、JVM 性能诊断工具:

基本工具:

1. JPS:用来查看主机上运行的 java 进程的进程 ID

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

2) -q 仅显示 pid。

3) -m 输出传递给 main 方法的参数。

4) -v 输出传递给 JVM 的参数。

2. JSTAT:堆 Java 应用程序的资源和性能进行实时监控,包括 heap 和垃圾回收状况

3. JMAP:输出内存堆中的对象。

使用:jmap -histo pid>a.olg 将其保存起来。

jmap -dump:format=b,file=f1 PID 可以将该 PID 进程的内存输出到 f1 文件中。

4. JSTACK:查看堆信息。

使用 jstack -l pid。

集成工具:

1. JConsole:

2. JVisualVM:

四、JAVA 代码优化:

1、合理并谨慎使用多线程:

启动线程数= [任务执行时间/(任务执行时间- IO 等待时间)] * CPU 核心数。

最佳启动线程数和 CPU 内核数量成正比,和 IO 阻塞时间成反比。如果任务都是 CPU 计算型任务,那么线程数最多不超过 CPU 内核数,因为启动再多的线程,CPU 也来不及调度。相反的,如果任务是需要阻塞等待的操作,那么启动多线程有助于提高任务的并发度,提高系统吞吐能力,改善系统性能。

2、资源争用与同步的问题:

1. Java 线程安全:

1) 方法局部变量:局部变量存储是在自己的线程栈中的基础数据类型,不与其他的线程共享。

2) 方法局部对象引用:在方法中创建的局部对象引用,并且没有传递到其它方法中。

2. 线程不安全的:

如果两个线程同时更新一个对象的同一个成员,那么这个代码就不是线程安全的。

3. ThreadLocal:

3、Java 内存泄漏:

1. 逻辑上的内存泄漏:引用着不再使用的对象。

长生命周期的对象,如 servlet。

静态容器,static 的 map 或 list。

缓存

4、合理使用线程池和对象池:

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

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

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

5、使用合适的 JDK 容器:

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

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

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

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

1. 减少对象驻留内存的时间。在一个方法中,一旦确定某个对象已经不用了,就可以赋个 null,让垃圾回收器回收它。

2. 在使用时创建对象,用完释放(赋 null,但会导致代码的复杂性增加,)。

3. 创建对象的步骤:

静态代码块->静态成员变量->父类构造函数->子类构造函数

7、使用 I/O buffer 及 NIO:

1. 延迟写与提前读策略

2. 异步无阻塞 IO 通信

8、优先使用 z 组合代替继承:

1. 减少对象耦合。

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

9、合理使用单例模式:

1. 无状态对象。(没有成员变量,或者成员变量也是无状态的)

2. 线程安全。

五、秒杀系统:

1、背景:

1. 突然有一个秒杀需求,并发访问量提高了 100 倍左右。

2. 时间紧,大概只有 5 天左右。

3. 网络带宽低,加载一张大图片导致系统崩溃。

2、解决方案:

1. 开发一个新的秒杀系统(新开发一个系统比修改老系统更简单),也与其它的系统隔离。

2. 让一些空闲的服务器分离出来,部署秒杀系统(采购新服务器来不及)。

3. 升级带宽,CDN 准备,借用 CDN。

4. 选购商品,点击秒杀按钮,下单在新的系统里,支付等后续操作在旧的系统里。

3、秒杀系统设计原则:

1. 静态化:采用 JS 自动更新技术将动态页面转化为静态页面,利于反向代理服务器和 CDN 服务器的缓存。

1) 页面的静态化:将秒杀商品的 list 和 detail 做成静态的 HTML 页面,可以设计一个将动态页面转化为静态页面的工具系统。

2) 秒杀按钮的设计:将按钮的跳转 url 的商品的 id 放在一个 js 当中,当秒杀还没开始的时候,该 js 是空的,开始了,后台将该 js 上传,用户刷新就能获取到该 js,就能点击秒杀按钮了。该 js 不缓存。

2. 设置阀门:只放最前面的一部分人进入秒杀系统。

点击秒杀的时候,基于 TT 的计数器,进行计数,大于 1000 的就不能给进入后面页面了。后面也可以继续使用计数器限制流量。

3. 简化流程:砍掉不必要的分支,如下单页面所有的数据库查询。下单成功,支付可以在 1 天内完成。

4. 采用 YSLOW 原则提升页面响应速度。

5. 其它软件优化,appche、tomcat


用户头像

jizhi7

关注

还未添加个人签名 2018.09.08 加入

还未添加个人简介

评论

发布
暂无评论
第九周-总结