写点什么

架构师训练营第九周课程笔记及心得

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

第一课:

数据库基本原理



PrepareStatement预编译

1.直接提交一条完整的sql让数据库执行

2.通过占位符先准备好sql的基本框架,再通过set来往占位符中填入数据



数据库架构:

一条sql是如何在数据库里执行的:



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

  2. 语法分析器:数据库的抽象语法树



  1. 

  2. 语义分析与优化器:主要是将各种复杂嵌套的sql进行语义等价转化,得到有限集中关系代数计算结构,并利用索引灯信息进一步优化

  3. 执行计划:例



  1. 

  2. Key:索引类型,NULL无索引

  3. Rows:需要执行的行数

  4. Possible_keys:潜在可以利用的索引



为什么PrepareStatement更好:

  1. PrepareStatement会预先提交带占位符的sql刀数据库进行预处理,提前生成执行计划,当给定占位符参数,真正执行sql的时候,执行引擎可以直接执行,效率更好

  2. PrepareStatement可以防止sql注入攻击



数据库的数据是由B+树来存储的



B+树:是一个多层的检索树

聚簇索引:将行记录记录在叶子节点上,聚簇索引的数据库记录和索引存储在一起。

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





非聚簇索引:非聚簇索引再叶子结点记录的就不是数据行记录,而是聚簇索引,也就是主键,通过非聚簇索引可以对主键之外的行和列建立索引

回表:通过非聚簇索引找到主键索引,在通过主键索引找到行记录的过程



添加必要的索引优化sql查询性能:

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



合理使用索引:

不要盲目的添加索引,尤其在生产环境中;删除不用的索引,避免不必要的增删开销

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

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



使用更小的数据类型创建索引:

  1. int 4字节,bigint 8字节,timestamp 4字节,datetime 8字节



数据库事务:

事务特性ACID:

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

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

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

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



数据库事务日志:

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

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

TransID:产生操作的事务ID。

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

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

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

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



第二课:

JVM虚拟机架构原理

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



java字节码文件

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

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

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

字节码执行流程:



java字节码文件编译过程:



类加载器的双亲委托模型:



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



自定义类加载器:

为什么需要自定义类加载器:不同的类加载器,加载出来的类在方法区是不一样的,所以需要自定义的类加载器

自定义类加载器的作用:

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

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

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



堆&栈:

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

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

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



方法区&程序计数器:

方法区:主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候实际上是以线程为单位运行的,当jvm进入启动类的main方法的时候,就会为引用程序创建一个主线程,main方法里的代码就会被这个主线程执行。

程序计数器:每个线程都有自己的java栈,栈里存放着方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器中。



java(线程)栈:

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



线程工作内存&volatile

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

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



java运行环境:



第三课:

JVM垃圾回收性能分析:

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

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



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

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

  2. 压缩:从堆空间的头部开始,将存货的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间

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



JVM分代垃圾回收:



YangGC:对新生代进行回收

FullGC:对新生代和老年代进行回收



JVM垃圾回收器算法:



G1垃圾回收内存管理机制:

默认情况下分成2000个区域,对小块进行角色区分,然后针对具体的区域进行回收



JVM启动参数:

标准参数:所有的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,JSTAT,JMAP,JSTACK

集成工具:JConsole,JvisualVM



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



常用的几个参数:

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

  2. -q:仅显示pid,不显示其他任何相关信息

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

  4. -v:输出传递给JVM的参数。在诊断JVM相关问题的时候,这个参数可以查看JVM相关参数的设置



JSTAT:是jdk自带的一个轻量级小工具。主要对java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控



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

  1. Option:选项,一般常使用-gcutil查看gc情况

  2. vmid :VM的进程号,即当前运行的java进程号,需要进行监控的java进程号

  3. interval:间隔时间,单位为秒

  4. count:打印次数,缺省则打印无数次



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

使用方法:

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

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



jstack:可以查看jvm内的线程堆栈信息



JConsole/JVisualVM:可视化的jvm控制台,监控工具



第四课:

Java代码优化技巧及原理:

合理并谨慎使用多线程:

  1. 使用场景:I/O阻塞,多CPU并发,多用户并发

  2. 问题:资源争用与同步问题(java.util.concurrent)

  3. 合理线程数公式:启动线程数=[任务执行时间/(任务执行时间-IO等待时间)]*CPU内核数

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



竞态条件与临界区:

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

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

  3. 如何避免:在临界区中使用适当的同步就可以避免竞态条件

java线程安全:

允许被多个线程安全执行的代码称作线程安全的代码,实现方法:

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

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

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



ThreadLocal:即是共享的也是私有的



使用方法:

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

    public static ThreadLocal myThreadLocal = new ThreadLocal();

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

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

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

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

Java中源码:ThreadLocal实现线程安全的方法



java内存泄漏

java内存泄漏时由于开发人员的错误引起的

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

  1. 长生命周期对象

  2. 静态容器

  3. 缓存

如何避免内存泄漏:

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

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

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

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

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

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

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

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

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

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

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

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



使用I/O buffer以及NIO:

  1. 延迟写与提前读策略

  2. 异步无阻塞IO通信

优先使用组合代替继承:

  1. 减少对象耦合

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

合理的使用单例模式:

  1. 无状态对象:尽量使用无状态的单例,减少共享的资源

  2. 线程安全:单例全局唯一,注意线程安全问题



第五课:

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

什么是秒杀系统:商品数量少,只在指定时间定量放出,会在短时间内出现大量访问的场景



秒杀系统需要面对的问题:

  1. 网络带宽耗尽

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

  3. 数据库瘫痪等



应对方法:

  1. 服务器准备:做一套全新的系统用来应对秒杀

  2. 带宽准备:本地带宽配合CDN减少带宽 小的压力

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

Airs

关注

Emmmmmmm 2018.02.28 加入

Emmmmmmm

评论

发布
暂无评论
架构师训练营第九周课程笔记及心得