总结
本文从基础到高级再到实战,由浅入深,把MySQL讲的清清楚楚,明明白白,这应该是我目前为止看到过最好的有关MySQL的学习笔记了,我相信如果你把这份笔记认真看完后,无论是工作中碰到的问题还是被面试官问到的问题都能迎刃而解!
MySQL50道高频面试题整理:
并发数是指系统同时能处理的请求数量。
需要注意,并发数和 QPS 不要搞混了,QPS 表示每秒的请求数量,而并发数是系统同时处理的请求数量,并发数量会大于 QPS,因为服务端的一个连接需要有一个处理时长,在这个请求处理结束之前,这个连接一直占用。
举个例子,如果 QPS=1000,表示每秒钟客户端会发起 1000 个请求到服务端,而如果一个请求的处理耗时是 3s,那么意味着总的并发=1000*3=3000,也就是服务端会同时有 3000 个并发。
计算方法
上面说的这些指标,怎么计算呢?举个例子。
假设在 10 点到 11 点这一个小时内,有 200W 个用户访问我们的系统,假设平均每个用户请求的耗时是 3 秒,那么计算的结果如下:
-
QPS=2000000/60*60 = 556 (表示每秒钟会有 556 个请求发送到服务端)
-
RT=3s(每个请求的平均响应时间是 3 秒)
-
并发数=556*3=1668
从这个计算过程中发现,随着 RT 的值越大,那么并发数就越多,而并发数代表着服务器端同时处理的连接请求数量,也就意味服务端占用的连接数越多,这些链接会消耗内存资源以及 CPU 资源等。所以 RT 值越大系统资源占用越大,同时也意味着服务端的请求处理耗时较长。
但实际情况是,RT 值越小越好,比如在游戏中,至少做到 100ms 左右的响应才能达到最好的体验,对于电商系统来说,3s 左右的时间是能接受的,那么如何缩短 RT 的值呢?
按照 2/8 法则来推算 1000w 用户的访问量
继续回到最开始的问题,假设没有历史数据供我们参考,我们可以使用 2/8 法则来进行预估。
-
1000W 用户,每天来访问这个网站的用户占到 20%,也就是每天有 200W 用户来访问。
-
假设平均每个用户过来点击 50 次,那么总共的 PV=1 亿。
-
一天是 24 小时,根据 2/8 法则,每天大部分用户活跃的时间点集中在(24*0.2) 约等于 5 个小时以内,而大部分用户指的是(1 亿点击 * 80%)约等于 8000W(PV), 意味着在 5 个小时以内,大概会有 8000W 点击进来,也就是每秒大约有 4500(8000W/5 小时)个请求。
-
4500 只是一个平均数字。在这 5 个小时中,不可能请求是非常平均的,有可能会存在大量的用户集中访问(比如像淘宝这样的网站,日访问峰值的时间点集中在下午 14:00、以及晚上 21:00,其中 21:00 是一天中活跃的峰值),一般情况下访问峰值是平均访问请求的 3 倍到 4 倍左右(这个是经验值),我们按照 4 倍来计算。那么在这 5 个小时内有可能会出现每秒 18000 个请求的情况。也就是说,问题由原本的支撑 1000W 用户,变成了一个具体的问题,就是服务器端需要能够支撑每秒 18000 个请求(QPS=18000)
服务器压力预估
大概预估出了后端服务器需要支撑的最高并发的峰值之后,就需要从整个系统架构层面进行压力预估,然后配置合理的服务器数量和架构。既然是这样,那么首先需要知道一台服务器能够扛做多少的并发,那这个问题怎么去分析呢?我们的应用是部署在 Tomcat 上,所以需要从 Tomcat 本身的性能下手。
下面这个图表示 Tomcat 的工作原理,该图的说明如下。
-
LimitLatch 是连接控制器,它负责控制 Tomcat 能够同时处理的最大连接数,在 NIO/NIO2 的模式中,默认是 10000,如果是 APR/native,默认是 8192
-
Acceptor 是一个独立的线程,在 run 方法中,在 while 循环中调用 socket.accept 方法中接收客户端的连接请求,一旦有新的请求过来,accept 会返回一个 Channel 对象,接着把这个 Channel 对象交给 Poller 去处理。
Poller 的本质是一个 Selector ,它同样也实现了线程,Poller 在内部维护一个 Channel 数组,它在一个死循环里不断检测 Channel 的数据就绪状态,一旦有 Channel 可读,就生成一个 SocketProcessor 任务对象扔给 Executor 去处理
-
SocketProcessor 实现了 Runnable 接口,当线程池在执行 SocketProcessor 这个任务时,会通过 Http11Processor 去处理当前这个请求,Http11Processor 读取 Channel 的数据来生成 ServletRequest 对象。
-
Executor 就是线程池,负责运行 SocketProcessor 任务类, SocketProcessor 的 run 方法会调用 Http11Processor 来读取和解析请求数据。我们知道, Http11Processor 是应用层协议的封装,它会调用容器获得响应,再把响应通过 Channel 写出。
从这个图中可以得出,限制 Tomcat 请求数量的因素四个方面。
当前服务器系统资源
我想可能大家遇到过类似“Socket/File:Can’t open so many files”的异常,这个就是表示 Linux 系统中的文件句柄限制。
在 Linux 中,每一个 TCP 连接会占用一个文件描述符(fd),一旦文件描述符超过 Linux 系统当前的限制,就会提示这个错误。
我们可以通过下面这条命令来查看一个进程可以打开的文件数量
ulimit -a 或者 ulimit -n
复制代码
open files (-n) 1024 是 linux 操作系统对一个进程打开的文件句柄数量的限制(也包含打开的套接字数量)
这里只是对用户级别的限制,其实还有个是对系统的总限制,查看系统总线制:
cat /proc/sys/fs/file-max
复制代码
file-max 是设置系统所有进程一共可以打开的文件数量 。同时一些程序可以通过setrlimit
调用,设置每个进程的限制。如果得到大量使用完文件句柄的错误信息,是应该增加这个值。
当出现上述异常时,我们可以通过下面的方式来进行修改(针对单个进程的打开数量限制)
vi /etc/security/limits.conf
root soft nofile 65535
root hard nofile 65535
* soft nofile 65535
* hard nofile 65535
复制代码
-
*
代表所有用户、root
表示 root 用户。 -
noproc 表示最大进程数量
-
nofile 代表最大文件打开数量。
-
soft/hard,前者当达到阈值时,制作警告,后者会报错。
另外还要注意,要确保针对进程级别的文件打开数量反问是小于或者等于系统的总限制,否则,我们需要修改系统的总限制。
vi /proc/sys/fs/file-max
复制代码
TCP 连接对于系统资源最大的开销就是内存。
因为 tcp 连接归根结底需要双方接收和发送数据,那么就需要一个读缓冲区和写缓冲区,这两个 buffer 在 linux 下最小为 4096 字节,可通过 cat /proc/sys/net/ipv4/tcp_rmem 和 cat /proc/sys/net/ipv4/tcp_wmem 来查看。
所以,一个 tcp 连接最小占用内存为 4096+4096 = 8k,那么对于一个 8G 内存的机器,在不考虑其他限制下,最多支持的并发量为:8_1024_1024/8 约等于 100 万。此数字为纯理论上限数值,在实际中,由于 linux kernel 对一些资源的限制,加上程序的业务处理,所以,8G 内存是很难达到 100 万连接的,当然,我们也可以通过增加内存的方式增加并发量。
Tomcat 依赖的 JVM 的配置
我们知道 Tomcat 是 Java 程序,运行在 JVM 上,因此我们还需要对 JVM 做优化,才能更好的提升 Tomcat 的性能,简单带大家了解一下 JVM,如下图所示。
在 JVM 中,内存划分为堆、程序计数器、本地方发栈、方法区(元空间)、虚拟机栈。
堆空间说明
其中,堆内存是 JVM 内存中最大的一块区域,几乎所有的对象和数组都会被分配到堆内存中,它被所有线程共享。 堆空间被划分为新生代和老年代,新生代进一步划分为 Eden 和 Surivor 区,如下图所示。
新生代和老年代的比例是 1:2,也就是新生代会占 1/3 的堆空间,老年代会占 2/3 的堆空间。 另外,在新生代中,空间占比为 Eden:Surivor0:Surivor1=8:1:1 。 举个例子来说,如果 eden 区内存大小是 40M,那么两个 Survivor 区分别是占 5M,整个新生代就是 50M,然后计算出老年代的内存大小是 100M,也就是说堆空间的总内存大小是 150M。
可以通过 java -XX:PrintFlagsFinal -version 查看默认参数
uintx InitialSurvivorRatio = 8
uintx NewRatio = 2
InitialSurvivorRatio: 新生代 Eden/Survivor 空间的初始比例
NewRatio : Old 区/Young 区的内存比例
堆内存的具体工作原理是:
-
绝大部分的对象被创建之后,会保存在 Eden 区,当 Eden 区满了的时候,就会触发 YGC(Young GC),大部分对象会被回收掉,如果还有活着的对象,就拷贝到 Survivor0,这时 Eden 区被清空。
-
如果后续再次触发 YGC,活着的对象 Eden+Survivor0 中的对象拷贝到 Survivor1 区, 这时 Eden 和 Survivor0 都会被清空
-
接着再触发 YGC,Eden+Survivor1 中的对象会被拷贝到 Survivor0 区,一直这么循环,直到对象的年龄达到阈值,则放入到老年代。(之所以这么设计,是因为 Eden 区的大部分对象会被回收)
-
Survivor 区装不下的对象会直接进入到老年代
-
老年代满了,会触发 Full GC。
GC 标记-清除算法 在执行过程中暂停其他线程??
程序计数器
程序计数器是用来记录各个线程执行的字节码地址等,当线程发生上下文切换时,需要依靠这个来记住当前执行的位置,当下次恢复执行后要沿着上一次执行的位置继续执行。
方法区
方法区是逻辑上的概念,在 HotSpot 虚拟机的 1.8 版本中,它的具体实现就是元空间。
方法区主要用来存放已经被虚拟机加载的类相关信息,包括类元信息、运行时常量池、字符串常量池,类信息又包括类的版本、字段、方法、接口和父类信息等。
方法区和堆空间类似,它是一个共享内存区域,所以方法区是属于线程共享的。
本地方发栈和虚拟机栈
Java 虚拟机栈是线程私有的内存空间,当创建一个线程时,会在虚拟机中申请一个线程栈,用来保存方法的局部变量、操作数栈、动态链接方法等信息。每一个方法的调用都伴随这栈帧的入栈操作,当一个方法返回之后,就是栈帧的出栈操作。
本地方法栈和虚拟机栈类似,本地方法栈是用来管理本地方法的调用,也就是 native 方法。
JVM 内存应该怎么设置
了解了上述基本信息之后,那么 JVM 中内存应该如何设置呢?有哪些参数来设置?
最后
看完美团、字节、腾讯这三家的面试问题,是不是感觉问的特别多,可能咱们又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。
开篇有提及我可是足足背下了1000道题目,多少还是有点用的呢,我看了下,上面这些问题大部分都能从我背的题里找到的,所以今天给大家分享一下互联网工程师必备的面试1000题。
注意不论是我说的互联网面试1000题,还是后面提及的算法与数据结构、设计模式以及更多的Java学习笔记等,皆可分享给各位朋友
互联网工程师必备的面试1000题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。
a学习笔记等,皆可分享给各位朋友
[外链图片转存中…(img-c7NNEUPn-1714898732492)]
互联网工程师必备的面试1000题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。
[外链图片转存中…(img-g3S0LoCQ-1714898732492)]