原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

今天介绍一个可以拿出去吹牛的功能:实现socket句柄在进程之间迁移!为了这篇文章,xjjdog可算下了苦功夫,半夜还在翻资料。因为需要验证后,才能证明这项技术确实是正确的。

正文。

我们的服务器上,运行着大量的server实例(instance)。这些instance,每个都要承载着数十万的连接和非常繁忙的网络请求。能够把这样的连接数,这样的流量,玩弄于股掌之间,是每个互联网程序员的梦想。

但软件总是要升级的,每当升级的时候,就需要先停掉原来的instance,然后再启动一个新的。在这一停一起之间,数十秒就过去了,更不要说JAVA这种启动时间就能生个孩子的速度了。

传统的做法,是先把这个instance从负载均衡上面摘除,然后启动起来再加上;对于微服务来说,就要先隔离,然后启动后再取消隔离。这些操作,对于海量应用来说,就是个噩梦。

1. 零停机更新

有没有一种方法,能够把一个进程所挂载的连接(socket),转移到另外一个进程之上呢?这样,我在升级的时候,就可可以先启动一个升级版本的进程,然后把老进程的socket,one by one的给转移过去。

实现零停机更新。

这个是可以的。Facebook就实践过类似的技术,它们把这项技术,叫做Socket Takeover。千万别用百度搜这个关键字,你得到的可能是一堆垃圾。

这么牛x的技术,还这么有用,为什么就没人科普呢?别问我,我也不知道,可能大家现在都在纠结怎么研究茴香豆的茴字写法,没时间干正事吧。

那今天就由xjjdog来介绍一下吧,顺便增加一下大家以后的吹牛资本。

这个牛x的功能,是由Linux一对底层的系统调用函数所实现的:sendmsg()recvmsg()。我们一般在发送网络数据包的时候,一般会使用send函数,但send函数只有在socket处于连接状态时才可以使用;与之不同的是,sendmsg在任何时候都可以使用。

2. 技术要点

在c语言网络编程中,首先要通过listen函数,来注册监听地址,然后再用accept函数接收新连接。比如:

int listen_fd = socket(addr->ss_family, SOCK_STREAM, 0);
...
bind(listen_fd, (struct sockaddr *) addr, addrlen);
...
int accept_fd = accept(fd, (struct sockaddr *) &addr, &addrlen);

我们首先要做的,就是把listen_fd,从一个进程,传递到另外一个进程中去。怎么发送呢?肯定是要通过一个通道的。在Linux上,那就是UDS,全称Unix Domain Sockets

2.1 Unix Domain Sockets监听

UDS(Unix Domain Sockets)在Linux上的表现,是一个文件。相比较于普通socket监听在端口上,一个进程也可以监听在一个UDS文件上,比如/tmp/xjjdog.sock。由于通过这个文件进行数据传输,并不需要走网卡等物理设备,所以通过UDS传输数据,速度是非常快的。

但今天我们不关心它有多块,而是关心它多有用。通过bind函数,我们同样可以通过这个文件接收连接,就像端口接收连接一样。

struct sockaddr_un addr;
char *path="/tmp/xjjdog.sock";
int err, fd;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, strlen(path));
addrlen = sizeof(addr.sun_family) + strlen(path);
err = bind(fd, (struct sockaddr *) &addr, addrlen);
...
accept_fd = accept(fd, (struct sockaddr *) &addr, &addrlen);


这样。其他的进程,就可以通过两种不同的方式,来连接我们的服务。

  1. 通过端口:进行正常的服务,输出正常的业务数据。执行正常业务

  2. 通过UDS:开始接收listen_fdaccept_fd们。执行不停机迁移socket业务

2.2 fd迁移技术要点

怎么迁移呢?我们关键看第二步。

实际上,当新升级的服务通过UDS连接上来,我们就开始使用sendmsg函数,将listen_fd给转移过去。

我们来看一下sendmsg这个函数的参数。

ssize_t sendmsg(int socket,const struct msghdr *message,int flags
);

socket可以理解为我们的UDS连接。关键在于msghdr这个结构体。

struct msghdr {void            *msg_name;      /* optional address */socklen_t       msg_namelen;    /* size of address */struct          iovec *msg_iov; /* scatter/gather array */int             msg_iovlen;     /* # elements in msg_iov */void            *msg_control;   /* ancillary data, see below */socklen_t       msg_controllen; /* ancillary data buffer len */int             msg_flags;      /* flags on received message */
};

其中, msg_iov表示要正常发送的数据,比如HelloWord;除此之外,还有两个ancillary (附属的) 的变量,提供了附加的功能,那就是变量msg_controlmsg_controllen。其中,msg_control又指向了另外一个结构体cmsghdr

struct cmsghdr {socklen_t cmsg_len;    /* data byte count, including header */int       cmsg_level;  /* originating protocol */int       cmsg_type;   /* protocol-specific type *//* followed by */unsigned char cmsg_data[];
};

在这个结构体中,有一个叫做cmsg_type的成员变量,是我们实现socket迁移的关键。

它共有三个类型。

  • SCM_RIGHTS

  • SCM_CREDENTIALS

  • SCM_SECURITY

其中,SCM_RIGHTS就是我们所需要的,它允许我们从一个进程,发送一个文件句柄到另外一个进程。


struct msghdr msg;
...
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;//socket fd列表,设置在cmsg_data上
int *fds = (int *) CMSG_DATA(cmsg);

依靠sendmsg函数,socket句柄就发送到另外一个进程了。

3. 接收和还原

同样的,recvmsg函数,将会接收这部分数据,然后将其还原成cmsghdr结构体。然后我们就可以从cmsg_data中获取句柄列表。

为什么能这么做呢?因为socket句柄,在某个进程里,其实只是一个引用。真正的fd句柄,其实是放在内核中的。所谓的迁移,只不过是把一个指针,从一个进程中去掉,再加到另外一个进程中罢了。

fd句柄的属性,有两种情况。

  • 监听fd,直接调用accept函数作用在fd上即可

  • 普通fd,需要将其还原成正常的socket


图片来自论文:(Zero Downtime Release: Disruption-free Load Balancing of a Multi-Billion User Website)

对于普通fd,肯定要调用与原新连接到来时相同的代码逻辑。所以,一个大体的迁移过程,包括:

  1. 首先迁移listener fd到新进程,并开启监听,以便新进程能快速接收新的请求。如果我们开启了SO_REUSEADDR选项,新老服务甚至能够一起进行服务

  2. 等待新进程预热之后,停掉原进程的监听

  3. 迁移原老进程中的大量socket,这些socket可能有数万条,最好编码能看到迁移进度

  4. 新进程接收到这些socket,陆续将其还原为正常的连接。相当于略过了accept阶段,直接就获取了socket列表

  5. 迁移完毕,老进程就空转了,此时可以安全的停掉

4. End

这是一项黑科技,其实已经在一些主流的应用中使用了。你会看到一些非常眼熟的软件,这项功能是它们的一大卖点。比如HAProxy,运行在4层网络的负载均衡;比如Envoy,Istio默认的数据平面软件,使用类似的技术完成热重启。

其实,在servicemesh的推进过程中,proxy的替换,也会使用类似的技术,比如SOFA。对于golang和C语言来说,由于API暴露的比较好,这种功能可以很容易的实现;但在Java中,却有不少的困难,因为Java的跨平台特性不会做这种为Linux定制的API。

可以看到,sendmsg和recvmsg这两个函数,可以实现的功能非常的酷。它比较适合无状态的proxy服务,如果服务内有状态存留,这种迁移并不见得安全,当然也可以尝试把此项技术运用在一些中间件上。但无论如何,这种黑科技,有一种别样的暴力美,肯定会把windows server用户给馋哭的。

作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

推荐阅读:

1. 玩转Linux
2. 什么味道专辑

3. 蓝牙如梦
4. 杀机!
5. 失联的架构师,只留下一段脚本
6. 架构师写的BUG,非比寻常

黑科技解密!实现socket进程间迁移!相关推荐

  1. 腾讯游戏许振文:王者荣耀实时大数据平台黑科技解密

    ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍ 许振文 腾讯游戏 增值服务部后台开发组组长 读完需要 20 分钟 速读仅需 5 分钟 从事游戏大数据相关领域 8 年多,负责游戏数据分析平台 i ...

  2. CANN 5.0黑科技解密 | 算力虚拟化,让AI算力“物尽其用”

    摘要:算力虚拟化技术对消费者而言,可有效降低算力的使用成本,对于设备商或运营商而言,则可极大提升算力资源的利用率,降低设备运营成本. 为什么要做算力虚拟化 近年来,人工智能领域呈井喷式发展,算力就是生 ...

  3. CANN5.0黑科技解密 | 高并发图片视频处理,为出行保驾,为生活添彩

    摘要:华为推出昇腾AI基础软硬件平台(昇腾AI处理器+异构计算架构CANN),不仅能高效承接各类人工智能计算任务,还可两招解决以上图像处理面临的诸多问题. 四通八达的路网和车水马龙的盛景诠释着城市的繁 ...

  4. CANN5.0黑科技解密 | 别眨眼,缩小隧道,让你的AI模型“身轻如燕”

    摘要:CANN作为释放昇腾硬件算力的关键平台,通过深耕先进的模型压缩技术,聚力打造AMCT模型压缩工具,在保证模型精度前提下,不遗余力地降低模型的存储空间和计算量. 随着深度学习的发展,推理模型巨大的 ...

  5. Linux 进程间通讯详解一

    进程间的通讯 两台主机间的进程通讯 --socket一台主机间的进程通讯 --管道(匿名管道,有名管道) --System V进程间通信(IPC)包括System V消息队列,System V信号量, ...

  6. 进程间的7种通信方式(含例程代码)

    下面的实验由我和我的同学完成的,这是操作系统课程的一个小实验,可以帮助理解进程间的通信. 进程间的通信 1.匿名管道 2.命名管道 3.消息队列 4.共享内存 5.信号 6.信号量 7.socket ...

  7. 手机安全很重要,局座张召忠为华为Mate 20生物识别黑科技点赞

    张召忠,出生于1952年,虽然作为海军少将已正式退休,但仍然奋战在军事评论岗位上.以犀利而富有娱乐色彩的军事评论启蒙了众多军迷,成为带动全社会军事热情的领军人物,被称为军事科普第一人. 张召忠的军事. ...

  8. 进程间的7种通信方式全解析及代码示例

    目录 1.匿名管道 2.命名管道 3.消息队列 4.共享内存 5.信号 6.信号量 7.socket 进程间的7种通信方式如下: 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在 ...

  9. python socket tcp远控_Python3实现ICMP远控后门(中)之“嗅探”黑科技

    ICMP后门 前言 在上两篇文章中,详细讲解了ICMP协议,同时实现了一个具备完整功能的ping工具,完整的代码发布在https://github.com/qiyeboy/LuLunZi/blob/m ...

最新文章

  1. python中eval与json.loads对json的处理
  2. Lisp语言: 在Windows下搭建CLisp环境
  3. MongoDB 主从集群配置
  4. 新入行程序员须知的8件事
  5. Android studio如何更改应用程序的图标以及名称
  6. java se开发_JAVA_SE基础——3.Java程序的开发流程
  7. lia人是什么意思_老话说“人穷别说话,位卑莫劝人”,什么意思?有何道理?...
  8. Python的for循环显示每个元素的下标
  9. 2020 年 9 月程序员工资统计,新出炉!
  10. vscode-扩展插件
  11. Anaconda 最新版本的下载和安装
  12. WiFi语音智能家居控制系统(一)
  13. XJOI 3709 测测你的RP
  14. 【1194】移动路线
  15. openjudge7939_膨胀的木棍
  16. BZOJ2900 好玩的数字游戏
  17. 好用的记事提醒软件,手机记事提醒便签下载
  18. 【unity 保卫星城】--- 开发笔记(Demo演示篇)
  19. win7防火墙例外设置方法_win7防火墙添加端口方法
  20. 内外升级动力不变 北京现代第四代途胜上市15.59-23.99万

热门文章

  1. Intellij IDEA 将.java文件识别成文本文档
  2. cad2016中选择全图字体怎么操作_CAD镜像怎么用,你会吗?
  3. ZZULIOJ:1008美元和人民币
  4. iOS中Mach异常和signal信号介绍,以及当APP崩溃时做线程保活弹出程序异常提示框
  5. go语言 Golang官网被墙解决办法
  6. 【超全】一文详解机器学习特征工程(附代码)
  7. 某易—将军令动态刨析算法(1)
  8. 前端上传图片并修改名字(数据库的图片名字,并非原图片名字)
  9. cmd看excel有多少个子表_excel表格拆分成多个表格方法工具
  10. 蛙蛙推荐:如何编写高质量的python程序