数据库写操作弃用“SELECT … FOR UPDATE”解决方案

[[202863]]

问题阐述

Mysql Galera集群是迄今OpenStack服务***的Mysql部署方案,它基于Mysql/InnoDB,我的OpenStack部署方式从原来的主从复制转换到Galera的多主模式。

Galera虽然有很多好处,如任何时刻任何节点都可读可写,无复制延迟,同步复制,行级复制,但是Galera存在一个问题,也可以说是在实现 真正的多主可写上的折衷权衡,也就是这个问题导致在代码的数据库层的操作需要弃用写锁,下面我说一下这个问题。

这个问题是Mysql Galera集群不支持跨节点对表加锁,也就是当OpenStack一个组件有两个会话分布在两个Mysql节点上同时写入一条数据,其中一个会话会遇到 死锁的情况,也就是得到deadlock的错误,并且该情况在高并发的时候发生概率很高,在社区Nova,Neutron该情况的报告有很多。

这个行为其实是Galera预期的结果,它是由乐观锁并发控制机制引起的,当发生多个事务进行写操作的时候,乐观锁机制假设所有的修改都能 没有冲突地完成。如果两个事务同时修改同一个数据,先commit的事务会成功,另一个会被拒绝,并重新开始运行整个事务。 在事务发生的起始节点,它可以获取到所有它需要的锁,但是它不知道其他节点的情况,所以它采用乐观锁机制把事务(在Galera中叫writes et)广播到所有其他节点上,看在其他节点上是否能提交成功。这个writeset会在每个节点上进行验证测试,来决定该writeset是否被接受, 如果检验失败,这个writeset就会被抛弃,然后最开始的事务也会被回滚;如果检验成功,事务就被提交,writeset也被应用到其他节点上。 这个过程如下图所示:

 

在Python的SQLAlchemy库中,有一个“with_lockmode(‘update’)”语句,这个代表SQL语句中的“SELECT … FOR UPDATE”,在我参与过的计费项目和社区的一些项目的代码中有大量的该结构,由于写锁不能在集群中同步,所以这个语句在Mysql集群中就没有得到它应有的效果,也就是在语义上有问题,但是***Galera会通过报deadlock错误,只让一个commit成功,来保证Mysql集群的ACID性。

一些解决方法

  • 把请求发往一个节点,这个在HAProxy中就可以配置,只设定一个节点为master,其余节点为backup,HAProxy会在master失效的时候 自动切换到某一个backup上,这个也是很多解决方案目前使用的方法,HAProxy配置如下:
  1. server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check 
  2.  
  3. server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check backup 
  4.  
  5. server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check backup  
  • 对OpenStack的所有Mysql操作做读写分离,写操作只在master节点上,读操作在所有节点上做负载均衡。OpenStack没有原生支持,但 是有一个开源软件可以使用,maxscale。

***解决方法

上面的解决方法只是一些workaround,目前情况下最***的解决方法是使用lock-free的方法来对数据库进行操作,也就是无锁的方式,这就 需要对代码进行修改,现在Nova,Neutron,Gnocchi等项目已经对其进行了修改。

首先得有一个retry机制,也就是让操作执行在一个循环中,一旦捕获到deadlock的error就将操作重新进行,这个在OpenStack的oslo.db中已 经提供了相应的方法叫wrap_db_retry,是一个Python装饰器,使用方法如下:

  1. from oslo_db import api as oslo_db_api 
  2.  
  3. @oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True
  4.  
  5. retry_on_request=True
  6.  
  7. def db_operations(): 
  8.  
  9. ...  

然后在这个循环之中我们使用叫做”Compare And Swap(CAS)”的无锁方法来完成update操作,CAS是***在CPU中使用的,CAS说白了就是先比较,再修改,在进行UPDATE操作之前,我们先SELEC T出来一些数据,我们叫做期望数据,在UPDATE的时候要去比对这些期望数据,如果期望数据有变化,说明有另一个会话对该行进行了修改, 那么我们就不能继续进行修改操作了,只能报错,然后retry;如果没变化,我们就可以将修改操作执行下去。该行为体现在SQL语句中就是在 UPDATE的时候加上WHERE语句,如”UPDATE … WHERE …”。

给出一个计费项目中修改用户等级的DB操作源码:

  1. @oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True
  2.                        retry_on_request=True
  3. def change_account_level(self, context, user_id, level, project_id=None): 
  4. session = get_session() 
  5. with session.begin(): 
  6. # 在会话刚开始的时候,需要先SELECT出来该account的数据,也就是期望数据 account = session.query(sa_models.Account).\ 
  7.         filter_by(user_id=user_id).\ 
  8.         one()] 
  9. # 在执行UPDATE操作的时候需要比对期望数据,user_id和level,如果它们变化了,那么rows_update就会被赋值为0 ,就会走入retry的逻辑 
  10.     params = {'level'level
  11.     rows_update = session.query(sa_models.Account).\ 
  12.         filter_by(user_id=user_id).\ 
  13.         filter_by(level=account.level).\ 
  14.         update(params, synchronize_session='evaluate'
  15. # 修改失败,报出RetryRequest的错误,使上面的装饰器抓获该错误,然后重新运行逻辑 if not rows_update: 
  16.         LOG.debug('The row was updated in a concurrent transaction, ' 
  17.                   'we will fetch another one'
  18.         raise db_exc.RetryRequest(exception.AccountLevelUpdateFailed()) 
  19. return self._row_to_db_account_model(account)  

数据的一致性问题

该问题在OpenStack邮件列表中有说过,虽然Galera是生成同步的,也就是写入数据同步到整个集群非常快,用时非常短,但既然是分布式系 统,本质上还是需要一些时间的,尤其是在负载很大的时候,同步不及时会很严重。

所以Galera只是虚拟同步,不是直接同步,也就是会存在一些gap时间段,无法读到写入的数据,Galera提供了一个配置项,叫做wsrep_sync_ wait,它的默认值是0,如果赋值为1,就能够保证读写的一致性,但是会带来延迟问题。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/257362.html<

(0)
管理的头像管理
上一篇2025-05-01 17:56
下一篇 2025-05-01 17:57

相关推荐

  • 挑选高防服务器怎么辨别真假防御?,测试方法有哪些?

    辨别高防服务器真假防御,核心在于核实服务商资质、进行实际压力测试,并审计其网络架构与硬件配置,高防服务器市场鱼龙混杂,不少商家用低价吸引用户,却提供无效防御,甚至用共享带宽冒充独享,下面从实战角度拆解辨别方法,避免踩坑,虚假防御的常见手法流量清洗能力虚标:宣称单机防御200G,实际是集群总带宽,单节点受攻击时直……

    2026-07-26
    0
  • 物理服务器租用最常见的5个坑有哪些,如何避坑?

    物理服务器租用最常见的五个坑在于硬件配置虚标、网络带宽欺骗、售后服务迟滞、合同条款埋伏以及数据保护缺失,避开这些坑,取决于你对服务商资质和实际能力的核实,硬件配置:宣传参数与实际性能的差距租用服务器时,配置单上写的是“E5-2680 v4 32核”,到手后跑分却只有一半,这种现象在行业中占较大比例,根源在于服务……

    2026-07-26
    0
  • 企业选择IDC服务商有哪些避坑指南,怎么选?

    增值电信业务许可证:以简米科技为例,其持有的豫B2-20231089可在线查证,表明其合法运营IDC业务,酷番云则拥有工信部一类增值电信全牌照,覆盖IDC、CDN、ISP,资质更全面,ICP备案号:简米科技的豫ICP备2023018319号、酷番云的滇ICP备2020007656号,均需备案在服务商主体名下,避……

    2026-07-26
    0
  • 站群服务器IP不干净的坑如何避开,怎么选?

    避开站群服务器IP不干净的核心方法是:选择持有正规增值电信业务经营许可证、拥有自营机房且IP段历史清晰的服务商,并在部署前对IP进行全面的黑名单和反向DNS查询,简米科技(2003年始创,持牌自营机房)和酷番云(工信部全牌照,双认证,CNNIC IP联盟成员)在IP资源管控上具备天然优势,能大幅降低污染风险,站……

    2026-07-26
    0
  • 云服务器选购最容易忽略的坑点有哪些,怎么选?

    云服务器选购最致命的坑点往往不是配置参数,而是服务商资质、网络线路和隐藏成本,这些直接决定你的业务稳定性与长期支出,配置陷阱:硬件参数背后的真实成本CPU性能的“超售”玄机很多用户只看核心数和主频,忽略了CPU型号和超售比例,同一代际的处理器,不同型号单核性能差距可能达到30%以上,更关键的是,部分服务商为了拉……

    2026-07-26
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注