首页

嗨,欢迎来到25Qi网址导航

客服QQ:2598903095

科技资讯

黑科技解密!实现socket进程间迁移

时间:2021-09-01 丨 作者:小姐姐味道架构设计 丨 关键词:黑科技

原创:小姐姐味道(微信公众号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用户给馋哭的。

 

推荐阅读:

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

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

最新收录
  • 祥鹏航空官方网站

    祥鹏航空是海航集团下属成员企业,公司注册地为云南省昆明市,运营基地为云南昆明、丽江、西双版纳,四川成都、绵阳,河南郑州等。2006年2月26日,祥鹏航空顺利开航。2008年6月,云南省国资委与海航集团签署战略合作协议,双方共建祥鹏航空。2016年,祥鹏航空正式实施低成本战略转型,旨在为旅客提供更多差异化的优质服务。 目前,祥鹏航空建立了以昆明为中心,连通全国一、二线及各大省会城市,辐射东南亚、东亚的立体航线网络。公司曾荣获中国最佳旅游供应商、消费者信赖企业、旅客话民航用户满意优质奖、中国最具发展潜力

  • 幸福航空官网

    幸福航空有限责任公司(以下简称“幸福航空”)于 2008 年由中 国航空工业集团发起组建,2018 年 11 月起由西安航空航天投资股份 有限公司控股,是由西安国资控股的航空公司。幸福航空成立以来一 直承担着支持我国国产民机发展的重任,为国产民机推广和应用做出 了巨大的贡献。 目前,幸福航空是全球最大且唯一形成商业规模的国产民机运营 商,拥有 24 架新舟 60 飞机,3 架波音 737 飞机,员工 1000 余名, 建成过夜基地 6 个,开通航线 40 余条。

  • Klook客路-探索景点门票、行程、票券

    探索热门旅游推荐景点,带你走访当地最热门的景点胜地,预订全球超过300个以上的目的地行程。Klook客路提供你最优惠价格的景点门票、一日游行程与当地交通,立即线上预订,立刻出票,现场免排队直接入场。

  • 九元航空官方网站-飞机票查询预订_航班查询

    九元航空网依托广州九元航空提供飞机票特价机票打折机票查询预定,机票预订,及国际机票、电子机票、航班查询飞机票。9元,99元国内国际最低价机票,为您提供低价,安全,温馨,优质的服务,24小时免费咨询热线400-105-1999。

  • 爱自由旅游网:爱自由人士的中国旅游网站

    爱自由旅游网是中国专业的旅游入口网站,致力于提供详实、专业的旅游资讯,全国景点深度介绍、旅游线路发布、旅游注意事项、旅游玩家的文章、旅游相册、旅伴交友等服务,让每位旅行者都能获得丰富实用的旅游资讯。

  • 宁波栎社国际机场

    宁波栎社国际机场于1984年建站,1990年6月30日迁至现址,定名为宁波栎社机场;2005年11月29日,经民航总局批复,更名为宁波栎社国际机场。 宁波机场位于浙东鄞西平原,距市区仅 12公里,机场高架路与甬金高速出口相连接,地铁2号线将机场与市内火车站、汽车客运中心相连,客流往返与物流运输均十分便利。 宁波机场在用T2航站楼于2019年12月份启用,候机楼面积11.24万㎡,机坪面积53.2万㎡,机位数量60个,现飞行区跑道长3200米,配备有国际先进的通信导航和航行管制设备,达到4E级标准,