本文转载自微信公众号「小姐姐味道」,作者小姐姐养的狗。转载本文请联系小姐姐味道公众号。
为云城等地区用户提供了全套网页设计制作服务,及云城网站建设行业解决方案。主营业务为成都网站设计、成都做网站、云城网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
今天介绍一个可以拿出去吹牛的功能:实现socket句柄在进程之间迁移!为了这篇文章,xjjdog可算下了苦功夫,半夜还在翻资料。因为需要验证后,才能证明这项技术确实是正确的。
正文。
我们的服务器上,运行着大量的server实例(instance)。这些instance,每个都要承载着数十万的连接和非常繁忙的网络请求。能够把这样的连接数,这样的流量,玩弄于股掌之间,是每个互联网程序员的梦想。
但软件总是要升级的,每当升级的时候,就需要先停掉原来的instance,然后再启动一个新的。在这一停一起之间,数十秒就过去了,更不要说JAVA这种启动时间就能生个孩子的速度了。
传统的做法,是先把这个instance从负载均衡上面摘除,然后启动起来再加上;对于微服务来说,就要先隔离,然后启动后再取消隔离。这些操作,对于海量应用来说,就是个噩梦。
有没有一种方法,能够把一个进程所挂载的连接(socket),转移到另外一个进程之上呢?这样,我在升级的时候,就可可以先启动一个升级版本的进程,然后把老进程的socket,one by one的给转移过去。
实现零停机更新。
这个是可以的。Facebook就实践过类似的技术,它们把这项技术,叫做Socket Takeover。千万别用百度搜这个关键字,你得到的可能是一堆垃圾。
这么牛x的技术,还这么有用,为什么就没人科普呢?别问我,我也不知道,可能大家现在都在纠结怎么研究茴香豆的茴字写法,没时间干正事吧。
那今天就由xjjdog来介绍一下吧,顺便增加一下大家以后的吹牛资本。
这个牛x的功能,是由Linux一对底层的系统调用函数所实现的:sendmsg()和recvmsg()。我们一般在发送网络数据包的时候,一般会使用send函数,但send函数只有在socket处于连接状态时才可以使用;与之不同的是,sendmsg在任何时候都可以使用。
在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);
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);
这样。其他的进程,就可以通过两种不同的方式,来连接我们的服务。
通过端口:进行正常的服务,输出正常的业务数据。执行正常业务
通过UDS:开始接收listen_fd和accept_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_control和msg_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就是我们所需要的,它允许我们从一个进程,发送一个文件句柄到另外一个进程。
- 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句柄就发送到另外一个进程了。
同样的,recvmsg函数,将会接收这部分数据,然后将其还原成cmsghdr结构体。然后我们就可以从cmsg_data中获取句柄列表。
为什么能这么做呢?因为socket句柄,在某个进程里,其实只是一个引用。真正的fd句柄,其实是放在内核中的。所谓的迁移,只不过是把一个指针,从一个进程中去掉,再加到另外一个进程中罢了。
fd句柄的属性,有两种情况。
图片来自论文:(Zero Downtime Release: Disruption-free Load Balancing of a Multi-Billion User Website)
对于普通fd,肯定要调用与原新连接到来时相同的代码逻辑。所以,一个大体的迁移过程,包括:
这是一项黑科技,其实已经在一些主流的应用中使用了。你会看到一些非常眼熟的软件,这项功能是它们的一大卖点。比如HAProxy,运行在4层网络的负载均衡;比如Envoy,Istio默认的数据平面软件,使用类似的技术完成热重启。
其实,在servicemesh的推进过程中,proxy的替换,也会使用类似的技术,比如SOFA。对于golang和C语言来说,由于API暴露的比较好,这种功能可以很容易的实现;但在Java中,却有不少的困难,因为Java的跨平台特性不会做这种为Linux定制的API。
可以看到,sendmsg和recvmsg这两个函数,可以实现的功能非常的酷。它比较适合无状态的proxy服务,如果服务内有状态存留,这种迁移并不见得安全,当然也可以尝试把此项技术运用在一些中间件上。但无论如何,这种黑科技,有一种别样的暴力美,肯定会把windows server用户给馋哭的。
作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。
当前名称:黑科技解密!实现Socket进程间迁移!
本文路径:http://www.mswzjz.com/qtweb/news1/178001.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联