潘逸安的博客

潘逸安

idea远程调试

远程应用配置

1
2
3
4
5
6
7
8
9
10
11
12
FROM registry.cn-hangzhou.aliyuncs.com/ppiian/openjdk:8-jdk-alpine

VOLUME /tmp

ADD ./target/*.jar app.jar

RUN ln -snf /usr/share/zoneinfo/PRC /etc/localtime && echo PRC > /etc/timezone

#在docker配置中修改JAVA_OPTS,达到切换环境的效果
ENV JAVA_OPTS="-Xms128m -Xmx512m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006"

ENTRYPOINT java $JAVA_OPTS -jar app.jar

远程启动配置项中添加

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006

这里的端口5006需要提供给idea

idea配置

【运行/调试配置】中添加一个 【远程jvm调试】

host(主持人)这里填远程的ip地址,端口填远程开放的端口

image-20240818191426153

lambda受检异常

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
51
52
53
54
55
56
public class Try {
public Try() {
}

public static <T, R> Function<T, R> of(Try.UncheckedFunction<T, R> mapper) {
Objects.requireNonNull(mapper);
return (t) -> {
try {
return mapper.apply(t);
} catch (Exception var3) {
throw Exceptions.unchecked(var3);
}
};
}

public static <T> Consumer<T> of(Try.UncheckedConsumer<T> mapper) {
Objects.requireNonNull(mapper);
return (t) -> {
try {
mapper.accept(t);
} catch (Exception var3) {
throw Exceptions.unchecked(var3);
}
};
}

public static <T> Supplier<T> of(Try.UncheckedSupplier<T> mapper) {
Objects.requireNonNull(mapper);
return () -> {
try {
return mapper.get();
} catch (Exception var2) {
throw Exceptions.unchecked(var2);
}
};
}

@FunctionalInterface
public interface UncheckedSupplier<T> {
@Nullable
T get() throws Exception;
}

@FunctionalInterface
public interface UncheckedConsumer<T> {
@Nullable
void accept(@Nullable T t) throws Exception;
}

@FunctionalInterface
public interface UncheckedFunction<T, R> {
@Nullable
R apply(@Nullable T t) throws Exception;
}
}

Function 用法

1
2
3
4
5
User apply = Try.of((String userStr) -> {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(userStr, User.class);
}).apply(s3);
System.out.println(apply);

Supplier 用法

1
2
3
4
5
User aaa = Try.of(() -> {
System.out.println("走if");
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(s3, User.class);
}).get();

Consumer用法

1
2
3
4
5
6
7
String s3 = "{\"name\":221, \"id\":1}";

Try.of((String str) -> {
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(str, User.class);
System.out.println(user);
}).accept(s3);

lambda中使用

1
2
3
4
5
User user2 = Optional.ofNullable(s3)
.map(Try.of((String userStr) -> {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(userStr, User.class);
})).orElse(null);

predicate

1
2
3
4
5
objects.stream().filter(Try.predicate(user -> {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(s3, User.class);
return user.getId().equals(1);
})).collect(Collectors.toList());
1
2
3
4
5
6
7
8
9
10
11
public static <T> Predicate<T> predicate(Try.UncheckedPredicate<T> mapper) {
Objects.requireNonNull(mapper);
return (t) -> {
try {
return mapper.test(t);
} catch (Exception var3) {
throw Exceptions.unchecked(var3);
}
};
}

1
2
3
4
5
@FunctionalInterface
public interface UncheckedPredicate<T> {
@Nullable
boolean test(T t) throws Exception;
}

Linux防火墙firewalld

CentOS7 防火墙(firewall)的操作命令

安装:yum install firewalld

1、firewalld的基本使用

启动: systemctl start firewalld

查看状态: systemctl status firewalld

禁用,禁止开机启动: systemctl disable firewalld

停止运行: systemctl stop firewalld

2.配置firewalld-cmd

查看版本: firewall-cmd –version

查看帮助: firewall-cmd –help

显示状态: firewall-cmd –state

查看所有打开的端口: firewall-cmd –zone=public –list-ports

更新防火墙规则: firewall-cmd –reload

更新防火墙规则,重启服务: firewall-cmd –completely-reload

查看已激活的Zone信息: firewall-cmd –get-active-zones

查看指定接口所属区域: firewall-cmd –get-zone-of-interface=eth0

拒绝所有包:firewall-cmd –panic-on

取消拒绝状态: firewall-cmd –panic-off

查看是否拒绝: firewall-cmd –query-panic

3.信任级别,通过Zone的值指定

drop: 丢弃所有进入的包,而不给出任何响应
block: 拒绝所有外部发起的连接,允许内部发起的连接
public: 允许指定的进入连接
external: 同上,对伪装的进入连接,一般用于路由转发
dmz: 允许受限制的进入连接
work: 允许受信任的计算机被限制的进入连接,类似 workgroup
home: 同上,类似 homegroup
internal: 同上,范围针对所有互联网用户
trusted: 信任所有连接

4.firewall开启和关闭端口

以下都是指在public的zone下的操作,不同的Zone只要改变Zone后面的值就可以

添加:

firewall-cmd –zone=public –add-port=80/tcp –permanent (–permanent永久生效,没有此参数重启后失效)

重新载入:

firewall-cmd –reload

查看:

firewall-cmd –zone=public –query-port=80/tcp

删除:

firewall-cmd –zone=public –remove-port=80/tcp –permanent

5.管理服务

以smtp服务为例, 添加到work zone

添加:

firewall-cmd –zone=work –add-service=smtp

查看:

firewall-cmd –zone=work –query-service=smtp

删除:

firewall-cmd –zone=work –remove-service=smtp

5.配置 IP 地址伪装

查看:

firewall-cmd –zone=external –query-masquerade

打开:

firewall-cmd –zone=external –add-masquerade

关闭:

firewall-cmd –zone=external –remove-masquerade

6.端口转发

打开端口转发,首先需要打开IP地址伪装

firewall-cmd –zone=external –add-masquerade

转发 tcp 22 端口至 3753:

firewall-cmd –zone=external –add-forward-port=22:porto=tcp:toport=3753

转发端口数据至另一个IP的相同端口:

firewall-cmd –zone=external –add-forward-port=22:porto=tcp:toaddr=192.168.1.112

转发端口数据至另一个IP的 3753 端口:

firewall-cmd –zone=external –add-forward-port=22:porto=tcp::toport=3753:toaddr=192.168.1.112

6.systemctl是CentOS7的服务管理工具中主要的工具,它融合之前service和chkconfig的功能于一体。

启动一个服务:systemctl start firewalld.service
关闭一个服务:systemctl stop firewalld.service
重启一个服务:systemctl restart firewalld.service
显示一个服务的状态:systemctl status firewalld.service
在开机时启用一个服务:systemctl enable firewalld.service
在开机时禁用一个服务:systemctl disable firewalld.service
查看服务是否开机启动:systemctl is-enabled firewalld.service
查看已启动的服务列表:systemctl list-unit-files|grep enabled
查看启动失败的服务列表:systemctl –failed

Linux定时任务Crontab命令详解

Linux定时任务Crontab命令详解

linux 系统则是由 cron (crond) 这个系统服务来控制的。Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另 外, 由于使用者自己也可以设置计划任务,所以, Linux 系统也提供了使用者控制计划任务的命令 :crontab 命令。

一、crond简介

crondlinux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务 工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。

Linux下的任务调度分为两类,系统任务调度和用户任务调度。

系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。在/etc目录下有一个crontab文件,这个就是系统任务调度的配置文件。

/etc/crontab文件包括下面几行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SHELL=/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin

MAILTO=HOME=/

# run-parts

51 * * * * root run-parts /etc/cron.hourly

24 7 * * * root run-parts /etc/cron.daily

22 4 * * 0 root run-parts /etc/cron.weekly

42 4 1 * * root run-parts /etc/cron.monthly

前 四行是用来配置crond任务运行的环境变量,第一行SHELL变量指定了系统要使用哪个shell,这里是bash,第二行PATH变量指定了系统执行 命令的路径,第三行MAILTO变量指定了crond的任务执行信息将通过电子邮件发送给root用户,如果MAILTO变量的值为空,则表示不发送任务 执行信息给用户,第四行的HOME变量指定了在执行命令或者脚本时使用的主目录。第六至九行表示的含义将在下个小节详细讲述。这里不在多说。

用户任务调度:用户定期要执行的工作,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab 文件都被保存在 /var/spool/cron目录中。其文件名与用户名一致。

使用者权限文件:

文件:

/etc/cron.deny

说明:

该文件中所列用户不允许使用crontab命令

文件:

/etc/cron.allow

说明:

该文件中所列用户允许使用crontab命令

文件:

/var/spool/cron/

说明:

所有用户crontab文件存放的目录,以用户名命名

crontab文件的含义:

用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下:

minute hour day month week command

其中:

1
2
3
4
5
6
7
8
9
10
11
minute: 表示分钟,可以是从0到59之间的任何整数。

hour:表示小时,可以是从0到23之间的任何整数。

day:表示日期,可以是从1到31之间的任何整数。

month:表示月份,可以是从1到12之间的任何整数。

week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。

command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。

在以上各个字段中,还可以使用以下特殊字符:

  • 星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。

  • 逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”

  • 中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”

  • 正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。

二、crond服务

安装crontab:

1
yum install crontabs

服务操作说明:

1
2
3
4
5
6
7
8
9
/sbin/service crond start //启动服务

/sbin/service crond stop //关闭服务

/sbin/service crond restart //重启服务

/sbin/service crond reload //重新载入配置

/sbin/service crond status //启动服务

查看crontab服务是否已设置为开机启动,执行命令:

ntsysv

加入开机自动启动:

1
chkconfig –level 35 crond on

三、crontab命令详解

1.命令格式:

1
2
3
crontab [-u user] file

crontab [-u user] [ -e | -l | -r ]

2.命令功能:

通过crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常设合周期性的日志分析或数据备份等工作。

3.命令参数:

-u user:用来设定某个用户的crontab服务,例如,“-u ixdba”表示设定ixdba用户的crontab服务,此参数一般有root用户来运行。

file:file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。

-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。

-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。

-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。

-i:在删除用户的crontab文件时给确认提示。

4.常用方法:

1). 创建一个新的crontab文件

在 考虑向cron进程提交一个crontab文件之前,首先要做的一件事情就是设置环境变量EDITOR。cron进程根据它来确定使用哪个编辑器编辑 crontab文件。9 9 %的UNIX和LINUX用户都使用vi,如果你也是这样,那么你就编辑$ HOME目录下的. profile文件,在其 中加入这样一行:

EDITOR=vi; export EDITOR

然后保存并退出。不妨创建一个名为 cron的文件,其中是用户名,例如, davecron。在该文件中加入如下的内容。

1
2
3
4
5
# (put your own initials here)echo the date to the console every

# 15minutes between 6pm and 6am

0,15,30,45 18-06 * * * /bin/echo ‘date’ > /dev/console

保存并退出。确信前面5个域用空格分隔。

在 上面的例子中,系统将每隔1 5分钟向控制台输出一次当前时间。如果系统崩溃或挂起,从最后所显示的时间就可以一眼看出系统是什么时间停止工作的。在有些 系统中,用tty1来表示控制台,可以根据实际情况对上面的例子进行相应的修改。为了提交你刚刚创建的crontab文件,可以把这个新创建的文件作为 cron命令的参数:

1
$ crontab davecron

现在该文件已经提交给cron进程,它将每隔1 5分钟运行一次。

同时,新创建文件的一个副本已经被放在/var/spool/cron目录中,文件名就是用户名(即dave)。

2). 列出crontab文件

为了列出crontab文件,可以用:

1
2
3
$ crontab -l

0,15,30,45,18-06 * * * /bin/echo `date` > dev/tty1

你将会看到和上面类似的内容。可以使用这种方法在$ H O M E目录中对crontab文件做一备份:

1
$ crontab -l > $HOME/mycron

这样,一旦不小心误删了crontab文件,可以用上一节所讲述的方法迅速恢复。

3). 编辑crontab文件

如果希望添加、删除或编辑crontab文件中的条目,而E D I TO R环境变量又设置为v i,那么就可以用v i来编辑crontab文件,相应的命令为:

1
$ crontab -e

可以像使用v i编辑其他任何文件那样修改crontab文件并退出。如果修改了某些条目或添加了新的条目,那么在保存该文件时, c r o n会对其进行必要的完整性检查。如果其中的某个域出现了超出允许范围的值,它会提示你。

我们在编辑crontab文件时,没准会加入新的条目。例如,加入下面的一条:

1
2
3
# DT:delete core files,at 3.30am on 1,7,14,21,26,26 days of each month

30 3 1,7,14,21,26 * * /bin/find -name “core’ -exec rm {} \;

现在保存并退出。最好在crontab文件的每一个条目之上加入一条注释,这样就可以知道它的功能、运行时间,更为重要的是,知道这是哪位用户的作业。

现在让我们使用前面讲过的crontab -l命令列出它的全部信息:

1
2
3
4
5
6
7
8
9
10
11
$ crontab -l

# (crondave installed on Tue May 4 13:07:43 1999)

# DT:ech the date to the console every 30 minites

0,15,30,45 18-06 * * * /bin/echo `date` > /dev/tty1

# DT:delete core files,at 3.30am on 1,7,14,21,26,26 days of each month

30 3 1,7,14,21,26 * * /bin/find -name “core’ -exec rm {} \;
4). 删除crontab文件

要删除crontab文件,可以用:

1
$ crontab -r
5). 恢复丢失的crontab文件

如果不小心误删了crontab文件,假设你在自己的$ H O M E目录下还有一个备份,那么可以将其拷贝到/var/spool/cron/,其中是用户名。如果由于权限问题无法完成拷贝,可以用:

1
$ crontab <filename>

其中,是你在$ H O M E目录中副本的文件名。

我建议你在自己的$ H O M E目录中保存一个该文件的副本。我就有过类似的经历,有数次误删了crontab文件(因为r键紧挨在e键的右边)。这就是为什么有些系统文档建议不要直接编辑crontab文件,而是编辑该文件的一个副本,然后重新提交新的文件。

有些crontab的变体有些怪异,所以在使用crontab命令时要格外小心。如果遗漏了任何选项,crontab可能会打开一个空文件,或者看起来像是个空文件。这时敲delete键退出,不要按,否则你将丢失crontab文件。

5.使用实例

实例1:每1分钟执行一次command

命令:

1
* * * * * command

实例2:每小时的第3和第15分钟执行
命令:

1
3,15 * * * * command

实例3:在上午8点到11点的第3和第15分钟执行
命令:

1
3,15 8-11 * * * command

实例4:每隔两天的上午8点到11点的第3和第15分钟执行
命令:

1
3,15 8-11 */2 * * command

实例5:每个星期一的上午8点到11点的第3和第15分钟执行
命令:

1
3,15 8-11 * * 1 command

实例6:每晚的21:30重启smb
命令:

1
30 21 * * * /etc/init.d/smb restart

实例7:每月1、10、22日的4 : 45重启smb
命令:

1
45 4 1,10,22 * * /etc/init.d/smb restart

实例8:每周六、周日的1 : 10重启smb
命令:

1
10 1 * * 6,0 /etc/init.d/smb restart

实例9:每天18 : 00至23 : 00之间每隔30分钟重启smb
命令:

1
0,30 18-23 * * * /etc/init.d/smb restart

实例10:每星期六的晚上11 : 00 pm重启smb
命令:

1
0 23 * * 6 /etc/init.d/smb restart

实例11:每一小时重启smb
命令:

1
* */1 * * * /etc/init.d/smb restart

实例12:晚上11点到早上7点之间,每隔一小时重启smb
命令:

1
* 23-7/1 * * * /etc/init.d/smb restart

实例13:每月的4号与每周一到周三的11点重启smb
命令:

1
0 11 4 * mon-wed /etc/init.d/smb restart

实例14:一月一号的4点重启smb
命令:

1
0 4 1 jan * /etc/init.d/smb restart

实例15:每小时执行/etc/cron.hourly目录内的脚本
命令:

1
01 * * * * root run-parts /etc/cron.hourly

说明:
run-parts这个参数了,如果去掉这个参数的话,后面就可以写要运行的某个脚本名,而不是目录名了

四、使用注意事项

注意环境变量问题

有时我们创建了一个crontab,但是这个任务却无法自动执行,而手动执行这个任务却没有问题,这种情况一般是由于在crontab文件中没有配置环境变量引起的。

在 crontab文件中定义多个调度任务时,需要特别注意的一个问题就是环境变量的设置,因为我们手动执行某个任务时,是在当前shell环境下进行的,程 序当然能找到环境变量,而系统自动执行任务调度时,是不会加载任何环境变量的,因此,就需要在crontab文件中指定任务运行所需的所有环境变量,这 样,系统执行任务调度时就没有问题了。

不要假定cron知道所需要的特殊环境,它其实并不知道。所以你要保证在shelll脚本中提供所有必要的路径和环境变量,除了一些自动设置的全局变量。所以注意如下3点:

1)脚本中涉及文件路径时写全局路径;

2)脚本执行要用到java或其他环境变量时,通过source命令引入环境变量,如:

cat start_cbp.sh

1
2
3
4
5
6
7
#!/bin/sh

source /etc/profile

export RUN_CONF=/home/d139/conf/platform/cbp/cbp_jboss.conf

/usr/local/jboss-4.0.5/bin/run.sh -c mev &

3)当手动执行脚本OK,但是crontab死活不执行时。这时必须大胆怀疑是环境变量惹的祸,并可以尝试在crontab中直接引入环境变量解决问题。如:

1
0 * * * * . /etc/profile;/bin/sh /var/www/java/audit_no_count/bin/restart_audit.sh
注意清理系统用户的邮件日志

每条任务调度执行完毕,系统都会将任务输出信息通过电子邮件的形式发送给当前系统用户,这样日积月累,日志信息会非常大,可能会影响系统的正常运行,因此,将每条任务进行重定向处理非常重要。

例如,可以在crontab文件中设置如下形式,忽略日志输出:

1
0 */3 * * * /usr/local/apache2/apachectl restart >/dev/null 2>&1

“/dev/null 2>&1”表示先将标准输出重定向到/dev/null,然后将标准错误重定向到标准输出,由于标准输出已经重定向到了/dev/null,因此标准错误也会重定向到/dev/null,这样日志输出问题就解决了。

系统级任务调度与用户级任务调度

系 统级任务调度主要完成系统的一些维护操作,用户级任务调度主要完成用户自定义的一些任务,可以将用户级任务调度放到系统级任务调度来完成(不建议这么 做),但是反过来却不行,root用户的任务调度操作可以通过“crontab –uroot –e”来设置,也可以将调度任务直接写入/etc /crontab文件,需要注意的是,如果要定义一个定时重启系统的任务,就必须将任务放到/etc/crontab文件,即使在root用户下创建一个 定时重启系统的任务也是无效的。

其他注意事项

新创建的cron job,不会马上执行,至少要过2分钟才执行。如果重启cron则马上执行。

当crontab突然失效时,可以尝试/etc/init.d/crond restart解决问题。或者查看日志看某个job有没有执行/报错tail -f /var/log/cron

千万别乱运行crontab -r。它从Crontab目录(/var/spool/cron)中删除用户的Crontab文件。删除了该用户的所有crontab都没了。

在crontab中%是有特殊含义的,表示换行的意思。如果要用的话必须进行转义%,如经常用的date ‘+%Y%m%d’在crontab里是不会执行的,应该换成date ‘+%Y%m%d’。

Stream排序

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
List<类> list; 代表某集合

//返回 对象集合以类属性一升序排序

list.stream().sorted(Comparator.comparing(类::属性一));

//返回 对象集合以类属性一降序排序 注意两种写法

list.stream().sorted(Comparator.comparing(类::属性一).reversed());//先以属性一升序,结果进行属性一降序

list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()));//以属性一降序

//返回 对象集合以类属性一升序 属性二升序

list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二));

//返回 对象集合以类属性一降序 属性二升序 注意两种写法

list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二));//先以属性一升序,升序结果进行属性一降序,再进行属性二升序

list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()).thenComparing(类::属性二));//先以属性一降序,再进行属性二升序

//返回 对象集合以类属性一降序 属性二降序 注意两种写法

list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一升序,升序结果进行属性一降序,再进行属性二降序

list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()).thenComparing(类::属性二,Comparator.reverseOrder()));
//先以属性一降序,再进行属性二降序

//返回 对象集合以类属性一升序 属性二降序 注意两种写法

list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二).reversed());//先以属性一升序,升序结果进行属性一降序,再进行属性二升序,结果进行属性一降序属性二降序

list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一升序,再进行属性二降序

spring-boot使用log4j2

1.在 Spring Boot 应用中集成 Log4j2

首先在项目pom文件中引入Log4j的依赖,排除掉默认的logging

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

在当前项目的resource目录下增加文件 log4j2.xml

img

下面介绍配置文件中各个节点,感兴趣的小伙伴也可以去官网查看

2.配置节点

2.1 根节点configuration

有两个属性:status,monitorinterval,两个子节点:Appenders和Loggers表名可以定义多个Appender和Logger

  • status,用来指定log4j本身的打印日志级别 OFF>FATAL>ERROR>WARN>INFO>DEBUG>TRACE>ALL
  • monitorinterval 重新读取配置文件的监测间隔时间,单位是s,最小是5s.

2.2 Properties

​ 属性(可选),用来定义常量(例如日志打印的模版),之后在其他配置项中通过${变量名}引用

2.3 Appenders

​ 常见的有三种子节点:Console、RollingFile、File

2.3.1 Console

​ 用来定义输出到控制台的Appender两个属性:name、target,一个节点:PatternLayout

  • name(属性):指定Appender的名字
  • target(属性):SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT.
  • PatternLayout(节点):指定输出日志的格式,可以使用pattern属性与Properties中定义的日志打印模板常量相结合,控制日志输出的模版,不设置则默认为:%m%n
2.3.2 File

​ 文件输出源,用于将日志写入到指定的文件,需要配置输入到哪个位置(例如:D:/logs/info.log)

  • name(属性)::指定Appender的名字.
  • fileName(属性):指定输出日志的目的文件带全路径的文件名.
  • PatternLayout(节点):输出格式,不设置默认为:%m%n.
2.3.3 RollingFile

​ 也是写入到文件,不同的是比File更加强大,可以指定当文件达到一定大小(如20MB)时,另起一个文件继续写入日志,另起一个文件就涉及到新文件的名字命名规则,因此需要配置文件命名规则
​ 这种方式更加实用,因为你不可能一直往一个文件中写,如果一直写,文件过大,打开就会卡死,也不便于查找日志。

  • name(属性):指定Appender的名字.
  • fileName(属性):指定输出日志的目的文件带全路径的文件名
  • PatternLayout(节点):输出格式,不设置默认为:%m%n
  • filePattern:指定新建日志文件的名称格式.
  • Policies(节点):指定滚动日志的策略,就是什么时候进行新建日志文件输出日志.
    • TimeBasedTriggeringPolicy(节点):Policies子节点,基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am..而不是7am.
    • SizeBasedTriggeringPolicy(节点):Policies子节点,基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小.
    • DefaultRolloverStrategy(节点):用来指定同一个文件夹下最多有几个日志文件时开始删除最l旧的,创建新的(通过max属性);可以指定压缩级别(通过compressionLevel属性),compressionLevel的值通常是一个介于0到9之间的整数,其中:0表示无压缩,1表示最快的压缩速度(但压缩率最低),9表示最高的压缩率(但压缩速度最慢)
      • Delete:清理旧日志文件的策略;basePath指定了日志文件所在的基目录;maxDepth用于指定清理或删除日志文件时搜索的目录深度,值是一个整数,表示从basePath开始向下搜索的目录层级数
      • IfFileName:Delete子节点,用于匹配要删除的文件名模式。glob接受一个通配符模式,用来匹配文件名。
        • IfLastModified:Delete子节点,用于检查文件的最后修改时间,并基于该时间点决定文件是否应该被清理或删除;age指定了文件最后修改时间与当前时间之间最大允许的时间差
          • 如果maxDepth=”0”,则只会考虑basePath所指向的目录本身,不包括任何子目录。
          • 如果maxDepth=”1”,则会考虑basePath所指向的目录及其直接子目录。
          • 如果maxDepth=”2”,则会考虑basePath所指向的目录、其直接子目录以及这些子目录下的子目录(即二级子目录)。

2.4 Logger

​ Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。level:日志输出级别,共有8个级别,按照从低到高为: All < Trace < Debug < Info < Warn < Error < Fatal < OFF

  • root :设定根日志的日志级别
  • name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.
  • AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个- Appender和Root的Appender中都会输出。
  • additivity:是否继承root节点,默认是true继承。默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。

3.日志输出模板参数介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 格式化输出:
%date{yyyy-MM-dd HH:mm:ss.SSS}: 简写为%d 日期 2023-08-12 15:04:30,123
%thread: %t 线程名, main
%-5level:%p 日志级别,从左往右至少显示5个字符宽度,不足补空格 INFO
%msg:%m 日志消息 info msg
%n: 换行符
{cyan}: 蓝绿色(青色)
%logger{36}: %c 表示 Logger 名字最长36个字符
%C: 类路径 com.qq.demolog4j2.TestLog4j2
%M: 方法名 main
%F: 类名 TestLog4j2.java
%L: 行号 12
%l: 日志位置, 相当于 %C.%M(%F.%L) com.qq.demolog4j2.TestLog4j2.main(TestLog4j2.java:16)
%highlight:高亮显示,%highlight{%-5level}

如果控制台输出的日志级别没有高亮显示,Log4j2默认关闭了Jansi:一个支持输出ANSI颜色的类库 IDEA中,点击右上角->Edit+Configurations,在VM+options中添加-Dlog4j.skipJansi=false

下面就是我在开发中使用的一些配置

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">

<properties>
<!-- 日志打印级别 -->

<property name="LOG_LEVEL">INFO</property>
<!-- APP名称 -->
<property name="APP_NAME" value="framework-project"/>
<!-- 日志文件存储路径 -->
<property name="LOG_HOME">./logs/</property>
<!-- 存储天数 -->
<property name="LOG_MAX_HISTORY" value="60d"/>
<!-- 单个日志文件最大值, 单位 = KB, MB, GB -->
<property name="LOG_MAX_FILE_SIZE" value="10 MB"/>
<!-- 每天每个日志级别产生的文件最大数量 -->
<property name="LOG_TOTAL_NUMBER_DAILY" value="10"/>
<!-- 压缩文件的类型,支持zip和gz,建议Linux用gz,Windows用zip -->
<property name="ARCHIVE_FILE_SUFFIX" value="zip"/>
<!-- 日志文件名 -->
<property name="LOG_FILE_NAME" value="${LOG_HOME}"/>
<property name="FILE_NAME_PATTERN" value="${LOG_HOME}%d{yyyy-MM-dd}"/>

<!--
格式化输出:
%date{yyyy-MM-dd HH:mm:ss.SSS}: 简写为%d 日期 2023-08-12 15:04:30,123
%thread: %t 线程名, main
%-5level:%p 日志级别,从左往右至少显示5个字符宽度,不足补空格 INFO
%msg:%m 日志消息 info msg
%n: 换行符
{cyan}: 蓝绿色(青色)
%logger{36}: %c 表示 Logger 名字最长36个字符
%C: 类路径 com.qq.demolog4j2.TestLog4j2
%M: 方法名 main
%F: 类名 TestLog4j2.java
%L: 行号 12
%l: 日志位置, 相当于 %C.%M(%F.%L) com.qq.demolog4j2.TestLog4j2.main(TestLog4j2.java:16)
-->
<!-- %d: 日期
%-5level: 日志级别,显示时占5个字符不足
[%t]: 线程名
%c{1.}: 显示调用者,只显示包名最后一截及方法名,前面的只取首字母
.%M(代码行号%L):
%msg%n": 需要打印的日志信息,换行:INFO>[MsgToMP:99]
Bright: 加粗 -->
<!--日志输出格式-控制台彩色打印-->
<property name="ENCODER_PATTERN_CONSOLE">%blue{%d{yyyy-MM-dd HH:mm:ss.SSS}} | %highlight{%-5level}{ERROR=Bright RED, WARN=Bright Yellow, INFO=Bright Green, DEBUG=Bright Cyan, TRACE=Bright White} | %yellow{%t} | %cyan{%c{1.}} : %white{%msg%n}</property>
<!--日志输出格式-文件-->
<property name="ENCODER_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %5pid --- [%15.15t] %c{1.} [%L] : %m%n</property>
<!--日志输出格式-控制台彩色打印-->
<property name="DEFAULT_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level} %style{%5pid}{bright,magenta} --- [%15.15t] %cyan{%c{1.} [%L]} : %m%n</property>
</properties>

<Appenders>
<!-- 控制台的输出配置 -->
<Console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${DEFAULT_PATTERN}" />
</Console>
<!-- 打印出所有的info及以下级别的信息,每次大小超过size进行压缩,作为存档-->
<RollingFile name="RollingFileAll" fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/info.log" filePattern="${FILE_NAME_PATTERN}/info.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="${LOG_LEVEL}" onMatch="ACCEPT" onMismatch="DENY" />
<!--输出日志的格式-->
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="9" max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob=".info.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>

<RollingFile name="RollingFileDebug"
fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/debug.log"
filePattern="${FILE_NAME_PATTERN}/debug.log">
<Filters>
<ThresholdFilter level="DEBUG" />
<ThresholdFilter level="INFO" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="9"
max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob="*.debug.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>

<RollingFile name="RollingFileWarn" fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/warn.log"
filePattern="${FILE_NAME_PATTERN}.warn.log">
<Filters>
<ThresholdFilter level="WARN" />
<ThresholdFilter level="ERROR" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="9"
max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob="*.warn.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>

<RollingFile name="RollingFileError"
fileName="${LOG_FILE_NAME}/${date:yyyy-MM-dd}/error.log"
filePattern="${FILE_NAME_PATTERN}.error.log">
<Filters>
<ThresholdFilter level="ERROR" />
</Filters>
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="${LOG_MAX_FILE_SIZE}" />
</Policies>
<DefaultRolloverStrategy compressionLevel="9" max="${LOG_TOTAL_NUMBER_DAILY}">
<Delete basePath="${LOG_HOME}" maxDepth="1">
<IfFileName glob="*.error.*.log" />
<IfLastModified age="${LOG_MAX_HISTORY}" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>

<!--只有定义了logger并引入以上Appenders,Appender才会生效-->

<Loggers>
<Logger name="com.example" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileAll"/>
<AppenderRef ref="RollingFileDebug"/>
<AppenderRef ref="RollingFileWarn"/>
<AppenderRef ref="RollingFileError"/>
</Logger>
<root level="${LOG_LEVEL}">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileAll"/>
<appender-ref ref="RollingFileDebug"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</Loggers>
</configuration>

springboot项目初始话配置

全局序列化配置

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
package com.example.projectdemo.common;

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class JacksonRuleCustomizerConfiguration {

@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return builder -> builder.serializerByType(Long.class, new ToStringSerializer())
.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter))
.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter))
.serializerByType(LocalDate.class, new LocalDateSerializer(dateFormatter))
.deserializerByType(LocalDate.class, new LocalDateDeserializer(dateFormatter))
;
}
}

全局跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.projectdemo.common;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")
.allowedOrigins("*");
}
}

全局统一响应

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
package com.example.projectdemo.common;

import lombok.Data;

@Data
public class Result {
private int code;
private String msg;

private Object data;
public Result(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}

public static Result success(Object data) {
return new Result(200, "success", data);
}

public static Result success(String msg) {
return new Result(200, msg, null);
}

public static Result error(Object data) {
return new Result(500, "fail", data);
}

public static Result error(int code ,String msg) {
return new Result(code, msg, null);
}
public static Result error(String msg, Object data) {
return new Result(500, msg, data);
}
}

枚举类

轻量使用

通过单个枚举实现数据接收数据返回,数据库传递
entity类的字段类型为一个枚举

1
2
3
4
/**
* 状态
*/
private UserStatusEnum status;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将枚举包装成json对象,处理响应
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@AllArgsConstructor
public enum UserStatusEnum{
OK(1, "正常"),
FREEZE(2, "冻结"),
DELETE(3, "删除");
@Getter
@JsonValue //json对象的值,响应需要返回的值
@EnumValue //mybatisplus 枚举值
private Integer value;
@Getter
private String name;
}

mapper中通过获取枚举的value

1
2
3
 <select id="listByStatus" resultType="com.example.projectdemo.entity.BladeUser">
select * from blade_user where status = #{status.value}
</select>

全局统一返回

基础注解

1
2
3
4
5
6
7
8
9
package com.example.projectdemo.entity.enume;

import com.baomidou.mybatisplus.annotation.IEnum;

import java.io.Serializable;

public interface BasicEnum extends IEnum<Integer>, Serializable {
String getName();
}

枚举常量

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
package com.example.projectdemo.entity;

import com.example.projectdemo.entity.enume.BasicEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;

public interface EnumConstant {
@AllArgsConstructor
enum UserStatusEnum implements BasicEnum {
OK(1, "正常"),
FREEZE(2, "冻结"),
DELETE(3, "删除");
@Getter
private Integer value;
@Getter
private String name;

}

@AllArgsConstructor
enum UserSexEnum implements BasicEnum{
MAN(1, "男"),
WOMEN(2, "女");
@Getter
private Integer value;
@Getter
private String name;
}
}

枚举解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.projectdemo.common;

import com.example.projectdemo.entity.enume.BasicEnum;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class EnumSerializer extends JsonSerializer<BasicEnum> {

@Override
public void serialize(BasicEnum basicEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 将枚举值写入
jsonGenerator.writeNumber(basicEnum.getValue());
// 获取当前字段名
String currentName = serializerProvider.getGenerator().getOutputContext().getCurrentName();
// 设置枚举值的名称
jsonGenerator.writeStringField(currentName+"DictText", basicEnum.getName());
}
}

nginx指南

原文地址

特点

  • 高并发、高性能;
  • 模块化架构使得它的扩展性非常好;
  • 异步非阻塞的事件驱动模型这点和 Node.js 相似;
  • 相对于其它服务器来说它可以连续几个月甚至更长而不需要重启服务器使得它具有高可靠性;
  • 热部署、平滑升级;
  • 完全开源,生态繁荣。

安装

1
yum install nginx -y

常规文件目录

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
# Nginx配置文件
/etc/nginx/nginx.conf # nginx 主配置文件
/etc/nginx/nginx.conf.default

# 可执行程序文件
/usr/bin/nginx-upgrade
/usr/sbin/nginx

# nginx库文件
/usr/lib/systemd/system/nginx.service # 用于配置系统守护进程
/usr/lib64/nginx/modules # Nginx模块目录

# 帮助文档
/usr/share/doc/nginx-1.16.1
/usr/share/doc/nginx-1.16.1/CHANGES
/usr/share/doc/nginx-1.16.1/README
/usr/share/doc/nginx-1.16.1/README.dynamic
/usr/share/doc/nginx-1.16.1/UPGRADE-NOTES-1.6-to-1.10

# 静态资源目录
/usr/share/nginx/html/404.html
/usr/share/nginx/html/50x.html
/usr/share/nginx/html/index.html

# 存放Nginx日志文件
/var/log/nginx

常用命令

systemctl 系统命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 开机配置
systemctl enable nginx # 开机自动启动
systemctl disable nginx # 关闭开机自动启动

# 启动Nginx
systemctl start nginx # 启动Nginx成功后,可以直接访问主机IP,此时会展示Nginx默认页面

# 停止Nginx
systemctl stop nginx

# 重启Nginx
systemctl restart nginx

# 重新加载Nginx
systemctl reload nginx

# 查看 Nginx 运行状态
systemctl status nginx

# 查看Nginx进程
ps -ef | grep nginx

# 杀死Nginx进程
kill -9 pid # 根据上面查看到的Nginx进程号,杀死Nginx进程,-9 表示强制结束进程

Nginx 应用程序命令:

1
2
3
4
5
6
nginx -s reload  # 向主进程发送信号,重新加载配置文件,热重启
nginx -s reopen # 重启 Nginx
nginx -s stop # 快速关闭
nginx -s quit # 等待工作进程处理完成后关闭
nginx -T # 查看当前 Nginx 最终的配置
nginx -t # 检查配置是否有问

配置示例

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
51

# main段配置信息
user nginx; # 运行用户,默认即是nginx,可以不进行设置
worker_processes auto; # Nginx 进程数,一般设置为和 CPU 核数一样
error_log /var/log/nginx/error.log warn; # Nginx 的错误日志存放目录
pid /var/run/nginx.pid; # Nginx 服务启动时的 pid 存放位置

# events段配置信息
events {
use epoll; # 使用epoll的I/O模型(如果你不知道Nginx该使用哪种轮询方法,会自动选择一个最适合你操作系统的)
worker_connections 1024; # 每个进程允许最大并发数
}

# http段配置信息
# 配置使用最频繁的部分,代理、缓存、日志定义等绝大多数功能和第三方模块的配置都在这里设置
http {
# 设置日志模式
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 /var/log/nginx/access.log main; # Nginx访问日志存放位置

sendfile on; # 开启高效传输模式
tcp_nopush on; # 减少网络报文段的数量
tcp_nodelay on;
keepalive_timeout 65; # 保持连接的时间,也叫超时时间,单位秒
types_hash_max_size 2048;

include /etc/nginx/mime.types; # 文件扩展名与类型映射表
default_type application/octet-stream; # 默认文件类型

include /etc/nginx/conf.d/*.conf; # 加载子配置项

# server段配置信息
server {
listen 80; # 配置监听的端口
server_name localhost; # 配置的域名

# location段配置信息
location / {
root /usr/share/nginx/html; # 网站根目录
index index.html index.htm; # 默认首页文件
deny 172.168.22.11; # 禁止访问的ip地址,可以为all
allow 172.168.33.44# 允许访问的ip地址,可以为all
}

error_page 500 502 503 504 /50x.html; # 默认50x对应的访问页面
error_page 400 404 error.html; # 同上
}
}
  • main: 全局配置
  • events :影响nginx 服务器与用户的网络连接;
  • http 配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置
  • server 配置虚拟主机的相关参数,一个http块中可以有多个server块
  • location 用于配置匹配的uri
  • upstream 负责后端服务器具体地址,负载均衡配置不可或缺的部分

user:指定运行 Nginx 的 woker 子进程的属主和属组,其中组可以不指定。

1
2
user USERNAME [GROUP]
user nginx lion; # 用户是nginx;组是lion

pid:指定运行 Nginx master 主进程的 pid 文件存放路径。

1
pid /opt/nginx/logs/nginx.pid # master主进程的的pid存放在nginx.pid的文件

worker_rlimit_nofile_number:指定 worker 子进程可以打开的最大文件句柄数。

1
worker_rlimit_nofile 20480; # 可以理解成每个worker子进程的最大连接数量。

worker_rlimit_core:指定 worker 子进程异常终止后的 core 文件,用于记录分析问题。

1
2
worker_rlimit_core 50M; # 存放大小限制
working_directory /opt/nginx/tmp; # 存放目录

worker_processes_number:指定 Nginx 启动的 worker 子进程数量。

1
2
worker_processes 4; # 指定具体子进程数量
worker_processes auto; # 与当前cpu物理核心数一致

worker_cpu_affinity:将每个 worker 子进程与我们的 cpu 物理核心绑定。

将每个 worker 子进程与特定 CPU 物理核心绑定,优势在于,避免同一个 worker 子进程在不同的 CPU 核心上切换,缓存失效,降低性能。但其并不能真正的避免进程切换。

worker_priority:指定 worker 子进程的 nice 值,以调整运行 Nginx 的优先级,通常设定为负值,以优先调用 Nginx 。

1
worker_priority -10; # 120-10=110,110就是最终的优先级

Linux 默认进程的优先级值是120,值越小越优先;nice 定范围为 -20 到 +19 。

[备注] 应用的默认优先级值是120加上 nice 值等于它最终的值,这个值越小,优先级越高。

worker_shutdown_timeout:指定 worker 子进程优雅退出时的超时时间。

1
worker_shutdown_timeout 5s;

timer_resolution:worker 子进程内部使用的计时器精度,调整时间间隔越大,系统调用越少,有利于性能提升;反之,系统调用越多,性能下降。

1
timer_resolution 100ms;

在 Linux 系统中,用户需要获取计时器时需要向操作系统内核发送请求,有请求就必然会有开销,因此这个间隔越大开销就越小。

daemon:指定 Nginx 的运行方式,前台还是后台,前台用于调试,后台用于生产。

1
daemon off; # 默认是on,后台运行模式

6.3 配置文件 events 段核心参数

use:Nginx 使用何种事件驱动模型。

1
2
use method; # 不推荐配置它,让nginx自己选择
method 可选值为:select、poll、kqueue、epoll、/dev/poll、eventport

worker_connections:worker 子进程能够处理的最大并发连接数。

1
worker_connections 1024 # 每个子进程的最大连接数为1024

accept_mutex:是否打开负载均衡互斥锁。

1
accept_mutex on # 默认是off关闭的,这里推荐打开

server_name 指令

指定虚拟主机域名。

1
2
server_name name1 name2 name3
# 示例:server_name www.nginx.com;

域名匹配的四种写法:

  • 精确匹配:server_name www.nginx.com ;
  • 左侧通配:server_name *.nginx.com ;
  • 右侧统配:server_name www.nginx.* ;
  • 正则匹配:server_name ~^www.nginx.*$ ;

匹配优先级:精确匹配 > 左侧通配符匹配 > 右侧通配符匹配 > 正则表达式匹配

server_name 配置实例:

  1. 配置本地 DNS 解析 vim /etc/hosts ( macOS 系统)
1
# 添加如下内容,其中 121.42.11.34 是阿里云服务器IP地址121.42.11.34 www.nginx-test.com121.42.11.34 mail.nginx-test.com121.42.11.34 www.nginx-test.org121.42.11.34 doc.nginx-test.com121.42.11.34 www.nginx-test.cn121.42.11.34 fe.nginx-test.club

[注意] 这里使用的是虚拟域名进行测试,因此需要配置本地 DNS 解析,如果使用阿里云上购买的域名,则需要在阿里云上设置好域名解析。

  1. 配置阿里云 Nginx ,vim /etc/nginx/nginx.conf
1
2
3
4
5
# 这里只列举了http端中的sever端配置
# 左匹配server { listen 80; server_name *.nginx-test.com; root /usr/share/nginx/html/nginx-test/left-match/; location / { index index.html; }}
# 正则匹配server { listen 80; server_name ~^.*\.nginx-test\..*$; root /usr/share/nginx/html/nginx-test/reg-match/; location / { index index.html; }}
# 右匹配server { listen 80; server_name www.nginx-test.*; root /usr/share/nginx/html/nginx-test/right-match/; location / { index index.html; }}
# 完全匹配server { listen 80; server_name www.nginx-test.com; root /usr/share/nginx/html/nginx-test/all-match/; location / { index index.html; }}
  1. 访问分析
  • 当访问 www.nginx-test.com 时,都可以被匹配上,因此选择优先级最高的“完全匹配”;
  • 当访问 mail.nginx-test.com 时,会进行“左匹配”;
  • 当访问 www.nginx-test.org 时,会进行“右匹配”;
  • 当访问 doc.nginx-test.com 时,会进行“左匹配”;
  • 当访问 www.nginx-test.cn 时,会进行“右匹配”;
  • 当访问 fe.nginx-test.club 时,会进行“正则匹配”。

root

指定静态资源目录位置,它可以写在 http 、 server 、 location 等配置中。

1
2
3
root path
例如:location /image { root /opt/nginx/static;}
当用户访问 www.test.com/image/1.png 时,实际在服务器找的路径是 /opt/nginx/static/image/1.png

[注意] root 会将定义路径与 URI 叠加, alias 则只取定义路径。

alias

它也是指定静态资源目录位置,它只能写在 location 中。

1
2
location /image {  alias /opt/nginx/static/image/;}
当用户访问 www.test.com/image/1.png 时,实际在服务器找的路径是 /opt/nginx/static/image/1.png

[注意] 使用 alias 末尾一定要添加 / ,并且它只能位于 location 中。

location

配置路径。

1
location [ = | ~ | ~* | ^~ ] uri {  ...}

匹配规则:

  • = 精确匹配;
  • ~ 正则匹配,区分大小写;
  • ~* 正则匹配,不区分大小写;
  • ^~ 匹配到即停止搜索;

匹配优先级:= > ^~ > ~ > ~* > 不带任何字符。

实例:

1
2
3
4
server {  listen  80;  server_name  www.nginx-test.com;
# 只有当访问 www.nginx-test.com/match_all/ 时才会匹配到/usr/share/nginx/html/match_all/index.html location = /match_all/ { root /usr/share/nginx/html index index.html }
# 当访问 www.nginx-test.com/1.jpg 等路径时会去 /usr/share/nginx/images/1.jpg 找对应的资源 location ~ \.(jpeg|jpg|png|svg)$ { root /usr/share/nginx/images; }
# 当访问 www.nginx-test.com/bbs/ 时会匹配上 /usr/share/nginx/html/bbs/index.html location ^~ /bbs/ { root /usr/share/nginx/html; index index.html index.htm; }}
  1. location 中的反斜线
1
2
location /test {  ...}
location /test/ { ...}
  • 不带 / 当访问 www.nginx-test.com/test 时, Nginx 先找是否有 test 目录,如果有则找 test 目录下的 index.html ;如果没有 test 目录, nginx 则会找是否有 test 文件;
  • 带 / 当访问 www.nginx-test.com/test 时, Nginx 先找是否有 test 目录,如果有则找 test 目录下的 index.html ,如果没有它也不会去找是否存在 test 文件。

2) return

停止处理请求,直接返回响应码或重定向到其他 URL ;执行 return 指令后, location 中后续指令将不会被执行。

1
2
3
4
5
return code [text];return code URL;return URL;
例如:location / { return 404; # 直接返回状态码}
location / { return 404 "pages not found"; # 返回状态码 + 一段文本}
location / { return 302 /bbs ; # 返回状态码 + 重定向地址}
location / { return https://www.baidu.com ; # 返回重定向地址}

rewrite

根据指定正则表达式匹配规则,重写 URL 。

1
2
3
语法:rewrite 正则表达式 要替换的内容 [flag];
上下文:server、location、if
示例:rewirte /images/(.*\.jpg)$ /pic/$1; # $1是前面括号(.*\.jpg)的反向引用

flag 可选值的含义:

  • last 重写后的 URL 发起新请求,再次进入 server 段,重试 location 的中的匹配;
  • break 直接使用重写后的 URL ,不再匹配其它 location 中语句;
  • redirect 返回 302 临时重定向;
  • permanent 返回 301 永久重定向。
1
2
3
4
5
server{  listen 80;  server_name fe.lion.club; # 要在本地hosts文件进行配置  root html;  location /search {    rewrite ^/(.*) https://www.baidu.com redirect;  }
location /images { rewrite /images/(.*) /pics/$1; }
location /pics { rewrite /pics/(.*) /photos/$1; }
location /photos {
}}

按照这个配置我们来分析:

  • 当访问 fe.lion.club/search 时,会自动帮我们重定向到 https://www.baidu.com;
  • 当访问 fe.lion.club/images/1.jpg 时,第一步重写 URL 为 fe.lion.club/pics/1.jpg ,找到 pics 的 location ,继续重写 URL 为 fe.lion.club/photos/1.jpg ,找到 /photos 的 location 后,去 html/photos 目录下寻找 1.jpg 静态资源。

if 指令

1
2
3
语法:if (condition) {...}
上下文:server、location
示例:if($http_user_agent ~ Chrome){ rewrite /(.*)/browser/$1 break;}

condition 判断条件:

  • $variable 仅为变量时,值为空或以0开头字符串都会被当做 false 处理;
  • =!= 相等或不等;
  • ~ 正则匹配;
  • ! ~ 非正则匹配;
  • ~* 正则匹配,不区分大小写;
  • -f! -f 检测文件存在或不存在;
  • -d! -d 检测目录存在或不存在;
  • -e! -e 检测文件、目录、符号链接等存在或不存在;
  • -x! -x 检测文件可以执行或不可执行;

实例:

1
2
server {  listen 8080;  server_name localhost;  root html;
location / { if ( $uri = "/images/" ){ rewrite (.*) /pics/ break; } }}

当访问 localhost:8080/images/ 时,会进入 if 判断里面执行 rewrite 命令。

autoindex

用户请求以 / 结尾时,列出目录结构,可以用于快速搭建静态资源下载网站。

autoindex.conf 配置信息:

1
2
3
server {  listen 80;  server_name fe.lion-test.club;
location /download/ { root /opt/source;
autoindex on; # 打开 autoindex,,可选参数有 on | off autoindex_exact_size on; # 修改为off,以KB、MB、GB显示文件大小,默认为on,以bytes显示出⽂件的确切⼤⼩ autoindex_format html; # 以html的方式进行格式化,可选参数有 html | json | xml autoindex_localtime off; # 显示的⽂件时间为⽂件的服务器时间。默认为off,显示的⽂件时间为GMT时间 }}

当访问 fe.lion.com/download/ 时,会把服务器 /opt/source/download/ 路径下的文件展示出来,如下图所示:

图片

6.4 变量

Nginx 提供给使用者的变量非常多,但是终究是一个完整的请求过程所产生数据, Nginx 将这些数据以变量的形式提供给使用者。

下面列举些项目中常用的变量:

图片

实例演示 var.conf :

1
server{  listen 8081;  server_name var.lion-test.club;  root /usr/share/nginx/html;  location / {    return 200 "remote_addr: $remote_addrremote_port: $remote_portserver_addr: $server_addrserver_port: $server_portserver_protocol: $server_protocolbinary_remote_addr: $binary_remote_addrconnection: $connectionuri: $urirequest_uri: $request_urischeme: $schemerequest_method: $request_methodrequest_length: $request_lengthargs: $argsarg_pid: $arg_pidis_args: $is_argsquery_string: $query_stringhost: $hosthttp_user_agent: $http_user_agenthttp_referer: $http_refererhttp_via: $http_viarequest_time: $request_timehttps: $httpsrequest_filename: $request_filenamedocument_root: $document_root";  }}

Docker+K8s+Jenkins 主流技术全解视频资料

当我们访问 http://var.lion-test.club:8081/test?pid=121414&cid=sadasd 时,由于 Nginx 中写了 return 方法,因此 chrome 浏览器会默认为我们下载一个文件,下面展示的就是下载的文件内容:

1
remote_addr: 27.16.220.84remote_port: 56838server_addr: 172.17.0.2server_port: 8081server_protocol: HTTP/1.1binary_remote_addr: 茉connection: 126uri: /test/request_uri: /test/?pid=121414&cid=sadasdscheme: httprequest_method: GETrequest_length: 518args: pid=121414&cid=sadasdarg_pid: 121414is_args: ?query_string: pid=121414&cid=sadasdhost: var.lion-test.clubhttp_user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36http_referer: http_via: request_time: 0.000https: request_filename: /usr/share/nginx/html/test/document_root: /usr/share/nginx/html

Nginx 的配置还有非常多,以上只是罗列了一些常用的配置,在实际项目中还是要学会查阅文档。

7. Nginx 应用核心概念

代理是在服务器和客户端之间假设的一层服务器,代理将接收客户端的请求并将它转发给服务器,然后将服务端的响应转发给客户端。

不管是正向代理还是反向代理,实现的都是上面的功能。

图片

7.1 正向代理

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。

正向代理是为我们服务的,即为客户端服务的,客户端可以根据正向代理访问到它本身无法访问到的服务器资源。

正向代理对我们是透明的,对服务端是非透明的,即服务端并不知道自己收到的是来自代理的访问还是来自真实客户端的访问。

7.2 反向代理

反向代理(Reverse Proxy)方式是指以代理服务器来接受 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

反向代理是为服务端服务的,反向代理可以帮助服务器接收来自客户端的请求,帮助服务器做请求转发,负载均衡等。

反向代理对服务端是透明的,对我们是非透明的,即我们并不知道自己访问的是代理服务器,而服务器知道反向代理在为他服务。

反向代理的优势:

  • 隐藏真实服务器;
  • 负载均衡便于横向扩充后端动态服务;
  • 动静分离,提升系统健壮性。

那么“动静分离”是什么?负载均衡又是什么?

7.3 动静分离

动静分离是指在 Web 服务器架构中,将静态页面与动态页面或者静态内容接口和动态内容接口分开不同系统访问的架构设计方法,进而提示整个服务的访问性和可维护性。

图片

一般来说,都需要将动态资源和静态资源分开,由于 Nginx 的高并发和静态资源缓存等特性,经常将静态资源部署在 Nginx 上。如果请求的是静态资源,直接到静态资源目录获取资源,如果是动态资源的请求,则利用反向代理的原理,把请求转发给对应后台应用去处理,从而实现动静分离。

使用前后端分离后,可以很大程度提升静态资源的访问速度,即使动态服务不可用,静态资源的访问也不会受到影响。

7.4 负载均衡

一般情况下,客户端发送多个请求到服务器,服务器处理请求,其中一部分可能要操作一些资源比如数据库、静态资源等,服务器处理完毕后,再将结果返回给客户端。

这种模式对于早期的系统来说,功能要求不复杂,且并发请求相对较少的情况下还能胜任,成本也低。随着信息数量不断增长,访问量和数据量飞速增长,以及系统业务复杂度持续增加,这种做法已无法满足要求,并发量特别大时,服务器容易崩。

很明显这是由于服务器性能的瓶颈造成的问题,除了堆机器之外,最重要的做法就是负载均衡。

请求爆发式增长的情况下,单个机器性能再强劲也无法满足要求了,这个时候集群的概念产生了,单个服务器解决不了的问题,可以使用多个服务器,然后将请求分发到各个服务器上,将负载分发到不同的服务器,这就是负载均衡,核心是「分摊压力」。Nginx 实现负载均衡,一般来说指的是将请求转发给服务器集群。

举个具体的例子,晚高峰乘坐地铁的时候,入站口经常会有地铁工作人员大喇叭“请走 B 口, B 口人少车空….”,这个工作人员的作用就是负载均衡。

图片

Nginx 实现负载均衡的策略:

  • 轮询策略:默认情况下采用的策略,将所有客户端请求轮询分配给服务端。这种策略是可以正常工作的,但是如果其中某一台服务器压力太大,出现延迟,会影响所有分配在这台服务器下的用户;
  • 最小连接数策略:将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求;
  • 最快响应时间策略:优先分配给响应时间最短的服务器;
  • 客户端 IP 绑定策略:来自同一个 IP 的请求永远只分配一台服务器,有效解决了动态网页存在的 session 共享问题。

8. Nginx 实战配置

在配置反向代理和负载均衡等等功能之前,有两个核心模块是我们必须要掌握的,这两个模块应该说是 Nginx 应用配置中的核心,它们分别是:upstream 、proxy_pass 。

8.1 upstream

用于定义上游服务器(指的就是后台提供的应用服务器)的相关信息。

图片

1
2
3
语法:upstream name {  ...}
上下文:http
示例:upstream back_end_server{ server 192.168.100.33:8081}

在 upstream 内可使用的指令:

  • server 定义上游服务器地址;
  • zone 定义共享内存,用于跨 worker 子进程;
  • keepalive 对上游服务启用长连接;
  • keepalive_requests 一个长连接最多请求 HTTP 的个数;
  • keepalive_timeout 空闲情形下,一个长连接的超时时长;
  • hash 哈希负载均衡算法;
  • ip_hash 依据 IP 进行哈希计算的负载均衡算法;
  • least_conn 最少连接数负载均衡算法;
  • least_time 最短响应时间负载均衡算法;
  • random 随机负载均衡算法。

server

定义上游服务器地址。

1
2
语法:server address [parameters]
上下文:upstream

parameters 可选值:

  • weight=number 权重值,默认为1;
  • max_conns=number 上游服务器的最大并发连接数;
  • fail_timeout=time 服务器不可用的判定时间;
  • max_fails=numer 服务器不可用的检查次数;
  • backup 备份服务器,仅当其他服务器都不可用时才会启用;
  • down 标记服务器长期不可用,离线维护。

keepalive

限制每个 worker 子进程与上游服务器空闲长连接的最大数量。

1
2
3
keepalive connections;
上下文:upstream
示例:keepalive 16;

keepalive_requests

单个长连接可以处理的最多 HTTP 请求个数。

1
2
3
语法:keepalive_requests number;
默认值:keepalive_requests 100;
上下文:upstream

keepalive_timeout

空闲长连接的最长保持时间。

1
2
3
语法:keepalive_timeout time;
默认值:keepalive_timeout 60s;
上下文:upstream

配置实例

1
2
3
语法:keepalive_timeout time;
默认值:keepalive_timeout 60s;
上下文:upstream

8.2 proxy_pass

用于配置代理服务器。

1
2
3
语法:proxy_pass URL;
上下文:location、if、limit_except
示例:proxy_pass http://127.0.0.1:8081proxy_pass http://127.0.0.1:8081/proxy

URL 参数原则:

  1. URL 必须以 http 或 https 开头;
  2. URL 中可以携带变量;
  3. URL 中是否带 URI ,会直接影响发往上游请求的 URL。

接下来让我们来看看两种常见的 URL 用法:

  1. proxy_pass http://192.168.100.33:8081
  2. proxy_pass http://192.168.100.33:8081/

这两种用法的区别就是带 / 和不带 / ,在配置代理时它们的区别可大了:

  • 不带 / 意味着 Nginx 不会修改用户 URL ,而是直接透传给上游的应用服务器;
  • 带 / 意味着 Nginx 会修改用户 URL ,修改方法是将 location 后的 URL 从用户 URL 中删除。

不带 / 的用法:

1
location /bbs/{  proxy_pass http://127.0.0.1:8080;}

分析:

  1. 用户请求 URL :/bbs/abc/test.html
  2. 请求到达 Nginx 的 URL :/bbs/abc/test.html
  3. 请求到达上游应用服务器的 URL :/bbs/abc/test.html

带 / 的用法:

1
location /bbs/{  proxy_pass http://127.0.0.1:8080/;}

分析:

  1. 用户请求 URL :/bbs/abc/test.html
  2. 请求到达 Nginx 的 URL :/bbs/abc/test.html
  3. 请求到达上游应用服务器的 URL :/abc/test.html

并没有拼接上 /bbs ,这点和 root 与 alias 之间的区别是保持一致的。

8.3 配置反向代理

这里为了演示更加接近实际,作者准备了两台云服务器,它们的公网 IP 分别是:121.42.11.34 与 121.5.180.193 。

我们把 121.42.11.34 服务器作为上游服务器,做如下配置:

1
2
# /etc/nginx/conf.d/proxy.confserver{  listen 8080;  server_name localhost;    location /proxy/ {    root /usr/share/nginx/html/proxy;    index index.html;  }}
# /usr/share/nginx/html/proxy/index.html<h1> 121.42.11.34 proxy html </h1>

配置完成后重启 Nginx 服务器 nginx -s reload 。

把 121.5.180.193 服务器作为代理服务器,做如下配置:

1
2
# /etc/nginx/conf.d/proxy.confupstream back_end {  server 121.42.11.34:8080 weight=2 max_conns=1000 fail_timeout=10s max_fails=3;  keepalive 32;  keepalive_requests 80;  keepalive_timeout 20s;}
server { listen 80; server_name proxy.lion.club; location /proxy { proxy_pass http://back_end/proxy; }}

本地机器要访问 proxy.lion.club 域名,因此需要配置本地 hosts ,通过命令:vim /etc/hosts 进入配置文件,添加如下内容:

1
121.5.180.193 proxy.lion.club

图片

分析:

  1. 当访问 proxy.lion.club/proxy 时通过 upstream 的配置找到 121.42.11.34:8080 ;
  2. 因此访问地址变为 http://121.42.11.34:8080/proxy
  3. 连接到 121.42.11.34 服务器,找到 8080 端口提供的 server ;
  4. 通过 server 找到 /usr/share/nginx/html/proxy/index.html 资源,最终展示出来。

8.4 配置负载均衡

配置负载均衡主要是要使用 upstream 指令。

我们把 121.42.11.34 服务器作为上游服务器,做如下配置( /etc/nginx/conf.d/balance.conf ):

1
2
3
server{  listen 8020;  location / {    return 200 'return 8020 \n';  }}
server{ listen 8030; location / { return 200 'return 8030 \n'; }}
server{ listen 8040; location / { return 200 'return 8040 \n'; }}

配置完成后:

  1. nginx -t 检测配置是否正确;
  2. nginx -s reload 重启 Nginx 服务器;
  3. 执行 ss -nlt 命令查看端口是否被占用,从而判断 Nginx 服务是否正确启动。

把 121.5.180.193 服务器作为代理服务器,做如下配置( /etc/nginx/conf.d/balance.conf ):

1
2
upstream demo_server {  server 121.42.11.34:8020;  server 121.42.11.34:8030;  server 121.42.11.34:8040;}
server { listen 80; server_name balance.lion.club; location /balance/ { proxy_pass http://demo_server; }}

配置完成后重启 Nginx 服务器。并且在需要访问的客户端配置好 IP 和域名的映射关系。

1
# /etc/hosts121.5.180.193 balance.lion.club

在客户端机器执行 curl http://balance.lion.club/balance/ 命令:

图片

不难看出,负载均衡的配置已经生效了,每次给我们分发的上游服务器都不一样。就是通过简单的轮询策略进行上游服务器分发。

接下来,我们再来了解下 Nginx 的其它分发策略。

hash 算法

通过制定关键字作为 hash key ,基于 hash 算法映射到特定的上游服务器中。关键字可以包含有变量、字符串。

1
2
upstream demo_server {  hash $request_uri;  server 121.42.11.34:8020;  server 121.42.11.34:8030;  server 121.42.11.34:8040;}
server { listen 80; server_name balance.lion.club; location /balance/ { proxy_pass http://demo_server; }}

hash $request_uri 表示使用 request_uri 变量作为 hash 的 key 值,只要访问的 URI 保持不变,就会一直分发给同一台服务器。

ip_hash

根据客户端的请求 IP 进行判断,只要 IP 地址不变就永远分配到同一台主机。它可以有效解决后台服务器 session 保持的问题。

1
2
upstream demo_server {  ip_hash;  server 121.42.11.34:8020;  server 121.42.11.34:8030;  server 121.42.11.34:8040;}
server { listen 80; server_name balance.lion.club; location /balance/ { proxy_pass http://demo_server; }}

最少连接数算法

各个 worker 子进程通过读取共享内存的数据,来获取后端服务器的信息。来挑选一台当前已建立连接数最少的服务器进行分配请求。

1
2
语法:least_conn;
上下文:upstream;

示例:

1
2
upstream demo_server {  zone test 10M; # zone可以设置共享内存空间的名字和大小  least_conn;  server 121.42.11.34:8020;  server 121.42.11.34:8030;  server 121.42.11.34:8040;}
server { listen 80; server_name balance.lion.club; location /balance/ { proxy_pass http://demo_server; }}

最后你会发现,负载均衡的配置其实一点都不复杂。

8.5 配置缓存

缓存可以非常有效的提升性能,因此不论是客户端(浏览器),还是代理服务器( Nginx ),乃至上游服务器都多少会涉及到缓存。可见缓存在每个环节都是非常重要的。下面让我们来学习 Nginx 中如何设置缓存策略。

proxy_cache

存储一些之前被访问过、而且可能将要被再次访问的资源,使用户可以直接从代理服务器获得,从而减少上游服务器的压力,加快整个访问速度。

1
2
3
语法:proxy_cache zone | off ; # zone 是共享内存的名称
默认值:proxy_cache off;
上下文:http、server、location

proxy_cache_path

设置缓存文件的存放路径。

1
2
3
4
5
6

语法:proxy_cache_path path [level=levels] ...可选参数省略,下面会详细列举

默认值:proxy_cache_path off

上下文:http

参数含义:

  • path 缓存文件的存放路径;
  • level path 的目录层级;
  • keys_zone 设置共享内存;
  • inactive 在指定时间内没有被访问,缓存会被清理,默认10分钟。

proxy_cache_key

设置缓存文件的 key 。

1
2
3
4
5
语法:proxy_cache_key

默认值:proxy_cache_key $scheme$proxy_host$request_uri;

上下文:http、server、location

proxy_cache_valid

配置什么状态码可以被缓存,以及缓存时长。

1
2
3
4
5
语法:proxy_cache_valid [code...] time;

上下文:http、server、location

配置示例:proxy_cache_valid 200 304 2m;; # 说明对于状态为200和304的缓存文件的缓存时间是2分钟

proxy_no_cache

定义相应保存到缓存的条件,如果字符串参数的至少一个值不为空且不等于“ 0”,则将不保存该响应到缓存。

1
2
3
4
5
语法:proxy_no_cache string;

上下文:http、server、location

示例:proxy_no_cache $http_pragma $http_authorization;

proxy_cache_bypass

定义条件,在该条件下将不会从缓存中获取响应。

1
2
3
4
5
6
语法:proxy_cache_bypass string;

上下文:http、server、location

示例:proxy_cache_bypass $http_pragma $http_authorization;

upstream_cache_status 变量

它存储了缓存是否命中的信息,会设置在响应头信息中,在调试中非常有用。

1
2
3
4
5
6
7
MISS: 未命中缓存
HIT:命中缓存
EXPIRED: 缓存过期
STALE: 命中了陈旧缓存
REVALIDDATED: Nginx验证陈旧缓存依然有效
UPDATING: 内容陈旧,但正在更新
BYPASS: X响应从原始服务器获取

配置实例

我们把 121.42.11.34 服务器作为上游服务器,做如下配置( /etc/nginx/conf.d/cache.conf ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

server {
listen 1010;
root /usr/share/nginx/html/1010;
location / {
index index.html;
}
}

server {
listen 1020;
root /usr/share/nginx/html/1020;
location / {
index index.html;
}
}

把 121.5.180.193 服务器作为代理服务器,做如下配置( /etc/nginx/conf.d/cache.conf ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
proxy_cache_path /etc/nginx/cache_temp levels=2:2 keys_zone=cache_zone:30m max_size=2g inactive=60m use_temp_path=off;

upstream cache_server{
server 121.42.11.34:1010;
server 121.42.11.34:1020;
}

server {
listen 80;
server_name cache.lion.club;
location / {
proxy_cache cache_zone; # 设置缓存内存,上面配置中已经定义好的
proxy_cache_valid 200 5m; # 缓存状态为200的请求,缓存时长为5分钟
proxy_cache_key $request_uri; # 缓存文件的key为请求的URI
add_header Nginx-Cache-Status $upstream_cache_status # 把缓存状态设置为头部信息,响应给客户端
proxy_pass http://cache_server; # 代理转发
}
}

缓存就是这样配置,我们可以在 /etc/nginx/cache_temp 路径下找到相应的缓存文件。

对于一些实时性要求非常高的页面或数据来说,就不应该去设置缓存,下面来看看如何配置不缓存的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...

server {
listen 80;
server_name cache.lion.club;
# URI 中后缀为 .txt 或 .text 的设置变量值为 "no cache"
if ($request_uri ~ \.(txt|text)$) {
set $cache_name "no cache"
}

location / {
proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,如果没有值则进行缓存
proxy_cache cache_zone; # 设置缓存内存
proxy_cache_valid 200 5m; # 缓存状态为200的请求,缓存时长为5分钟
proxy_cache_key $request_uri; # 缓存文件的key为请求的URI
add_header Nginx-Cache-Status $upstream_cache_status # 把缓存状态设置为头部信息,响应给客户端
proxy_pass http://cache_server; # 代理转发
}
}

8.6 HTTPS

在学习如何配置 HTTPS 之前,我们先来简单回顾下 HTTPS 的工作流程是怎么样的?它是如何进行加密保证安全的?

HTTPS 工作流程

  1. 客户端(浏览器)访问 https://www.baidu.com 百度网站;
  2. 百度服务器返回 HTTPS 使用的 CA 证书;
  3. 浏览器验证 CA 证书是否为合法证书;
  4. 验证通过,证书合法,生成一串随机数并使用公钥(证书中提供的)进行加密;
  5. 发送公钥加密后的随机数给百度服务器;
  6. 百度服务器拿到密文,通过私钥进行解密,获取到随机数(公钥加密,私钥解密,反之也可以);
  7. 百度服务器把要发送给浏览器的内容,使用随机数进行加密后传输给浏览器;
  8. 此时浏览器可以使用随机数进行解密,获取到服务器的真实传输内容;

这就是 HTTPS 的基本运作原理,使用对称加密和非对称机密配合使用,保证传输内容的安全性。

配置证书

下载证书的压缩文件,里面有个 Nginx 文件夹,把 xxx.crt 和 xxx.key 文件拷贝到服务器目录,再进行如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 443 ssl http2 default_server; # SSL 访问端口号为 443
server_name lion.club; # 填写绑定证书的域名(我这里是随便写的)
ssl_certificate /etc/nginx/https/lion.club_bundle.crt; # 证书地址
ssl_certificate_key /etc/nginx/https/lion.club.key; # 私钥地址
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 支持ssl协议版本,默认为后三个,主流版本是[TLSv1.2]

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}

如此配置后就能正常访问 HTTPS 版的网站了。

8.7 配置跨域 CORS

先简单回顾下跨域究竟是怎么回事。

跨域的定义

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通常不允许不同源间的读操作。

同源的定义

如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。

下面给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:

1
2
3
4
http://store.company.com/dir2/other.html 同源
https://store.company.com/secure.html 不同源,协议不同
http://store.company.com:81/dir/etc.html 不同源,端口不同
http://news.company.com/dir/other.html 不同源,主机不同

不同源会有如下限制:

  • Web 数据层面,同源策略限制了不同源的站点读取当前站点的 Cookie 、 IndexDB 、 LocalStorage 等数据;
  • DOM 层面,同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作;
  • 网络层面,同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。

Nginx 解决跨域的原理

例如:

  • 前端 server 的域名为:fe.server.com
  • 后端服务的域名为:dev.server.com

现在我在 fe.server.com 对 dev.server.com 发起请求一定会出现跨域。

现在我们只需要启动一个 Nginx 服务器,将 server_name 设置为 fe.server.com 然后设置相应的 location 以拦截前端需要跨域的请求,最后将请求代理回 dev.server.com 。如下面的配置:

1
2
3
4
5
6
7
server {
listen 80;
server_name fe.server.com;
location / {
proxy_pass dev.server.com;
}
}

这样可以完美绕过浏览器的同源策略:fe.server.com 访问 Nginx 的 fe.server.com 属于同源访问,而 Nginx 对服务端转发的请求不会触发浏览器的同源策略。

8.8 配置开启 gzip 压缩

GZIP 是规定的三种标准 HTTP 压缩格式之一。目前绝大多数的网站都在使用 GZIP 传输 HTML 、CSS 、 JavaScript 等资源文件。

对于文本文件, GZiP 的效果非常明显,开启后传输所需流量大约会降至 1/4~1/3 。

并不是每个浏览器都支持 gzip 的,如何知道客户端是否支持 gzip 呢,请求头中的 Accept-Encoding 来标识对压缩的支持。

图片

启用 gzip 同时需要客户端和服务端的支持,如果客户端支持 gzip 的解析,那么只要服务端能够返回 gzip 的文件就可以启用 gzip 了,我们可以通过 Nginx 的配置来让服务端支持 gzip 。下面的 respone 中 content-encoding:gzip ,指服务端开启了 gzip 的压缩方式。

图片

在 /etc/nginx/conf.d/ 文件夹中新建配置文件 gzip.conf :

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
# # 默认off,是否开启gzip
gzip on;
# 要采用 gzip 压缩的 MIME 文件类型,其中 text/html 被系统强制启用;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

# ---- 以上两个参数开启就可以支持Gzip压缩了 ---- #

# 默认 off,该模块启用后,Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该 .gz 文件内容;
gzip_static on;

# 默认 off,nginx做为反向代理时启用,用于设置启用或禁用从代理服务器上收到相应内容 gzip 压缩;
gzip_proxied any;

# 用于在响应消息头中添加 Vary:Accept-Encoding,使代理服务器根据请求头中的 Accept-Encoding 识别是否启用 gzip 压缩;
gzip_vary on;

# gzip 压缩比,压缩级别是 1-9,1 压缩级别最低,9 最高,级别越高压缩率越大,压缩时间越长,建议 4-6;
gzip_comp_level 6;

# 获取多少内存用于缓存压缩结果,16 8k 表示以 8k*16 为单位获得;
gzip_buffers 16 8k;

# 允许压缩的页面最小字节数,页面字节数从header头中的 Content-Length 中进行获取。默认值是 0,不管页面多大都压缩。建议设置成大于 1k 的字节数,小于 1k 可能会越压越大;
# gzip_min_length 1k;

# 默认 1.1,启用 gzip 所需的 HTTP 最低版本;
gzip_http_version 1.1;

其实也可以通过前端构建工具例如 webpack 、rollup 等在打生产包时就做好 Gzip 压缩,然后放到 Nginx 服务器中,这样可以减少服务器的开销,加快访问速度。

关于 Nginx 的实际应用就学习到这里,相信通过掌握了 Nginx 核心配置以及实战配置,之后再遇到什么需求,我们也能轻松应对。接下来,让我们再深入一点学习下 Nginx 的架构。

9. Nginx 架构

9.1 进程结构

多进程结构 Nginx 的进程模型图:

图片

多进程中的 Nginx 进程架构如下图所示,会有一个父进程( Master Process ),它会有很多子进程( Child Processes )。

  • Master Process 用来管理子进程的,其本身并不真正处理用户请求。

    • 某个子进程 down 掉的话,它会向 Master 进程发送一条消息,表明自己不可用了,此时 Master 进程会去新起一个子进程;
    • 某个配置文件被修改了 Master 进程会去通知 work 进程获取新的配置信息,这也就是我们所说的热部署。
  • 子进程间是通过共享内存的方式进行通信的。

9.2 配置文件重载原理

reload 重载配置文件的流程:

  1. 向 master 进程发送 HUP 信号( reload 命令);
  2. master 进程检查配置语法是否正确;
  3. master 进程打开监听端口;
  4. master 进程使用新的配置文件启动新的 worker 子进程;
  5. master 进程向老的 worker 子进程发送 QUIT 信号;
  6. 老的 worker 进程关闭监听句柄,处理完当前连接后关闭进程;
  7. 整个过程 Nginx 始终处于平稳运行中,实现了平滑升级,用户无感知。

9.3 Nginx 模块化管理机制

Nginx 的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展。Nginx 的模块是互相独立的,低耦合高内聚。

image-20240815092547467

docker搭建SqlServer2019

docker部署sqlserver

  1. 拉镜像
1
docker pull mcr.microsoft.com/mssql/server:2019-latest
  1. 创建持久化目录
1
2
mkdir -p /home/pan/docker/sqlserver/data
chmod 777 /etc/sqlserver_data
  1. 运行镜像
1
2
3
4
5
6
7
8
docker run \
-e "ACCEPT_EULA=Y" \
-e "MSSQL_SA_PASSWORD=Ictsoft@123" \
-e "MSSQL_PID=Express" \
-p 12433:1433 \
-d --name sql-server2019 \
-v /home/pan/docker/sqlserver/data:/var/opt/mssql \
mcr.microsoft.com/mssql/server:2019-latest
  1. 基本操作
    登录数据库
1
/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Ictsoft@123"

4.1 查看版本

1
2
Select @@version
GO

4.2 创建数据库

1
2
3
CREATE DATABASE TestDB;
SELECT Name from sys.databases;
GO

4.3 插入数据

1
2
3
4
5
6
7
8
USE TestDB;
CREATE TABLE dbo.Inventory (
id INT, name NVARCHAR(50),
quantity INT
);
INSERT INTO dbo.Inventory VALUES (1, 'banana', 150);
INSERT INTO dbo.Inventory VALUES (2, 'orange', 154);
GO

docker搭建mysql8

目录结构

1
2
3
4
5
.
├── conf
│   └── my.cnf
├── data
└── log

conf/my.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
init_connect='SET collation_connection = utf8mb4_general_ci'
init_connect='SET NAMES utf8mb4'
#服务器默认编码格式
character-set-server=utf8mb4
#服务器默认字符集
collation-server=utf8mb4_general_ci
# 跳过mysql程序起动时的字符参数设置 ,使用服务器端字符集设置
skip-character-set-client-handshake
# 跳过域名解析,只能使用127.0.0.1
skip-name-resolve
secure_file_priv=/var/lib/mysql
default_authentication_plugin= mysql_native_password

docker构建命令

1
2
3
4
5
6
7
8
9
10
11
docker run \
-v /root/docker/mysql/log:/var/log/mysql \
-v /root/docker/mysql/data:/var/lib/mysql \
-v /root/docker/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=rootroot \
-p 3306:3306 \
-u root \
--restart=always \
--privileged=true \
--name mysql \
-d mysql:8

如果mysqld: Can't read dir of '/etc/mysql/conf.d/' (OS errno 2 - No such file or directory)

-v /home/vagrant/docker/mysql/conf:/etc/mysql \

改成

-v /home/vagrant/docker/mysql/conf:/etc/mysql/conf.d \

不加 -v /data/mysql/mysql-files:/var/lib/mysql-files \ 会报错误

1
mysqld: Error on realpath() on '/var/lib/mysql-files' (Error 2 - No such file or directory

登录 Docker 中的 Mysql 存在两种情况

第一种(可行)

  1. 使用命令 docker exec -it mysql /bin/bash 进入容器
  2. 使用命令 mysql -uroot -p 按下 enter 再按 enter 直接登录到 Mysql 里面去,因为里面 root 用户对应的密码是空的
  3. 给 root 用户设置密码
1
2
3
4
# 修改用户对应的密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';
# 刷新权限
flush privileges;
  1. 需要远程连接时,执行如下命令
1
2
CREATE USER 'root'@'%' IDENTIFIED BY '用户密码';
flush privileges;
  1. 创建出来的用户是没有任何创建数据库权限的,需要执行如下命令进行授权
1
2
grant all privileges on *.* to 'root'@'%' with grant option;
flush privileges;

第二种(不可行)

  1. 在执行生成 mysql 容器的时候,我们设置了 mysql 初始密码的,使用那个密码,即可直接登录到 mysql

说明一下 Mysql 的配置 my.cnf

1
2
3
4
5
6
7
[mysqld]
# 禁止表名大小写验证
lower_case_table_names=1
# 禁用dns解析,所以在mysql的授权表中就不能使用主机名了,只能使用IP
skip-name-resolve
# mysql8的安全机制升级而需要修改的配置,不配置的话将无法登录管理
default_authentication_plugin= mysql_native_password

问题

发现docker容器无法启动,报错如下:

1
2
Failed to access directory for --secure-file-priv. Please make sure 
that directory exists and is accessible by MySQL Server. Supplied value : /var/lib/mysql-files

windows下:修改my.ini 在[mysqld]内加入secure_file_priv=/var/lib/mysql
linux下:修改my.cnf 在[mysqld]内加入secure_file_priv=/var/lib/mysql