分布式系统的中枢Naming-Service

  上次有朋友说,想让老王讲讲如何部署线上服务的,那这周跟大家分享一下分布式系统里面的一个中枢系统:命名服务。看懂了这个,基本上就知道线上服务是如何部署的了。

  在很久很久以前,有一个叫老王的人为了解决在学校搭服务器性能顶不住的问题,加入了百度去学习技术。刚刚进去的时候,就发现百度真的好先进,用 C 语言写web 服务。逻辑服务层的程序叫做 UI (这个也是用 c 写的,包括页面渲染……),他的配置文件里面有一个长长的列表,这个列表里面都是他连接的存储系统服务器 IP 和Port ,大概长这个样子:

  用户请求到达 UI 程序服务器的时候,他先从 login--list 中按照某种负载均衡算法(比如:随机,忘了的朋友可以看看老王之前写的关于负载均衡的文章),取出一个 ip 和 port ,然后去连接对应的服务器的进程做登录,登录成功后,又取出一个 message--list 中的元素,去获取用户消息。如果获取失败,则按照某种重试的原则,再获取另外一个 ip 和 port 重新连接。

  初出茅庐的老王(那会儿应该还叫小王,正是意气风发的年代)觉得好牛 * 啊,可以用 c 语言程序来实现分布式的程序,通过配置管理服务器列表,这样就可以将请求分担到不同的机器,最终达到应对大流量的要求。

  可是随着在百度呆的日子越来越长,就日益发现这里面问题越来越多。特别是当服务越来越多、流量越来越大的时候,之前的工作方式似乎 work 的不好了。比如,刚刚那个 UI 程序,在线上部署了 10 台机器(比方: 10.0.3.1 - 10.0.3.10 ),这个时候, login 的服务要增加一台机器,怎么办呢?将这个 IP 添加到上述 10 台机器的服务器配置里(这还不复杂,对吧)。后来,流量大了, UI 服务器从 10 台变成了 100 台,这时候,有一台 login 服务器出问题了,要从配置列表中删除,所以…… 运维人员就快疯了。但是,他们想起来,还有一种绝密武器: shell 脚本,可以批量处理。

  又到后来,为了将提交和浏览分隔开来,虽然用了同样的程序,但是他们的配置是不一样的,有些服务器连浏览相关的服务,有些服务器连提交相关的服务。而且产品经理疯狂的加需求,使得后端服务越来越多,这个时候研发说,我要上线改一个 ip 配置,运维人员真的疯了……

  老王离开百度以后,据说逐步解决了这个问题。与此同时,老王加入了百词斩,做了一件现在都觉得千值万值的事情:写了一个系统,他叫 Naming-Service 。

  1 、整体的拓扑架构

  从名字上就可以看出这个系统是跟名字相关的,不错,他就是管理其他服务的服务。所有的线上服务都要到他这里来注册才能提供给其他人服务。是不是觉得好拗口?哈哈,老王画了两张图,大家一看就明白了。

物联网

  这是我们之前的部署拓扑图,看下那些密密麻麻的线,如果再多几台服务器,做运维的同学心里阴影面积会有多大?

物联网

  这是我们增加 Naming-Service 以后的服务拓扑图,从以前的混联状态变成了星形状。所有的服务都将自己的名字、 IP 、 Port 注册到命名服务器,如果其他人要调用这个服务,就去命名服务器根据名字获取对应的 IP:PORT 列表。看,这种结构是不是就清爽很多了。

  就算服务再多 N 倍,我们的架构也是很清晰的(当然,在不同服务量级的体量下,这种架构也可能不是最好的一种结构)。就算服务器再增加 M 台,我们也不需要去改配置。怎么样,是不是感到运维的同学马上就要请你吃饭了呢?

  2 、需要解决的问题

  是不是有这个拓扑结构就完了呢?回答:肯定不是,不然老王怎么凑字数呢,哈哈哈 ~

  其实这种结构我们有几个问题需要去解决:

  A 、服务如何注册?

  老王在做这个系统的时候,采用了两种方式:主动注册和手动添加。

  绝大部分服务(主要是我们自己写的服务)都自己每隔一段时间向 Naming-Service做一次注册,大体像这样的 RPC 调用: regist("simplemain.login","10.0.1.2", 1031) 。Naming-Service 则判断,这个服务器是否已经注册过,如果已经注册过则直接返回,否则向系统里添加一条记录: simplemain.login -> [10.0.1.1:1031, 10.0.1.2:1031 ]。那为什么要隔一段时间就要注册一次呢?老王当时是这样考虑的:如果我们的 Naming-Service 数据遭到了破坏(比如磁盘坏了),我能在很短的时间内,重新注册上这些服务。而且重复注册本身没有太大的代价。