dss加几号油合适夏季?,多少岁叫金婚银婚钻石婚

程序员进一线互联网大厂必知必会的高性能Java核心知识点!非常全

高性能Java核心知识概述

高性能

高性能(High Performance)指程序处理速度快,所占内存少,CPU占用率低。高性能的指标和高并发的指标紧密相关,想要提高性能,就要提高系统发并发能力,两者是相互捆绑在一起的。在做性能优化时,计算密集型和I/O密集型是有很大差别的,需要分开考虑。除此之外,还可以通过增加服务器的数量、内存等提升系统的并发能力,但不要浪费资源。

假设在两台拥有相同CPU和内存的服务器上部署两个项目,接受同等的并发数量,即分别以同等数量的请求(同等压力)调用两台服务器相同的HTTP接口,若第一台服务器所消耗的CPU、内存等硬件数值更低,则第一台服务器的性能优于第二台服务器。通常更高性能的服务器代表着更高的并发。

高性能与算法和GC息息相关,算法越快,GC消耗越小。另外,对于无用对象应及时删除,每次删除都可以留下更小的遗留占比。

例如,当前场景要取出User表的所有用户的数量,若先读取User表的全部数据,再通过List.size();获得行数长度,则速度肯定没有直接从MySQL中取count值快。影响程序性能的原因极多,例如:

• 因为I/O阻塞会让CPU闲置,导致CPU浪费。

• 在多线程间增加锁来保证同步,导致并行系统串行化。

• 创建、销毁、维护太多进程和线程,导致操作系统把资源浪费在调度上。

• 没有异步返回数据进行处理。

• 数据量过大,线程循环次数过多。

• 协议消耗资源过多。

• 未控制的慢请求、慢读取造成的并发不良。

高并发

一台服务器放在桌面上,它的CPU和内存的生产厂家及型号是确定的。但是代码(应用程序)放置在这台服务器中,其性能再优化也不可能超过服务器自身的承载能力。程序员可以靠各种设计手段和实现方式让这台服务器速度更快一些。例如,应用程序的开发自然是进程内的快于进程外的,进程外的快于服务器外的(服务器交互),多节点的集群承载力和可用性高于单节点的,异步多线程的设计优于同步的设计等。

因为应用程序不可能超过服务器自身的承载能力,所以每秒几万、几十万,甚至上亿数值的TPS完全没有参考意义(TPS为系统吞吐量,通常TPS数值越大,表示性能越高)。在性能测试过程中,如果不考虑最大返回时间和占比,只考虑TPS数值,则几乎没有参考意义。

高并发(High Concurrency)的本质指通过设计保证系统能够同时并行处理大量请求。在高并发性能测试时,需要综合参考以下因素才能得到相对真实的TPS数值。

• 最大返回时间和最小返回时间。最大返回时间不要设置得太大,尽可能减少最大返回时间,即程序中要着重设计超时时间,否则一次7000~8000秒没有结束的HTTP请求会直接拉低整体的TPS数值。

• 错误率。错误率相当于错误总数除以样本总数,错误率超过0.05%即为不合格。错误率可根据项目进行浮动,若错误率高于预期,则需无视此次性能测试结果,并进行优化和再测试。

• 返回时间(分位值)。90分位值通常为理想的最终值。服务器最后的10%、5%、1%和0.1%分位值的响应时间有可能几十万倍大于50分位值的响应时间,在性能测试中,通常以90分位值作为大量用户的响应结果。

• 服务器的CPU、I/O、带宽和内存。硬件配置越好,性能越高。

• 服务器地址。例如,当服务器都在阿里云上时,还要看是否在一个大区,地址越远,响应速度越慢。外网慢于内网,内网慢于同一大区。

• 在高压测试时服务器是反应速度缓慢,还是直接崩溃。若服务器崩溃,则无视此次性能测试结果,并进行优化和再测试。高并发项目要有削峰结果,即当并发超高、压力超大时,允许单台服务器返回速度变慢,但尽可能不要崩溃。在集群架构中,某个服务器崩溃之后通常有其他可以运行的服务器接管,并且自动对崩溃的服务器进行重启。

高可用

高可用(High Availability)通常用来描述一个系统在经过设计后,使停工时间减少,从而保证其服务的高度可用性。简单来说,在两台服务器中的一台崩溃之后,另一台仍然可以提供相应服务,即为高可用。

造成程序宕机的情况有很多,例如,CPU无法正常处理所有请求、内存溢出、停电导致服务器无法正常运行、正在运行的服务遭到渗透攻击、并发太高、程序不断异常,等等。

目前,很多企业都要求服务器的可用性达到五个9,即99.999%的时间都可以正常提供服务。按一年365天,每天24小时,每小时60分钟计算,可得出一年有365 × 24 × 60 = 525600分钟,它的0.001%就是5.256分钟,即每年最多允许有5.256分钟的宕机时间。通常高并发和高性能需要为高可用让步。

算法、GC与诊断工具

算法

“算法”(algorithm)一词得名于波斯数学家花拉子密。公元9世纪,这位数学家写过一本书,讨论用纸笔解决数学问题的技巧。书名为al-Jabrwa’l-Muqabala,其中,“al-jabr”就是后来“algebra”(代数)这个词的前身。不过,最早的数学算法早于花拉子密。在巴格达附近出土的4000年前的苏美尔人的泥板文献上,就刻有一幅长除法示意图。

但是,算法不仅限于数学。当按照食谱介绍烤面包时,食谱上的所有步骤就是一个算法。当按照图样编织毛衣时,这份图样就是一个算法。使用鹿角的末端连续精确地敲打,使石器形成锋利的刃的过程(这是制作精密石器的一个关键步骤),也遵循着一个算法。从石器时代开始,算法就已经是人类生活的一部分了。

——《算法之美》<美>布莱恩·克里斯汀;<美>汤姆·格里菲思 著

在Java编程中,大部分程序员所接触的算法与数学并不相关,一些数学题在工作上很难体现出意义,但并不是说算法就毫无意义了,相反,算法在Java程序优化的过程中有着举足轻重的地位。大多数时候,算法与GC和业务逻辑息息相关。例如,优化循环次数、优化业务逻辑、减少内存开销、减少从数据源处提取的数据量、每次GC都删除更多的垃圾、把同步线程换为异步线程、把多次线程开销优化为线程池等,以上优化皆属于算法。

在Java编程中,算法上的经验大多来自工作中的经验。例如,虽然某个接口增加了缓存,但是返回速度仍然很慢,此时就需要根据Arthas之类的诊断工具配合GC相关的内容,不断地优化自身的代码。如果无法优化算法,则先优化业务需求,再优化算法。

GC

在Java中,各版本所涵盖的GC(垃圾回收器)都不同,最著名的有三款,分别为CMS、G1和ZGC。

在JDK 1.3之前,垃圾回收是单线程回收的,并且会stop the world(下文简称为STW)。也就是说,在垃圾回收时,会暂停所有用户线程。由于其运行方式是单线程的,所以适合Client模式的应用和单CPU环境。串行的GC有两种,即Serial和SerialOld,一般会搭配使用。在年轻代使用Serial复制算法,在年老代使用Serial Old标记整理算法。客户端应用或者命令行程序可以通过-XX:+UseSerialGC开启上述回收模式。

G1(Garbage First)先结合OpenJDK源码分析重要算法(如SATB)和重要存储结构(如CSet、RSet、TLAB、PLAB、Card Table等),再梳理G1 GC的YoungGC和MixedGC的收集过程。GC的主要回收区域是年轻代、年老代和持久代。在JDK8之后,持久代被替换成元空间(Metaspace)。元空间会在普通的堆区上进行分配。为了提高垃圾回收效率,G1通常采用分代回收的方式,即对不同的回收区域使用不同的GC。在系统正常运行时,Full GC会触发整个堆的扫描和回收(这在年轻代中是比较频繁的)。在G1中,最好的优化状态是不断地调整分区空间,避免进行Full GC,以便大幅提高吞吐量。

CMS(Concurrent Mark Sweep)基于标记—删除算法,主要用于年老代,所以其关注点在于减少因垃圾回收导致的STW时间。对于重视服务响应速度的应用可以使用CMS。CMS是并发运行的,即垃圾回收线程可以和用户线程同时运行。

ZGC与G1和CMS的设计不同,ZGC是革命性的垃圾回收器。在JDK9之后,默认的垃圾回收器是G1,自JDK11之后,ZGC成为新的垃圾回收器,它取消了年轻代和年老代的设计模式,ZGC只把Page作为内存存储,支持最大4TB级堆内存,最长停顿时间为10ms、吞吐量最大不超过15%。ZGC只对内存数据进行标记,而不会单独放置在一个空间中,不会出现Full GC的情况。

jvmtop

jvmtop是一个轻量级的控制台程序,可用来监控机器上运行的所有Java虚拟机,它显示了很多JVM内部信息,如内存等,使用命令如下所示:

jstat

jstat可以查看堆内存各部分的使用量,以及加载类的数量,命令格式如下:


下面通过jstat查看年老代信息,即查看pid为123456的Java程序的每秒GC信息,如下所示:

在用jstat查看年老代信息后,部分显示内容说明如下所示:

• S0C:年轻代中第一个survivor(幸存者区)的容量(字节)。

• S1C:年轻代中第二个幸存者区的容量(字节)。

• S0U:年轻代中第一个幸存者区目前已使用空间(字节)。

• S1U:年轻代中第二个幸存者区目前已使用空间(字节)。

• EC:年轻代中Eden(伊甸园)的容量(字节)。

• EU:年轻代中伊甸园目前已使用空间(字节)。

• OC:年老代的容量(字节)。

OU:年老代目前已使用空间(字节)。

• PC:持久代的容量(字节)。

• PU:持久代目前已使用空间(字节)。

• YGC:从应用程序启动到采样时年轻代中的垃圾回收次数。

• YGCT:从应用程序启动到采样时年轻代的垃圾回收所用时间(s)。

• FGC:从应用程序启动到采样时年老代的(Full GC)垃圾回收次数。

• FGCT:从应用程序启动到采样时年老代的(Full GC)垃圾回收所用时间(s)。

• GCT:从应用程序启动到采样时垃圾回收所用的总时间(s)。

• NGCMN:年轻代的初始化大小(字节)。

• NGCMX:年轻代的最大容量(字节)。

• NGC:年轻代的当前容量(字节)。

• OGCMN:年老代的初始化大小(字节)。

• OGCMX:年老代的最大容量(字节)。

• OGC:年老代当前新生成的容量(字节)。

• PGCMN:持久代的初始化大小(字节)。

• PGCMX:持久代的最大容量(字节)。

• PGC:持久代当前新生成的容量(字节)。

• S0:年轻代中第一个幸存者区已使用的容量占当前容量的百分比。

• S1:年轻代中第二个幸存者区已使用的容量占当前容量的百分比。

• E:年轻代中伊甸园已使用的容量占当前容量的百分比。

• O:年老代中已使用的容量占当前容量的百分比。

• P:持久代中已使用的容量占当前容量的百分比。

• S0CMX:年轻代中第一个幸存者区的最大容量(字节)。

• S1CMX:年轻代中第二个幸存者区的最大容量(字节)。

• ECMX:年轻代中伊甸园的最大容量(字节)。

• DSS:当前需要幸存者区的容量(字节)(伊甸园已满)。

• TT:持有次数限制。

• MTT:最大持有次数限制。

Arthas

Arthas是阿里巴巴开源的Java诊断工具,它集成了jvmtop与jstat等绝大部分Java诊断工具,并进行了创新。当遇到以下类似问题而束手无策时,Arthas可以快速解决。

• 这个类是从哪个jar包加载的?为什么会报各种类相关的异常?

• 我改的代码为什么没有执行到?是没有提交,还是分支搞错了?

• 遇到问题无法在线上debug,难道只能通过加日志再重新发布吗?

• 线上遇到某个用户的数据处理有问题,但线上无法debug,而线下无法重现?

• 是否可以通过全局视角查看系统的运行状况?

• 是否可以监控JVM的实时运行状态?

• 如何快速定位应用的热点,生成火焰图?

Arthas支持JDK 6以上版本,支持Linux、Mac和Windows操作系统。它采用命令行交互模式,同时提供了丰富的Tab自动补全功能,可以方便地对问题进行定位和诊断。

Arthas包含大量命令,此处仅介绍部分与性能有关的命令。例如,通过sysprop命令可以查看当前Java程序中的所有System Properties信息,结果如图1-1所示。

通过thread命令可以查看当前Java程序中的所有线程信息,结果如图1-2所示。

通过trace命令可以查看方法的调用耗时,结果如图1-3所示。

通过monitor命令可以监控方法的实行进度及耗时,结果如图1-4所示。

除此之外,Arthas还可以查询最近5秒CPU使用率最高的线程、获取函数调用栈、反编译并直接在jar包处修改正在执行的代码、跟踪所有的Filter函数、动态修改当前运行程序的Logger日志等级、获取当前运行程序的static变量数值等,是Java调优中一个功能强大的工具包。

分离术

1. 动静分离

“动静”指动态资源和静态资源。动态资源通常指从MySQL之类的数据源中取出的数据,静态资源通常指各种.git、.jpg、.html等资源。

动静分离指在Web服务器架构中,将动态资源与静态资源(或者将动态内容接口和静态内容接口)分成不同系统访问的架构设计方法,进而提升整个服务访问性能和可维护性。

2. 前后端分离

前后端分离的核心思想是前端HTML页面通过AJAX调用后端的RESTful API接口,并使用JSON数据进行交互。

3. 主从分离与读写分离

主从分离通常指数据库的主从分离,有些用Java编写的程序因业务需要也需要做成主从分离的结构。

读写分离的基本原理就是让主数据库(写库)处理事务性操作(如增、改、删等),让从数据库(读库)处理查询操作。数据库复制可以把事务性操作导致的写库变更同步到读库。以SQL为例,写库负责写数据、读数据,读库仅负责读数据。每次写库的写数据操作都需要同步更新到读库。写库只有一个,读库可以有多个,它们之间采用日志同步的方式实现写库和多个读库的数据同步。

在代码中可以通过Spring的AOP达到读写分离的架构要求。

基准测试

基准测试的概念

在安装、部署MySQL之后,应先进行基准测试,在应用程序开发之后,再对应用程序整合MySQL部分进行性能测试。基准测试指通过科学的测试方法、测试工具和测试系统,对一类测试对象的某项性能指标进行定量的和可对比的测试。

基准测试的实际用途

(1)通常基准测试的值为服务器性能指标的最大值,在实际编程后,服务器性能指标会大概率低于该值,但在后续的性能测试中通常以该值作为参考指标,以便了解当前应用程序对性能的影响。

(2)识别系统或环境的配置变更对性能带来的影响。

(3)识别不同硬件或不同硬件集成的配置变更对性能带来的影响。

(4)为系统优化前后的性能提升或下降提供参考指标。

(5)观察系统的整体性能趋势与拐点,及早识别系统性能风险。

基准测试与一般性能测试的区别

(1)实际用途不同。性能基准测试大多为服务器裸机的参考指标,而一般性能测试指在服务器上部署应用程序之后的综合测试。

(2)测试逻辑不同。通常性能基准测试只使用单一方式增加压力,测试服务器I/O、带宽、线程、响应时间等基本指标。而性能测试可以通过不同协议进行场景化逻辑测试,即先调用某HTTP接口之后得到相应参数,再根据该参数调用下一个HTTP接口,这两个HTTP接口可能需要调用缓存中的数据,从而得到每个接口和总体场景的响应时间、错误率、TPS值等参数。

性能测试

性能测试的目的

性能测试的目的是验证软件系统是否能够实现用户提出的性能指标,同时发现软件系统中存在的性能瓶颈,进而优化软件。性能测试的目的有评估系统极限并发的能力、判断系统是否有内存溢出与高可用失效等相关现象,以及在长时间高压下性能测试服务器因“疲劳”所产生的其他现象。

性能测试着重观察的指标

Web性能测试需着重观察的聚合报告结果参数如表1-1所示。


性能测试存在的误区

在性能测试开始之前,应当着重考虑服务器的硬件配置。各厂商通常表述自己的数据库可以达到多少TPS,但很少说明自己服务器的硬件配置是怎样的。实际上,计算机的硬件配置会极大地影响TPS结果,因此厂商推荐的数据库和程序服务只能作为参考。在进入生产环境之前,应当通过JMeter或基准测试对数据库进行相应的测试。

除硬件配置外,在性能测试准备期间还应着重注意当前带宽是否能够满足性能测试的需要,以免性能测试只返回带宽上限的请求数目,而无法测试出应用程序的极限。在带宽不足的情况下,测试结果是无效的,需视为因带宽不足而导致的结果不正确。

如果部分性能测试结果的错误率过高,则此次性能测试结果应当作废,需视为因错误率过高而导致的结果不正确。因为一旦出现异常与错误,则接口响应时间将是一个无法确定的值,即有些延时太高的线程无法返回,导致整体承载量下降。通常当遇到错误率过高的程序时,应当先进行优化,再重新进行性能测试。

性能测试的平均返回时间应仅作为参考,在生产环境中,95百分位表示大多数正常用户的响应时间,99百分位可视为部分较卡用户的响应时间。不要以平均值作为普通用户的响应时间。

性能测试的最大返回时间通常被视作服务器的TimeOut时间,若该时间过长,则需要对程序进行优化,在限制TimeOut时间后再进行测试,否则性能测试结果的参考性不高。线程在没有被服务器返回的情况下,十分消耗服务器的CPU与内存。与此同时,若中位数或90百分位的用户响应时间过长,则应当对应用程序进行优化。例如,对于HTTP接口来说,5秒以上即算过长的响应时间。若是高并发应用程序,则响应时间应当更少。

如果性能测试的最小时间为毫秒级,那么该数据通常是作为缓存存在的。如果性能测试的平均时间接近最小值,则该测试结果需要作废,因为这很可能是服务器直接对数据进行了返回,并没有真正地进入代码与业务逻辑。此处需要根据系统的特性进行斟酌。

在性能测试中,通常有压力机与被测试机两种类型的机器。由于性能测试工具与脚本同样消耗性能,因此在实际工作中,可能出现多台压力机同时压测一台被测试机的情况,此时需要注意压力机自身的CPU与内存不要处于“爆表”的状态,否则该测试结果应当作废,需视为因压力机无法正常运行而导致的结果不正确。

性能测试应涵盖的内容

下面以登录场景为例,通常性能测试应至少包含以下测试报告:

• 在模拟生产环境的同时,95百分位的用户的登录响应时间是否小于N秒,N为服务方提供的值。例如,单次登录时间超过N秒则将视为系统高并发能力不足,需查看服务器配置及代码,以免由于登录时间过长而影响用户体验。诸如此类验证响应时间的接口,都需要按照百分位的方式进行判断,即绝大部分用户的体验是否正常。另外,对响应时间需要有一个预期,即某系统达到什么样的响应时间则视为合格。因为系统的复杂度和使用场景不同,所以预期也不同。一般来说,单个用户的登录时间不应超过2秒;打开App时,App初始化时间不应超过3秒;页面跳转时间不应超过1秒;跳转下一页的时间不应超过0.5秒;在搜索相关数据时,结果返回时间不应超过0.5秒。

• 在高并发场景下,用户登录是否请求了过多的接口。例如,在登录之后,需要请求N个接口才能展示整体页面,如果是在高并发情况下,则用户体验将会很差。如果在用户登录时或用户登录后需要请求过多的接口,则需要进行优化。例如,对于一个页面可以分别读取数据并部分展示,而不是在全部数据读取完之后才打开页面;在渲染页面时是否有额外的资源消耗;在返回数据时是否有无用字段,是否可对接口进行精简、缓存等。

• 用户登录时是否包含监控的功能,并且在高并发场景下监控仍然能够正常响应,包括用户行为监控、性能监控和服务端硬件监控等。

• 在性能测试时需要进行高并发下的慢连接、慢读取、慢请求等安全测试,以保证在任何情况下都不会因客户端的性能问题而影响服务器的性能。

例如,某用户使用App通过WebSocket协议连接Java服务器,在Java服务器主动推送数据后,由于App网络较差无法正常收到数据,而使Java服务器内存溢出导致崩溃。此时需要使用JMeter+Fiddler组合测试,即弱网下的高并发测试。

• 长时间大量用户连续登录和退出是否会引发内存溢出、缓存失效或穿透缓存等问题。

• 连续使用虚假用户进行登录,是否被有效拦截,是否会穿透缓存。

业务测试

业务测试需关注的点如下:

• 分页处理技术:比如,在单击加载更多之后,接口是返回重复数据,还是返回无效字段?

• 数据显示是否完整:尤其是最后一页数据,是否显示完全。

• 页面上展示排序的方式:是由后台服务器负责排序,还是由前台JavaScript负责排序,这里着重查看由哪里分担排序的压力。

• 页面跳转是否正常:尤其是当携带了session或cookie等信息时,查看页面跳转是否正常。

• 异常出现情况:是否打印了过多无用的堆栈信息。如果是,则需要简化堆栈信息。此时,App与Web应当跳转到适当的页面,这样既不会影响用户体验,也不会让用户看到错误的堆栈信息。

• 程序是否可逆:任何程序都需要进行可逆性的操作,即在增加数据后可以删除数据,在删除数据后可以回滚。

• 日志分割:日志是否有效,以及日志是否按日期及大小进行分割,以便提取日志。

• 日志可读性:日志是否存储了有效信息,以便查找线上问题。

• 程序是否包含灾备处理:当前数据库如果被渗透,是否可以快速使用备份数据恢复生产。

• 程序是否包含高可用处理:当由于被渗透或高并发等导致程序崩溃时,是否仍然可以正常提供服务。

• 断网与弱网处理:当断网或弱网时是否包含超时约定,或者为弱网用户提供拒绝服务的约定。

• 数据处理:在数据量较大的情况下是否增加压缩机制,以保证传输速度及响应速度,减少用户使用流量,减少服务器压力。

• 脱敏机制:对用户密码和手机号码等是否增加了脱敏机制,以防止用户信息被渗透。

• 数据的及时性:在Web控制台处修改数据时,App是否能够及时有效地更新数据。

单元测试

单元测试需关注的点如下:

• 单元测试是持续集成的,即在某次改动之后,之前的单元测试仍然可以使用,本次改动的代码同样需要放置在单元测试中。

• 单元测试不应依赖其他单元测试所返回的数据,单元与单元之间应相对独立。

• 单元测试不应过度依赖外部不确定的资源,例如数据库、外部接口等,最好能够随时运行单元测试,随时知道结果。部分外置接口和资源存在不确定性,可能导致自动化测试断言出现异常,需要费心调整。

• 单元测试的结果应当是真实的,并且有意义,不要为了通过测试而做单元测试。

• 单元测试应在代码覆盖率高的基础上,通过持续集成、自动化测试等,更加方便地了解项目,以及了解新增加的代码对当前项目的影响。

• 在实际工作中单元测试代码通常由开发人员编写,测试人员与开发人员都会对其进行使用,不要把这部分的测试任务完全交由测试人员。基础的单元测试、性能测试与渗透测试应当由开发人员自身先审查。

• 单元测试的代码重要程度与源码相同,不要因为单元测试放在了不同的文件里而去随意命名,这会导致许多测试在不加管理之后变得完全看不懂。

下面介绍单元测试用例的设计方法。

等价类划分

等价类划分指的是一种典型的、重要的黑盒测试方法,它可以解决如何选择适当的数据子集来代表整个数据集的问题。它通过降低测试的数目实现“合理的”覆盖,以此发现更多的软件缺陷,在统计好数据后,再对软件进行改进升级。

等价类划分的方法是把程序所有可能的输入数据(有效的和无效的)划分成若干等价类,然后从每部分中选取具有代表性的数据当作测试用例进行合理的分类。测试用例由有效等价类和无效等价类的代表组成,从而保证测试用例的完整性和代表性。

利用这一方法设计的测试用例可以不考虑程序的内部结构。以需求规格说明书为依据,选择适当的典型子集,认真分析和推敲说明书中的各项需求,特别是功能需求,要尽可能多地发现错误。等价类划分是一种需要系统性输入测试条件的方法。

由于等价类划分是在需求规格说明书的基础上划分数据的,不仅可以用来确定测试用例中数据输入、输出的精确取值范围,还可以用来准备中间值、状态和与时间相关的数据及接口参数等,所以在系统测试、集成测试和组件测试中,在有明确的条件和限制的情况下,利用等价类划分可以设计出完备的测试用例。这种方法可以减少设计一些不必要的测试用例,因为这种测试用例一般使用相同的等价类数据,使测试对象能够得到同样的反应和行为。

等价类划分可分为两个主要步骤,即划分等价类型和设计测试用例。有效等价类数据集的示例如下:

• 终端用户输入的命令。

• 与最终用户交互的系统提示。

• 接受的用户文件的名称。

• 提供初始化值和边界等。

• 提供格式化输出数据的命令。

• 图形模式(比如在鼠标单击时)提供的数据。

• 失败时显示的消息。

无效等价类数据集的示例如下:

• 在一个不正确的地方提供值。

• 验证边界值。

• 验证外部边界值。

• 用户输入的命令。

• 最终用户与系统交互的提示。

• 验证与边界和外部边界值的数值数据。

等价类划分的示例如下:

• 按区间划分。

• 按数值划分。

• 按数值集合划分。

• 按限制条件或规划划分。

• 按处理方式划分。

边界值分析

边界值分析就是对输入或输出的边界值进行测试的一种黑盒测试方法。

通常来说,边界值分析是对等价类划分的补充,在这种情况下,其测试用例来自等价类的边界。

实际上,大量的错误都发生在输入或输出范围的边界上,而不是发生在输入或输出范围的内部。因此针对各种边界情况设计测试用例,可以查出更多的错误。

与等价类划分的区别如下:

• 边界值分析不是从某等价类中随便挑一个作为代表,而是等价类的每条边界都要作为测试条件。

• 边界值分析不仅要考虑输入条件,还要考虑输出空间产生的测试情况。

边界值分析的示例如下所示:

• int、long等数值类型的边界。

• string最大长度或最小长度的边界。

• 数据返回第一行和最后一行的边界。

• 数组元素第一行和最后一行的边界。

• 屏幕像素点最上方、最下方、最左方、最右方的边界。

• 做除法后无限循环的最后一位是否限制。

错误推测法

错误推测法指在测试程序时,可以根据经验或直觉推测程序中可能存在的各种错误,从而有针对性地编写检查这些错误的测试用例的方法。错误推测法的示例如下:

• 姓名处是否可以输入空白字符串或null等。

• 手机号、身份证号的正确性验证。

• 性别、年龄处是否可以输入不合法字符。

• 正常程序只能登录一个账号,当前程序是否可以打开多个页面分别登录不同的账号。

• 手机和Web在秒杀系统高并发的设计下是否只有一个终端可以进行抢购。

• Web与App是否构建了良好的防抓包、防爬虫处理。

除此之外,单元测试用例的设计方法还有因果图法和正交表分析法。因果图与流程图相似,它以图解的方式表示输入的各种组合关系,从而设计相应的测试用例。正交表分析法指将测试用例的影响因子制作成二维表结构的正交表。

数据库概述

数据库是“按照数据结构来组织、存储和管理数据的仓库”,是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。

数据库中的数据以一定格式存储在一起,并且能与多个用户共享,数据之间有尽可能小的冗余度。数据库可被视为电子化的文件柜——存储电子文件的场所,用户可以对文件中的数据进行新增、查询、更新、删除等操作。

数据库与应用程序彼此独立,在制作应用程序时,数据库的性能上限在某种程度上代表了应用程序的性能上限,因此对于数据库来说,基准测试、SQL与索引的优化、主从复制,以及分表等工作都是重中之重。

• 基准测试可以测试出MySQL服务器的指标极限,在应用程序制作完成之后,越接近基准测试,说明程序在代码上的可优化空间越少。

• SQL与索引的优化将大幅地提高MySQL的查询速度。

• 主从复制主要用来解决MySQL的单台性能瓶颈问题,通过多台服务器可以平摊写入或读取的压力,让MySQL服务器的整体服务性能提升数倍。

• 分表既可以解决MySQL大表查询速度过慢的问题(单靠索引无法解决),也可以单独存储历史性数据和冗余性数据,不会因历史性、冗余性的数据影响查询速度。

数据库分类

数据库主要分为关系数据库和非关系数据库两种。关系数据库指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,以便于用户理解。关系数据库中的行和列被称为表,一组表组成了数据库。用户通过查询来检索数据库中的数据。关系模型可以简单理解为二维表格模型,一个关系数据库就是由二维表格及其之间的关系组成的一个数据组织。目前,最常用的关系数据库有Oracle、MySQL、DB2和SQL Server。

最早的Oracle版本是1979年夏季发布的。最流行的Oracle版本为Oracle11g,是2007年11月发布的。

SQL Server最初由Microsoft、Sybase和Ashton-Tate三家公司共同开发,于1988年推出第一个OS/2版本。

MySQL是1996年发布的,开始只面向一小拨人,相当于内部发布。1996年10月,MySQL 3.11.1发布(MySQL没有2.x版本),最开始只提供Solaris下的二进制版本。一个月后,Linux版本出现了。在接下来的两年里,MySQL被依次移植到各个平台。由于MySQL是免费的,并且可以在Linux系统上运行,所以热度逐渐超过了Oracle和SQL Server。

非关系数据库又称为NoSQL数据库,随着互联网Web 2.0网站的兴起,传统的关系数据库在处理Web 2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站时已经显得力不从心,出现了很多难以克服的问题,而非关系的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合和多重数据种类带来的挑战,尤其是大数据应用难题。因为非关系数据库的性能远高于关系数据库,所以通常将非关系数据库作为目前应用程序的缓存,让系统的响应速度更快一些,目前非关系数据库最常用的分别是Redis和MongoDB。

• Redis是2009年正式发布的,主要以键值对作为存储结构对数据进行存储。

• MongoDB最初于2007年开发,主要以JSON作为存储结构对数据进行存储。

由于Redis更新换代较为迅速,集群版更加稳定,不仅免费,而且有使用简单、并发可观等优点,所以一直占据着NoSQL数据库的市场。

在应用程序规模不断扩大的今天,网络应用程序、接口、性能指标、试用场景等日新月异,过去的数据库已无法满足大数据、人工智能等方面的需要,因此NoSQL衍生出了四种新类型,如表1-2所示。

值得注意的是,虽然在项目中经常使用部分NoSQL数据库作为缓存,但是NoSQL数据库并不代表缓存,所以不要混淆NoSQL数据库和缓存的概念,后续章节会详细介绍缓存。除上述市场上常见的数据库及其存储方式外,其他特殊的存储方式如表1-3所示。

除Hadoop生态圈、Spark生态圈、数据挖掘系列、数据仓储系列、大规模并行数据库系列、数据集成系列等所涉及的存储外,大部分应用程序使用的数据库和存储方式都介绍到了。用表1-1与表1-2的存储方式可以满足绝大部分新闻网站、官网、游戏、电商、社交、数据平台(HTTP API)、医疗管理系统(HIS)、股票分析系统、理财系统、财务系统、管理系统等应用场景的需要。

时序性数据库是一种较为特殊的数据库,它使用了类SQL语句。例如,InfluxDB中使用的InfluxSQL与SQL语句几乎没有差别,其存储格式仍为二维表、非文档结构或键值对结构,所以并没有把InfluxDB放到表1-2中。但是几乎不可能对InfluxDB数据库做多表联查、一对一、一对多等相关查询操作,因为时序性数据库的并发极大,实时响应速度极快,该数据库把一切性能都放在了新增、查询、实时响应等场景上。当数据出现错误时,应尽可能按照Time时间段删除数据。

另外,SQLite虽然同样作为新关系数据库存在,但是过于微型,还原、备份等能力远不如MySQL、Oracle等常规关系数据库,所以通常不作为关系数据库使用。由于SQLite存储空间与本身体积都较小,所以SQLite活跃在移动端,通常作为移动端的缓存而存在,有时也会作为应用程序的缓存而存在。

Elasticsearch 在 官 方 文 档 中 属 于 分 布 式 RESTful 响 应 的 搜 索 引 擎(Distributed RESTful Search Engine),但Elasticsearch的存储格式又是文档式的,所以Elasticsearch既可归属于文档数据库,又可归属于搜索引擎。

上述内容属于概念类知识,在学习过程中不要过多纠结归属类型,只要擅长在不同场景的架构中使用不同的存储方式达到理想的业务与性能目标即可 。 目 前 市 场 上 最 常 用 的 仍 然 是 MySQL 、 Oracle 、 Redis 、 MongoDB 、Elasticsearch和FastDFS等一系列“老功臣”。

1. 关系数据库与非关系数据库的区别

关系数据库与非关系数据库的区别如下:

数据存储的方式不同

关系数据天然就是二维表,因此适合存储在数据表的行和列中。数据表不仅可以彼此关联协作存储,也很容易提取数据。与其相反,非关系数据是大块组合在一起的,因而不适合存储在数据表的行和列中。

数据存储的地址不同

关系数据库通常直接存储进二进制文件中。非关系数据库根据自身配置不同,有的只存储在内存中,有的暂存在缓存中。当非关系数据库存储在内存中时,可以达到响应速度更快的目的,但是也更容易丢失数据,一旦重启非关系数据库,则相当于丢失了所有内容。当非关系数据库暂存在缓存中时,它会定期把数据存储到二进制文件中,即便重启非关系数据库,也可保证部分数据不会丢失。

对事务性的支持不同

如果数据操作需要高事务性,那么关系数据库(SQL数据库)是最佳选择。SQL数据库支持对事务原子性细粒度的控制,并且易于回滚事务。虽然非关系数据库也可以使用事务操作,但在稳定性方面无法和关系数据库相比。

非关系数据库真正的价值是在操作的扩展性和大数据处理方面。

总而言之,在计算机性能选择上,有得必有失,在增加了响应速度和并发的情况下,非关系数据库和搜索引擎通常牺牲了一部分的数据安全性。

许多高级的Oracle DBA开玩笑称Oracle为“只要磁盘没有物理损坏,任何Oracle存储的数据都可以从磁盘中重新拿回来,甚至有些轻微的物理损坏仍然可以拿到其中一部分数据。”虽然只是一句玩笑话,但这确实是Oracle在一次次互联网浪潮的冲击之下仍然屹立不倒的原因之一,而这也恰恰是大部分非关系数据库无法与之相比的地方之一。

2. 关系数据库等级

大部分在校学生和工作2~3年的程序员经常会提出这样一个问题,即“在工作中对关系数据库需要掌握到什么程度?”

这是一个比较常见的问题,但是因为岗位不同,使用的关系数据库不同,所以对关系数据库需要掌握的程度也不同。笔者按照“游戏等级”的方式,以MySQL为例,划分了不同等级下对MySQL掌握的熟练度,如表1-4所示。

3. 常用的MySQL工具

(1)常用的性能基准测试工具有sysbench和mysqlslap。

(2)对应用程序进行性能测试的常用工具是JMeter。

(3)对MySQL和服务器CPU等信息进行性能监控架构可选择Grafana +InfluxDB +Telegraf架构或Prometheus + Grafana架构。

(4)集群可选择MyCAT。

(5)相关统计可选择percona-toolkit。

(6)慢SQL查询可选择mysqldumpslow。

(7)分布式事务可选择Fescar(Seata)。

(8)事务处理测试可选择HammerDB。

(9)快速备份与恢复可选择mysqlhotcopy。

(10)常规备份与恢复可选择mysqldump。

(11)二进制日志(binlog)解析工具可选择Maxwell。

数据库测试的具体内容

(1)初始程序架构时,在设计数据结构与表结构之后,应对设计的表结构与数据结构进行基准性能测试,得到该套结构的基准信息。

(2)在对数据库进行主从复制、MyCAT集群等优化之后,需要进行适当压力的性能测试,以保证集群化后,MySQL单节点性能没有被降低过多。

(3)在编码结束之后应对每条可能执行的SQL语句执行计划解读,确保执行语句中不存在全表索引之类的操作。如果包含全表索引,则需增加索引或优化SQL语句。

(4)应当对数据库做业务存储量测试,即测试当存储的数据量不同时,应用程序的返回时间为多少。此测试通常以应用程序作为入口。

(5)需要对数据库做疲劳测试,在应用程序运行过程中,是否因运行时间过长而出现数据库内存泄漏的情况。

(6)应当对数据库做灾备测试,即当主从复制或相关集群架构部署结束时,需测试断网、断电情况下是否会进行正常的灾备处理与响应服务。

(7)应当对数据库做安全测试,即账号、密码、权限、防火墙、弱密码、脱敏等相关内容是否设计得体,除防止别有用心的人渗透外,是否可以防止当前用户误操作导致数据丢失等情况。

缓存的核心知识

缓存是为了减少数据库和服务器压力而产生的,在应用层编程时需主要考虑以下几种情况:

• 客户端缓存。

• 服务端缓存。

• 网络缓存(CDN缓存)。

客户端缓存负责减轻服务端的存储和频繁的数据请求等压力。例如,在QQ初始阶段,只有“会员”才可以把QQ表情存储在“云端”之上,因为腾讯内部并没有庞大的存储系统存储大量的QQ表情。虽然现在腾讯已经取消了只有“会员”才可以存储QQ表情的限制,但是大部分QQ表情仍然默认存储在本地客户端。客户端缓存大致可分为以下几种:

• 客户端本地文件缓存,包括图片、.txt文件、.doc文件等。

• 客户端本地HTTP、cookie等浏览器缓存。

• 客户端注册表。

• 客户端微型数据库(SQLite)。

• 客户端本地计算机内存。

服务端缓存主要是为了减少数据库压力和外部服务接口的压力,这也是实际编程中最常用的手段。除减少数据库的压力外,缓存返回数据的响应速度比数据库要快。另外,尽可能不调用外部接口,因为外部接口无论WebSocket、WebService,还是HTTP,其响应速度都是不可控的。如果外部接口响应时间过长,也会影响自身性能。服务端缓存大致分为以下几种:

• 容器缓存,如Tomcat、Nginx、JBoss、Servlet等。

• 中 间 件 缓 存 , 如 MongoDB 、 Elasticsearch 、 Redis 、 RocketMQ 、Kafka、ZooKeeper等。

• JDK缓存,如磁盘缓存、堆内缓存、堆外缓存等。

• 页面静态化缓存,如FreeMaker、Thymeleaf等。

• 文件管理,如FastDFS等。

缓存的命中率

缓存的命中率指的是“缓存查询的次数”与“总查询次数”的比值。在多级缓存下,可以调研每一级缓存的命中率,以便调整代码。若某缓存命中率过低,则很可能是缓存穿透问题。

缓存回收方式

• 基于时间:当某缓存超过生存时间时,则进行缓存回收。或者当某缓存最后被访问后超过某时间仍然没有被访问,则进行缓存回收。

• 基于空间:当缓存超过某大小时,则进行缓存回收。

• 基于容量:当缓存超过某存储条数时,则进行缓存回收。

• 基于引用:软引用和弱引用缓存会在JVM堆内存不足时进行缓存回收。

缓存回收策略

• 先进先出(First In First Out,FIFO):一种简单的淘汰策略,缓存对象以队列的形式存在,如果空间不足,就释放队列头部的(先缓存)对象,一般用链表实现。

• 最近最久未使用(Least Recently Used,LRU):是根据访问的时间先后进行淘汰的,如果空间不足,就释放最久没有被访问的对象(上次访问时间最早的对象)。

• 最近最少使用(Least Frequently Used,LFU):根据最近访问的频率进行淘汰,如果空间不足,就释放最近访问频率最低的对象。

缓存的设计模式

(1)Cache Aside模式:首先读取缓存中的数据,若缓存没有命中,则读取DB。当DB需要更新时,直接删掉缓存中的数据。由于实现简单,因此是最常用的一种设计模式,适用于读操作多的情况。

(2)Read/Write through模式:在读取时先到缓存中查询数据是否存在。如果存在,则直接返回。如果不存在,则由缓存组件负责从数据库中同步加载数据,此数据永不过期。在写入时,先查询要写入的数据在缓存中是否存在。如果存在。则更新缓存中的数据,并且由缓存组件把数据同步更新到数据库中。Read/Write through模式初步屏蔽了底层数据库操作,但是当把数据从缓存组件写入DB时,有可能出现异常无法正确写入的情况。因而需要谨慎记录时间戳,以便跟踪维护处理数据。该方案适合对持久性要求较低的业务场景。

( 3 ) Write Behind Caching ( Write Back ) 模 式 : Write BehindCaching模式属于Read/Write through模式的进阶版,完全不考虑DB,增删改查全部通过缓存进行处理。如果读取不到数据,则直接认为该数据不存在,服务器会定期把缓存中的数据存储到DB中。一般高并发应用程序最常用的是Write Behind Caching设计模式,它是性能最好的设计模式,但是实现较为复杂,一旦服务器宕机则有可能导致大量数据丢失。

缓存测试应涵盖的内容

(1)当前程序是否有可能出现缓存穿透、缓存击穿、缓存雪崩等常见问题。

(2)缓存是否设置了最大位数及时间等功能,是否会出现内存溢出的现象。

(3)缓存能够节省各数据源多少比重的读取,例如进程内缓存节省了多少读取Redis的比重,Redis缓存节省了多少读取磁盘缓存的比重,磁盘缓存节省了多少读取MySQL的比重。

(4)App在无网或弱网环境下,是否可以正常打开及使用。例如网易云音乐在没有网络的情况下可以听一些本地缓存的歌曲。

(5)App在弱网转正常网络之后,缓存是否能被正常覆盖。

(6)各级缓存与数据库是否能够保持数据一致性,是否包含脏读、不可重复读等相关问题。

(7)缓存是否能够被手动删除或刷新,若遇到紧急状况是否能够进行可

逆性操作。

(8)缓存的回收策略、回收方式等内容是否正常生效。

实战:秒杀系统设计方案

秒杀系统设计要解决的问题如下:

• 突发性大量接口请求导致服务器高负载,此时需要用限流和削峰的方案进行处理。

• 如果突然增加的带宽超过服务商提供的带宽上限,则要注意数据传输的完整性,即从客户端向服务器传输数据时即使速度缓慢,也要保证数据的完整性,以免数据丢失导致相应的错误,此时需要用队列及分布式锁等方案进行处理。• 秒杀时应通过减库存操作维持数据的一致性,以免造成重复下单(超买/超卖)、库存不足等现象。此时需要用网关及队列等方案进行处理。

• 在秒杀之前按钮应为灰色,之后在不刷新页面的情况下将按钮点亮。

此处尽量隐藏URL,并对通信信息进行加密处理,以限制各种脚本请求,尽可能按F12键后查看不到各种地址及相关信息。

• 控制刷新页面,当用户即将参与秒杀时通常会不断按F5键刷新页面,重新加载页面同样会请求接口,应减少此种接口的请求,并将部分数据缓存至客户端,减轻服务器的压力。

秒杀系统需要达到限流、削峰、异步处理、高可用、缓存、可扩展等要求,具体如下:

• 限流:鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。常见的限流有单一端口登录(同一账号只能单一端口登录,例如App与Web只允许一个端口正在登录)、只有登录账号才能参与秒杀、当单击按钮次数过多时应限制单击次数,例如,每个账号每秒只能单击3次等不同的限流方案。

• 削峰:因为秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会出现瞬间峰值。高峰值流量是压垮系统的主要原因之一,如何把瞬间的高流量转变成一段时间的平稳的流量是设计秒杀系统很重要的思路。对流量进行削峰的解决方案是用消息队列缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。消息队列中间件主要解决应用耦合、异步消息、流量削峰等问题。常用的消息队列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ和RocketMQ等。削峰不是一次性可以解决的方案,而是要层层削峰,每一层都把压力降到最小,再传输给下一层,这样才能接受更大的压力与并发。

• 异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。异步处理的设计不仅可以削峰,还可以减轻对缓存、数据库和I/O的压力。

45• 高可用:所有服务器无论应用层还是数据层都要达到高可用的标准,即任何一台服务器宕机都可由其他服务器暂时替代,并通过自动或手动的方式迅速重启服务器,保证用户几乎感受不到服务器宕机。

• 缓存:秒杀系统最大的瓶颈一般都是数据库读写。由于数据库读写属于磁盘I/O,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,则会极大地提升效率。

• 可扩展:这里讲的可扩展指一旦性能无法支撑当前并发,则可以迅速通过提升服务器性能或快速平滑地增加集群服务器的方式,顶住当前高峰压力。当压力下降时,再通过自动或手动的方式,平滑地卸下集群内部的服务器。

总结——业务、性能、编程、架构相辅相成

业务、性能、编程、架构四者相辅相成,从不是单独存在的。应用程序的架构与编码是为业务服务的,但这并不代表编码与架构方面需要无条件满足业务,当某些业务的实现逻辑只改变很少的一个点,但却能承载更多的并发时,业务就需要给性能做出一定的让步。

无论黑盒测试还是白盒测试,都需要由负责编码的人员进行辅助,否则得出的结论可能是没有任何意义的。在测试之前要明确本次测试的原因。例如,此次测试是为了验证当前应用程序的最大并发是多少,或是为了优化程序CPU和内存的使用量,或是为了了解应用程序在某用户量进行操作时对数据库的压力是多少,或是为了了解应用程序是否包含内存溢出,是否会出现宕机,或是为了了解Linux内核参数、MySQL配置参数、Nginx配置参数等内容是否需要进行更改等。性能测试给予应用程序的压力,绝不是以给应用程序“压迫致死”作为目标,而是为了测试出期望的结果,达到更了解当前程序或优化当前程序的目的。

当性能测试得出结论时,既可能需要对架构进行优化,也可能对代码进行优化,在实践中,需要根据人力、服务器成本等采取不同的方案。

后面列举了一些常见的架构,由于篇幅有限,知识点众多,所以没有为每种架构的每个技术都以步骤的形式进行体现。总体来说,Java程序的性能优化可分为横向优化与纵向优化两种:

横向优化即通过负载均衡等,增加服务器来提升整体服务的并发性能。

另外,在优化性能时要不断对当前服务器的响应情况进行测试与监控。

纵向优化即通过优化算法,或者通过FastDFS和Redis等中间件纵向增加缓存层,或通过优化SQL语句、更换协议等方式减少CPU与内存的开销,提升单台Java应用程序的并发性能。

本文给大家讲解的内容是高性能Java核心知识概述

  • 下文给大家讲解的是为MySQL填充亿级数据

2024-04-26

后面没有了,返回>>电动车百科