当前位置:网站首页>Redis之sentinel哨兵集群怎么部署
Redis之sentinel哨兵集群怎么部署
2022-07-29 03:11:00 【亿速云】
Redis之sentinel哨兵集群怎么部署
本文小编为大家详细介绍“Redis之sentinel哨兵集群怎么部署”,内容详细,步骤清晰,细节处理妥当,希望这篇“Redis之sentinel哨兵集群怎么部署”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
一、Redis sentinel哨兵集群概述
(1)Redis哨兵概述
*Sentinel 哨兵:这是一个分布式系统,该进程是用于监控Redis集群中Master主服务器的工作状态,在Master主服务器发生故障时,可以实现Master和Slave服务器的秒级切换,保证系统有一个Master主服务器,提供了Redis集群的高可用,在Reids2.6.版本时被加入,到2.8版本之后得到了稳定
Redis哨兵和Redis主从的区别:
Redis哨兵:主服务器出现故障后,会有一个从服务器代替主服务器
Redis主从:主服务器出现故障后,从服务器不会做任何事

(2)Redis哨兵的工作机制
哨兵只需要部署在master主服务器上即可
工作进程:
监控(Monitoring):哨兵通过流言协议(gossip protocols)会不断检查集群中每一台服务器是否运作正常
提醒(Notification):当哨兵监控的某个redis服务器出现问题时,哨兵可以通过API(应用程序接口)向管理员或者其他应用程序发送通知
自动故障转移(Automatic failover):在集群中如果有一个Master主服务器出现故障时,哨兵会通过投票协议(Agreement Protocols)开始一次自动故障迁移操作,他会选择一台数据较完整的Slave从服务器升级为主服务器,当客户端试图连接失效的Master主服务器时,集群也会向客户端返回新的Master主服务器的地址,使得集群可以使用现在的Master替换掉失效的Master。
Master和Slave切换后,Master的redis主配置文件、Slave的redis主配置文件和哨兵的配置文件的内容都会发生相应的改变,即原来的Master的redis主配置文件会多一行Slave服务器的配置,之后哨兵的监控目标就会改变到现在的Master主服务器上

(3)哨兵的三个定时监控任务
每隔10秒,每个Sentinel节点会向主节点和从节点发送info命令获取Redis数据节点的信息
作用:
通过向主节点执行info命令,获取从节点的信息,这也是为什么Sentinel节点不需要显式配置监控从节点。当有新的从节点加入时都可以立刻感知出来,当节点不可达或者故障转移后,可以通过info命令实时更新节点拓扑信息。
每隔1秒,每个Sentinel节点会向主节点、从节点、发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达如果主节点挂掉,那么sentinel,就会从剩余的从节点选择一个数据比较完整来做主节点
二、部署Redis哨兵系统
(1)实验环境
| 系统 | ip | 主机名 | Redis版本 | 端口 | 扮演角色 |
|---|---|---|---|---|---|
| Centos7.4 | 192.168.100.202 | master | Redis-5.0.4 | Redis:6379 Sentinel:26379 | Master |
| Centos7.4 | 192.168.100.203 | slave1 | Redis-5.0.4 | Redis:6379 | Slave |
| Centos7.4 | 192.168.100.204 | slave2 | Redis-5.0.4 | Redis:6379 | Slave |
(2)实验步骤 -在每台服务器上都安装Redis
安装步骤相同,主机名、ip不同,下面只写Master配置
[[email protected] ~]# hostnamectl set-hostname master[[email protected] ~]# su[[email protected] ~]# systemctl stop firewalld[[email protected] ~]# setenforce 0setenforce: SELinux is disabled[[email protected] ~]# mount /dev/cdrom /mnt/mount: /dev/sr0 写保护,将以只读方式挂载mount: /dev/sr0 已经挂载或 /mnt 忙 /dev/sr0 已经挂载到 /mnt 上[[email protected] ~]# ll总用量 1928-rw-------. 1 root root 1264 1月 12 18:27 anaconda-ks.cfg-rw-r--r-- 1 root root 1966337 6月 9 01:16 redis-5.0.4.tar.gz[[email protected] ~]# tar xf redis-5.0.4.tar.gz[[email protected] ~]# cd redis-5.0.4[[email protected] redis-5.0.4]# make[[email protected] redis-5.0.4]# mkdir -p /usr/local/redis[[email protected] redis-5.0.4]# cp /root/redis-5.0.4/src/redis-server /usr/local/redis/[[email protected] redis-5.0.4]# cp /root/redis-5.0.4/src/redis-cli /usr/local/redis/[[email protected] redis-5.0.4]# cp /root/redis-5.0.4/redis.conf /usr/local/redis/ [[email protected] redis-5.0.4]# vim /usr/local/redis/redis.conf #修改。。。。。。 68 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 69 bind 192.168.100.202 #修改为本机地址,如果为127.0.0.1就只能本机访问 70 。。。。。。 87 # are explicitly listed using the "bind" directive. 88 protected-mode no #关闭redis的保护模式,如果为yes的话其他客户端就无法连接到此服务器 89 。。。。。。 135 # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. 136 daemonize yes #开启redis的后台守护程序,即在redis开启之后是放在后台运行的 137 。。。。。。 262 # Note that you must specify a directory here, not a file name. 263 dir /usr/local/redis/rdb 264 。。。。。。 506 # 507 requirepass 123123 #去掉注释,修改redis的密码为123123 508 #保存退出[[email protected] redis-5.0.4]# mkdir /usr/local/redis/rdb[[email protected] redis-5.0.4]# vim /etc/init.d/redis#!/bin/sh# chkconfig: 2345 80 90# description: Start and Stop redis#PATH=/usr/local/bin:/sbin:/usr/bin:/binREDISPORT=6379EXEC=/usr/local/redis/redis-serverREDIS_CLI=/usr/local/redis/redis-cliPIDFILE=/var/run/redis_6379.pidCONF="/usr/local/redis/redis.conf"AUTH="123123"LISTEN_IP=$(netstat -utpln |grep redis-server |awk '{print $4}'|awk -F':' '{print $1}')case "$1" in start) if [ -f $PIDFILE ] then echo "$PIDFILE exists, process is already running or crashed" else echo "Starting Redis server..." $EXEC $CONF fi if [ "$?"="0" ] then echo "Redis is running..." fi ;; stop) if [ ! -f $PIDFILE ] then echo "$PIDFILE does not exist, process is not running" else PID=$(cat $PIDFILE) echo "Stopping ..." $REDIS_CLI -h $LISTEN_IP -p $REDISPORT -a $AUTH SHUTDOWN while [ -x ${PIDFILE} ] do echo "Waiting for Redis to shutdown ..." sleep 1 done echo "Redis stopped" fi ;; restart|force-reload) ${0} stop ${0} start ;; *) echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2 exit 1esac[[email protected] redis-5.0.4]# chkconfig --add redis[[email protected] redis-5.0.4]# chmod 755 /etc/init.d/redis[[email protected] redis-5.0.4]# ln -s /usr/local/redis/* /usr/local/bin/[[email protected] redis-5.0.4]# /etc/init.d/redis start Starting Redis server...5233:C 09 Jun 2021 01:25:53.069 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo5233:C 09 Jun 2021 01:25:53.069 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=5233, just started5233:C 09 Jun 2021 01:25:53.069 # Configuration loadedRedis is running...[[email protected] redis-5.0.4]# netstat -anpt | grep 6379tcp 0 0 192.168.100.202:6379 0.0.0.0:* LISTEN 5234/redis-server 1
-做redis主从
******(1)Master配置[[email protected] redis-5.0.4]# vim /usr/local/redis/redis.conf #修改。。。。。。 292 # 293 masterauth 123123 #配置主服务器密码,哨兵有一个问题,就是当主服务器坏掉,切换到从服务器时,原来的主服务器可以正常运行之后,再次加入集群是加不进去的,因为哨兵没有配置主服务器的密码,所以无法连接,所以在使用哨兵集群时,要把每台的主服务器密码都配置上,每台redis的密码最好都一样 294 。。。。。。 456 # 457 min-replicas-to-write 1 #设置slave服务器的数量,当slave服务器少于这个数量时,Master主服务器会停止接收客户端的一切写请求 458 min-replicas-max-lag 10 #设置主服务器和从服务器之间同步数据的超时时间,当超过此时间时,master主服务器会停止客户端的一切写操作,单位为秒 459 #。。。。。。[[email protected] redis-5.0.4]# /etc/init.d/redis restart #重启redisStopping ...Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.Redis stoppedStarting Redis server...5291:C 09 Jun 2021 02:04:39.132 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo5291:C 09 Jun 2021 02:04:39.132 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=5291, just started5291:C 09 Jun 2021 02:04:39.132 # Configuration loadedRedis is running...******(2)Slave1配置[[email protected] redis-5.0.4]# vim /usr/local/redis/redis.conf 。。。。。。 285 # 286 replicaof 192.168.100.202 6379 #在从服务器上指定主服务器的ip和端口 287 。。。。。。 292 # 293 masterauth 123123 #指定主服务器上redis的密码 294。。。。。。#保存退出[[email protected] redis-5.0.4]# /etc/init.d/redis restart #重启服务Stopping ...Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.Redis stoppedStarting Redis server...5304:C 09 Jun 2021 02:11:32.241 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo5304:C 09 Jun 2021 02:11:32.241 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=5304, just started5304:C 09 Jun 2021 02:11:32.241 # Configuration loadedRedis is running...******(3)Slave2配置[[email protected] redis-5.0.4]# vim /usr/local/redis/redis.conf 。。。。。。 286 replicaof 192.168.100.204 6379 287 288 # If the master is password protected (using the "requirepass" configuration 289 # directive below) it is possible to tell the replica to authenticate before 290 # starting the replication synchronization process, otherwise the master will 291 # refuse the replica request. 292 # 293 masterauth 123123 294 。。。。。。#保存退出[[email protected] redis-5.0.4]# /etc/init.d/redis restart Stopping ...Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.Redis stoppedStarting Redis server...5253:C 09 Jun 2021 17:50:25.680 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo5253:C 09 Jun 2021 17:50:25.680 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=5253, just started5253:C 09 Jun 2021 17:50:25.680 # Configuration loadedRedis is running...******(3)验证主从是否成功[[email protected] ~]# redis-cli -h 192.168.100.202 -a 123123Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.192.168.100.202:6379> set aaa bbbOK192.168.100.202:6379> set bbb ccc OK192.168.100.202:6379> keys *1) "aaa"2) "bbb"[[email protected] ~]# redis-cli -h 192.168.100.203 -a 123123Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.192.168.100.203:6379> keys *1) "bbb"2) "aaa"192.168.100.203:6379> set ttt fff(error) READONLY You can't write against a read only replica. #从服务器无法写入数据[[email protected] redis-5.0.4]# redis-cli -h 192.168.100.204 -a 123123Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.192.168.100.204:6379> keys *1) "aaa"2) "bbb"192.168.100.204:6379> set ggg aaa(error) READONLY You can't write against a read only replica.#主从配置完成
-配置哨兵
******(1)在master上配置sentinel哨兵[[email protected] ~]# cp redis-5.0.4/src/redis-sentinel /usr/local/redis/ #复制哨兵启动脚本[[email protected] ~]# cp redis-5.0.4/sentinel.conf /usr/local/redis/ #复制哨兵配置文件[[email protected] ~]# mkdir -p /var/redis/data #创建日志文件存放位置 [[email protected] ~]# vim /usr/local/redis/sentinel.conf #修改哨兵配置文件。。。。。。 21 port 26379 #指定端口默认为26379 22 23 # By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it. 24 # Note that Redis will write a pid file in /var/run/redis-sentinel.pid when 25 # daemonized. 26 daemonize yes #yes为放在后台运行,使用no放在前台运行可以看到主从切换时候的信息 27 。。。。。。 64 # unmounting filesystems. 65 dir /var/redis/data #指定日志存放位置,就是刚才创建的路径 66 。。。。。。 83 # The valid charset is A-z 0-9 and the three characters ".-_". 84 sentinel monitor mymaster 192.168.100.202 6379 1 #指定用户为mymaster,ip为202,端口为6379,1表示当有一台master出现故障时,就进行切换 85 86 # sentinel a。。。。。。112 # Default is 30 seconds.113 sentinel down-after-milliseconds mymaster 3000 #指定master的失效时间,单位为毫秒3000为3秒,表示master超过3秒没响应就判定为故障114 。。。。。。145 # Default is 3 minutes.146 sentinel failover-timeout mymaster 180000 #切换操作完成的超时时间,单位为毫秒180000为180秒,在主从切换超过这个时间就判定为切换失败147 148 # SCRIPTS EXE。。。。。。102 #103 sentinel auth-pass mymaster 123123 #连接master和slave的密码104 sentinel config-epoch mymaster 1 #切换后最多有多少节点可以于新的master进行同步数据105 #保存退出[[email protected] ~]# /usr/local/redis/redis-sentinel /usr/local/redis/sentinel.conf #启动哨兵1118:X 09 Jun 2021 18:09:29.027 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1118:X 09 Jun 2021 18:09:29.027 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=1118, just started1118:X 09 Jun 2021 18:09:29.027 # Configuration loaded[[email protected] ~]# netstat -anpt | grep 26379tcp 0 0 0.0.0.0:26379 0.0.0.0:* LISTEN 1119/redis-sentinel tcp6 0 0 :::26379 :::* LISTEN 1119/redis-sentinel [[email protected] ~]# kill -9 1119 #先关闭哨兵[[email protected] ~]# netstat -anpt | grep 26379[[email protected] ~]# sed -i '26s/yes/no/g' /usr/local/redis/sentinel.conf #修改为前台启动[[email protected] ~]# /usr/local/redis/redis-sentinel /usr/local/redis/sentinel.conf #再次开启哨兵,稍等一段时间会有提示1129:X 09 Jun 2021 18:11:02.585 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1129:X 09 Jun 2021 18:11:02.585 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=1129, just started1129:X 09 Jun 2021 18:11:02.585 # Configuration loaded1129:X 09 Jun 2021 18:11:02.586 * Increased maximum number of open files to 10032 (it was originally set to 1024). _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 5.0.4 (00000000/0) 64 bit .-`` .-". "\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 1129 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 1129:X 09 Jun 2021 18:11:02.586 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.1129:X 09 Jun 2021 18:11:02.586 # Sentinel ID is fce7776020cf12792fd239f6f9d34f2d3fdef98c1129:X 09 Jun 2021 18:11:02.586 # +monitor master mymaster 192.168.100.202 6379 quorum 11129:X 09 Jun 2021 18:18:04.434 * +reboot slave 192.168.100.204:6379 192.168.100.204 6379 @ mymaster 192.168.100.202 6379 #看到新增两条消息,从服务器增加了203和204主服务器时2021129:X 09 Jun 2021 18:18:14.478 * +reboot slave 192.168.100.203:6379 192.168.100.203 6379 @ mymaster 192.168.100.202 6379#哨兵配置完成
-测试哨兵的故障切换
******(1)把master服务器在开启一个终端,在新开启的终端中关闭redis,测试是否可以主从切换[[email protected] ~]# /etc/init.d/redis stop Stopping ...Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.Redis stopped******(2)切换到开启哨兵的终端,查看新弹出的信息1129:X 09 Jun 2021 18:20:36.588 # +failover-end master mymaster 192.168.100.202 63791129:X 09 Jun 2021 18:20:36.588 # +switch-master mymaster 192.168.100.202 6379 192.168.100.203 63791129:X 09 Jun 2021 18:20:36.588 * +slave slave 192.168.100.204:6379 192.168.100.204 6379 @ mymaster 192.168.100.203 6379 #发现主服务器变成了2031129:X 09 Jun 2021 18:20:36.588 * +slave slave 192.168.100.202:6379 192.168.100.202 6379 @ mymaster 192.168.100.203 63791129:X 09 Jun 2021 18:20:39.607 # +sdown slave 192.168.100.202:6379 192.168.100.202 6379 @ mymaster 192.168.100.203 6379‘******(3)在203上测试主从复制是否可以正常同步[[email protected] ~]# redis-cli -h 192.168.100.203 -a 123123Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.192.168.100.203:6379> keys *1) "aaa"2) "bbb"192.168.100.203:6379> set yyy aaaOK192.168.100.203:6379> keys *1) "yyy"2) "aaa"3) "bbb"[[email protected] redis-5.0.4]# redis-cli -h 192.168.100.204 -a 123123Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.192.168.100.204:6379> keys * #发现同步成功1) "yyy"2) "bbb"3) "aaa"******(4)此时重新开启202的redis,并且查看哨兵的提示消息[[email protected] ~]# /etc/init.d/redis start Starting Redis server...1167:C 09 Jun 2021 18:23:39.756 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1167:C 09 Jun 2021 18:23:39.756 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=1167, just started1167:C 09 Jun 2021 18:23:39.756 # Configuration loadedRedis is running...1129:X 09 Jun 2021 18:23:50.324 * +convert-to-slave slave 192.168.100.202:6379 192.168.100.202 6379 @ mymaster 192.168.100.203 6379 #提示增加了一台slave******(5)在202的新终端上查看redis的数据是否成功同步[[email protected] ~]# redis-cli -h 192.168.100.202 -a 123123Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.192.168.100.202:6379> keys * #发现已经成功同步1) "bbb"2) "aaa"3) "yyy"#测试故障切换缓存,发现在master主机出现故障然后重新连接到集群后,master角色不会进行转移
-哨兵日志分析
#把哨兵放在前台运行时,日志信息会直接输出到终端上,放到后台运行时,日志会写到指定的路径中+reset-master <instance details> #当master被重置时.+slave <instance details> #当检测到一个slave并添加进slave列表时.+failover-state-reconf-slaves <instance details> #Failover状态变为reconf-slaves状态时+failover-detected <instance details> #当failover发生时+slave-reconf-sent <instance details> #sentinel发送SLAVEOF命令把它重新配置时+slave-reconf-inprog <instance details> #slave被重新配置为另外一个master的slave,但数据复制还未发生时。+slave-reconf-done <instance details> #slave被重新配置为另外一个master的slave并且数据复制已经与master同步时。-dup-sentinel <instance details> #删除指定master上的冗余sentinel时,当一个sentinel重新启动时,可能会发生这个事件+sentinel <instance details> #当master增加了一个sentinel时。+sdown <instance details> #进入SDOWN状态时;-sdown <instance details> #离开SDOWN状态时。+odown <instance details> #进入ODOWN状态时。-odown <instance details> #离开ODOWN状态时。+new-epoch <instance details> #当前配置版本被更新时。+try-failover <instance details> #达到failover条件,正等待其他sentinel的选举。+elected-leader <instance details> #被选举为去执行failover的时候。+failover-state-select-slave <instance details> #开始要选择一个slave当选新master时。no-good-slave <instance details> #没有合适的slave来担当新masterselected-slave <instance details> #找到了一个适合的slave来担当新masterfailover-state-send-slaveof-noone <instance details> #当把选择为新master的slave的身份进行切换的时候。failover-end-for-timeout <instance details> #failover由于超时而失败时。failover-end <instance details> #failover成功完成时。switch-master <master name> <oldip> <oldport> <newip> <newport> #当master的地址发生变化时。通常这是客户端最感兴趣的消息了。+tilt #进入Tilt模式。-tilt #退出Tilt模式。
读到这里,这篇“Redis之sentinel哨兵集群怎么部署”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。
边栏推荐
- centos安装mysql8
- C陷阱与缺陷 第3章 语义“陷阱” 3.3 作为参数的数组声明
- Codeworks 5 questions per day (average 1500) - day 25
- 服务器运行管理制度
- 第09章_性能分析工具的使用
- Typescript learning (I)
- MySQL - the difference between count (field), count (primary key), count (1), count (*)
- [NPM error] - NPM err code eresolve and NPM err eresolve could not resolve problems
- Plato Farm在Elephant Swap上铸造的ePLATO是什么?为何具备高溢价?
- 会议OA之反馈功能
猜你喜欢

力扣刷题之分数加减运算(每日一题7/27)

STC单片机驱动1.8‘TFT SPI屏幕演示示例(含资料包)

西瓜书学习第六章---SVM

mycat读写分离配置

接口自动化测试实践指导(上):接口自动化需要做哪些准备工作

C语言程序设计 | 交换二进制数奇偶位(宏实现)

JVM基础入门篇一(内存结构)

Learn more than 4000 words, understand the problem of this pointing in JS, and handwrite to realize call, apply and bind

Unable to start after idea installation

数字图像处理 第10章——图像分割
随机推荐
【C】数组
Detailed steps for installing MySQL 8.0 under Linux
What is eplato cast by Plato farm on elephant swap? Why is there a high premium?
The Federal Reserve raised interest rates again, Powell "let go of doves" at 75 basis points, and US stocks reveled
Apache文件管理自学笔记——映射文件夹和基于单ip多域名配置apache虚拟机
C陷阱与缺陷 第3章 语义“陷阱” 3.1 指针与数组
C陷阱与缺陷 第3章 语义“陷阱” 3.9 整数溢出
Interpreting AI robots' pet raising and leading fashion trends
mycat读写分离配置
4000 多字学懂弄通 js 中 this 指向问题,顺便手写实现 call、apply 和 bind
Navicat new database
Unity game special effects
2022-07-28 顾宇佳 学习笔记
Introduction and advanced level of MySQL (12)
数字图像处理 第10章——图像分割
2. Nodejs -- path (\dirname, \filname), URL URL, querystring module, mime module, various paths (relative paths), web page loading (interview questions *)
Redis配置缓存过期监听事件触发
【打开新世界大门】看测试老鸟如何把API 测试玩弄在鼓掌之间
扫雷简单版
C陷阱与缺陷 第2章 语法“陷阱” 2.6 “悬挂”else引发的问题
