性能优化 (三):性能分析 (JVM、搜索引擎)& 案例分析
性能分析
分析性能瓶颈,需熟悉整个计算机体系的基本运行原理,才能快速判断问题的根源
Java 虚拟机(JVM)
Java 语言要在硬件上运行,需要设计一个面向 Java 语言特性的虚拟机。该虚拟机能识别指令序列也就是字节码;同时该虚拟机能在各个平台上运行,可以是硬件也可以是软件实现执行引擎;还有提供一个托管环境,代替我们完成了内存管理、动态监测等功能。
一个类通过类加载器 加载到 运行期的数据区(方法区、堆区、Java 方法栈、本地方法栈、程序计数寄存器), 通过 执行引擎 运行在硬件机器上。从虚拟机,硬件底层两个角度分析下 JVM 是怎么运行 Java 字节码的:
虚拟机:每当调用进入一个 java 方法,JVM 会在当前线程的 java 方法栈中生成一个栈帧,用以存放局部变量和字节码操作数。栈帧在内存空间中不要求是连续分布的。
硬件底层:有两种形式,一种是通过解释执行器逐行解释执行字节码,一种是热点代码通过即时编译器编译成机器码缓存起来,以后可直接在硬件上运行。即时编译器有多种:C1(client, 启动性能好),C2(server, 对峰值处理性能好),Graal,JVM 是通过分层编译方式实现的。
Java 的基本类型,有两个层次的规范:Java 语言规范,JVM 规范(局部变量等价于一个数组,除 double, long 占两个单元,其他都只占用一个单元;堆中字段或数组元素,与 Java 语言值域吻合)
1. 类加载:双亲委托模型
Java 语言类型分为两大类:
i) 基本类型(primitive ): JVM 预先定义好的
ii) 引用类型(reference types):接口(对应的字节流),类(对应的字节流),数组类(JVM 运行时生成),泛型参数(编译时擦除)
对于不同形式的字节流(Java 文件编译生成的 class 文件、程序内部直接生成的、网络中获取)都会被加载到 JVM 中成为类或接口;对于直接生成数组、加载的类,JVM 都会进行链接和初始化。
整个加载过程包含:
i) 加载:类加载器使用双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器;
ii) 链接:将创建的类合并至 JVM 中,使之能够执行的过程。包含:验证(JVM 约束条件),准备(静态字段分配内存,虚方法的动态绑定方法表也就是符号引用),解析(符号引用解析实际引用,引用未加载的会触发类的加载) 三个阶段。
iii) 初始化:标记为常量值(final 静态字段,且类型是基本类型或字符串时)的字段赋值,其他的都会被编译器置于同一 clinit 方法中。
2. 内存管理 &垃圾回收
运行时数据区
垃圾回收的关键点如下
i)哪些对象可回收? 可达性分析
ii) 什么时候回收? 分代回收
iii) 怎么回收? 清理、压缩、复制
iv) 垃圾回收器算法?串行、并行、并发(CMS)、G1
具体参考:https://xie.infoq.cn/article/6f494853a572fc7b26c98df46
3. JVM 性能诊断工具
基本工具:JPS、JSTAT、JMAP
集成工具:JConsole、JVisualVM
4. Java 代码优化
i) 合理并谨慎使用多线程
计算公式:启动线程数=[任务执行时间/(任务执行时间-IO 等待时间)]*CPU 内核数
如果是 CPU 密集型,最多不超过 CPU 内核数,如果是 IO 密集型,多开启线程,可增加吞吐率,改善系统性能;
ii) 竞态条件/临界区 & 线程安全
多线程访问相同的资源,需适当的同步,避免竞态条件;
允许被多个线程安全执行的代码称作线程安全的代码;
ThreadLocal 为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题;
iii) Java 内存泄漏
内存泄漏是由于开发人员错误使用引起的;
程序中保留对永远不再使用的对象的引用,这些对象将会耗尽内存,造成泄漏;
包含:长生命周期对象、静态容器、缓存等
iv) 合理使用线程池、对象池
复用线程或对象资源,池资源管理算法,对象内容清除;
感悟:计算机领域任何问题都可通过增加一个中间层来解决。
搜索引擎
整体架构
关键技术
i) 网络爬虫系统
如果网站不想被人爬取,可启用“禁爬协议”
ii) 倒排索引
通过对文章进行分词处理后,按分词后的单词为索引 key , 存储对应的文章编号列表;
包含:带词频的倒排索引、带词频与位置的倒排索引;
iii) 排序算法(链接分析)
网页排名算法 PageRank, 让网页的链接来投票;
常用的搜索引擎
i) Lucene:
ii) Solr:
iii) Elestic Search: 分布式,高可用
案例分析
通过性能测试获取到当前系统性能基准数据,结合性能分析工具和分析数据,对影响整个系统性能的硬件(计算、储存、网络)、软件(OS, 虚拟机、容器、中间件、应用程序)进行排查和定位,最后针对具体问题制定优化的方案。下面通过几个案例进行探讨
秒杀
核心思想:不能再老系统上进行改造,新开发一个前置系统实现秒杀功能。
技术挑战: 瞬间高并发、防作弊、一致性、扩展性
领悟:新系统或新的东西,一般都是低风险高回报
具体参考:https://xie.infoq.cn/article/6f494853a572fc7b26c98df46
一个小型网站的性能优化实践
对一个网站/系统进行优化前,需对这个网站/系统有一定了解,包含:当前系统的
i) 应用架构(系统提供的主要服务、服务特点);
ii) 部署架构(系统物理部署结构);
iii) 技术架构(使用技术栈、核心逻辑流程);
iv) 系统容量(系统 TPS、RT、并发数);
v) 基础设施和中间件健康状态;
如果没有相关的平台和文档,需搭建相关平台和整理相关文档来熟悉当前系统的状况。
性能优化的工作,首先需使用性能测试工具,然后从架构、代码、数据库、运维各个层面进行梳理,分析并发现系统性能瓶颈,并做针对性的优化。具体如下:
i) 性能测试
通过目前业务订单数,推算出接口的调用,并以此构造测试场景,使用 Jmeter 测试工具,利用监控系统,抓取服务线程快照、MySQL slow.log 等监控数据,分析系统瓶颈;
ii) 架构优化
规范分布式缓存的使用:哪些数据需使用缓存(频繁读数据)、失效策略设置、架构升级(使用集群和主从复制部署);
使用 CDN 加速静态资源访问,通过 Nginx 反向代理提供静态文件的前端缓存;
数据库进行主从读写分离
iii) 代码优化
前端页面性能优化:压缩、合并、减少 HTTP 请求;
SQL 语句与索引优化:全表扫描无法命中索引(数据类型不匹配、使用函数);
数据库连接池优化:更换性能更好的连接池 Druid;
缓存使用优化:使用 Jedis 的 pipeline 减少 redis 通信次数;
订单数据冷热分离:因业务订单的生命周期较短,把已处理完状态的订单定期迁移到冷库中,可以是其他类型数据库如文件数据库,订单查询时,可同时查询热库和冷库,再进行数据合并;
iv) 完善系统的监控:包含基础设施、中间件、应用系统、接口、前端等端到端的监控和日志体系;
版权声明: 本文为 InfoQ 作者【dony.zhang】的原创文章。
原文链接:【http://xie.infoq.cn/article/845dd43c2a32c15e0298f826a】。文章转载请联系作者。
评论