“浅”谈容灾和双活数据中心(上)


有读者建议冬瓜哥标题里去掉 浅谈 ,说是如果写成这样还叫浅谈,那么其他人还咋办。冬瓜哥一向认为,最近这几篇确实比较浅,并不深刻,从关注人数三天内的暴涨可以看出, 是对的,太深了,反而不适应大部分人的口味。而第一篇写 CAPI 的那个感觉倒是比较深,但是那几天基本没人关注,证明有时不得不符合大众口味。本次瓜哥想聊一聊容灾和双活 DC 。瓜哥手里并没有一堆厂商的 ppt ,但是瓜哥是了解他们底层是怎么玩的了,因为瓜哥是做过底层的。有些人手里一堆材料,也却只能贴几个图象征性说几句而已,所谓看不透底层,就只能在表面徘徊。另外有人反馈说文章配图太少,这个确实是个问题,瓜哥的风格是尝试用纯文字来描述清楚一个事物,这样其实略显功底和逼格,也可以锻炼自身的功底和思维,也锻炼大家的思维清晰度。所以,这次冬瓜哥还是不贴图,另外一个原因也是冬瓜哥是在没工夫去弄个 ppt 画个图出来,或者随便找某个厂商 ppt 截个图出来。此外,瓜哥是不会藏着掖着某些东西的,瓜哥有更高逼格的追求,不会藏着掖着怕给抢了以后没法装逼了,瓜哥骨子里其实是个人民教师,人民教师的天职就是传道授业,只不过现有*容不下瓜哥这种存在。另外,有人说冬瓜哥不能自称为 瓜哥 了,因为有另一个 瓜哥 ,冬瓜哥听到这里笑而不语,其实有很多往事,难追忆,冬瓜哥一直是冬瓜哥,一直是瓜哥,瓜哥就是冬瓜哥,写《大话存储》的冬瓜哥。

上周六圈里几个朋友聚在一起讨论了讨论容灾方面内容,不过感觉没讨论出什么底层技术来,不太爽,虽然讨论的时候瓜哥一句话也没说,因为瓜哥一向低调,在这种公开讨论场合,由于几乎不可能有人出面很清晰把整个体系梳理清楚,所以瓜哥一般是习惯在一旁为每个人的逼格和思维清晰程度打打分,但是不表示瓜哥没得可说,所以,写这个文章,主要是为了梳理清楚这堆东西。闲话少说,本文共分为四部分,为了避免过长,先发出前两部分。下周再发送后两部分。建议闲暇时间阅读。

第一部分:复制链路

1.1 链路类型

链路,来根线连接起来,不就可以了么?没问题,裸光纤连起来,这就是链路,如果是一座楼内,甚至一个园区内,管井你可以随便用,铺设一根光缆,没多少钱。问题是本地和远端相隔太远,出了园区,你就不能随便在两个楼之间飞一根线了,必须租用电信部门提供的各种线路了,当然,点对点无线传输也是个可以考虑的路子,如果两楼之间相隔不太远且视觉直达,到可以考虑这种方式。电信部门有各种专用链路或者共享链路提供出租,最原始的一种就是裸光纤,两地之间直接通过电信部门部署好的光缆,其中分出两根纤芯给你,当然,电信部门会有中继站,负责对光信号的路径交叉及信号增强中继。因为不可能任意两点间都恰好有光纤直连,必须通过中继和交叉。有了裸光纤,你两端跑什么信号什么协议什么速率就随你了,只要光模块的波长功率合适,两端就可以连通。裸光纤不能走太远距离,一个是价格太贵另一个是电信部门也不可能租给你用,因为距离太远的时候,光缆资源越来越稀缺,不可能让你独占一根,要知道,电信部门使用 DWDM 设备是可以在一路光纤上目前复用高达 80 路光波的。实际上近距离传输也很少有人用裸光纤了,尤其是大城市,因为资源太稀缺,除非互联网这种体量的用户,其他基本都是租用与别人贡献的某种虚拟链路。比如使用 ADSL EPON/GPON E1 等最后一公里接入方式,上到电信部门的 IP 网,或者直接上到 SDH 同步环网,不同的方式和速率,价格也不同。

存储设备一般都支持 iSCSI 协议复制,那么此时可以接入使用以太网作为最终连接方式的比如 ADSL GPON 等,如果仅支持 FC 协议复制,要么增加一个 FC IP 路由器再接入 IP 网,要么使用局端提供的专用 FC 协议转换设备直接上 SDH 同步网。

1.2 长肥管道效应

链路的时延除了与距离有关之外,还与链路上的各个局端中继和转换设备数量有关。光在光纤中传播靠的是全反射,等效速度为每秒 20 万千米,而更多时延则是由信号转换和中继设备引入的,电信运营商的网络可分为接入网和骨干网,本地的信号比如以太网或者 FC ,先被封装成接入设备所允许的信号,比如 GPON 等,再视专线类型,上传到以太网交换机、路由器或者直接到局端骨干网入口设备比如 OTN 。在这种高时延链路之下,每发送一个数据包,要等待较长时间才能得到 ack ,此时如果源端使用同步复制,性能将非常差,链路带宽根本无法利用起来,太高时延的链路必须使用异步复制。应用端同步 IO 模式 + 同步复制,这种场景是吞吐量最差的场景,异步 IO+ 同步复制,效果其实尚可,最好的还是异步复制(不管同步还是异步 IO )。

不管是异步复制还是同步复制,如果使用了 FCP 或者 TCP 这种有滑动窗口的传输协议,那么难免会遇到传输卡壳, TCP 有个最大可容忍未 ACK buffer 量,传输的数据达到这个 buffer ,就必须停止发送,而必须等待 ack 返回,只要一卡壳,链路上这段时间内就不会有数据传送,严重降低了传输带宽,为此,专业一点的存储设备都要支持多链接复制,向对端发起多个 tcp 链接,在一条链接卡壳的时候,另一条正在传输数据,这个与数字通信领域常用的多 VC/ 队列 / 缓冲道理是一样的,一个 VC 由于某种策略导致卡壳的时候,其他 VC 流量一样会利用起底层链路的带宽。

第二部分:双活数据中心

2.1 双活并发访问的底层机制

从存储系统的视角来看双活数据中心,怎么个双活法?应用首先得双活,应用不双活,就无所谓双活数据中心。也就是多个实例可以共同处理同一份数据,而不是各处理各的(互备,或者说非对称双活)谁坏了对方接管,支持多活的应用典型比如 Oracle RAC 、各类集群文件系统等,这些应用的每个实例之间是可以相互沟通的,相互传递各种锁信息及元数据信息,从而实现多活。一般来讲,这类多活应用,其多个实例一定要看到同一份数据,如果这同一份数据有多个副本,那么一定要保证着多个副本之间是时刻一样的(有些互联网应用除外,不要求实时一致性),这与多核心多路 CPU Cache Coherency 思想是一样的(具体可以看本公众号最早的一篇文章 聊聊 CAPI“ ,所以冬瓜哥一直认为底层通了,一通百通)。所以,要么让这多个应用实例所在的主机通过网络的方式共享访问同一个数据卷,比如使用 SAN (多活数据库比如 Oracle RAC ,或者共享式集群文件系统,这两类多活应用需要访问块设备)或者 NAS (有些非线编集群应用可以使用 NAS 目录),用这种方式,数据卷或者目录只有唯一的一份而且天生支持多主机同时访问。在这个基础上,如果将这份数据卷镜像一份放到远端数据中心的话,而且保持源卷和镜像卷时刻完全一致(同步复制),那么多活应用就可以跨数据中心部署了,多活应用看到的还是同一份数据,只不过本地实例看到本地的数据卷,远端实例看到远端的数据卷,数据卷在底层用同步复制实现完全一致,这与在本地多个应用实例看到唯一一份数据卷副本的效果是一模一样的,这也就做到了多活,能够在整个数据中心全部当掉时,短时间内几乎无缝业务接管。

但是,实现这种双活,也就是让存储系统在底层实现 源卷和镜像卷时刻一致 ,并不是那么简单的事情。首先,传统容灾数据复制技术里,业务是冷启动(灾备端应用主机不开机)或者暖启动(灾备端应用主机开机但是应用实例不启动,比如 Windows 下对应的服务设置为 手动 启动),这个极大节省了开发难度,灾备端存储系统所掌管的镜像卷,不需要挂起给上层应用提供数据 IO ,而只需要接收源卷复制过来的数据即可。而多活场景下,应用实例在灾备端也是启动而且有业务 IO 的,那就意味着,源卷和镜像卷都要支持同时被写入,而且每一笔写入都要同步到对端之后才能 ACK ,这种方式称之为 双写 ,以及 双向同步 ,只有这样,才能做到两边的实例看到的底层数据卷是一模一样的,而不是其中某个实例看到的是历史状态,而其他实例看到了最新状态,后者是绝对不能发生的,否则应用轻则数据不一致,重则直接崩溃。这种双写双向同步,看似简单,同步不就行了么?其实,不了解底层的人可能也就到这一步了,然后就去出去装逼去了,殊不知,很多坑你都没有填,说不定哪天就把自己给装死了。

存储端实现双写双向同步的第一个难点在于,如何保障数据的时序一致性。与单副本本地多活应用系统相比,双副本多活,也就是双活数据中心,两边的应用实例各自往各自的副本写入数据,如果 A 实例像 A 卷某目标地址写入了数据,那么当这份更新数据还没来得及同步到 B 卷之前, B 实例如果发起针对同一个目标地址的读操作, B 卷不能响应该 IO ,因为 B 卷该目标地址的数据是旧数据。 B 卷如何知道 A 实例已经在 A 卷写入了数据呢?这就需要复杂的加锁机制来解决, A 端的存储系统收到 A 实例写入 A 卷的 IO 之后,不能够 ACK A 卷,它需要先向 B 端的存储系统发起一个针对该目标地址的 Exclusive 排他锁,让 B 端存储系统知道有人要在 A 端写数据了,然后才能向 A 卷中写入数据,如果 B 端的 B 卷针对此目标地址正在执行写入操作(由 B 实例发起,但是通常不会出现这种情况,应用实例之间不会出现两个以上实例同时写入某目标地址的操作,因为多活应用实例之间自身也会相互加锁,但是存储系统依然要考虑这种情况的发生),则此次加锁不成功, A 端存储系统会挂住 A 实例的写操作,直到能够加锁成功为止,这里可以定期探寻也可以 spin lock ,不过在这么远距离上去 spin lock 恐怕性能会很差,所以不会使用 spin lock 的模式。加锁之后, B 端如果收到 B 实例针对该目标地址的读或者写 IO ,都不能够响应,而是要挂住,此时 B 端存储系统要等待 A 端存储系统将刚才那笔针对 A 卷的写 IO 发送过来之后,才能够返回给 B 实例,这样,存储系统任何时刻都能保证对 A 实例和 B 实例展现同样的数据副本。同理, B 卷被 B 实例写入时,也需要执行相同的过程,对 A 存储的 A 卷对应地址加锁,然后后台异步的将数据同步到 A 端。

第二个难点,就是如何克服高时延链路导致的 IO 性能降低。通过上面的描述,大家可以看到上文中有个坑没被填,也就是 A 存储在何时给 A 实例发送写成功 ACK ?是在向 B 端加锁成功后,还是在 A 实例写入的数据被完全同步到的 B 端之后?如果是后者,那就是传统的同步复制技术了,把这块数据同步到 B 端,是需要一定时间的,如果使用的是 TCPIP 方式传输,根据上文中的分析,还会出现卡壳,等待传输层 ACK 等,时延大增,性能当然差。但是如果 A 端在向 B 端加锁成功后立即给 A 发送写 ACK ,那么时延就可以降低,此时虽然数据还没有同步到 B ,但是 B 端已经获知 A 端有了最新数据这件事情,如果 B 实例要访问 B 端的这份数据, B 存储会挂住这个 IO ,一直等到 A 将数据复制过来之后才会返回给 B 实例,所以不会导致数据一致性问题。也就是说,这种方式,拥有异步 IO 的性能效果,以及同步 IO 的数据一致性效果,两者兼得。而代价,则是丢失数据的风险,一旦数据还没有同步到 B 端之前,链路或者整个 A 端故障,那么 B 端的数据就是不完整且不一致的,要性能就注定要牺牲一致性和 RPO

第三个难点:解决死锁问题。如果某个应用不按照规律来,该应用的两个实例在两边分别同时发出针对同一个目标地址的写 IO 操作给两边的存储控制器,两边会同时向对方发起加锁请求,这个过程中由于链路时延总是存在的,锁 ack 总会延时收到,导致两边同时对该地址加了锁,结果谁都无法写入,这便是死锁。解决这个问题就得找一个单一集中地点来管理锁请求,也就是让其中一个存储控制器来管理全部锁请求,那么无疑该存储控制器一定会比对端更快的抢到锁,不过这也不是什么大问题,本地访问永远先于对端访问,这无可厚非。

综上所述,双活或者多活数据中心方案里,存储系统的实现难点在于锁机制。与多 CPU 体系(也是一种多活体系结构)相比,多 CPU 在针对某个进程写入数据到 Cache 时,是不向其它节点加锁的,而只是广播,如果多个进程同一时刻并发写入同一个目标地址,那么就发送多个广播,谁先后到,覆盖先到的,此时 CPU 不保证数据一致性,数据或被撕裂或被循环覆盖,这个场景需要程序遵守规则,写前必须抢到锁,而这个锁就是放在某个目标地址的一个集中式的锁,程序写前使用举例来讲 spin lock 来不断测试这把锁是否有人抢到,没抢到就写个 1 到这个地址,而 spin lock 本身也必须是原子操作, spin lock 对应的底层机器码中其实包含一条 lock 指令,也就是让 cpu 会在内部的 Ring 以及 QPI 总线上广播一个锁信号,锁住所有针对这个地址的访问,以协助该进程抢到锁,保证抢锁期间不会有其他访问乱入。存储系统其实也可以不加锁,如果上层应用真的两边并发同时访问同一个地址,证明这个应用实现的有问题,但是存储系统为了保证数据的一致性,不得不底层加锁,因为谁知道哪个应用靠不靠谱,多线程应用很多,程序员们已经驾轻就熟,但是多实例应用,非常少,出问题的几率也是存在的。

冬瓜哥很少提及具体产品,因为冬瓜哥认为底层通一通百通,不需要了解具体产品实现,所有实现都逃不出那套框架。但是为了迎合大家口味,冬瓜哥还是提一提产品比较好。可以肯定的是, HDS 的双活,是确保将数据完全同步到对端之后(同时向对端加锁),才返回本端应用写 ACK 信号;而 EMC Vplex 所谓的 基于目录的缓存一致性 ,则是使用了加锁完便 ACK 方式,这也就是其号称“ 5ms 时延做双活”的底层机制。至于其他家的双活,逃不出这两种模式,爱是谁是谁,爱抄谁抄谁,冬瓜哥就不操心了。不过, 基于目录的缓存一致性 其实是出自 CPU 体系结构里的学术名词, EMC 很善于包装各种市场和技术概念,连学术名词都不放过。不过,双活的这种一致性机制,与多核或者多 CPU 实现机制的确是同样思想,在 MESI 协议中,某个节点更新了数据,会转为 M 态( Modify ),并向其他所有节点发起 Probe 操作作废掉其他节点中对应的 cache line ,这一点和上述思想是一致的,只不过 M 态的数据一般不用写回到主存,而是呆在原地,其他节点入有访问,则 M cache owner 节点返回最新数据,而不是将数据同步到所有其他节点上(早期某些基于共享总线的 CPU 体系结构的确是这样做的)。

2.2 双活与 Server-SAN

能从双活扯到 ServerSAN ?是的, ServerSAN 本身也是多活的,其实现机制类似于传统存储系统的双活方案,只不过其有分布式的成分夹杂在里面。目前主流 ServerSAN 实现方式是将一个块设备切片比如切成几个 MB 大小的块,然后用 Hash 算法来均衡放置到所有节点中,每个切片在其他节点保存一到两份镜像,主副本和镜像副本保持完全同步,不复制完不发送 ack 。由于时刻同步,所以 ServerSAN 可以承载多活应用,每个节点上都可以跑一个应用实例,所有实例看到时刻相同的存储空间,可以并发写入多个镜像副本,为了防止某些不守规矩的应用,多节点间也要实现分布式锁,也要解决死锁,所以一般使用集中式锁管理,比如 zookeeper 之类。如果把 ServerSAN 多个节点拆开放到多个数据中心,这就是多活数据中心了。所以你能看到, ServerSAN 本身与双活数据中心都是同样的思想。

(未完,本周末发后两部分:数据复制及一致性、业务容灾)


本文转载请注明出自 大话存储 公众号。长按识别二维码关注 大话存储 获取业界最高逼格的存储知识。 这篇文章如果你看到这里都不给瓜哥点小费的话,是不是有点说不过去了?平时群里发红包装逼,不如把红包猛烈的砸向冬瓜哥吧!三块五块不嫌少,三十五十不嫌多。冬瓜哥后续会有更多高逼格的东西出炉。大话存储,只出精品。

“浅”谈容灾和双活数据中心(上)“浅”谈容灾和双活数据中心(上)