写点什么

架构师训练营第一期 - 第九周学习总结

发布于: 2020 年 11 月 22 日

数据库架构原理

数据库服务端主要由连接器、分析器、优化器、执行器构成。

连接器主要负责客户端的连接建立、权限验证及管理维护连接,数据库连接器会为每个连接请求分配一块专用的内存空间用于会话上下文管理。建立数据库连接是比较耗时的操作,通常是初始化一些连接放到连接池中,客户端请求SQL执行的时候直接使用连接池中的连接。

分析器通过语法分析构建生成一颗抽象语法树,如果SQL语句语法有错误在构建语法树的时候就会出错。

优化器是对抽象语法树进行进一步的语义分析和优化,将各种复杂的SQL语句进行语义的等价转换得到有限的几种关系代数运算(选择、投影、交、积、连接等),然后在关系代数运算的基础上利用语法特征进行优化(比如合并、前置计算、规则转换等)及使用索引信息进行优化。然后将优化后的语法树生成执行计划。

执行器拿到执行计划找到对应的表及相关的索引到数据库的存储文件里去把把相关的数据查找出来返回结果。

Java客户端程序针对数据库编程有两种方式:statement和PrepareStatement,实际使用时应该使用PrepareStatement预编译这种模式。这里有两个原因:

1.PrepareStatement 会预先提交带占位符的SQL 到数据库进行预处理,提前生成执行计划,当给定占位符参数,真正执行SQL 的时候,执行引擎拿到传入的变量直接执行就可以了,性能会更好。

2.PrepareStatement 可以防止SQL 注入攻击,因为执行计划已经提前生成好了,而直接提交SQL可能会被注入一些不可知的字符串,这些字符串符合语法树的生成,但是却被生成了另外的语义及执行计划,从而变成安全攻击。

数据库的数据记录是采用B+树作为索引来存储的,通过索引来快速查找数据记录。索引有聚簇索引和非聚簇索引两种。

聚簇索引是将数据库记录和索引存储在一起,使用主键ID作为索引key,主键ID和所在的记录行存储在一起放在B+树中一个叶子节点的value里。

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

使用索引是为了提高查询性能,没有索引就需要全表扫描一行一行查找下去才能找到所需记录,性能就会很慢。添加索引是数据库性能优化的一个主要手段,但是也不能盲目的添加索引。添加索引是alter操作,会消耗比较长的时间(分钟级)同时操作的时候所有数据库的增删改操作全部阻塞,对应用而言,因为连接不能释放,后续的查询也会被阻塞。

添加索引也并不是一定能提升性能,添加索引的时候就会增加一个B+树,当增加、删除数据的时候,行记录上的所有索引树都需要增加、删除对应的树节点(维护索引树),建立的索引越多B+树越多,维护索引树的开销越大,从而影响数据库性能。因此,增加索引的时候只在必要的字段上添加,同时删除掉掉哪些不用的索引,避免不必要的增删开销。(根据对表的SQL语句操作来决定)。另外,在创建索引的时候尽量使用更小的数据类型,对于一个B+树节点同样的空间字段越小就可以放越多的数据,这也意味着一次查找拿到一个数据节点里面得到的数据越多查询就更快,同时索引树也会更小需要的存储空间更少。

事务特性ACID

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

务之前(事务回滚)。

隔离性(Isolation): 如果2个事务T1 和T2 同时运行,事务T1 和T2 最终的结果是相同

的,不管T1和T2谁先结束,隔离性主要依靠锁实现。

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

据库中。

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

数据库事务实现主要是通过写UODO日志和REDO日志,UODO日志记录更新前的数据、REDO日志记录更新后的数据。操作执行失败,要么全部回滚,要么接着全部再执行一遍。



JVM 虚拟机原理

JVM主要可以分为三个部分:类加载器、运行期数据区和执行引擎。启动一个Java程序的时候,首先启动jvm进程,然后通过类加载器把Bootstrap的字节码加载进来,把类放到运行期数据区的方法区,为主线程分配一个java栈(每个线程都有自己的堆栈),为线程分配一个程序计数寄存器(记录当前的线程执行到哪里了),然后创建一个用户的主线程开始执行线程,线程启动以后检查程序计数寄存器指向的执行指令,根据到方法区去把指令(可执行的字节码)取出来然后交给执行引擎,执行引擎把字节码指令通过解释或者编译的方式编译成本地操作系统的执行指令去执行。

java字节码的执行流程

1.检查字节码指令是否已经编译过了

2.如果编译过了把编译后的机器码直接交给操作系统去让CPU执行

3.如果不是,方法调用计数器加1

4.检查计数器是否超过阈值

5.1.如果超过阈值,说明这个指令重复执行多次,是一个热点指令,那就提交给编译器,

5.2.编译器编译生成本地指令代码;

5.3.执行编译后的指令,编译后的代码放到code cache中缓存;

6.1.如果没有超过阈值,将字节码指令提交给解释器;

6.2.解释器解析生成可执行本地代码;

6.3.执行编译后的指令

7.返回执行结果

java字节码的编译流程:.java源文件经过词法分析、语法分析、语义分析、字节码生成出来后生成字节码。

类加载器是分层加载的,最高层是Bootstrap ClassLoader 加载Java自身的核心类jre/lib/rt.jar 是加载运行时环境的一些java class,第二层是平台类加载器Platform ClassLoader(现在加扩展类加载器Extension ClassLoader)去加载jre/lib/ext下面的jar包,最下面是Application ClassLoader去加载指定的java的class path路径下的那些class。如果低层次的类加载器想加载一个未知类,需要上级类加载器确认,只有当上级类加载器没有加载过这个类,也允许加载的时候,才让当前类加载器加载这个未知类。

java运行环境(过程):Java源代码.java文件通过Java编译器编译生成字节码文件.class文件,通过本地或者网络加载,类加载器再加载到方法区里去,然后经过java解释器或即时编译器处理生成本地可执行指令代码,交给操作系统在硬件CPU上去执行。



JVM垃圾回收

JVM 垃圾回收就是将JVM 堆中的已经不再被使用的对象清理掉,释放宝贵的内存资源。如果一个对象不再被在程序中的变量引用就成为不再被使用的对象了,那这个对象就可以被清理掉了。JVM 通过一种可达性分析算法进行垃圾对象的识别,具体过程是:从线程栈帧中的局部变量,或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没有被标记的对象就是可回收的垃圾对象了。

在实际中Java的对象大部分的生存时间是很短暂的,它们都是方法中的生成的对象引用,方法执行完退出方法的时候栈帧出栈引用被销毁,引用指向的对象就失去引用变成垃圾对象可以被回收了,仅仅是方法内部的生成时间。

在具体回收的时候,JVM使用分代垃圾回收的策略,把内存空间分为新生代和老年代,新生代再分为三个区:edan区、from区、to区。每一个对象都先在edan区创建,当edan区满时进行垃圾回收,回收时先将edan区区还再被引用的对象复制到from区,然后清空eden区。eden区再满时重复之前的垃圾回收处理。当from区也存放满了时,则将edan区和from区的存活对象复制到to区,然后清空edan区和from区,然后将to区的点对象复制到from区,保持from区为空,如此往复。经过多次回收处理仍然存活的对象就复制到老年代,一段时间以后老年代的空间也满了,那对老年代进行一次垃圾回收,在进行老年代垃圾回收的时候新生代也要进行垃圾回收。因此垃圾回收分为两种:只进行新生代的垃圾回收叫Yong GC,同时进行新生代和老年代的垃圾回收叫Full GC。

JVM 垃圾回收器算法

串行垃圾回收器:垃圾回收的时候,阻塞所有用户线程,只有一个垃圾回收线程运行。

并行垃圾回收器:垃圾回收的时候,阻塞所有用户线程,多个垃圾回收线程并行运行。

并发垃圾回收器CMS:将垃圾回收分为4个阶段,初始化标记、并发标记、重标记、并发清理,初始化标记的时候仍然阻塞所有用户线程,但做完一个初始标记以后就进入并发标记阶段,垃圾回收线程和用户线程并发执行,然后进行重标记(因为并发阶段又有新的对象产生了,初始标记可能不准),这时也要阻塞所有用户线程,标记完之后进行清理,清理线程和用户线程并发执行。

G1垃圾回收器将内存分为更多小区域(默认是2000个区域)分别进行垃圾回收处理,每个区域越小进行垃圾回收的效率越好,每个区域分为edan区、from区、to区新生代(edan区、from区、to区)、老年区和大内存区(这里有疑问)。使用Max最大的GC暂停时间去控制回收处理,通过你期望暂停时间有多少(GC造成的stop-the-word时间是多少)来指定时间,根据期望的时间动态调整回收策略达到在这个时间内完成回收。



用户头像

还未添加个人签名 2019.01.15 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营第一期-第九周学习总结