第九周学习总结
知识点总结
数据库基本原理
数据库架构
连接器
数据库连接器会为每个连接请求分配一块专用的内存空间用于会话上下文管理。建立连
接对数据库而言相对比较重,需要花费一定的时间,因此应用程序启动的时候,通常会
初始化建立一些数据库连接放在连接池里,这样当处理外部请求执行 SQL 操作的时候,
就不需要花费时间建立连接了。
语义分析与优化器
语义分析与优化器就是要将各种复杂嵌套的 SQL 进行语义等价转化,得到有限几种关系
代数计算结构,并利用索引等信息进一步进行优化。
为什么 PrepareStatement 更好
PrepareStatement 会预先提交带占位符的 SQL 到数据库进行预处理,提前生成执行计
划,当给定占位符参数,真正执行 SQL 的时候,执行引擎可以直接执行,效率更好一点。
B+ 树
聚簇索引
聚簇索引:聚簇索引的数据库记录和索引存储在一起。
MySQL 数据库的主键就是聚簇索引,主键 ID 和所在的记录行存储在一个 B+树中。
非聚簇索引
非聚簇索引在叶子节点记录的就不是数据行记录,而是聚簇索引,也就是主键。
通过非聚簇索引找到主键索引,再通过主键索引找到行记录的过程也被称作回表。
合理使用索引
不要盲目添加索引,尤其在生产环境中
添加索引的 alter 操作会消耗较长的时间(分钟级)
Alter 操作期间,所有数据库的增删改操作全部阻塞,对应用而言,因为连接不能释放,事实上,查询也被阻塞。
删除不用的索引,避免不必要的增删开销
使用更小的数据类型创建索引
int 4 字节 bigint 8 字节,Timestamp 4 字节 Datetime 8 字节
数据库事务
事务特性 ACID
原子性(Atomicity): 事务要么全部完成,要么全部取消。 如果事务崩溃,状态回到事务之前(事务回滚)。
隔离性(Isolation): 如果 2 个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1 和 T2 谁先结束,隔离性主要依靠锁实现。 • 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。
一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。
数据库事务日志
进行事务操作时,事务日志文件会记录更新前的数据记录,然后再更新数据库中的记录,
如果全部记录都更新成功,那么事务正常结束,如果过程中某条记录更新失败,那么整
个事务全部回滚,已经更新的记录根据事务日志中记录的数据进行恢复,这样全部数据
都恢复到事务提交前的状态,仍然保持数据一致性。
LSN:一个按时间顺序分配的唯一事务记录日志序列号。
TransID:产生操作的事务 ID。
PageID:被修改的数据在磁盘上的位置。
PrevLSN:同一个事务产生的上一条日志记录的指针。
UNDO:取消本次操作的方法,按照此方法回滚。
REDO:重复本次操作的方法。
JVM 虚拟机原理
JVM 组成架构
Java 是一种跨平台的语言,JVM 屏蔽了底层系统的不同,为 Java 字节码文件构造了一个统一的运行环境。
执行引擎负责在不同的操作系统中将代码转换为本地代码执行
方法区主要存放从硬盘加载进来的类字节码,静态变量是放在方法区的。堆存放在程序执行过程中创建的类的实例。
Java 字节码文件
class 字节码文件的开头 8 位:cafe babe Java 所有的指令有 200 个左右,一个字节(8 位)就可以存储 256 种不同的指令信息,一个这样的字节就被称为字节码(ByteCode)在代码执行过程中,JVM 会把字节码解释执行,屏蔽对底层操作系统的依赖,JVM 也可以将字节码编译执行,如果是热点代码,会通过 JIT 动态地编译成机器码,提高执行效率
字节码执行流程
Java 字节码文件编译过程
类加载器的双亲委派模型
低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类。如果低层次的类加载器想要加载一个未知类,需要向上级类加载器确认,如果上级没有加载过这个类,且也同意加载的时候,才会让当前类加载器加载这个类。
自定义类加载器
隔离加载类:同一个 JVM 中不同组件加载同一个类的不同版本。
扩展加载源:从网络、数据库等处加载字节码。
字节码加密:加载自定义的加密字节码, 在 ClassLoader 中解密。
堆 & 栈
堆
每个 JVM 实例唯一对应一个堆。
应用程序在运行中创建的所有类实例或数组都存放在堆中
应用的所有线程共享这个堆
堆栈
JVM 为每个新创建的线程分配一个堆栈
Java 中所有对象的存储空间都是在堆中分配的,但对象的引用却是在堆栈中分配。也就是说在建立一个对象的时候,要做两个操作。一是在堆中实际建立这个对象,二是在堆栈中分配内存,存放指向这个堆对象的引用。
程序运行时,实际上是以线程为单位运行的。所以说,对于一个 Java 程序来说,它的运行就是通过对堆栈的操作来完成的。
方法区 & 程序计数器
方法区主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候,实际上是以线程为单位运行的,当 JVM 进入启动类的 main 方法的时候,就会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执行,每个线程有自己的 Java 栈,栈里存放着方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器。
Java(线程)栈
所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的。
线程工作内存 & volatile
Java 内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行。
一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
Java 运行环境
JVM 的垃圾回收
JVM 垃圾回收就是将 JVM 堆中的已经不再被使用的对象清理掉,释放宝贵的内存资源。JVM 通过一种可达性分析算法进行垃圾对象的识别,具体过程是:从线程栈帧中的局部变量,或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没有被标记的对象就是可回收的垃圾对象了。
进行完标记以后,JVM 就会对垃圾对象占用的内存进行回收,回收主要有三种方法
清理:将垃圾对象占据的内存清理掉,其实 JVM 并不会真的将这些垃圾内存进行清理,而是将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象。
压缩:从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间。
复制:将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。
JVM 分代垃圾回收
JVM 垃圾回收器算法
串行回收器:单独启动一个线程专门用于垃圾回收,当 GC 时所有线程都停止工作『stop the world』,等待这个线程的 GC 完成
并行回收器:并行是指可以启动多个线程进行垃圾回收;当 GC 发生时,仍然会『stop the world』,但可以根据 cpu 核心数,启动多个线程进行 GC,效率更高。
并发垃圾标记清理(CMS):并发是指 GC 线程和业务线程可以同时工作,同时也就意味着可能会出现重标记,重标记期间还是会发生『stop the world』,比并行回收器,对业务线程影响较小,但更好资源。
G1 回收器:jdk1.7 高版本开始引入 G1
G1 垃圾回收内存管理机制
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
JPS
JPS 用来查看 host 上运行的所有 Java 进程的 pid(jvmid),一般情况下使用这个工具的目的只是为了找出运行的 JVM 进程 ID,即 lvmid,然后可以进一步使用其它的工具来监控和分析 JVM
常用的几个参数:
-l 输出 Java 应用程序的 main class 的完整包
-q 仅显示 pid,不显示其它任何相关信息
-m 输出传递给 main 方法的参数
-v 输出传递给 JVM 的参数。在诊断 JVM 相关问题的时候,这个参数可以查看 JVM 相关参数的设置
JSTAT
JSTAT( “Java Virtual Machine statistics monitoring tool” )是 JDK 自带的一个轻量级小工具。主要对 Java 应用程序的资源和性能进行实时的命令行的监控,包括了对 Heapsize 和垃圾回收状况的监控。
语法结构如下:jstat [Options] vmid [interval] [count]
Options -- 选项,我们一般使用 - gcutil 查看 gc 情况
vmid -- VM 的进程号,即当前运行的 Java 进程号
interval-- 间隔时间,单位为毫秒
count -- 打印次数,如果缺省则打印无数次
JMAP
JMAP 是一个可以输出所有内存中对象的工具,甚至可以将 VM 中的 heap,以二进制输出成文本。
使用方法
jmap -histo pid>a.log 可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以对比出 GC 回收了哪些对象。
jmap -dump:format=b,file=f1 PID 可以将该 PID 进程的内存 heap 输出出来到 f1 文件里。
JSTACK
查询 jvm 中的线程堆栈信息
其他可视化的工具
JConsole
JVisualVM
Java 代码优化
合理并谨慎使用多线程
使用场景(I/O 阻塞,多 CPU 并发)
资源争用与同步问题
java.util.concurrent
启动线程数 = [任务执行时间 / (任务执行时间 - IO 等待时间)] * CPU 内核数
最佳启动线程数和 CPU 内核数量成正比,和 IO 阻塞时间成反比。如果任务都是 CPU 计算型任务,那么线程数最多不超过 CPU 内核数,因为启动再多线程,CPU 也来不及调 度;相反如果是任务需要等待磁盘操作,网络响应,那么多启动线程有助于提高任务并 发度,提高系统吞吐能力,改善系统性能。
竞态条件与临界区
在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。在临界区中使用适当的同步就可以避免竞态条件。
Java 线程安全
允许被多个线程安全执行的代码称作线程安全的代码。
方法局部变量
局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。
方法局部的对象引用
如果在某个方法中创建的对象不会逃逸出该方法,那么它就是线程安全的。
对象成员变量
对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。
ThreadLocal
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 内存泄漏
Java 内存泄漏是由于开发人员的错误引起的。
如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存。
长生命周期对象
静态容器
缓存
合理使用线程池和对象池
复用线程或对象资源,避免在程序的生命期中创建和删除大量对象
池管理算法(记录哪些对象是空闲的,哪些对象正在使用)
对象内容清除(ThreadLocal 的清空)
使用合适的 JDK 容器类(顺序表,链表,Hash)
LinkList 和 ArrayList 的区别及适用场景
HashMap 的算法实现及应用场景
使用 concurrent 包
缩短对象生命周期,加速垃圾回收
减少对象驻留内存的时间
在使用时创建对象,用完释放
创建对象的步骤(静态代码段-静态成员变量-父类构造函数-子类构造函数)
使用 I/O buffer 及 NIO
延迟写与提前读策略
异步无阻塞 IO 通信
优先使用组合代替继承
减少对象耦合
避免太深的继承层次带来的对象创建性能损失
合理使用单例模式
无状态对象
线程安全
计算机的任何问题都可以通过虚拟层(或者中间层)解决
面向接口编程
7 层网络协议
JVM
编程框架
一致性 hash 算法的虚拟化实现
秒杀案例
XXXX 性能现状
XXXX 网站的正常流量情况
并发(单台),高峰期 < 10;
吞吐量(TPS,单台) 高峰期,<60;
CPU 负载 Load 高峰期,<2,大部分服务器 <1;
CPU 使用率 , 一般只占 1 颗核,平均 60% 左右;
服务器平均响应时间 高峰期 , <150ms;
图片总流量带宽 1.8G(各网站总合)。
高并发下的风险
网络带宽耗尽
服务器 Load 飙高,停止响应。
数据库瘫痪
高并发下的事故
事故:网站运营推广页面弹出 1 兆大图片导致带宽耗尽
增加审核机制:运营推广增加的图片流量不能超过现有流量的 30%
合作媒体推广:迅雷,暴风影音浮出广告,导致 ZZ 集群 Crash。
秒杀
XXXX.com 开业 88 小时不间断秒杀活动
高并发实例:XXXX.com 开业秒杀活动
商业需求
为庆祝 XXXX.com 开业退出 88 小时不间断秒杀活动。
每小时整点推出 8 款商品……
每款商品供 168 件,每人限批 3 件,成交人数 56 人。
CCTV 黄金广告时间,各种网络,平面媒体轰炸,总广告费:1.5 亿。
接到运营通知,距秒杀开始仅仅 5 天时间。
技术挑战
瞬间高并发
8000 并发:预估秒杀在线人数可达 8000 人 。 Ø 风险:带宽耗尽。
服务器:崩溃,可以理解成自己给自己准备的 D.D.O.S 攻击。
秒杀器
第一种:秒杀前不断刷新秒杀页面,直到秒杀开始,抢着下单。
第二种:跳过秒杀页面,直接进入下单页面,下单。
XXXX.com 秒杀系统:服务器和网络准备
服务器准备(距秒杀开始仅五天时间来不及采购)
style 服务器(Lighttpd 集群):5 台
图片服务器(Nginx 集群) :5 台
静态服务器(Apache 集群) :10 台
交易服务器(JBoss 动态集群):10 台
带宽准备
图片出口带宽上限:2.5G (出口带宽支持 10G,但图片服务器集群的处理能力:图片服务集群最大并发处理能力 X 网站平均图片大小 = 2.5G)
CDN 准备:Chinacache 沟通;借用 CCCC CDN
XXXX.com 秒杀系统:架构目标
图片网络带宽:1.0G
新增图片带宽:必须控制在 1.0G 左右
每件商品秒杀页面的图片总大小不得超过:1000000/(1000*8) = 125K/每商品
网站并发:
单件商品并发:1000 【来自运营的预估】
总并发: 8(件商品)X 1000(人/商品)= 8000
XXXX.com 秒杀系统:设计原则
静态化
采用 JS 自动更新技术将动态页面转化为静态页面并发控制,防秒杀器
设置阀门,只放最前面的一部分人进入秒杀系统
简化流程
砍掉不重要的分支流程,如下单页面的所有数据库查询
以下单成功作为秒杀成功标志。支付流程只要在 1 天内完成即可。
前端优化
采用 YSLOW 原则提升页面响应速度
静态化
秒杀商品 list 和 Detail 是静态 HTML 页面
秒杀商品列表/秒杀商品介绍页面,如何判断秒杀开始否。
三道阀门的设计
阀门:基于 TT 的计数器
秒杀器的预防
秒杀 Detail 页面
URL:随机
秒杀前 2 秒放出,脚本生成,秒杀前。
1000 次访问上限控制【每件商品只能放入 1000 人浏览】。
下单页面:
订单 ID,随机。
不能直接跳过秒杀 Detail 页面进入。
每个秒杀商品,带预先生成的随机 Token 作 URL 参数。
如果秒杀过,直接跳到秒杀结束页面。
100 次访问上限控制【每件商品只能放入 1000 人下单】。
Web Server 调优 – Apache 调优
KeepAlive 相关参数调优
其他参数调优
HostnameLookups 设为 off, 对 allowfromdomain 等后的域名不进行正向和反向的 dns 解析。
关闭 cookies-log 日志
打开 Linux sendfile()
关闭无用的 module
mod_Gzip
(秒杀页面,非图片 HTML 文本所占流量比重可忽略不计,
zip 意义不大)
mod_Beacon
mod_hummock(等待反应过来,秒杀已经 over 了)
秒杀静态页面优化
图片合并
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 秒批订单管理列表分离
禁止用模糊查询功能
应急预案
域名分离,独立域名,不影响 XXXX 原有业务。
Style 集群: style.XXXX.china.XXXX.com
图片服务器集群:img.XXXX.china.XXXX.com
静态页面集群:page.XXXX.china.XXXX.com
出问题直接把 XXXX 相关域名卡掉,所有请求跳到万能出错页面。
机动服务器 10 台,备用。
拆东墙补西墙战略
5 天时间来不及采购服务器,因此 SA 待命,随时准备将非核心应用集群的冗余服务器下线,加入到秒杀集群。
壁虎断尾策略
所有办法均失效的情况下,例如流量耗尽。
非核心应用集群统统停止服务,如资讯,论坛,博客等社区系统。
保住首页,Offer Detail,旺铺页面等核心应用的可用性。
万能出错页面:秒杀活动已经结束
任何出错都 302 跳转到此页面
位于另外集群
评论