写点什么

极客时间架构师训练营 1 期 - 第 9 周总结

用户头像
Kaven
关注
发布于: 2020 年 11 月 21 日
数据库架构原理

一、 PrepareStatement预编译



PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句,而不必先编译。

当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。

选择PreparedStatement对象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。

如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出它预编译的优越性。



statement.executeUpdate("UPDATE Users SET stateus= 2 WHERE userID=233");

PreparedStatementupdateUser= con.prepareStatement("UPDATE Users SET stateus= ? WHERE userID= 233");

updateUser.setInt(1, 2);
updateUser.executeUpdate();



二、 数据库原理





  • 连接器

数据库连接器会为每个连接请求分配一块专用的内存空间用于会话上下文管理。建立连 接对数据库而言相对比较重,需要花费一定的时间,因此应用程序启动的时候,通常会 初始化建立一些数据库连接放在连接池里,这样当处理外部请求执行SQL 操作的时候, 就不需要花费时间建立连接了。

  • 语法分析器

  • 语义分析与优化器

语义分析与优化器就是要将各种复杂嵌套的SQL 进行语义等价转化,得到有限几种关系代数计算结构,并利用索引等信息进一步进行优化。



  • 执行计划



三、 为什么PrepareStatement更好

  • PrepareStatement会预先提交带占位符的SQL 到数据库进行预处理以及优化,提前生成执行计划,当给定占位符参数,真正执行SQL 的时候,执行引擎可以直接执行,效率更好一点。

  • PrepareStatement可以防止SQL 注入攻击。

select * from users where username = 'Frank';

输入

Frank';droptable users;-

容易造成sql注入攻击,生成如下sql语句

select * from users where username = 'Frank';droptable users;--’;

建议使用如下sql语句:

select * from users where username = ?;



四、 数据存储结构



  • B+树

  • 聚簇索引

聚簇索引:聚簇索引的数据库记录和索引存储在一起。

MySQL 数据库的主键就是聚簇索引,主键ID 和所在的记录行存储在一个B+树中。

  • 非聚簇索引

非聚簇索引在叶子节点记录的就不是数据行记录,而是聚簇索引,也就是主键。 通过非聚簇索引找到主键索引,再通过主键索引找到行记录的过程也被称作回表。



  • 添加必要的索引优化SQL查询性能

在几百万行的数据库中查找一个条记录,如果没有索引,就需要全表扫描,检索所有的 行记录,才能找到需要的记录。



  • 合理使用索引

不要盲目添加索引,尤其在生产环境



  1. 添加索引的alter操作会消耗较长的时间(分钟级);

  2. Alter操作期间,所有数据库的增删改操作全部阻塞,对应用而言,因为连接不能释放,事实 上,查询也被阻塞。



删除不用的索引,避免不必要的增删开销

使用更小的数据类型创建索引,压缩存储空间,减少搜索路径。

int 4字节
bigint 8字节
Timestamp 4字节
Datetime 8字节



五、 数据库事务



  • 事务特性ACID

  1. 原子性(Atomicity): 事务要么全部完成,要么全部取消。如果事务崩溃,状态回到事 务之前(事务回滚)。

  2. 隔离性(Isolation): 如果2个事务T1 和T2 同时运行,事务T1 和T2 终的结果是相同 的,不管T1和T2谁先结束,隔离性主要依靠锁实现。

  3. 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。

  4. 一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。

  • 数据库事务日志

进行事务操作时,事务日志文件会记录更新前的数据记录,然后再更新数据库中的记录, 如果全部记录都更新成功,那么事务正常结束,如果过程中某条记录更新失败,那么整 个事务全部回滚,已经更新的记录根据事务日志中记录的数据进行恢复,这样全部数据 都恢复到事务提交前的状态,仍然保持数据一致性。





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

TransID:产生操作的事务ID。

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

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

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

REDO:重复本次操作的方法






JVM虚拟机原理

一、 JVM组成架构

Java 是一种跨平台的语言,JVM 屏蔽了底 层系统的不同,为Java 字节码文件构造了 一个统一的运行环境





二、 Java字节码文件

Java如何实现在不同操作系统、不同硬件平台上,都可以不用修改代码就能顺畅地执行?

计算机领域的任何问题都可以通过增加个中间层(虚拟层)来解决。

Java 所有的指令有200 个左右,一个字节( 8 位)可以存储256 种不同的指令信息,一 个这样的字节称为字节码( Bytecode )。在代码的执行过程中,JVM将字节码解释执行, 屏蔽对底层操作系统的依赖,JVM也可以将字节码编译执行,如果是热点代码,会通过 JIT动态地编译为机器码,提高执行效率。





三、 字节码执行流程





四、 Java字节码文件编译过程





五、 类加载器的双亲委托模型

低层次的当前类加载器,不 能覆盖更高层次类加载器已 经加载的类。如果低层次的 类加载器想加载一个未知类, 需要上级类加载器确认,只 有当上级类加载器没有加载 过这个类,也允许加载的时 候,才让当前类加载器加载 这个未知类。





通常类加载机制有三个基本特征:



  • 双亲委派模型。但不是所有类加载都遵守这个模型,有的时候,启动类加载器所加载的类型,是可能要加载用户代码的,比如 JDK 内部的 ServiceProvider/ServiceLoader机制,用户可以在标准 API 框架上,提供自己的实现,JDK 也需要提供些默认的参考实现。 例如,Java 中 JNDI、JDBC、文件系统、Cipher 等很多方面,都是利用的这种机制,这种情况就不会用双亲委派模型去加载,而是利用所谓的上下文加载器。

  • 可见性,子类加载器可以访问父加载器加载的类型,但是反过来是不允许的,不然,因为缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑。

  • 单一性,由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。



六、 自定义类加载器

隔离加载类:同一个JVM中不同组件加载同一个类的不同版本

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

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



七、 堆&栈



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

  • 堆栈:JVM为每个新创建的线程都分配一个堆栈。也就是说,对于一个Java程序来说, 它的运行就是通过对堆栈的操作来完成的。



Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配, 也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对 象,而在堆栈中分配的内存只是一个指向这个堆对象的引用而已。



八、 方法区&程序计数器



方法区主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放 在堆里。程序运行的时候,实际上是以线程为单位运行的,当JVM 进入启动类的main 方法的时候,就会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执 行,每个线程有自己的Java 栈,栈里存放着方法运行期的局部变量。而当前线程执行 到哪一行字节码指令,这个信息则被存放在程序计数寄存器。



九、 Java(线程)栈

所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中, 线程的栈彼此隔离,所以这些变量一定是线程安全的。



十、线程工作内存&volatile



Java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内 存拷贝主内存变量副本来进行。

一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,保证了不同 线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他 线程来说是立即可见的



十一、 Java运行环境





十二、 JVM 垃圾回收

JVM垃圾回收就是将JVM堆中的已经不再被使用的对象清理掉,释放宝贵的内存资源。

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

进行完标记以后,JVM 就会对垃圾对象占用的内存进行回收,回收主要有三种方法



  • 清理:将垃圾对象占据的内存清理掉,其实JVM 并不会真的将这些垃圾内存进行清理,而是将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象.

  • 压缩(整理):从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间。

  • 复制:将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。





十三、 JVM分代垃圾回收





十四、JVM垃圾回收器算法





十五、 G1垃圾回收内存管理机制





十六、 具体垃圾回收器



  • Serial GC,它是最古老的垃圾收集器。

串行运行;复制算法;响应速度优先;适用于单CPU环境下的client模式。

"Serial"体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的 GC 实现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。从年代的角度,通常将其老年代实现单独称作 Serial Old,它采用了标记 - 整理(Mark-Compact)算法,区别于新生代的复制算法。

Serial GC 的对应 JVM 参数是:

-XX:+UseSerialGC
  • ParNew GC

并行运行;作用于新生代;复制算法;响应速度优先;多CPU环境Server模式下与CMS配合使用。

新生代 GC 实现,它实际是 Serial GC 的多线程版本,最常见的应用场景是配合老年代的 CMS GC 工作,下面是对应参数

-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
  • Parallel Scavenge收集器

并行运行;作用于新生代;复制算法;吞吐量优先;适用于后台运算而不需要太多交互的场景。

  • Serial Old收集器

串行运行;作用于老年代;标记-整理算法;响应速度优先;单CPU环境下的Client模式。

  • CMS(Concurrent Mark Sweep) GC

并发运行;作用于老年代;标记-清除算法;响应速度优先;适用于互联网或B/S业务。

基于标记 - 清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于 Web 等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用 CMS GC。但是,CMS 采用的标记 - 清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生 full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent),CMS 会占用更多 CPU 资源,并和用户线程争抢。

  • Parallel GC/Parallel Old收集器

并行运行;作用于老年代;标记-整理算法;吞吐量优先;适用于后台运算而不需要太多交互的场景。

在早期 JDK 8 等版本中,它是 server 模式 JVM 的默认 GC 选择,也被称作是吞吐量优先的 GC。它的算法和 Serial GC 比较相似,尽管实现要复杂的多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。开启选项是:

-XX:+UseParallelGC

另外,Parallel GC 引入了开发者友好的配置项,我们可以直接设置暂停时间或吞吐量等目标,JVM 会自动进行适应性调整,例如下面参数:



-XX:MaxGCPauseMillis=value
-XX:GCTimeRatio=N // GC时间和用户时间比例 = 1 / (N+1)
  • G1

并发运行;可作用于新生代或老年代;标记-整理算法+复制算法;响应速度优先;面向服务端应用。

G1 GC 这是一种兼顾吞吐量和停顿时间的 GC 实现,是 Oracle JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标,相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是比最差情况要好很多。

G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。

G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 已经在 JDK 9 中被标记为废弃(deprecated),所以 G1 GC 值得你深入掌握。

在 G1 实现中,年代是个逻辑概念,具体体现在,一部分 region 是作为 Eden,一部分作为 Survivor,除了意料之中的 Old region,G1 会将超过 region 50% 大小的对象(在应用中,通常是 byte 或 char 数组)归类为 Humongous 对象,并放置在相应的 region 中。逻辑上,Humongous region 算是老年代的一部分,因为复制这样的大对象是很昂贵的操作,并不适合新生代 GC 的复制算法。



从 GC 算法的角度,G1 选择的是复合算法,可以简化理解为:

  1. 在新生代,G1 采用的仍然是并行的复制算法,所以同样会发生 Stop-The-World 的暂停。

  2. 在老年代,大部分情况下都是并发标记,而整理(Compact)则是和新生代 GC 时捎带进行,并且不是整体性的整理,而是增量进行的。



十七、 Java启动参数



  • 标准参数,所有的JVM实现都必须实现这些参数的功能,而且向后兼容

运行模式-server,-client
类加载路径-cp,-classpath
运行调试–verbose
系统变量–D
  • 非标准参数,默认jvm实现这些参数,但不保证所有JVM实现都实现,且不保证向后兼容

-Xms初始堆大小
-Xmx大堆大小
-Xmn新生代大小
-Xss 线程堆栈大小
  • 非Stable参数,此类参数各个jvm实现会有所不同,将来可能会随时取消。

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

十八、 JVM性能诊断工具

基本工具:JPS ,JSTAT,JMAP,JSTACK

集成工具: JConsole,JVisualVM,JMC



  • JPS

JPS用来查看host上运行的所有Java进程的pid(jvmid),一般情况下使用这个工具 的目的只是为了找出运行的JVM进程ID,即lvmid,然后可以进一步使用其它的工具来 监控和分析JVM

常用的几个参数:

-l 输出Java应用程序的mainclass的完整包
-q 仅显示pid,不显示其它任何相关信息
-m 输出传递给main方法的参数
-v 输出传递给JVM的参数。在诊断JVM相关问题的时候,这个参数可以查看JVM相关参 数的设置
  • JSTAT

JSTAT(“Java Virtual Machine statistics monitoring tool” )是JDK自带的一个轻量级小工具。主要对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。

语法结构如下:jstat [Options] vmid [interval] [count]

Options --选项,我们一般使用-gcutil查看gc情况
vmid --VM的进程号,即当前运行的Java进程号
interval--间隔时间,单位为毫秒
count --打印次数,如果缺省则打印无数次






S0 --Heap上的Survivor space0 区已使用空间的百分比
S1 --Heap上的Survivor space 1 区已使用空间的百分比
E --Heap上的Eden space 区已使用空间的百分比
O --Heap上的Old space 区已使用空间的百分比
YGC --从应用程序启动到采样时发生Young GC 的次数
YGCT--从应用程序启动到采样时Young GC 所用的时间(单位秒)
FGC --从应用程序启动到采样时发生Full GC 的次数
FGCT--从应用程序启动到采样时Full GC 所用的时间(单位秒)
GCT--从应用程序启动到采样时用于垃圾回收的总时间(单位秒)



  • JMAP

JMAP是一个可以输出所有内存中对象的工具,甚至可以将VM中的heap,以二进制输 出成文本。

使用方法

jmap-histopid>a.log 可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以 对比出GC回收了哪些对象.

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



  • jstack



jstack可以查看JVM内的线程堆栈信息。




Java代码优化



一、 合理并谨慎使用多线程

使用场景(I/O阻塞,多CPU并发

资源争用与同步问题

java.util.concurrent

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

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



二、竞态条件与临界区

在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞 态条件发生的代码区称作临界区。

在临界区中使用适当的同步就可以避免竞态条件。



三、 Java线程安全

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



  • 方法局部变量

局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以, 基础类型的局部变量是线程安全的。

  • 方法局部的对象引用

如果在某个方法中创建的对象不会逃逸出该方法,那么它就是线程安全的。

  • 对象成员变量

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



四、 ThreadLocal





创建一个ThreadLocal变量(X类静态成员变量):

public static ThreadLocal myThreadLocal=newThreadLocal();

存储此对象的值(A类a方法):

X.myThreadLocal.set("A thread local value");

读取一个ThreadLocal对象的值(B类b方法):

String threadLocalValue= (String)X.myThreadLocal.get();



五、 Java 内存泄漏问题

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

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

长生命周期对象
静态容器
缓存


合理使用线程池和对象池



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

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

  • 对象内容清除(ThreadLocal的清空)



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



  • LinkList和ArrayList的区别及适用场景

  • HashMap的算法实现及应用场景

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



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



  • 减少对象驻留内存的时间

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

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



使用I/O buffer及NIO

  • 延迟写与提前读策略

  • 异步无阻塞IO通信



优先使用组合代替继承

  • 减少对象耦合

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



合理使用单例模式

  • 无状态对象

  • 线程安全



计算机的任何问题都可以通过虚拟层(或者中间层)解决

* 面向接口编程
* 7层网络协议
* JVM
* 编程框架
* 一致性hash算法的虚拟化实现







秒杀系统

一、 问题与挑战



  • 对现有网站业务造成冲击

秒杀系统独立部署

  • 高并发对网站性能的影响 (应用、数据库负载)

秒杀商品页面静态化

优化秒杀流程,简化功能,将部分功能流程提前或者流程延后

  • 突然增加得网络以及服务器带宽、

统计网络带宽,租界秒杀活动网络带宽

  • 安全性-直接下单

动态生成随机下单页面URL



二、 秒杀系统设计原则

  • 静态化

采用JS自动更新技术将动态页面转化为静态页面

  • 并发控制,防秒杀器

设置阀门,只放前面的一部分人进入秒杀系统

阀门:基于TT的计数器

  • 简化流程

  1. 砍掉不重要的分支流程,如下单页面的所有数据库查询

  2. 以下单成功作为秒杀成功标志。支付流程只要在1天内完成即可。

  • 前端优化

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



三、 Web Server 调优–Apache调优



  • KeepAlive相关参数调优

  • 其他参数调优

HostnameLookups设为off,对allowfromdomain等后 的域名不进行正向和反向的dns解析。

  • 关闭cookies-log日志

  • 打开Linux sendfile()

  • 关闭无用的module

mod_Gzip

(秒杀页面,非图片HTML文本所占流量比重可忽略不计, zip意义不大)

mod_Beacon

mod_hummock(等待反应过来,秒杀已经over了)



四、 Web Server 调优–JBoss调优

Mod-jkworker 调优

JBossAJP Connector

Tomcat APR 设定



五、 秒杀静态页面优化



  • 图片合并

8张图片合并成1张,CSS偏移展示。

减少HTTP请求数,减少请求等待数。

减少发送Cookies的量。



  • HTML内容压缩

  • 图片压缩:图片Bytes < 长X宽/2250

  • HTML Header Cache-Control设置

  • CSS,JS 精简

CSS,JS 精简到极致,部分直接写在页面中,减少HTTP请求次数。



六、 下单页面优化



  • 数据库操作:全部砍掉

原下单页面要访问8次数据库,全部砍掉。

  • 秒杀流程精简

砍掉填写或选择收货地址,放在秒杀成功后填写。

砍掉调用是否开通支付接口,秒杀首页文案提示必须开通。

  • 采用内存缓存

秒杀Offer数据,支付相关信息,缓存。



七、 交易系统性能优化



  • 关闭KeepAlive(分析交易系统accesslog,用户在短时间内连续点击概率很低)

  • JVM优化

  • 优化CMS 垃圾回收器的参数

  • 消灭Top10 Bottlenecks (瓶颈)

Velocity参数调优

采用DBCP1.4 替换C3P0

Offer 产品参数的XML解析



八、 二跳页面的优化

XXXX.com其他页面

  • 前端优化:Yslow规则调优

减少HTTP请求,合并JS,CSS,图片,充分利用浏览器缓存。

  • 图片压缩,公式:

  • 避免发送Cookies



交易系统优化

  • 普通订单管理列表和XXXX秒批订单管理列表分离

  • 禁止用模糊查询功能



九、 应急预案

任何项目都要做好具体得应急预案

用户头像

Kaven

关注

还未添加个人签名 2019.04.13 加入

还未添加个人简介

评论

发布
暂无评论
极客时间架构师训练营 1 期 - 第 9 周总结