高密度部署,基于动态库的尝试,rust动态调库

11 篇文章 3 订阅
8 篇文章 1 订阅

前言

 最近在搞faas平台,也试了各大云厂商的产品,效果都不是很理想。和我心目中的faas想去甚远。
 和小伙伴们吹完牛逼,心有所感,写下这篇文章,时间跨度较长,故事的结局可以直接看尾语。

faas特点

 faas最突出的特点只有两个,方便快捷低成本

 方便快捷是开发者使用faas最大的原因。faas平台能够提供全套的底层服务,包括日志,流量,监控,测试集成,各种触发方式,各种语言支持,云编辑器,开发框架等等。作为开发者,理想的faas状态是,写好了业务代码发个版就用。

 低成本是对开发者和平台供应商是双向的。使用faas的开发者一般都不会开发非常复杂的东西,换而言之就是要省成本,省钱。用的时候花钱,不用的时候0成本。对于平台提供商而言,每一个提供出来的函数服务都要足够省成本,资源利用率要足够合理。faas是可以按照请求收费的,如果成本不够低,平台商就要赔出裤衩子。最理想的状态下,faas可以将实例缩成0,不消耗任何资源。

方案

 基于物理机或k8s,这种很好理解。将每个函数做成一个pod跑起来就行。阿里云用的就是这种方法。将函数放到ECS里执行。不好的地方是代价高昂,而且冷启动巨慢,根据官方的数据,最快启动也要在300ms以上。所以实际生产上不可能将实例缩成0

 基于KVM虚拟化技术,已知AWS用的这种方法,其底层框架就是rust写的Firecracker。个人感觉这种还不如docker。如果docker是按秒启动,它就得按照分钟启动。不过隔离性到是很好,技术也很成熟。

 基于wasm(WebAssembly),这种是底层有个Wasm Runtime,它能够将各种语言编好的特定二进制包运行起来,启动速度在毫秒级。这种方式还不太成熟,就算是其中最优秀的的wasmtime(bytecodealliance的作品)框架,也仅仅支持少数语言。据说字节有用这种方法,"强如死月"就在字节搞这种东西,在火山引擎上感受了一把,嗯~ 好像闻不对味。

思考

 如果说wasm还不成熟,那为啥不用动态库的方法那,动态库发展折磨多年,技术早就成熟的一匹,各种稀奇古怪的语言都支持。而且可以把动态库做成热加载模式。类似wasm,让函数和服务分离,在需要的时候再加载进来。
我们整体构想如下。
在这里插入图片描述

  • 事件:一次对函数的调用称之为事件,可以是http请求,mq消息,定时器等等。各种Faas几乎都是事件驱动,是一种很好的抽象和设计。
  • xxx trigger 产生事件的各种触发器。
  • Function Manager: 用来管理各种语言编写的动态库。
  • Fuction Pool:函数运行池,里面运行多个DLL Runtime,并根据策略进行调度。图上只画了三个动态库运行时,实际生产中会复杂的多,比如会先对资源分组,再将DLL Runtime运行在对应的分组中。faas毕竟也是平台级产品,这里也需要提供多租户的支持。
  • DLL Runtime:动态库运行时,负责加载和执行动态库。当DLL Runtime被启动时,是不加载任何动态库的。当事件被路由过来时,再从Function Manager中加载动态库。并且根据一定策略,比如三分钟内没有事件过来,就会将对应动态库从运行时中卸载。真正做到了0资源消耗,不占有cpu 内存 网络。。。
    • 思考:有的时候我们用faas不光需要运行一段逻辑,也需要外部文件,比如配置文件,各种库文件。所以这里考虑,将动态库和其他所需文件直接通过文件挂载到DLL Runtime的目录中,比如通过NFS,Ceph,或者各种云厂商的云存储。而Function Manager只用来处理动态库的管理逻辑。
  • Event SLE 事件分发器:会将得到的事件路由到对应的DLL Runtime中。
  • 资源监视器:负责监控DLL Runtime的资源使用情况,并根据策略增加或者减少DLL Runtime。比如设置CPU 60%使用率,当检测CPU使用率高于60后,会启动新的DLL Runtime到Function Pool中。

实践

关说不练假把式,下面来动手实操一下。

制作动态库

用任何语言制作一个标准C接口的动态库。
当然也可以用我这个现成的。
https://github.com/woshihaoren4/wd_passport/tree/main/cdylib
这个库是用rust写的,用来做鉴权和认证用的。特点是证书内敛,且定期自动升级。可以把公钥分发出去做分布式验证。
主要封了两个标准C的接口一个是签名,一个是验证。用c语言写大概就是这种

int sign(char* data,long timestamp,char * sign) // 用于签名 并将签名串写到入参sign中

int verify(cahr * data, char * sign,long timestamp) //验证 返回0表示成功 其他则失败

我本人用的mac所以,使用.dylib结尾的文件。其他系统所需动态库 需要跨平台编译,我这里质变仪了.so的。并附上该包跨平台编译方法。

//查看rust支持的平台
rustc --print target-list

//下载平台支持库,这里加一个linux的musl编译支持
rustup target add x86_64-unknown-linux-musl

//因为上面用的musl编译,所以需要安装musl
brew install FiloSottile/musl-cross/musl-cross

//在项目的cargo.toml文件中,设置编译目标 参考:https://doc.rust-lang.org/reference/linkage.html?highlight=crate-type#linkage
//staticlib:静态库 cdylib:c标准动态库
[lib]
name = "lib_name"
crate-type = ["staticlib", "cdylib"]

//linker  创建./cargo/config.toml 文件并写入如下内容
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

//编译
cargo build --target x86_64-unknown-linux-musl

//如果报错 找不到musl-gcc 则执行如下命令
ln -s /usr/local/bin/x86_64-linux-musl-gcc /usr/local/bin/musl-gcc

调用动态库

这里使用libloading库动态加载动态库
将上面编译好的库放到下面的代码中执行。
完整代码

use std::ffi::{c_longlong, CString};
use std::os::raw::{c_char, c_int};

fn main() {
    let start = std::time::Instant::now();
    let result = call_dynamic().expect("运行出错");
    let use_time = start.elapsed();
    println!("success :{} 总用时:{}毫秒", result, use_time.as_millis());
}

fn call_dynamic() -> Result<u32, Box<dyn std::error::Error>> {
    unsafe {
        let start = std::time::Instant::now();
        let lib = libloading::Library::new("./src/libwd_passport.dylib")?;
        let sign_func: libloading::Symbol<
            unsafe extern "C" fn(
                data: *const c_char,
                timestamp: c_longlong,
                sign: *const c_char,
            ) -> c_int,
        > = lib.get(b"sign")?;
        let verify_func: libloading::Symbol<
            unsafe extern "C" fn(
                data: *const c_char,
                sign: *const c_char,
                timestamp: c_longlong,
            ) -> c_int,
        > = lib.get(b"verify")?;
        let use_time = start.elapsed();
        println!("启动用时:{}微秒", use_time.as_micros());

        let start = std::time::Instant::now();
        let data = CString::from_vec_unchecked(Vec::from("hello world"));
        let sign = CString::from_vec_unchecked(vec![1; 128]);
        let result = sign_func(data.as_ptr(), 1866248975, sign.as_ptr());
        assert_eq!(result,0,"签名失败");
        let use_time = start.elapsed();
        println!("签名用时:{}微秒", use_time.as_micros());

        let start = std::time::Instant::now();
        let result = verify_func(data.as_ptr(), sign.as_ptr(), 1866248975);
        assert_eq!(result,0,"验签失败");
        let use_time = start.elapsed();
        println!("验证用时:{}微秒", use_time.as_micros());

        return Ok(0);
    }
}

运行结果:
在这里插入图片描述
这里可以看到,启动时间在亚毫秒级。因为代码中是从文件中加载,文件加载和读取是需要花费一定时间的,当多次运行测试代码后,会发现启动时间小于毫秒,因为系统会缓存文件。
签名用时较久是因为第一次会初始化一些配置。如果将代码中的单次签名改成多次,会发现签名用时大概在2毫秒左右。动态库用的是rsa sha256算法。

尾语

高密度部署一直是faas比较头疼的事,动态库方法固然能够极大降低资源成本,但是会让开发变得即为复杂。
领导评估这个方案直接就是不通过,理由是开发周期过长,不能快速上线。
最终方案还是用k8s,套个壳就叫faas了。嗯~ 这很具有z国特色。

截止到我写下这篇博文23年2月份,项目开发了6个月了。实际上两个月的时候我们就开发完了,三个月的时候上线了第一版。截止目前,项目组辞掉了外包,遣散了其他部门借调的人(包括我),只留下大猫两三只继续开发。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值