各位观众老爷,大家好!我是今天的主讲人,江湖人称“数据库小钢炮”,今天咱来聊聊MySQL的Semi-Sync,也就是半同步复制。这玩意儿在网络延迟下会发生什么,以及我们如何在性能和RPO(Recovery Point Objective,恢复点目标)之间找到平衡点,保证咱的数据不丢,同时也不能慢到让用户想砸电脑。
开场白:为啥要有Semi-Sync?
在说Semi-Sync之前,咱们先回顾一下MySQL的传统复制模式:异步复制。异步复制就像是你给朋友发微信,你发完就完事了,至于对方有没有收到,什么时候收到,你压根不知道。好处是快,坏处是万一主库挂了,还没同步过去的数据就丢了。这就好比你辛辛苦苦码了一天的代码,没保存,电脑突然蓝屏了……简直是程序员的噩梦!
为了解决这个问题,Semi-Sync应运而生。它的核心思想是:主库在提交事务之前,必须至少收到一个从库的确认,证明这个事务已经成功写入了从库的relay log。这样,即使主库挂了,至少有一个从库拥有最新的数据,可以提升数据的安全性。
Semi-Sync原理:握手协议
Semi-Sync的原理其实不复杂,简单来说,就是一个握手协议。
- 主库提交事务: 主库执行完一个事务,准备提交。
- 发送binlog: 主库将binlog发送给所有配置了Semi-Sync的从库。
- 从库写入relay log: 从库收到binlog后,将其写入relay log。
- 从库发送ACK: 从库成功写入relay log后,向主库发送一个ACK(acknowledgement,确认)。
- 主库提交事务: 主库收到至少一个从库的ACK后,才会提交事务。
你可以把这个过程想象成你给女朋友买了个包,你付款后,店家得确认收到钱,才会把包发给你。
Semi-Sync配置:简单几步搞定
配置Semi-Sync也很简单,只需要在主库和从库上分别进行一些设置即可。
主库配置:
-- 安装Semi-Sync插件
INSTALL PLUGIN rpl_semi_sync_master SONAME 'rpl_semi_sync_master.so';
-- 启用Semi-Sync
SET GLOBAL rpl_semi_sync_master_enabled = ON;
-- 设置超时时间,单位毫秒
SET GLOBAL rpl_semi_sync_master_timeout = 10000; -- 10秒
-- 查看状态
SHOW GLOBAL VARIABLES LIKE 'rpl_semi_sync_master%';
SHOW GLOBAL STATUS LIKE 'rpl_semi_sync_master%';
从库配置:
-- 安装Semi-Sync插件
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'rpl_semi_sync_slave.so';
-- 启用Semi-Sync
SET GLOBAL rpl_semi_sync_slave_enabled = ON;
-- 设置从库类型为半同步从库
SET GLOBAL rpl_semi_sync_slave_wait_point = AFTER_SYNC; -- 5.7 之后的版本默认是AFTER_SYNC
-- 查看状态
SHOW GLOBAL VARIABLES LIKE 'rpl_semi_sync_slave%';
SHOW GLOBAL STATUS LIKE 'rpl_semi_sync_slave%';
连接主库:
在从库上执行CHANGE MASTER TO
命令,连接到主库。注意要指定MASTER_HOST
、MASTER_USER
、MASTER_PASSWORD
等参数。
CHANGE MASTER TO
MASTER_HOST='your_master_host',
MASTER_USER='your_replication_user',
MASTER_PASSWORD='your_replication_password',
MASTER_LOG_FILE='mysql-bin.000001', -- 初始binlog文件名
MASTER_LOG_POS=4, -- 初始binlog位置
MASTER_CONNECT_RETRY=10,
MASTER_HEARTBEAT_PERIOD=1,
GET_MASTER_PUBLIC_KEY=1, -- 如果开启了SSL
REQUIRE_ROW_FORMAT = 1, -- 推荐使用ROW格式
MASTER_SSL=0, -- 如果开启了SSL
MASTER_USE_GTID = AUTOMATIC; -- 如果使用了GTID
配置完成后,启动从库的复制线程:
START SLAVE;
网络延迟的影响:一场“龟兔赛跑”
Semi-Sync虽然提高了数据的安全性,但也带来了新的问题:网络延迟。如果主库和从库之间的网络延迟很高,那么主库就需要等待更长的时间才能收到从库的ACK,这会导致主库的事务提交速度变慢,从而影响整个系统的性能。
你可以把这个过程想象成一场“龟兔赛跑”。兔子(异步复制)跑得飞快,但容易出错;乌龟(Semi-Sync)虽然跑得慢,但更稳。如果赛道(网络)很短,那么兔子肯定赢;但如果赛道很长,那么乌龟可能更有优势,因为它不容易出错。
网络延迟测试:模拟真实场景
为了更好地理解网络延迟的影响,我们可以进行一些测试。我们可以使用ping
命令来测试主库和从库之间的网络延迟。
ping your_slave_host
如果ping
的结果显示延迟很高,那么就需要考虑优化网络,或者调整Semi-Sync的参数。
Semi-Sync参数调优:找到最佳平衡点
Semi-Sync提供了几个参数,可以用来调整其行为,以适应不同的网络环境。
rpl_semi_sync_master_timeout
: 主库等待从库ACK的超时时间,单位毫秒。如果超过这个时间没有收到ACK,主库会切换回异步复制模式。默认值是10000毫秒(10秒)。rpl_semi_sync_master_wait_for_slave_count
: 主库需要等待多少个从库的ACK。默认值是1。rpl_semi_sync_master_wait_point
: 主库等待从库ACK的位置。有两个选项:AFTER_SYNC
和AFTER_COMMIT
。AFTER_SYNC
表示主库在从库写入relay log后等待ACK;AFTER_COMMIT
表示主库在从库提交事务后等待ACK。
如何权衡RPO和性能?
RPO和性能是两个相互制约的目标。RPO越小,数据的安全性越高,但性能可能会越差;反之,RPO越大,性能可能会越好,但数据的安全性会降低。
那么,如何在RPO和性能之间找到最佳平衡点呢?
- 评估业务需求: 首先要评估业务对数据安全性的要求。如果业务对数据丢失的容忍度很低,那么就需要设置较小的RPO,并牺牲一定的性能。如果业务对数据丢失的容忍度较高,那么可以设置较大的RPO,以提高性能。
- 测试网络环境: 测试主库和从库之间的网络延迟,并根据测试结果调整Semi-Sync的参数。如果网络延迟很高,可以适当增加
rpl_semi_sync_master_timeout
的值,或者减少rpl_semi_sync_master_wait_for_slave_count
的值。 - 监控系统性能: 监控系统的性能指标,如事务提交速度、QPS(Queries Per Second,每秒查询数)等,并根据监控结果调整Semi-Sync的参数。如果发现性能下降明显,可以适当调整参数,或者考虑使用其他复制模式。
Semi-Sync的进化:增强半同步(Enhanced Semi-Sync)
MySQL 5.7 之后,引入了增强半同步复制,它在原有的 Semi-Sync 的基础上做了改进,主要体现在以下几个方面:
- Lossless Failover(无损故障切换): 增强半同步复制确保在故障切换时,至少有一个从库包含所有已提交的事务,从而实现无损故障切换。这极大地提高了数据安全性。
- Priority-Based ACK: 增强半同步复制可以根据从库的优先级来选择 ACK 的来源。这意味着你可以指定某些从库作为“优先从库”,只有这些从库返回 ACK 后,主库才会提交事务。
代码示例:模拟Semi-Sync行为
虽然不能直接模拟MySQL的底层实现,但我们可以用Python代码来模拟Semi-Sync的基本行为,帮助理解其原理。
import time
import threading
class Master:
def __init__(self, slaves):
self.slaves = slaves
self.data = []
self.lock = threading.Lock()
def write_data(self, data):
with self.lock:
print(f"Master: Writing data: {data}")
self.data.append(data)
# 模拟发送binlog给slave
acks = []
threads = []
for slave in self.slaves:
t = threading.Thread(target=slave.receive_data, args=(data,acks))
threads.append(t)
t.start()
# 等待至少一个slave的ACK
timeout = 5 # 秒
start_time = time.time()
while len(acks) == 0 and time.time() - start_time < timeout:
time.sleep(0.1)
if len(acks) > 0:
print("Master: Received ACK from slaves, committing transaction.")
else:
print("Master: Timeout waiting for ACK, switching to asynchronous mode.")
# 在真实场景中,这里可以切换到异步模式,但这里简化处理
for t in threads:
t.join()
class Slave:
def __init__(self, name, delay=0):
self.name = name
self.relay_log = []
self.delay = delay # 模拟网络延迟
self.lock = threading.Lock()
def receive_data(self, data, acks):
time.sleep(self.delay) # 模拟网络延迟
with self.lock:
print(f"Slave {self.name}: Received data: {data}, writing to relay log.")
self.relay_log.append(data)
acks.append(self)
print(f"Slave {self.name}: Sent ACK to master.")
# 创建master和slaves
slave1 = Slave("Slave1", delay=1) # 模拟1秒延迟
slave2 = Slave("Slave2", delay=2) # 模拟2秒延迟
master = Master([slave1, slave2])
# 模拟master写入数据
master.write_data("Data 1")
master.write_data("Data 2")
这段代码创建了一个简单的Master和Slave类,模拟了Semi-Sync的基本流程。Slave可以设置不同的延迟,模拟不同的网络环境。
总结:选择合适的复制模式
Semi-Sync是一种折衷方案,它在数据安全性和性能之间找到了一个平衡点。但是,它并不是万能的。在选择复制模式时,需要根据具体的业务需求和网络环境进行评估。
- 异步复制: 适用于对数据安全性要求不高,但对性能要求很高的场景。
- Semi-Sync: 适用于对数据安全性有一定要求,但又不能完全牺牲性能的场景。
- 增强半同步: 适用于对数据安全性要求极高,需要无损故障切换的场景。
- 组复制(Group Replication): 适用于需要强一致性,高可用性的场景,牺牲一定性能。
表格:各种复制模式的对比
复制模式 | 数据安全性 | 性能 | 适用场景 |
---|---|---|---|
异步复制 | 低 | 高 | 对数据安全性要求不高,但对性能要求很高的场景 |
Semi-Sync | 中 | 中 | 对数据安全性有一定要求,但又不能完全牺牲性能的场景 |
增强半同步 | 高 | 中偏低 | 对数据安全性要求极高,需要无损故障切换的场景 |
组复制(GR) | 极高 | 低 | 需要强一致性,高可用性的场景,牺牲一定性能。 |
结束语:没有银弹,只有合适的方案
数据库的世界里没有银弹,只有合适的方案。选择合适的复制模式,需要根据具体的业务需求和网络环境进行评估,并不断进行测试和调优。希望今天的讲座能帮助大家更好地理解Semi-Sync,并在实际应用中做出明智的选择。
感谢各位的观看!下次再见!