Redis Sentinel 的概念
Redis 的主从复制 模式下(哨兵用来解决主从复制的一些问题 ),⼀旦主节点由于故障不能提供服务 ,需要人工进行主从切换 ,同时大量的客户端需要被通知切换到新的主节点上,对于上了⼀定规模的应⽤来说,这种方案是无法接受的,于是 Redis 从 2.8 开始提供了 Redis Sentinel(哨兵)加个来解决这个问题。
哨兵存在的意义,能够在redis主从结构出现问题的时候(比如主节点挂了),此时哨兵节点能够自动帮我们选出一个主节点来代替之前挂掉的节点,保证整个redis仍然是可用的状态。
Redis Sentinel 相关名词解释
| 名词 | 逻辑结构 | 物理结构 |
|---|---|---|
| 主节点 | Redis 主服务 | 一个独立的 redis-server 进程 |
| 从节点 | Redis 从服务 | 一个独立的 redis-server 进程 |
| Redis 数据节点 | 主从节点 | 主节点和从节点的进程 |
| 哨兵节点 | 监控 Redis 数据节点的节点 | 一个独立的 redis-sentinel 进程 |
| 哨兵节点集合 | 若干哨兵节点的抽象组合 | 若干 redis-sentinel 进程 |
| Redis 哨兵(Redis Sentinel) | Redis 提供的高可用方案 | 哨兵节点集合 和 Redis 主从节点 |
| 应用方 | 泛指一个或多个客户端 | 一个或多个连接 Redis 的进程 |
关于主从复制问题
Redis 的主从复制模式可以将主节点的数据改变同步给从节点,这样从节点就可以起到两个作用:
- 作为主节点的⼀个备份,⼀旦主节点出了故障不可达的情况,从节点可以作为后备 "顶" 上来,并且保证数据尽量不丢失(主从复制表现为最终⼀致性)。
- 从节点可以分担主节点上的读压力,让主节点只承担写请求的处理,将所有的读请求负载均衡到各个从节点上。
但是主从复制模式并不是万能的,它同样遗留下以下几个问题:
- 主节点发生故障时,进行主备切换的过程是复杂的,需要完全的人工参与,导致故障恢复时间无法保障。
- 主节点可以将读压力分散出去,但写压力/存储压力是无法被分担的。
其中**第⼀个问题是高可用问题,即 Redis 哨兵主要解决的问题。**第⼆个问题是属于存储分布式的问题,留给 Redis 集群去解决。
人工恢复主节点------比较繁琐,实际生产不用
如图是几个客户端连接主节点进行修改数据操作,主节点有两个从节点进行同步数据。

主节点因为某些原因挂了
1.检查主节点是否能进行抢救
2.主节点无法抢救,需要挑一个从节点设置成为新的主节点
- 把选中的从节点,通过
slaveof no one,晋升成主节点。 - 其他从节点,
slaveof {masterHost} {masterPort},连上新的主节点。 - 告知客户端(修改客户端配置),让客户端能够连接新的主节点,用来完成修改数据的操作。

哨兵自动恢复主节点故障

从节点挂了,没事,同步主节点数据即可。
如果主节点挂了,需要哨兵节点发挥作用。
- 哨兵节点中,就会推举出一个leader,由这个leader负责从现有的从节点中,挑选一个作为新的主节点。
- 挑选出新节点之后,哨兵节点,就会自动控制该被选中的节点,执行slaveof no one,并且控制其他从节点,修改slaveof到新的节点上。
- 哨兵节点会自动通知客户端,告知新的主节点是谁,并且后续客户端再进行写操作,就会针对新的主节点进行操作。
注意,redis哨兵节点只有一个也是可以的,但是会出现很多问题
- 这个单一的哨兵节点它自身是容易出问题的,万一这个哨兵节点挂了,后续redis节点也挂了,就无法进行自动恢复的过程了。
- 出现误判的概率比较高。
redis哨兵核心功能:
- 监控
- 自动故障转移
- 通知
Redis Sentinel 的部署
以下为例,3个哨兵节点,1个主节点,2个从节点进行部署。

这6个节点,是要在6个不同的服务器主机上。然而我们只有一台云服务器。
虚拟机,通过软件,在一个电脑上模拟出另外的一些硬件(构造出了另一台虚拟电脑),也就是说,用一台主机模拟出多台主机。
但是虚拟机有个很大的问题:比较吃配置。
docker可以认为是一个轻量级的虚拟机,起到了虚拟机的隔离环境的效果,但是有没有吃很多硬件资源。
使用docker搭建环境
使用docker获取到redis镜像
docker中的镜像 和容器 ,类似于可执行程序 和进程 的关系。
镜像:可以自己构建,也可以直接使用别人构建好的
docker hub:包含了很多其他大佬们构建好的镜像。
例子--类比
git pull 使用git从中央仓库拉取代码
docker pull 使用docker从中央仓库(默认是docker hub)拉取代码
例子:
bash
docker pull redis:5.0.9
拉取到的镜像,里面包含一个精简的Linux操作系统,并且上面会安装redis。
只要直接基于这个镜像创建一个容器跑起来,redis服务器就搭建好了。
bash
root@VM-8-5-ubuntu:~# docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
redis:5.0.9 2a9865e55c37 144MB 35.9MB
接下来进行基于docker来搭建redis哨兵环境
docker-compose来进行容器编排。
通过一个配置文件,把具体要创建哪些容器及每个容器运行的各种参数,描述清楚。后续通过一个简单的命令,就能批量的启动/停止这些容器。
使用 yml 这样的格式来作为配置文件。(java spring也是使用yml作为配置文件)
配置文件格式介绍
经典配置文件格式:xml
例子:
bash
<student>
<id>1</id>
<name>张三</name>
</student>
与html中的标签很像,但是有区别:
- html中的标签,都是标准规定的
- xml中的标签,都是自定义的
后来,出现 json 格式 配置文件的方式
例子:
bash
{
id:1,
name:'张三'
}
yml格式 与json格式有相似之处
例子:
bash
student:
id:1
name:"张三"
1.创建三个容器,作为redis的数据节点(一个主,两个从)------yml格式
2.创建三个容器,作为redis的哨兵节点------yml格式
其实也是可以用一个yml文件,直接启动6个容器
如果把这6个容器同时启动,可能是哨兵节点先启动,数据节点后启动,哨兵节点可能认为数据节点挂了,影响观察。
建立如下目录及文件
bash
redis/
├── redis-data/
│ └── docker-compose.yml # Redis 数据节点/主从相关
└── redis-sentinel/
├── docker-compose.yml # Redis 哨兵(Sentinel)相关
├── sentinel1.conf #哨兵节点的配置文件
├── sentinel2.conf #哨兵节点会在运行过程中,对配置文件进行修改,因此,不能拿一个配置文件给三个容器映射使用。
└── sentinel3.conf
注意:配置文件名都为 docker-compose.yml
哨兵节点会在运行过程中,对配置文件进行修改,因此,不能拿一个配置文件给三个容器映射使用,所以创建三个哨兵节点的 redis 配置文件
数据节点配置文件
bash
version: '3.7'
services:
master: #名字自己定
image: 'redis:5.0.9' #镜像
container_name: redis-master #容器名字
restart: always #是否自动重启
command: redis-server --appendonly yes #开启AOF
ports: #端口映射
- 6379:6379
slave1:
image: 'redis:5.0.9'
container_name: redis-slave1
restart: always
command: redis-server --appendonly yes --slaveof redis-master 6379
ports:
- 6380:6379
slave2:
image: 'redis:5.0.9'
container_name: redis-slave2
restart: always
command: redis-server --appendonly yes --slaveof redis-master 6379
ports:
- 6381:6379
配置文件写好后,用docker-compose后台创建容器
bash
root@VM-8-5-ubuntu:~/redis/redis-data# docker-compose up -d #-d以后台运行
Creating network "redis-data_default" with the default driver
Creating redis-master ... done
Creating redis-slave1 ... done
Creating redis-slave2 ... done
root@VM-8-5-ubuntu:~/redis/redis-data# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8030fc65f18e redis:5.0.9 "docker-entrypoint.s..." About a minute ago Up About a minute 0.0.0.0:6380->6379/tcp, [::]:6380->6379/tcp redis-slave1
99244e2992f4 redis:5.0.9 "docker-entrypoint.s..." About a minute ago Up About a minute 0.0.0.0:6381->6379/tcp, [::]:6381->6379/tcp redis-slave2
9889de081326 redis:5.0.9 "docker-entrypoint.s..." About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp redis-master
root@VM-8-5-ubuntu:~/redis/redis-data# netstat -anp | grep -E '6379|6380|6381'
tcp 0 0 0.0.0.0:6381 0.0.0.0:* LISTEN 1633452/docker-prox
tcp 0 0 0.0.0.0:6380 0.0.0.0:* LISTEN 1633420/docker-prox
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 1633398/docker-prox
tcp6 0 0 :::6381 :::* LISTEN 1633461/docker-prox
tcp6 0 0 :::6380 :::* LISTEN 1633431/docker-prox
tcp6 0 0 :::6379 :::* LISTEN 1633403/docker-prox
关于端口映射
在docker容器中,容器中的端口号 和外面宿主机的端口号 是两个体系。
将容器内部的端口号映射成宿主机的端口号 ,这样容器外就可以通过宿主机的端口号访问容器里的端口号。
例子:
bash
ports:
- 6380:6379 #宿主机端口号:容器内的端口号

三个容器,每个容器都可以视为不同主机,容器1,2,3中端口号 6379 被映射成不同的宿主机端口,所以不会有冲突。
站在宿主机角度,访问上面几个端口时,不必区分背后映射,直接使用宿主机端口使用redis服务即可。
哨兵节点配置文件
哨兵节点docker配置文件
bash
version: '3.7'
services:
sentinel1:
image: 'redis:5.0.9'
container_name: redis-sentinel-1
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel1.conf:/etc/redis/sentinel.conf
ports:
- 26379:26379
sentinel2:
image: 'redis:5.0.9'
container_name: redis-sentinel-2
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel2.conf:/etc/redis/sentinel.conf
ports:
- 26380:26379
sentinel3:
image: 'redis:5.0.9'
container_name: redis-sentinel-3
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel3.conf:/etc/redis/sentinel.conf
ports:
- 26381:26379
networks:
default:
external:
name: redis-data_default
关于这一段
bash
networks:
default:
external:
name: redis-data_default
docker-compose 一下启动了N个容器,此时N个容器都处于同一个局域网中,这N个容器之间可以相互访问。
三个redis-server节点是一个局域网。
三个哨兵节点是另一个局域网。
默认情况下,这两个网络是不互通的。
docker network ls 列出当前docker中的局域网
bash
NETWORK ID NAME DRIVER SCOPE
2c8d320d2bc0 bridge bridge local
786471e1d7df host host local
fe6493a8463b none null local
cffc41c340d5 redis-data_default bridge local #两个不同的局域网
8ddd3960442b redis-sentinel_default bridge local
解决方案 :可以使用docker-compose把此处的两组服务给放到同一个局域网中。
先启动三个数据节点------相当于创建了第一个局域网,再启动三个哨兵节点,加入如上代码,直接让这三个节点加入到上面的局域网中,而不是新建的局域网。
哨兵节点redis具体的配置文件
bash
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2 #告诉哨兵节点监控哪个服务器
sentinel down-after-milliseconds redis-master 1000
关于sentinel monitor
bash
sentinel monitor 主节点名 主节点ip 主节点端⼝ 法定票数
- 主节点名, 这个是哨兵内部自己起的名字。
- 主节点 ip, 部署 redis-master 的设备 ip . 此处由于是使⽤ docker, 可以直接写 docker 的容器名, 会被⾃动 DNS 成对应的容器 ip。
- 主节点端⼝。
- 法定票数, 哨兵需要判定主节点是否挂了. 但是有的时候可能因为特殊情况, ⽐如主节点仍然工作正常, 但是哨兵节点自己网络出问题了, ⽆法访问到主节点了. 此时就可能会使该哨兵节点认为主节点下线, 出现误判 . 使用投票的方式来确定主节点是否真的挂了是更稳妥的做法. 需要多个哨兵都认为主节点挂了, 票数 >= 法定票数 之后, 才会真的认为主节点是挂了。
关于 sentinel down-after-milliseconds
心跳包超时时间主节点和哨兵之间通过心跳包来进⾏沟通. 如果心跳包在指定的时间内还没回来, 就视为是节点出现故障.
使用docker-compose up启动哨兵节点
bash
root@VM-8-5-ubuntu:~/redis/redis-sentinel# docker-compose up -d
Creating redis-sentinel-3 ... done
Creating redis-sentinel-1 ... done
Creating redis-sentinel-2 ... done
运行后哨兵节点对配置文件进行修改,如下是修改后的哨兵节点的redis配置文件
bash
bind 0.0.0.0
port 26379
sentinel myid 79c2d52ba3386c95343c407d63be172fbd9d3210
sentinel deny-scripts-reconfig yes
# Generated by CONFIG REWRITE
dir "/data"
sentinel monitor redis-master 172.18.0.2 6379 2
sentinel down-after-milliseconds redis-master 1000
sentinel config-epoch redis-master 0
sentinel leader-epoch redis-master 0
sentinel known-replica redis-master 172.18.0.4 6379
sentinel known-replica redis-master 172.18.0.3 6379
sentinel known-sentinel redis-master 172.18.0.5 26379 a212ee748e50f4043cec7cb9938a0d695514e9f5
sentinel known-sentinel redis-master 172.18.0.7 26379 9e3d626a804f64abc87d3ef031b3872e716e000f
sentinel current-epoch 0
至此所有的容器都运行起来
bash
root@VM-8-5-ubuntu:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
287209ae70a0 redis:5.0.9 "docker-entrypoint.s..." 5 hours ago Up 5 hours 6379/tcp, 0.0.0.0:26379->26379/tcp, [::]:26379->26379/tcp redis-sentinel-1
d0dfbaa1b02f redis:5.0.9 "docker-entrypoint.s..." 5 hours ago Up 5 hours 6379/tcp, 0.0.0.0:26380->26379/tcp, [::]:26380->26379/tcp redis-sentinel-2
f887da76f806 redis:5.0.9 "docker-entrypoint.s..." 5 hours ago Up 5 hours 6379/tcp, 0.0.0.0:26381->26379/tcp, [::]:26381->26379/tcp redis-sentinel-3
8030fc65f18e redis:5.0.9 "docker-entrypoint.s..." 6 hours ago Up 6 hours 0.0.0.0:6380->6379/tcp, [::]:6380->6379/tcp redis-slave1
99244e2992f4 redis:5.0.9 "docker-entrypoint.s..." 6 hours ago Up 6 hours 0.0.0.0:6381->6379/tcp, [::]:6381->6379/tcp redis-slave2
9889de081326 redis:5.0.9 "docker-entrypoint.s..." 6 hours ago Up 6 hours 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp redis-master
选举原理------哨兵的检测恢复机制
一、主观下线
当 redis-master 宕机, 此时 redis-master 和三个哨兵之间的⼼跳包就没有了。
此时, 站在三个哨兵的⻆度来看, redis-master 出现严重故障。因此三个哨兵均会把 redis-master 判定为主观下线 (SDown)。
二、客观下线
此时, 哨兵 sentenal1, sentenal2, sentenal3 均会对主节点故障这件事情进⾏投票。当故障得票数 >= 配置的法定票数之后,此时意味着 redis-master 故障这个事情被做实了。此时触发客观下线 (ODown)。
哨兵redis配置文件
bash
sentinel monitor redis-master 172.22.0.4 6379 2 #这个地方配置2,即为法定票数
三、选举出哨兵的 leader
接下来需要哨兵把剩余的 slave 中挑选出⼀个新的 master。这个工作不需要所有的哨兵都参与。只需要选出个代表 (称为 leader), 由 leader 负责进行 slave 升级到 master 的提拔过程。
这个选举的过程涉及到 Raft 算法 。简而⾔之, Raft 算法的核心就是 "先下手为强" ------谁率先发出了拉票请求 , 谁就有更大的概率成为 leader,换言之,就是看哪个节点的网络延迟小。
- 发起选举 :所有哨兵(S1/S2/S3)初始为跟随者,检测到无 Leader 后,均转为候选人,自增选举轮次,给自己投 1 票(看谁速度快),再向其他两节点发拉票请求。
- 处理投票:收到请求的节点,若未投过票且请求轮次有效,就投给该候选人(一人一票),否则拒绝。
- 判定 Leader:总节点 3 个,得票≥2 票(过半)的候选人直接当选 Leader。
如下是
docker-compose logs日志下的信息1号哨兵自投一票
bashredis-sentinel-1 | 1:X 18 Feb 2026 11:18:59.605 # +vote-for-leader 79c2d52ba3386c95343c407d63be172fbd9d3210 12号哨兵,投给1号哨兵
bashredis-sentinel-1 | 1:X 18 Feb 2026 11:18:59.612 # 9e3d626a804f64abc87d3ef031b3872e716e000f voted for 79c2d52ba3386c95343c407d63be172fbd9d3210 13号哨兵,投给1号哨兵
bashredis-sentinel-1 | 1:X 18 Feb 2026 11:18:59.612 # a212ee748e50f4043cec7cb9938a0d695514e9f5 voted for 79c2d52ba3386c95343c407d63be172fbd9d3210 1
简而言之:
每个哨兵手里有一票。
当哨兵1第一个发现当前是客观下线后,就立即给自己投一票,并且告诉了2,3,我来负责这个事情。
2,3反应慢了一会,发现是客观下线,观察到1乐意负责这个事情,立即投了赞成票。
如果总的票数超过哨兵总数一半,选举就完成了(把哨兵个数设置为奇数个节点,就是为了方便投票)。
四、leader 挑选出合适的 slave 成为新的 master
挑选规则:
- 比较优先级。优先级高(数值小)的上位. 优先级是配置文件中的配置项。( slave-priority 或者 replica-priority )
- 比较 replication offset 谁复制的数据多, 高的上位。(数值越大,说明从节点和主节点的数据越接近)
- ⽐较 run id , 谁的 id 小, 谁上位。(就是随机挑)
当某个 slave 节点被指定为 master 之后,
- leader 指定该节点执行 slave no one , 成为 master
- leader 指定剩余的 slave 节点, 都依附于这个新 master
实验
手动把主节点挂掉
bash
root@VM-8-5-ubuntu:~# docker stop redis-master
redis-master
root@VM-8-5-ubuntu:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
287209ae70a0 redis:5.0.9 "docker-entrypoint.s..." 5 hours ago Up 5 hours 6379/tcp, 0.0.0.0:26379->26379/tcp, [::]:26379->26379/tcp redis-sentinel-1
d0dfbaa1b02f redis:5.0.9 "docker-entrypoint.s..." 5 hours ago Up 5 hours 6379/tcp, 0.0.0.0:26380->26379/tcp, [::]:26380->26379/tcp redis-sentinel-2
f887da76f806 redis:5.0.9 "docker-entrypoint.s..." 5 hours ago Up 5 hours 6379/tcp, 0.0.0.0:26381->26379/tcp, [::]:26381->26379/tcp redis-sentinel-3
8030fc65f18e redis:5.0.9 "docker-entrypoint.s..." 7 hours ago Up 7 hours 0.0.0.0:6380->6379/tcp, [::]:6380->6379/tcp redis-slave1
99244e2992f4 redis:5.0.9 "docker-entrypoint.s..." 7 hours ago Up 7 hours 0.0.0.0:6381->6379/tcp, [::]:6381->6379/tcp redis-slave2
9889de081326 redis:5.0.9 "docker-entrypoint.s..." 7 hours ago Exited (0) 3 seconds ago redis-master
bash
root@VM-8-5-ubuntu:~/redis/redis-sentinel# docker-compose logs
Attaching to redis-sentinel-1, redis-sentinel-2, redis-sentinel-3
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.224 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.224 # Redis version=5.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.224 # Configuration loaded
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.225 * Increased maximum number of open files to 10032 (it was originally set to 1024).
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.226 * Running mode=sentinel, port=26379.
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.231 # Sentinel ID is a212ee748e50f4043cec7cb9938a0d695514e9f5
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.231 # +monitor master redis-master 172.18.0.2 6379 quorum 2
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.233 * +slave slave 172.18.0.3:6379 172.18.0.3 6379 @ redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:19.236 * +slave slave 172.18.0.4:6379 172.18.0.4 6379 @ redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:21.296 * +sentinel sentinel 9e3d626a804f64abc87d3ef031b3872e716e000f 172.18.0.7 26379 @ redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 06:02:21.329 * +sentinel sentinel 79c2d52ba3386c95343c407d63be172fbd9d3210 172.18.0.6 26379 @ redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 06:05:20.013 * +fix-slave-config slave 172.18.0.3:6379 172.18.0.3 6379 @ redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 06:05:20.013 * +fix-slave-config slave 172.18.0.4:6379 172.18.0.4 6379 @ redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 11:18:59.497 # +sdown master redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 11:18:59.609 # +new-epoch 1
redis-sentinel-2 | 1:X 18 Feb 2026 11:18:59.612 # +vote-for-leader 79c2d52ba3386c95343c407d63be172fbd9d3210 1
redis-sentinel-2 | 1:X 18 Feb 2026 11:19:00.673 # +odown master redis-master 172.18.0.2 6379 #quorum 3/2
redis-sentinel-2 | 1:X 18 Feb 2026 11:19:00.673 # Next failover delay: I will not start a failover before Wed Feb 18 11:25:00 2026
redis-sentinel-2 | 1:X 18 Feb 2026 11:19:00.733 # +config-update-from sentinel 79c2d52ba3386c95343c407d63be172fbd9d3210 172.18.0.6 26379 @ redis-master 172.18.0.2 6379
redis-sentinel-2 | 1:X 18 Feb 2026 11:19:00.733 # +switch-master redis-master 172.18.0.2 6379 172.18.0.3 6379
redis-sentinel-2 | 1:X 18 Feb 2026 11:19:00.733 * +slave slave 172.18.0.4:6379 172.18.0.4 6379 @ redis-master 172.18.0.3 6379
redis-sentinel-2 | 1:X 18 Feb 2026 11:19:00.733 * +slave slave 172.18.0.2:6379 172.18.0.2 6379 @ redis-master 172.18.0.3 6379
redis-sentinel-2 | 1:X 18 Feb 2026 11:19:01.758 # +sdown slave 172.18.0.2:6379 172.18.0.2 6379 @ redis-master 172.18.0.3 6379

sdown主观下线 :本哨兵节点,认为该主节点挂了
odown客观下线:其他哨兵节点都认为该节点挂了,达成一致(投票数超过法定票数)