V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
huadi
V2EX  ›  程序员

读写分离到底是大招还是人云亦云 ?真正做过的人有多少?

  •  3
     
  •   huadi · 2019-04-19 10:16:12 +08:00 · 7505 次点击
    这是一个创建于 1805 天前的主题,其中的信息可能已经有所发展或是发生改变。
    大部分所谓架构文章都会先介绍分库分表,再扛不住就读写分离对吧。

    但没人往下讲细节了。
    如果是异步同步,可以保证写主库成功之后返回,但保证不了延迟。比如写完马上读就会有问题。
    如果等待同步从库成功后再返回,实际就是双写。那么故障几率就更大了,可用性就会降低。

    好吧我知道还有所谓“具体业务具体分析”,但实际上哪有那么多具体业务能忍受可用性降低或者同步延迟。就比如订单,钱不能算错,库存也不能错,但没听说过谁说用可用性来做 trade off 的。

    我始终认为,提升系统容量,要有一套能撑得住的运维体系,然后一直分库分表就好了。所谓主从同步,是一个“高可用”方案,在主库挂掉的时候从库顶上。而不是 scale out 方案。

    那,真正做过读写分离方案的,到底有多少?
    第 1 条附言  ·  2019-04-19 11:31:17 +08:00

    补充几点:
    我的疑问是原文的两个如果。

    分库分表可以多机做多实例,每个实例再包含多库和多表。所以也是可以做到负载均衡的。
    主从同步的延迟很低是没错,但关键是无法保证延迟。网络抖动一下,延迟就上来了。一主多从的情况下,每个从库的延迟可能都不相同。所以我想知道读写分离的方案处理这个无法保证的延迟是什么思路。主动监控吗?还是靠其他中间件?或者认为延迟超过某个阈值就是故障,不去考虑高延迟?
    我想了下,多实例分库分表也拉低了系统的可用性,但这个可用性和读写db双写还不一样。首先可以用主从来做高可用(我赞同用主从做高可用,而不是负载均衡),其次挂掉的是一部分数据,线上故障肯定是会有了,但不会全网挂。

    44 条回复    2019-04-20 20:52:41 +08:00
    LinInV2
        1
    LinInV2  
       2019-04-19 10:41:54 +08:00
    互联网公司标配吧。
    保证高可用性的啊
    wentaoliang
        2
    wentaoliang  
       2019-04-19 10:46:26 +08:00 via iPhone
    这个理解就不对了。主从除了高可用,另外一个作用就是负载均衡,你想想如果访问到数据库的 qps 有几 w,你再怎么分库分表,单机都不可能承受的住
    tiedan
        3
    tiedan  
       2019-04-19 10:47:59 +08:00
    读写的同步延迟间隙可以用缓存覆盖掉
    guyujiezi
        4
    guyujiezi  
       2019-04-19 10:48:10 +08:00   ❤️ 1
    你用过 memcache 或 redis 缓存吗?缓存就是一种读写分离
    jimrok
        5
    jimrok  
       2019-04-19 10:49:18 +08:00
    大部分的查询请求,其实不用那么实时,例如订单完成之后,可以不用变化。而且 mysql 的复制还是非常快的,100 毫秒上下,这样主库就可以把 cpu 剩下来提高写入的 ops。
    huadi
        6
    huadi  
    OP
       2019-04-19 10:50:24 +08:00
    @wentaoliang 分库又不是只能在单台机器上分,跑在多台机器上的多个实例也叫分库。
    关键是纸上谈兵的文章太多了,深入细节就没人讲。
    那么,我原文问的的两个如果怎么破?
    expkzb
        7
    expkzb  
       2019-04-19 10:50:26 +08:00 via iPhone
    虽然我不是搞后端的,但我知道磁盘也是有 io 瓶颈的。一般业务都是读多写少的。
    zhengyongtao
        8
    zhengyongtao  
       2019-04-19 10:51:59 +08:00   ❤️ 1
    读写分离是为了负载均衡,单机性能同时做读写撑不了多少 qps,另外一般订单这种都会使用缓存和队列,而不是直接入库,什么都等入库黄花菜都凉了。
    gaius
        9
    gaius  
       2019-04-19 10:54:21 +08:00
    多数据源,具体方法上加自定义注解结合 aop 选折具体数据源,这样讲是不是细节了一点。
    tiedan
        10
    tiedan  
       2019-04-19 10:55:26 +08:00
    读写分离的情况下,无实时性要求的业务读从,高实时性的业务强制读主。
    wentaoliang
        11
    wentaoliang  
       2019-04-19 10:55:28 +08:00 via iPhone
    @huadi 比如你是一个库的某个表请求几 w,你就只能读写分离了,对于读写同步延迟的。就不应该在同一个请求中写完立马就读,如果非得这么做,让第二次强制读主库
    huadi
        12
    huadi  
    OP
       2019-04-19 10:59:25 +08:00
    @guyujiezi 缓存和两个 db 双写还是不一样的。
    缓存很快。
    缓存不是持久化方案,可以采用 db+缓存双写而不降低可用性。
    缓存和 db 的一致性,仍然是问题。
    huadi
        13
    huadi  
    OP
       2019-04-19 11:05:33 +08:00
    @wentaoliang 分库分表最大的问题是数据倾斜和运维水平。比如按 userId 分,可能有某个 user 的请求很多。或者 db 特别多。否则无论如何我都可以降低 QPS。
    强制读主库确实是个好方法。但我不知道怎么控制这个时间阈值,因为同步延迟很难保证。网络抖动这个天灾就受不了。
    vmskipper
        14
    vmskipper  
       2019-04-19 11:06:00 +08:00
    根据自己的 qps tps 而定 链接过多或者活动事务过多 就弄呗 读压力过大就加缓存 写压力过大就分片 每天 2000 万的记录 消息几亿 总量百亿 现在就这么弄的
    guyujiezi
        15
    guyujiezi  
       2019-04-19 11:11:58 +08:00
    @huadi 主从架构不能叫“双写”,这样说法会有歧义的,两主才叫双写。

    主从同步一般都是异步的,这叫最终一致性,同步写入那就变成两阶段提( 2PC )交了
    whileFalse
        16
    whileFalse  
       2019-04-19 11:22:40 +08:00   ❤️ 4
    “写完马上读就会有问题”

    没错。所以要从业务层弥补。
    当如果写和读属于两个无关业务,比如用户 A 刚更新了自己的头像,用户 B 立即去拿结果拿到了旧的;这种情况通常是可忍受的。因为如果用户 B 的这个请求早发送 1s,那么他看到的肯定就是旧的;两个业务之前不存在因果关系。

    如果写读属于同一个业务,比如更改用户单张借记卡的余额,然后显示用户所有卡的总余额;这种情况要不将两个数据库操作合并为同一个操作,要不从业务层将两个独立的请求合并为一个请求,要不强制读主库;要不告诉用户”更新可能延迟“之类的。

    总之,上读写分离是和业务相关的,无法做到对开发透明。在业务理解并做针对性优化的情况下,可用性不会是问题。
    huadi
        17
    huadi  
    OP
       2019-04-19 11:27:08 +08:00
    @guyujiezi 我的原始问题是原文的两个如果。
    如果不双写,做异步同步,这没问题的。那我的疑问是怎么处理延迟。
    主从延迟很低是没错,但关键是这个延迟是无法保证的。也就是说平常 100ms 之内,但网络一抖动,分钟级别的延迟都有可能发生。这个时候从库是没办法提供服务的,怎么处理这种问题呢?
    huadi
        18
    huadi  
    OP
       2019-04-19 11:35:55 +08:00
    @whileFalse 想请问下,合并两个请求有什么思路么?现在大部分应用都是 HTTP,有什么工具或者链接能分享下?
    glfpes
        19
    glfpes  
       2019-04-19 11:40:21 +08:00
    你举的例子就是那种“读写分离不太合适”的情况。订单系统这种强一致性的场景不多且一般都是关键场景,花更多的资源有价值。
    实际上很多场景,不需要强一致性。当读写不均衡的情况下,比如读的 qps 比写高几个数量级,读写分离还是很常见的做法。比如用户画像。其实用到本地缓存的地方都可以搞读写分离。
    lhx2008
        20
    lhx2008  
       2019-04-19 11:46:20 +08:00   ❤️ 2
    分布式系统最大的问题就是很难保证强一致性,对于普通业务,主要通过逻辑弥补。对于敏感的业务,主写从读会出问题。

    逻辑弥补有很多方法 (来自李运华的文章)
    1. 业务可以接受,系统不优化
    2. 写操作后的 n 秒内读操作指定发给数据库主服务器
    3. 读从机失败后再读一次主机
    4. 关键业务只走主机,可接受延迟的业务走从机
    5. 走缓存,先更新缓存,缓存过期肯定已经刷入从机了

    楼主看的“所谓架构文章”,怕不是 CSDN 上面的?
    wentaoliang
        21
    wentaoliang  
       2019-04-19 11:51:50 +08:00
    @huadi 理解一下 cap 理论就知道,按目前分区无法完全保证的情况下,强一致性和高可用只能满足一个。所有从理论层面告诉你,你的只能在业务层面控制
    yanaraika
        22
    yanaraika  
       2019-04-19 12:12:59 +08:00
    首先,CAP 定理了解一下,要 C+P 必然会牺牲可用。实际中是要检测延时的,这也是不推荐在公有云上自己搭数据库集群的原因:网络没有专有网络稳定。

    现在也有一些利用 RDMA 提升 RSM 复制的方案。

    另一点是:吞吐和延时也是不可兼得的,要使用 batching 等方式提升 replicate 的吞吐就会牺牲延时。

    一种折中方案是 写 + 读确认节点 > N。一般写少读多的话成功写入 3/4 N 节点返回,成功读到 1/4N + 1 节点返回。

    顺带一提,当你需要读写分离的时候再折腾 MySQL 读写分离主从之类的基本就是收效最小的方案,之后主不可用、一致性太弱等问题会让你很头疼,最后层层 patch 后会发现自己造了一个已有的轮子。建议一步到位上无中心化的方案如 Cassandra/NewSQL 等。

    @lhx2008 这几个方案都有很多缺陷,最简单的就是客户端时钟不可靠
    lhx2008
        23
    lhx2008  
       2019-04-19 12:20:43 +08:00
    @yanaraika 这个确实是一个折中的方法,但是实现的复杂度也非常高了。当然 NoSQL 的经过验证的分布式的主从架构更加可靠。至于时钟问题,确实是一个存在的因素,我没有过多研究,不过 Redis 作者在说 RedLock 的时候,没有认为这是一个主要问题。"well, you can assume your clock is mostly correct though!"
    guyujiezi
        24
    guyujiezi  
       2019-04-19 12:28:10 +08:00
    @huadi 如果你的业务代码写得正确,那是不会发生你所说的问题的。因为这个分布式模型是可以实现最终一致性的

    我猜你的意思可能是:往从库里取得商品库存,由于数据没有同步,所以从库中的库存数量多余实际数量,然后程序因此创建了本不应该创建的订单。是这样吧?

    然而你这个代码本身就是错误的呀
    alvin666
        25
    alvin666  
       2019-04-19 12:28:24 +08:00 via Android
    我们做的主从是直接走的云服务商的内网,内网要是再波动了,就算不做主从也会受影响
    yanaraika
        26
    yanaraika  
       2019-04-19 12:35:53 +08:00 via Android
    @lhx2008 http://antirez.com/news/101 这个问题非常复杂。我个人是不喜欢 redlock 这种对时间源一个外界因素有依赖的方案的
    yanaraika
        27
    yanaraika  
       2019-04-19 12:46:59 +08:00 via Android
    @alvin666 主要是延时没 SoA 保证…而且延时低到一定程度后 gc 等因素也无法忽略
    calpes
        28
    calpes  
       2019-04-19 12:56:35 +08:00
    @huadi 最简单的例子。。。你确认写入成功后不再次读库,直接将你写入的数据返回给用户,这就是一种很常见的操作,主从分离这个事某种意义上是无法做到对开发者透明的,而且这里你有一个钻牛角尖的思路,就是主从之间的网络可能一抖抖个一分钟啥的,需要业务代码来容错,如果把这些东西加到研发成本里,基本上是没法接受的。OP 角度上来讲每个服务都是有他的可用性指标的,五个 9 以上我觉得这种抖动很难感知到,而如果你的业务在主从分离的场景下由于 db 的可用性指标不够导致业务上大面积的出问题,我建议你们换个更专业的 DB 和 OP,毕竟服务可用性他是一个系统性工程。
    idblife
        29
    idblife  
       2019-04-19 13:22:56 +08:00
    楼主土鳖而已
    哪家互联网公司不做读写分离能扛得住?
    wangxiaoaer
        30
    wangxiaoaer  
       2019-04-19 13:39:59 +08:00
    @jimrok #5 即使多个实例机器,那么同一个数据库的某张表你打算放到几台机器呢?如果 1 台,势必会有瓶颈,如果多台,不还是主从复制吗
    passerbytiny
        31
    passerbytiny  
       2019-04-19 13:43:00 +08:00
    读写分离是抽象,分库分表是技术实现,啥时候被当成解决问题的两种方式了。人家文章的意思其实是:一般你用分库分表这种简单实现方式凑合以下就行了,要是扛不住,你得从根本的抽象上去寻找解决方案,比如说读写分离;至于具体是什么,限于篇幅无法细说(其实我也只知道个标题)。

    读写分离这种标题很容易理解但内容很复杂的高级抽象,网上你就只能找到标题,想系统的学习,还是要买书。推荐你一本书《实现领域驱动设计》,里面“架构”、“事件”等章节有关于读写分离架构的描述。
    jimrok
        32
    jimrok  
       2019-04-19 14:03:22 +08:00
    @wangxiaoaer 你这个问题,要结合业务场景来讲。就说订单这个东西,如果你的用户少,可以不做拆表处理的。当用户购买商品后,去查询订单,这个请求是个读请求,而且查询可能根据时间,状态过滤。这个列表数据是不容易做 memcahced 缓存的,所以,必须将这部分查询请求放在从库上执行。这样主库的压力就会降下来,放几台要根据你的 qps 量进行估算。如果每秒有 2w 笔查询,那你单台能支撑的是 5000qps,你就需要准备至少 4 台服务器。如果你再能在应用层将请求分割开,那么每台的 mysql 可能只负责某个区域的用户查询请求,那么 mysql 的 buffcache 就可能非常高效。
    jimrok
        33
    jimrok  
       2019-04-19 14:14:08 +08:00
    @huadi 主从复制不是解决强一致性问题的,通常情况下的应用,不用害怕这个延迟。在运维层面来讲,通常有一个读写分离的 proxy,有这个 proxy 来代理 sql 端的请求。这个代理也同事负责监控主从库的监控状况,如果从库失败,这个代理会把 select 的语句发给主库执行的。如果你用过阿里云的 RDS,他们会给你一个 readonly 的 mysql 地址,还有一个读写分离的地址,你不需要考虑后端是否可用,这个 proxy 已经管理了后端的群集。
    CRVV
        34
    CRVV  
       2019-04-19 14:20:56 +08:00
    如果一个数据库 instance 搞不定所有的读写请求,那么你可以使用多个 instance 来搞
    有不同的方案来使用多个 instance,读写分离是其中的一个
    或者说你也不一样要完全读写分离,只要把一部分读请求分走,也许就能好了

    > 如果是异步同步,可以保证写主库成功之后返回,但保证不了延迟。比如写完马上读就会有问题

    是会有问题,但有的业务可以接受有这个问题,或者用另外的手段处理这个问题。
    或者你可以只把不需要强一致性的读请求分走。

    > 如果等待同步从库成功后再返回,实际就是双写。那么故障几率就更大了,可用性就会降低

    出故障的概率会变大,但是数据出错的概率会变小。某些情况下可以接受系统挂掉但是不能接受丢数据。

    而且这么干的前提是你用一个 instance 搞不定,搞不定的事情就没有可用性,现在能搞定了才有了可用性。
    这里不应该说可用性降低了。
    Raymon111111
        35
    Raymon111111  
       2019-04-19 14:22:51 +08:00
    读写分离太多了

    我们目前单库写入 qps 3000 (分库分表 5 个库), 读写分离框架用的很顺利

    你说的主从延迟的问题, 绝大多数情况下都能控制在 100ms 内. 也就是说主库写入后从库很短时间内就可以读到, 能够满足绝大多数的使用场景

    有一些很特殊的场景会采用强制读主库的方案, 但是这种一般控制量, 主库读影响写入性能

    至于监控, 产品也很多, 最快的学习方法是进一个大公司, 一般这些东西很成熟, 你一看就明白了
    tairan2006
        36
    tairan2006  
       2019-04-19 16:13:12 +08:00 via Android
    喷了,用缓存本质上就是读写分离吧。网络分区下高可用和一致性是矛盾的,这不就是 CAP 定理么,楼主自己瞎琢磨啥呢。
    zzzzzzZ
        37
    zzzzzzZ  
       2019-04-19 16:54:34 +08:00
    读写分离是标配,不是大招,不是人云亦云,是一个稍微水平正常的开发都能做的标配架构

    如果你还卡在你第一个"如果",那就是你代码写的烂,别玩架构了
    如果你出现了你第二个"如果",那就是你架构搭的烂,别玩架构了

    你要的所谓的“中间件”,数据库层面的是 Mycat,就是 @jimrok #32 做的事
    代码层面的纯代码就能解决,想不明白怎么敲代码,就别来纸上谈兵的学一些架构的东西,架构从来都是实践出来的
    janxin
        38
    janxin  
       2019-04-19 17:45:43 +08:00
    我觉得其实楼主你应该实操一下?空想很容易钻牛角尖。

    另外一个,基本上根据 CAP 去牺牲某一个,根据业务需求再去考虑牺牲哪一个。

    即便加上缓存,也是根据具体的业务需求,决定缓存 miss 和脏缓存的接受程度。

    毕竟,没有银弹嘛。
    phx13ye
        39
    phx13ye  
       2019-04-19 19:22:14 +08:00 via iPhone
    借楼问问各位大佬,多对多关系的的中间表一般怎么分库分表。是要双向查的,只能冗余两份各自 sharding 吗?。例如我给哪些文章点赞了,文章被谁点赞。学生选修哪些课程,课程有哪些学生选修。
    bringyou
        40
    bringyou  
       2019-04-19 20:33:29 +08:00
    分库分表限制了一些业务实现,比如说表之间的 join。一般业务场景下,对于数据的读取是多于写入(更新),所以为了那些特性,很多场景下都只是先读写分离了。而且一般的规模下这个也足够了。至于主从同步的延迟,阿里云 rds 是 1 毫秒级别的,很少超过 10ms,动辄 100ms 甚至 1min 的延迟,我觉得还是先招些专门的网络运维人员,先优化网络链路来的实在一些,因为网络慢不只是影响数据库主从同步,正常的业务应用也会受到影响。

    如果对于 1ms 级别的延迟不能接受的话,可以向前面老哥提到的,再次查询写库,这个重复的工作可以通过 aop 或者中间件来做,自己不用重复的。

    同步模型可以看看 mysql 的异步同步、半同步和组同步。
    bringyou
        41
    bringyou  
       2019-04-19 20:36:58 +08:00
    @phx13ye 之前我司是这样。如果不局限于数据库的话,可以试试一些 newsql 或者 elasticsearch 这样的搜索引擎
    akira
        42
    akira  
       2019-04-20 01:05:09 +08:00
    之前做读写分离,是把一些报表类的,不太要求实时性的查询 放在只读库 ,
    大部分业务还是落在主库上面
    curdoldman
        43
    curdoldman  
       2019-04-20 13:01:18 +08:00
    SQL HINT 了解一下。
    HINT 是一种可选的 SQL 片段或注释,可用于在 SQL 语句里强制索引,强制使用主库,强制不使用缓存等等
    fox0001
        44
    fox0001  
       2019-04-20 20:52:41 +08:00 via Android
    我们是采用 Solr + 数据库,不完全的读写分离。但是 Solr 抗住了绝大部分的复杂查询,效果不错
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1248 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 23:26 · PVG 07:26 · LAX 16:26 · JFK 19:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.