Kernel space, user space, and syscall

这篇文章介绍什么是 kernel space 和 user space,以及系统调用(System call,以下会用 syscall表示)。

为什么要分成 kernel space 和 user space 呢?

这要对系统对进程的抽象说起,每一个进程看到的内存空间都从固定的位置开始,main() 总是从 0x4005db 开始,stack 也总是从特定的位置开始。那么不同进程使用的内存都是重叠的吗?

当然不是。这就是系统对进程的抽象。每个进程只能看到自己的内存,MMU(内存管理单元)将解决每个进程看到的虚拟内存和实际物理内存的对应(内存管理是一个很大的话题,这里只是简单的描述,有关内存管理有很多资料可以参考,这里提供一个有趣的文章:Virtual Memory With 256 Bytes of RAM – Interactive Demo)。不仅仅是内存,每个进程只能使用有限的资源,而 kernel 可以使用所有的资源。当 user space 需要使用硬件资源时,user space 通过 syscall 告诉 kernel space,kernel space 来完成调用。

这样做的目的是将资源隔离开,确保 user space 崩溃不会影响 kernel space;以及限制 user space 所能做的事情,不能让程序可以随意进入 kernel space 进行任意的操作。怎么做到的呢?当计算机启动的时候,CPU 会进入 ring0,即特权状态,在一个地址设置好 syscall 的对应关系(这个对应关系可以在这里查),比如 1 对应的就是 write() syscall,然后 CPU 退出到 ring3,即 user space。再次进入 ring0 的唯一方法就是通过 syscall。syscall 要求传入一个 int 表示要调用哪一个 syscall,这样,kernel 就可以控制所能执行的动作,即只能执行事先定义好的 syscall mapping。

Syscall 和一般的函数调用是不一样的,这一点读者应该已经明白了。因为在程序内部,可以使用 stack 或内存在调用之间传值,但是 user space 和 kernel space 是隔离的(当然也可以通过共享的内存传值),stack 不是共用的。如果写一个简单的 write() 程序反编译,可以看到 syscall 的汇编代码是 mov exa, 1 syscall ,其中1就是前面我们说的 syscall mapping,即 write(),然后 syscall 是 user space 向 kernel space 发出的软中断,进入 kernel space,kernel 会检查 exa 寄存器的值,找到对应的函数执行。(不同的架构实现会有所不同)

简单来说,kernel space 和 user space 是两个世界,syscall 就是连接这两个世界的桥梁。虽然这个桥梁我们一般不会直接使用,而是通过 glibc,glibc 是 syscall 的一个 wrapper,让我们 call 起来更加简单方便。比如说 printf 函数,其实就是 write() 的一个 wrapper。

通过 man 2 write 可以看到 write() 的原型如下:

所以我们可以这么调用,向屏幕打印字符:

有关 syscall 大体就是这样。以上是我的理解,如果有错误欢迎交流。下面再介绍一些相关的工具。

上面那张截图 radare2 的界面,一个反编译工具。使用方式是 radare2 -d ./a.out ,然后在s sym.main ,设置断点,可以看到 syscall 是如何执行的。

通过 man syscalls 可以查看系统提供的 syscalls. strace 工具可以将一个进程使用的 syscall 输出到 stderr,包括调用的参数,和返回值。比如查看 ls 使用的系统调用:

-c 参数很有用,可以显示每次 call 花费的时间,call 的次数,占用的总时间等,是一个 perf 的好工具。

 

参考资料:

  1. Linux System Programming
  2. Youtube: Syscalls, Kernel vs. User Mode and Linux Kernel Source Code – bin 0x09
  3. jvns 的这篇漫画:https://drawings.jvns.ca/userspace/
 

谈谈预防故障的性价比

说到保障系统的稳定性上,从预防的层面上看,总是有无数的事情可以去做。我觉得人们经常陷入的一个误区是,总是假设系统的某些方面会出问题,然后想办法针对这些特定的问题去做预防,认为预防好了这些问题自己的系统就万无一失了。这就导致很多时间花费在穷举系统可能出现的错误上、针对特定的错误做预防措施上。

最常见的错误就是增加流程。比如上线的流程、修改某个参数的流程。很可能一开始所有的流程都是简洁并快速的,每个人都可以将精力集中在自己的工作上。直到有一天出现了故障,大家在复盘的时候,发现“哦!假如说我们在发布之前增加一个流程,可以确认一下某个东西没有错误,那这个错误就可以避免了!” 这样的故障复盘越来越多,流程也就开始堆积的越来越多了,比如发布之前要求满足多少的覆盖率测试才能发布,某个变更做出之前必须经过某某审批才能执行。

其实添加一个流程是复盘最容易做出的决策——如果什么都不做的话,就等于问题发生了,作为领导或者负责人什么作为都没有,这个事情后面再发生或者再说起来就会很尴尬。事实上,但很多时候这么做其实是没有什么用的,首先,发生的故障一般都是新的故障,对已经发生的故障进行预防性价比太低;然后增加的很多流程其实并没有什么实际作用,最后会变成一种形式主义。比如强制要求测试的覆盖率,会导致程序员去写一些除了增加覆盖率并没有实际测试功能的代码;增加审批流程会徒增同事之间的沟通成本和审批时间,审批人大部分情况下可能也不知道他审批的是什么东西,背后有什么风险,最后也变成了纯粹的“走流程”。

另一种错误是试图穷举出系统中所有可能发生的故障,对这些故障设定自动化的处理方法或做好这个特定故障的应对方案。在一个很复杂的大型的分布式系统中,穷举出所有的故障几乎是不可能的。针对已经发生的故障提供针对的应对策略或者自动化的解决方式也帮助不大,虽然看起来给人一种安全感——我们每次的故障都不会再发生啦!但实际上就算什么都不做,相同的故障再发生一遍的概率又有多少呢?这又犯了形式主义了,我们从错误中学到的不是预防这一个错误,而是想想这个错误为什么会发生,是不是我们现有的机制出了问题(不要盲目修改机制,不然就犯了上面说的“流程”类的错误了。增加一个流程应该是慎重的,要考虑到这对将来每次的流程都会增加成本。)问题发生之后我们用了多长时间恢复,能否提高恢复的速度?还有一个方面,这种强调特定错误的“自愈”一般是和业务强耦合的,业务在发展,“自愈”的测试成本又很高,很难保持这种“自愈”长期有效。

将大部分的成本花在这部分上面,可能看起来让人很有安全感:“看,我们能应对这么多的情况了。”但是这些穷举出的情况并没有多大的意义,故障依旧会发生,并且总是以我们没有想到的方式。

所以我觉得将精力放在补救措施上会更有意义。寻找那些应对场景广泛的补救方法,不去针对特定的场景,而是针对特定的表现。比如部署在多个 Available Zone,如果监控显示一个 AZ 的流量有问题,无脑切换到其他 AZ 就可以;比如对非关键的服务提供降级措施,日常可以快速发布和迭代,如果出现故障立即降级即可。

以前软件行业那种开发与运维职能分开的模式,典型的矛盾就是开发想快速迭代,运维为了稳定性不想做出任何改变。现在流程的 devops/SRE 文化其实并没有从根本的解决这个矛盾。我觉得答案可能就是 Facebook 那句话“Move fast and breaking things.” 错误终究会发生,不要试图完全预防错误,应该尝试提供快速补救、简单可靠的方案(简单很重要,只有简单的东西才是值得依靠的)。并且还有要 blame free 的文化——建立学习和责任的平衡,不带有惩罚性、责备性的报告。不要再使用不专业的(恐吓性质)的故障责任机制。

相关阅读:

  1. 谈谈 Ops(最终篇):工具和实践

 

去厦门

今年的五一假期有四天,于是就跑去厦门玩了,这几天正好天气也不错,好像早早的迎来了今年的夏天一样。

厦门是一个岛(我竟然出发前才知道,之前一直以为是福建省的一个沿海城市),岛最外圈是一整圈马路,叫环岛路,很漂亮。一圈大约30km,本来想骑一圈的,但是每天都很累,后来选择坐观光巴士看了一圈。

说起来厦门市和我的家乡青岛很像,虽然青岛只是在一个半岛上。但是厦门有沙滩,青岛也有;厦门地势不平,很多坡,很多隧道,青岛也是;厦门吃的基本是就海鲜,青岛也是。但是房价倒是比青岛高的多,我以为厦门也就是个二线城市的样子,没想到房价跟上海差不多。市中心要10万/平左右,最低也要5万。应该是岛上的资源有限吧,就这么大的地方,盖完了就没了。青岛有地方填过海造陆地,厦门也填过,但是已经不能再填了,沿海的地方已经填的很深了。最近几年厦门一直将工厂往岸上迁移,现在岛上的大部分工厂已经迁到对岸了,所以厦门的空气非常好。听一个司机说,岛上的机场也准备迁到另一个岛上去,因为机场在厦门主岛上一是占地方,二是周围的楼层要限高。嘿,这也跟青岛一样,现在我们那边的流亭机场要迁到胶州去。司机还说厦门很排外,不知道真假,但是看起来肯德基,星巴克,麦当劳这种连锁店在厦门很少见到,厦门的连锁店都是我之前没见到的一些牌子(厦门人见了不要伤心,司机说的不一定对)。

厦门的位置基本就在台湾岛的正对面,所以会有很多台湾文化的影响。南边有一个沙滩,就叫“一国两制沙滩”,路上还写着“一国两制,统一中国”。岛上有一个中心干道叫“成功大道”,一开始以为就是一个普通的名字,后来想想应该是纪念郑成功的。鼓浪屿上还有一个皓月园,是专门为纪念郑成功建的,名字取自“思君寝不寐,皓月透素帏”。里面有一个郑成功大雕像,是郑成功的10倍高。

说起吃的,倒觉得厦门没有什么特别好吃的东西。网上搜到的著名小吃沙茶面、花生汤、土笋冻都觉得很一般,去了大名鼎鼎的醉一号吃海鲜,也觉得一般,食材倒是感觉挺新鲜的。中山路步行街、局口街小吃、八市这些地方,跟全国各地的小吃街都差不多,都买油炸香肠、臭豆腐什么的,没有什么特别好吃的。我估计曾厝垵应该也是类似这种东西,所以就直接没去,不太喜欢这种商业化的小吃街。大众点评有一家很有名的惠源面包店,可惜我12:30去的,小哥跟我说现在啥都没有,要15:00才能拷出来。好吧。

倒是现在的榴莲很便宜,买一整个的话是 15/斤,也非常好吃。

鼓浪屿是离厦门主岛很近的一个小岛,挺漂亮的,可惜现在控制上岛人数,每天5w人,船票就很难抢(游客的码头和居民的码头是分开的),我只抢到 16:30 上岛的(本地司机跟我说他小时候都游过去)。上去之后就去了日光岩看日落,非常美。然后天就黑了,在岛上随便溜达了几圈,晚上坐在海边玩了一会就回去了。鼓浪屿上面猫特别多,印象深刻,很多旅馆饭店名字都带“猫”。去的时候又是春天,到了晚上这个猫叫,此起彼伏。

厦门大学旁边有一个西村,简直太大学了,好像所有的大学附近都有这么一个地方:各种小店门头林立,装修散发出廉价又想文艺的气息,价格便宜味道普通,但是都有一个文艺的名字。在这里同样也没找到啥好吃的。

铁路文化公园很漂亮,废弃的铁路改造的狭长的公园。我还是第一次见这么长形状的公园。

植物园、南普陀这些地方还没来得及去。

几点到厦门的旅行建议:

  • 坐飞机非常方便,高崎机场现在离市中心很近(离我们住的酒店就4km),高铁站倒是挺远的,所以建议坐飞机来
  • 提前做好攻略,提前准备,非常重要!下面是一些必须提前预定的东西
    • 鼓浪屿限制上岛人数,五一不提前三五天基本搞不到票,我们只买到下午上岛的了,尽量提前看票;飞猪有旅行社可以帮你抢,他们成功率高一些,但是票贵,2-3倍的价格;
    • 环岛巴士在网上(美团大众点评、飞猪等)订票的话,要付钱之后2小时才能出票,所以不要想着去了再在网上买票,要提前2小时买。实在不行现场买也可以,网上买45/人,现场买60/人。早上8:30 – 下午16:40 不限次数随便上下。
    • 厦门大学控制入校游客数,不要想着混进去,要提前预约。
  • 不要让司机推荐你去哪玩,你问10个司机,9个告诉你去坐帆船。帆船并不好玩,他们拿回扣的。

下面是照片的分割线。


环岛路

观光巴士风景

对面就是鼓浪屿

厦门路边种了很多这样的花,美

环岛路高架

厦门大学附近的“西村”

西村里面的猫文化

铁路公园,晚上拍的,不是很好

沙滩

海边

杂乱的局口小吃街

厦门地铁

中山步行街

八市

第一次体验缆车

车上没有玻璃,门就一个栓子插着,感觉挺危险,挂个风我就掉下去了。

去之前我还专门学习了缆车的结构

好在不是很高,下面都是树木

大名鼎鼎的惠源面包店,可惜我去的时候说还没烤好,要下午三点。隐藏在八市中的看起来非常不起眼的一家小店。

鼓浪屿上面的猫,看起来挺抑郁。猫又出不了岛,应该跟岛上其他的猫都认识吧。

日光岩18:00之后票价半价,现在日落时间是18:35左右,正好上去看日落

俯瞰厦门市

等待日落

鼓浪屿码头,游客专用

福建土楼,田螺坑(四菜一汤)

土楼

土楼2

《大鱼海棠》里面的土楼

土楼3

土楼4

这是我女朋友

土楼的照片是我女朋友用 iPhone Xs Max 拍摄;其余照片是 iPhone 6s 拍摄。图片经过了压缩。

 

Python 为什么这么慢?

Python 在近几年变得异常流行,Python 语言学习成本低,写出来很像伪代码(甚至很像英语),可读性高,等等有很多显而易见的有点。被 DevOps, Data Science, Web Development 各种场景所青睐。但是这些美誉里面从来都没有速度。相比于其他语言,无论是 JIT 的,还是 AOT 的,Python 几乎总是最慢的。导致 Python 的性能问题的有很多方面,本文尝试谈论一下这个话题。

TL;DR

  1. Python 有 GIL
  2. Python 是一种“解释型”语言
  3. Python 是动态类型的语言

GIL

现代计算机处理器一般都会有多核,甚至有些服务器有多个处理器。所以操作系统抽象出 Thread,可以在一个进程中 spawn 出多个 Thread,让这些 Thread 在多个核上面同时运行,发挥处理器的最大效率。(在 top 命令里面可以看到系统中的 threads 数量)

所以很显然,在编程时使用 Thread 来并行化运行可以提升速度。

但是 Python (有时候)不行。

Python 是不需要你手动管理内存的(C 语言就需要手动 malloc/free),它自带垃圾回收程序。意思是你可以随意申请、设置变量,Python 解释器会自动判断这个变量什么时候会用不到了(比如函数退出了,函数内部变量就不用到了),然后自动释放这部分内存。实现垃圾回收机制有很多种方法,Python 选择的是引用计数+分代回收。引用计数为主。原理是每一个对象都记住有多少其他对象引用了自己,当没有人引用自己的时候,就是垃圾了。

但是在多线程情况下,大家一起运行,引用计数多个线程一起操作,怎么保证不会发生线程不安全的事情呢?很显然多个线程操作同一个对象需要加锁。

这就是 GIL,只不过这个锁的粒度太大了,整个 Python 解释器全局只有一个 Thread 可以运行。详见 dabeaz 博客

绿色表示正在运行的线程,一次只能有一个

因为其他语言没有 GIL,所以很多人对 GIL 误解。比如:

“Python 一次只能运行一个线程,所以我写多线程程序是不需要加锁的。” 这是不对的,“一次只能运行一个线程”指的是 Python 解释器一次只能运行一个线程的字节码(Python 代码会编译成字节码给Python虚拟机运行),是 opcode 层面的。一行 Python 代码,比如 a += 1 ,实际上会编译出多条 opcode:先 load 参数 a,然后 a + 1,然后保存回参数 a。假如 load 完成还没计算,这时候线程切换了,其他线程修改了 a 的值,然后切换回来继续执行计算和存储 a,那么就会造成线程不安全。所以多线程同时操作一个变量的时候,依然需要加锁。

“Python 一次只能运行一个线程,所以 Python 的多线程是没有意义的。” 这么说也不完全对。假如你要用多线程利用多核的性能,那 Python 确实不行。但是假如 CPU 并不是瓶颈,网络是瓶颈,多线程依然是有用的。通常的编程模式是一个线程处理一个网络连接,虽然只有一个线程在运行,但其他线程都在等待网络连接,也不算“闲着”。简单说,CPU 密集型的任务,Python 的多线程确实没啥用(甚至因为多线程切换的开销还会比单线程慢),IO 密集型的任务,Python 的多线程依然可以加速。

这么说可能比较好理解:无论你的电脑的 CPU 有多少核,对 Python 来说,它只用 1 个核。

其他的 Python Runtime 呢?Pypy 有 GIL,但是可以比 CPython 快 3x。Jython 是基于 JVM 的,JVM 没有 GIL,所以 Jython 依然 JVM 的内存分配,它也没有 GIL。

其他语言呢?刚刚说了 JVM,Java 也是用的引用计数,但是它的的 GC 是 multithread-aware 的,实现上更复杂一些(有朋友跟我说 Java 已经不是引用计数了,这个地方请读者注意,附一个参考资料)。JavaScript 是单线程异步编程的模式,所以它没有这个问题。

作为一个解释型的语言……

像 C/C++/Rust 这些语言直接编译成机器码运行,是编译型语言;Python 的运行过程是虚拟机读入 Python 代码(文本),词法分析,编译成虚拟机认识的 opcode,然后虚拟机解释 opcode 执行。但这其实不是最主要的原因,Python import 之后会缓存编译后的 opcode,(pyc 文件或者 __pycache__ 文件夹)。所以读入、词法分析和编译并没有占用太多的时间。

那么真正的慢的是哪一步分呢?就是后面的虚拟机解释 opcode 执行的部分。前期的编译是将 Python 代码编译成解释器可以理解的中间代码,解释器再将中间代码翻译成 CPU 可以理解的指令。相比于 AOT(提前编译型语言,比如C)直接编译成机器码,肯定是慢的。

但是为什么 Java 不慢呢?

因为 Java 有 JIT。即时编译技术将代码分成 frames,AOT 编译器负责在运行时将中间代码翻译成 CPU 可以理解的代码。这一部分跟 Python 的解释器没有太大的区别,依然是翻译中间代码、执行。真正快的地方是,JIT 可以在运行时做优化,比如虚拟机发现一段代码在频繁执行(大多数情况下我们的程序都在反复执行一段代码),就会开始优化,将这段代码用更改的版本替换掉。这是仅有虚拟机语言才有的优势,因为要收集运行时信息。像 gcc 这种 AOT编译器,只能基于静态分析做一些分析。

为什么 Python 没有 JIT 呢?

第一是 JIT 开发成本比较高,非常复杂。C# 也有很好的 JIT,因为微软有钱。

第二是 JIT 启动速度慢,Java 和 C# 虚拟机启动很多。CPython 也很慢,Pypy 有 JIT,它比 CPython 还要慢 2x – 3x。长期运行的程序来说,启动慢一些没有什么,毕竟运行时间长了之后代码会变快,收益更高。但是 CPython 是通用目的的虚拟机,像命令行程序来说,启动速度慢体验就差很多了。

第三是 Java 和 C# 是静态类型的虚拟机,编译器可以做一些假设。(这么说不知道对不对,因为 Lua 也有很好的 JIT)

动态类型

静态类型的语言比如 C,Java,Go,需要在声明变量的时候带上类型。而 Python 就不用,Python 帮你决定一个变量是什么类型,并且可以随意改变。

动态类型为什么慢呢?每次检查类型和改变类型开销太大;如此动态的类型,难以优化。

动态类型带来好处是,写起来非常简单,符合直觉(维护就是另一回事了);可以在运行时修改对象的行为,Monkey Patch 非常简单。

近几年的语言都是静态类型的,比如 Go,Rust。静态类型不仅对编译器来说更友好,对程序员来说程序也更好维护。个人认为,未来是属于静态类型的。

 

阅读资料:

  1. Python 官方 wiki
  2. Removing Python’s GIL: The Gilectomy
  3. David Beazley 有关 GIL 的 Slides:http://www.dabeaz.com/GIL/ ,视频(比较糊,毕竟2010年的)
  4. Gilectomy的最新动态
  5. https://hackernoon.com/why-is-python-so-slow-e5074b6fe55b
  6. https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/
  7. https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/
 

连接池中的连接失效的几种处理方案

在分布式系统中,用连接池缓存住连接,来节省连接反复销毁和创建的成本,是一种很常见的做法。但是在高可用的分布式系统中,”切换”是一个非常普遍的操作,切换就会造成连接池失效的问题。

TCP 虽说是”有连接的”,但这个连接实际上是一个虚拟连接。客户端用一段内存保存连接的五元组(源端口,源IP,目的端口,目的IP,协议),服务端也保存一个这样的五元组,双方就认为有这么一个连接了,可以通过这个连接收发。

但是假如客户端保存有这个五元组,服务器因为某种原因将这个五元组删掉了,就会造成客户端单方面认为有这么一个连接,而服务器不承认(反过来也有这个问题,但是服务器丢失连接的情况更加普遍也更加严重)。当客户端想使用这个连接向服务器发送请求的时候,会被服务器拒绝。

保持一个连接池的做法会使这个问题更加严重,因为连接用完之后不会马上销毁,等待下一次使用,这段时间天知道会发生什么。比如说应用连接数据库这种情况,一般会有连接池保持和数据库的连接。假如 DB 重启了、DB 端因为超时或者其他什么原因把连接关闭了(即把内存的五元组删掉)、DB 出现故障,从一个IP迁移到另一个IP了,都会造成客户端连接池中的连接不可用,这个时候客户端通过这些”坏连接”向DB发送请求,就会失败。(多久时间会返回失败也是一个问题,这受 socketTimeout 参数和 Linux 系统参数影响,具体可见这篇 Datebase timeouts.)

除了DB之外,像 Nginx 保持向后端应用的连接等负载均衡设备通常也有这种问题,只不过有的组件自身选好了方案处理掉了,有的是暴露出来选项让你自己设置。本文讨论处理这种问题的几种思路。

一、失败了就是败了,重建就是

这是成本最低的方式,连接池的实现一般都会包括如果连接不可用,就删除并重建。影响是会失败一些请求(数量=连接池的数量)。

当然有些时候我们是可以预测需要切换DB的,有条件的话可以将业务切换走,然后再切换DB。业务回切之前先对应用发送一波压测流量,用假的业务流量刷掉不用的连接。

二、主动保持

之前在博客中介绍过 TCP Keepalive,可以正好解决这种场景。就是设置一个定时器,定时检查连接是否还可用。

对于 TCP 来说,由于这是一个基于流的协议,所以可以发送一段长度为0的片段来检验是否能收到 ACK。对于 MySQL 来说,要看驱动程序的实现,比如如果实现了 ping 函数可以使用 ping;如果没有 ping 可以使用 SELECT 1。对于 redis 来说也有 ping 命令。Jboss 提供的 background-validation 参数,就是这个原理。

一般来说这个方案是复杂度低,并且比较有效的。缺点是当分布式的连接池巨大的时候,比如上千台机器的话,每台 DB 要承受测活的连接数是 机器数 * 连接池大小,资源消耗是很大的。

三、使用较小的 idle timeout 和 小连接池

连接池一般都提供设置两个参数:

  1. idle timeout:当 idle 时间超过之后,会主动回收;
  2. min connections:连接池最小要保持多少连接;

通过设置 idle timeout,可以保证没有连接是开头说的那种很长时间没用过的连接,因为都会回收掉了。

但是这里会存在一个问题:min connections 要保证连接池有多少个连接,假如设置的是10,平时只用5的话,那么因为 idle timeout 参数会回收5个连接,导致连接池会新建5个新连接。过一段时间,因为有一些用不到又会被回收,就造成了在反复创建连接。

假如采取另一种策略:回收 idle timeout 的连接但是保证连接数要大于 min connections,假如连接数不大于 min connections,那么即使 idle timeout 也不回收。这样可以解决上面的问题,但是会造成无法保证 connection pool 里面的连接都是新鲜的,回到我们最初的问题了。

所以这种方案其实又要做一个 trade off,如果能保证连接需求的比较稳定,需要的连接数总是大于 min connections 又不至于大太多,倒是可以用在这种方案。

四、每次使用之前检查一次

顾名思义,每次使用连接之前先检查一下这个连接是否可用。成本太高一般不用,对客户端来说会增加延迟,对DB来说会增加压力。

实现的话,可以看下 DBCP 的 TestOnBorrow

五、HAProxy

HAProxy 并不能说是一个思路,只是一个现成的工具,它是一个正向代理,实现了连接的测活和切换。HAProxy 的原理我还没研究,但是应该不出这几种思路。甚至是可以让你选的,毕竟它配置项目那么多。

HAProxy 怎么用呢?考虑有多个应用连接多个数据库的场景,从原来的直接连接变成通过 HAProxy 连接。当需要切换的时候,应用不需要感知 DB IP 的变化,而是 HAProxy 更改后端 DB 的 IP,从而对应用透明了,应用只需要一个 DB 集群的 URL 就可以了。

App 直接连接 DB

App 通过 HAProxy 连接 DB

 

另一方面,也节省了 DB 的连接数。