架构师训练营第九周课程笔记及心得
第一课:
数据库基本原理
PrepareStatement预编译
1.直接提交一条完整的sql让数据库执行
2.通过占位符先准备好sql的基本框架,再通过set来往占位符中填入数据
数据库架构:
一条sql是如何在数据库里执行的:
连接器:数据库连接器会为每个链接请求分配一块专用的内存空间,用于会话上下文管理。建立连接堆数据库而言相对比较重要,需要花费一定时间,因此应用程序启动的时候,通常会初始化建立一些数据库连接放在连接池里,这样当处理外部请求执行sql操作的时候,就不需要花费时间建立连接了。
语法分析器:数据库的抽象语法树
语义分析与优化器:主要是将各种复杂嵌套的sql进行语义等价转化,得到有限集中关系代数计算结构,并利用索引灯信息进一步优化
执行计划:例
Key:索引类型,NULL无索引
Rows:需要执行的行数
Possible_keys:潜在可以利用的索引
为什么PrepareStatement更好:
PrepareStatement会预先提交带占位符的sql刀数据库进行预处理,提前生成执行计划,当给定占位符参数,真正执行sql的时候,执行引擎可以直接执行,效率更好
PrepareStatement可以防止sql注入攻击
数据库的数据是由B+树来存储的
B+树:是一个多层的检索树
聚簇索引:将行记录记录在叶子节点上,聚簇索引的数据库记录和索引存储在一起。
Mysql数据库的主键就是聚簇索引,主键ID和所在的记录行存储在一个B+树中。
非聚簇索引:非聚簇索引再叶子结点记录的就不是数据行记录,而是聚簇索引,也就是主键,通过非聚簇索引可以对主键之外的行和列建立索引
回表:通过非聚簇索引找到主键索引,在通过主键索引找到行记录的过程
添加必要的索引优化sql查询性能:
在几百万行的数据库中查找一条记录,如果没有索引,就需要全表扫描,检索所有的行记录,才能找到需要的记录
合理使用索引:
不要盲目的添加索引,尤其在生产环境中;删除不用的索引,避免不必要的增删开销
添加索引的alter操作会消耗较长的时间(分钟级)
alter操作期间,所有数据库的增删改操作全部阻塞,对应用而言,因为连接不能释放,事实上,查询也被阻塞
使用更小的数据类型创建索引:
int 4字节,bigint 8字节,timestamp 4字节,datetime 8字节
数据库事务:
事务特性ACID:
原子性(Atomicity):事务要么全部完成,要么全部取消。如果事务崩溃,状态回到事务之前(事务回滚)
隔离性(Isolation):如果两个事务T1和T2同时运行,事务T1和T2最终的结果是相同的,不管T1和T2谁先结束,隔离性主要依靠锁实现
持久性(Durability):一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中
一致性(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字节码文件编译过程:
类加载器的双亲委托模型:
低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类。如果低层次的类加载器想加载一个未知类,需要上级类加载器确认,只有当上级类加载器没有加载过这个类,也允许加载的时候,才让当前类加载器加载这个未知类。
自定义类加载器:
为什么需要自定义类加载器:不同的类加载器,加载出来的类在方法区是不一样的,所以需要自定义的类加载器
自定义类加载器的作用:
隔离加载类:同一个JVM中不同组件加载同一个类的不同版本
扩展加载源:从网络、数据库等处加载字节码。
字节码加密:加载自定义的加密字节码,在ClassLoader中解密。
堆&栈:
java中所有对象的存储空间都在堆中分配的,但是这个对象的引用确实在对战中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在栈中分配的内存只是一个指向这个堆对象的引用而已。
堆:每个JVM实例唯一对应一个堆。应用程序在运行过程中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享。
栈:JVM为每个新创建的线程都分配一个堆栈。也就是说,对于一个java程序来说,它的运行就是通过对堆栈的操作来完成的。
方法区&程序计数器:
方法区:主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候实际上是以线程为单位运行的,当jvm进入启动类的main方法的时候,就会为引用程序创建一个主线程,main方法里的代码就会被这个主线程执行。
程序计数器:每个线程都有自己的java栈,栈里存放着方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器中。
java(线程)栈:
所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的。
线程工作内存&volatile
java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量来进行。
一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
java运行环境:
第三课:
JVM垃圾回收性能分析:
JVM垃圾回收就是将JVM堆中已经不再被使用的对象清理掉,释放宝贵的内存资源。
JVM通过一种可达性分析算法进行辣鸡对象的识别,具体过程是:从线程栈帧中的局部变量,或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没被标记的对象就是可回收的垃圾对象了。
进行完标记以后,JVM就回对垃圾对象占用的内存进行回收,回收主要有三种方法:
清理:将垃圾对象占据的内存清理掉,其实JVM并不会针真的将这些垃圾内存进行清理,而是将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象。
压缩:从堆空间的头部开始,将存货的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间
复制:将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中
JVM分代垃圾回收:
YangGC:对新生代进行回收
FullGC:对新生代和老年代进行回收
JVM垃圾回收器算法:
G1垃圾回收内存管理机制:
默认情况下分成2000个区域,对小块进行角色区分,然后针对具体的区域进行回收
JVM启动参数:
标准参数:所有的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
JPS:用来查看host商运行的所有java进程的pid(jvmid),一般情况下试用这个工具的目的只是为了找出运行的JVM进程ID,即jvmid,然后可以进一步试用其他的工具来监控和分析JVM
常用的几个参数:
-l:输出java应用程序的main class的完整包
-q:仅显示pid,不显示其他任何相关信息
-m:输出传递给main方法的参数
-v:输出传递给JVM的参数。在诊断JVM相关问题的时候,这个参数可以查看JVM相关参数的设置
JSTAT:是jdk自带的一个轻量级小工具。主要对java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控
语法结构如下:jstat [Option] vmid [interval] [count]
Option:选项,一般常使用-gcutil查看gc情况
vmid :VM的进程号,即当前运行的java进程号,需要进行监控的java进程号
interval:间隔时间,单位为秒
count:打印次数,缺省则打印无数次
JMAP:时一个可以输出所有内存中对象的工具,甚至可以将VM中的heap,以二进制输出成文本。
使用方法:
jmap -histo pid>a.log 可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以对比出GC回收了哪些对象
jmap -dump:format=b,file=f1 PID 可以将该PID进程的内存heap输出出来到f1文件里
jstack:可以查看jvm内的线程堆栈信息
JConsole/JVisualVM:可视化的jvm控制台,监控工具
第四课:
Java代码优化技巧及原理:
合理并谨慎使用多线程:
使用场景:I/O阻塞,多CPU并发,多用户并发
问题:资源争用与同步问题(java.util.concurrent)
合理线程数公式:启动线程数=[任务执行时间/(任务执行时间-IO等待时间)]*CPU内核数
最佳启动线程数和CPU内核数量成正比,和IO阻塞时间成反比。如果任务都是CPU计算型任务,那么线程数最多不超过CPU内核数,因为启动再多线程,CPU也来不及调度;相反如果是任务需要等待磁盘操作,网络响应,那么多启动线程有助于提高任务并发度,提高系统吞吐能力,改善系统性能。
竞态条件与临界区:
多线程问题:在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。
竞态条件:当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区
如何避免:在临界区中使用适当的同步就可以避免竞态条件
java线程安全:
允许被多个线程安全执行的代码称作线程安全的代码,实现方法:
方法局部变量:局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。
方法局部的对象引用:如果在某个方法中创建的对象不会逃逸出该方法,那么它就是线程安全的。
对象成员变量:对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。
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内存泄漏时由于开发人员的错误引起的
如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存
长生命周期对象
静态容器
缓存
如何避免内存泄漏:
合理使用线程池和对象池:
复用线程或对象资源,避免在程序的生命期中创建和删除大量对象
池管理算法(记录哪些对象是空闲的,哪些对象正在使用)
对象内容清除(ThreadLocal的清空)
使用合适的JDK容器类(顺序表,链表,Hash)
Linklist和ArrayList的区别及适用场景
HashMap的算法实现及应用场景
适用concurrent包,ConcurrentHashMap和HashMap的线程安全特性有什么不同
缩短对象生命周期,加速垃圾回收
减少对象驻留内存的时间
在使用时创建对象,用完释放
创建对象的步骤(静态代码段-静态成员变量-父类构造函数-子类构造函数)
使用I/O buffer以及NIO:
延迟写与提前读策略
异步无阻塞IO通信
优先使用组合代替继承:
减少对象耦合
避免太深的继承层次带来的对象创建性能损失
合理的使用单例模式:
无状态对象:尽量使用无状态的单例,减少共享的资源
线程安全:单例全局唯一,注意线程安全问题
第五课:
系统性能优化案例:秒杀系统
什么是秒杀系统:商品数量少,只在指定时间定量放出,会在短时间内出现大量访问的场景
秒杀系统需要面对的问题:
网络带宽耗尽
服务器load飙高,停止响应
数据库瘫痪等
应对方法:
服务器准备:做一套全新的系统用来应对秒杀
带宽准备:本地带宽配合CDN减少带宽 小的压力
版权声明: 本文为 InfoQ 作者【Airs】的原创文章。
原文链接:【http://xie.infoq.cn/article/d7384573b7c56f34c14ba9f31】。未经作者许可,禁止转载。
评论