技术文档收录
ASCII
Tcpdump
IPV4保留地址段
深入理解以太网网线原理 - 三帛的世界
Linux
WireGuard 一键安装脚本 | 秋水逸冰
SSH Config 那些你所知道和不知道的事 | Deepzz's Blog
Linux 让终端走代理的几种方法
ubuntu 20.04 server 版设置静态 IP 地址 - 链滴
Linux 挂载 Windows 共享磁盘的方法 - 技术学堂
将 SMB/CIFS 网络硬盘永久的挂载到 Ubuntu 上 - 简书
linux 获取当前脚本的绝对路径 | aimuke
[Linux] Linux 使用 / dev/urandom 生成随机数 - piaohua's blog
Linux 生成随机数的多种方法 | Just Do It
Linux 的 Centos7 版本下忘记 root 或者普通用户密码怎么办?
Git 强制拉取覆盖本地
SSH 安全加固指南 - FreeBuf 网络安全行业门户
Linux 系统安全强化指南 - FreeBuf 网络安全行业门户
Linux 入侵排查 - FreeBuf 网络安全行业门户
sshd_config 配置详解 - 简书
SSH 权限详解 - SegmentFault 思否
CentOS 安装 node.js 环境 - SegmentFault 思否
如何在 CentOS 7 上安装 Node.js 和 npm | myfreax
几款 ping tcping 工具总结
OpenVpn 搭建教程 | Jesse's home
openvpn 一键安装脚本 - 那片云
OpenVPN 解决 每小时断线一次 - 爱开源
OpenVPN 路由设置 – 凤曦的小窝
OpenVPN 设置非全局代理 - 镜子的记录簿
TinyProxy 使用帮助 - 简书
Ubuntu 下使用 TinyProxy 搭建代理 HTTP 服务器_Linux_运维开发网_运维开发技术经验分享
Linux 软件包管理工具 Snap 常用命令 - 简书
linux systemd 参数详解
Systemd 入门教程:命令篇 - 阮一峰的网络日志
记一次 Linux 木马清除过程
rtty:在任何地方通过 Web 访问您的终端
02 . Ansible 高级用法 (运维开发篇)
终于搞懂了服务器为啥产生大量的 TIME_WAIT!
巧妙的 Linux 命令,再来 6 个!
77% 的 Linux 运维都不懂的内核问题,这篇全告诉你了
运维工程师必备:请收好 Linux 网络命令集锦
一份阿里员工的 Java 问题排查工具单
肝了 15000 字性能调优系列专题(JVM、MySQL、Nginx and Tomcat),看不完先收
作业调度算法(FCFS,SJF,优先级调度,时间片轮转,多级反馈队列) | The Blog Of WaiterXiaoYY
看了这篇还不会 Linux 性能分析和优化,你来打我
2019 运维技能风向标
更安全的 rm 命令,保护重要数据
求你了,别再纠结线程池大小了!
Linux sudo 详解 | 失落的乐章
重启大法好!线上常见问题排查手册
sudo 使用 - 笨鸟教程的博客 | BY BenderFly
shell 在手分析服务器日志不愁? - SegmentFault 思否
sudo 与 visudo 的超细用法说明_陈发哥 007 的技术博客_51CTO 博客
ESXI 下无损扩展 Linux 硬盘空间 | Naonao Blog
Linux 学习记录:su 和 sudo | Juntao Tan 的个人博客
使用者身份切换 | Linux 系统教程(笔记)
你会使用 Linux 编辑器 vim 吗?
在 Windows、Linux 和 Mac 上查看 Wi-Fi 密码
linux 隐藏你的 crontab 后门 - 简书
Linux 定时任务详解 - Tr0y's Blog
linux 的 TCP 连接数量最大不能超过 65535 个吗,那服务器是如何应对百万千万的并发的?_一口 Linux 的博客 - CSDN 博客_tcp 连接数多少正常
万字长文 + 28 张图,一次性说清楚 TCP,运维必藏
为什么 p2p 模式的 tunnel 底层通常用 udp 而不是 tcp?
记一次服务器被入侵挖矿 - tlanyan
shell 判断一个变量是否为空方法总结 - 腾讯云开发者社区 - 腾讯云
系统安装包管理工具 | Escape
编译代码时动态地链接库 - 51CTO.COM
甲骨文 Oracle Cloud 添加新端口开放的方法 - WirelessLink 社区
腾讯云 Ubuntu 添加 swap 分区的方法_弓弧名家_玄真君的博客 - CSDN 博客
Oracle 开放全部端口并关闭防火墙 - 清~ 幽殇
谁再说不熟悉 Linux 命令, 就把这个给他扔过去!
即插即用,运维工程师必会正则表达式大全
Shell脚本编写及常见面试题
Samba 文件共享服务器
到底一台服务器上最多能创建多少个 TCP 连接 | plantegg
SSH 密钥登录 - SSH 教程 - 网道
在 Bash 中进行 encodeURIComponent/decodeURIComponent | Harttle Land
使用 Shell 脚本来处理 JSON - Tom CzHen's Blog
Docker
「Docker」 - 保存镜像 - 知乎
终于可以像使用 Docker 一样丝滑地使用 Containerd 了!
私有镜像仓库选型:Harbor VS Quay - 乐金明的博客 | Robin Blog
exec 与 entrypoint 使用脚本 | Mr.Cheng
Dockerfile 中的 CMD 与 ENTRYPOINT
使用 Docker 配置 MySQL 主从数据库 - 墨天轮
Alpine vs Distroless vs Busybox – 云原生实验室 - Kubernetes|Docker|Istio|Envoy|Hugo|Golang | 云原生
再见,Docker!
docker save 与 docker export 的区别 - jingsam
如何优雅的关闭容器
docker 储存之 tmpfs 、bind-mounts、volume | 陌小路的个人博客
Dockerfile 中 VOLUME 与 docker -v 的区别是什么 - 开发技术 - 亿速云
理解 docker 容器的退出码 | Vermouth | 博客 | docker | k8s | python | go | 开发
【Docker 那些事儿】容器监控系统,来自 Docker 的暴击_飞向星的客机的博客 - CSDN 博客
【云原生】Docker 镜像详细讲解_微枫 Micromaple 的博客 - CSDN 博客_registry-mirrors
【云原生】Helm 架构和基础语法详解
CMD 和 Entrypoint 命令使用变量的用法
实时查看容器日志 - 苏洋博客
Traefik 2 使用指南,愉悦的开发体验 - 苏洋博客
为你的 Python 应用选择一个最好的 Docker 映像 | 亚马逊 AWS 官方博客
【云原生】镜像构建实战操作(Dockerfile)
Docker Compose 中的 links 和 depends_on 的区别 - 编程知识 - 白鹭情
Python
Pipenv:新一代Python项目环境与依赖管理工具 - 知乎
Python list 列表实现栈和队列
Python 各种排序 | Lesley's blog
Python 中使用 dateutil 模块解析时间 - SegmentFault 思否
一个小破网站,居然比 Python 官网还牛逼
Python 打包 exe 的王炸 - Nuitka
Django - - 基础 - - Django ORM 常用查询语法及进阶
[Python] 小知識:== 和 is 的差異 - Clay-Technology World
Window
批处理中分割字符串 | 网络进行时
Windows 批处理基础命令学习 - 简书
在Windows上设置WireGuard
Windows LTSC、LTSB、Server 安装 Windows Store 应用商店
windows 重启 rdpclip.exe 的脚本
中间件
Nginx 中的 Rewrite 的重定向配置与实践
RabbitMQ 的监控
RabbitMq 最全的性能调优笔记 - SegmentFault 思否
为什么不建议生产用 Redis 主从模式?
高性能消息中间件——NATS
详解:Nginx 反代实现 Kibana 登录认证功能
分布式系统关注点:仅需这一篇,吃透 “负载均衡” 妥妥的
仅需这一篇,妥妥的吃透” 负载均衡”
基于 nginx 实现上游服务器动态自动上下线——不需 reload
Nginx 学习书单整理
最常见的日志收集架构(ELK Stack)
分布式之 elk 日志架构的演进
CAT 3.0 开源发布,支持多语言客户端及多项性能提升
Kafka 如何做到 1 秒处理 1500 万条消息?
Grafana 与 Kibana
ELK 日志系统之通用应用程序日志接入方案
ELK 简易 Nginx 日志系统搭建: ElasticSearch+Kibana+Filebeat
记一次 Redis 连接池问题引发的 RST
把 Redis 当作队列来用,你好大的胆子……
Redis 最佳实践:业务层面和运维层面优化
Redis 为什么变慢了?常见延迟问题定位与分析
好饭不怕晚,扒一下 Redis 配置文件的底 Ku
rabbitmq 集群搭建以及万级并发下的性能调优
别再问我 Redis 内存满了该怎么办了
Nginx 状态监控及日志分析
uWSGI 的安装及配置详解
uwsgi 异常服务器内存 cpu 爆满优化思路
Uwsgi 内存占用过多 - 简书
Nginx 的 limit 模块
Nginx 内置模块简介
Redis 忽然变慢了如何排查并解决?_redis_码哥字节_InfoQ 写作社区
领导:谁再用 redis 过期监听实现关闭订单,立马滚蛋!
Nginx 限制 IP 访问频率以及白名单配置_问轩博客
Nginx $remote_addr 和 $proxy_add_x_forwarded_for 变量详解
Caddy 部署实践
一文搞定 Nginx 限流
数据库
SqlServer 将数据库中的表复制到另一个数据库_MsSql_脚本之家
SQL Server 数据库同步,订阅、发布、复制、跨服务器
sql server 无法删除本地发布 | 辉克's Blog
SQLite全文检索
SQL 重复记录查询的几种方法 - 简书
SQL SERVER 使用订阅发布同步数据库(转)
Mysql 查看用户连接数配置及每个 IP 的请求情况 - 墨天轮
优化 SQL 的 21 条方案
SQL Server 连接时好时坏的奇怪问题
MS SQL 执行大脚本文件时,提示 “内存不足” 的解决办法 - 阿里云开发者社区
防火墙-iptables
iptables 常用规则:屏蔽 IP 地址、禁用 ping、协议设置、NAT 与转发、负载平衡、自定义链
防火墙 iptables 企业防火墙之 iptables
Linux 防火墙 ufw 简介
在 Ubuntu 中用 UFW 配置防火墙
在 Ubuntu20.04 上怎样使用 UFW 配置防火墙 - 技术库存网
监控类
开箱即用的 Prometheus 告警规则集
prometheus☞搭建 | zyh
docker 部署 Prometheus 监控服务器及容器并发送告警 | chris'wang
PromQL 常用命令 | LRF 成长记
prometheus 中使用 python 手写 webhook 完成告警
持续集成CI/CD
GitHub Actions 的应用场景 | 记录干杯
GithubActions · Mr.li's Blog
工具类
GitHub 中的开源网络广告杀手,十分钟快速提升网络性能
SSH-Auditor:一款 SHH 弱密码探测工具
别再找了,Github 热门开源富文本编辑器,最实用的都在这里了 - srcmini
我最喜欢的 CLI 工具
推荐几款 Redis 可视化工具
内网代理工具与检测方法研究
环境篇:数据同步工具 DataX
全能系统监控工具 dstat
常用 Web 安全扫描工具合集
给你一款利器!轻松生成 Nginx 配置文件
教程类
Centos7 搭建神器 openvpn | 运维随笔
搭建 umami 收集个人网站统计数据 | Reorx’s Forge
openvpn安装教程
基于 gitea+drone 完成小团队的 CI/CD - 德国粗茶淡饭
将颜色应用于交替行或列
VMware Workstation 全系列合集 精简安装注册版 支持 SLIC2.6、MSDM、OSX 更新 16.2.3_虚拟机讨论区_安全区 卡饭论坛 - 互助分享 - 大气谦和!
在 OpenVPN 上启用 AD+Google Authenticator 认证 | 运维烂笔头
Github 进行 fork 后如何与原仓库同步:重新 fork 很省事,但不如反复练习版本合并 · Issue #67 · selfteaching/the-craft-of-selfteaching
卧槽,VPN 又断开了!!- 阿里云开发者社区
Grafana Loki 学习之踩坑记
zerotier 的 planet 服务器(根服务器)的搭建踩坑记。无需 zerotier 官网账号。
阿里云 qcow2 镜像转 vmdk,导入 ESXi - 唐际忠的博客
Caddy 入门 – 又见杜梨树
【Caddy2】最新 Caddy2 配置文件解析 - Billyme 的博客
Web 服务器 Caddy 2 | Haven200
手把手教你打造高效的 Kubernetes 命令行终端
Keras 作者:给软件开发者的 33 条黄金法则
超详细的网络抓包神器 Tcpdump 使用指南
使用 fail2ban 和 FirewallD 黑名单保护你的系统
linux 下 mysql 数据库单向同步配置方法分享 (Mysql)
MySQL 快速删除大量数据(千万级别)的几种实践方案
GitHub 上的优质 Linux 开源项目,真滴牛逼!
WireGuard 教程:使用 Netmaker 来管理 WireGuard 的配置 – 云原生实验室 - Kubernetes|Docker|Istio|Envoy|Hugo|Golang | 云原生
Tailscale 基础教程:Headscale 的部署方法和使用教程 – 云原生实验室 - Kubernetes|Docker|Istio|Envoy|Hugo|Golang | 云原生
Nebula Graph 的 Ansible 实践
改进你的 Ansible 剧本的 4 行代码
Caddy 2 快速简单安装配置教程 – 高玩梁的博客
切换至 Caddy2 | 某不科学的博客
Caddy2 简明教程 - bleem
树莓派安装 OpenWrt 突破校园网限制 | Asttear's Blog
OpenVPN 路由设置 – 凤曦的小窝
个性化编译 LEDE 固件
盘点各种 Windows/Office 激活工具
[VirtualBox] 1、NAT 模式下端口映射
VirtualBox 虚拟机安装 openwrt 供本机使用
NUC 折腾笔记 - 安装 ESXi 7 - 苏洋博客
锐捷、赛尔认证 MentoHUST - Ubuntu 中文
How Do I Use A Client Certificate And Private Key From The IOS Keychain? | OpenVPN
比特记事簿: 笔记: 使用电信 TR069 内网架设 WireGuard 隧道异地组网
利用 GitHub API 获取最新 Releases 的版本号 | 这是只兔子
docsify - 生成文档网站简单使用教程 - SegmentFault 思否
【干货】Chrome 插件 (扩展) 开发全攻略 - 好记的博客
一看就会的 GitHub 骚操作,让你看上去像一位开源大佬
【计算机网络】了解内网、外网、宽带、带宽、流量、网速_墩墩分墩 - CSDN 博客
mac-ssh 配置 | Sail
如何科学管理你的密码
VirtualBox NAT 端口映射实现宿主机与虚拟机相互通信 | Shao Guoliang 的博客
CentOS7 配置网卡为静态 IP,如果你还学不会那真的没有办法了!
laisky-blog: 近期折腾 tailscale 的一些心得
使用 acme.sh 给 Nginx 安装 Let’ s Encrypt 提供的免费 SSL 证书 · Ruby China
acme 申请 Let’s Encrypt 泛域名 SSL 证书
从 nginx 迁移到 caddy
使用 Caddy 替代 Nginx,全站升级 https,配置更加简单 - Diamond-Blog
http.proxy - Caddy 中文文档
动手撸个 Caddy(二)| Caddy 命令行参数最全教程 | 飞雪无情的总结
Caddy | 学习笔记 - ijayer
Caddy 代理 SpringBoot Fatjar 应用上传静态资源
使用 graylog3.0 收集 open××× 日志进行审计_年轻人,少吐槽,多搬砖的技术博客_51CTO 博客
提高国内访问 github 速度的 9 种方法! - SegmentFault 思否
VM16 安装 macOS 全网最详细
2022 目前三种有效加速国内 Github
How to install MariaDB on Alpine Linux | LibreByte
局域网内电脑 - ipad 文件共享的三种方法 | 岚
多机共享键鼠软件横向测评 - 尚弟的小笔记
VLOG | ESXI 如何升级到最新版,无论是 6.5 还是 6.7 版本都可以顺滑升级。 – Vedio Talk - VLOG、科技、生活、乐分享
远程修改 ESXi 6.7 管理 IP 地址 - 腾讯云开发者社区 - 腾讯云
几乎不要钱自制远程 PLC 路由器方案
traefik 简易入门 | 个人服务器运维指南 | 山月行
更完善的 Docker + Traefik 使用方案 - 苏洋博客
MicroSD·TF 卡终极探秘 ·MLC 颗粒之谜 1 三星篇_microSD 存储卡_什么值得买
macOS 绕过公证和应用签名方法 - 走客
MiscSecNotes / 内网端口转发及穿透. md at master · JnuSimba/MiscSecNotes
我有特别的 DNS 配置和使用技巧 | Sukka's Blog
SEO:初学者完整指南
通过 OpenVPN 实现流量审计
OpenVPN-HOWTO
OpenVPN Server · Devops Roadmap
Linux 运维必备的 13 款实用工具, 拿好了~
linux 平台下 Tomcat 的安装与优化
Linux 运维跳槽必备的 40 道面试精华题
Bash 脚本进阶,经典用法及其案例 - alonghub - 博客园
推荐几个非常不错的富文本编辑器 - 走看看
在 JS 文件中加载 JS 文件的方法 - 月光博客
#JavaScript 根据需要动态加载脚本并设置自定义参数
笔记本电脑 BIOS 修改及刷写教程
跨平台加密 DNS 和广告过滤 personalDNSfilter · LinuxTOY
AdGuard Home 安装及使用指北
通过 Amazon S3 协议挂载 OSS
记一次云主机如何挂载对象存储
本文档发布于https://mrdoc.fun
-
+
首页
肝了 15000 字性能调优系列专题(JVM、MySQL、Nginx and Tomcat),看不完先收
> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [www.tuicool.com](https://www.tuicool.com/articles/26Fveqz) 前言 -- 性能调优,无疑是个庞大的话题,也是很多项目中非常重要的一环,性能调优难做是众所周知的,毕竟性能调优涵盖的面实在是太多了,在这里我就大概的讲一下企业中最常用的四种调优——JVM 调优、MySQL 调优、Nginx 调优以及 Tomcat 调优,一家之言,有什么说的不对的还请多包涵补充。 篇幅所限,有些东西是肯定写不到的,所以本文只是挑了一些重要部分来剖析,如果需要完整详细的掌握性能调优,可以来领取系统整理的 [**性能调优笔记和相关学习资料**](https://jq.qq.com/?_wv=1027&k=PjEVS3qt) 话不多说,坐稳扶好,发车喽! 一、Jvm 性能调优 ---------- 1、JVM 类加载机制详解 ------------- 如下图所示,JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。 ![](https://img2.tuicool.com/Mf6N7bE.png!web) ### 1.1 加载 在加载阶段,虚拟机需要完成以下三件事情: 1)通过一个类的全限定名来获取定义此类的二进制字节流。注意这里的二进制字节流不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以从网络中获取,也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区这些数据的访问入口。 相对于类加载过程的其他阶段,加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段既可以使用系统提供的类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员们可以通过定义自己的类加载器去控制字节流的获取方式。 ### 1.2 验证 这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 ### 1.3 准备 准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为: ``` public static int v = 8080; ``` 实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 `putstatic` 指令是程序被编译后,存放于类构造器 `<client>` 方法之中,这里我们后面会解释。 但是注意如果声明为: ``` public static final int v = 8080; ``` 在编译阶段会为 v 生成 `ConstantValue` 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v 赋值为 8080。 ### 1.4 解析 解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的: * CONSTANT_Class_info * CONSTANT_Field_info * CONSTANT_Method_info 等类型的常量。 下面我们解释一下符号引用和直接引用的概念: * 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。 * 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。 ### 1.5 初始化 初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。 初始化阶段是执行类构造器 `<clint>` 方法的过程。 `<clint>` 方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证 `<clint>` 方法执行之前,父类的 `<clint>` 方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成 `<clint>()` 方法。 #### 注意以下几种情况不会执行类初始化: * 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。 * 定义对象数组,不会触发该类的初始化。 * 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。 * 通过类名获取 Class 对象,不会触发类的初始化。 * 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。 * 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。 ### 1.6 类加载器 虚拟机设计团队把类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流” 这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性。这句话可以表达得更通俗一些:比较两个类是否 “相等”,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类是来源于同一个 Class 文件,只要加载它们的类加载器不同,那这两个类就必定不相等。这里所指的“相等”,包括代表类的 Class 对象的 equals() 方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用 instanceof 关键字做对象所属关系判定等情况。如果没有注意到类加载器的影响,在某些情况下可能会产生具有迷惑性的结果。 JVM 提供了 3 种类加载器: * 启动类加载器 ( `Bootstrap ClassLoader` ):负责加载 JAVA_HOME\lib 目录中的,或通过 `-Xbootclasspath` 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。 * 扩展类加载器 ( `Extension ClassLoader` ):负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。 * 应用程序类加载器 ( `Application ClassLoader` ):负责加载用户路径(classpath)上的类库。 JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader 实现自定义的类加载器。 ![](https://img0.tuicool.com/qA7zM3m.png!web) 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 `java.lang.Object` ,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 `Object` 对象。 在有些情境中可能会出现要我们自己来实现一个类加载器的需求,由于这里涉及的内容比较广泛,我想以后单独写一篇文章来讲述,不过这里我们还是稍微来看一下。我们直接看一下 jdk 中的 `ClassLoader` 的源码实现: ``` protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } ``` * 首先通过 `Class c = findLoadedClass(name);` 判断一个类是否已经被加载过。 * 如果没有被加载过执行 `if (c == null)` 中的程序,遵循双亲委派的模型,首先会通过递归从父加载器开始找,直到父类加载器是 `Bootstrap ClassLoader` 为止。 * 最后根据 `resolve` 的值,判断这个 class 是否需要解析。 而上面的 `findClass()` 的实现如下,直接抛出一个异常,并且方法是 `protected` ,很明显这是留给我们开发者自己去实现的,这里我们以后我们单独写一篇文章来讲一下如何重写 `findClass` 方法来实现我们自己的类加载器。 ``` protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } ``` 2、JVM 内存模型 ---------- ![](https://img1.tuicool.com/YZVNVfv.png!web) ### 2.1 各部分的功能 这几个存储区最主要的就是栈区和堆区,那么什么是栈什么是堆呢?说的简单点,栈里面存放的是基本的数据类型和引用,而堆里面则是存放各种对象实例的。 ![](https://img0.tuicool.com/riUnQjA.png!web) 堆与栈分开设计是为什么呢? * 栈存储了处理逻辑、堆存储了具体的数据,这样隔离设计更为清晰 * 堆与栈分离,使得堆可以被多个栈共享。 * 栈保存了上下文的信息,因此只能向上增长;而堆是动态分配 栈的大小可以通过 - XSs 设置,如果不足的话,会引起 java.lang.StackOverflowError 的异常 #### 栈区 线程私有,生命周期与线程相同。每个方法执行的时候都会创建一个栈帧(stack frame)用于存放 局部变量表、操作栈、动态链接、方法出口。 #### 堆 存放对象实例,所有的对象的内存都在这里分配。垃圾回收主要就是作用于这里的。 * 堆得内存由 - Xms 指定,默认是物理内存的 1/64;最大的内存由 - Xmx 指定,默认是物理内存的 1/4。 * 默认空余的堆内存小于 40% 时,就会增大,直到 - Xmx 设置的内存。具体的比例可以由 - XX:MinHeapFreeRatio 指定 * 空余的内存大于 70% 时,就会减少内存,直到 - Xms 设置的大小。具体由 - XX:MaxHeapFreeRatio 指定。 因此一般都建议把这两个参数设置成一样大,可以避免 JVM 在不断调整大小。 ### 2.2 程序计数器 这里记录了线程执行的字节码的行号,在分支、循环、跳转、异常、线程恢复等都依赖这个计数器。 ### 2.3 方法区 类型信息、字段信息、方法信息、其他信息 ### 2.4 总结 ![](https://img2.tuicool.com/RrA7ZfI.png!web) 3、垃圾收集机制详解 ---------- ### 3.1 如何定义垃圾 有两种方式,一种是引用计数(但是无法解决循环引用的问题);另一种就是可达性分析。 判断对象可以回收的情况: * 显示的把某个引用置位 NULL 或者指向别的对象 * 局部引用指向的对象 * 弱引用关联的对象 ### 3.2 垃圾回收的方法 #### 3.2.1Mark-Sweep 标记 - 清除算法 ![](https://img0.tuicool.com/fYne22b.png!web) 这种方法优点就是减少停顿时间,但是缺点是会造成内存碎片。 #### 3.2.2 Copying 复制算法 ![](https://img2.tuicool.com/m2eiumU.png!web) 这种方法不涉及到对象的删除,只是把可用的对象从一个地方拷贝到另一个地方,因此适合大量对象回收的场景,比如新生代的回收。 #### 3.2.3 Mark-Compact 标记 - 整理算法 ![](https://img2.tuicool.com/MJVJBj6.png!web) 这种方法可以解决内存碎片问题,但是会增加停顿时间。 #### 3.2.4 Generational Collection 分代收集 最后的这种方法是前面几种的合体,即目前 JVM 主要采取的一种方法,思想就是把 JVM 分成不同的区域。每种区域使用不同的垃圾回收方法。 ![](https://img1.tuicool.com/2yQJr2Z.png!web) 上面可以看到堆分成两个个区域: * 新生代 (Young Generation):用于存放新创建的对象,采用复制回收方法,如果在 s0 和 s1 之间复制一定次数后,转移到年老代中。这里的垃圾回收叫做 minor GC; * 年老代 (Old Generation):这些对象垃圾回收的频率较低,采用的标记整理方法,这里的垃圾回收叫做 major GC。 这里可以详细的说一下新生代复制回收的算法流程: 在新生代中,分为三个区:Eden, from survivor, to survior。 * 当触发 minor GC 时,会先把 Eden 中存活的对象复制到 to Survivor 中; * 然后再看 from survivor,如果次数达到年老代的标准,就复制到年老代中;如果没有达到则复制到 to survivor 中,如果 to survivor 满了,则复制到年老代中。 * 然后调换 from survivor 和 to survivor 的名字,保证每次 to survivor 都是空的等待对象复制到那里的。 ### 3.3 垃圾回收器 ![](https://img1.tuicool.com/aq6jeyQ.png!web) #### 3.3.1 串行收集器 Serial 这种收集器就是以单线程的方式收集,垃圾回收的时候其他线程也不能工作。 ![](https://img2.tuicool.com/6ZV3yyj.png!web) #### 3.3.2 并行收集器 Parallel 以多线程的方式进行收集 ![](https://img2.tuicool.com/imEFnib.png!web) #### 3.3.3 并发标记清除收集器 Concurrent Mark Sweep Collector, CMS 大致的流程为:初始标记 -- 并发标记 -- 重新标记 -- 并发清除 ![](https://img2.tuicool.com/RnIzQvn.png!web) #### 3.3.4 G1 收集器 Garbage First Collector 大致的流程为:初始标记 -- 并发标记 -- 最终标记 -- 筛选回收 ![](https://img1.tuicool.com/YJRVbia.png!web) #### 篇幅所限,关于类字节码文件、调优工具以及 GC 日志分析这里就不写了,如果有感兴趣的朋友可以点击 [领取我整理的完整 JVM 性能调优笔记](https://jq.qq.com/?_wv=1027&k=PjEVS3qt) ,里面会有详细叙述。 二、Mysql 性能调优 ------------ 1、SQL 执行原理详解 ------------ ![](https://img2.tuicool.com/U7j6RnV.jpg!web) ### 1.1 SQL Server 组成部分 ![](https://img1.tuicool.com/UZBjiaF.png!web) #### 1.1.1 关系引擎:主要作用是优化和执行查询。 包含三大组件: (1)命令解析器:检查语法和转换查询树。 (2)查询执行器:优化查询。 (3)查询优化器:负责执行查询。 #### 1.1.2 存储引擎:管理所有数据及涉及的 IO 包含三大组件: (1)事务管理器:通过锁来管理数据及维持事务的 ACID 属性。 (2)数据访问方法:处理对行、索引、页、行版本、空间分配等的 I/O 请求。 (3)缓冲区管理器:管理 SQL Server 的主要内存消耗组件 Buffer Pool。 #### 1.1.3Buffer Pool 包含 SQL Server 的所有缓存。如计划缓存和数据缓存。 #### 1.1.4 事务日志 记录事务的所有更改。保证事务 ACID 属性的重要组件。 #### 1.1.5 数据文件 数据库的物理存储文件。 6.SQL Server 网络接口 建立在客户端和服务器之间的网络连接的协议层 ### 1.2 查询的底层原理 ![](https://img1.tuicool.com/rQ3mQ3Q.png!web) 1.2.1 当客户端执行一条 T-SQL 语句给 SQL Server 服务器时,会首先到达服务器的网络接口,网络接口和客户端之间有协议层。 1.2.2 客户端和网络接口之间建立连接。使用称为 “表格格式数据流”(TDS) 数据包的 Microsoft 通信格式来格式化通信数据。 1.2.3 客户端发送 TDS 包给协议层。协议层接收到 TDS 包后,解压并分析包里面包含了什么请求。 1.2.4 命令解析器解析 T-SQL 语句。命令解析器会做下面几件事情: (1)检查语法。发现有语法错误就返回给客户端。下面的步骤不执行。 (2)检查缓冲池(Buffer Pool)中是否存在一个对应该 T-SQL 语句的执行计划缓存。 (3)如果找到已缓存的执行计划,就从执行计划缓存中直接读取,并传输给查询执行器执行。 (4)如果未找到执行计划缓存,则在查询执行器中进行优化并产生执行计划,存放到 Buffer Pool 中。 1.2.5 查询优化器优化 SQL 语句 当 Buffer Pool 中没有该 SQL 语句的执行计划时,就需要将 SQL 传到查询优化器,通过一定的算法,分析 SQL 语句,产生一个或多个候选执行计划。选出开销最小的计划作为最终执行计划。然后将执行计划传给查询执行器。 1.2.6 查询执行器执行查询 查询执行器把执行计划通过 OLE DB 接口传给存储引擎的数据访问方法。 1.2.7 数据访问方法生成执行代码 数据访问方法将执行计划生成 SQL Server 可操作数据的代码,不会实际执行这些代码,传送给缓冲区管理器来执行。 1.2.8 缓冲区管理器读取数据。 先在缓冲池的数据缓存中检查是否存在这些数据,如果存在,就把结果返回给存储引擎的数据访问方法;如果不存在,则从磁盘(数据文件)中读出数据并放入数据缓存中,然后将读出的数据返回给存储引擎的数据访问方法。 1.2.9 对于读取数据,将会申请共享锁,事务管理器分配共享锁给读操作。 1.2.10 存储引擎的数据访问方法将查询到的结果返回关系引擎的查询执行器。 1.2.11 查询执行器将结果返回给协议层。 1.2.12 协议层将数据封装成 TDS 包,然后协议层将 TDS 包传给客户端。 2、索引底层剖析 -------- ### 2.1 为何要有索引? 一般的应用系统,读写比例在 10:1 左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。 ### 2.2 什么是索引? 索引在 MySQL 中也叫做 “键” 或者 "key"(primary key,unique key,还有一个 index key),是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要,减少 io 次数,加速查询。(其中 primary key 和 unique key,除了有加速查询的效果之外,还有约束的效果,primary key 不为空且唯一,unique key 唯一,而 index key 只有加速查询的效果,没有约束效果) 索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。 索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。 强调:一旦为表创建了索引,以后的查询最好先查索引,再根据索引定位的结果去找数据 ### 2.3 索引原理 索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等,下面内容看不懂的同学也没关系,能明白这个目录的道理就行了。 那么你想,书的目录占不占页数,这个页是不是也要存到硬盘里面,也占用硬盘空间。 你再想,你在没有数据的情况下先建索引或者说目录快,还是已经存在好多的数据了,然后再去建索引,哪个快,肯定是没有数据的时候快,因为如果已经有了很多数据了,你再去根据这些数据建索引,是不是要将数据全部遍历一遍,然后根据数据建立索引。你再想,索引建立好之后再添加数据快,还是没有索引的时候添加数据快,索引是用来干什么的,是用来加速查询的,那对你写入数据会有什么影响,肯定是慢一些了,因为你但凡加入一些新的数据,都需要把索引或者说书的目录重新做一个,所以索引虽然会加快查询,但是会降低写入的效率。 ### 2.4 索引的数据结构 前面讲了索引的基本原理,数据库的复杂性,又讲了操作系统的相关知识,目的就是让大家了解,现在我们来看看索引怎么做到减少 IO,加速查询的。任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘 IO 次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b + 树应运而生。 ![](https://img1.tuicool.com/fQ7fAbj.png!web) 3、Mysql 锁机制与事务隔离级别详解 -------------------- ![](https://img1.tuicool.com/nueAjmI.png!web) ### 3.1 为什么需要学习数据库锁知识 即使我们不会这些锁知识,我们的程序在 **一般情况下** 还是可以跑得好好的。因为这些锁数据库 **隐式** 帮我们加了 * 对于 `UPDATE、DELETE、INSERT` 语句, **InnoDB** 会 **自动** 给涉及数据集加排他锁(X) * **MyISAM** 在执行查询语句 `SELECT` 前,会 **自动** 给涉及的所有表加 **读锁** ,在执行更新操作( `UPDATE、DELETE、INSERT` 等)前,会 **自动** 给涉及的表加 **写锁** 只会在某些特定的场景下才需要 **手动** 加锁,学习数据库锁知识就是为了: * 能让我们在特定的场景下派得上用场 * 更好 **把控自己写的程序** * 在跟别人聊数据库技术的时候可以搭上几句话 * **构建自己的知识库体系** !在面试的时候不虚 ### 3.2 表锁简单介绍 首先,从锁的粒度,我们可以分成两大类: * **表锁** * 开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低 * **行锁** * 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高 不同的存储引擎支持的锁粒度是不一样的: * **InnoDB 行锁和表锁都支持** ! * **MyISAM 只支持表锁** ! InnoDB 只有通过 **索引条件** 检索数据 **才使用行级锁** ,否则,InnoDB 将使用 **表锁** * 也就是说, **InnoDB 的行锁是基于索引的** ! #### 表锁下又分为两种模式: * 表读锁(Table Read Lock) * 表写锁(Table Write Lock) * 从下图可以清晰看到,在表读锁和表写锁的环境下: **读读不阻塞,读写阻塞,写写阻塞** ! * 读读不阻塞:当前用户在读数据,其他的用户也在读数据,不会加锁 * 读写阻塞:当前用户在读数据,其他的用户 **不能修改当前用户读的数据** ,会加锁! * 写写阻塞:当前用户在修改数据,其他的用户 **不能修改当前用户正在修改的数据** ,会加锁! * 写锁和其他锁均布兼容,只有读和读之间兼容 ![](https://img0.tuicool.com/2QVvIrY.png!web) 从上面已经看到了: **读锁和写锁是互斥的,读写操作是串行** 。 * 如果某个进程想要获取读锁, **同时** 另外一个进程想要获取写锁。在 mysql 里边, **写锁是优先于读锁的** ! * 写锁和读锁优先级的问题是可以通过参数调节的: `max_write_lock_count` 和 `low-priority-updates` 值得注意的是: * **MyISAM 可以** 支持查询和插入操作的 **并发** 进行。可以通过系统变量 `concurrent_insert` 来指定哪种模式,在 **MyISAM** 中它默认是:如果 MyISAM 表中没有空洞(即表的中间没有被删除的行),MyISAM 允许在一个进程读表的同时,另一个进程从 **表尾** 插入记录。 * 但是 **InnoDB 存储引擎是不支持的** ! ### 3.3 MVCC 和事务的隔离级别 数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别 MVCC(Multi-Version Concurrency Control) 多版本并发控制,可以简单地认为:MVCC 就是行级锁的一个变种 (升级版)。 * 事务的隔离级别就是通过锁的机制来实现,只不过隐藏了加锁细节 在表锁中我们读写是阻塞的,基于提升并发性能的考虑,MVCC 一般读写是不阻塞的 (所以说 MVCC 很多情况下避免了加锁的操作) * MVCC 实现的读写不阻塞正如其名:多版本并发控制 ---> 通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本。 快照有两个级别: * 语句级 针对于 Read committed 隔离级别 * 事务级别 针对于 Repeatable read 隔离级别 我们在初学的时候已经知道,事务的隔离级别有 4 种: * Read uncommitted 会出现脏读,不可重复读,幻读 * Read committed 会出现不可重复读,幻读 * Repeatable read 会出现幻读 (但在 Mysql 实现的 Repeatable read 配合 gap 锁不会出现幻读!) * Serializable 串行,避免以上的情况! ----------------------------- Read uncommitted 会出现的现象 ---> 脏读:一个事务读取到另外一个事务未提交的数据 * 例子:A 向 B 转账,A 执行了转账语句,但 A 还没有提交事务,B 读取数据,发现自己账户钱变多了!B 跟 A 说,我已经收到钱了。A 回滚事务【rollback】,等 B 再查看账户的钱时,发现钱并没有多。 * 出现脏读的本质就是因为操作 (修改) 完该数据就立马释放掉锁,导致读的数据就变成了无用的或者是错误的数据。 Read committed 避免脏读的做法其实很简单: * 就是把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作 但 Read committed 出现的现象 ---> 不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改 * 注:A 查询数据库得到数据,B 去修改数据库的数据,导致 A 多次查询数据库的结果都不一样【危害:A 每次查询的结果都是受 B 的影响的,那么 A 查询出来的信息就没有意思了】 上面也说了,Read committed 是语句级别的快照!每次读取的都是当前最新的版本! Repeatable read 避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据。 呃... 如果还是不太清楚,我们来看看 InnoDB 的 MVCC 是怎么样的吧 (摘抄《高性能 MySQL》) InnoDB 的 MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列一个保存了行的创建时间,一个保存了行的过期(删除)时间。当然存储的并不是真正的时间值,而是系统版本号。每开始一个新的事务,系统版本号会自动递增,事务开始的时候的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 select InnoDB 会根据以下两个条件检查每行记录: a. InnoDB 只查找版本早于当前事务版本的数据行,这样可以确保事务读取到的数据,要么是在事务开始前就存在的,要么是事务自身插入或更新的 b. 行的删除版本要么未定义要么大于当前事务版本号,确保了事务读取到的行,在事务开始前未被删除 至于虚读 (幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。 * 注:和不可重复读类似,但虚读 (幻读) 会读到其他事务的插入的数据,导致前后读取不一致 * MySQL 的 Repeatable read 隔离级别加上 GAP 间隙锁已经处理了幻读了。 三、Nginx 调优 ---------- 1、Nginx 定义 ---------- nginx 常用做静态内容服务和反向代理服务器,以及页面前端高并发服务器。适合做负载均衡,直面外来请求转发给后面的应用服务(tomcat 什么的) 2、熟练掌握 Nginx 核心配置 ----------------- ### 2.1 全局配置块 ``` user root; #运行worker进程的账户,user 用户 [组],默认以nobody账户运行 worker_processes 7; #要使用的worker进程数,可设置为数值、auto(根据机器性能自动设置),默认值1 error_log logs/error.log; #nginx进程(master+worker)的日志设置,保存位置、输出级别,此即为默认保存位置 #error_log logs/error.log notice; #输出级别可选,由低到高依次为:debug(输出信息最多),info,notice,warn,error,erit(输出信息最少) pid logs/nginx.pid; #nginx主进程的pid的保存位置,此即为默认值 worker_rlimit_nofile 65535; #单个worker进程可打开的最大文件描述符数 ``` #### worker_processes: 实际运营时一般设置为很接近 CPU 的线程数,比如说 CPU 是 8 线程,一般设置为 6、7。 我们自己开发、用时一般设置为 1、2 即可,不然太吃资源。 #### worker_rlimit_nofile: r 是 read,limit 是限制,单个 worker 进程最多只能打开指定个数的文件,超过便不能再读取文件。打开一次文件便会产生一个文件描述符。 此设置是为了防止单个 worker 进程消耗大量的系统资源。 ps -ef | grep nginx 查询下 nginx 的进程: ![](https://img0.tuicool.com/NrYFrin.jpg!web) 不管设置多少个 worker 进程,主进程只有一个(即运行 sbin/nginx)。 主进程由 Linux 当前登录的账户运行,工作进程由 user 指令指定的账户运行。第一列数字是进程的 PID。 nginx 工作进程和 nginx 主进程都是 Linux 中的进程,但主进程(父进程)可以控制 worker 进程(子进程)的开启、结束。 master 进程可以看做老板,worker 进程可以看做打工仔。 ### 2.2 events 块 ``` events { accept_mutex on; #防止惊群 multi_accept on; #允许单个worker进程可同时接收多个网络连接的请求,默认为off use epoll; #设置worker进程使用高效模式 worker_connections 1024; #指定单个worker进程最多可建立的网络连接数,默认值1024。 } ``` #### accept_mutex: 惊群现象:一个网络连接到来,所有沉睡的 worker 进程都会被唤醒,但只用一个 worker 处理连接,其余被唤醒的 worker 又开始沉睡。 设置为 on:要使用几个 worker 就唤醒几个,不全部唤醒,默认值就是 on。 设置为 off:一律全部唤醒。一片 worker 醒来是要占用资源的,会影响性能。 #### use: 指定 nginx 的工作模式,可选的值:select、poll、kqueue、epoll、rtsig、/dev/poll。 其中 select、poll 都是标准模式,kqueue、epoll 都是高效模式, kqueue 是在 BSD 系统中用的,epoll 是在 Linux 系统中用的。(BSD 是 Unix 的一个分支,Linux 是一种类 Unix 系统)。 全局块中的 worker_processes、events 块中的 worker_connections 是 nginx 支持高并发的关键,这 2 个数值相乘即 nginx 可建立的最大连接数。 一个连接要用一个文件来保存, worker_connections 设置的单个 worker 进程的最大连接数,受全局块中 worker_rlimit_nofile 设置的单个 worker 进程可打开的最大文件数限制。 而 worker_rlimit_nofile 只是 nginx 对单个 worker 进程的限制,要受 Linux 系统对单个进程可打开的最大文件描述符数限制。 Linux 默认单个进程最多只能打开 1024 个文件描述符,需要我们修改下 Linux 的资源限制,设置单个进程可打开的最大文件描述符数: ``` ulimit -n 65536 ``` ulimit 命令可以限制单个进程使用的系统资源的尺寸、数量,包括内存、缓冲区、套接字、栈、队列、CPU 占用时间等。 可用 ulimit --help 查看参数。 ### 2.3 http 块 ``` http{ #http全局块 #server块 } ``` 可以有多个 server 块。 #### (1)http 全局块 http 全局块的配置作用于整个 http 块(http 块内的所有 server 块)。 ``` include mime.types; #将conf/mime.types包含进来 default_type application/octet-stream; #设置默认的MIME类型,二进制流。如果使用的MIME类型在mime.types中没有,就当作默认类型处理。 #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log; #设置日志,这个日志保存的是客户端请求的信息,包括客户端地址、使用的浏览器、浏览器内核版本、请求的url、请求时间、请求方式、响应状态等。 #access_log logs/access.log main; #可指定日志格式,上面定义的main格式即默认格式。保存位置默认是logs/access.log sendfile on; #开启文件高效传输模式,默认为off,不开启。 #tcp_nopush on; #如果响应体积过大,默认会分多个批次传输给客户端,设置为on会一次性传给客户端,可防止网络阻塞 #tcp_nodelay on; #如果响应体积过小,默认会放在缓冲区,缓冲区满了才刷给客户端,设置为on直接刷给客户端,可防止网络阻塞 keepalive_timeout 65; #与客户端保持连接的超时时间,在指定时间内,如果客户端没有向Nginx发送任何数据(无活动),Nginx会关闭该连接。 gzip on; #使用gzip模块压缩响应数据。启用后响应体积变小,传输到客户端所需时间更少,节省带宽,但nginx压缩、客户端解压都有额外的时间、资源开销,nginx的负担也会加大。 upstream servers{ #设置负载均衡器,可同时设置多个负载均衡器。负载均衡器的名称中不能含有_,此处指定名称为servers server 192.168.1.7:8080; #tomcat服务器节点 server 192.168.1.8:8081; server 192.168.1.7:8080 down; #down表示该节点下线,暂不使用 server 192.168.1.8:8081 backup; #backup表示该节点是备胎,只有在其他节点忙不过来时才会启用(比如一些节点出故障了、其他节点负载变大)。 server 192.168.1.8:8081 max_fails=3 fail_timeout=60s; #如果对该节点的请求失败3次,就60s内暂时不使用该节点,60s后恢复使用 } ``` 日志格式常用的值: * $remote_addr 客户端的 ip 地址 * $time_local : 访问时间与时区 * $request : 请求的 url 与 http 协议 * $status : 请求状态,成功是 200 * $http_referer :从那个页面链接访问过来的 * $http_user_agent :客户端浏览器的信息 #### (2)server 模块 ``` server{ #server全局块 listen 80; #要监听的端口 server_name localhost; #虚拟主机(即域名),要在dns上注册过才有效,没有注册的话只能用localhost。可指定多个虚拟主机,空格分开即可 charset utf-8; #使用的字符集。 #access_log logs/host.access.log main; #在http全局块、server全局块中任意一处设置日志即可。http全局块已经设置了日志,此处可不用设置。 #错误页设置 error_page 404 /404.html; #html目录下默认只有index.html(nginx首页)、50x.html,需要自己写404.html location = /404.html { root html; #指定404.html所在目录,此处使用相对路径,nginx主目录下的html目录,也可以使用绝对路径 } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } #处理静态资源 location ~* \.(html|css|js|gif|jpg|png|mp4)$ { #使用正则表达式匹配url,如果请求的是这些文件,就使用下面的处理方式 root static; #如果使用nginx处理静态资源,需使用root指定静态资源所在目录。在nginx主目录下新建目录static,把静态资源放进去即可。 expires 30d; #设置缓存过期时间 #proxy_pass http://192.168.1.10:80; #如果使用apache等其他机器处理静态资源,使用proxy_pass转发过去即可,多台机器集群时使用负载均衡器即可。 } #设置默认处理方式 location / { #如果url没有指定匹配,就使用默认的处理方式来处理 root html; #指定处理请求的根目录。nginx本身作为web服务器直接处理客户端请求时,比如请求login.jsp,会调用root指定目录下的login来处理请求。 index index.html index.htm; #指定nginx服务器的首页地址。root、index2项配置都是必需的。 proxy_pass http://servers; #指定要使用的负载均衡器,转发给其中某个节点处理。如不设置此项(代理),则默认nginx本身作为web服务器,直接处理请求,会到root指定目录下找请求的文件 } } ``` 设置的错误页面是 nginx 作为 web 服务器(处理静态资源)出现问题时,比如 nginx 上的静态资源找不到,返回给客户端的。 如果是 tomcat 出现的问题,比如 tomcat 上的 xxx.jsp 找不到,返回的是 tomcat 的错误页面,不是 nginx 的。 如果使用 nginx 本身要作为 web 服务器,直接处理客户端请求,比如处理静态资源,要将全局块中 user 设置为运行 nginx 的账户(即当前登陆 Linux 的账户), 否则 worker 进程(默认 nobody 账户)无权限读取当前账户(即运行 nginx 主进程的账户)的静态资源,客户端会显示 403 禁止访问。 可以使用正则表达式来过滤客户端 ip,也可以把客户端的 ip 过滤规则写在文件中,然后包含进来。 3、掌握 Nginx 负载算法配置 ----------------- #### (1)轮询 将列表中的服务器排成一圈,从前往后,找空闲的服务器来处理请求。 轮询适合服务器性能差不多的情况。默认使用的就是轮询,不需要设置什么。 #### (2)加权轮询 ``` upstream servers{ server 192.168.1.7:8080 weight=1; server 192.168.1.8:8081 weight=2; } ``` 设置权重,权重大的轮到的机会更大,适合服务器性能有明显差别的情况。 #### (3)ip_hash ``` upstream servers{ ip_hash; server 192.168.1.7:8080; server 192.168.1.8:8081; } ``` 根据客户端 ip 的 hash 值来转发请求,同一客户端(ip)的请求都会被转发给同一个服务器处理,可解决 session 问题。 #### (4)url_hash(第三方) ``` upstream servers{ hash $request_uri; server 192.168.1.7:8080; server 192.168.1.8:8081; } ``` 根据请求的 url 来转发,会将 url 相同的请求转发给同一服务器处理。 一直处理某个 url,服务器上一般都有该 url 的缓存,可直接从缓存中获取数据作为响应返回,减少时间开销。 #### (5)fair(第三方) ``` upstream servers{ fair; server 192.168.1.7:8080; server 192.168.1.8:8081; } ``` 根据服务器响应时间来分发请求,响应时间短的分发的请求多。 fair 公平,nginx 先计算每个节点的平均响应时间,响应时间短说明该节点负载小(闲),要多转发给它;响应时间长说明该节点负载大,要少转发给它。 ip_hash、url_hash 都是使用特定节点来处理特定请求,如果特定节点故障,nginx 会剔除不可用的节点,将特定请求转发给其它节点处理,url_hash 影响不大,但 ip_hash 会丢失之前的 session 数据。 四、Tomcat 调优 ----------- 1、基础参数设置 -------- #### 在 server.xml 中配置: * **maxThreads:** Tomcat 使用线程来处理接收的每个请求。这个值表示 Tomcat 可创建的最大的线程数。 * **acceptCount:** 指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。 * **connnectionTimeout:** 网络连接超时,单位:毫秒。设置为 0 表示永不超时,这样设置有隐患的。通常可设置为 30000 毫秒。 * **minSpareThreads:** Tomcat 初始化时创建的线程数。 * **maxSpareThreads:** 一旦创建的线程超过这个值,Tomcat 就会关闭不再需要的 socket 线程 2、Tomat 的 4 种连接方式对比 ------------------- tomcat 默认的 http 请求处理模式是 bio(即阻塞型,下面第二种),每次请求都新开一个线程处理。下面做一个介绍 ``` <Connector port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" redirectPort="8443"/> ``` ``` <Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/> ``` ``` <Connector executor="tomcatThreadPool" port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> ``` ``` <Connector executor="tomcatThreadPool" port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" redirectPort="8443" /> ``` 我们姑且把上面四种 Connector 按照顺序命名为 NIO, HTTP, POOL, NIOP。测试性能对比,数值为每秒处理的请求数,越大效率越高 ``` NIO HTTP POOL NIOP 281 65 208 365 666 66 110 398 692 65 66 263 256 63 94 459 440 67 145 363 ``` 得出结论:NIOP > NIO > POOL > HTTP 虽然 Tomcat 默认的 HTTP 效率最低,但是根据测试次数可以看出是最稳定的。且这只是一个简单页面测试,具体会根据复杂度有所波动。 配置参考:Linux 系统每个进程支持的最大线程数是 1000,windos 是 2000。具体跟服务器的内存,Tomcat 配置的数量有关联。 ``` <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="500" minSpareThreads="25" maxSpareThreads="250" enableLookups="false" redirectPort="8443" acceptCount="300" connectionTimeout="20000" disableUploadTimeout="true"/> ``` 3、Tomcat 的集群 ------------ Tomcat 的部署,是一台服务器部署一个 Tomcat(上线多个项目),还是一台服务器部署多个 tomact(每个 tomcat 部署 1~n 个项目)。多核必选配置多个 Tomcat,微服务多线程的思想模式。 4、Tomcat 内存设置 ------------- 修改 / bin/catalina.sh,增加如下设置: ``` JAVA_OPTS='-Xms【初始化内存大小】 -Xmx【可以使用的最大内存】' ``` 需要把这个两个参数值调大,大小的可以根据服务器内存的大小进行调整。例如: ``` JAVA_OPTS='-Xms1024m –Xmx2048m' ``` 服务器是 8G 内存,跑了 3 个 tomcat 服务,给分配了 2G 的内存,因为还有其他进程。 本篇文章写到这里差不多就结束了,当然也有很多东西还没有写到,不过限于篇幅也是没辙,我整理了很详细的 [**JVM、MySQL、NGINX 和 Tomcat 的学习笔记以及资料**](https://jq.qq.com/?_wv=1027&k=PjEVS3qt) , 需要的朋友直接点击领取就可以了。 最后,码字不易,所以,可以点个赞和收藏吗兄弟们! end
Jonny
2022年5月3日 12:31
245
0 条评论
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
如遇文档失效,可评论告知,便后续更新!
【腾讯云】2核2G云服务器新老同享 99元/年,续费同价
【阿里云】2核2G云服务器新老同享 99元/年,续费同价(不要✓自动续费)
【腾讯云】2核2G云服务器新老同享 99元/年,续费同价
【阿里云】2核2G云服务器新老同享 99元/年,续费同价(不要✓自动续费)
Markdown文件
Word文件
PDF文档
PDF文档(打印)
分享
链接
类型
密码
更新密码
有效期