分布式系统在如今越来越普及,了解分布式系统中的原理与实现更是很重要,本系列从分布式原理以及性能优化角度来剖析分布式架构

彻底搞通服务发现的原理和实现

服务发现,作为互联网从业人员,大家应该都不陌生,一个完善的服务集群,微服务是必不可少的功能之一。

最近一直想写这个话题,也一直在构思,但不知道从何入手,或者说不知道写哪方面。如果单纯写如何实现,这个未免太乏味枯燥了;而如果只是介绍现有成熟方案呢,却达不到我的目的。想了很久,准备先从微服务的架构入手,切入 服务发现 要解决什么问题,搭配常见的处理模式,最后介绍下现有的处理方案。

微服务服务于分布式系统,是个分散式系统。服务部署跨主机、网段、机房乃至大区。各个服务之间通过 RPC(remote procedure call)进行调用。然后,在架构上最重要的一环,就是服务发现。如果说服务发现是微服务架构的灵魂也当之无愧,试想一下,当一个系统被拆分成多个服务,且被大量部署的时候,有什么能比"找到"想调用的服务在哪里,以及能否正常提供服务重要呢?同样的,有新服务启动时,如何让其他服务知道该服务在哪里?

微服务考研的是治理大量服务的能力,包含多种服务,同样也包含多个实例。

概念

服务发现之所以重要,是因为它解决了微服务架构最关键的问题:如何精准的定位需要调用的服务 ip 以及端口。无论使用哪种方式来提供服务发现功能,大致上都包含以下三点:

  • Register, 服务启动时候进行注册
  • Query, 查询已注册服务信息
  • Healthy Check,确认服务状态是否健康

整个过程很简单。大致就是在服务启动的时候,先去进行注册,并且定时反馈本身功能是否正常。由服务发现机制统一负责维护一份正确或者可用的服务清单。因此,服务本身需要能随时接受查下,反馈调用方服务所要的信息。

注册模式

一整套服务发现机制顺利运行,首先就得维护一份可用的服务列表。包含服务注册与移除功能,以及健康检查。服务是如何向注册中心"宣告"自身的存在?健康检查,是如何确认这些服务是可用的呢?

做法大致分为两类:

  • 自注册模式自注册,顾名思义,就是上述这些动作,有服务(client)本身来维护。每个服务启动后,需要到统一的服务注册中心进行注册登记,服务正常终止后,也可以到注册中心移除自身的注册记录。在服务执行过程中,通过不断的发送心跳信息,来通知注册中心,本服务运行正常。注册中心只要超过一定的时间没有收到心跳消息,就可以将这个服务状态判断为异常,进而移除该服务的注册记录。
  • 三方注册模式这个模式与自注册模式相比,区别就是健康检查的动作不是有服务本身(client)来负责,而是由其它第三方服务来确认。有时候服务自身发送心跳信息的方式并不精确,因为可能服务本身已经存在故障,某些接口功能不可用,但仍然可以不断的发送心跳信息,导致注册中心没有发觉该服务已经异常,从而源源不断的将流量打到已经异常的服务上来。

这时候,要确认服务是否正常运转的健康检查机制,就不能只依靠心跳,必须通过其它第三方的验证(ping),不断的从外部来确认服务本身的健康状态。

这些都是有助于协助注册中心提高服务列表精确到的方法。能越精确的提高服务清单状态的可靠性,整套微服务架构的可靠度就会更高。这些方法不是互斥的,在必要的时候,可以搭配使用。

发现模式

服务发现的发现机制主要包括三种:

  • 服务提供者:服务启动时将服务信息注册到注册中心,服务退出时将注册中心的服务信息删除掉。
  • 服务消费者:从服务注册表获取服务提供者的最新网络位置等服务信息,维护与服务提供者之间的通信。
  • 注册中心:服务提供者和服务消费者之间的一个桥梁

服务发现机制的关键部分是注册中心。注册中心提供管理和查询服务注册信息的 API。当服务提供者的实例发生变更时(新增/删除服务),服务注册表更新最新的状态列表,并将其最新列表以适当的方式通知给服务消费者。目前大多数的微服务框架使用 Netflix Eureka、Etcd、Consul 或 Apache Zookeeper 等作为注册中心。

为了说明服务发现模式是如何解决微服务实例地址动态变化的问题,下面介绍两种主要的服务发现模式:

  • 客户端发现模式
  • 服务端发现模式。

客户端模式与服务端模式,两者的本质区别在于,客户端是否保存服务列表信息。

客户端发现模式

在客户端模式下,如果要进行微服务调用,首先要进行的是到服务注册中心获取服务列表,然后再根据调用端本地的负载均衡策略,进行服务调用。

在上图中,client 端提供了负载均衡的功能,其首先从注册中心获取服务提供者的列表,然后通过自身负载均衡算法,选择一个最合理的服务提供者进行调用:

1、 服务提供者向注册中心进行注册,提交自己的相关信息

2、 服务消费者定期从注册中心获取服务提供者列表

3、 服务消费者通过自身的负载均衡算法,在服务提供者列表里面选择一个合适的服务提供者,进行访问

客户端发现模式的优缺点如下:

  • 优点:
  • 负载均衡作为 client 中一个功能,用自身的算法,从服务提供者列表中选择一个合适服务提供者进行访问,因此 client 端可以定制化负载均衡算法。优点是服务客户端可以灵活、智能地制定负载均衡策略,包括轮询、加权轮询、一致性哈希等策略。
  • 可以实现点对点的网状通讯,即去中心化的通讯。可以有效避开单点造成的性能瓶颈和可靠性下降等问题。
  • 服务客户端通常以 SDK 的方式直接引入到项目,这种方式语言的整合程度最佳,程序执行性能最佳,程序错误排查更加容易。
  • 缺点:
  • 当负载均衡算法需要更新时候,很难做到同一时间全部更新,所以就造成新旧算法同时运行
  • 与注册中心紧密耦合,如果要换注册中心,需要去修改代码,重新上线。微服务的规模越大,服务更新越困难,这在一定程度上违背了微服务架构提倡的技术独立性。

目前来说,大部分服务发现的实现都采取了客户端模式。

服务端发现模式

在服务端模式下,调用方直接向服务注册中心进行请求,服务注册中心再通过自身负载均衡策略,对微服务进行调用。这个模式下,调用方不需要在自身节点维护服务发现逻辑以及服务注册信息。

在服务端模式下:1、 服务提供者向注册中心进行服务注册 2、 注册中心提供负载均衡功能,3、 服务消费者去请求注册中心,由注册中心根据服务提供列表的健康情况,选择合适的服务提供者供服务消费者调用

现代容器化部署平台(如 Docker 和 Kubernetes)就是服务端服务发现模式的一个例子,这些部署平台都具有内置的服务注册表和服务发现机制。容器化部署平台为每个服务提供路由请求的能力。服务客户端向路由器(或者负载均衡器)发出请求,容器化部署平台自动将请求路由到目标服务一个可用的服务实例。因此,服务注册,服务发现和请求路由完全由容器化部署平台处理。

服务端发现模式的特点如下:

  • 优点:
  • 服务消费者不需要关心服务提供者的列表,以及其采取何种负载均衡策略
  • 负载均衡策略的改变,只需要注册中心修改就行,不会出现新老算法同时存在的现象
  • 服务提供者上下线,对于服务消费者来说无感知
  • 缺点:
  • rt 增加,因为每次请求都要请求注册中心,尤其返回一个服务提供者
  • 注册中心成为瓶颈,所有的请求都要经过注册中心,如果注册服务过多,服务消费者流量过大,可能会导致注册中心不可用
  • 微服务的一个目标是故障隔离,将整个系统切割为多个服务共同运行,如果某服务无法正常运行,只会影响到整个系统的相关部分功能,其它功能能够正常运行,即去中心化。然而,服务端发现模式实际上是集中式的做法,如果路由器或者负载均衡器无法提供服务,那么将导致整个系统瘫痪。

实现方案

file

以文件的形式实现服务发现,这是一个比较简单的方案。其基本原理就是将服务提供者的信息(ip:port)写入文件中,服务消费者加载该文件,获取服务提供者的信息,根据一定的策略,进行访问。

需要注意的是,因为以文件形式提供服务发现,服务消费者要定期的去访问该文件,以获得最新的服务提供者列表,这里有个小优化点,就是可以有个线程定时去做该任务,首先去用该文件的最后一次修改时间跟服务上一次读取文件时候存储的修改时间做对比,如果时间一致,表明文件未做修改,那么就不需要重新做加载了,反之,重新加载文件。

文件方式实现服务发现,其特点显而易见:

  • 优点:实现简单,去中心化
  • 缺点:需要服务消费者去定时操作,如果某一个文件推送失败,那么就会造成异常现象

zookeeper

ZooKeeper 是一个集中式服务,用于维护配置信息、命名、提供分布式同步和提供组服务。

zookeeper 是一个树形结构,如上图所示。

使用 zookeeper 实现服务发现的功能,简单来讲,就是使用 zookeeper 作为注册中心。服务提供者在启动的时候,向 zookeeper 注册其信息,这个注册过程其实就是实际上在 zookeeper 中创建了一个 znode 节点,该节点存储了 ip 以及端口等信息,服务消费者向 zookeeper 获取服务提供者的信息。服务注册、发现过程简述如下:

  • 服务提供者启动时,会将其服务名称,ip 地址注册到配置中心
  • 服务消费者在第一次调用服务时,会通过注册中心找到相应的服务的 IP 地址列表,并缓存到本地,以供后续使用。当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从 IP 列表中取一个服务提供者的服务器调用服务
  • 当服务提供者的某台服务器宕机或下线时,相应的 ip 会从服务提供者 IP 列表中移除。同时,注册中心会将新的服务 IP 地址列表发送给服务消费者机器,缓存在消费者本机
  • 当某个服务的所有服务器都下线了,那么这个服务也就下线了
  • 同样,当服务提供者的某台服务器上线时,注册中心会将新的服务 IP 地址列表发送给服务消费者机器,缓存在消费者本机
  • 服务提供方可以根据服务消费者的数量来作为服务下线的依据

服务注册

假设我们服务提供者的服务名称为 services,首先在 zookeeper 上创建一个 path /services,在服务提供者启动时候,向 zookeeper 进行注册,其注册的原理就是创建一个路径,路径为/services/$ip:port,其中 ip:port 为服务提供者实例的 ip 和端口。如下图所示,我们现在 services 实例有三个,其 ip:port 分别为 192.168.1.1:1234、192.168.1.2:1234、192.168.1.3:1234 和 192.168.1.4:1234,如下图所示:

健康检查

zookeeper 实现了一种 TTL 的机制,就是如果客户端在一定时间内没有向注册中心发送心跳,则会将这个客户端摘除。

获取服务提供者的列表

前面有提过,zookeeper 实际上是一个树形结构,那么服务消费者是如何获取到服务提供者的信息呢?最重要的也是必须的一点就是 知道服务提供者信息的父节点路径。以上图为例,我们需要知道

/services

复制代码

通过 zookeeper client 提供的接口 getchildren(path)来获取所有的子节点。

感知服务上线与下线

zookeeper 提供了“心跳检测”功能,它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除,比如 192.168.1.2 这台机器如果宕机了,那么 zookeeper 上的路径/services/下就会只剩下 192.168.1.1:1234, 192.168.1.2:1234,192.168.1.4:1234。如下图所示:

假设此时,重新上线一个实例,其 ip 为 192.168.1.5,那么此时 zookeeper 树形结构如下图所示:

服务消费者会去监听相应路径(/services),一旦路径上的数据有任务变化(增加或减少),zookeeper 都会通知服务消费方服务提供者地址列表已经发生改变,从而进行更新。

实现

下面是服务提供者在 zookeeper 注册中心注册时候的核心代码:

int ZKClient::Init(const std::string& host, int timeout,int retry_times) {host_ = host;timeout_ = timeout;retry_times_ = retry_times;hthandle_ = zookeeper_init(host_.c_str(), GlobalWatcher, timeout_,NULL, this, 0);return (hthandle_ != NULL) ? 0 : -1;
}int ZKClient::CreateNode(const std::string& path,const std::string& value,int type) {int flags;if (type == Normal) {flags = 0;} else if (type == Ephemeral) {flags = ZOO_EPHEMERAL;} else {return -1;}int ret = zoo_exists(hthandle_, path.c_str(), 0, NULL);if (ret == ZOK) {return -1;}if (ret != ZNONODE) {return -1;}ret = zoo_create(hthandle_, path.c_str(), value.c_str(), value.length(),&ZOO_OPEN_ACL_UNSAFE, flags, NULL, 0);return ret == ZOK ? 0 : -1;
}int main() {std::string ip; // 当前服务ipint port; // 当前服务的端口std::string path = "/services/" + ip + ":" + std::to_string(port);ZKClient zk;zk.init(...);//初始化zk客户端zk.CreateNode(path, "", Ephemeral);...return 0
}

复制代码

上面是服务提供者所做的一些操作,其核心功能就是:

在服务启动的时候,使用 zookeeper 的客户端,创建一个临时(Ephemeral)节点

从代码中可以看出,创建 znode 的时候,指定了其 node 类型为 Ephemeral,这块非常重要,在 zookeeper 中,如果 znode 类型为 Ephemeral,表明,在服务提供者跟注册中心断开连接的时候,这个节点会自动小时,进而注册中心会通知服务消费者重新获取服务提供者列表。

下面是服务消费者的核心代码:

int ZKClient::Init(const std::string& host, int timeout,int retry_times) {host_ = host;timeout_ = timeout;retry_times_ = retry_times;hthandle_ = zookeeper_init(host_.c_str(), GlobalWatcher, timeout_,NULL, this, 0);return (hthandle_ != NULL) ? 0 : -1;
}int ZKClient::GetChildren(const std::string& path,const std::function<int(const std::vector<std::tuple<std::string, std::string>>)>& on_change,std::vector<std::tuple<std::string, std::string>>* children) {std::lock_guard<std::recursive_mutex> lk(mutex_);int ret = zoo_get_children(handle, path, 1, children); // 通过来获取子节点if (ret == ZOK) {node2children_[path] = std::make_tuple(on_change, *children); // 注册事件}return ret;}int main() {ZKClient zk;zk.Init(...);std::vector<std::string> children// 设置回调通知,即在某个path下子节点发生变化时,进行通知zk.GetChildren("/services", callback, children);...return 0;
}

复制代码

对于服务消费者来说,其需要有两个功能:

  • 获取服务提供者列表
  • 在服务提供者列表发生变化时,能得到通知

其中第一点可以通过

zoo_get_children(handle, path, 1, children);

复制代码

来获取列表,那么如何在服务提供者列表发生变化时得到通知呢?这就用到了 zookeeper 中的 watcher 机制。

watcher 目的是在 znode 以某种方式发生变化时得到通知。watcher 仅被触发一次。 如果您想要重复通知,您将需要重新注册观察者。 读取操作(例如 exists、get_children、get_data)可能会创建监视。

okeeper 中的 watcher 机制,不在本文的讨论范围内,有兴趣的读者,可以去查阅相关书籍或者资料。

下面,我们对使用 zookeeper 作为注册中心,服务提供者和消费者需要做的操作进行下简单的总结:

  • 服务提供者
  • 在服务启动的时候,使用 zookeeper 的客户端,创建一个临时(Ephemeral)节点
  • 服务消费者
  • 通过 zoo_get_children 获取子节点
  • 注册 watcher 回调,在 path 下发生变化时候,会直接调用该回调函数,在回调函数内重新获取子节点,并重新注册回调

etcd

Etcd 是基于 Go 语言实现的一个 KV 结构的存储系统,支持服务注册与发现的功能,官方将其定义为一个可信赖的分布式键值存储服务,主要用于共享配置和服务发现。其特点如下:

  • :安装配置简单,而且提供了 HTTP API 进行交互,使用也很简单键值对存储:
  • 据存储在分层组织的目录中,如同在标准文件系统中
  • 变更:监测特定的键或目录以进行更改,并对值的更改做出反应
  • :根据官方提供的 benchmark 数据,单实例支持每秒 2k+ 读操作
  • :采用 Raft 算法,实现分布式系统数据的可用性和一致性

服务注册

每一个服务器启动之后,会向 Etcd 发起注册请求,同时将自己的基本信息发送给 etcd 服务器。服务器的信息是通过 KV 键值进行存储。key 是用户真实的 key, value 是对应所有的版本信息。keyIndex 保存 key 的所有版本信息,每删除一次都会生成一个 generation,每个 generation 保存了这个生命周期内从创建到删除中间的所有版本号。

更新数据时,会开启写事务。

  • 会根据当前版本的 key,rev 在 keyindex 中查找是否有当前 key 版本的记录。主要获取 created 与 ver 的信息。
  • 生成新的 KeyValue 信息。
  • 更新 keyindex 记录。

健康检查

在注册时,会初始化一个心跳周期 ttl 与租约周期 lease。服务器需要在心跳周期之内向 etcd 发送数据包,表示自己能够正常工作。如果在规定的心跳周期内,etcd 没有收到心跳包,则表示该服务器异常,etcd 会将该服务器对应的信息进行删除。如果心跳包正常,但是服务器的租约周期结束,则需要重新申请新的租约,如果不申请,则 etcd 会删除对应租约的所有信息。

在 etcd 中,并不是在磁盘中删除对应的 keyValue 信息,而是对其进行标记删除。

  • 首先在 delete 中会生成一个 ibytes,对其追加标记,表示这个 revision 是 delete。
  • 生成一个 KeyValue,该 KeyValue 只包含 Key 的信息。
  • 同时修改 Tombstone 标志位,结束当前生命周期,生成一个新的 generation,更新 kvindex。

再次需要做个说明,因为笔者是从事 c++开发的,现在线上业务用的 zookeeper 来作为注册中心实现服务发现功能。上半年的时候,也曾想转到 etcd 上,但是 etcd 对 c++并不友好,笔者用了将近两周时间各种调研,编译,发现竟然不能将其编译成为一个静态库...

需要特别说明的是,用的是 etcd 官网推荐的 c++客户端etcd-cpp-apiv3

纵使 etcd 功能再强大,不能支持 c++,算是一个不小的遗憾。对于笔者来说,算是个损失吧,希望后续能够支持。

下面是 etcd c++ client 不支持静态库,作者以及其他使用者的反馈,以此作为本章节的结束。

亿级流量实验平台设计与实现

怎样才能避免此问题发生呢?这就引入了实验平台,通过对流量打标签,然后分析实验效果,从而再决定是否实验推全还是下线。

一、概念

实验平台,从字面意思来看,就是一个用来做实验的平台。其 基本原理 就是把流量进行分流,特定流量,匹配特定策略。不同批次的用户,看到的不同的策略。然后通过曝光、点击等数据进行统计分析,得出实验效果的好坏,从而决定是否推全该实验。

换句话说,就是为同一个目标制定两个方案(比如两个页面),将产品的用户流量根据特定策略分割成 A/B 两组,一组实验组,一组对照组,两组实验同时运行一段时间后分别统计两组用户的表现,再将相关结果数据(比如 pv/uv、商机转化率等)进行对比,就可以科学地帮助决策。

通过对流量进行分流,运行实验,统计实验数据,进行数据分析,然后得出实验效果。如下【图一】

再根据实验效果,进行反向数据分析,定位出实验效果不好的源,进行头脑风暴,再次做实验,从而最终达到理想的实验目的。如下【图二】

二、分层实验模型

在进行实验平台讲解之前,先介绍下实验平台的理论基础,以便大家能够更好的理解后面的内容。

现在业界大部分实验平台,都基于谷歌 2017 年的一篇论文《Overlapping Experiment Infrastructure: More, Better, Faster Experimentation》,其模型架构如下图【图三】所示:

在该模型中,有几个概念:

  • 域(domain):划分的一部分流量
  • 层(layer):系统参数的一个子集
  • 实验(exp):在一个域上,对一个或者多个参数修改,都将影响效果
  • 流量在每个层被打散(分配函数),层与层之间流量正交

下图【图四】为笔者线上实验的一个简略图,在该图中,总共有三个实验层,分别为 CTR 预估层,用户画像层和频次策略层。

从图四可以看到,流量在层之间穿梭,而一个流量只能命中一个层中的一个实验,一个流量请求过程可能会命中多个层中的多个实验(每层命中一个实验)。

使用分层实验模型,需要满足以下几点:

  • 相关联的策略参数位于同一实验层(即都是做 CTR 预估,那么 CTR 预估相关的实验,就放在同一层,即 CTR 预估层,以方便做实验对比)
  • 相互独立的策略参数分属于不同的实验层(如上图中 ,CTR 预估的实验和频次实验是两种性质不同的实验,因此要放在两层来实现,如果在一层的话,由于实验性质不同,难以比较实验效果)
  • 一个流量只能命中一个层中的一个实验
  • 层之间的流量正交,不会互相影响(即层与层之间的实验不会互相影响,如【图五】)

使用该分层模型作为实验平台理论基础的好处有以下几点:

  • 可以作为一个独立的部分,不与系统中的其他业务或者功能相互 影响
  • 每一层流量都是 100%的全量,可以避免流量切分过细,保证实验间的可对比性、客观性
  • 层与层之间,流量独立正交,不会互相影响

三、功能特点

一个可用或者完善的实验平台,需要有以下几个功能:

  • 支持多实验并发迭代
  • 支持白名单体验
  • 提供便捷的管理工具
  • 便捷的查看实验效果分析数据
  • 支持实验的切流和关闭
  • 保证线上实验安全性
  • 支持实验推全
  • 支持功能定制化

下面,我们将从几个功能点出发,详细讲述各个功能的原理。

支持多实验并发迭代,指的是一个完善的实验平台,在功能上,需要支持多个实验同时运行(包括同一层和不同层之间)。

支持白名单体验:因为实验分流有自己的策略,创建实验的用户不一定能够命中这个实验,而白名单的功能就是让在白名单的用户能够每次都命中该实验。

便捷的管理工具:这个指的是实验后台,一个实验后台需要能够创建实验,打开关闭实验等

查看实验效果分析数据:判断一个实验效果的好坏,就是通过实验标签分析实验数据,能够很直观的观察到实验效果,从进行下一步实验决策

实验的切流和关闭:实验的切流,指的是该实验需要一定百分比的流量进行实验,而实验关闭,则指的是停掉该实验

支持线上安全性:实验平台,本身就是起锦上添花的作用,其只是用来打实验标签的,不可本末倒置,影响了线上业务的正常功能

支持实验推全:一个实验效果如果好的话,就需要将全部的流量都命中该实验,而实验推全就是达到此效果

支持功能定制:没有一个实验平台能够满足不同公司,甚至同一个公司不同部门之间的业务需求,但是,为了尽可能的向这个目标靠拢,实验平台需要尽可能的灵活,低耦合,能够尽量的配置化。

四、架构及模块

实验平台必备的三个模块,分别为:

  • 管理后台
  • 实验后台
  • 分流平台

管理后台主要是管理实验的,包括创建和停止实验等。实验后台与管理后台进行数据上的交互,将管理后台的消息转换成特有的消息,进行转发和持久化。而分流平台,其一接收实验后台的实验消息建立内部维度索引,其二接收线上流量,根据流量属性,给流量打上对应的标签。

上图,是一个实验平台架构图,下面从创建实验角度,和流量的角度进行讲解。

创建实验角度:

1、在管理后台创建对应的实验

2、管理后台将创建的实验信息发送给实验后台

3、实验后台将实验首先发送至消息系统(kafka 等),然后将实验落地持久化(DB)

4、分流平台消耗消息系统中的实验消息,加载至内存

流量角度:1、 流量携带其基本属性(用户画像,app 信息等)请求分流平台

2、 分流平台根据流量属性,进行定制化匹配,然后使用分流算法,计算实验标签

3、流量返回至调用方 SDK 后,SDK 上报曝光、点击等信息至数据总线(大数据集群)

4、数据计算服务分析大数据集群的数据,计算对应的指标展示在管理后台。

实验平台,从模块划分上 ,如下图所示:

五、设计与实现

在具体介绍下文之前,我们先理解一个概念,以便能更方便的理解下述内容。

bucket 即桶。实验平台最底层,将流量进行 hash 之后,只能流入某一个桶里。

流量划分

一般有以下几种划分方式:

  • 完全随机
  • 用户 id 哈希。
  • 该流量划分会使同一个用户会一直命中同一实验,从而保证了用户体验的一致性;而且也满足对同一用户具有累积效应的策略的实验需求。但存在的问题就是,对于一些工具类属性,没有用户 id 一说,只有设备 id 比如 idfa 和 imei 这些标记,但是随着系统升级,这俩标记很难再获取到,所以就需要系统能够生成用户唯一 id
  • 用户 id + 日期作为一个整体进行哈希 这是一种更为严格的保证流量均匀性的分流方式,可以保证流量划分在跨时间维度上更为均匀。
  • 会牺牲用户请求跨时间区间的一致性
  • 跟上面这种用户 id 哈希存在同样的问题
  • 用户 id 尾号进行哈希
  • 流量不均匀
  • 也会存在用户 id 所存在的问题

为了兼容上述几种哈希划分方式的优点,而摒弃其缺点,我们引入了第四种流量划分方式:

bucket_id = hash(uid + layer_id) % 1000

复制代码

常见的哈希算法有 md5,crc 以及 MurmurHash 等。

  • MD5 消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个 128 位(16 字节)的散列值(hash value),MD5 算法将数据(如一段文字)运算变为另一固定长度值,是散列算法的基础原理。由美国密码学家 Ronald Linn Rivest 设计,于 1992 年公开并在 RFC 1321 中被加以规范。
  • CRC 循环冗余校验(Cyclic Redundancy Check)是一种根据网络数据包或电脑文件等数据,产生简短固定位数校验码的一种散列函数,由 W. Wesley Peterson 于 1961 年发表。生成的数字在传输或者存储之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。由于本函数易于用二进制的电脑硬件使用、容易进行数学分析并且尤其善于检测传输通道干扰引起的错误,因此获得广泛应用。
  • MurmurHash 是一种非加密型哈希函数,适用于一般的哈希检索操作。由 Austin Appleby 在 2008 年发明,并出现了多个变种,与其它流行的哈希函数相比,对于规律性较强的键,MurmurHash 的随机分布特征表现更良好。

其中,第三种 MurmurHash 算法,已经被很多开源项目使用,比如 libstdc++ (4.6 版)、Perl、nginx (不早于 1.0.1 版)、Rubinius、 libmemcached、maatkit、Hadoop 以及 redis 等。而且经过大量的测试,其流量分布更加均匀,所以笔者采用的是此种哈希算法。

白名单

白名单,在实验平台中算是比较重要的功能,其目的就是存在于白名单中的用户优先于流量分桶,命中某个实验。

需要注意的一点是,假如白名单所在实验和流量经过哈希分桶之后的实验是两个不同的实验,这就只能以白名单优先级为最高,换句话说,如果白名单命中了某个实验,那么在该层上,就不该再命中其他实验。

实验后台

用来管理实验的,比如权限管理、层管理等,如下图:

在上图中,管理后台,主要有以下几个模块:

  • 实验管理,顾名思义,管理现有实验和创建新实验
  • 实验列表:现有已经创建的所有实验
  • 创建实验:创建新实验
  • 基础配置,一些配置管理信息都在此模块中
  • 实验层:增删改实验层
  • 其他:针对实验做的一些定制化,比如增加广告位定向、年龄定向等
  • 系统管理,主要针对用户及其分组
  • 分组管理,管理用户属于某个分组
  • 实验平台的用户权限管理,比如普通权限或者管理员权限

需要注意的是,用户管理这块的权限非常重要,因为实验平台可能涉及到很核心的实验,比如商业化中策略影响的实验,所以用户之间不能看到其他人创建的实验,这层物理隔离非常重要。

分流平台

分流评估 ,顾名思义,对流量进行分离,有两个功能:

  • 承接实验后台的实验消息,建立维度索引
  • 接收线上流量,根据维度索引、白名单以及对用户设备哈希分桶后,给流量打标签

分流平台,是整个实验平台的核心模块,一定要高性能,高可靠。

六、实验效果

请求的实验信息会以标签的形式上报给 sdk,sdk 在进行曝光点击的时候,会将这些信息上报,比如"123_456_789"格式。最终,这些会经过广告实时流系统进行消费、数据清洗、实验效果指标计算等工作。由于广告系统是多业务指标系统,包括售卖率,ECPM, CTR, ACPE,负反馈率、财务消耗计算等。广告实时流系统还需要日志的关联工作,比如关联广告互动日志,广告负反馈日志。实时流的计算的结果,会落地到 druid 系统,方便实验效果数据的快速检索和二度加工。实验效果实时指标数据计算延迟控制在一定的范围内。

结语

更多java笔记资料,BAT大厂面试专题及答案,源码分析,分布式专题,并发编程,微服务架构,性能优化,数据结构与算法,后端开发,企业级落地实战等java笔记资料,直接后台小信封扣【999】撩我领取吧!

微服务架构模式下,服务实例动态配置,因此服务消费者需要动态了解到服务提供者的变化,所以必须使用服务发现机制。

服务发现的关键部分是注册中心。注册中心提供注册和查询功能。目前业界开源的有 Netflix Eureka、Etcd、Consul 或 Apache Zookeeper,大家可以根据自己的需求进行选择。

服务发现主要有两种发现模式:客户端发现和服务端发现。客户端发现模式要求客户端负责查询注册中心,获取服务提供者的列表信息,使用负载均衡算法选择一个合适的服务提供者,发送请求。服务端发现模式,客户端每次都请求注册中心,由注册中心内部选择一个合适的服务提供者,并将请求转发至该服务提供者,需要注意的是 当一个请求过来的时候,注册中心内部获取服务提供者列表和使用负载均衡算法

这个世界没有完美的架构和模式,不同的场景都有适合的解决方案。我们在调研决策的时候,一定要根据实际情况去权衡对比,选择最适合当前阶段的方案,然后通过渐进迭代的方式不断完善优化方案。

疯狂架构师最强分享:分布式架构与性能优化,你学会了吗?相关推荐

  1. 阿里P8架构师深度概述互联网分布式架构

    简介 作为一名架构师,我们要专业,要能看懂代码,及时光着臂膀去机房,也能独挡一面!及时同事搞不定问题,或者撂挑子,你也能给老大一个坚定的眼神:不怕,有我在!还能在会议室上滔滔不绝,如若无人,让不懂技术 ...

  2. 《沈剑架构师训练营》第3章 - 快速性能优化

    11.性能优化:动静分离,互联网优化利器? no11:与静态页面.动态页面匹配的加速技术有哪些? 静态页面 CDN nginx squid/varnish 动态页面 分层架构 服务化架构 数据库,缓存 ...

  3. 阿里8年资深架构师最强分享:JAVA面试八股文。堪称史上最强分享。

    一.Java 基础知识 1.Object 类相关方法 getClass 获取当前运行时对象的 Class 对象. hashCode 返回对象的 hash 码. clone 拷贝当前对象, 必须实现 C ...

  4. Docker实践,来自沪江、滴滴、蘑菇街架构师的交流分享

    架构师小组交流会:每期选一个时下最热门的技术话题进行实践经验分享. 第一期主题:容器实践.Docker 作为当前最具颠覆性的开源技术之一,其轻量虚拟化.可移植性是CI/CD,DevOps,微服务的重要 ...

  5. 资深架构师十几年的架构干货经验总结分享!

    图片来源:pexels.com 1 架构师承担什么样的责任 记录片<黑猩猩的守护者>中珍妮·古道尔博士说过:「唯有了解,才會關心,唯有關心,才會採取行動,唯有行動,生命才有希望」,套用到架 ...

  6. 闪存联盟启动“百强架构师”行动 迎接认知时代架构挑战

    近日,以"从领先到顶尖,从局限到无限"为主题的2016 IBM全闪存时代峰会暨闪存联盟第六季在京盛大召开.IBM携手中国闪存联盟及两百余位企业和合作伙伴,共论认知时代下闪存解决方案 ...

  7. Python-专访豆瓣网首席架构师洪强宁:Python,简单的力量

    摘要:[51CTO独家报道]豆瓣网对互联网用户来说是知名的Web2.0社区,但对开发者而言,更重要的是一个应用Python打造的非常成功的Web2.0站点.Python诞生已有20年的历史,目前国内的 ...

  8. 2018中国计算机大会,阿里云分布式存储架构师:自研分布式文件系统服务

    10月27日下午,2018中国计算机大会上举办了主题"存储软硬件之国产化挑战与机遇"的技术论坛,一起探讨存储软硬件栈上的关键系统与技术的国产化发展道路.论坛上,阿里云分布式存储团队 ...

  9. 75个PPT下载丨2020中国系统架构师大会PPT分享(SACC2020)

    之前有读者询问一些数据库相关的大会资源,小编收集整理了很多文档,创建了相应的墨天轮专栏,后续将逐个分享到"数据和云"公众号. 2020年10月22日~24日,由IT168旗下ITP ...

最新文章

  1. Spring Boot 监听 Redis Key 失效事件实现定时任务
  2. chromedriver与chrome版本映射表(更新至v2.30)
  3. android源码模块编译错误,Android 源码编译错误记录
  4. IP分类以及特殊IP
  5. CSS中属性的书写顺序
  6. nuxt服务端php,nuxt服务端部署指南
  7. 机械加工工艺师手册_机械加工工艺师——机床应用篇
  8. asp.net记录错误日志的方法
  9. node 更新_被创造者嫌弃,Node.js 如何应对来自 Deno 的挑战
  10. Django 实现第三方账号登录网站
  11. Android GridView如何适配不同屏幕
  12. Ubuntu14.04 安装pip
  13. python怎么判断日期是星期几_【Java编程基本功】(八)逆序输出、是否为回文数,判断星期几,升序排列...
  14. linux删除一个网口的ip地址,linux一个网口多个ip地址
  15. android苹果耳机音量调节,安卓线控耳机怎么调节音量大小?
  16. 软件项目管理/ IT项目管理 总复习
  17. Lua C API 研究 —— 基础篇
  18. [电路笔记]三相电路
  19. Android 抖音爱心动画,Android实现抖音心形函数
  20. 历史二—— 浮点运算与数组下标寻址

热门文章

  1. 普渡机器人服务北京大学
  2. vue 视频播放(使用vue-video-player)
  3. COGS 2815. 天黑请闭眼
  4. 2020第五届上海第二工业大学新生程序设计竞赛(Java题解)
  5. MS933/MS934 适用于 1MP/60fps 摄像头,15MHz100MHz,10 位/12 位的具有直流平衡编码和双向控制通道的串化器和解串器
  6. 计算机学报模板百度云,计算机学报论文模板.doc
  7. 微信控制树莓派运行python_树莓派笔记07-微信公众号控制树莓派(一)
  8. c语言编程期刊论文管理系统,C语言编程下计算机软件论文
  9. Windows11 笔记本有线连接 WLAN(WIFI)共享方法(不知道Win10这种方法好不好使)
  10. WPS 加载项开发说明