以OpenResty搭建RTB竞价引擎接入层

时间:2022-07-23
本文章向大家介绍以OpenResty搭建RTB竞价引擎接入层,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

概念解析

OpenResty: OpenResty是一个基于Nginx与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关,以下或也称之为Nginx。

RTB竞价引擎: 互联网广告实时竞价系统,以下的tomcat为其单实例。 当我们的应用单实例不能支撑用户请求的时候,此时就需要扩容,一般称之“横向扩容”,即:从一台服务器扩容到两台、几十台不等。如下是我们的架构方式:

其中红框中的nginx群,我们称之为“接入层”。在广告引擎系统中,常常需要做一些A-B测试用于对一些投放策略的比较、新增或者更改的功能同样需要拿到线上做bug风险测试,而所有这些都需要以重启服务器来实现。投放系统流量大且实时性要求高,重启系统导致系统的波动不说,还会影响整个广告系统的收入,这就成了我们的痛点。

发现痛点,就解决它!如此,我们便有了以上的架构。对于开发同学来说,只需要关心到接入层就够了,Haproxy由运维同学维护。在接入层,我们搭建流量分发功能,以下称之为“灰度发布”。灰度发布,通过lua脚本实现了特定的流量发送到特定的Tomcat服务。这还不够,服务的启停,需要实时动态的流量切换,这就需要一个服务发现系统。而我们选择在接入层搭建这些功能,解决这个问题,无非是考虑到与业务逻辑解耦,同时看中了OpenResty的高并发、高吞吐以及丰富的模块设计。

如下,分两个部分介绍一下我们的实现方式:

服务发现

我们的服务发现功能是基于OpenResty+upsync+consul功能搭建,辅以nginx_upstream_check_module做健康检查。 upsync是由微博开源的一款nginx插件,可以从consul或其他服务发现框架同步上游服务器,动态修改后端服务器属性(weight,max_fails等),而无需重新加载nginx。对于大流量、高负载的RTB引擎来说,nginx reload会进一步增加系统负载并暂时降低性能。而且增加upsync模块,将我们的服务于调用者来说完全解耦,后端系统的升级重启,乃至限流扩容都透明化了。

upstream online {
    server 0.0.0.1:1234; 
    upsync 127.0.0.1:80/v1/kv/upstreams/springboottest upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
    upsync_dump_path /usr/local/nginx/nginx/conf/servers/spring-boot-consul.conf;
    upsync_lb hash_ketama;
    hash $arg_uid consistent;
    check interval=1000 rise=2 fall=2 timeout=3000 type=http default_down=false;
    check_http_send "HEAD / HTTP/1.0rnrn";
    check_http_expect_alive http_2xx http_3xx;
}

其中代码行:

第2:为假数据,预防启动时nginx报错;
第3:为拉取consul数据地址,此处使用kv形式;
    upsync_timeout   :拉取超时时间,不可小于5m;
    upsync_interval  :拉取时间间隔;
    upsync_type      :拉取形式,此处使用consul;
    strong_dependency:打开时,每次nginx启动或重新加载时,nginx都会从consul中提取服务器,此处关闭;
第4: 为服务器列表落地本地地址;
第5:结合第6行,设置一致性hash;
第7:设置healthy check的检测方式:
`syntax: *check interval=milliseconds [fall=count] [rise=count][timeout=milliseconds] [default_down=true|false]       [type=tcp|http|ssl_hello|mysql|ajp|fastcgi]*`
    interval    :检测间隔,单位milliseconds;
    rise        :检查成功rise次后,服务器被标记为启动;
    fall        :检查失败fall次后,服务器被标记为关闭;
    timeout     :健康检测超时时间
    type        :健康检测类型
    default_down:设置后端服务器的初始状态,默认为关闭。

如配置所示,nginx会定时从consul拉取上游服务器列表,同时落地在本地(防止consul集群不可用)。由于相对传统的取模操作一致性hash对后端服务的缓存友好且拥有更好的容错性与可扩展性,所以我们这里使用了一致性hash。upsync对每个上游服务器增加了160*weight个虚拟节点,使其负载均衡。

对于consul而言,我们只选用了它的kv功能,更看重的是,consul提供了time_wait和修改版本号概念,如果consul发现该kv没有变化就会hang住这个请求5分钟,在这5分钟内如果有任何变化都会及时返回结果。通过比较版本号我们就知道是超时了还是kv的确被修改了。由此OpenResty可以及时地获取consul的kv store变化。而consul不支持多路注册,使用它的service功能的话,很可能导致注册结果不可控,这一直以来是consul为开发者所诟病的大坑。而且注册对于后端服务来说,它和其所注册的server是一一对应的,一旦consul某个server挂掉,后端服务也被认为不存在了,这是我说不能容忍的。所以取用其长,而避用其短,搭配nginx的nginx_upstream_check_module所提供的丰富的api和健康检查方式实现服务发现功能。

灰度发布

百度词条对灰度发布的定义是:又名金丝雀发布,是指在黑与白之间,能够平滑过渡的一种发布方式。我们的灰度发布功能则是在服务发现的基础之上,增加流量分发层,使进入接入层的流量可以根据我们的业务需求,如userId、广告位、Ip、Ip段、操作系统等制定相应的规则,指定到相应的上游服务器中,便于我们测试一些新进功能或者策略的优劣。流程图如下。

我们的规则是通过json的方式存储于redis中,但是json的方式是非常浪费性能的,所以我们均采用unix domain socket的方式连接本地redis从库,OpenResty的lua-resty-redis对此支持非常好;其次在其上层使用lua_shared_dict搭建缓存,把相关的分发加入缓存中,从而降低redis的压力。另外,采用加权平均的方式,可以将指定流量的一部分分发到指定的一台或者多台机器上去。

这是我前两年发布在知乎上的文章,拿过来一起整理下。请多多批评