写点什么

性能优化(三)

用户头像
escray
关注
发布于: 2020 年 11 月 22 日
性能优化(三)

极客大学架构师训练营第 1 期第 9 周课程


9.1 数据库的基本原理


PrepareStatement


statement.executeUpdate(“UPDATE Users SET status = 2 WHERE userID = 233”);
复制代码


PrepareStatement updateUser =  conn.prepareStatement(“UPDATE Users SET status = ? WHERE userID = 233”);updateUser.setInt(1, 2);updateUser.executeUpdate();
复制代码


数据库架构



连接器


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


语法分析器



mysql> select s_grade from staff where s_city not in (select p_city from proj where s_empname = p_pname)mysql> explain select * from users whee id = 1ERROR 1064(42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘id = 1’ at line 1
复制代码


语义分析与优化器


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


select f.id from orders f where f.user_id = (select id from users);select f.id from orders f join users u on f.user_id = u.id;
复制代码


优化无止尽


执行引擎



Key:索引类型,NULL 无索引

Rows:需要处理的行数

Possible_keys:潜在可以利用的索引


为什么 PrepareStatement 更好


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


PrepareStatement 可以防止 SQL 注入攻击


select * from users where username = ‘Frank’;
复制代码


如果输入:Frank’; drop table users; --


select * from users where username = ‘Frank’; drop table users; —select * from users where username = ?;
复制代码


B+ 树



聚簇索引


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


非聚簇索引


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


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


如果没有索引,就需要全表扫描


合理使用索引


不要盲目添加索引(尤其是在生产环境中)

  • 添加索引的 ALTER 操作会消耗较长时间(分钟级)

  • ALTER 操作期间,数据库增删改操作全部阻塞,对于应用而言,因为连接不能释放,查询在事实上也被阻塞


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


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


数据库事务


事务特性 ACID


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

  • 隔离性 Isolation:两个事务 T1 和 T2 同时运行,不论 T1 和 T2 谁先结束,最终的结果相同,隔离性主要依靠锁实现

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

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


-- 开始一个事务begin;-- 示意代码update table set A = A - 10000;update table set B = B + 10000;-- 其他读写操作commit;-- 提交事务
复制代码


数据库事务日志


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



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

TransID:产生操作的事务 ID

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

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

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

REDO:重复本次操作的方法


9.2 JVM 虚拟机架构原理


JVM 组成架构



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


java org.apache.catalina.startup.Bootstrap “$@“start
复制代码


首先启动的是 Java 程序(进程),然后通过类加载器加载 Java 类,然后把参数交给 Java 类。首先启动一个 JVM 进程,把类放到方法区 Method Area,创建一个进程(用户主线程),分配 Java 堆 Heap 和栈 Stack、程序计数寄存器 PC Register(记录当前线程执行的位置)。


线程启动之后,检查程序技术寄存器,然后从方法区取出字节码指令,使用执行引擎 Execution Engine 通过解析或编译的方式,生成本地指令。


new 出来本地对象(实例)放在堆里面,对于对象的引用(变量)记录在栈里面。


Java 栈和程序计数寄存器是由线程独享的。


Java 字节码文件


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


Java 所有的指令有 200 个左右,一个字节(8 位)可以存储 256 种不同的指令信息,一个这样的字节称为字节码(Bytecode)。


在代码执行过程中,JVM 将字节码解释执行,屏蔽对底层操作系统的依赖;JVM 也可以将字节码编译执行,如果是热点代码,会通过 JIT 动态的编译为机器码,提高执行效率。


magic word: cafe babe



字节码执行流程



Java 字节码文件编译过程



类加载器的双亲委托模型



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


自定义类加载器


public class CustomClassLoader extends ClassLoader {  @Override  protected Class<?> findClass(String name) throws ClassNotFoundException {      try {      byte[] result = getClassFromCustomPath(name);      if (result == null) {        throw new FileNotFoundException();      } else {        return defineClass(name, result, 0, result.legnth);      }    } catch (Exception e) {      e.printStackTrace();    }    throw new ClassNotFoundException(name);  }
private byte[] getClassFromCustomPath(String name) { // Load class from custom path }
public static void main(String[] args) { CustomClassLoader customClassLoader = new CustomClassLoader(); try { Class<?> clazz = Class.forName("One", true, customClassLoader); Object obj = clazz.newInstance(); System.out.println(obj.getClass().getClassLoader()); } catch (Exception e) { e.printStackTrace(); } }}
复制代码


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

扩展加载类:从网络、数据库等出加载字节码

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


堆 & 栈


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


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


Java 中所有对象的存储空间都是在堆中分配的,但是对象引用是在栈中分配。


方法区 & 程序计数器


方法区主要存放从磁盘加载进来的类字节码(程序运行过程中创建的类实例存放在堆里)。


程序运行的时候,以线程为单位,当 JVM 进入启动类的 main 方法,会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执行,每个线程都有自己的 Java 栈,栈里存放着方法运行期的局部变量。


程序技术寄存器中存放当前线程执行到哪一行字节码指令。


package com.bjxt.text
public class Demo01public { public static void main(String[] args) { A a = new A(); System.out.println(A.width); }}
class A { public static int width = 100; static { System.out.println(""); width = 300; }
public A() { System.out.println(""); }}
复制代码



Java 线程栈


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


线程工作内存 & volatile


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


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



Java 运行时环境





9.3 JVM 垃圾回收性能分析


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


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


JVM 回收垃圾对象的三种方法:


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

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

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


JVM 分代垃圾回收



JVM 垃圾回收器算法



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,即 jvmid,然后可以进一步使用其他工具来监控和分析 JVM


usage: jps [--help]       jps [-q] [-mlvV] [<hostid>]
Definitions: <hostid>: <hostname>[:<port>] -? -h --help -help: Print this help message and exit.
复制代码


常用参数:


-l 输出 Java 应用程序的 main class 的完整包

-q 仅显示 pid,不现实其他任何相关信息

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

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


JSTAT


JSTAT,Java Virtual Machine Statistics Monitoring tool,是 JDK 自带的一个轻量级工具,主要对 Java 应用程序的资源和性能进行事实的命令行的监控,包括对 Heap size 和垃圾回收状况的监控


Usage: jstat --help|-options       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
Definitions: <option> An option reported by the -options option <vmid> Virtual Machine Identifier. A vmid takes the following form: <lvmid>[@<hostname>[:<port>]] Where <lvmid> is the local vm identifier for the target Java virtual machine, typically a process id; <hostname> is the name of the host running the target Java virtual machine; and <port> is the port number for the rmiregistry on the target host. See the jvmstat documentation for a more complete description of the Virtual Machine Identifier. <lines> Number of samples between header lines. <interval> Sampling interval. The following forms are allowed: <n>["ms"|"s"] Where <n> is an integer and the suffix specifies the units as milliseconds("ms") or seconds("s"). The default units are "ms". <count> Number of samples to take before terminating. -J<flag> Pass <flag> directly to the runtime system. -? -h --help Prints this help message. -help Prints this help message.
复制代码


语法:jstat [options] vmid [interval] [count]


  • options 选项,一般使用 -gcutil 查看 gc 情况

  • vmid VM 的进程号,即当前运行的 Java 进程号

  • interval 间隔时间,单位为毫秒

  • count 打印次数,如果缺省则打印无数次



S0 — Heap 上的 Survivor space 0 区已使用空间的百分比

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 -histo pid > a.log
复制代码


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


jmap -dump:format=b,file=f1 PID
复制代码


可以将该 PID 进程的内存 heap 输出出来到 f1 文件里。


Usage:    jmap -clstats <pid>        to connect to running process and print class loader statistics    jmap -finalizerinfo <pid>        to connect to running process and print information on objects awaiting finalization    jmap -histo[:[<histo-options>]] <pid>        to connect to running process and print histogram of java object heap    jmap -dump:<dump-options> <pid>        to connect to running process and dump java heap    jmap -? -h --help        to print this help message
dump-options: live dump only live objects all dump all objects in the heap (default if one of "live" or "all" is not specified format=b binary format file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
histo-options: live count only live objects all count all objects in the heap (default if one of "live" or "all" is not specified) file=<file> dump data to <file>
Example: jmap -histo:live,file=/tmp/histo.data <pid>
复制代码


JSTACK


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


Usage:    jstack [-l][-e] <pid>        (to connect to running process)
Options: -l long listing. Prints additional information about locks -e extended listing. Prints additional information about threads -? -h --help -help to print this help message
复制代码



JConsole



JVisualVM



9.4 Java 代码优化技巧及原理


  1. 合理并谨慎使用多线程


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


资源争用与同步问题


java.util.concurrent


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


最佳启动线程数和 CPU 内核数成正比,与 IO 阻塞时间成反比。


如果任务都是 CPU 计算型任务,那么线程数最多不超过 CPU 内核数,因为启动再多线程,CPU 也来不及调度;


相反,如果是任务需要等待磁盘操作或网络响应,那么多启动线程有助于提高任务并发度,提高系统吞吐能力,改善系统性能。


竞态条件与临界区


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


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


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


Java 线程安全


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


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

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

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


Java Web 应用的多线程从哪里来?


Servlet 是线程安全的么?


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();
复制代码


ThreadLocal 的实现:


public void set(T value) {    Thread t = Thread.currentThread(); // 获取当前线程    ThreadLocalMap map = getMap(t);    // 获取当前线程的 Map 对象    if (map != null) {        map.set(this, value);    } else {        createMap(t, value);    }}
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}
复制代码


其实是有两层 Map,第一层 Map 的 Key 是当前线程对象,返回的对象是 ThreadLocalMap,根据不同的线程,访问不同的第二层 Map。


  1. Java 内存泄漏


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


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


  • 长生命周期对象

  • 静态容器

  • 缓存


  1. 合理使用线程池和对象池


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

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

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


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


关注数据结构的基本知识


  • LinkList 和 ArrayList 的区别及适用场景:随机访问数组更好,插入和删除链表更好

  • HashMap 的算法实现及使用场景:

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


HashMap 线程不安全,多线程并发访问需要加锁,性能会比较差

ConcurrentHashMap 线程安全,不需要应用程序加锁,自身实现了分段锁,性能更好


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


减少对象驻留内存的时间

在使用时创建对象,用完释放:将引用对象置为 null


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


public class StaticClass {    static {        A a = new A();    }
public static void cc() { System.out.println(“cc()") }}
复制代码


  1. 使用 I/O buffer 和 NIO


延迟写于提前读策略

异步无阻塞 IO 通信


  1. 优先使用组合代替继承


减少对象耦合

避免太深的集成层次带来的对象创建型


  1. 合理使用单例模式


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


  • 面向接口编程

  • 7 层网络协议

  • JVM

  • 编程框架

  • 一致性 hash 算法的虚拟化实现


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


网站的正常流量情况


  • 并发(单台),高峰期 < 10;

  • 吞吐量(TPS,单台) 高峰期,< 60; 

  • CPU 负载 Load 高峰期,< 2,大部分服务器 < 1;

  • CPU 使用率, 一般只占 1 颗核,平均 60% 左右; 

  • 服务器平均响应时间高峰期, < 150ms; 

  • 图片总流量带宽 1.8G(各网站总合)。 


服务器的资源有一定的冗余


高并发下的风险 


  • 网络带宽耗尽

  • 服务器 Load 飙高,停止响应。 

  • 数据库瘫痪 


高并发下的事故


  • 事故:网站运营推广页面弹出 1 兆大图片导致带宽耗尽(平时的图片可能几十、几百 KB) 

  • 增加审核机制:运营推广增加的图片流量不能超过现有流量的 30% 

  • 合作媒体推广:迅雷,暴风影音浮出广告,导致 ZZ 集群 Crash。 


秒杀 


XXXX.com 开业 88 小时不间断秒杀活动,网站域名变更


高并发对网站性能的影响


高并发实例:XXXX.com 开业秒杀活动


商业需求


  • 为庆祝 XXXX.com 开业,推出 88 小时不间断秒杀活动。 

  • 每小时整点推出 8 款商品…… 

  • 每款商品供 168 件,每人限批 3 件,成交人数 56 人。 

  • CCTV 黄金广告时间,各种网络,平面媒体轰炸,总广告费:1.5 亿。 

  • 接到运营通知,距秒杀开始仅仅 5 天时间。 


技术挑战 


提前做好技术和心理准备,在困难的时候站出来,出人头地或者……


瞬间高并发 


  • 8000 并发:预估秒杀在线人数可达 8000 人。现有并发数的 100 倍 

  • 风险:带宽耗尽。 

  • 服务器:崩溃,可以理解成自己给自己准备的 D.D.O.S 攻击。 


秒杀器 


第一种:秒杀前不断刷新秒杀页面,直到秒杀开始,抢着下单。

第二种:跳过秒杀页面,直接进入下单页面,下单。


服务器和网络准备


如果并发量是平时的 100 倍以上,把现有系统优化到支撑 100 倍以上的并发,显然是不现实的(经济、时间)。考虑构建一个新的系统,与现有系统隔离,部署一个单独的秒杀服务器集群。


全新的系统比维护老系统更容易。


架构师的核心能力就是做隔离,把事情理清楚。


服务器准备(距秒杀开始仅五天时间来不及采购,从之前的服务器中隔离出一些服务器)


  • style 服务器(Lighttpd 集群):5 台

  • 图片服务器(Nginx 集群) :5 台

  • 静态服务器(Apache 集群) :10 台

  • 交易服务器(JBoss 动态集群):10 台


带宽准备 


  • 图片出口带宽上限:2.5G (出口带宽支持 10G,但图片服务器集群的处理能力有限:图片服务集群最大并发处理能力 X 网站平均图片大小 = 2.5G) 

  • CDN 准备:与 Chinacache 沟通;借用 CCCC CDN


架构目标


图片网络带宽:1.0G 


  • 新增图片带宽:必须控制在 1.0G 左右 

  • 每件商品秒杀页面的图片总大小不得超过:1000000/(1000*8) = 125K/每商品 


网站并发:


  • 单件商品并发:1000 【来自运营的预估】 

  • 总并发: 8(件商品)X 1000(人/商品)= 8000


组成


简单系统: 三个页面组成:秒杀商品列表,秒杀商品介绍,下单



下单成功后,进入支付系统,走支付流程


设计原则


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


并发控制,防秒杀器:设置阀门,只放最前面的一部分人进入秒杀系统 


简化流程:砍掉不重要的分支流程,如下单页面的所有数据库查询;以下单成功作为秒杀成功标志。支付流程只要在 1 天内完成即可。 


前端优化:采用 YSLOW 原则提升页面响应速度


静态化


秒杀商品列表和详情是静态的 HTML 页面



秒杀商品列表、秒杀商品介绍页面如何判断秒杀是否开始?


valid-offer.js

var valid_offerIds = 23624364, 53778658, 35885833, 46999696, 5006797057
复制代码



三道阀门


阀门:基于 TT 的计时器




架构师面对问题的时候,要想清楚究竟要解决的是什么样的问题。


预防秒杀器


秒杀详情页面:


  • URL:随机

  • 秒杀前 2 秒放出,脚本生成,秒杀前

  • 1000 次访问上限控制(每件商品只能放入 1000 人浏览)


SetEnvIfNoCase Referer “^http://1688\.com/“ local_ref=1
<FilesMatch “\.(htm|html)”> Order Allow,Deney Allow from env=local_ref</FilesMatch>
复制代码


下单页面:


  • 订单 ID 随机

  • 不能直接跳过秒杀详情页进入

  • 每个秒杀商品,带预先生成的随机 Token 作 URL 参数

  • 如果秒杀过,直接跳转秒杀结束页面

  • 100 次访问上限控制(每件商品只能放入 1000 人下单)


Web Server 调优


  • Apache 调优

  • JBoss 调优


Apache 调优


Timeout 30KeepAlive OnMaxKeepAliveRequests 1000KeepAliveTimeout 30
<IfModule worker.c> StartServers 5 MaxClients 1024 MinSpareThreads 25 MaxSpareThreads 250 ThreadsPerChild 64 ThreadLimit 128 ServerLimit 16</IfModule>
# Assume no memory leaks at allMaxRequestPerChild 20000
复制代码


  • KeepAlive 相关参数调优

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

  • 关闭 cookies-log 日志 

  • 打开 Linux sendfile() 

  • 关闭无用的 module 

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


JBoss 调优


Mod-jk worker 调优



JBoss AJP Connector



Tomcat APR 设定


秒杀静态页面优化


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

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

  • 减少发送 Cookies 的数量


HTML 内容压缩


图片压缩:图片大小 Bytes < 长 × 宽 / 2250


HTML Header Cache-Control 设置


CSS,JS 精简:精简到极致,部分直接卸载页面中,减少 HTTP 请求次数


下单页面优化


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


秒杀流程精简:

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

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


采用内存缓存:秒杀 Offer 数据、支付相关信息全部缓存在内存中


交易系统性能优化


交易系统调优目标



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

JVM 优化

优化 CMS 垃圾回收器参数

消灭 Top 10 Bottlenecks

Velocity 参数调优

采用 DBCP 1.4 替换 C3P0

Offer 产品参数的 XML 解析


二跳页面的优化


其他页面:


  • 前端优化:YSLOW 规则调优。减少 HTTP 请求,合并 JS、CSS、图片,充分利用浏览器缓存

  • 图片压缩,公式:

  • 避免发送 Cookies


交易系统优化:


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

  • 禁止使用模糊查询功能


应急预案


  • 域名分离,独立域名,不影响 XXXX 原有业务。

  • Style 集群: style.XXXX.china.XXXX.com 

  • 机动服务器 10 台,备用。

  • 拆东墙补西墙战略:5 天时间来不及采购服务器,因此 SA 待命,随时准备将非核心应用集群的冗余服务器下线,加入到秒杀集群。

  • 壁虎断尾策略:所有办法均失效的情况下,例如流量耗尽。

  • 非核心应用集群统统停止服务,如资讯,论坛,博客等社区系统。

  • 万能出错页面:秒杀活动已经结束

  • 任何出错都 302 跳转到此页面


万幸:最终所有的预案都没有用上


秒杀活动结果


88 小时秒杀,坚守阵地,大获成功。


秒杀还是被秒杀?终于有了答案 


三道阀门设计非常有效,拦住了秒杀器。



XXXX.com 静态集群总并发情况(首页,秒杀列表,秒杀商品页面)



交易系统集群总并发情况(下单页面)


秒杀活动总结


改进一:采用更轻量、快速的服务器


采用 Lighttpd 代替 Apache 杀手锏(AIO)


架构师不需要拿高大上的架构方案到处敲打,而是找到问题的关键。


改进二:前端优化自动化


XXXX 服务器响应时间 <150ms,但 Offer Detail 页面用户等待时间 5s,大部分时间耗在路上(资源请求和网络传输) 


  • 图片自动压缩(CMS 自动压缩) 

  • Cookies 服务化(控制 Cookies 的大小) 

  • XXXX 前端延迟加载框架 SmartLoad (只加载首屏数据) 

  • Google mod_pagespeed module :自动压缩图片,静态资源,智能浏览 器缓存技术 

  • Google Diffable (增量下载静态资源技 术)



改进三:架设镜像站组件山寨 CD




改进四:采用反向代理,加速核心页面


在 Offer 集群前部署 Squid 反向代理集群



Offer Detail 的 Squid 反向代理改造



基于消息的 Squid 缓存更新机制


改进五:海量数据的透明垂直切分



9.7 第九周课后练习


作业一:至少完成一个


  • 请简述 JVM 垃圾回收原理。

  • 设计一个秒杀系统,主要的挑战和问题有哪些?核心的架构方案或者思路有哪些?


作业二:根据当周学习情况,完成一篇学习总结


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

escray

关注

Let's Go 2017.11.19 加入

在学 Elasticsearch 的项目经理

评论

发布
暂无评论
性能优化(三)