上篇文章提到如何在容器环境中使用阿里云离线IP地理位置库,前文中测试性能看起来满足日常离线小样本、低频率私密调用性能没有大的问题,但是针对大量数据的场景,再不搭建集群多实例的情况下,显然是无法满足需求的。

本篇记录 C++ 版本 SDK 的踩坑过程,希望帮助后面的同学节约折腾时间。另外希望看到本文的产品 PD 同学可以尽快推动文档完善。

写在前面

查询 IP 的场景分为在线和离线两种模式,对于有大量大尺寸访问日志的场景,一般因为以下原因,会放弃在线公有云 API 调用:

  • 对于机器资源要求颇高,需要更多的节点、更大的带宽
  • 跨区域存在很高的时延
  • 需要针对存量日志进行流式处理或者切分,执行效率上也有着非常大的限制
  • 服务需要额外的运维成本

所以需要转变思路,寻找更高效的解决方案:

  • 内网部署,减少网络开销
  • 简化接口实现,提升执行效率
  • 使用集群提供更高性能的服务能力

官方目前提供了三个版本的 SDK (Java 、Python 、C++),下载了解发现 Python 和 C++ 版本本质都是 C++ 实现,从性能角度考虑,我们放弃了 Java 版本,上篇已经从 Python 版本实现过了,本篇从 C++ 版本入手。因为当前生产服务都使用了容器部署维护,所以这里也需要考虑容器化实现。

基于官方 C++ SDK 进行容器化封装

因为在服务器使用,所以这里需要参考官方 C++ SDK (Linux环境)的文档:https://help.aliyun.com/document_detail/172359.html,截止本文产生,文档更新日期为 2020-10-10 14:29:50

文档中依旧提示可以在 Linux x86_64 环境使用,不过兼容列表比之前 Python 少了一个 Ubuntu 环境,只剩下了 RHEL 和 CentOS。依旧本着绿色环保,要试试是否能使用 Alpine 启动执行。

尝试封装 Alpine 容器镜像

和上一篇不同的是,我们除了因为 Alpine 版本缺少我们需要的 2.3.0 版本以上的 botan2 ,需要源码构建外,还需要配置支持构建 SDK 和我们程序的 C++ 环境。

但是有了上一篇的踩坑记录,在 Alpine 环境下从源码构建 botan2 就顺利多了。首先还是编写构建 botan2 的基础容器环境:

接着我们编写可以支持编译构建C++应用的环境,在原有文件中添加以下几行(程序文件使用官方文档中的示例程序):

虽然我们明明在编写 Dockerfile 的时候,已经将官方 IP 库文件放在了 /usr/local/lib/ 目录下,但是在执行后依旧会得到类似下面的错误:

原因很简单,官方在文档中有提到,程序默认会读取 LD_LIBRARY_PATH 这个环境变量,且通过试验发现,并不会在该目录之外的系统目录进行依赖查找…

调整原来的命令后,我们继续执行,则会发现另外一个错误:

通过寻找我们在 Alpine 官方 Package 仓库中找到了:libc6-compat这个包含 libm.so.6libc.so.6 的软件包。

继续调整原来的 Dockerfile,在其中添加安装这个包的命令:

再次运行构建,会得到一个“船新”的错误反馈:

这里的错误提示我们依赖库有问题,或许是不能使用动态链接,先修改 Dockerfile 将动态链接库替换为静态库。

再次尝试构建,发现问题依旧:

尝试使用上篇文章的 Ubuntu 环境的链接库,得到的结果也是如此,所以在官方正式推出 Alpine 环境编译的库之前,我们是不能使用 Alpine 镜像来执行 C++ SDK和相关程序的。

所以,只好调转方向,试试其他环境啦。

简化官方示例文件

在继续封装之前,我们先对官方SDK和示例代码进行简单精简,去掉非 Linux 环境等不必要的内容。

将上面的内容保存为 geoipclient.hpp 后,我们继续处理 example.cpp 文件:

将两个文件保存到相同目录中后,我们继续进行镜像封装。

封装Ubuntu / Debian 环境的容器镜像

在 Alpine 版本构建失败后,和官方维护的同学沟通得知官方没有使用容器环境进行构建,暂时不支持 Alpine,官方目前使用 CentOS 7 和 Ubuntu 进行源码构建。

但是通过搜索发现 CentOS 7 其实并不能直接使用阿里云官方文档中的 yum install botan2 来完成依赖软件安装。加之考虑当前多数线上系统使用 Ubuntu / Debian,所以这里采用 Debian 作为容器基础环境进行继续尝试。

这里可以直接参考上篇进行 Dockerfile 编写:

这里有一些需要注意的细节:

  • debian 没有 libbotan-2 这个软件包,虽然查找到 libbotan-2-dev 中包含我们需要的软件库,但是查看软件包仓库页面可以发现版本并不能满足我们的需求,所以这里只能使用源码编译,或者直接使用阿里云官方提供的二进制文件。
  • 官方提供二进制在使用上有一些比较黑盒的限制,在构建的时候,需要将 libbotan 复制到 /usr/local/lib/ 目录,否则构建会报错。
  • 在编译后产物使用的时候,则需要将二进制文件保存至 /usr/lib/ 目录
  • 实测 export LD_LIBRARY_PATH=/usr/local/lib 并不需要声明,Ubuntu / Debian版本会自动寻找依赖库。

使用命令 docker build -t alidns-geoip:cplus-ubuntu . 构建镜像,然后使用 docker run --rm -it alidns-geoip:cplus-ubuntu 启动应用,会看到程序正常运行。

接下来我们验证执行性能,并进行基础优化。

验证容器内基础查询性能

进入 Ubuntu 镜像封装的容器执行 time ./example,会得到类似下面的结果:

可以看到整体运行时间和上一篇内容中 python 示例没有太大区别,但是 sys 运行时间基本减少到了一半,C++编译器确实还是更强。

之所以为什么这么慢,之前的文章我们有分析和验证过,所以这里我们直接开始使用之前的“套路”来针对程序进行性能优化。

使用 CS 模式优化程序性能

因为之前没有写过 C++,所以花了几个小时搜索网上资料整合了一套示例代码,先贴 server.cpp 内容 ,包含一些调试信息,将阿里云官方 SDK 头文件扔一块,大概 130 行不到,如果将校验、异常等信息放在客户端,应该能砍到100行左右:

上面服务端实现大概做了几件事:

  • 启动一个 UDP Server,绑定端口到 1024 ,监听任意网卡数据信息。
  • 使用阿里云 IP 库 SDK初始化一个实例放到内存中,等待使用。
  • 接收客户端发来的消息,并验证是否为 IP v4地址格式,如果是则进行查询,将消息返回客户端。

接着来实现客户端 client.cpp 代码,九十行内解决战斗:

简单概述下客户端做了哪些事情:

  • 允许使用命令行执行,接收传递的参数,并验证是否为IP格式
  • 使用 UDP 协议尝试连接服务端,查询请求的IP地址

接着修改 Dockerfile :

执行 docker build -t alidns-geoip:cplus-ubuntu . 构建镜像,分别执行 docker run --rm -it --name=cplus alidns-geoip:cplus-ubuntu ./serverdocker exec -it cplus ./client 47.116.2.4 会看到久违的 IP 查询结果(体感上的速度有明显的提升)。

接着使用 docker exec -it cplus bash 进入容器,还是执行 time 来验证程序基础运行性能。

在无缓存的情况下,单个程序实例的性能相比之前已经提升了两个数量级,这里可以看到,时间中有数值为零的内容,说明 time 精度在这里已经不够用了。

这里为了获取更精准的时间消耗,在程序中添加了一个“埋点”,执行后可以看到不经过本地环境网络传输情况下的时间损耗:

重新编译执行后会惊奇的发现性能比想象中的还要好:

不需要详细计算,算上各种损耗,粗略估计,同机请求,即使单实例情况解决每秒 10万以上的请求问题不大。

优化镜像文件尺寸

和之前一样,在满足需求之后,我们对镜像尺寸进行检查,发现出现了 913MB 这样惊人的尺寸。

再次使用多阶段构建大法,进行容器尺寸瘦身。

构建完毕,再看容器镜像尺寸,已经缩减到了 93.5MB

最后

本篇中,我们对官网 C++ SDK 进行了一些简单的修改和扩充,并结合容器进行了镜像构建,使用不到两百行代码,实现了在容器内高性能的调用阿里云地理位置库。

以目前的性能来看,稍加扩展是能够满足我们在线和离线计算需求的,我就暂时不折腾啦。如果你愿意的话,可以尝试使用 libv 、 proxygen 拓展这个 SDK,会发现新大陆和新玩法。

–EOF