- 总结 哨兵机制实现原理,并搭建主从哨兵集群。
1.1为什么要redis需要哨兵?
主从架构和MySQL的主从复制一样,无法实现master和slave角色的自动切换,即当master出现故障时,
不能实现自动的将一个slave 节点提升为新的master节点,即主从复制无法实现自动的故障转移功能,如果
想实现转移,则需要手动修改配置,才能将 slave 服务器提升新的master节点.此外只有一个主节点支持写
操作,所以业务量很大时会导致Redis服务性能达到瓶颈
需要解决的主从复制的存在以下弊端:
master和slave角色的自动切换,且不能影响业务
提升Redis服务整体性能,支持更高并发访问
1.2哨兵机制架构
1.3哨兵机制实现原理
专门的Sentinel 服务进程是用于监控redis集群中Master工作的状态,当Master主服务器发生故障的时
候,可以实现Master和Slave的角色的自动切换,从而实现系统的高可用性
Sentinel是一个分布式系统,即需要在多个节点上各自同时运行一个sentinel进程,Sentienl 进程通过流
言协议(gossip protocols)来接收关于Master是否下线状态,并使用投票协议(Agreement Protocols)来
决定是否执行自动故障转移,并选择合适的Slave作为新的Master
每个Sentinel进程会向其它Sentinel、Master、Slave定时发送消息,来确认对方是否存活,如果发现某
个节点在指定配置时间内未得到响应,则会认为此节点已离线,即为主观宕机Subjective Down,简称
为 SDOWN
如果哨兵集群中的多数Sentinel进程认为Master存在SDOWN,共同利用 is-master-down-by-addr 命令
互相通知后,则认为客观宕机Objectively Down, 简称 ODOWN
接下来利用投票算法,从所有slave节点中,选一台合适的slave将之提升为新Master节点,然后自动修
改其它slave相关配置,指向新的master节点,最终实现故障转移failover
Redis Sentinel中的Sentinel节点个数应该为大于等于3且最好为奇数
客户端初始化时连接的是Sentinel节点集合,不再是具体的Redis节点,即 Sentinel只是配置中心不是代
理。
Redis Sentinel 节点与普通 Redis 没有区别,要实现读写分离依赖于客户端程序
Sentinel 机制类似于MySQL中的MHA功能,只解决master和slave角色的自动故障转移问题,但单个
Master 的性能瓶颈问题并没有解决
Redis 3.0 之前版本中,生产环境一般使用哨兵模式较多,Redis 3.0后推出Redis cluster功能,可以支持更大
规模的高并发环境
Sentinel中的三个定时任务
Sentinel中的三个定时任务
每10 秒每个sentinel 对master和slave执行info
发现slave节点
确认主从关系
每2秒每个sentinel通过master节点的channel交换信息(pub/sub)
通过sentinel__:hello频道交互
交互对节点的“看法”和自身信息
每1秒每个sentinel对其他sentinel和redis执行ping
Sentinel-master-slave 三者的定时任务(默认)
并搭建主从哨兵集群:
思路:
一般来说redis-sentinel 需要至少6台设备: 3台sentinel(至少3台以上的奇数,偶数会脑裂),一主,2从(主或者从down后无设备切换)。
节约成本方案:sentinel 安装在1主2从上面设备复用:
环境:192.168.44.8主节点,192.168.44.48从节点,192.168.44.58从节点
运行环境:
redis二进制安装路径: /apps/redis/bin/
redis日志路径: /apps/redis/log/
redis运行路径: /apps/redis/run/
redis配置路径: /apps/redis/etc/
redis数据库存储路径: /apps/redis/data/ ##用于AOF 和 RDB数据的存盘和temp临时文件的存放(临时文件是在正在备份时候产生,临时生产备份完成会替换原来的数据文件,防止备份时间就替换了数据文件导致数据丢失)
步骤:
1:3台设备的主从认证密码和登录密码设置成一样,新建系统账号redis,所有相关的目录权限修改为redis拥有,redis组。
2:设置主从,其他类似,主要是密码
[root@centos7 etc]# grep -Ev '^$|#' redis.conf
bind 0.0.0.0 #指定监听地址,支持用空格隔开的多个监听IP
protected-mode yes #redis3.2之后加入的新特性,在没有设置bind IP和密码的时候,redis只允许访
问127.0.0.1:6379,可以远程连接,但当访问将提示警告信息并拒绝远程访问
port 6379 #监听端口,默认6379/tcp
tcp-backlog 511 #三次握手的时候server端收到client ack确认号之后的队列值,即全连接队列长度
timeout 0 #客户端和Redis服务端的连接超时时间,默认是0,表示永不超时
tcp-keepalive 300 #tcp 会话保持时间300s
daemonize no #默认no,即直接运行redis-server程序时,不作为守护进程运行,而是以前台方式运行,
如果想在后台运行需改成yes,当redis作为守护进程运行的时候,它会写一个 pid 到
/var/run/redis.pid 文件
pidfile "/apps/redis/etc/run/redis_6379.pid" #pid文件路径
loglevel notice #日志级别
logfile "/apps/redis/log/redis_6379.log" #日志路径,
databases 16 #设置数据库数量,默认:0-15,共16个库
always-show-logo yes #在启动redis 时是否显示或在日志中记录记录redis的logo
set-proc-title yes #默认情况下,Redis 会修改进程标题(如“top”和“ps”所示)以提供一些运行时信息。 可以通过将以下设置为 no 来禁用它并使进程名称保持为已执行状态。
proc-title-template "{title} {listen-addr} {server-mode}" ## 当set-proc-title yes 必须指定title的格式,详见说明
stop-writes-on-bgsave-error yes #默认为yes时,可能会因空间满等原因快照无法保存出错时,会禁
止redis写入操作,生产建议为no #此项只针对配置文件中的自动save有效
rdbcompression yes #持久化到RDB文件时,是否压缩,"yes"为压缩,"no"则反之
rdbchecksum yes #是否对备份文件开启RC64校验,默认是开启
dbfilename "dump.rdb"#快照文件名
rdb-del-sync-files no ##主从进行全量同步时,通过传输 RDB 内存快照文件实现,没有开启 RDB 持久化的实例在同步完成后会删除该文件,通常情况下保持默认即可。
dir "/apps/redis/data" #快照文件保存路径,示例:dir "/apps/redis/data"
masterauth "123456" #如果当前节点是 slave,且 master 节点配置了 requirepass 参数设置了密码,那么 slave 节点必须使用该参数配置为 master 的密码,否则 master 节点将拒绝该 slave 节点的请求。
replica-serve-stale-data yes ##当从库同主库失去连接或者复制正在进行,从机库有两种运行方式:
1、设置为yes(默认设置),从库会继续响应客户端的读请求,此为建议值
2、设置为no,除去特定命令外的任何请求都会返回一个错误"SYNC with master in progress"。
replica-read-only yes #是否设置从库只读,建议值为yes,否则主库同步从库时可能会覆盖数据,造成
数据丢失
repl-diskless-sync yes #是否使用socket方式复制数据(无盘同步),新slave第一次连接master时需
要做数据的全量同步,redis server就要从内存dump出新的RDB文件,然后从master传到slave,有两种
方式把RDB文件传输给客户端:
1、基于硬盘(disk-backed):为no时,master创建一个新进程dump生成RDB磁盘文件,RDB完成之后由
父进程(即主进程)将RDB文件发送给slaves,此为默认值
2、基于socket(diskless):master创建一个新进程直接dump RDB至slave的网络socket,不经过主
进程和硬盘
#推荐使用基于硬盘(为no),是因为RDB文件创建后,可以同时传输给更多的slave,但是基于socket(为
yes), 新slave连接到master之后得逐个同步数据。只有当磁盘I/O较慢且网络较快时,可用
diskless(yes),否则一般建议使用磁盘(no)
repl-diskless-sync-delay 5 #diskless时复制的服务器等待的延迟时间,设置0为关闭,在延迟时间
内到达的客户端,会一起通过diskless方式同步数据,但是一旦复制开始,master节点不会再接收新slave
的复制请求,直到下一次同步开始才再接收新请求。即无法为延迟时间后到达的新副本提供服务,新副本将排
队等待下一次RDB传输,因此服务器会等待一段时间才能让更多副本到达。推荐值:30-60
repl-diskless-sync-max-replicas 0 # 如果是无硬盘传输,如果预期的最大副本数已连接,则可以在最大延时之前进行复制
在主服务器配置 默认 0 标识未定义
repl-diskless-load disabled
#1. RDB文件落在Slave磁盘上模式,通常情况下磁盘比网络慢,存储和加载RDB文件会增加复制时间,也会增加Master的复制写内存和Slave的缓冲区
2. 无硬盘模式是测试阶段,因为直接从socket获取rdb数据会有一个问题,那就是需要拿到完整的rdb数据才可以进行同步,针对这个问题以下由几种方案
disable 不使用 无硬盘方案
on-empty-db 只有在完全安全才使用无硬盘
swapdb 在解析socket的rdb数据时,将当前数据库的数据放到内存中,这样可以在复制的时候为客户端提供服务,但是可能会造成内存溢出
repl-diskless-load <disabled / on-empty-db / swapdb> 默认是disable
repl-disable-tcp-nodelay no #是否在slave套接字发送SYNC之后禁用 TCP_NODELAY,如果选
择"yes",Redis将合并多个报文为一个大的报文,从而使用更少数量的包向slaves发送数据,但是将使数据
传输到slave上有延迟,Linux内核的默认配置会达到40毫秒,如果 "no" ,数据传输到slave的延迟将会
减少,但要使用更多的带宽。
repl-backlog-ttl 3600 #多长时间内master没有slave连接,就清空backlog缓冲区,默认值
replica-priority 100 #优先级,从的优先级低的优先成为主
acllog-max-len 128 #ACL 日志的最大长度,默认配置 acllog-max-len 128 表示最大 128 mb。
另外,使用 aclfile /etc/redis/users.acl 配置 ACL 文件所在位置
requirepass "123456" # 客户端登录的密码
lazyfree-lazy-eviction no #由于 maxmemory 和 maxmemory-policy 策略配置,我会删除一些数据,防止内存爆掉。使用lazyfree-lazy-eviction yes表示使用 lazy free 机制,该场景开启 lazy free 可能会导致淘汰数据的内存释放不及时,出现内存超限。
lazyfree-lazy-expire no #对于设置了 TTL 的键,过期后删除。如果想启用 lazy free 机制删除,则配置 lazyfree-lazy-eviction yes。
lazyfree-lazy-server-del no #针对有些指令在处理已存在的键时,会带有一个隐式的 DEL 键的操作。
如 rename 命令,当目标键已存在,我会先删除目标键,如果这些目标键是一个 big key,那可能会出现阻塞删除的性能问题。此参数设置就是解决这类问题,建议配置 lazyfree-lazy-server-del yes 开启。
replica-lazy-flush no #该配置针对 slave 进行全量数据同步,在加载 master 的 RDB 内存快照文件之前,会先运行 flashall清理数据的时候是否采用异步 flush 机制。
推荐你使用 replica-lazy-flush yes配置,可减少全量同步耗时,从而减少 master 因输出缓冲区暴涨引起的内存增长。
lazyfree-lazy-user-del no #意思是是否将 DEL 指令的默认行为替换成 lazy free 机制删除,效果就跟 UNLINK 一样,只要配置成 lazyfree-lazy-user-del yes
lazyfree-lazy-user-flush no #FLUSHDB, FLUSHALL, SCRIPT FLUSH, FUNCTION FLUSH可以使用额外参数 ASYNC|SYNC 决定使用同步还是异步操作,当没有指定这个可选项,可以通过 lazyfree-lazy-user-flush yes 表示使用异步删除。
oom-score-adj no #出现内存溢出时,Redis 主动控制 oom-score_adj 设置的值来决定 进程的死亡顺序
取值范围:
no: 在所有其他进程之前先杀死后台子进程,并且在主进程之前杀死其他进程 《默认》
yes: 相对的
absolute: 把 oom-score-adj-values 直接写入到内核
relative: 服务器启动,将值改为 -1000 ~ 1000 的范围内,
oom-score-adj <no>
oom-score-adj-values 0 200 800
disable-thp yes
appendonly no#是否开启AOF持久化 appendonly <no,yes> 默认no
appendfilename "appendonly.aof" ##指定aof文件名称,文件会自增
例子:
appendfilename "appendonly.aof"
appendonly.aof.1.base.rdb 基础文件
appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof 作为增量文件
appendonly.aof.manifest 清单文件
appenddirname "appendonlydir"#指定aof文件的存储路径
appendfsync everysec #将实际数据存储在磁盘中
取值范围
1. no 让操作系统自己决定什么时候将数据存储到磁盘中 ,性能高 ,不安全
2. always 每次写入都通知操作系统,fsync 性能低,最安全
3. everysec 每秒fsync一次 , 性能中等,安全性中等,最多丢失1秒的数据
appendfsync ererysec 默认的、、
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #重写AOF,指定文件的增长百分比自动重写,隐式调用 BGREWRITEAOF
因为AOF记录的都是指令,有很多之前的写入的指令不需要记录了( 10分钟前写入了一个key设置的过期时间是3分钟 ,那么aof文件在3分钟后就没有必要记录这个key值了,这个是时间的例子, 大小也是一样)
这两个配置项配合一起使用,可能百分比设置了但是文件很小,就会一直重写aof
取值范围
0 表示禁用自动AOF
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #到了64M我就重写,例如优化到了50MB,那么100的含义就是下次重写就是100M。
------------------------------------------------------------------
aof-load-truncated yes #如果读取到一个错误的aof文件,由两种方案
取值范围
1. yes 打印日志通知用户
2. no 服务器报错并且拒绝启动
aof-load-truncated <no /yes> 默认yes
aof-use-rdb-preamble yes #
aof-timestamp-enabled no
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-listpack-entries 512
hash-max-listpack-value 64
list-max-listpack-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-listpack-entries 128
zset-max-listpack-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4kb
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
jemalloc-bg-thread yes
supervised systemd
save 3600 1 #在3600秒内有1个key内容发生更改,就执行快照机制
save 300 100 #在300秒内有100个key内容发生更改,就执行快照机制
save 60 10000 #60秒内如果有10000个key以上的变化,就自动快照备份
latency-tracking-info-percentiles 50 99 99.9
replicaof 192.168.44.48 6379
3:检查主从关系:
从节点:
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.44.48
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:5233324
slave_repl_offset:5233324
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:136b7c019b2f84205e2abd1101b211cffd1caadf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:5233324
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4172858
repl_backlog_histlen:1060467
主节点:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.44.8,port=6379,state=online,offset=5264340,lag=0
slave1:ip=192.168.44.58,port=6379,state=online,offset=5264340,lag=1
master_failover_state:no-failover
master_replid:136b7c019b2f84205e2abd1101b211cffd1caadf
master_replid2:ba0ad6e7d792a888de91b070863f2371bbaf5973
master_repl_offset:5264481
second_repl_offset:2593582
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4206297
repl_backlog_histlen:1058185
127.0.0.1:6379>
4,拷贝解压二进制文件的sentinel.conf只规划好的安装文件的目录
5,配置sentinel的配置文件。配置权限为redis拥有
6,写service文件(可选)
7.启动sentinel,service启动略,
[root@centos7 ~]# ll /apps/redis/bin/redis-sentinel
lrwxrwxrwx 1 redis redis 12 Nov 13 20:07 /apps/redis/bin/redis-sentinel -> redis-server
[root@centos7 ~]# /apps/redis/bin/redis-sentinel /apps/redis/etc/sentinel.conf
配置文件:
[root@centos7 ~]# grep -Ev '^$|#' /apps/redis/etc/sentinel.conf
protected-mode no #关闭保护模式
bind 0.0.0.0 #监听地址
port 26379 #单口
daemonize yes # 开启后台运行,也可以写service文件。
pidfile "/apps/redis/run/redis-sentinel.pid" #pid文件路径
logfile "/apps/redis/etc/sentinel.log" #log文件路径
dir "/apps/redis/data/" 工作目录
sentinel monitor mymaster 192.168.44.48 6379 2 # 当前redis的名称,mymaster(可随便写) 主的地址 端口 投票2(同意故障迁移的法定票数。即表示有几个哨兵认可主观下线。达到一定票数后认定为客观下线(宕机、不可用)
sentinel auth-pass mymaster 123456 #配置连接master(名称要和上条规定的一样)服务的密码
sentinel down-after-milliseconds mymaster 3000 #指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线 — 主观下线
sentinel parallel-syncs mymaster 1 ##发生故障转移后,可以同时向新master同步数据的slave的数量,数字越小总同步时间越长,但可以减轻新
master的负载压力
sentinel failover-timeout mymaster 180000 #所有slaves指向新的master所需的超时时间,单位:毫秒
acllog-max-len 128
sentinel deny-scripts-reconfig yes #禁止修改脚本
sentinel resolve-hostnames no
sentinel announce-hostnames no
latency-tracking-info-percentiles 50 99 99.9
user default on nopass sanitize-payload ~* &* +@all
sentinel myid baae2b9d5402fe448d0a7754bc1e05555a54774a
sentinel config-epoch mymaster 7
sentinel leader-epoch mymaster 7
sentinel current-epoch 7
sentinel known-replica mymaster 192.168.44.58 6379
sentinel known-replica mymaster 192.168.44.8 6379
sentinel known-sentinel mymaster 192.168.44.8 26379 2c14518666f9c7239b5144cff0a7b9a506f481d3
sentinel known-sentinel mymaster 192.168.44.58 26379 2b7b81c3c21b142b705598edd73dc6b2427c6634
[root@centos7 ~]#
登录检查sentinel状态是否正常
[root@centos7 ~]# redis-cli -p 26379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
AUTH failed: ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.44.48:6379,slaves=2,sentinels=3
127.0.0.1:26379>
验证是否能切换:
[root@192 ~]# tail -f /apps/redis/log/sentinel.log
10816:X 17 Nov 2023 17:25:44.913 # +switch-master mymaster 192.168.44.58 6379 192.168.44.48 6379
10816:X 17 Nov 2023 17:25:44.914 * +slave slave 192.168.44.8:6379 192.168.44.8 6379 @ mymaster 192.168.44.48 6379
10816:X 17 Nov 2023 17:25:44.914 * +slave slave 192.168.44.58:6379 192.168.44.58 6379 @ mymaster 192.168.44.48 6379
10816:X 17 Nov 2023 17:25:44.916 * Sentinel new configuration saved on disk
10816:X 17 Nov 2023 17:25:47.941 # +sdown slave 192.168.44.58:6379 192.168.44.58 6379 @ mymaster 192.168.44.48 6379
10816:X 17 Nov 2023 17:25:53.350 # -sdown slave 192.168.44.58:6379 192.168.44.58 6379 @ mymaster 192.168.44.48 6379
10816:X 17 Nov 2023 20:28:53.391 # +tilt #tilt mode entered
10816:X 17 Nov 2023 20:29:23.408 # -tilt #tilt mode exited
10816:X 17 Nov 2023 23:52:00.919 # +sdown sentinel baae2b9d5402fe448d0a7754bc1e05555a54774a 192.168.44.48 26379 @ mymaster 192.168.44.48 6379 #主观down
10816:X 18 Nov 2023 00:30:23.903 # -sdown sentinel baae2b9d5402fe448d0a7754bc1e05555a54774a 192.168.44.48 26379 @ mymaster 192.168.44.48 6379
10816:X 18 Nov 2023 00:35:21.748 # +sdown master mymaster 192.168.44.48 6379
10816:X 18 Nov 2023 00:35:21.853 * Sentinel new configuration saved on disk
10816:X 18 Nov 2023 00:35:21.853 # +new-epoch 8
10816:X 18 Nov 2023 00:35:21.854 * Sentinel new configuration saved on disk
10816:X 18 Nov 2023 00:35:21.854 # +vote-for-leader baae2b9d5402fe448d0a7754bc1e05555a54774a 8
10816:X 18 Nov 2023 00:35:22.856 # +odown master mymaster 192.168.44.48 6379 #quorum 3/2 #客观down2 超过2/3
10816:X 18 Nov 2023 00:35:22.856 # Next failover delay: I will not start a failover before Sat Nov 18 00:41:22 2023
10816:X 18 Nov 2023 00:35:22.956 # +config-update-from sentinel baae2b9d5402fe448d0a7754bc1e05555a54774a 192.168.44.48 26379 @ mymaster 192.168.44.48 6379
10816:X 18 Nov 2023 00:35:22.956 # +switch-master mymaster 192.168.44.48 6379 192.168.44.58 6379 #转移了主
10816:X 18 Nov 2023 00:35:22.956 * +slave slave 192.168.44.8:6379 192.168.44.8 6379 @ mymaster 192.168.44.58 6379
10816:X 18 Nov 2023 00:35:22.956 * +slave slave 192.168.44.48:6379 192.168.44.48 6379 @ mymaster 192.168.44.58 6379
10816:X 18 Nov 2023 00:35:22.959 * Sentinel new configuration saved on disk
10816:X 18 Nov 2023 00:35:25.997 # +sdown slave 192.168.44.48:6379 192.168.44.48 6379 @ mymaster 192.168.44.58 6379
- 总结redis cluster工作原理,并搭建集群实现扩缩容。
1,为什么需要redis cluster
redis cluster
使用哨兵sentinel 只能解决Redis高可用问题,实现Redis的自动故障转移,但仍然无法解决Redis Master
单节点的性能瓶颈问题
为了解决单机性能的瓶颈,提高Redis 服务整体性能,可以使用分布式集群的解决方案
2,redis cluster 架构
Redis cluster 需要至少 3个master节点才能实现,slave节点数量不限,当然一般每个master都至少对应的
有一个slave节点
如果有三个主节点采用哈希槽 hash slot 的方式来分配16384个槽位 slot
此三个节点分别承担的slot 区间可以是如以下方式分配
节点M1 0-5460
节点M2 5461-10922
节点M3 10923-16383
3,搭建集群实现扩缩容
扩容步骤,
1,
- 总结 LVS的NAT和DR模型工作原理,并完成DR模型实战。
LVS的NAT模型工作原理:
lvs-nat:本质是多目标IP的DNAT,通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和
PORT实现转发
(1)RIP和DIP应在同一个IP网络,且应使用私网地址;RS的网关要指向DIP
(2)请求报文和响应报文都必须经由Director转发,Director易于成为系统瓶颈
(3)支持端口映射,可修改请求报文的目标PORT
(4)VS必须是Linux系统,RS可以是任意OS系统
nat模式自己的理解:
请求包:客户端CIP请求vs(调度器)的VIP:端口,那么vs根据调度规则(例如轮询RR)例如选RS1(提供服务的物理server),那么vs就把客户端请求的vip和端口替换成,rs1的RIP:端口发给rs1。
回复包:rs1 回复 将 发送包的源(cip)目标(Rip)地址转换,发送给vs,vs 根据请求包的映射将RS1的回复包的源地址和端口(RIP ,Rport)替换成cip请求的vip的IP和端口,发给clinet。完成整个过程。
客户端不知道真正提供服务的地址和端口,以为就是vip和端口提供的服务。
LVS的DR模型工作原理:
LVS-DR:Direct Routing,直接路由,LVS默认模式,应用最广泛,通过为请求报文重新封装一个MAC首部
进行转发,源MAC是DIP所在的接口的MAC,目标MAC是某挑选出的RS的RIP所在接口的MAC地址;源
IP/PORT,以及目标IP/PORT均保持不变
DR模式自己的理解:
请求包:客户端CIP请求vs(调度器)的VIP:端口,那么vs根据调度规则(例如轮询RR)例如选RS1(提供服务的物理server),那么vs就把客户端请求的vip和端口不替换加一个rs1的mac地址二层转发到rs1上面。
回复包:rs1 回复 将 发送包的源(cip)目标(vip)地址转换,直接发给路由器不经过vs转发,路由器发给clinet。完成整个过程。
此过程vs只做请求和转发,回复rs回复,大大提高vs的性能。
DR模式拓扑和配置:
环境:五台主机
一台:客户端 eth0:仅主机 192.168.18.130/24 GW:192.168.18.129
一台:ROUTER (用防火墙模拟路由器)
eth0 :NAT 192.168.44.18/24
eth1: 仅主机 192.168.18.129/24
启用 IP_FORWARD
一台:LVS
eth0:NAT:DIP:192.168.44.48/24 GW:192.168.44.18
两台RS:
RS1:eth0:NAT:192.168.44.58/24 GW:192.168.44.18
RS2:eth0:NAT:192.168.44.88/24 GW:192.168.44.18
vip为:192.168.44.100 端口:80 DR模式不支持端口映射
客户端配置:
ip address add 192.168.18.130/24 dev eth0
ip route add default via 192.168.18.129 dev eth0
路由器端配置:
ip address add 192.168.44.18/24 dev eth0
ip address add 192.168.18.129/24 dev eth1
ip route del default via 192.168.44.2 dev eth0 #删除路由表,没有就不用删除
ip route del default via 192.168.18.2 dev eth1 #删除路由表,没有就不用删除
验证:
[root@rocky-8 ~]# ip route
192.168.18.0/24 dev eth1 proto kernel scope link src 192.168.18.129 metric 101
192.168.44.0/24 dev eth0 proto kernel scope link src 192.168.44.18 metric 102
开启ipforward:不开启2个网段不通
临时开启:
root@router ~]#echo 1 > /proc/sys/net/ipv4/ip_forward
永久开启:
[root@router ~]#echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
[root@router ~]#sysctl -p
rs1服务器端配置:
ip address add 192.168.44.58/24 dev eth0
ip route add default via 192.168.44.18 dev eth0 #DIP的地址实际生产可以使用实际的网关地址
设置取消vip在rs1上面的免费arp和回应arp,防止地址冲突。
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
永久生效:
[root@router ~]#echo 'net.ipv4.conf.all.arp_ignore=1' >> /etc/sysctl.conf
[root@router ~]#echo 'net.ipv4.conf.all.arp_announce=2' >> /etc/sysctl.conf
[root@router ~]#echo 'net.ipv4.conf.Lo.arp_announce=2' >> /etc/sysctl.conf
[root@router ~]#echo 'net.ipv4.conf.lo.arp_ignore=1' >> /etc/sysctl.conf
[root@router ~]#sysctl -p
ip address add 192.168.44.100/32 dev lo label lo:1
永久写入:
vim /etc/sysconfig/network-scripts/ifcfg-eth0
nmcli connection reload
nmcli connection up eth0
测试:
ping 网关
安装hhtpd
echo 192.168.44.58 RS1> /var/www/html/index.html
重启httpd
rs2服务器端配置:
ip address add 192.168.44.88/24 dev eth0
ip route add default via 192.168.44.18 dev eth0 #DIP的地址实际生产可以使用实际的网关地址
设置取消vip在rs1上面的免费arp和回应arp,防止地址冲突。
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
永久生效:
[root@router ~]#echo 'net.ipv4.conf.all.arp_ignore=1' >> /etc/sysctl.conf
[root@router ~]#echo 'net.ipv4.conf.all.arp_announce=2' >> /etc/sysctl.conf
[root@router ~]#echo 'net.ipv4.conf.Lo.arp_announce=2' >> /etc/sysctl.conf
[root@router ~]#echo 'net.ipv4.conf.lo.arp_ignore=1' >> /etc/sysctl.conf
[root@router ~]#sysctl -p
ip address add 192.168.44.100/32 dev lo label lo:1
永久写入:
vim /etc/sysconfig/network-scripts/ifcfg-eth0
nmcli connection reload
nmcli connection up eth0
测试:
ping 网关
安装hhtpd
echo 192.168.44.88 RS2> /var/www/html/index.html
重启httpd
vs服务器端配置:
yum install ipvsadm -y
ip address add 192.168.44.88/24 dev eth0
ip route add default via 192.168.44.18 dev eth0 #DIP的地址实际生产可以使用实际的网关地址
不需要取消vip在lv上面的免费arp和回应arp,他要回复请求。
ip address add 192.168.44.100/32 dev lo label lo:1
永久写入:
vim /etc/sysconfig/network-scripts/ifcfg-eth0
nmcli connection reload
nmcli connection up eth0
[root@centos7 ~]# ipvsadm -A -t 192.168.44.100:80 -s rr ## -A 新建虚拟调度的vip # -t tcp协议 vip:端口 #-s rr 调度算法 轮询。
--scheduler -s scheduler one of rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq,
the default scheduler is wlc.
[root@centos7 ~]# ipvsadm -a -t 192.168.44.100:80 -r 192.168.44.58:80 -g #-a 新建真实的rs服务器和端口和vip的对应关系 ,-r rs服务器和端口 # -g 工作模式 DR,不写默认也是dr
--gatewaying -g gatewaying (direct routing) (default)
--ipip -i ipip encapsulation (tunneling) ##tunnel模式
--masquerading -m masquerading (NAT) #nat模式
[root@centos7 ~]# ipvsadm -a -t 192.168.44.100:80 -r 192.168.44.88:80 -g
验证:
[root@centos7 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.44.100:80 rr
-> 192.168.44.58:80 Route 1 0 0
-> 192.168.44.88:80 Route 1 0 0
[root@centos7 ~]#
验证:
抓包情况:
客户端端访问vip调度到了rs2
第一个包:路由器–发给vip的第一个包:
Src: 192.168.18,130,Dst: 192.168,44.100
Src:VMware da:9e:e0 (00:0c:29:da:9e:e0), Dst: VMware 99:72:83 (00:0c:29:99:72:83)(lvs调度器的mac地址)
第二个包:lvs–收到请求,替换了目标成rs2的mac后,重发给rs2的第一个包:
Src: 192.168.18,130,Dst: 192.168,44.100 ## 网络层以上没变
Src:VMware da:9e:e0 (00:0c:29:da:9e:e0), Dst: 00:0c:29:25:15:41(将lvs的mac替换成rs2的mac地址发送给rs2)
第三个包:rs2收到第二个包回应tcp的一个包:
Src:192.168,44.100 ,Dst: 192.168.18,130 ## 网络层以上没变
Src: 00:0c:29:25:15:41(rs2的mac) Dst:VMware da:9e:e0 (00:0c:29:da:9e:e0)(路由器的mac),
第四个包:lvs的收到路由器的tcp的第三个包请求包:请求包会经过lvs
Src: 192.168.18,130(cip) ,Dst: 192.168,44.100(vip) ## 网络层以上没变
Src: VMware da:9e:e0 (00:0c:29:da:9e:e0)(路由器的mac) Dst:00:0c:29:99:72:83(lvs的mac)
第五个包:lvs的收到路由器的tcp的第三个包请求包,lvs 替换了rs2的mac又转给了rs2让rs2回应。
Src: 192.168.18,130(cip) ,Dst: 192.168,44.100(vip) ## 网络层以上没变
Src: VMware da:9e:e0 (00:0c:29:da:9e:e0)(路由器的mac) Dst:00:0c:29:25:15:41(rs2的mac)
第6个包:rs2收到了lvs转过来的三次握手的包让rs2回应。
第五个包:客户端获取http的内容,请求包还是经过了lvs,
Src: 192.168.18,130(cip) ,Dst: 192.168,44.100(vip) ## 网络层以上没变
Src: VMware da:9e:e0 (00:0c:29:da:9e:e0)(路由器的mac) Dst:00:0c:29:99:72:83(lvs的mac)
第六个包:lvs的收到路由器的tcp的第三个包请求包,lvs 替换了rs2的mac又转给了rs2让rs2回应http的请求。
第七个包:rs2回应http的请求。
Src:192.168,44.100(vip) ,Dst: 192.168.18,130(cip) ## 网络层以上没变
Src: 00:0c:29:99:72:83(lvs的mac) Dst: VMware da:9e:e0 (00:0c:29:da:9e:e0)(路由器的mac)
DR模型结论:
1,请求报文都会先发送给lvs,然后lvs替换目标的mac为本次调度的rs服务器的mac转给rs。
2,rs回应报文的目的mac都是路由器(网关的mac),回包不进过lvs。
- 总结 http协议的通信过程详解
协议简介
HTTP 是在网络上传输HTML的协议,用于浏览器和服务器的通信
HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议,默认端口号是 80
HTTP 协议是以 ASCII 码传输,是无连接无状态的
请求报文
HTTP 请求分为三个部分:状态行、请求头、消息主体。类似于下面这样:
1 <method> <request-URL> <version>
2 <headers>
3
4 <entity-body>
HTTP 定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE。URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而 HTTP 中的GET,POST,PUT,DELETE就对应着对这个资源的查,增,改,删4个操作
响应报文
HTTP响应也由3个部分构成,分别是:状态行、响应头(Response Header)、响应正文
常见的状态码有如下几种:
200 OK 客户端请求成功
301 Moved Permanently 请求永久重定向
302 Moved Temporarily 请求临时重定向
304 Not Modified 文件未修改,可以直接使用缓存的文件
400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解
401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
404 Not Found 请求的资源不存在,例如,输入了错误的URL
500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求
503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常
条件 GET
客户端重复访问站点域名,为了优化带宽,服务器根据请求中的 If-Modified-Since 字段判断响应文件是否有更新,如果没有更新,服务器返回 304 Not Modified 响应,告诉浏览器使用缓存文件,具体如下:Status Code: 304 Not Modified
持久连接
我们知道 HTTP 协议采用“请求-应答”模式,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接,Keep-Alive 功能使客户端到服务器端的连接持续有效,避免重复创建
HTTP 1.0 版本中,如果客户端浏览器支持 Keep-Alive ,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive在 HTTP 1.1 版本中,默认情况下所有连接都被保持,如果加入 “Connection: close” 才关闭Keep-Alive 简单说就是保持当前的TCP连接,避免了重新建立连接
长连接不可能一直保持,例如 Keep-Alive: timeout=5, max=100,表示这个TCP通道可以保持5秒,max=100,表示这个长连接最多接收100次请求就断开
HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive 没能改变这个结果
使用长连接之后,客户端、服务端怎么知道本次传输结束呢?两部分:1. 判断传输数据是否达到了Content-Length 指示的大小;2. 动态生成的文件没有 Content-Length ,它是分块传输(chunked),这时候就要根据 chunked 编码来判断,chunked 编码的数据在最后有一个空 chunked 块,表明本次传输数据结束
Transfer-Encoding
标示 HTTP 报文传输格式的头部值,当前的 HTTP 规范定义了一种传输取值——chunked。标示消息体由数量未定的块组成,并以最后一个大小为0的块为结束
chunked 的优势:服务器端可以边生成内容边发送,无需事先生成全部的内容
HTTP/2 不支持 Transfer-Encoding: chunked,HTTP/2 有自己的 streaming 传输方式(Source:MDN - Transfer-Encoding)
HTTP Pipelining
默认情况下 HTTP 协议中每个传输层连接只能承载一个 HTTP 请求和响应,浏览器会在收到上一个请求的响应之后,再发送下一个请求。
HTTP Pipelining(管线化)是将多个 HTTP 请求整批提交的技术,在传送过程中不需等待服务端的回应。
管线化机制通过持久连接(persistent connection)完成,仅 HTTP/1.1 支持此技术(HTTP/1.0不支持)
只有 GET 和 HEAD 请求可以进行管线化,而 POST 则有所限制
初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持 HTTP/1.1 版本的协议
管线化不会影响响应到来的顺序
HTTP /1.1 要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可
由于上面提到的服务器端问题,开启管线化很可能并不会带来大幅度的性能提升,而且很多服务器端和代理程序对管线化的支持并不好,因此现代浏览器如 Chrome 和 Firefox 默认并未开启管线化支持
- 总结 网络IO模型和nginx架构
什么是网络IO
网络通信就是网络协议栈到用户空间进程的IO就是网络IO
网络I/O 处理过程
获取请求数据,客户端与服务器建立连接发出请求,服务器接受请求(1-3)
构建响应,当服务器接收完请求,并在用户空间处理客户端的请求,直到构建响应完成(4)
返回数据,服务器将已构建好的响应再通过内核空间的网络 I/O 发还给客户端(5-7)
不论磁盘和网络I/O
每次I/O,都要经由两个阶段:
第一步:将数据从文件先加载至内核内存空间(缓冲区),等待数据准备完成,时间较长
第二步:将数据从内核缓冲区复制到用户空间的进程的内存中,时间较短
I/O 模型相关概念
同步/异步:关注的是消息通信机制,即调用者在等待一件事情的处理结果时,被调用者是否提供完成状
态的通知。
同步:synchronous,被调用者并不提供事件的处理结果相关的通知消息,需要调用者主动询问事
情是否处理完成
异步:asynchronous,被调用者通过状态、通知或回调机制主动通知调用者被调用者的运行状态
阻塞/非阻塞:关注调用者在等待结果返回之前所处的状态
阻塞:blocking,指IO操作需要彻底完成后才返回到用户空间,调用结果返回之前,调用者被挂
起,干不了别的事情。
非阻塞:nonblocking,指IO操作被调用后立即返回给用户一个状态值,而无需等到IO操作彻底完
成,在最终的调用结果返回之前,调用者不会被挂起,可以去做别的事情。
网络 I/O 模型
阻塞IO模型是最简单的I/O模型,用户线程在内核进行IO操作时被阻塞
用户线程通过系统调用read发起I/O读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将
接收的数据拷贝到用户空间,完成read操作
用户需要等待read将数据读取到buffer后,才继续处理接收的数据。整个I/O请求的过程中,用户线程是
被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够
优点:程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用 CPU 资源
缺点:每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销
较大,apache 的prefork使用的是这种模式。
非阻塞型 I/O 模型 (nonblocking IO)
用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据
到达后,才真正读取到数据,继续执行。即 “轮询”机制存在两个问题:如果有大量文件描述符都要等,
那么就得一个一个的read。这会带来大量的Context Switch(read是系统调用,每调用一次就得在用户
态和核心态切换一次)。轮询的时间不好把握。这里是要猜多久之后数据才能到。等待时间设的太长,
程序响应延迟就过大;设的太短,就会造成过于频繁的重试,干耗CPU而已,是比较浪费CPU的方式,一
般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
非阻塞:程序向内核发送请I/O求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回IO结果,进
程将不再等待,而且继续处理其他请求,但是仍然需要进程隔一段时间就要查看内核I/O是否完成。
I/O 多路复用型 (I/O multiplexing)
上面的模型中,每一个文件描述符对应的IO是由一个线程监控和处理
多路复用IO指一个线程可以同时(实际是交替实现,即并发完成)监控和处理多个文件描述符对应各自
的IO,即复用同一个线程
一个线程之所以能实现同时处理多个IO,是因为这个线程调用了内核中的SELECT,POLL或EPOLL等系统调
用,从而实现多路复用IO
I/O multiplexing 主要包括:select,poll,epoll三种系统调用,select/poll/epoll的好处就在于单个
process就可以同时处理多个网络连接的IO。
它的基本原理就是select/poll/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数
据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,
当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从
kernel拷贝到用户进程。
Apache prefork是此模式的select,worker是poll模式。
信号驱动式 I/O 模型 (signal-driven IO)
信号驱动I/O的意思就是进程现在不用傻等着,也不用去轮询。而是让内核在数据就绪时,发送信号通知
进程。
调用的步骤是,通过系统调用 sigaction ,并注册一个信号处理的回调函数,该调用会立即返回,然后
主程序可以继续向下执行,当有I/O操作准备就绪,即内核数据就绪时,内核会为该进程产生一个 SIGIO
信号,并回调注册的信号回调函数,这样就可以在信号回调函数中系统调用 recvfrom 获取数据,将用户
进程所需要的数据从内核空间拷贝到用户空间
此模型的优势在于等待数据报到达期间进程不被阻塞。用户主程序可以继续执行,只要等待来自信号处
理函数的通知。
在信号驱动式 I/O 模型中,应用程序使用套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继
续运行并不阻塞
当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。优
点:线程并没有在等待数据时被阻塞,内核直接返回调用接收信号,不影响进程继续处理其他请求因此
可以提高资源的利用率
缺点:信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知
异步 I/O 模型 (asynchronous IO)
异步I/O 与 信号驱动I/O最大区别在于,信号驱动是内核通知用户进程何时开始一个I/O操作,而异步I/O
是由内核通知用户进程I/O操作何时完成,两者有本质区别,相当于不用去饭店场吃饭,直接点个外卖,
把等待上菜的时间也给省了
相对于同步I/O,异步I/O不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备
好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直
接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。
信号驱动IO当内核通知触发信号处理程序时,信号处理程序还需要阻塞在从内核空间缓冲区拷贝数据到
用户空间缓冲区这个阶段,而异步IO直接是在第二个阶段完成后,内核直接通知用户线程可以进行后续
操作了
优点:异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠
缺点:要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的
异步 I/O,在 Linux 系统下,Linux 2.6才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编
程时以 IO 复用模型模式+多线程任务的架构基本可以满足需求
Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent、libev、
libuv。
五种 IO 对比
I/O 的具体实现方式
I/O常见实现
Nginx支持在多种不同的操作系统实现不同的事件驱动模型,但是其在不同的操作系统甚至是不同的系
统版本上面的实现方式不尽相同,主要有以下实现方式:
1、select:
select库是在linux和windows平台都基本支持的 事件驱动模型库,并且在接口的定义也基本相同,只是部
分参数的含义略有差异,最大并发限制1024,是最早期的事件驱动模型。
2、poll:
在Linux 的基本驱动模型,windows不支持此驱动模型,是select的升级版,取消了最大的并发限制,在编
译nginx的时候可以使用--with-poll_module和--without-poll_module这两个指定是否编译select
库。
3、epoll:
epoll是库是Nginx服务器支持的最高性能的事件驱动库之一,是公认的非常优秀的事件驱动模型,它和
select和poll有很大的区别,epoll是poll的升级版,但是与poll有很大的区别.
epoll的处理方式是创建一个待处理的事件列表,然后把这个列表发给内核,返回的时候在去轮询检查这个
表,以判断事件是否发生,epoll支持一个进程打开的最大事件描述符的上限是系统可以打开的文件的最大
数,同时epoll库的I/O效率不随描述符数目增加而线性下降,因为它只会对内核上报的“活跃”的描述符进行
操作。
4、kqueue:
用于支持BSD系列平台的高校事件驱动模型,主要用在FreeBSD 4.1及以上版本、OpenBSD 2.0级以上版
本,NetBSD级以上版本及Mac OS X 平台上,该模型也是poll库的变种,因此和epoll没有本质上的区别,
效效率和epoll相似
5、IOCP:
Windows系统上的实现方式,对应第5种(异步I/O)模型。
6、/dev/poll:
用于支持unix衍生平台的高效事件驱动模型,主要在Solaris 平台、HP/UX,该模型是sun公司在开发
Solaris系列平台的时候提出的用于完成事件驱动机制的方案,它使用了虚拟的/dev/poll设备,开发人员将
要见识的文件描述符加入这个设备,然后通过ioctl()调用来获取事件通知,因此运行在以上系列平台的时候
请使用/dev/poll事件驱动机制。效率和epoll相似
7、rtsig:
不是一个常用事件驱动,最大队列1024,不是很常用
8、eventport:
该方案也是sun公司在开发Solaris的时候提出的事件驱动库,只是Solaris 10以上的版本,该驱动库可防
止内核崩溃等情况的发生。
常用I/O模型比较
Nginx 架构
Nginx 进程结构
web请求处理机制
多进程方式:服务器每接收到一个客户端请求就有服务器的主进程生成一个子进程响应客户端,直
到用户关闭连接,这样的优势是处理速度快,子进程之间相互独立,但是如果访问过大会导致服务
器资源耗尽而无法提供请求。
多线程方式:与多进程方式类似,但是每收到一个客户端请求会有服务进程派生出一个线程和此客
户端进行交互,一个线程的开销远远小于一个进程,因此多线程方式在很大程度减轻了web服务器
对系统资源的要求,但是多线程也有自己的缺点,即当多个线程位于同一个进程内工作的时候,可
以相互访问同样的内存地址空间,所以他们相互影响,一旦主进程挂掉则所有子线程都不能工作
了,IIS服务器使用了多线程的方式,需要间隔一段时间就重启一次才能稳定。
Nginx是多进程组织模型,而且是一个由Master主进程和Worker工作进程组成。
- 完成nginx编译安装脚本
报错:
#yum -y pcre-devel
不安装 pcre-devel 就会报错:
./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.
#yum -y zlib-devel
不安装zlib-devel 就会报错:也可以去掉这个模块--without-http_gzip_module
./configure: error: the HTTP gzip module requires the zlib library.
You can either disable the module by using --without-http_gzip_module
option, or install the zlib library into the system, or build the zlib library
statically from the source with nginx by using --with-zlib=<path> option.
安装:
1,wget http://nginx.org/download/nginx-1.20.2.tar.gz #随便下载在那里,习惯/usr/local/src/
2,yum install #yum -y install gcc pcre-devel zlib-devel #其他模块可能需要更多依赖的lib。
3,useradd -r -s /sbin/nologin nginx
3,tar -xvf nginx-1.20.2.tar.gz
4,cd nginx-1.20.2
5,mkdir -p /apps/nginx/run
6,./configure --prefix=/apps/nginx --user=nginx --group=nginx #根据需要编译模块,可以参考yum编译的选项。
7,make -J 4 && make install
8,ln -s /apps/nginx/sbin/nginx /usr/sbin/
9,chown -R nginx.nginx /apps/nginx
10.nginx -v
[root@centos7 nginx]# nginx -v
nginx version: nginx/1.20.2
11,拷贝service文件,可以拷贝yum安装的service文件修改:
rsync -a 192.168.44.58:/usr/lib/systemd/system/nginx.service /usr/lib/systemd/system/nginx.service
12,vim /usr/lib/systemd/system/nginx.service
[root@centos7 ~]# cat /usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/apps/nginx/run/nginx.pid
# Nginx will fail to start if /run/nginx.pid already exists but has the wrong
# SELinux context. This might happen when running `nginx -t` from the cmdline.
# https://bugzilla.redhat.com/show_bug.cgi?id=1268621
ExecStartPre=/usr/bin/rm -f /apps/nginx/run/nginx.pid
ExecStartPre=/apps/nginx/sbin/nginx -t
ExecStart=/apps/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=mixed
PrivateTmp=true
LimitNOFILE=100000
[Install]
WantedBy=multi-user.target
13,systemctl daemon-reload
14,vim /apps/nginx/conf/nginx.conf #修改配置文件的pid文件位置:PIDFile=/apps/nginx/run/nginx.pid
15,systemctl enable --now nginx.service
16,systemctl status nginx.service
[root@centos7 ~]# systemctl status nginx.service
● nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2023-11-21 22:07:08 CST; 27min ago
Process: 10428 ExecStart=/apps/nginx/sbin/nginx (code=exited, status=0/SUCCESS)
Process: 10426 ExecStartPre=/apps/nginx/sbin/nginx -t (code=exited, status=0/SUCCESS)
Process: 10425 ExecStartPre=/usr/bin/rm -f /apps/nginx/run/nginx.pid (code=exited, status=0/SUCCESS)
Main PID: 10430 (nginx)
CGroup: /system.slice/nginx.service
├─10430 nginx: master process /apps/nginx/sbin/nginx
└─10431 nginx: worker process
Nov 21 22:07:08 centos7.9 systemd[1]: Starting The nginx HTTP and reverse proxy server...
Nov 21 22:07:08 centos7.9 nginx[10426]: nginx: the configuration file /apps/nginx/conf/nginx.conf syntax is ok
Nov 21 22:07:08 centos7.9 nginx[10426]: nginx: configuration file /apps/nginx/conf/nginx.conf test is successful
Nov 21 22:07:08 centos7.9 systemd[1]: Failed to parse PID from file /apps/nginx/run/nginx.pid: Invalid argument
Nov 21 22:07:08 centos7.9 systemd[1]: Started The nginx HTTP and reverse proxy server.
官方yum安装编译的选项:
[root@rocky-8 ~]# nginx -V
nginx version: nginx/1.14.1
built by gcc 8.4.1 20200928 (Red Hat 8.4.1-1) (GCC)
built with OpenSSL 1.1.1g FIPS 21 Apr 2020 (running with OpenSSL 1.1.1k FIPS 25 Mar 2021)
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-http_auth_request_module --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E'
- 总结nginx核心配置,并实现nginx多虚拟主机
默认的nginx.conf 配置文件格式说明:
#全局配置端,对全局生效,主要设置nginx的启动用户/组,启动的工作进程数量,工作模式,Nginx的PID
路径,日志路径等。
user nginx nginx;
worker_processes 1; #启动工作进程数数量
events { #events设置快,主要影响nginx服务器与用户的网络连接,比如是否允许同时接受多个网络连
接,使用哪种事件驱动模型处理请求,每个工作进程可以同时支持的最大连接数,是否开启对多工作进程下的
网络连接进行序列化等。
worker_connections 1024; #设置单个nginx工作进程可以接受的最大并发,作为web服务器
的时候最大并发数为worker_connections * worker_processes,作为反向代理的时候为
(worker_connections * worker_processes)/2
}
http { #http块是Nginx服务器配置中的重要部分,缓存、代理和日志格式定义等绝大多数功能和第三方模
块都可以在这设置,http块可以包含多个server块,而一个server块中又可以包含多个location块,
server块可以配置文件引入、MIME-Type定义、日志自定义、是否启用sendfile、连接超时时间和单个链
接的请求上限等。
include mime.types;
default_type application/octet-stream;
sendfile on; #作为web服务器的时候打开sendfile加快静态文件传输,指定是否使用
sendfile系统调用来传输文件,sendfile系统调用在两个文件描述符之间直接传递数据(完全在内核中操
作),从而避免了数据在内核缓冲区和用户缓冲区之间的拷贝,操作效率很高,被称之为零拷贝,硬盘 >>
kernel buffer (快速拷贝到kernelsocket buffer) >>协议栈。
keepalive_timeout 65; #长连接超时时间,单位是秒
server { #设置一个虚拟机主机,可以包含自己的全局快,同时也可以包含多个location模块。比如
本虚拟机监听的端口、本虚拟机的名称和IP配置,多个server 可以使用一个端口,比如都使用80端口提供
web服务、
listen 80; #配置server监听的端口
server_name localhost; #本server的名称,当访问此名称的时候nginx会调用当前
serevr内部的配置进程匹配。
location / { #location其实是server的一个指令,为nginx服务器提供比较多而且灵活的指
令,都是在location中体现的,主要是基于nginx接受到的请求字符串,对用户请求的UIL进行匹配,并对特
定的指令进行处理,包括地址重定向、数据缓存和应答控制等功能都是在这部分实现,另外很多第三方模块的
配置也是在location模块中配置。
root html; #相当于默认页面的目录名称,默认是安装目录的相对路径,可以使用绝对路
径配置。
index index.html index.htm; #默认的页面文件名称
}
error_page 500 502 503 504 /50x.html; #错误页面的文件名称
location = /50x.html { #location处理对应的不同错误码的页面定义到/50x.html,这个
跟对应其server中定义的目录下。
root html; #定义默认页面所在的目录
}
}
#和邮件相关的配置
#mail {
# ...
# } mail 协议相关配置段
#tcp代理配置,1.9版本以上支持
#stream {
# ...
# } stream 服务器相关配置段
#导入其他路径的配置文件
#include /apps/nginx/conf.d/*.conf
}
http 配置块:
http 协议相关的配置结构:
http {
...
... #各server的公共配置
server { #每个server用于定义一个虚拟主机,第一个server为默认虚拟服务器
...
}
server {
...
server_name #虚拟主机名
root #主目录
alias #路径别名
location [OPERATOR] URL { #指定URL的特性
...
if CONDITION {
...
}
}
}
}
http 协议配置说明:
http {
include mime.types; #导入支持的文件类型,是相对于/apps/nginx/conf的目录
default_type application/octet-stream; #除mime.types中文件类型外,设置其它文件默认
类型,访问其它类型时会提示下载不匹配的类型文件
#日志配置部分
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
#自定义优化参数
sendfile on;
#tcp_nopush on; #在开启了sendfile的情况下,合并请求后统一发送给客户端,必须开启
sendfile
#tcp_nodelay off; #在开启了keepalived模式下的连接是否启用TCP_NODELAY选项,当为
off时,延迟0.2s发送,默认On时,不延迟发送,立即发送用户响应报文。
#keepalive_timeout 0;
keepalive_timeout 65 65; #设置会话保持时间,第二个值为响应首部:keepAlived:timeout=65,可以和第一个值不同
#gzip on; #开启文件压缩
server {
listen 80; #设置监听地址和端口
server_name localhost; #设置server name,可以以空格隔开写多个并支持正则表达式,
如:*.magedu.com www.magedu.* ~^www\d+\.magedu\.com$ default_server
#charset koi8-r; #设置编码格式,默认是俄语格式,建议改为utf-8
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html; #定义错误页面
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ { #以http的方式转发php请求到指定web服务器
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ { #以fastcgi的方式转发php请求到php处理
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht { #拒绝web形式访问指定文件,如很多的网站都是通过.htaccess文件来
改变自己的重定向等功能。
# deny all;
#}
location ~ /passwd.html {
deny all;
}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server { #自定义虚拟server
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm; #指定默认网页文件,此指令由
ngx_http_index_module模块提供
# }
#}
# HTTPS server
#
#server { #https服务器配置
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
nginx多虚拟主机:
1,修改主配置文件。#也可以增加子配置文件,建议新增单独的配置文件。
vim /apps/nginx/conf/nginx.conf
...............................................................
include /apps/nginx/conf.d/*.conf;##现在是加在系统主配置文件的http模块最后的。
}
...............................................................
http {
。。。。。。。。。。略。。。。。。。。
#gzip on;
include /apps/nginx/conf.d/*.conf;#现在是加在系统主配置文件的server模块前面,效果就是比nginx默认的网页优先级高,然后会去找子配置文件的第一个配置为默认站点。
server {
listen 80;
server_name localhost;
。。。。。。。。。。略。。。。。。。。
2,准备pc.mobile.golang的index.html文件。
mkidr /opt/web/pc
mkdir /opt/web/mobile
mkdir /opt/web/golang
echo 'i am pc server' >/opt/web/pc/index.html
echo 'i am mobile server' >/opt/web/mobile/index.html
echo 'i am golang server' >/opt/web/golang/index.html
chown nginx.nginx /opt/web/*
3,准备各子目录的配置信息:
[root@centos7 conf.d]# cat /apps/nginx/conf.d/pc.conf
server {
listen 80;
server_name pc.magedu.org;
location / {
root /opt/web/pc;
}
}
[root@centos7 conf.d]# cat /apps/nginx/conf.d/mobile.conf
server {
listen 80;
server_name mobile.magedu.org; #指定第二个站点名称
location / {
root /opt/web/mobile;
}
}
[root@centos7 conf.d]# cat /apps/nginx/conf.d/golang.conf
server {
listen 80;
server_name golang.magedu.org; #指定第二个站点名称
location / {
root /opt/web/golang;
}
}
4,systemctl reload nginx
5,验证
vim /etc/hosts
192.168.44.58 mobile.magedu.org
192.168.44.58 pc.magedu.org
192.168.44.58 golang.magedu.org
[root@rocky-8 ~]# curl mobile.magedu.org
i'am mobile server
[root@rocky-8 ~]# curl pc.magedu.org
i'am pc server
[root@rocky-8 ~]# curl golang.magedu.org
i'am golang server
- 总结nginx日志格式定制
访问日志是记录客户端即用户的具体请求内容信息,而在全局配置模块中的error_log是记录nginx服务
器运行时的日志保存路径和记录日志的level,因此两者是不同的,而且Nginx的错误日志一般只有一
个,但是访问日志可以在不同server中定义多个,定义一个日志需要使用access_log指定日志的保存路
径,使用log_format指定日志的格式,格式中定义要保存的具体日志内容。
访问日志由 ngx_http_log_module 模块实现
自定义默认格式日志
如果是要保留日志的源格式,只是添加相应的日志内容,则配置如下:
#注意:此指令只支持http块,不支持server块
log_format nginx_format1 '$remote_addr - $remote_user [$time_local] "$request"
'
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'
'$server_name:$server_port';
#注意:此指令一定要在放在log_format命令后
access_log logs/access.log nginx_format1;
自定义json格式日志:
Nginx 的默认访问日志记录内容相对比较单一,默认的格式也不方便后期做日志统计分析,生产环境中
通常将nginx日志转换为json日志,然后配合使用ELK做日志收集,统计和分析。
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;
实现:
1,修改主配置文件:##
vim /apps/nginx/conf/nginx.conf
........................
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
log_format access_json '{"@timestamp":"$time_iso8601",' # log_format 指定日志的格式 access_json 格式的名称,方便后面单独调用,{......}日志的值:
'"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; ##在主配置文件指定accesslog的文件路径 和用哪个子自定义格式的名称,全部主机生效。
效果:
[root@centos7 ~]# tail -f /apps/nginx/logs/access_json.log
{"@timestamp":"2023-11-23T17:50:27+08:00","host":"192.168.44.58","clientip":"192.168.44.88","size":153,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"pc.magedu.org","uri":"/abc/1.gif","xff":"-","referer":"-","tcp_xff":"-","http_user_agent":"curl/7.61.1","status":"404"}
{"@timestamp":"2023-11-23T17:50:31+08:00","host":"192.168.44.58","clientip":"192.168.44.88","size":27,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"pc.magedu.org","uri":"/abc/index.html","xff":"-","referer":"-","tcp_xff":"-","http_user_agent":"curl/7.61.1","status":"200"}
........................
给单独server 定制日志文件:用于不同的网站,单独定制或者一个server的不同的location单独日志:
1,修改server的子配置文件
[root@centos7 ~]# cat /apps/nginx/conf.d/mobile.conf
server {
listen 80;
access_log /apps/nginx/logs/mobile_access_json.log access_json; ## 可以在主配置定制日志 在需要的server模块调用
server_name mobile.magedu.org;
location / {
root /opt/web/mobile;
}
}
2,测试
[root@centos7 ~]# tail -f /apps/nginx/logs/mobile_access_json.log
{"@timestamp":"2023-11-23T17:59:13+08:00","host":"192.168.44.58","clientip":"192.168.44.88","size":153,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"mobile.magedu.org","uri":"/abc/","xff":"-","referer":"-","tcp_xff":"-","http_user_agent":"curl/7.61.1","status":"404"}
{"@timestamp":"2023-11-23T17:59:18+08:00","host":"192.168.44.58","clientip":"192.168.44.88","size":20,"responsetime":0.000,"upstreamtime":"-","upstreamhost":"-","http_host":"mobile.magedu.org","uri":"/index.html","xff":"-","referer":"-","tcp_xff":"-","http_user_agent":"curl/7.61.1","status":"200"}
access_log 可以插入的模块:
Context: http, server, location, if in location, limit_except