LJKのBlog

学无止境

数据库组件(对象)

数据库、表、索引、视图、用户、存储过程、函数、触发器、事件调度器等

SQL 语言语法标准

命名规则

  • 必须以字母开头,可包括数字和三个特殊字符(# _ $);

  • 不要使用 MySQL 的保留字

SQL 语句分类

  • DDL:Data Defination Language 数据定义语言

    CREATE,DROP,ALTER

  • DML:Data Manipulation Language 数据操纵语言

    INSERT,DELETE,UPDATE

  • DQL:Data Query Language 数据查询语言

    SELECT

  • DCL:Data Control Language 数据控制语言

    GRANT,REVOKE,COMMIT,ROLLBACK

字符集和排序

uft8 最长占用 3 个字节,utf8mb4 最长占用 4 个字节,为了获取更好的兼容性,推荐使用 uft8mb4

以下排序规则:

  • utf8mb4_unicode_ci:基于标准的 Unicode 来排序和比较,能够在各种语言之间精确排序

  • utf8mb4_general_ci:没有实现 Unicode 排序规则,在遇到某些特殊语言或者字符集,排序结果可能不一致

  • utf8mb4_0900_ai_ci:比以上两种都好,mysql8.0 默认的排序规则,mysql8.0 以下版本不支持

所以:mysql8.0 使用默认排序,mysql8.0 以下版本设置 utf8mb4_general_ci

数据库

  • 创建数据库

    1
    create database [if not exists] 'db_name' character set 'utf8mb4' collate 'utf8mb4_general_ci';
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    10:20:30(root@localhost) [(none)]> create database if not exists blog character set utf8mb4 collate utf8mb4_general_ci;
    Query OK, 1 row affected (0.00 sec)

    10:20:36(root@localhost) [(none)]> show create database blog;
    +----------+------------------------------------------------------------------+
    | Database | Create Database |
    +----------+------------------------------------------------------------------+
    | blog | CREATE DATABASE `blog` /*!40100 DEFAULT CHARACTER SET utf8mb4 */ |
    +----------+------------------------------------------------------------------+
    1 row in set (0.01 sec)

    10:20:40(root@localhost) [(none)]> exit
    Bye
    [root@centos7 script]$cat /data/mysql/blog/db.opt
    default-character-set=utf8mb4
    default-collation=utf8mb4_general_ci
  • 修改数据库

    1
    alter database 'db_name' character set 'utf8mb4';

只能修改字符集,不能修改排序

  • 删除数据库

    1
    drop database [if exists] 'db_name';
  • 查看数据库列表

    1
    show databases;

数据类型

选择数据类型三大原则:

  1. 更小的通常更好,尽量使用可正确存储数据的最小数据类型
  2. 简单就好,简单数据类型的操作通常需要更少的 CPU 周期
  3. 尽量避免 NULL,包含为 NULL 的列,对 MySQL 更难优化

1. 整数

  • int(m)里的 m 表示 select 查询结果集中的显示宽度,并不影响实际的取值范围,规定了 MySQL 的一些交互工具(例如 MySQL 命令行客户端)用来显示字符的个数。对于存储和计算来说,Int(1)和 Int(20)是相同的
  • BOOL,BOOLEAN:布尔型,是 TINYINT(1)的同义词。zero 值被视为假,非 zero 值视为真

2. 浮点

float(m,d) 单精度浮点型 8 位精度(4 字节) m 总个数,d 小数位
double(m,d) 双精度浮点型 16 位精度(8 字节) m 总个数,d 小数位

3. 定点数

decimal(m,d) 十进制,精确值,总个数 m<65,小数位 d<30 且 d<\m

MySQL5.0 和更高版本将数字打包保存到一个二进制字符串中(每 4 个字节存 9 个数字)。

例如:
decimal(18,9)小数点两边将各存储 9 个数字,一共使用 9 个字节:其中,小数点前的 9 个数字用 4 个字节,小数点后的 9 个数字用 4 个字节,小数点本身占 1 个字节
浮点类型在存储同样范围的值时,通常比 decimal 使用更少的空间。float 使用 4 个字节存储。double 占用 8 个字节
因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用 decimal,例如存储财务数据。但在数据量比较大的时候,可以考虑使用 bigint 代替 decimal

4. 字符串

char(n):固定长度,最多 255 个字符

varchar(n):可变长度,最多 65535 个字符,很多人以为 n 最大是 255,其实不然,只是 varchar(255)是习惯性写法

tinytext:可变长度,最多 255 个字符

text:可变长度,最多 65535 个字符

mediumtext:可变长度,最多 2^24-1 个字符

longtext:可变长度,最多 2^32-1 个字符

BINARY(M):固定长度,可存储二进制或字符,长度为 0-M 字节

VARBINARY(M):可变长度,可存二进制或字符,允许长度为 0-M 字节

ENUM:枚举

SET:集合

char 和 varchar

  1. char(n) 若存入字符数小于 n,则以空格补于其后,查询之时再将空格去掉,所以 char 类型存储的字符串末尾不能有空格,varchar 不限于此

  2. .char(n) 固定长度,char(4)不管是存入几个字符,都将占用 4 个字节,varchar 是存入的实际字符数+1 个字节所以 varchar(4),存入 3 个字符将占用 4 个字节

  3. char 比 varchar 快

varchar 和 text

  1. varchar 可指定 n,text 不能指定,内部存储 varchar 是存入的实际字符数+1 个字节(n< n>255),text 是实际字符数+2 个字节。
  2. text 类型不能有默认值
  3. varchar 可直接创建索引,text 创建索引要指定前多少个字符。varchar 查询速度快于 text

5. 二进制 BLOB

  • TEXT 以文本方式存储,英文存储区分大小写,而 Blob 以二进制方式存储,不分大小写
  • BLOB 存储的数据只能整体读出
  • TEXT 可以指定字符集,BLOB 不用指定字符集

6. 日期时间

  • date:日期 ‘2008-12-2’
  • time:时间 ‘12:25:36’
  • datetime:日期时间 ‘2008-12-2 22:06:44’
  • timestamp:自动存储记录修改时间
  • YEAR(2), YEAR(4):年份
  • timestamp:字段里的时间数据会随其他字段修改的时候自动刷新,这个数据类型的字段可以存放这条记录最后被修改的时间

7. 修饰符

适用所有类型的修饰符:

  • DEFAULT 默认值、
  • NULL 数据列可包含 NULL 值,默认值
  • NOT NULL 数据列不允许包含 NULL 值,*为必填选项
  • PRIMARY KEY 主键,所有记录中此字段的值不能重复,且不能为 NULL
  • UNIQUE KEY 唯一键,所有记录中此字段的值不能重复,但可以为 NULL
  • CHARACTER SET name 指定一个字符集

适用数值型的修饰符:

  • AUTO_INCREMENT 自动递增,适用于整数类型
  • UNSIGNED 无符号

insert、update、select、delete

insert

  • 插入单条数据

    1
    2
    insert into 表名 values(值列表, 注意: 值的顺序一定要和表结构中的顺序保持一致);
    insert into 表名 (字段列表) values(值列表);

    注意:插入字段的个数,必须要与值的个数一致,字段的顺序可以与表结构中的字段顺序不一致,插入字段可以省略,但是省略的字段必须要有 default 值

  • 插入多条数据

    1
    2
    insert into 表名 values(),(),()...;
    insert into 表名(字段列表) values(),(),()...;
  • 从其他表中 copy 数据

    1
    2
    insert into 目标表 select * from 数据来源表;
    insert into 目标表(字段列表) select 字段列表 from 数据来源表;

    蠕虫复制:将自己表中的数据插入到自己表中,可以用来测试数据库性能

  • replace into

    主要是用来解决在添加数据时, 与主键或唯一键冲突的情况下,数据如何变化的问题

update

1
2
3
4
update 表名 set 字段1=1, 字段2=2,... where 条件;

# update:更新的关键字
# set: 设置 字段 =

注意:一定要指定更新的条件,如果没有指定则更新的是整张表

delete

1
delete from 表名 where 条件;    # 如果不加条件,则为清空表

另外:

1
truncate 表名;    # 清空表,主键也会被摧毁,重新开始

select

  • 单表操作

    select 五子句,每个子句都可以省略, 但是位置不能乱

    1
    2
    3
    4
    5
    6
    select 字段列表 from 表名 where 条件 group by 字段 having 条件 order by ASC | DESC limit m,n;

    # group by : 按照字段进行分组, 目的是为了统计, 通常配合MySQL中的聚合函数来使用。
    # having : 对分组之后的结果, 在进行一次过滤。
    # order byasc是升序,desc是降序
    # limit: limit n 是显示n条数据, limit m,n 是从m开始(不包含m),显示n条数据。

    别名: as
    运算符: =,>,<,<=,>=,!=
    is null 和 is not null
    in 和 not in
    between and 和 not between and
    逻辑判断: and(&&) or(||)
    聚合函数:聚合函数对一组值执行计算并返回单一的值。除了 count 以外,聚合函数会忽略空值。聚合函数经常和 group by 子句一起使用

    1
    2
    3
    4
    5
    count(*)    统计数量
    sum(字段) 求和
    avg(字段) 平均值
    min(字段) 求最小值
    max(字段) 求最大值
  • 多表操作

    join

    1
    2
    3
    4
    Select * from 表A  cross|inner |left outer|right outer  | natural |  join 表B  on 表A.字段名= 表B.字段名

    # 最常用的是左连接
    select * from 表A left join 表B on 表A.字段名 = 表B.字段名;

select 语句处理顺序:

练习:

练习
导入 hellodb.sql 生成数据库

  1. 在 students 表中,查询年龄大于 25 岁,且为男性的同学的名字和年龄
  2. 以 ClassID 为分组依据,显示每组的平均年龄
  3. 显示第 2 题中平均年龄大于 30 的分组及平均年龄
  4. 显示以 L 开头的名字的同学的信息
  5. 显示 TeacherID 非空的同学的相关信息
  6. 以年龄排序后,显示年龄最大的前 10 位同学的信息
  7. 查询年龄大于等于 20 岁,小于等于 25 岁的同学的信息
  8. 以 ClassID 分组,显示每班的同学的人数
  9. 以 Gender 分组,显示其年龄之和
  10. 以 ClassID 分组,显示其平均年龄大于 25 的班级
  11. 以 Gender 分组,显示各组中年龄大于 25 的学员的年龄之和
  12. 显示前 5 位同学的姓名、课程及成绩
  13. 显示其成绩高于 80 的同学的名称及课程
  14. 取每位同学各门课的平均成绩,显示成绩前三名的同学的姓名和平均成绩
  15. 显示每门课程课程名称及学习了这门课的同学的个数
  16. 显示其年龄大于平均年龄的同学的名字
  17. 显示其学习的课程为第 1、2,4 或第 7 门课的同学的名字
  18. 显示其成员数最少为 3 个的班级的同学中年龄大于同班同学平均年龄的同学
  19. 统计各班级中年龄大于全校同学平均年龄的同学

SHOW 语句

常用 show 语句:

  • 显示所有数据库

  • 显示数据库中所有表

    1
    2
    3
    show databases;
    show tables;
    show tables from database_name;
  • 显示数据表中所有列

    1
    show columns from database_name.table_name
  • 显示一个用户的权限,结果显示类似 grant 命令

    1
    2
    3
    4
    5
    6
    7
    8
    08:04:50(root@localhost) [(none)]> show grants for root@localhost;
    +---------------------------------------------------------------------+
    | Grants for root@localhost |
    +---------------------------------------------------------------------+
    | GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION |
    | GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION |
    +---------------------------------------------------------------------+
    2 rows in set (0.00 sec)
  • 显示表的索引

    1
    show index from mysql.user;
  • 显示一些系统特定资源的信息,例如,正在运行的线程数量

    1
    show status;
  • 显示系统变量的名称和值

    1
    show variables;
  • 显示系统中正在运行的所有进程,也就是当前正在执行的查询。大多数用户可以查看他们自己的进程,但是如果他们拥有 process 权限,就可以查看所有人的进程,包括密码

    1
    show processlist;
  • 显示 database 中的每个表的信息。信息包括表类型和表的最新更新时间

    1
    show table status from mysql;
  • 示服务器所支持的不同权限

    1
    show privileges;
  • 显示创建数据库的语句

  • 显示创建数据表的语句

    1
    2
    08:16:18(root@localhost) [(none)]> show create database mysql;
    08:16:18(root@localhost) [(none)]> show create table mysql.user;
  • 显示可用的存储引擎和默认引擎

    1
    08:19:02(root@localhost) [(none)]> show engines;
  • 显示 innoDB 存储引擎的状态

    1
    08:21:11(root@localhost) [(none)]> show engine innodb status;
  • 显示最后一个执行的语句所产生的错误、警告和通知

  • 显示最后一个执行语句所产生的错误

    1
    2
    show warnings;
    show errors;
  • 查看视图定义

VIEW 视图

虚拟表,保存实表的查询结果,相当于别名

  • 创建视图:

    1
    2
    3
    create view 视图名称 as 查询语句;
    # 例如
    create view v_age as * from tb_stu where age < 25;
  • 查看视图

    1
    2
    SHOW CREATE VIEW view_name #只能看视图定义
    SHOW CREATE TABLE view_name # 可以查看表和视图
  • 删除视图

    1
    DROP VIEW [IF EXISTS] view_name

FUNCTION 函数

分为系统内置函数和自定义函数

内置函数

参考:mysql8.0mysql5.7,注意:从 mysql5.7 开始,取消了 password()函数

常见内置函数:

  • database():当前所在数据库

  • now()

    1
    2
    3
    4
    5
    6
    7
    08:53:59(root@localhost) [mysql]> select now();
    +---------------------+
    | now() |
    +---------------------+
    | 2020-10-19 20:56:46 |
    +---------------------+
    1 row in set (0.00 sec)
  • curdate():当前日期

  • curtime():当前时间

  • datediff():时间差

    1
    2
    3
    4
    5
    6
    7
    08:53:42(root@localhost) [mysql]> select datediff('2020-10-19','2020-7-27');
    ------------------------------------+
    | datediff('2020-10-19','2020-7-27') |
    +------------------------------------+
    | 84 |
    +------------------------------------+
    1 row in set (0.00 sec)
  • version()

  • from_unixtime():时间戳转日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    08:48:39(root@localhost) [mysql]> select from_unixtime(1603111741);
    +---------------------------+
    | from_unixtime(1603111741) |
    +---------------------------+
    | 2020-10-19 20:49:01 |
    +---------------------------+
    1 row in set (0.00 sec)
    08:50:10(root@localhost) [mysql]> select from_unixtime(1603111741,'%Y-%m-%d %T');
    +-----------------------------------------+
    | from_unixtime(1603111741,'%Y-%m-%d %T') |
    +-----------------------------------------+
    | 2020-10-19 20:49:01 |
    +-----------------------------------------+
    1 row in set (0.00 sec)

文本处理函数:

  • trim():去除两边的空格
  • upper():返回大写字符
  • lower():返回小写字符
  • concat():返回连接字符串 ★★★
  • substring():返回截取的字符
  • repace(str, str1, str2):在 str 中, 将字符串 str1 替换为 str2

数学函数:

  • abs(x):返回 x 的绝对值
  • bin(x):返回 x 的二进制(oct 八进制, hex 返回十六进制)
  • ceiling(x):向上取整
  • floor(x):向下取整
  • mod(被除数, 除数):返回余数, 取模
  • pow(底数, 指数):返回求指数值

自定义函数

user-defined function,UDF,保存在 mysq.proc 表中

变量

两种变量:系统内置变量和用户自定义变量

  • 系统变量:MySQL 数据库中内置的变量,可用@@var_name 引用
  • 用户自定义变量分为以下两种
    • 普通变量:在当前会话中有效,可用@var_name 引用
    • 局部变量:在函数或存储过程内才有效,需要用 DECLARE 声明,之后直接用 var_name 引用

PROCEDURE 存储过程

多表 SQL 语句的集合,可以独立执行,存储过程保存在 mysql.proc 表中

流程控制

存储过程函数 中可以使用流程控制来控制语句的执行

  • IF:用来进行条件判断。根据是否满足条件,执行不同语句
  • CASE:用来进行条件判断,可实现比 IF 语句更复杂的条件判断
  • LOOP:重复执行特定的语句,实现一个简单的循环
  • LEAVE:用于跳出循环控制,相当于 SHELL 中 break
  • ITERATE:跳出本次循环,然后直接进入下一次循环,相当于 SHELL 中 continue
  • REPEAT:有条件控制的循环语句。当满足特定条件时,就会跳出循环语句
  • WHILE:有条件控制的循环语句

TRIGGER 触发器

触发器是由事件来触发某个操作, 这些事件包括 insert、update 和 delete 语句。当执行这些事件时,就会激活触发器执行响应的操作

Event 事件

用户管理

  • 用户创建,新建用户默认权限:USAGE

    1
    CREATE USER [IF NOT EXISTS] 'USERNAME'@'HOST' [IDENTIFIED BY 'password'];
  • 用户重命名

    1
    RENAME USER old_user_name TO new_user_name;
  • 删除用户

    1
    DROP USER [IF EXISTS] 'USERNAME'@'HOST'
  • 修改密码

    如果 mysql.user 表的 authentication_string 和 password 字段都保存密码,authentication_string 优先生效

    1
    2
    3
    # 方法一
    ALTER USER [IF EXISTS] 'root'@'localhost' IDENTIFIED BY \"${rootpwd}\";
    # 其他方法,略...
  • 忘记管理员密码的解决方法

    1. 重启 mysqld 时,使用选项 --skip-grant-tables --skip-networking
    2. 使用 update 命令修改 mysql.user 表,把管理员密码去掉
    3. 移除上述两个选项,然后重启
    4. 使用ALTER USER 'root'@'localhost' IDENTIFIED BY \"${rootpwd}\";设置密码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@c71 ~]$vim /etc/my.cnf
    [mysqld]
    skip-grant-tables
    skip-networking # MySQL8.0不需要此选项
    ...
    [root@c71 ~]$systemctl restart mysqld.service
    [root@c71 ~]$msyql -e "update mysql.user set authentication_string='' where user='root' and host='localhost';"
    [root@c71 ~]$vim /etc/my.cnf
    [mysqld]
    # skip-grant-tables
    # skip-networking # MySQL8.0不需要此选项
    ...
    [root@c71 ~]$systemctl restart mysqld.service
    [root@c71 ~]$mysql -e "alter user 'root'@'localhost' IDENTIFIED BY \"123456\";"

角色管理

给账户分配角色,更加方便管理多个同样权限的账户

语句 作用
CREATE ROLEDROP ROLE 创建和删除角色
GRANTREVOKE 给角色授权和取消授权
SHOW GRANTS 查看角色权限
SET DEFAULT ROLE 设置账户默认使用什么角色
SET ROLE 改变当前会话角色
CURRENT_ROLE()函数 显示当前会话的角色
mandatory_roles 和 activate_all_roles_on_login 系统变量 允许定义用户登陆时强制的或者激活授权的角色

权限管理 GRANT/REVOKE

三种授权方式:

  1. 给用户分配权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    GRANT
    priv_type [(column_list)][, priv_type [(column_list)]] ...
    ON [object_type] priv_level
    TO user_or_role [, user_or_role] ...
    [WITH GRANT OPTION]
    [AS user
    [WITH ROLE
    DEFAULT
    | NONE
    | ALL
    | ALL EXCEPT role [, role ] ...
    | role [, role ] ...
    ]
    ]
    • priv_type:权限类型,有很多种
      • column_list:有些权限作用与数据表的字段
    • object_type:表(TABLE)|函数(FUNCTION)|存储过程(PROCEDURE)
      • priv_level:
        • * | *.*:所有数据库
        • db_name.*:指定数据库中所有表
        • db_name.tbl_name:指定数据库的指定数据表
        • db_name.routine_name:指定数据库的函数、存储过程、触发器
    • with_option:设置权限某些参数,例如:
      • MAX_QUERIES_PER_HOUR count
      • MAX_UPDATES_PER_HOUR count
      • MAX_CONNECTIONS_PER_HOUR count
      • MAX_USER_CONNECTIONS count
  2. 给用户分配角色

    1
    2
    3
    GRANT role [, role] ...
    TO user_or_role [, user_or_role] ...
    [WITH ADMIN OPTION]
  3. 代理其他用户:这种情况暂时不研究

查看用户权限:

1
2
SHOW GRANTS FOR 'user'@'host';
SHOW GRANTS FOR CURRENT_USER[()];

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> show grants for root@localhost;
+---------------------------------------------------------------------+
| Grants for root@localhost |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION |
| GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION |
+---------------------------------------------------------------------+
2 rows in set (0.02 sec)

mysql> show grants for root@127.0.0.1;
+---------------------------------------------------+
| Grants for root@127.0.0.1 |
+---------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' | # 没有操作用户的权限
+---------------------------------------------------+
1 row in set (0.02 sec)

取消授权:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
REVOKE
priv_type [(column_list)][, priv_type [(column_list)]] ...
ON [object_type] priv_level
FROM user_or_role [, user_or_role] ...

REVOKE ALL [PRIVILEGES], GRANT OPTION
FROM user_or_role [, user_or_role] ...

REVOKE PROXY ON user_or_role
FROM user_or_role [, user_or_role] ...

REVOKE role [, role ] ...
FROM user_or_role [, user_or_role ] ...

user_or_role: {
user (see Section 6.2.4, “Specifying Account Names”)
| role (see Section 6.2.5, “Specifying Role Names”.
}

# 范例
REVOKE DELETE ON *.* FROM 'testuser'@'172.16.0.%';

设置权限后需要刷新权限:

1
FLUSH PRIVILEGES;

权限类型:

所有权限、管理类、程序类、数据库级别、表级别、字段级别

  • 所有权限:ALL PRIVILEGESALL

  • 管理类:CREATE USERFILESUPERSHOW DATABASESRELOADSHUTDOWNREPLICATION SLAVEREPLICATION CLIENTLOCK TABLESPROCESSCREATE TEMPORARY TABLES

  • 程序类:针对 FUNCTION、PROCEDURE、TRIGGER

    CREATEALTERDROPEXCUTE

  • 库和表级别:针对 DATABASE、TABLE

    ALTERCREATECREATE VIEWDROP INDEXSHOW VIEW

    WITH GRANT OPTION:能将自己获得的权限转赠给其他用户

  • 数据操作:SELECTINSERTDELETEUPDATE

  • 字段级别:SELECT(col1,col2,...)UPDATE(col1,col2,...)INSERT(col1,col2,...)

了解范式之前需要先了解依赖的概念:

完全依赖:如果只有一个主键,其他字段通过主键查询,如果是联合主键,其他字段通过所有主键查询

部分依赖:只针对联合主键,其他字段仅需要部分主键就能查询

传递依赖:假如字段 B 除了可以通过主键查询,也可以通过字段 A 查询,主键 –> 字段 A –> 字段 B,这样字段 B 和主键之间存在传递依赖关系

  1. 第一范式:无重复的列,确保每一列的原子性
  2. 第二范式:基于第一范式,消除部分依赖
  3. 第三范式:基于第二范式,消除传递依赖

范式的优点是避免数据冗余,节约空间。关于范式,要了解理论。
实际生产中,常第二范式和第三范式混用,也不会刻意保持字段的原子性,会根据业务添加冗余字段,这就是“反范式“。

安装方式可以选择:

  • 源码编译:手动指定各种编译参数,生产推荐使用
  • 二进制:解压并简单配置即可使用

初始化脚本提高安全性

mysql_secure_installation

  • 设置数据库管理员 root 口令
  • 禁止 root 远程登录
  • 删除 anonymous 用户帐号
  • 删除 test 数据库

MySQL 组成

1. 客户端程序

  • mysql:交互式 CLI 工具
  • mysqldump:备份工具,基于 mysql 协议向 mysqld 发起查询请求,并将查询得到的所有数据转换成 insert 等写操作语句保存在文本文件中
  • mysqladmin:基于 mysql 协议管理 mysqld
  • mysqlimport:数据导入工具

MyISAM 存储引擎管理工具:

  • myisamchk:检查 MyISAM 库
  • myisampack:打包 MyISAM 表,只读

msyql 运行命令类型

mysql 客户端可以运行两种命令:客户端命令和服务端命令

  • 客户端命令:本地执行,每个命令都有完整形式和简写形式

    1
    2
    3
    4
    mysql> \h, help
    mysql> \u,use
    mysql> \s,status
    mysql> \!,system
  • 服务端命令:通过 mysql 协议发往服务器执行并取回结果,命令末尾都必须使用命令结束符号,默认为分号

    1
    mysql>SELECT VERSION();

mysql 使用模式

mysql 客户端有两种使用模式:交互模式和脚本模式

  • 交互模式

    1
    2
    3
    [root@4710419222 test]# mysql -uUserName -pPassWord
    # 或使用 -e 参数
    [root@4710419222 test]# mysql -uUserName -pPassWord -e "sql command"
  • 脚本模式

    1
    2
    3
    4
    5
    # 将sql语句写入文件
    [root@4710419222 test]# mysql -uUserName -pPassWord < test.sql
    # 在交互模式下,使用source也可以调用文件,执行文件中的sql语句
    [root@4710419222 test]# mysql -uUserName -pPassWord
    MySQL [(none)]> source ~/test/test.sql

mysql 命令

1
mysql [OPTIONS] [database]

常用 OPTIONS:

  • -A, –no-auto-rehash 禁止补全
  • -u, –user= 用户名,默认为 root
  • -h, –host= 服务器主机,默认为 localhost
  • -p, –passowrd= 用户密码,建议使用-p,默认为空密码
  • -P, –port= 服务器端口
  • -S, –socket= 指定连接 socket 文件路径
  • -D, –database= 指定默认数据库
  • -C, –compress 启用压缩
  • -e “SQL“ 执行 SQL 命令
  • -V, –version 显示版本
  • -v –verbose 显示详细信息
  • –print-defaults 获取程序默认使用的配置

2. 服务端程序

  • mysqld_safe:
  • mysqld:
  • mysqld_multi:多实例,示例:mysqld_multi –example

服务端配置

三种配置方式:1. 启动时配置命令行选项; 2. 配置文件;3. set 变量

配置文件:

  • /etc/my.cnf # 全局配置
  • /etc/mysql/my.cnf # 全局配置
  • ~/.my.cnf # 个人配置

配置文件格式:

1
2
3
4
5
6
7
8
9
10
[mysqld]
# parameter = value
# _ 和 - 相同
# ON,TRUE,1意义相同;0,OFF,FALSE意义相同,不区分大小写
[mysqld_safe]
[mysqld_multi]
[mysql]
[mysqldump]
[server]
[client]

服务器监听两种 socket 地址:

  • ip socket: 监听在 tcp 的 3306 端口,支持远程通信 ,侦听 3306/tcp 端口可以在绑定有一个或全部接口 IP 上

  • unix sock: 监听在 sock 文件上,仅支持本机通信, 如:/var/lib/mysql/mysql.sock)

    说明:host 为 localhost 时自动使用 unix sock

3. 用户账号

mysql 用户账号由两部分组成:username@host

  • host:支持通配符,% 匹配任意长度任意字符,_ 匹配任意单个字符

    172.16.0.0/255.255.0.0 等于 172.16.%.%

高可用集群

集群类型

  • LB:load balance,负载均衡,例如 LVS / HAProxy / Nginx(http/upstream、stream/upstream)
  • HA:high availability 高可用集群
  • HPC:High Performance Computing,高性能集群,例如 超级计算机天河二号

系统可用性

SLA:Service-Level Agreement,服务等级协议,指标:99.9%, 99.99%, 99.999%,99.9999%

MTBF:Mean Time Between Failure,平均无故障工作时间

MTTR:Mean Time To Repair,平均故障时间

A = MTBF / (MTBF+MTTR)

1
2
# 99.95%
(60*24*30)*(1-0.9995)=21.6分钟 # 一般按一个月停机时间统计

HA Cluster 实现方案 VRRP

Virtual Router Redundancy Protocol,虚拟路由冗余协议,解决静态网关单点风险

  • 物理层:路由器、三层交换机
  • 软件层:keeplived

VRRP 网络硬件实现

参考:https://support.huawei.com/enterprise/zh/doc/EDOC1000141382/19258d72/basic-concepts-of-vrrp

VRRP 备份组示意图:

VRRP 相关术语

  • VRRP 路由器(VRRP Router):运行 VRRP 协议的设备,它可能属于一个或多个虚拟路由器,如 SwitchA 和 SwitchB。
  • 虚拟路由器(Virtual Router):又称 VRRP 备份组,由一个 Master 设备和多个 Backup 设备组成,被当作一个共享局域网内主机的缺省网关。如 SwitchA 和 SwitchB 共同组成了一个虚拟路由器。
  • Master 路由器(Virtual Router Master):承担转发报文任务的 VRRP 设备,如 SwitchA。
  • Backup 路由器(Virtual Router Backup):一组没有承担转发任务的 VRRP 设备,当 Master 设备出现故障时,它们将通过竞选成为新的 Master 设备,如 SwitchB。
  • VRID:虚拟路由器的标识。如 SwitchA 和 SwitchB 组成的虚拟路由器的 VRID 为 1。
  • 虚拟 IP 地址(Virtual IP Address):虚拟路由器的 IP 地址,一个虚拟路由器可以有一个或多个 IP 地址,由用户配置。如 SwitchA 和 SwitchB 组成的虚拟路由器的虚拟 IP 地址为 10.1.1.10/24。
  • IP 地址拥有者(IP Address Owner):如果一个 VRRP 设备将虚拟路由器 IP 地址作为真实的接口地址,则该设备被称为 IP 地址拥有者。如果 IP 地址拥有者是可用的,通常它将成为 Master。如 SwitchA,其接口的 IP 地址与虚拟路由器的 IP 地址相同,均为 10.1.1.10/24,因此它是这个 VRRP 备份组的 IP 地址拥有者。
  • 虚拟 MAC 地址(Virtual MAC Address):虚拟路由器根据虚拟路由器 ID 生成的 MAC 地址。一个虚拟路由器拥有一个虚拟 MAC 地址,格式为:00-00-5E-00-01-{VRID}(VRRP for IPv4);00-00-5E-00-02-{VRID}(VRRP for IPv6)。当虚拟路由器回应 ARP 请求时,使用虚拟 MAC 地址,而不是接口的真实 MAC 地址。如 SwitchA 和 SwitchB 组成的虚拟路由器的 VRID 为 1,因此这个 VRRP 备份组的 MAC 地址为 00-00-5E-00-01-01。

keepalived 初步介绍

vrrp 协议的软件实现,原生设计目的是为了高可用 ipvs(ip virtual server)服务

负载均衡有 LVS、Nginx、HAProxy 可以选择,但是冗余路由只能选择 keepalived,没有其他类似的软件可以选择

官网:http://keepalived.org

  • 基于 vrrp 协议完成地址流动
  • 为 vip 地址所在的节点生成 ipvs 规则(在配置文件中预先定义)
  • 为 ipvs 集群的各 RS 做健康状态检测
  • 基于脚本调用接口完成脚本中定义的功能,进而影响集群事务,以此支持 nginx、haproxy 等服务

keepalived 架构

用户空间的核心组件:

  • VRRP Stack:最重要的核心功能,实现了 VIP,利用 VIP 就可以做到 IP 地址的浮动,将来用户只需要访问 VIP,至于 VIP 是由哪个机器提供,利用 VRRP 协议可以控制,谁是主谁就提供 VIP
  • Checkers:状态监测,可以监测后端服务器(RS)的健康状况,而 LVS 是不提供这个功能的
  • System call:在 vrrp 协议状态转换时(就是主节点挂了,把备用节点提升为主节点的时候),可以调用脚本执行一些操作
  • SMTP:邮件组件,比如 当服务器挂了,可以发邮件报警
  • IPVS wrapper:管理 ipvs 规则
  • Netlink Reflector:网络接口,监控网卡状态,和网卡进行通信
  • WatchDog:监控其他组件,其他组件出现问题,由 watchdog 进行解决
  • 控制组件:提供 keepalived.conf 的解析器,完成 Keepalived 配置
  • IO 复用器:针对网络目的而优化的自己的线程抽象
  • 内存管理组件:为某些通用的内存管理功能(例如分配,重新分配,发布等)提供访问权限

keepalived 启动后会有三个进程:

  1. 父进程:内存管理,子进程管理等等
  2. 子进程:VRRP 子进程
  3. 子进程:healthchecker 子进程
1
2
3
Keepalived <-- Parent process monitoring children # 一个主进程
\_ Keepalived <-- VRRP child # 子进程,负责VRRP
\_ Keepalived <-- Healthchecking child # 子进程,负责健康性检查

有图可知,两个子进程都被系统 WatchDog 看管,两个子进程各自复杂自己的事,healthchecker 子进程复杂检查各自服务器的健康程度,例如 HTTP,LVS 等等,如果 healthchecker 子进程检查到 MASTER 上服务不可用了,就会通知本机上的兄弟 VRRP 子进程,让他删除通告,并且去掉虚拟 IP,转换为 BACKUP 状态。

keepalived 安装

1
2
3
4
5
6
7
8
9
10
11
12
[root@c71 ~]$yum install keepalived -y
[root@c71 ~]$rpm -ql keepalived
/etc/keepalived
/etc/keepalived/keepalived.conf # 配置文件
/etc/sysconfig/keepalived
/usr/bin/genhash
/usr/lib/systemd/system/keepalived.service
/usr/libexec/keepalived
/usr/sbin/keepalived # 主程序文件
...
各种配置示例文件
...

注意:CentOS7 上重启可能失败

1
2
systemctl restart keepalived # 可能失败,新配置无法生效
systemctl stop keepalived; systemctl start keepalived # 将重启分为停止和启动两步来执行

keepalived 配置文件

参考:https://blog.csdn.net/weixin_42256765/article/details/103223793

keepalived 配置文件:/etc/keepalived/keepalived.conf

1
2
3
global_defs  # 全局配置
vrrp_instance # vrrp配置
virtual_server # 和lvs相关的设置

生产环境复杂时,keepalived.conf 中内容过多,不易管理,可以将不同集群的配置放在单独的子配置文件,利用 include 指令包含子配置文件

1
2
3
4
5
6
7
8
9
10
[root@c71 ~]$cd /etc/keepalived/
[root@c71 keepalived]$mkdir conf.d
[root@c71 keepalived]$cat keepalived.conf
global_defs {
...
}
vrrp_instance VI_1 {
...
}
include ./conf.d/*.conf

全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
global_defs {
notification_email {
441757636@qq.com # keepalived发生故障切换时邮件发送的目标邮箱,可以按行区分写多个
}
notification_email_from keepalived@localhost # 发邮件的地址
smtp_server 127.0.0.1 # 邮件服务器地址
smtp_connect_timeout 30 # 邮件服务器连接timeout
# 以上邮箱相关的配置都是无效的,配置了也无法生效

router_id c71 # 每个keepalived主机唯一标识,建议使用当前主机名,但其实重名了也不影响
vrrp_skip_check_adv_addr # 默认对所有通告报文都检查,比较消耗性能,启用此配置后,如果收到的通告报文和上一个报文是同一个路由器,则跳过检查
vrrp_strict # 严格遵守VRRP协议,如果启用此项,以下三种状况将无法启动服务:1.无VIP地址 2.配置了单播邻居 3.在VRRP版本2中有IPv6地址。开启动此项并且没有配置vrrp_iptables时会自动开启iptables防火墙规则,默认导致VIP无法访问,建议注释掉此项配置
vrrp_garp_interval 0 # gratuitous ARP messages 报文发送延迟,0表示不延迟
vrrp_gna_interval 0 # unsolicited NA messages (不请自来)消息发送延迟
vrrp_mcast_group4 224.0.0.18 # 指定组播IP地址范围:224.0.0.0到239.255.255.255,如果有多组keepalived服务器集群,应该每个集群设置不同的组播地址,避免产生干扰
vrrp_iptables # 此项和vrrp_strict同时开启时,则不会添加防火墙规则,如果无配置vrrp_strict项,则无需启用此项配置
}

如果启用了vrrp_strict,也需要启用vrrp_iptables,否则因为防火墙,会导致生成的 VIP 无法访问

配置虚拟路由器

允许在一个物理服务器上安装多个 vrrp 实例,每个 vrrp 实例可以单独配置 VIP,例如,有的 vrrp 实例后端是 mysql 服务,有的 vrrp 实例后端是 nginx 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
vrrp_instance VI_1 { # 指定实例名,VI_1
state MASTER # 指定master或者backup,其实此项不起作用,真正起作用的是优先级 priority
interface eth0 # vrrp实例绑定的网口,因为在虚拟IP的时候必须是在已有的网口上添加
virtual_router_id 51 # 虚拟路由器的id,0-255,一个局域网内,要确保每个虚拟路由器的id都不同
priority 100 # 优先级,1-254,虚拟路由器中的每个keepalived节点的优先级都不同
advert_int 1 # 相互通告优先级,默认1s,超时则认为出故障
authentication { # 相互通告优先级,需要验证
auth_type PASS # 认证方式:PASS为简单密码(建议使用)
auth_pass 123456 # 密码
}
virtual_ipaddress { # 虚拟ip,生产环境可能指定上百个IP地址
# <IPADDR>/<MASK> brd <IPADDR> dev <STRING> scope <SCOPE> label <LABEL>
10.0.0.100/24 dev eth0 label eth0:1
}
# 跟踪接口,设置额外的监控,里面任意一块网卡出现问题,都会进入故障(FAULT)状态,切换主备状态,例如,用nginx做均衡器的时候,内网必须正常工作,如果内网出问题了,这个均衡器也就无法运作了,所以必须对内外网同时做健康检查
track_interface {
eth0
eth1
...
}
}

[root@c71 keepalived]$systemctl restart keepalived.service # 重启
[root@c71 keepalived]$ip a
...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:02:6d:64 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.71/24 brd 10.0.0.255 scope global noprefixroute eth0
valid_lft forever preferred_lft forever
inet 10.0.0.100/24 scope global secondary eth0:1 # 成功将vip10.0.0.100分配给eth0
valid_lft forever preferred_lft forever
inet6 fe80::f850:2415:92a0:3fe6/64 scope link noprefixroute
valid_lft forever preferred_lft forever
...

启用 keepalived 日志功能

每个节点都要统一以下配置:

1
2
3
4
5
6
7
8
9
[root@c71 log]$vim /etc/sysconfig/keepalived
KEEPALIVED_OPTIONS="-D -S 6" # 添加 "-S 6" 参数

[root@c71 log]$vim /etc/rsyslog.conf
...
local6.* /var/log/keepalived.log # 添加此行
...

[root@c71 log]$tail -f /var/log/keepalived.log # 查看是否记录日志

keepalived 企业应用

单主架构

两台服务器:10.0.0.71、10.0.0.72

master 配置

10.0.0.71

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
global_defs {
notification_email {
441757636@qq.com
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id c71 # c71
vrrp_skip_check_adv_addr
# vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
vrrp_mcast_group4 224.0.0.18
}

vrrp_instance VI_1 {
state MASTER # MASTER
interface eth0
virtual_router_id 1
priority 100 # 优先级
advert_int 1
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.0.100/24 dev eth0 label eth0:1
}
}

# include ./conf.d/*.conf

backup 配置

10.0.0.72

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 配置文件和master基本一致,只需修改三行
global_defs {
notification_email {
441757636@qq.com
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id c72 # c72
vrrp_skip_check_adv_addr
# vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
vrrp_mcast_group4 224.0.0.18
}

vrrp_instance VI_1 {
state BACKUP # BACKUP
interface eth0
virtual_router_id 1
priority 80 # 优先级
advert_int 1
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.0.100/24 dev eth0 label eth0:1
}
}

# include conf.d/*.conf

抓包观察

1
tcpdump -i eth0 -nn host 224.0.0.18  # 224.0.0.18 是组播地址

双主架构

上面的单主模式只有一个 VIP,如果是多个 VIP,为了提高服务器的利用率,将 master 和 backup 均匀分配到每一台服务器上,这种模式就是双主架构

两台服务器:10.0.0.71、10.0.0.72
两个 VIP:10.0.0.100/24、10.0.0.200/24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 10.0.0.71,VI_1 master,VI_2,backup
global_defs {
notification_email {
441757636@qq.com
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id c71 # c71
vrrp_skip_check_adv_addr
# vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
vrrp_mcast_group4 224.0.0.18
}

vrrp_instance VI_1 {
state MASTER # MASTER
interface eth0
virtual_router_id 1
priority 100 # 优先级
advert_int 1
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.0.100/24 dev eth0 label eth0:1
}
}

vrrp_instance VI_2 {
state BACKUP # BACKUP
interface eth0
virtual_router_id 1
priority 80 # 优先级
advert_int 1
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.0.200/24 dev eth0 label eth0:1
}
}

# include ./conf.d/*.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 10.0.0.72,VI_1 backup,VI_2 master,只需要修改5行,省略号表示配置一样的部分
global_defs {
...
router_id c72 # c72
# vrrp_strict
...
}

vrrp_instance VI_1 {
state BACKUP # MASTER
priority 80 # 优先级
...
}

vrrp_instance VI_2 {
state MASTER # MASTER
priority 100 # 优先级
...
}

# include ./conf.d/*.conf

抢占模式和非抢占模式

抢占式:preempt,高优先级的主机恢复在线后,会抢占低先级的主机的 master 角色,造成网络抖动

非抢占式:nopreempt,高优级主机恢复后,不会抢占低优先级主机的 master 角色

如果只有一个 VIP,建议设置为非抢占模式 nopreempt

非抢占模式

两台服务器:10.0.0.71、10.0.0.72
一个 VIP:10.0.0.100/24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 10.0.0.71
vrrp_instance VI_1 {
state BACKUP # BACKUP,两台服务器都是BACKUP
priority 100 # 优先级高
nopreempt # 指定模式:非抢占式
...
}

# 10.0.0.72
vrrp_instance VI_1 {
state BACKUP # BACKUP,两台服务器都是BACKUP
priority 80 # 优先级低
nopreempt # 指定模式:非抢占式
...
}

抢占延迟模式

优先级高的主机恢复后,不会立即抢回 VIP,而是延迟一段时间(默认 300s)再抢回 VIP

两台服务器:10.0.0.71、10.0.0.72
一个 VIP:10.0.0.100/24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 10.0.0.71
vrrp_instance VI_1 {
state BACKUP # BACKUP,两台服务器都是BACKUP
priority 100 # 优先级高
preempt_delay 60 # 抢占延迟模式,默认延迟300s
...
}

# 10.0.0.72
vrrp_instance VI_1 {
state BACKUP # BACKUP,两台服务器都是BACKUP
priority 80 # 优先级低
...
}

VIP 单播配置

keepalived 主机之间默认利用多播相互通告消息,会造成网络拥塞,可以替换成单播,减少网络流量

在所有节点 vrrp_instance 语句块中设置对方主机的 IP,建议设置为专用于对应心跳线网络的地址,而非使用业务网络

1
2
3
4
5
unicast_src_ip <IPADDR>  # 指定发送单播的源IP
unicast_peer {
<IPADDR> # 指定接收单播的对方目标主机IP
......
}

两台服务器:10.0.0.71、10.0.0.72
一个 VIP:10.0.0.100/24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 10.0.0.71
vrrp_instance VI_1 {
...
virtual_ipaddress {
10.0.0.10/24 dev eth0 label eth0:1
}
unicast_src_ip 10.0.0.71 # 本机IP
unicast_peer{
10.0.0.72 # 指向对方主机IP
10.0.0.82 # 如果有多个keepalived,再加其它节点的IP
}
}

# 10.0.0.72
vrrp_instance VI_1 {
...
virtual_ipaddress {
10.0.0.10/24 dev eth0 label eth0:1
}
unicast_src_ip 10.0.0.72 # 本机IP
unicast_peer{
10.0.0.71 # 指向对方主机IP
}
}

抓包:

1
tcpdump -i eth0 -nn host 10.0.0.71 and host 10.0.0.72

通知脚本配置

当 keepalived 的状态变化时,可以自动触发脚本(sh、py、php 各种脚本)的执行,比如:发邮件、短信、微信通知用户

1
2
3
4
5
global_defs {
......
# 指定脚本执行用户的身份,如果不指定,默认用户keepalived_script,如果此用户不存在,则root执行脚本
script_user <USER>
}

通知脚本类型

1
2
3
4
5
notify_master <STRING>|<QUOTED-STRING>  # 当前节点成为主节点时触发的脚本
notify_backup <STRING>|<QUOTED-STRING> # 当前节点转为备节点时触发的脚本
notify_fault <STRING>|<QUOTED-STRING> # 当前节点转为“失败”状态时触发的脚本
notify <STRING>|<QUOTED-STRING> # 通用格式的通知触发机制,一个脚本可完成以上三种状态的转换时的通知
notify_stop <STRING>|<QUOTED-STRING> # 当停止VRRP时触发的脚本

邮件服务

1
2
3
4
5
6
7
8
9
10
11
# 安装邮件服务
yum -y install mailx

# 配置/etc/mail.rc
set from=ljkk3014@foxmail.com
set smtp=smtp.qq.com
set smtp-auth-user=ljkk3014@foxmail.com
set smtp-auth-password=jwxxxxxxxxg

# 发送测试邮件
echo -e "Hello, I am `whoami`,The system version is here,please help me to check it ,thanks! \n`cat /etc/os-release`" | mail -s hello 441757636@qq.com

实战案例:实现 Keepalived 状态切换的通知脚本

在所有 keepalived 节点配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@c72 ~]$cat /etc/keepalived/notify.sh
#!/bin/bash

contact='441757636@qq.com'
notify() {
mailsubject="$(hostname) to be $1, vip floating"
mailbody="$(date +'%F %T'): vrrp transition, $(hostname) changed to be $1"
echo "$mailbody" | mail -s "$mailsubject" $contact
}
case $1 in
master)
notify master
;;
backup)
notify backup
;;
fault)
notify fault
;;
*)
echo "Usage: $(basename $0) {master|backup|fault}"
exit 1
;;
esac
[root@c72 ~]$cat /etc/keepalived/keepalived.conf
...
vrrp_instance VI_1 {
...
virtual_ipaddress {
10.0.0.100/24 dev eth0 label eth0:1
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}

# include conf.d/*.conf

# 将master下线
[root@c72 ~]$systemctl stop keepalived.service

查看邮箱:

实现 IPVS 的高可用性

keepalived 也提供了管理 IPVS 规则的功能,在实现 VIP 高可用的同时,还能实现 IPVS 的高可用。

所以 ipvsadm 就没什么用了

VS 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
virtual_server IP port    # 定义虚拟主机IP地址及其端口
virtual_server fwmark int # ipvs的防火墙打标,实现基于防火墙的负载均衡集群
virtual_server group string # 使用虚拟服务器组

virtual_server IP port {
...
real_server {
...
}
real_server {
...
}
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
virtual_server IP port {     # VIP和PORT
delay_loop <INT> # 检查后端服务器的时间间隔
lb_algo rr|wrr|lc|wlc|lblc|sh|dh # 定义调度方法
lb_kind NAT|DR|TUN # 集群的类型,注意要大写
persistence_timeout <INT> # 持久连接时长
protocol TCP|UDP|SCTP # 指定服务协议,一般为TCP
sorry_server <IPADDR> <PORT> # 所有RS故障时,备用服务器地址
real_server <IPADDR> <PORT> { # RS的IP和PORT
weight <INT> # RS权重
notify_up <STRING>|<QUOTED-STRING> # RS上线通知脚本
notify_down <STRING>|<QUOTED-STRING> # RS下线通知脚本
HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|MISC_CHECK { ... } #定义当前主机健康状态检测方法
}
}

VS 组

将多个 VS 定义成一个组,统一对外服务,如:http 和 https 定义成一个虚拟服务器组

示例文件:/usr/share/doc/keepalived-1.3.5/samples/keepalived.conf.virtual_server_group

应用层检测

应用层检测:http:HTTP_GET;https:SSL_GET

示例文件:
/usr/share/doc/keepalived-1.3.5/samples/keepalived.conf.HTTP_GET.port
/usr/share/doc/keepalived-1.3.5/samples/keepalived.conf.SSL_GET

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP_GET|SSL_GET {
url {
path <URL_PATH> # 定义要监控的URL
status_code <INT> # 判断上述检测机制为健康状态的响应码,一般为 200
}
connect_timeout <INTEGER> # 客户端请求的超时时长, 相当于haproxy的timeout server
nb_get_retry <INT> # 重试次数
delay_before_retry <INT> # 重试之前的延迟时长
connect_ip <IP ADDRESS> # 向当前RS哪个IP地址发起健康状态检测请求
connect_port <PORT> # 向当前RS的哪个PORT发起健康状态检测请求
bindto <IP ADDRESS> # 向当前RS发出健康状态检测请求时使用的源地址
bind_port <PORT> # 向当前RS发出健康状态检测请求时使用的源端口
}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
virtual_server 10.0.0.10 80 {
delay_loop 3
lb_algo rr
lb_kind DR
protocol TCP
sorry_server 127.0.0.1 80
real_server 10.0.0.7 80 {
weight 1
HTTP_GET {
url {
path /monitor.html
status_code 200
}
connect_timeout 1
nb_get_retry 3
delay_before_retry 1
}
}

real_server 10.0.0.17 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 1
nb_get_retry 3
delay_before_retry 1
}
}
}

TCP 检测

传输层检测:TCP_CHECK

1
2
3
4
5
6
7
TCP_CHECK {
connect_ip <IP ADDRESS> # 向当前RS的哪个IP地址发起健康状态检测请求
connect_port <PORT> # 向当前RS的哪个PORT发起健康状态检测请求
bindto <IP ADDRESS> # 发出健康状态检测请求时使用的源地址
bind_port <PORT> # 发出健康状态检测请求时使用的源端口
connect_timeout <INTEGER> # 客户端请求的超时时长, 等于haproxy的timeout server
}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
virtual_server 10.0.0.10 80 {
delay_loop 6
lb_algo wrr
lb_kind DR
#persistence_timeout 120 #会话保持时间
protocol TCP
sorry_server 127.0.0.1 80
real_server 10.0.0.7 80 {
weight 1
TCP_CHECK {
connect_timeout 5
nb_get_retry 3
delay_before_retry 3
connect_port 80
}
}
real_server 10.0.0.17 80 {
weight 1
TCP_CHECK {
connect_timeout 5
nb_get_retry 3
delay_before_retry 3
connect_port 80
}
}
}

案例

实现单主的 LVS-DR 模式
实现双主的 LVS-DR 模式
实现单主的 LVS-DR,实现单主的 LVS-DR 模式,利用 FWM 绑定成多个服务为一个集群服务

实现其他应用的高可用性 VRRP Script

示例文件:/usr/share/doc/keepalived-1.3.5/samples/keepalived.conf.vrrp.localcheck

调用外部的辅助性脚本进行资源监控,并根据监控的结果实现优先动态调整,从而实现其他应用的高可用性功能

vrrp_script

自定义资源监控脚本,一般放在 gloal_defs 设置块之后

1
2
3
4
5
6
7
8
9
10
vrrp_script <SCRIPT_NAME> {
script <STRING>|<QUOTED-STRING> # shell命令或脚本路径,返回值为0即检测成功
interval <INTEGER> # 间隔时间,单位为秒,默认1秒
timeout <INTEGER> # 超时时间
weight <INTEGER:-254..254> # 权重,配合vrrp_instance的priority调整vrrp实例的优先级:当设置为负数,如果标记KO,vrrp_instance的优先级=priority+weight,优先级降低;当设置为正数,如果标记KO,vrrp_instance的优先级=priority+weight,优先级提高;默认为0,通常使用负值。
fall <INTEGER> # 脚本几次失败就标记KO,建议设为2以上
rise <INTEGER> # 脚本连续监测成功几次,就标记OK
user USERNAME [GROUPNAME] # 执行监测脚本的用户或组
init_fail # 设置默认标记为失败状态,监测成功之后再转换为成功状态
}

track_script

调用脚本,调用 vrrp_script 定义的脚本去监控资源,定义在 vrrp 实例内

1
2
3
4
5
6
vrrp_instance VI_1 {
...
track_script {
SCRIPT_NAME
}
}

示例:利用脚本实现主从角色切换

两台服务器:10.0.0.71、10.0.0.72
一个 VIP:10.0.0.100/24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 10.0.0.71
global_defs {
notification_email {
441757636@qq.com
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id c71 # 10.0.0.72设置为c72
vrrp_skip_check_adv_addr
# vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
vrrp_mcast_group4 224.0.0.18
}

vrrp_script check_down {
script "[ ! -f /etc/keepalived/down ]" # /etc/keepalived/down存在时返回非0,触发权重-30
interval 1 # 每秒执行一次script
weight -30
fall 3 # 当script执行三次失败,priority = 100 - 30
rise 2
timeout 2
}

vrrp_instance VI_1 {
state MASTER # 10.0.0.72设置为BACKUP
interface eth0
virtual_router_id 1
priority 100 # 10.0.0.72设置为80
advert_int 1
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
10.0.0.100/24 dev eth0 label eth0:1
}
track_interface {
eth0
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
track_script {
check_down # 调用前面定义的脚本
}
}

示例:实现单主模式的 Nginx 反向代理的高可用

示例:实现双主模式 Nginx 反向代理的高可用

实现 HAProxy 高可用

示例:实现 MySQL 双主模式的高可用

同步组

LVS NAT 模型 VIP 和 DIP 需要同步,需要同步组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vrrp_sync_group VG_1 {
group {
VI_1 # name of vrrp_instance (below)
VI_2 # One for each moveable IP
}
}
vrrp_instance VI_1 {
eth0
vip
}
vrrp_instance VI_2 {
eth1
dip
}

综合实战案例

Keepalived + LVS

  • 编译安装 keepalived
  • 实现 keepalived(20 个以上的 VIP) + Nginx 双主高可用
  • 实现 keepalived(60 个以上的 VIP) + HAProxy 三服务器高可用
  • 实现 keepalived(100 个以上的 VIP) + LVS 高可用、Real Server 状态监测及规则管理

Keepalived + HAProxy

  • 编译安装 HAProxy 较新 LTS 版本,选择编译安装 keepalived
  • 开启 HAProxy 多进程,进程数与 CPU 核心数保持一致并实现 HAProxy 进程绑定
  • 因业务较多避免配置文件误操作,需要按每业务一个配置文件并统一保存至/etc/haproxy/conf 目录中
  • 实现 keepalived include 导入配置文件功能,使用 LVS-DR 模型代理后端 Nginx web 服务器
  • 基于 ACL 实现单 IP 多域名负载功能(适用于企业较少公网 IP 多域名场景)
  • 实现 MySQL 主从复制,并通过 HAProxy 对 MySQL 进行反向代理
  • 最终完成 HAProxy+Nginx+Tomcat+ Redis,并实现 session 会话保持统一保存到 Redis

HAProxy 的 frontend 的 bind 绑定 Keepalived 的 VIP

MyCAT 集群方案

默认的 Linux 内核参数不符合 web 服务器高并发的要求,Nginx 作为静态 web 内容服务器、反向代理服务器或者提供压缩服务的服务器时,内核参数的调整是不一样的,下面是针对更多并发请求的 TCP 网络做的参数优化

优化内核参数

https://blog.csdn.net/bytxl/article/details/46437363

修改/etc/sysctl.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fs.file-max = 1000000 # 表示单个进程较大可以打开的句柄数
net.ipv4.tcp_tw_reuse = 1 # 参数设置为1,表示允许将TIME_WAIT状态的socket重新用于新的TCP链接,这对于服务器来说意义重大,因为总有大量TIME_WAIT状态的链接存在
net.ipv4.tcp_keepalive_time = 600 # 当keepalive启动时,TCP发送keepalive消息的频度;默认是2小时,将其设置为10分钟,可更快的清理无效链接
net.ipv4.tcp_fin_timeout = 30 # 当服务器主动关闭链接时,socket保持在FIN_WAIT_2状态的较大时间
net.ipv4.tcp_max_tw_buckets = 5000 # 表示操作系统允许TIME_WAIT套接字数量的较大值,如超过此值,TIME_WAIT套接字将立刻被清除并打印警告信息,默认为8000,过多的TIME_WAIT套接字会使Web服务器变慢
net.ipv4.ip_local_port_range = 1024 65000 # 定义UDP和TCP链接的本地端口的取值范围
net.ipv4.tcp_rmem = 10240 87380 12582912 # 定义了TCP接受缓存的最小值、默认值、较大值
net.ipv4.tcp_wmem = 10240 87380 12582912 # 定义TCP发送缓存的最小值、默认值、较大值
net.core.netdev_max_backlog = 8096 # 当网卡接收数据包的速度大于内核处理速度时,会有一个列队保存这些数据包。这个参数表示该列队的较大值

net.core.rmem_default = 6291456 # 表示内核套接字接受缓存区默认大小
net.core.wmem_default = 6291456 # 表示内核套接字发送缓存区默认大小
net.core.rmem_max = 12582912 # 表示内核套接字接受缓存区较大大小
net.core.wmem_max = 12582912 # 表示内核套接字发送缓存区较大大小,
# 注意:以上的四个参数,需要根据业务逻辑和实际的硬件成本来综合考虑

net.ipv4.tcp_syncookies = 1 # 与性能无关。用于解决TCP的SYN攻击
net.ipv4.tcp_max_syn_backlog = 8192 # 这个参数表示TCP三次握手建立阶段接受SYN请求列队的较大长度,默认1024,将其设置的大一些可使出现Nginx繁忙来不及accept新连接时,Linux不至于丢失客户端发起的链接请求
net.ipv4.tcp_tw_recycle = 1 # 这个参数用于设置启用timewait快速回收
net.core.somaxconn=262114 # 选项默认值是128,这个参数用于调节系统同时发起的TCP连接数,在高并发的请求中,默认的值可能会导致链接超时或者重传,因此需要结合高并发请求数来调节此值。
net.ipv4.tcp_max_orphans=262114 # 选项用于设定系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。如果超过这个数字,孤立链接将立即被复位并输出警告信息。这个限制指示为了防止简单的DOS攻击,不用过分依靠这个限制甚至人为的减小这个值,更多的情况是增加这个值

张 sir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# tcp conn
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syn_retries = 3
tcp_retries1 = 3
tcp_retries2 = 15

# tcp conn reuse
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 1

net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_max_orphans= 262114
net.ipv4.tcp_synack_retries= 1
net.ipv4.tcp_syncookies = 1

# keepalive conn
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.ip_local_port_range = 10001 65000

# swap
vm.overcommit_memory = 0
vm.swappiness = 0

PAM 资源限制优化

/etc/security/limits.conf 最后增加:

1
2
3
4
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
1
2
3
4
5
6
root soft core unlimited
root hard core unlimited
root soft nproc 65535
root hard nproc 65535
root soft nofile 65535
root hard nofile 65535

反向代理:reverse proxy,指的是代理外网用户的请求到内部指定的服务器,并将数据返回给用户的一种方式,这是用的比较多的一种方式

nginx 除了可以在企业提供高性能的 web 服务之外,还可以将 nginx 本身不具备的请求通过某种预定义的协议转发至其他服务器处理,不同的协议就是 nginx 服务器进行通信的一种规范,主要在不同的场景使用以下模块实现不同的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将客户端的请求以http协议转发至指定服务器进行处理
ngx_http_proxy_module

# 允许定义一组服务器。它们可以在指令proxy_pass、 fastcgi_pass和 memcached_pass中被引用到
ngx_http_upstream_module

# 将客户端的请求以tcp协议转发至指定服务器处理
ngx_stream_proxy_module

# 将客户端对php的请求以fastcgi协议转发至指定服务器助理
ngx_http_fastcgi_module

# 将客户端对Python的请求以uwsgi协议转发至指定服务器处理
ngx_http_uwsgi_module

逻辑调用关系:

生成环境部署结构:

访问逻辑图:

http 反向代理

反向代理配置参数

  • proxy_pass

    设置后端服务器的协议和地址,协议支持 http 和 https,地址既可以是域名也可以是 ip,还可以配置端口

    语法: proxy_pass URL;
    默认值: —
    上下文: location, if in location, limit_except

    URL 分为两种:结尾加”/“和结尾不加”/“,如果不加”/“,proxy_pass 类似 root,如果加”/“,proxy_pass 类似 alias

    范例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    location /web {
    # http://to2b.cn/web/index.html ==> http://127.0.0.0.1:8080/web/index.html
    proxy_pass http://127.0.0.1:8080
    }

    location /web {
    # http://to2b.cn/web/index.html ==> http://127.0.0.0.1:8080/index.html
    proxy_pass http://127.0.0.1:8080/
    }

    # 如果location定义uri时使用了正则表达式模式(包括~和~*),则结尾不能加"/"
    location ~|~* /uri/ {
    proxy_pass http://host:port; # proxy_pass后面的url 不能加/
    }
  • proxy_hide_header

    nginx 作为反向代理服务器时,在返回给客户端 http 响应时,隐藏后端服务器响应头的信息

    语法: proxy_hide_header field;
    默认值: —
    上下文: http, server, location

    示例:隐藏后端服务器的 ETag 首部字段

    1
    2
    3
    4
    5
    location /web {
    index index.html;
    proxy_pass http://10.0.0.18:8080/;
    proxy_hide_header ETag;
    }
  • proxy_pass_header

    允许传送被屏蔽的后端服务器响应头到客户端。

    默认 nginx 在响应报文中不传递后端服务器的首部字段 Date, Server, X-Pad, X-Accel 等参数,如果要传递的话则要使用proxy_pass_header field声明将后端服务器返回的值传递给客户端

    field 首部字段大小不敏感

    语法: proxy_pass_header field;
    默认值: —
    上下文: http, server, location

  • proxy_pass_request_body

    是否向后端服务器发送 HTTP 实体部分,默认开启

    Syntax: proxy_pass_request_body on | off;
    Default: proxy_pass_request_body on;
    Context: http, server, location

  • proxy_pass_request_headers

    是否将客户端的请求头部转发给后端服务器

    Syntax: proxy_pass_request_headers on | off;
    Default: proxy_pass_request_headers on;
    Context: http, server, location

  • proxy_set_header

    可更改或添加客户端的请求头部信息内容并转发至后端服务器,比如在后端服务器想要获取客户端的真实 IP 的时候,就要更改每一个报文的头部

    语法: proxy_set_header field value;
    默认值: proxy_set_header Host $proxy_host;
    proxy_set_header Connection close;
    上下文: http, server, location

    示例:

    1
    2
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
  • proxy_connect_timeout

    配置 nginx 服务器与后端服务器尝试建立连接的超时时间,默认为 60 秒,超时会返回客户端 504 响应码

    语法: proxy_connect_timeout time;
    默认值: proxy_connect_timeout 60s;
    上下文: http, server, location

    示例:

    1
    proxy_connect_timeout 6s;
  • proxy_read_timeout

    定义从后端服务器读取响应的超时。此超时是指相邻两次读操作之间的最长时间间隔,而不是整个响应传输完成的最长时间。如果后端服务器在超时时间段内没有传输任何数据,连接将被关闭

    语法: proxy_read_timeout time;
    默认值: proxy_read_timeout 60s;
    上下文: http, server, location

  • proxy_send_timeout

    定义向后端服务器传输请求的超时。此超时是指相邻两次写操作之间的最长时间间隔,而不是整个请求传输完成的最长时间。如果后端服务器在超时时间段内没有接收到任何数据,连接将被关闭

    语法: proxy_send_timeout time;
    默认值: proxy_send_timeout 60s;
    上下文: http, server, location

  • proxy_http_version

    语法: proxy_http_version 1.0 | 1.1;
    默认值: proxy_http_version 1.0;
    上下文: http, server, location

    设置代理使用的 HTTP 协议版本。默认使用的版本是 1.0,而 1.1 版本则推荐在使用 keepalive 连接时一起使用

  • proxy_ignore_client_abort

    决定当客户端在响应传输完成前就关闭连接时,nginx 是否应关闭后端连接,默认为 off

    当客户端网络中断请求时,nginx 服务器中断其对后端服务器的请求。即如果设置 on 开启,服务器会忽略客户端中断并一直等着代理服务执行返回,如果设置 off,则客户端中断后 Nginx 也会中断客户端请求并立即记录 499 日志

    语法: proxy_ignore_client_abort on | off;
    默认值: proxy_ignore_client_abort off;
    上下文: http, server, location

  • proxy_headers_hash_bucket_size

    当配置了 proxy_hide_header 和 proxy_set_header 的时候,用于设置 nginx 保存 HTTP 报文头的 hash 表的上限,默认 64 字节

    示例:

    1
    proxy_headers_hash_bucket_size 128;
  • proxy_headers_hash_max_size

    设置 proxy_headers_hash_bucket_size 的最大可用空间,默认 512 字节

  • server_names_hash_bucket_size

    server_name hash 表申请空间大小

  • server_names_hash_max_size

    设置服务器名称 hash 表的上限大小

缓存 proxy_buffering 和 proxy_cache

为了方便,我们定义三个角色:A 客户端 browser、B 代理服务器 Nginx,C 被代理服务器 PHP

proxy_buffering:缓冲,实现被代理服务器的数据和客户端的请求异步,A发起请求到B,B再去请求C,C反馈的数据先到 B 的 buffer 上

proxy_cache:缓存,B 将从 C 获取到的数据缓存起来,之后 A 再请求时,直接将缓存的数据返回,而不必再向 C 去获取

缓冲

  • proxy_buffering

    开启缓冲,默认开启,如果不开启,C 返回的数据实时的通过 B 发送给 A

    proxy_buffering 如果设置为 off,proxy_buffers 和 proxy_busy_buffers_size 这两个指令将会失效

  • proxy_buffers

    设置 B 的缓冲区占用的 buffer 的个数和每个 buffer 的大小,所以缓冲区的大小为这两个数字的乘积

    示例:

    1
    proxy_buffers 8 4k;  # 缓冲区总的大小为32k
  • proxy_busy_buffers_size

    proxy_busy_buffers_size 通常设置为两个 buffer 的大小,示例:

    1
    proxy_busy_buffers_size 8k;

    上例中,proxy_busy_buffers_size 设置为 8K,如果返回的数据小于 8K,则 B 从 C 请求的数据全部到位后,一次性返回给 A;如果返回的数据大于 8K,则 B 从 C 请求的数据分每次 8K 返回给 A,例如一共 20K 数据,分 8K、8K、4K 三次返回给 A

  • proxy_buffer_size

    该参数用来设置一个特殊 buffer 大小,C 到 B 的第一部分相应数据就存在这个 buffer 中,通常是 header,如果该参数设置太小,会出现 502 错误,不论 proxy_buffering,是否生效,proxy_buffer_size 都生效

    示例:

    1
    proxy_buffer_size 4k; # 通常设置为单个buffer的大小
  • proxy_temp_path

    缓冲区的容量毕竟有限,如果并发请求太多,响应的数据量太大,超出缓冲区的数据会储存在临时目录下

    proxy_temp_path 定义临时目录

    语法: proxy_temp_path path [level1 [level2 [level3]]];
    默认值: proxy_temp_path proxy_temp;
    上下文: http, server, location

    至多设置三层子目录,目录的命名规则和 client_body_temp_path 一样

  • proxy_max_temp_file_size

    临时文件的最大容量,默认 1024m,也就是 1G

  • proxy_temp_file_write_size

    每次写入临时文件的数据量, 通常和 proxy_busy_buffers_size 设置的值一样,两个 buffer 的大小

缓存

  • proxy_cache

    指定用于页面缓存的共享内存。同一块共享内存可以在多个地方使用。off 参数可以屏蔽从上层配置继承的缓存功能

    语法: proxy_cache zone | off;
    默认值: proxy_cache off;
    上下文: http, server, location

    zone 是缓存区域的名称,需要 proxy_cache_path 事先定义

  • proxy_cache_path

    语法: proxy_cache_path path [levels=levels] keys_zone=name:size [inactive=time] [max_size=size] [loader_files=number] [loader_sleep=time] [loader_threshold=time];
    默认值: —
    上下文: http

    示例:

    1
    2
    3
    4
    5
    6
    7
    proxy_cache_path /var/cache/nginx/proxy_cache levels=1:2:2 keys_zone=proxycache:20m inactive=120s max_size=1g

    # /var/cache/nginx/proxy_cache 定义缓存保存路径,proxy_cache会自动创建
    # levels=1:2:2 定义缓存目录结构层次,1:2:2可以生成2^4x2^8x2^8=2^20=1048576个目录
    # keys_zone=proxycache:20m 内存中缓存的大小,主要用于存放key和metadata
    # inactive=120s 缓存有效时间
    # max_size=1g 最大磁盘占用空间,磁盘存入文件内容的缓存空间最大值
  • proxy_cache_key

    定义如何生成缓存的键,默认值就挺好,一般不用设置

    语法: proxy_cache_key string;
    默认值: proxy_cache_key $scheme$proxy_host$request_uri;
    上下文: http, server, location

  • proxy_cache_valid

    为不同的响应状态码设置不同的缓存时间

    语法: proxy_cache_valid [code …] time;
    默认值: —
    上下文: http, server, location

    示例:

    1
    2
    3
    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 404 1m;
    proxy_cache_valid any 1m;
  • proxy_cache_use_stale

    如果后端服务器出现状况,nginx 可以使用过期的响应缓存。这条指令就是定义何种条件下允许开启此机制

    语法: proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_404 | off …;
    默认值: proxy_cache_use_stale off;
    上下文: http, server, location

    示例:

    1
    proxy_cache_use_stale error http_502 http_503;
  • proxy_cache_methods

    对哪些客户端请求方法对应的响应进行缓存,虽然 GET 和 HEAD 方法总是被缓存,但是建议显式的指定

    示例:

    1
    proxy_cache_methods GET | HEAD | POST ...;

添加头部报文信息

基于模块 ngx_http_headers_module,可以实现对后端服务器响应给客户端的报文中添加指定的响应首部字段

Syntax: add_header name value [always];
Default: —
Context: http, server, location, if in location

示例:

1
2
3
4
5
6
add_header X-Via $server_addr;      # 当前nginx主机的IP
add_header X-Cache $upstream_cache_status; # 是否缓存命中
add_header X-Accel $server_name; # 客户访问的FQDN

# 添加自定义响应信息的尾部,使用较少,1.13.2版后支持
add_trailer name value [always];

反向代理高级应用

基于 ngx_http_upstream_module 模块,实现服务器分组转发、权重分配、状态监测、调度算法等高级功能

  • upstream

    定义一组服务器。 这些服务器可以监听不同的端口。 而且,监听在 TCP 和 UNIX 域套接字的服务器可以混用

    语法: upstream name { … }
    默认值: —
    上下文: http

    示例:

    1
    2
    3
    4
    5
    upstream backend {
    server backend1.example.com weight=5;
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
    }
  • server

    在 upstream 内,至少要有一个 server 服务器配置

    语法: server address [parameters];
    默认值: —
    上下文: upstream

    定义服务器的地址和其他参数,地址可以是域名、ip、unix 套接字,如果没有指定端口,默认 80 端口

    如果一个域名解析到了多个 ip,本质就是在 upstream 定义了多个 server

    parameters:

    • weight=number:设定服务器的权重,默认是 1
    • max_conns=number:给当前 server 设置最大活动链接数,默认为 0 表示没有限制
    • max_fails=number:fail_timeout 规定时间内,对后端服务器连续监测失败的次数,超过则标记为不可用,默认为 1 次,当客户端访问时,才会利用 TCP 触发对探测后端服务器健康性检查,而非周期性的探测
    • fail_timeout=time:fail_timeout 时间内,连续监测失败 max_fails 次;或者 fail_timeout 时间内,一直监测失败,则标记服务器不可用
    • backup:标记为备用服务器,当其他服务器都不可用时,才会启用此服务器
    • down:标记为 down 状态
    • resolve:如果 server 定义的是主机名,当 A 记录发生变化会自动应用新 IP 而不用重启 Nginx

    示例:

    1
    2
    3
    4
    5
    6
    7
    upstream backend {
    server backend1.example.com weight=5;
    # 30s内,对后端服务器连续监测失败3次,标记次服务器不可用
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
    server backup1.example.com:8080 backup;
    }
  • hash

    Syntax: hash key [consistent];
    Default: —
    Context: upstream

    基于指定的 key 做 hash 计算,key 来自请求报文中首部字段或者 URI,consistent 定义使用一致性 hash 运算,一
    致性 hash 基于取模运算,适用于后端是 Cache 服务器(如 varnish)时使用

    示例:

    1
    2
    hash $request_uri consistent; # 基于用户请求的uri做hash
    hash $cookie_sessionid; # 基于cookie中的sessionid这个key进行hash调度,实现会话绑定

  • ip_hash

    源地址 hash 调度方法,基于的客户端的 remote_addr(源地址 IPv4 的前 24 位或整个 IPv6 地址)做 hash 计算,以实现会话保持

  • least_conn

    最少连接调度算法,优先将客户端请求调度到当前连接最少的后端服务器,相当于 LVS 中的 WLC

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
http {
upstream webserver {
#hash $request_uri consistent;
#hash $cookie_sessionid
#ip_hash;
#least_conn;
server 10.0.0.101:80 weight=1 fail_timeout=5s max_fails=3; #后端服务器状态监测
server 10.0.0.102:80 weight=1 fail_timeout=5s max_fails=3;
#server 127.0.0.1:80 weight=1 fail_timeout=5s max_fails=3 backup;
}
server {
listen 80;
server_name www.magedu.org;
location / {
index index.html index.php;
root /data/nginx/html/pc;
}
location /web {
index index.html;
proxy_pass http://webserver/; # 结尾加"/",proxy_pass类似alias
}
}

反向代理客户端 IP 透传

1
2
3
4
5
6
7
8
9
10
11
12
13
# 第一个代理服务器 10.0.0.8
location / {
proxy_pass http://10.0.0.18;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# 第二个代理服务器 10.0.0.18
location / {
proxy_pass http://10.0.0.28;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# 第三个服务器 10.0.0.28,最终会接收到客户端和中间两个代理服务器的ip

nginx 把客户端的请求轮询到不同的服务器,对于已经登录的客户端(用户信息储存在 cookie),就不适合轮询了,应该始终访问储存登录信息的那台服务器

1
2
3
4
5
6
7
8
9
10
http {
upstream websrvs {
hash $cookie_PHPSESSID; # PHPSESSID是cookie的key的名称
server 10.0.0.101:80 weight=2;
server 10.0.0.102:80 weight-1;
}
server {
proxy_pass http://websrvs;
}
}

tcp 负载均衡

基于 ngx_stream_proxy_module 模块,允许通过 TCP、UDP、和 unix 域套接字代理数据流,nginx1.9.0 版本支持

编译安装需要添加 –with-stream 编译选项

stream

没有在官方文档中找到这个参数,但是它确实存在,和 http 同级别,位于 main 上下文

负载均衡 redis

1
2
3
4
5
6
7
8
9
10
11
12
13
stream {
upstream redis_server {
#hash $remote_addr consistent;
server 10.0.0.18:6379 max_fails=3 fail_timeout=30s;
server 10.0.0.28:6379 max_fails=3 fail_timeout=30s;
}
server {
listen 10.0.0.8:6379;
proxy_connect_timeout 3s;
proxy_timeout 3s;
proxy_pass redis_server; # server处于stream上下文,这里proxy_pass的服务器组没有http
}
}

负载均衡 mysql

1
2
3
4
5
6
7
8
9
10
11
12
stream {
upstream mysql_server {
least_conn;
server 10.0.0.18:3306 max_fails=3 fail_timeout=30s;
}
server {
listen 10.0.0.8:3306;
proxy_connect_timeout 6s;
proxy_timeout 15s;
proxy_pass mysql_server;
}
}

FastCGI

CGI 和 FastCGI

最早的 web 服务器只能处理静态文件,对于 php、java 这样的动态语言文件,apache 实现的方式是打补丁,nginx 则通过某种特定的协议将客户端请求转发给语言解析器,这个特定的协议就是 CGI(通用网关接口 common gateway interface),语言解析器新建进程处理请求

CGI 协议解决了语言解析器和 web 服务器之间通讯的问题,但是它的效率太低,因为每处理一个请求,就要新建进程,处理完之后就销毁进程,而 FastCGI 解决了这个问题,每次处理完请求后不会关闭进程,等待处理下一个进程,直到超时没有任务才会被销毁

php-fpm

FastCGI Process Manager,FastCGI 进程管理器,是一个实现了 FastCGI 的程序,提供进程管理功能,进程包括 master 进程和 worker 进程。master 进程只有一个,负责监听端口,接受来自 web server 的请求。worker 进程一般会有多个,每个进程中会嵌入一个 PHP 解析器,进行 PHP 代码的处理

FastCGI 配置指令

1
2
3
4
5
6
7
8
9
fastcgi_pass address:port; # 请求转发到后端服务器,指定后端服务器的地址和端口
fastcgi_index name; # 默认的主页资源
fastcgi_param parameter value [if_not_empty]; # 设置传递的参数
# 示例
fastcgi_param REMOTE_ADDR $remote_addr; #客户端源IP
fastcgi_param REMOTE_PORT $remote_port; #客户端源端口
fastcgi_param SERVER_ADDR $server_addr; #请求的服务器IP地址
fastcgi_param SERVER_PORT $server_port; #请求的服务器端口
fastcgi_param SERVER_NAME $server_name; #请求的server name

动静分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 静态文件
location / {
proxy_pass http://10.0.0.28;
index index.html;
}
# 动态文件
location ~ \.php$ {
root /data/php;
fastcgi_pass 10.0.0.18:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /data/php$fastcgi_script_name;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

rewrite 是 nginx 服务器的重要功能之一,用于实现 URL 的重写,URL 的重写是非常有用的功能,比如它可以在我们改变网站结构之后,不需要客户端修改原来的书签,也无需其他网站修改我们的链接,就可以设置为访问,另外还可以在一定程度上提高网站的安全性

基于 ngx_http_rewrite_module 模块解析和处理 rewrite 请求,依赖 PCRE,因此编译之前要先安装 PCRE 库

ngx_http_rewrite_module 模块指令

ngx_http_rewrite_module 模块指令:if、set、break、return、rewrite、rewrite_log

下文统称为 rewrite 指令集

if 指令

条件判断,根据判断结果选择不同的 nginx 配置,可以配置在 server 或 location 块中,只能做单次判断,不支持使用 if else 这样的多重判断

1
if (condition) {...}

使用正则表达式对变量进行匹配,匹配成功时条件为 true,否则为 fasle,变量与表达式之间使用以下符号链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
=     # 变量和字符串比较,相等为true
!= # 变量和字符串比较,不相等为true
~ # 通过正则表达式匹配,满足匹配条件为真,区分大小写
!~ # 通过正则表达式匹配,不满足匹配条件为真,区分大小写

~* # 通过正则表达式匹配,满足匹配条件为真,不区分大小写
!~* # 通过正则表达式匹配,不满足匹配条件为真,不区分大小写

-f 和 !-f # 判断请求的文件是否存在
-f 和 !-d # 判断请求的目录是否存在
-x 和 ! -x # 判断文件是否可执行
-e 和 !-e # 判断请求的文件或目录是否存在(包括文件,目录,软链接)

# 注意:
# 变量值为空字符或者0为false,其他情况为true
# nginx1.0.1版本之前,变量值不能以0开头则为false,例如 $var=0xxx 返回false

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location /main {
index index.html;
default_type text/html;
if ( $scheme = http ){
echo "if-----> $scheme";
}
if ( $scheme = https ){
echo "if ----> $scheme";
}
#if (-f $request_filename) {
# echo "$request_filename is exist";
#}
if (!-e $request_filename) {
echo "$request_filename is not exist";
#return 409;
}
}

set 指令

变量赋值:set $var value,value 可以是字符串,可以是变量,也可以是两者结合

范例:

1
2
3
4
5
6
7
8
9
location /main {
root /data/nginx/html/pc;
index index.html;
default_type text/html;
set $name magedu;
echo $name;
set $my_port $server_port;
echo $my_port;
}

break 指令

break 用于 server、location、if

break 的作用是中断重定向,具体表现就是:

  • server 或 if 中使用 break:break 后面的 rewrite 指令集全部失效,注意 location 不属于 rewrite 指令集,location 之外的 break 影响不到 location 内的指令
  • location 中使用 break:不影响此 location 中后面的所有指令,一般搭配 rewrite 指令使用,直接将重定向的结果返回给客户端

break 和 last

两者通常都搭配 rewrite 指令使用,区别在于:

break 不会再去匹配其他 location,而是直接将重定向的结果返回给客户端,而 last 则继续重定,返回最后一个 location 的匹配结果给客户端

示例 1:break

1
2
3
4
5
6
location /last {
root /data/nginx; # 这里只能用root,alias不能和rewrite搭配
index index.html;
# /data/nginx/last重定向到/data/nginx/test1,将/data/nginx/test1中的内容返回给客户端
rewrite ^/last/(.*) /test1/$1 break;
}

示例 2:last

1
2
3
4
5
6
7
location /test1 {
default_type text/html;
return 200 "test1"; # 最终返回客户端的结果
}
location /last {
rewrite ^/last/(.*) /test1/$1 last; # 重定向到location /test1
}

return 指令

完成对请求的处理,直接向客户端返回响应状态码,return 后面的所有指令都不被执行

语法: return code [text];
return code URL;
return URL;
默认值: —
上下文: server, location, if

rewrite_log 指令

设置是否记录 ngx_http_rewrite_module 模块日志记录到 error_log 日志文件当中

语法: rewrite_log on | off;
默认值: rewrite_log off;
上下文: http, server, location, if

rewrite 指令

通过正则表达式的匹配来改变 URI,可以同时存在一个或多个指令,按照顺序依次对 URI 进行匹配,rewrite 主要是针对用户请求的 URL 或者是 URI 做具体处理

语法: rewrite regex replacement [flag];
默认值: —
上下文: server, location, if

注意:如果在同一级配置块中存在多个 rewrite 规则,那么会自上而下逐个检查;被某条件规则替换完成后,会重新一轮的替换检查,隐含有循环机制,但不超过 10 次;如果超过,提示 500 响应码,[flag]所表示的标志位用于控制此循环机制

要注意避免出现超过十次以及 URL 重写后返回错误的给用户

如果替换后的 URL 是以 http://或 https://开头,则替换结果会直接以重定向返回给客户端, 即永久重定向 301

302:浏览器不会缓存当前域名的解析记录
301:浏览器会缓存永久重定向的 DNS 解析记录

flag

rewrtie 有四种不同的 flag,分别是 redirect(临时重定向 302)、permanent(永久重定向 301)、break 和 last。其中前两种是跳转型的 flag,后两种是代理型

  • 跳转型指由客户端浏览器重新对新地址进行请求
  • 代理型是在 WEB 服务器内部实现跳转

正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.    # 匹配除换行符以外的任意字符
\w # 匹配字母或数字或下划线或汉字
\s # 匹配任意的空白符
\d # 匹配数字
\b # 匹配单词的开始或结束
^ # 匹配字付串的开始
$ # 匹配字符串的结束
* # 匹配重复零次或更多次
+ # 匹配重复一次或更多次
? # 匹配重复零次或一次
(n) # 匹配重复n次
{n,} # 匹配重复n次或更多次
{n,m} # 匹配重复n到m次
*? # 匹配重复任意次,但尽可能少重复
+? # 匹配重复1次或更多次,但尽可能少重复
?? # 匹配重复0次或1次,但尽可能少重复
{n,m}? # 匹配重复n到m次,但尽可能少重复
{n,}? # 匹配重复n次以上,但尽可能少重复
\W # 匹配任意不是字母,数字,下划线,汉字的字符
\S # 匹配任意不是空白符的字符
\D # 匹配任意非数字的字符
\B # 匹配不是单词开头或结束的位置
[^x] # 匹配除了x以外的任意字符
[^magedu] # 匹配除了magedu 这几个字母以外的任意字符

案例:自动跳转 https

1
2
3
if ($scheme = http ){       # 如果没有加条件判断,会导致死循环
rewrite / https://$host redirect;
}

案例:判断文件是否存在

1
2
3
4
if (!-e $request_filename) {
rewrite .* http://www.magedu.org/index.html; # 实现客户端浏览器的302跳转
#rewrite .* /index.html; # web服务器内部跳转
}

防盗链

防盗链基于客户端携带的 referer 实现,referer 是记录打开一个页面之前记录是从哪个页面跳转过来的标记信息,如果别人只链接了自己网站图片或某个单独的资源,而不是打开了网站的整个页面,这就是盗链,referer 就是之前的那个网站域名,正常的 referer 信息有以下几种

1
2
3
4
5
none # 请求报文首部没有referer首部,比如用户直接在浏览器输入域名访问web网站,就没有referer信息
blocked # 请求报文有referer首部,但无有效值,比如为空
server_names # referer首部中包含本主机名即nginx监听的server_name
arbitrary_string # 自定义指定字符串,但可使用\*作通配符。示例: \*.magedu.org www.magedu.\*
regular expression # 被指定的正则表达式模式匹配到的字符串,要使用~开头,例如:~.*\.magedu\.com

实现防盗链

基于访问安全考虑,nginx 支持通过 ngx_http_referer_module 模块,检查访问请求的 referer 信息是否有效实现防盗链功能

1
2
3
4
5
6
7
location /images {
alias /data/images/;
valid_referers none blocked server_names lujinkai.cn ~\.baidu\.; #定义有效的referer
if ($invalid_referer) {
return 403;
}
}

Referer 请求头为指定值时,内嵌变量$invalid_referer 被设置为空字符串, 否则这个变量会被置成“1”。查找匹配时不区分大小写

状态页

状态页用于输出 nginx 的基本状态信息

基于 ngx_http_stub_status_module 模块实现,编译安装时需要添加 –with-http_stub_status_module 编译参数

注意:状态页显示的是整个服务器的状态,而非单独某个虚拟机的状态

输出信息示例:

1
2
3
4
Active connections: 2
server accepts handled requests
35836 35836 27001
Reading: 0 Writing: 1 Waiting: 1
  • Active connections:当前处于活动状态的客户端连接数,reading + writing + waiting
    • Reading:正在读取客户端请求报文首部的连接的连接数,数值越大,说明排队现象严重,性能不足
    • Writing:正在向客户端发送响应报文过程中的连接数,数值越大,说明访问量很大
    • Waiting:正在等待客户端发出请求的空闲连接数,如果开启 keep-alive,这个值等于 active – (reading+writing)
  • server accepts handled requests:三个值分别对应 accepts、handled、requests
    • accepts:Nginx 自启动后已经接受的客户端请求的总数
    • handled:Nginx 自启动后已经处理完成的客户端请求总数,通常等于 accepts,除非有因
      worker_connections 限制等被拒绝的连接
    • requests:Nginx 自启动后客户端发来的总的请求数

第三方模块

第三模块是对 nginx 的功能扩展,第三方模块需要在编译安装 Nginx 的时候使用参数–add-module=PATH 指定路径添加,nginx 支持第三方模块需要从源码重新编译支持

范例:支持开源的 echo 模块 https://github.com/openresty/echo-nginx-module

1
2
3
4
5
6
7
8
9
[root@centos8 src]$ git clone https://github.com/openresty/echo-nginx-module.git
[root@centos8 src]$ ./configure \
--prefix=/user/local/nginx \
...
--add-module=/usr/local/src/echo-nginx-module # 指定模块源代码路径
[root@centos8 src]# make && make install
[root@centos8 ~]# /apps/nginx/sbin/nginx -t
[root@centos8 ~]# systemctl restart nginx
[root@centos8 ~]#nginx -V

变量使用

nginx 的变量可以在配置文件中引用,作为功能判断或者日志等场景使用

变量可以分为内置变量和自定义变量

内置变量是由 nginx 模块自带,通过变量可以获取到众多的与客户端访问相关的值

内置变量

http://nginx.org/en/docs/varindex.html

http://tengine.taobao.org/nginx_docs/cn/docs/http/ngx_http_core_module.html#variables

常用内置变量:

  • $remote_addr:客户端的公网 IP

  • $proxy_add_x_forwarded_for

    X-Forwarded-For:简称 XFF,只有在通过 http 代理或者负载均衡服务器时才会添加该项

    如果请求头中有 XFF,$proxy_add_x_forwarded_for 就是XXF头的值追加$remote_addr,所以,如果请求头中没有 XFF,$proxy_add_x_forwarded_for 就等于 $remote_addr

    1
    2
    # client1是真实的客户端ip,后面的是经过的代理或者负载均衡的ip,经过几个就会出现几个
    X-Forwarded-For: client, proxy1, proxy2
    1
    2
    3
    4
    5
    # 范例:
    location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 实现ip透传
    proxy_pass http://36.10.10.118:9015;
    }
  • $args:url 中的 get 参数

    例如 http://ljk.cn/main/index.do?id=1&partner=search,$args就是id=1&partner=search

  • $document_root:当前请求的root指令或alias指令的配置

  • $document_uri:和$uri 相同,当前请求中不包含参数的 URI

    例如 http://ljk.cn/main/index.do?id=1&partner=search,$document_uri就是/main/index.do

  • $host:请求的 host 名称

  • $limit_rate:limit_rate 项配置的网络速率,默认为 0

  • $remote_port:客户端请求 Nginx 服务器时随机打开的端口,这是每个客户端自己的端口

  • $remote_user:已经经过 Auth Basic Module 验证的用户名

  • $request_body_file:做反向代理时发给后端服务器的本地资源的名称

  • $request_method:请求资源的方式,GET/PUT/DELETE 等

  • $request_filename:当前请求的资源文件的磁盘路径,由 root 或 alias 指令与 URI 请求生成的文件绝对路径,如:/apps/nginx/html/main/index.html

  • $request_uri:包含请求参数的原始 URI,不包含主机名,相当于:$document_uri?$args,例如如:/main/index.do?id=20190221&partner=search

  • $scheme:请求的协议,例如:http、https、ftp 等

  • $server_protocol:保存了客户端请求资源使用的协议的版本,例如:HTTP/1.0,HTTP/1.1,HTTP/2.0 等

  • $server_addr:保存了服务器的 IP 地址

  • $server_name:请求的服务器的主机名

  • $server_port:请求的服务器的端口号

  • $http_<name>:name 为任意请求报文首部字段,表示记录请求报文的首部字段,用下划线代替横线

    示例:echo $http_User_Agent

  • $http_user_agent:客户端浏览器的详细信息

  • $http_cookie:客户端的 cookie 信息

  • $cookie_<name>:name 为任意请求报文首部字部 cookie 的 key 名

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
location /main {
default_type text/html;
echo "hello world,main-->";
echo $remote_addr ;
echo $args ;
echo $document_root;
echo $document_uri;
echo $host;
echo $http_user_agent;
echo $http_cookie;
echo $request_filename;
echo $scheme;
echo $scheme://$host$document_uri?$args;
}

自定义变量

语法: set variable value;
默认值: —
上下文: server, location, if

范例:

1
2
set $name magedu;
set $my_port $server_port;

自定义访问日志

错误日志(error_log)一般只有一个,但是访问日志可以在不同的 server 中定义多个,access_log 指定日志的保存路径,log_format 指定日志的格式,格式中定义要保存的具体日志内容

访问日志由 ngx_http_log_module 模块实现

自定义访问日志的格式:

语法: log_format name [escape=default|json|none] string …;
默认值: log_format combined “…”;
上下文: http

支持三种转义方式:

  1. default:,\ 以及小于 32 或大于 126 的字符被转义为\xXX
  2. json:所有 json 中不被允许的字符都进行转义,被转义成 \\被转义成\\,值小于 32 的字符转义成\n\r\tbf\u00XX
  3. none:禁止转义

combined 是内置的默认格式,以空格分割字符串形式记录日志

1
2
3
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';

自定义访问日志的路径以及其他配置:

语法: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
access_log off;
默认值: access_log logs/access.log combined;
上下文: http, server, location, if in location, limit_except

format:默认 combined
buffer=size:缓冲区大小,默认 64K
gzip=level:压缩等级,1-9,数值越大,压缩率最高,压缩最慢,默认为 1,可以使用zcat解压缩
flush=time:缓冲区的数据写入磁盘的间隔时间,必须搭配 buffer=size 一起使用,时间到了,即使缓冲区还没满,也得写入磁盘
if=condition:其他条件

范例:

1
access_log /path/to/log.gz combined gzip flush=5m;
1
2
3
4
5
map $status $loggable {
~^[23] 0;
default 1;
}
access_log /path/to/access.log combined if=$loggable;

自定义 json 格式日志

Nginx 的默认访问日志记录内容相对比较单一,默认的格式也不方便后期做日志统计分析,生产环境中通常将 nginx 日志转换为 json 日志,然后配合使用 ELK 做日志收集-统计-分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
log_format access_json '{"@timestamp":"$time_iso8601",'
'"host":"$server_addr",'
'"clientip":"$remote_addr",'
'"size":$body_bytes_sent,'
'"responsetime":$request_time,' # 总的处理时间
'"upstreamtime":"$upstream_response_time",'
'"upstreamhost":"$upstream_addr",' # 后端应用服务器处理时间
'"http_host":"$host",'
'"uri":"$uri",'
'"xff":"$http_x_forwarded_for",'
'"referer":"$http_referer",'
'"tcp_xff":"$proxy_protocol_addr",'
'"http_user_agent":"$http_user_agent",'
'"status":"$status"}';
access_log /apps/nginx/logs/access_json.log access_json;

json 格式的日志访问统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
#coding:utf-8
status_200= []
status_404= []
with open("access_json.log") as f:
for line in f.readlines():
line = eval(line)
if line.get("status") == "200":
status_200.append(line.get)
elif line.get("status") == "404":
status_404.append(line.get)
else:
print("状态码 ERROR")
print((line.get("clientip")))
f.close()

print("状态码200的有--:",len(status_200))
print("状态码404的有--:",len(status_404))

Nginx 压缩功能

Nginx 支持对指定类型的文件进行压缩然后再传输给客户端,有助于降低出口带宽的利用率,降低企业的 IT 支出,不过会占用相应的 CPU 资源

依赖于模块 ngx_http_gzip_module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#启用或禁用gzip压缩,默认关闭
gzip on | off;
#压缩比由低到高从1到9,默认为1
gzip_comp_level level;
#禁用IE6 gzip功能
gzip_disable "MSIE [1-6]\.";
#gzip压缩的最小文件,小于设置值的文件将不会压缩,默认单位为字节 1K=1024
gzip_min_length 1k;
#启用压缩功能时,协议的最小版本,默认HTTP/1.1
gzip_http_version 1.0 | 1.1;
#指定Nginx服务需要向服务器申请的缓存空间的个数和大小,平台不同,默认:32 4k或者16 8k;
gzip_buffers number size;
#指明仅对哪些类型的资源执行压缩操作;默认为gzip_types text/html,不用显示指定,否则出错
gzip_types mime-type ...;
#如果启用压缩,是否在响应报文首部插入“Vary: Accept-Encoding”,一般建议打开
gzip_vary on | off;

# Nginx作为反向代理的时候启用,开启或者关闭后端服务器返回的结果,匹配的前提是后端服务器必须要返回包含”Via”的 header头。
gzip_proxied off | expired | no-cache | no-store | private | no_last_modified | no_etag | auth | any ...;
# off – 关闭所有的代理结果数据的压缩
# expired – 启用压缩,如果header头中包含 “Expires” 头信息
# no-cache – 启用压缩,如果header头中包含 “Cache-Control:no-cache” 头信息
# no-store – 启用压缩,如果header头中包含 “Cache-Control:no-store” 头信息
# private – 启用压缩,如果header头中包含 “Cache-Control:private” 头信息
# no_last_modified – 启用压缩,如果header头中不包含 “Last-Modified” 头信息
# no_etag – 启用压缩 ,如果header头中不包含 “ETag” 头信息
# auth – 启用压缩 , 如果header头中包含 “Authorization” 头信息
# any – 无条件启用压缩

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gzip on;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml
text/javascript application/javascript application/x-javascript
text/x-json application/json application/x-web-app-manifest+json
text/css text/plain text/x-component
font/opentype application/x-font-ttf application/vnd.ms-fontobject
image/x-icon;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";

https

https = http + ssl / tls

https 的实现过程可以参考前面的关于“安全和加密”部分的笔记

基于模块 ngx_http_ssl_module 实现,yum 安装默认开启,编译安装则需要指定编译参数 –with-http_ssl_module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ssl on | off; # 开启ssl功能,这个参数1.15版本已经废除了,应该使用 listen ssl 替代
listen 443 ssl; # 替代ssl指令,同时指定端口
ssl_certificate /path/to/file; # 证书文件,一般是crt或者pem文件
ssl_certificate_key /path/to/file; # 私钥文件,一般是key文件
# 支持ssl协议版本,早期为ssl现在是TLS,默认为后三个
ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2];
ssl_session_timeout time; # 客户端连接可以复用ssl session cache中缓存的有效时长,默认5m
# 配置ssl缓存
ssl_session_cache off | none | [builtin[:size]] [shared:name:size];
# off:关闭缓存
# none:通知客户端支持ssl session cache,但实际不支持
# builtin[:size]:使用OpenSSL内建缓存,为每worker进程私有
# [shared:name:size]:在各worker之间使用一个共享的缓存,需要定义一个缓存名称和缓存空间大小,一兆可以存储4000个会话信息,多个虚拟主机可以使用相同的缓存名称

范例:实现 hsts https://www.cnblogs.com/sunsky303/p/8862600.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server {
listen 80;
listen 443 ssl http2;
ssl_certificate /usr/local/nginx/conf/ssl/xcx.to2b.cn.pem;
ssl_certificate_key /usr/local/nginx/conf/ssl/xcx.to2b.cn.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_timeout 10m;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_buffer_size 1400;
# Strict-Transport-Security, 即hsts,强制https的意思
# 一年内,浏览器自动将http请求转成https
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_stapling on;
ssl_stapling_verify on;
server_name xcx.to2b.cn;
access_log /data/wwwlogs/xcx.to2b.cn_nginx.log combined;
index index.html index.htm index.php;
root /data/wwwroot/xcx.to2b.cn/public;
# 第一次http请求,需要重定向到https
if ( $scheme = http ) {rewrite ^/(.*)$ https://www.magedu.org/$1 redirect;}
...
...
}

关于 favicon.ico

favicon.ico 文件是浏览器收藏网址时显示的图标,当客户端使用浏览器问页面时,浏览器会自己主动发起请求获取页面的 favicon.ico 文件,但是当浏览器请求的 favicon.ico 文件不存在时,服务器会记录 404 日志,而且浏览器也会显示 404 报错

1
2
3
4
5
6
location = /favicon.ico {
log_not_found off;
access_log off;
root /data/nginx/html/pc/images;
expires 365d; #设置文件过期时间
}

官方帮助文档:http://nginx.org/en/docs/

中文帮助文档:http://tengine.taobao.org/nginx_docs/cn/docs/ 不是很全

语法:

  1. 指令必须分号结尾

  2. 内建变量由 nginx 模块引入,可直接饮用;自定义变量由用户使用 set 命令自定义

    $variable_name 的形式饮用变量

配置文件结构

主配置文件 nginx.conf,包括四部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1.main: 主配置段,即全局配置段,对http和mail都有效
...
# 事件驱动相关配置
event {
...
}

# 2.http/https协议相关配置
http {
...
}

# 3.mail协议相关配置,默认不包括
mail {
...
}

# 4.stream服务器相关配置
stream {
...
}

配置项说明

说明:在文档中,每个配置项都有详细的说明

范例:user 配置项,只能用于全局配置中

语法: user user [group];
默认值: user nobody nobody;
上下文: main

范例:charset 配置项,可以用于 http、server、location 配置中

语法: charset charset | off;
默认值: charset off;
上下文: http, server, location, if in location

全局配置

上下文是 main,全局生效,包括事件驱动相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
user www www;
# 启动worker进程的数量,注意并不是设置越大越好,建议设置为auto,worker进程数等于cpu核心数量
worker_processes [number | auto];
# 将worker进程和cpu核心绑定,避免了worker进程在不同的cpu核心上来回跳转,建议设置,可以有效提升nginx性能
# 0001:0号CPU、0010:1号CPU
worker_cpu_affinity 0001 0010 0100 1000;
# 错误日志配置
# error_log file [debug | info | notice | warn | error | crit | alert | emerg]
error_log /data/wwwlogs/error_nginx.log crit;
# pid文件保存路径
pid /var/run/nginx.pid;
# 工作进程优先级,-20~19,越小则优先级越高,建议调小
worker_priority 0;
# 所有worker进程能打开的文件数量上限,注意这里设置的值不能超过`ulimit -n`显示的值,否则没有意义,系统默认打开的最大文件数为1024,建议调大,具体怎么调整,查询其他笔记中的ulimit相关知识
worker_rlimit_nofile 65536;
# 前台运行Nginx服务,主要用于测试、docker等环境
daemon off;
# #是否开启Nginx的master-worker工作模式,仅用于开发调试场景,默认为on
master_process off|on;

# 事件驱动相关配置
events {
# nginx支持众多事件驱动,推荐设置为epoll
use epoll;
# 单个worker进程的最大并发连接
worker_connections 51200;
# 默认off,新请求回唤醒所有worker进程(惊群),推荐设置为on,worker进程轮流处理新请求
accept_mutex on;
# 默认off,推荐设置为on,一个worker进程可以同时接收多个新的网络连接
multi_accept on;
}

http 配置

1
2
3
4
5
6
7
8
9
http {
# server的公共配置
...
...
# 每个server用于定义一个虚拟主机,第一个server为默认的虚拟服务器
server {
...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
http {
# 导入支持的文件类型,mime.types的格式:大类/小类 后缀
include mime.types;
# mime.types中没有的文件类型,默认为dafault_type设置的文件类型
default_type application/octet-stream;
# 默认off,不显示,建议改为utf-8,在响应头会显示:Content-Type: text/html; charset=utf-8
charset utf-8;
# 配置响应头的server,on:‘nginx/版本’;off:‘nginx’,不显示版本
server_tokens on | off;

sendfile on; # 开启零拷贝技术,减少I/O次数
tcp_nopush on; # 在开启了sendfile的情况下,合并请求后统一发给客户端
# 设置会话保持时间,第二个值为响应头的keep-Alived:timeout值,可以和第一个值不同,可省略
# https://cloud.tencent.com/developer/article/1541434
keepalive_timeout 120;
# 在一次长连接上所允许请求的资源的最大数量,默认为100次,建议适当调大,比如:500
keepalive_requests 500;
tcp_nodelay off; # off:延迟0.2s发送;on:立即发送,推荐设置为on,提高网络利用率

server {
listen 80;
# 支持通配符,支持正则(主机名前加‘~’)
# 处理请求头没有host字段的请求,server_name设置为空“”
# ‘_’可以匹配所有的请求,其实也没什么特别,‘_’只是一个非法域名,当然也可以使用“--”和“!@#”等等
# http://tengine.taobao.org/nginx_docs/cn/docs/http/server_names.html
server_name localhost;
access_log logs/host.access.log main; # 日志设置 main是日志格式
# location / { } 可以省略,将root和index放到server上下文中
location / {
root html;
index index.html index.htm;
}
# 根目录使用root,其他子目录适合使用alias更直观
location /about {
# root about # http://host/about 访问的是about/about/index.html
alias about; # http://host/about 访问的是about/index.html
}
location /img {
default_type text/html;
alias /data/wwwroot/default/qiniu/upload.html;
}
# 定义错误页面
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location /50x.html {
root html;
}

}
}

商业版本的 nginx 中,可以自定义 nginx 的名称,免费的社区版本不支持,但是可以通过修改源码自定义:

1
2
3
4
5
6
7
server_tokens on:修改 src/core/nginx.h 修改第13-14行,如下示例
#define NGINX_VERSION "1.68.9"
#define NGINX_VER "lnginx/" NGINX_VERSION

server_tokens off:修改 src/http/ngx_http_header_filter_module.c第49行,如下示例:
static char ngx_http_server_string[] = "Server: nginx" CRLF;
把其中的nginx改为自己想要的文字即可,如:lujinkaix

核心配置示例

ngx_http_core_module

基于不同的 IP、端口、域名实现不同的虚拟主机,依赖于核心模块:ngx_http_core_module

location 语法

一个 server 中可以配置多个 location

  • 精确匹配 =

    通常用于匹配相对固定的 url

    1
    2
    3
    4
    location = /logo.jpg {
    root /data/nginx/images;
    index index.html;
    }
  • 区分大小写的正则匹配 ~

    1
    2
    3
    4
    location ~ .*\.(js|css)?$ {
    expires 7d;
    access_log off;
    }
  • 不区分大小写的正则匹配 ~*

    1
    2
    3
    4
    # 注意:这里只是对img不分区大小写,img、imG都会被匹配到,但是具体到文件,还是区分大小写的
    location ~* /img {
    alias /images;
    }
  • 匹配 URI 开始 ^~

    通常用于匹配目录,注意:不支持正则

    1
    2
    3
    4
    location ^~ /images {
    root /data/nginx/;
    index index.html;
    }

匹配的优先级:=、^、~*、/

作为上传服务器

以下指令控制上传数据

1
2
3
4
5
6
# 允许客户端上传单个文件的最大值,默认值为1m,上传文件超过此值会出413错误
client_max_body_size 1m;
# 用于接收每个客户端请求报文的body部分的缓冲区大小;默认16k;超出此值,则暂存在client_body_temp_path指定的目录下
client_body_buffer_size size;
# level1、level2、level3表示的是目录层级1、2、3级的命名,具体规律看下面的范例
client_body_temp_path path [level1 [level2 [level3]]];

范例:

1
2
3
client_max_body_size 100m; # 如果php上传,还需要修改php.ini的相关配置
client_body_buffer_size 1024k;
client_body_temp_path /data/nginx/client_body_temp/ 1 2 2;

将上传的文件进行 hash 取值,例如 hash 值为:95f6f65f498c74938064851b1bb963d4,因为设置 1 2 2,所以从后往前去分截取 1、2、2 位,即 4、3d、96,最终的目录名称就是把 16 进制转换为十进制后的数字,即:

1
2
3
4
/data/nginx/client_body_temp/
├── 4
└── 61
└── 150

ngx_http_access_module

访问控制,可以通过匹配客户源 ip 地址进行限制

注意:如果能在防火墙设备进行控制,最好就不要在 nginx 上配置,可以更好的节约资源

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
location = /login/ {
root /data/nginx/html/pc;
allow 10.0.0.0/24;
deny all;
}
location /about {
alias /data/nginx/html/pc;
index index.html;
deny 192.168.1.1;
allow 192.168.1.0/24;
allow 10.1.1.0/16;
allow 2001:0db8::/32;
deny all; # 按小范围到大范围排序
}

ngx_http_auth_basic_module

提供账户认证功能,某些网页例如状态页需要通过认证才能访问

ngx_http_autoindex_module

ngx_http_autoindex_module 模块处理以斜杠字符 “/“ 结尾的请求,并生成目录列表,可以做为下载服
务配置使用

1
2
3
4
5
autoindex on | off; # 自动文件索引功能,默认为off
autoindex_exact_size on | off; # 计算文件大小(单位bytes),off 显示大概大小(单位K、M),默认on
autoindex_localtime on | off; # 显示本机时间而非GMT(格林威治)时间,默认off
autoindex_format html | xml | json | jsonp; # 显示索引的页面文件风格,默认html
limit_rate rate; # 限制响应客户端传输速率(除GET和HEAD以外的所有方法),单位B/s,即bytes/second,默认值0,表示不限速,此指令由ngx_http_core_module提供

范例:实现下载站点

1
2
3
4
5
6
7
location /download {
root /data/nginx/html/pc;
autoindex on;
autoindex_exact_size on;
autoindex_localtime on;
limit_rate 1024k;
}

error_page

定义错误页面

语法: error_page code … [=[response]] uri;
默认值: —
上下文: http, server, location, if in location

范例:

1
2
3
4
5
6
7
8
9
error_page 404             /404.html;
error_page 500 502 503 504 /50x.html;
# =response 可以改变响应码
error_page 404 =200 /empty.gif;
# 对错误处理进行重定向
error_page 403 http://example.com/forbidden.html;
error_page 404 =301 http://example.com/notfound.html;
# 将URI发送到被代理的服务器或者FastCGI服务器处理
error_page 404 = /404.php;

如果处理uri产生了错误,那么 nginx 将最后一次出错的 HTTP 响应状态码返回给客户端。

error_log

语法: error_log file | stderr [debug | info | notice | warn | error | crit | alert | emerg];
默认值: error_log logs/error.log error;
上下文: main, http, server, location

范例:

1
error_log /data/wwwlogs/error_nginx.log crit;

第一个参数是错误日志路径,如果设置为特殊值stderr,nginx 会将日志输出到标准错误输出。

第二个参数是日志级别,debug 到 emerg 严重性由轻到重,如果要设置为 debug,编译时需要添加--with-debug编译选项。

try_files

语法: try_files file … uri;
try_files file … =code;
默认值: —
上下文: server, location

内置变量 $uri 和 $request_uri:

如果用户请求的完整 url:http://s1.lujinkai.cn/www/nav/v1/index.html#/nav

  • $uri:/www/nav/v1,不包含?和#等参数
  • $request_uri:/www/nav/v1/index.html#/nav,包含?和#等参数

try_files 会按顺序检查文件是否存在,返回第一个找到的文件或文件夹(结尾加斜线表示为文件夹),如果所有文件或文件夹都找不到,会进行一个内部重定向到最后一个参数。只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部 URI 的指向。最后一个参数是回退 URI 且必须存在,否则会出现内部 500 错误。

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location / {
root /data/nginx/html/pc;
index index.html;
try_files $uri $uri.html $uri/index.html /about/default.html;
#try_files $uri $uri/index.html $uri.html =489;
}

[root@centos8 ~]$ echo "default page" >> /data/nginx/html/pc/about/default.html
# 重启nginx并测试,当访问到http://www.magedu.org/about/xx.html等不存在的uri会显示default.html,如果是自定义的状态码则会显示在返回数据的状态码中

# 注释default.html行,启用上面489行生效后,再观察结果
[root@centos8 ~]$ curl -I http://www.magedu.org/about/xx.html
HTTP/1.1 489 # 489就是自定义的状态返回码
Server: nginx
Date: Thu, 21 Feb 2019 00:11:40 GMT
Content-Length: 0
Connection: keep-alive
Keep-Alive: timeout=65

其他配置

  • 针对行为异常的浏览器关闭长连接功能:

    语法: keepalive_disable none | browser …;
    默认值: keepalive_disable msie6;
    上下文: http, server, location

  • 限制客户端使用除了指定的请求方法之外的其它方法:

    语法: limit_except method … { … }
    默认值: —
    上下文: location

    method :GETHEADPOSTPUTDELETEMKCOLCOPYMOVEOPTIONSPROPFINDPROPPATCHLOCKUNLOCK 或者 PATCH。 指定methodGET方法的同时,会自动添加HEAD方法

    1
    2
    3
    4
    5
    6
    # 除了GET和HEAD 之外其它方法仅允许192.168.1.0/24网段主机使用
    limit_except GET {
    allow 192.168.1.0/32;
    deny all;
    }
    ​```
  • 异步 I/O:

    1
    2
    3
    4
    # 是否开启异步I/O功能,如果开启,编译时需要加入--with-file-aio
    aio on | off;
    # 如果开启aio,必须同时开启directio,否则读取将是阻塞的,注意打开了directio则自动关闭sendfile
    directio size | off;

    范例:

    1
    2
    3
    4
    5
    6
    location /video/ {
    aio on;
    directio 512;
    # 从磁盘读取响应的缓冲区的数量和大小。默认1 32k
    output_buffers 1 128k;
    }
  • 缓存:

    nginx 可以缓存以下三种信息:

    1. 文件元数据:文件的描述符、文件大小和最近一次的修改时间
    2. 打开的目录结构
    3. 没有找到的或者没有权限访问的文件的相关信息
    1
    2
    3
    4
    5
    6
    7
    8
    # 是否缓存打开过文件信息,默认off
    # max:可缓存的缓存项上限数量;
    # inactive:缓存项的非活动时长在此处指定的时长内未被命中的或命中的次数少于open_file_cache_min_uses指令所指定的次数的缓存项即为非活动项,将被删除
    open_file_cache off | max=N [inactive=time];

    open_file_cache_valid time; # 缓存项有效性的检查验证频率,默认值为60s
    open_file_cache_errors on | off; # 是否缓存查找时发生错误的文件一类的信息,默认值为off
    open_file_cache_min_uses number; # open_file_cache指令的inactive参数指定的时长内,至少被命中几数方可被归类为活动项,默认值为1

    范例:

    1
    2
    3
    4
    open_file_cache max=10000 inactive=60s; # 最大缓存10000个文件,非活动数据超时时长60s
    open_file_cache_valid 60s; # 每间隔60s检查一下缓存数据有效性
    open_file_cache_min_uses 5; # 60秒内至少被命中访问5次才被标记为活动数据
    open_file_cache_errors on; # 缓存错误信息

概述

功能

nginx 的功能主要有两个:web 服务器和反向代理

  • html、图片、js、css 等静态资源的服务器
  • http/https 协议的反向代理
  • 结合 FastCGI/uWSGI/SCGI 等协议反向代理动态资源请求
  • tcp/udp 协议的请求转发(反向代理)
  • imap4/pop3 协议的反向代理

基础特性

  • 模块化设计,较好的扩展性
  • 高可靠性
  • 支持热部署:不停机的更新配置文件,升级版本,更换日志文件
  • 低内存消耗:1000 个 keep-alive 连接模式下的非活动连接,仅需 2.5m 内存
  • event-driven、aio、mmap、sendfile

web 相关的功能

  • 虚拟主机(server)
  • 支持 keep-alive 和管道连接(利用一个连接做多次请求)
  • 访问日志(支持基于日志缓冲提高性能)
  • url rewrite
  • 路径别名
  • 基于 ip 及用户的访问控制
  • 支持速率限制及并发数限制
  • 重新配置和在线升级而无须终端客户的工作进程

架构

nginx 是多进程,一个 master 主进程和若干 worker 工作进程组成

master 进程的功能:

  1. 对外接口,接收外部的操作(信号)
  2. 对内转发,根据外部的操作的不同,通过信号管理 worker
  3. 监控 worker 进程的运行状态,worker 进程异常终止后,自动重启 worker 进程
  4. 读取 nginx 配置文件,并验证其有效性和正确性
  5. 建立、绑定和关闭 socket 连接
  6. 按照配置生成、管理和结束工作进程
  7. 不中断服务,实现平滑升级(升级失败进行回滚处理)、重启服务并应用新的配置
  8. 开启日志文件,获取文件描述符
  9. 编译和处理 per 脚本

worker 进程的功能:

  1. 所有 worker 进程都是平等的
  2. 实际处理网络请求,将请求依次送入各个功能模块进行处理
  3. I/O 调用,获取响应数据
  4. 与后端服务器通信,接受后端处理器的处理结果
  5. 缓存数据,访问缓存索引,查询和调用缓存数据
  6. 发送请求结果,响应客户的请求

进程间通信

master 与 worker 通信:worker 进程是由 master 进程生成,主进程在启动之后,建立一个主进程指向工作进程的单向通道,master 进程通过信号机制和与外界通信,当接受到需要处理的信号时,就通过管道向相关的进程发送正确的指令,每个 worker 进程都有能力捕捉管道中的可读事件,当管道中有可读事件时,worker 进程就会从管道中读取并解析指令,然后才去相应的执行动作,这样就完成了主进程与子进程的交互

worker 进程之间通信:worker 进程之间只要能够取得彼此的信息,建立管道即可通信,但是由于 worker 进程之间是完全隔离的,因此一个进程想要知道另外一个进程的状态信息,就只能通过 master 进程来实现。

master 进程在生成 worker 进程之后,会把新进程的 ID 以及针对该进程建立的管道句柄传递给其他 worker 进程,这样能实现通过 master 进程进行通信

http 连接建立

  1. nginx 启动,master 进程加载配置文件
  2. master 进程初始化监听的 socket
  3. master 进程 fork 出多个 worker 进程
  4. worker 进程,竞争新的连接,获胜方通过三次握手,建立 socket 连接,开始处理请求

http 处理过程

模块

nginx 高度模块化

  • 核心模块:正常运行必不可少,提供错误日志记录、配置文件解析、事件驱动机制、进程管理等核心功能
  • 标准 http 模块:提供 http 协议解析相关的功能,比如:端口配置、网页编码设置、http 响应头设置等
  • 可选 http 模块:主要用于扩展标准的 http 功能,让 nginx 可以处理一些特殊的服务,比如:flash 多媒体传输、解析 GeoIP 请求、网络传输压缩、安全协议 SSL 支持等
  • 邮件服务服务:主要用于支持 nginx 的邮件服务,包括对 pop3 协议、IMAP 协议和 SMTP 协议的支持
  • stream 服务模块:实现反向代理功能,包括 TCP 协议代理
  • 第三方面模块:为了扩展 nginx 服务器应用,完成开发者自定义功能,比如:json 支持、lua 支持等

安装

推荐编译安装,可以自定义功能

1
2
3
4
5
6
7
# 查看编译参数
[root@4710419222 ~]# nginx -V
nginx version: nginx/1.15.7
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC)
built with OpenSSL 1.0.2p 14 Aug 2018
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx --user=www --group=www --with-http_stub_status_module --with-http_v2_module --with-http_ssl_module --with-http_gzip_static_module --with-http_realip_module --with-http_flv_module --with-http_mp4_module --with-openssl=../openssl-1.0.2p --with-pcre=../pcre-8.42 --with-pcre-jit --with-ld-opt=-ljemalloc
1
2
3
4
# 启动前,可以检查配置文件nginx.conf的合法性
[root@4710419222 vhost]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

Apache

prefork 模型

worker 模型

event 模型

属于事件驱动模型(epoll),它和 worker 模式很像,最大的区别在于,它解决了 keepalive 场景下,长期被占用的线程的资源浪费问题

优点:单线程响应多请求,占据更少的内存,高并发下表现更优秀,会有一个专门的线程来管理 keep-alive 类型的线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,允许它释放

缺点:没有线程安全控制

一次完整的 I/O

I/O:input / output intput 是写,output 是读,I/O 就是对文件的读写。

I/O 分为 磁盘 I/O 和 网络 I/O,但 Linux 中一切皆文件,网络 I/O 本质是对 socket 文件(socket = ip + port)的读写。

内核空间与用户空间都在内存中,但是是严格隔离的,用户空间的程序要想使用内核空间的数据,要先把数据先从内核空间 copy 到用户空间,然后使用 copy 的这份,反之亦然。

所以每次 读 或 写 操作都要经过两个阶段:

读:

  1. 将数据从文件加载到内核空间(缓冲区),时间较长
  2. 将数据从内核空间(缓冲区)复制到用户空间,时间较短

写:

  1. 将数据从用户空间复制到内核空间(缓冲区),时间较短
  2. 将数据从内核空间(缓冲区)写入到文件中,时间较长

I/O 相关概念理解

同步/异步 关注的是 消息通知机制

同步

阻塞

  1. 用户线程发起 I/O 操作 的请求,然后挂起;
  2. 内核开始处理请求,直到两个阶段都完成,才返回结果(读操作返回读取的内容,写操作返回是否成功);
  3. 用户线程接收到结果 被激活,然后继续工作;

当然 用户线程等待的过程是不消耗 CPU 的。

非阻塞

  1. 用户线程发起 I/O 操作 的请求;
  2. 内核马上返回一个错误(EWOULDBLOCK),然后开始处理请求。
  3. 用户线程接收到错误,然后去处理其他任务,同时定时检查,如果还是返回错误(EWOULDBLOCK)就说明内核还没处理完。
  4. 直到定时检查有结果返回,用户线程才回来继续完成任务

异步

注意:只有同步才有阻塞/非阻塞的说法,异步就是异步(都异步了哪来的阻塞?)不涉及阻塞问题。

  1. 用户线程发起 I/O 操作 的请求;

  2. 内核返回 0,然后开始处理请求

  3. 用户线程接收到 0,然后去处理其他任务;

  4. 内核请求处理完后,通知线程;

    什么时候算处理完?

    • 写操作:完成第二阶段;

    • 读操作:有两种方式 NIO 和 AIO:

      1
      2
      3
      NIO:完成第一阶段就算完成,告诉用户线程”我可以读了“,第二阶段还是要阻塞用户线程。

      AIO:完成第二阶段才算完成,告诉用户线程”我读完了“,是真正的异步,但是因为第二阶段非常快,所以对比NIO提升不大,而且要实现真正的异步 I/O,操作系统需要做大量的工作,目前AIO并不成熟,所以用的不多。

    怎么通知线程?状态、通知或回调。用户线程无需关心具体使用什么方式,

    用户线程一定有个“地方”专门接收通知,对于 NIO,这个”地方“就是下文重点介绍的 select、poll、epoll;对于 AIO 就不是很清楚了

网络 IO 模型

下图是几种常见 I/O 模型的对比:

以 socket.read()为例子:BIO 里用户最关心“我要读”,NIO 里用户最关心”我可以读了”,在 AIO 模型里用户更需要关注的是“读完了”。

阻塞 I/O

最简单的 I/O 模型,用户线程在内核进行 IO 操作时阻塞,每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销较大,apache 的 preforck 使用的是这种模式。

非阻塞 I/O

程序向内核发送请 I/O 求后一直等待内核响应,如果内核处理请求的 IO 操作不能立即返回 IO 结果,进程将不再等待,而且继续处理其他请求,但是仍然需要进程隔一段时间就要查看内核 I/O 是否完成。

上图可知,在设置连接为非阻塞时,当应用进程系统调用 recvfrom 没有数据返回时,内核会立即返回一个 EWOULDBLOCK 错误,而不会一直阻塞到数据准备好。如上图在第四次调用时有一个数据报准备好了,所以这时数据会被复制到 应用进程缓冲区 ,于是 recvfrom 成功返回数据

I/O 多路复用(重点)

参考:https://www.cnblogs.com/flashsun/p/14591563.html

多路复用就是复用线程的意思,实现单线程处理多任务。

NIO 是多路复用的基础,对于读操作,多路复用只能实现第一阶段的异步,无法实现第二阶段的异步。

I/O 多路复用 的实现方式主要有 3 种:select、poll、epoll

select

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建一个线程,负责接收客户端的连接,并把 socket fd 放到 list 里
for {
connfd := accept(listenfd)
fcntl(connfd, F_SETFL, O_NONBLOCK)
fdlist.Add(connfd)
}

// 再创建一个线程,调用select,将list交给操作系统去遍历,找到就绪的 socket fd
// select 接收一个fdList,然后遍历fdList,将就绪的fd标注状态
for {
fds := select(fdlist) // 获取标注状态之后的fdList
for _, fd := fds { // 遍历处理
if fd.Status { // 判断fd是否就绪(可读或可写)
read(fd, buffer) // 从fd读取数据
process(buffer) // 处理读取的数据
close(fd); // 关闭连接
fdList.Delete(fd) // 将处理完的fd删除
}
}
}

动图示例:

缺点:

  1. fdList 最长 1024
  2. 每次调用 select,都要将 fdList 拷贝到内核空间,内核处理完后,再拷贝到用户空间,高并发场景下这样的拷贝消耗的资源是惊人的。
  3. select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。
  4. select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。

poll

select 的升级版,本质上和 select 没有区别,去掉了 select 只能监听 1024 个文件描述符的限制。

epoll

epoll 针对 select 和 poll 的缺点,做了三点改进:

  1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
  2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。
  3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

具体,操作系统提供了这三个函数。

1
2
3
int epoll_create(int size);            // 第一步,创建一个 epoll 句柄
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // 第二步,向内核添加、修改或删除要监控的文件描述符。
int epoll_wait(int epfd, struct epoll_event *events, int max events, int timeout); // 第三步,类似发起了 select() 调用

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建一个线程,负责接收客户端的连接,并把 socket fd 放到 list 里
epfd := epoll_create(n) // 创建epoll
for {
connfd := accept(listenfd)
fcntl(connfd, F_SETFL, O_NONBLOCK)
epoll_ctl(epfd, add, connfd) // 向epoll中添加connfd
}


// 再创建一个线程,调用epoll_wait,找到就绪的 socket fd
for {
fds := epoll_wait() // 获取当前有哪些fd就绪(可读或可写)
for _, fd := fds { // 遍历处理这些fd
read(fd, buffer) // 从fd读取数据
process(buffer) // 处理读取的数据
close(fd); // 关闭连接
epoll_ctl(epfd, delete, fd) // 将处理完的fd删除
}
}

动图示例:

select poll epoll
操作方式 遍历 遍历 回调
底层实现 数组 链表 哈希表
IO 效率 每次调用都进行线性遍历,时间复杂度为 O(n) 每次调用都进行线性遍历,时间复杂度为 O(n) 事件通知方式,每当 fd 就绪,系统注册的回调函数就会被调用,将就绪 fd 放到 readyList 里面,时间复杂度 O(1)
最大连接数 1024 无上限 无上限
fd 拷贝 每次调用 select,都需要把 fd 集合从用户态拷贝到内核态 每次调用 poll,都需要把 fd 集合从用户态拷贝到内核态 调用 epoll_ctl 时拷贝进内核并保存,之后每次 epoll_wait 不拷贝

总结

一切的开始,都起源于这个 read 函数是操作系统提供的,而且是阻塞的,我们叫它 阻塞 I/O

为了破这个局,程序员在用户态通过多线程来防止主线程卡死。

后来操作系统发现这个需求比较大,于是在操作系统层面提供了非阻塞的 read 函数,这样程序员就可以在一个线程内完成多个文件描述符的读取,这就是 非阻塞 I/O

但多个文件描述符的读取就需要遍历,当高并发场景越来越多时,用户态遍历的文件描述符也越来越多,相当于在 while 循环里进行了越来越多的系统调用。

后来操作系统又发现这个场景需求量较大,于是又在操作系统层面提供了这样的遍历文件描述符的机制,这就是 I/O 多路复用

多路复用有三个函数,最开始是 select,然后又发明了 poll 解决了 select 文件描述符的限制,然后又发明了 epoll 解决 select 的不足。


所以,IO 模型的演进,倒逼着操作系统将更多的功能加到自己的内核而已。

如果你建立了这样的思维,很容易发现网上的一些错误。

比如好多文章说,多路复用之所以效率高,是因为用一个线程就可以监控多个文件描述符。

这显然是知其然而不知其所以然,多路复用产生的效果,完全可以由用户态去遍历文件描述符并调用其非阻塞的 read 函数实现。而多路复用快的原因在于,操作系统提供了这样的系统调用,使得原来的 while 循环里多次系统调用,变成了一次系统调用 + 内核层遍历这些文件描述符。

就好比我们平时写业务代码,把原来 while 循环里调 http 接口进行批量,改成了让对方提供一个批量添加的 http 接口,然后我们一次 rpc 请求就完成了批量添加。一个道理。

信号驱动 I/O

不需要轮训,而是让内核在数据就绪时,发送信号通知

缺点:信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知

异步 I/O

异步 IO 与信号驱动 IO 最主要的区别是信号驱动 IO 是由内核通知应用程序何时可以进行 IO 操作,而异步 IO 则是由内核告诉用户线程 IO 操作何时完成。信号驱动 IO 当内核通知触发信号处理程序时,信号处理程序还需要阻塞在从内核空间缓冲区拷贝数据到用户空间缓冲区这个阶段,而异步 IO 直接是在第二个阶段完成后,内核直接通知用户线程可以进行后续操作了

缺点:要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的异步 I/O,在 Linux 系统下,Linux 2.6 才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编程时以 IO 复用模型模式+多线程任务的架构基本可以满足需求

Linux 提供了 AIO 库函数实现异步,但是用的很少。目前有很多开源的异步 IO 库,例如 libevent、libev、libuv 但是这些的底层都是通过 epoll 的多路复用模拟的异步 I/O。

零拷贝

通过尽量避免拷贝操作来缓解 CPU 的压力。零拷贝并没有真正做到“0”拷贝,它更多是一种思想,很多的零拷贝技术都是基于这个思想去做的优化

MMAP ( Memory Mapping )

内存映射,用户空间的内存映射到内核空间

SENDFILE

DMA 辅助的 SENDFILE

需要硬件支持

冒泡: 点击元素, 也会触发父元素的点击事件

defineProperty: 实现属性代理; 实现单向数据绑定

v-开头的属性, 称之为 Vue 的指令

标签分为两类: 表单标签 和 非表单标签, 单向绑定的指令一般用于非表单标签, 双向绑定的指令用于表单标签

  • v-text:和双花括号的效果一样
  • v-html:和 v-text 唯一的区别就是能解析 html 代码
  • v-bind:属性名:**简写”:”**,给元素的属性赋值
  • v-on:事件名:**简写”@”**,事件注册
  • v-if、v-else-if、v-else:必须是相邻的兄弟元素控制,元素是否显示( 不显示的时候是从 dom 中移除 )
  • v-show: 控制元素是否显示(不显示的时候是 display:none)
  • v-for:循环, 常用于列表渲染
  • v-model

绑定方向除了 v-model 是双向,其他的都是单向( 数据=>DOM )

双花括号、v-text、v-html、v-bind 对应的位置可以写 js 表达式, 注意不能写 js 语句(例如: 三元表达式可以写, 全局对象方法可以调用, if 语句不能写)

事件修饰符
Vue 允许我们在注册事件时,进行额外的操作。我们在事件名后添加 .xxxx 来对事件进行修饰

适用事件 事件修饰符 说明 示例
click .self 只要点击当前元素自己时, 才会触发事件 <div class="xx" v-on:click.self="handlerDiv">
.stop 表示阻止冒泡
.prevent 阻止事件的默认行为 submit.prevent="xxx"

补充: 关于按钮修饰符给 input 注册 keyup 事件, 但是 .enter 表示, 只有按下 回车键 时, 才触发。示例: <input v-on:keyup.enter="handler"/>

过滤器 ( filter ):常规过滤器、全局过滤器

计算属性 ( computed ) : 和过滤器的本质区别: 当数据变化时才执行

观察者( watch ) : 监视 data 中的属性,一旦属性发生了改变就去自动调用对应的方法

自定义属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div >
<div style="cursor: pointer;" ref="xx" @click="getId($event,b)" :data-b="b">{{a}}</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el: '#box',
data() {
return {
a: 123,
b: 455
}
},
methods: {
getId(e,b) {
console.dir(this.$refs.xx.dataset.b)
console.dir(e.target.getAttribute('data-b'))
}
}
})
</script>

子进程和子线程:https://blog.csdn.net/weixin_39561473/article/details/89576549

开启一个 php-fpm 主进程,用户是 root,然后开启多个 php-fpm 子进程,用户是 www。

每个子进程同时只能处理一个请求,所有开启多少 php-fpm,它的并发就是多少,例如开启了 50 个 php-fpm 子进程,那么并发就是 50。子进程是不能无限开启的,进程之间是隔离的,每开启一个进程操作系统都要为其分配内存。

https://www.easyswoole.com/Components/jwt.html
https://baijiahao.baidu.com/s?id=1608021814182894637&wfr=spider&for=pc
使用 JWT 实现单点登录(完全跨域方案)

JWT:JSON Web Token

JWT 不仅可用于认证,还可用于信息交换。善用 JWT 有助于减少服务器请求数据库的次数

生成 token 编码

用户信息 + 固定数据(加密方式、用户、过期时间等)+ 密钥 加密,得到 签名,然后将这四部分数据 base64 编码,生成一个长字符串,这个长字符串就是 token

注意:

  • 密钥保存在服务器上,千万不能泄露,可以设置成 项目名称+随机字符串 的格式
  • token 中的所有信息都是公开的,所以一定不能存放密码等隐私信息
  • 一旦 JWT 签发,有效期内将会一直有效,不能取消 token 或更改 token 的权限为了减少盗用,JWT 的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证
  • 密钥可以保证 token 的安全性,但是无法防止 token 的盗用问题,为了减少盗用和窃取,推荐使用 HTTPS 协议进行传输

解析 token 解码

base64 解码 token,得到一堆明文信息和签名,明文信息里有一个字段说明了加密用的算法,将明文信息使用指定的加密方式加密,如果等于签名,则 token 校验通过,在进行超时等其他参数的校验

easyswoole 中的 jwt

官方文档:https://www.easyswoole.com/Components/jwt.html

官方文档写的比较简略,通过查阅源码,可以有更详细的了解。

token 由三或四部分组成:

  1. 前缀:prefix,可省略

  2. 头部:header,是 alg(加密方式)和 typ(类型,默认 JWT 且无法更改)的组合,经过 base64 转码

  3. 有效载荷:payload,包含以下信息,经过 base64 转码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    'exp' => $this->getExp(),   // 过期时间,默认两个小时
    'sub' => $this->getSub(), // 主题,没有默认值
    'nbf' => $this->getNbf(), // 在此之前不可用,默认time()
    'aud' => $this->getAud(), // 用户,无默认值
    'iat' => $this->getIat(), // 发布时间,默认time()
    'jti' => $this->getJti(), // jwt-id,用于标识jwt,默认10字节的随机字符串
    'iss' => $this->getIss(), // 发行人,默认'EasySwoole'
    // token状态,0初始状态,1合法token,-1非法token,-2过期token,生成token的时候,status默认给1
    // 不太理解payload中为什么要包含这个字段
    'status' => $this->getStatus(),
    'data' => $this->getData() // 自定义的数据

    必要的信息都有默认值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // Jwt
    private $secretKey = 'EasySwoole';
    private $alg = Jwt::ALG_METHOD_HS256; // 默认加密方式

    // JwtObject
    protected $iss = 'EasySwoole'; // 发行人

    if (empty($this->nbf)) {
    $this->nbf = time();
    }
    if (empty($this->iat)) {
    $this->iat = time();
    }
    if (empty($this->exp)) {
    $this->exp = time() + 7200;
    }
    if (empty($this->jti)) {
    $this->jti = Random::character(10);
    }
  4. 签名:signature,包含 secretKey(秘钥)、header、payload、alg,经过加密然后 base64 转码

    除了 secretKey,其他所有信息相当于都是明文,secretKey 储存在服务器的内存中,如果重启服务,secretKey 就会丢失,之前的 token 就都无法通过校验,所以推荐直接修改 Jwt 组件中的默认 secretKey,然后在生成 token 的时候,不指定 secretKey,使用默认值

生成 token,以下是最简略的写法:

1
2
3
use EasySwoole\Jwt\Jwt;

$token = Jwt::getInstance()->publish()->__toString();

生成 token,以下是推荐写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
use EasySwoole\Jwt\Jwt;

$jwtObject = Jwt::getInstance()->publish();
$jwtObject->setPrefix('Bearer'); // token前缀,Bearer表示标准jwt格式
$jwtObject->setSub('yuanzhan'); // 主题
$jwtObject->setAud($userId); // 用户id
$jwtObject->setExp(time() + 3600); // 过期时间设置为1个小时
// 自定义数据
$jwtObject->setData([
'other_info'
]);

$token = $jwt->__toString();

自动续期

设置 token 过期时间 2h,2h 内用户一直在操作,然后贸然过期肯定不合理,所以 token 要有自动续期机制。

方案:

  • 后端:每次校验 token 的过期时间,如果过期但是过期时间不超过 4 个小时,则生成一个新 token,并附着在此次请求返回的数据的基础上。这里用“refreshToken”字段
  • 前端:每次 http 请求校验返回值中有没有“refreshToken”字段,如果有,则刷新本地 token

上传文件大小受package_max_length配置项限制, 默认为 2M。

不要用 swoole 处理大文件上传, swoole 底层是全内存的,因此如果package_max_length设置过大,可能导致大量并发请求将服务器内存耗尽。

私有属性不释放。可以通过 gc 来释放。注意 gc 方法一定要先执行parent::gc();实现父类的 gc,因为基类中 gc 会进行重置 public 属性的操作。

控制器对象常驻内存,构造方法和析构方法只有在进程开启和退出的时候才会执行。可以用 onRequest 代替构造方法,用 afterAction 代替析构方法。

defer、 invoker、 getObj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
go(function () {
$redisPool = new \App\Pool\RedisPool(new \EasySwoole\Pool\Config(), new \EasySwoole\Redis\Config\RedisConfig(\EasySwoole\EasySwoole\Config::getInstance()->getConf('REDIS')));
$redis = $redisPool->getObj();
var_dump($redis->echo('仙士可'));
$redisPool->recycleObj($redis);
});

go(function () {
$redisPool = new \App\Pool\RedisPool(new \EasySwoole\Pool\Config(), new \EasySwoole\Redis\Config\RedisConfig(\EasySwoole\EasySwoole\Config::getInstance()->getConf('REDIS')));
$redisPool->invoke(function (\EasySwoole\Redis\Redis $redis) {
var_dump($redis->echo('仙士可'));
});
});

go(function () {
$redisPool = new \App\Pool\RedisPool(new \EasySwoole\Pool\Config(), new \EasySwoole\Redis\Config\RedisConfig(\EasySwoole\EasySwoole\Config::getInstance()->getConf('REDIS')));
$redis = $redisPool->defer();
var_dump($redis->echo('仙士可'));
});

getObj 获取一个连接池对象

​ 通过 getObj 方法获取的对象,都必须调用 unsetObj 或者 recycleObj 进行回收,否则连接池对象会越来越少

invoke 获取一个连接,传入到$call 回调函数中进行处理,回调结束后自动回收连接

​ 通过该方法无需手动回收连接,在回调函数结束后,则自动回收

defer 获取一个连接,协程结束后自动回收

​ 通过该方法无需手动回收连接,在协程结束后,则自动回收

​ 需要注意的事,defer 方法是协程结束后才回收,如果你当前协程运行时间过长,则会一直无法回收,直到协程结束

三者的联系和区别

getObj 获取一个连接池对象, 需要手动回收, 而 defer 中封装了 swoole 的 Coroutine::defer, 在 Coroutine::defer 处理连接池对象的回收。

所以说 getObj 和 defer 的区别就在于回收的时机,前者自己把控,后者只能是在当前协程结束的时候回收。

而 invoke 是传入一个匿名函数,在 invoke 内部执行$obj = $this->getObj(), 然后将$obj 传入匿名函数。利用 try finally, 在匿名函数执行完毕后就回收对象。

总结:本质上都是通过 getObj 获取连接池对象。只不过 defer 和 invoke 又封装了一层,目的都是做到自动回收连接池对象。

先进先出

enqueue: 进入队列

dequeue: 退出队列, 返回退出队列的元素

bottom(): 队列底, 就是第一个添加到队列的元素

top(): 队列头, 就是最后一个添加到队列的元素

offsetSet(‘0’, data): 更新元素的值, offset=0 是 bottom 所在的位置, 1、2、3… 以此类推

rewind(): 使得指针指向 bottom 所在位置的节点

key(): 当前节点的 index

current(): 当前节点的 value

valid(): 判断当前节点是否有效

next(): 使得指针指向下一个节点

参考://img.to2b.cn/blog/ljk/1607699201217.png