秒杀笔记 —— 性能优化

秒杀笔记 —— 性能优化

主要介绍下服务端的性能优化技巧。

影响性能的因素

首先要认识什么是“性能”,服务设备不同,性能的定义也不尽相同,例如:CPU看主频、磁盘主要看IOPS(Input/Output Operations Per Second,即每秒进行读写操作的次数)。
本篇“性能”主要指服务端性能 —— QPS(Query Per Second,每秒请求数)来衡量,还有一个影响和QPS也息息相关,就是响应时间(Response Time, RT),可以理解为服务器处理响应的耗时。

正常情况下,响应时间(RT)越短,一秒钟处理的请求数(QPS)就越多,在单线程处理情况下看起来就是线性关系,即我们把每个请求的响应时间降到最低,性能就会提高。

想到响应时间总会有一个极限,不可能无限下降,所以又出现了通过多线程来处理请求。

基于这个理论“总QPS = (1000ms / 响应时间) x 线程数量”,这样性能就和两个因素相关了,一个是一次响应的服务端耗时,一个是处理请求的线程数。

那么,这两个因素会造成什么影响呢?

响应时间与QPS的关系

大部分WEB系统来说,响应时间一般是由CPU执行时间和线程等待时间(比如RPC、IO等待、Sleep、Wait等)组成,服务器在处理请求时,一部分CPU在做运算,另一部分在各种等待。

真正对性能有影响的是CPU的执行时间,不是减少服务器等待时间即可提升性能,CPU的执行是真正消耗了服务器资源。实际测试发现如果减少CPU一半的执行时间,就可以增加一倍的QPS。

所以,致力于减少CPU的执行时间才是提升性能的一个方向。

扫描二维码关注公众号,回复: 5535595 查看本文章

线程数对QPS的影响

但看 “总QPS”计算公式,会觉得线程越多QPS也就越高,这是错误的!线程不是越多越好,线程也会消耗资源,线程越多,系统线程切换的成本就会越来越高,而且每个线程都会耗费一定内存。
认识线程切换(待完善)
大部分多线程场景默认配置:“线程数 = 2 * CPU核数 + 1 ”
除了上面常规公式,还有最佳实践得出的公式:
线程数 = [( 线程等待时间 + 线程CPU时间) / 线程CPU时间] X CPU时间
除此之外就是通过性能测试压出最符合实际业务和公司设备的公式

提升服务器性能关键点

如何提升服务器性能
1. 减少CPU执行时间
2. 设置一个合理的并发数

如何发现瓶颈

知道如何提升服务器性能,那么我们如何发现系统瓶颈呢?
对于服务器而言,出现瓶颈的位置会很多,例如CPU、内存、磁盘以及网络等都可以会导致瓶颈。
不同的系统对瓶颈关注点也不尽相同,例如缓存系统更关注内存,而存储型系统I/O更容易成为瓶颈。
秒杀场景下,瓶颈更多发生于CPU上。

CPU 诊断方式

诊断方式 描述
工具 JProfiler / Yourkit (可列出请求中每个函数CPU执行时间,可发现哪个函数消耗CPU时间最多)
jstack 定时打印调用栈,如果某些函数调用频繁或者耗时较多,就会出现在系统调用栈中。

秒杀场景下,服务端的瓶颈大多会出现在CPU上,除恶服务端,网络传输也有可能出现瓶颈(例如请求较多,同时页面又很大)

如何判断CPU是否为瓶颈?

当QPS达到极限时,服务器CPU使用率是否超过95%,如果没有,CPU还有提升空间,要么有锁限制,要么就是出现过多的本地I/O等待发生。
JProfiler 使用(待完善)

如何优化系统

减少编码

Java编码运行速度较慢(它的语言缺点),涉及字符串操作(如输入输出操作、I/O 操作)都比较耗CPU资源,无论是磁盘 I/O 还是网络 I/O ,都需要将字符转换为字节,而这个转换必须编码。

每个字符编码需要查表(编码比对表)
非常耗资源,所以,减少字符字节或者相反的转换、减少字符编码会提高效率。即减少编码可以提升性能。

如何减少编码
静态字符串提前编码转换为字节码,再通过OutputStream()函数写,就可以减少静态数据的编码转换。例如网页是可以直接进行流输出的(通过resp.getOutputStream()函数写数据)。

扩展阅读
《深入分析 Java Web 技术内幕》第17章 —— Velocity优化实践,通过把静态字符串提前编码成字节并缓存,然后直接输出字节内容,从而减少编码性能消耗,并提升性能。

减少序列化

减少Java中的序列化也能提升性能。又因为序列化和编码通常是同时发生,所以减少序列化即减少了编码。

序列化大多发生到场景在RPC中,因此减少RPC就是减少序列化可以将多个关联性比较强的应用进行“合并部署”,从而减少不同应用之间的RPC,可以减少序列化的消耗。
合并部署:将两个原本在不同机器的不同应用合并部署到一台机器上,并且还要在同一个Tomcat容器中,且不能走本机的Socket。才能避免序列化发生。

Java极致优化

Java和通用Web服务器(如Nginx或Apache服务器)相比,处理大并发HTTP请求时较弱,所以大流量的Web系统做静态话改造,而Java只需处理少量数据的动态请求。可以采用以手段进行Java优化:

  • 直接使用Servlet
    避免使用传统的MVC框架,可以避免一大堆复杂且用处不大的处理逻辑。
  • 直接输出流数据
    使用 resp.getOutputStream() 而不是 resp.getWriter()函数 ,省掉部分不变字符数据的编码,从而提升性能;数据输出时推荐JSON而不是模版引擎(一般是解释执行)来输出页面
    为什么要使用JSON传输数据?(待完善)

并发读优化

并发读优化第一时间想到就是放到Tair内缓存里面。集中式缓存为了保证命中率一般采用一致性Hash,所以同一个Key会落到同一台机器上。 Tair 缓存搭建及实战(待完善)
虽然 单台缓存机器能支撑30W/S的请求,但是远不足以应对“秒杀”场景,如何突破瓶颈:

采用应用层LoadCache,在秒杀系统的单机上缓存商品相关数据。

如何缓存数据?
将数据划分为动态数据和静态数据

  • 静态数据
    商品中“标题”、“描述”不变的数据,秒杀前全量推送到秒杀机器上,并一直缓存到秒杀结束
  • 动态数据
    库存这类动态数据,采用“被动失效”的方式缓存一定时间(多少秒),失效后再去缓存拉取最新数据;

疑问:
库存这种频繁更新的数据,一旦数据不一致,会不会出现超卖?

答疑:
借鉴分层校验原则,“读”场景允许有一定的脏数据,误判只会导致少量原本无库存的下单请求误认为有库存,等到 真正写数据 时在保证最终一致性,从高可用和一致性之间取平衡,来解决高并发数据问题。

总结

  • 发现短板
    • 数据传输本身有物理距离的限制(光速:C=30万千米/秒; 光纤:V=C/1.5=20万千米/秒)
    • 网速限制(带宽,传输速率,可以根据当下的权威数据进行计算)
    • 网络结构(交换机/网卡限制)
    • TCP/IP
    • 虚拟机(内存/CPU/IO等资源的限制)
    • 以及应用本身瓶颈
  • 减少数据
    • 服务端处理数据时不可避免地存在字符和字节的相互转化(RPC)
    • HTTP请求做Gzip压缩,网络传输耗时,这些都和数据大小有关
  • 数据分级
    • 保证首屏为先
    • 重要信息为先
    • 次要信息则异步加载
  • 减少中间环节
    • 减少字符到字节的转换
    • 增加预处理(提前做字符到字节的转换)
    • 去掉不需要操作
  • 基线搭建
    • 应用基线
    • 性能基线(何时性能突然下降)
    • 成本基线(用了多少台机器)
    • 链路基线(系统发生什么变化)

猜你喜欢

转载自blog.csdn.net/Cy_LightBule/article/details/87984783