博客

  • 在Rust中使用TensorFlow

    TensorFlow是一个开源的机器学习框架,上手简单,且在大量商业场景使用,可靠性高。

    机器学习常用的语言主要是python、c,官方也提供了Java版本sdk,示例也很丰富。对于Rust语言,虽然官方文档没有写,其实官方也提供了rust版本的绑定,项目名称就是rust。

    在Cargo.toml中配置版本,这里的0.18.0对应TensorFlow 2.8.0

    tensorflow = "0.18.0"

    这个依赖中的-sys模块会根据实际情况进行处理,如果本地没有Tensorflow的库文件,就会自动下载。如果没有预编译好的库文件,或者用户指定要从源码编译,那就会在本地编译。

    我这里使用的是frozen graph,在rust代码中新建一个Graph进行加载即可。

    let mut graph = Graph::new();
            let model_file = MODEL_DIR
                .get_file("mobilenet/mobilenet_v2_1.4_224_frozen.pb")
                .unwrap();
            let label_file = MODEL_DIR.get_file("mobilenet/label.txt").unwrap();
            graph
                .import_graph_def(model_file.contents(), &ImportGraphDefOptions::new())
                .unwrap();

    为了最后打包方便,这里使用了include_dir包,会把模型和最终产物打包到一起。

    这里的模型是一个mobilenet的图片分类模型,输入为224像素。图片的处理使用image包。

    let img = image::open(photo.get_store_path())?;
            let resized = image::imageops::thumbnail(&img, 224, 
  • 跨语言编程

    日常工作中由于公司业务和IT资产的原因,可能并没有接触到这块。但是跨语言编程是现代程序语言中非常重要的一个方向,也被广泛应用于复杂系统的设计与实现中。

    场景

    跨语言可以用于很多场景,但是常用的有以下几种

    基于系统级语言实现系统的关键路径

    一些高级语言或者说脚本语言受限于封装的运行环境,对于一些系统操作比较困难,也就是底层能力比较弱,在和操作系统或者硬件打交道的时候更多的是基于C/C++此类系统级语言去进行这次底层操作。上层语言直接调用。

    依赖当前语言不支持的能力

    不同语言都有适用的领域,也有能力的上限。比如早期的Java对于线程以下操作比较弱,相关API不完善,直接依赖其他语言开发的封装层更容易操作。

    复用库和业务方法

    这个场景的情况最多,这个有可能公司遗留遗产,也可能是公司其他系统的核心二次封装成SDK给其他部门调用。也可能是成熟开源库在当前语言没有替代品,比如在java中使用opencv。

    复用不一定是为了业务功能,很多时候也处于性能考虑,在特定场景特定语言优势比较明显,比如矩阵操作等。

    原理

    跨语言调用方法一就是RPC调用,通过网络端口提供服务,并通过约定的请求格式进行通讯。这个方法简单成熟,非常适合不同语言负责的是不同领域的场合。比如一个帮助系统(Python),调用一个自然语言处理模块(C++)进行分词等操作。由于基于RPC通讯,双方本质上只依赖于协议,具体实现解耦。方便扩展更多语言的调用方,也方便双方独立升级。

    另外一种更紧密的结合就是在同一个CPU运行,不管语言、环境等等花里胡哨的东西,最后都要走到CPU执行指令这里。这里细分两种,一种是中间代码是相同的,那么他们就通过编译工具进行融合,比如LLVM IR。更宽泛的调用时基于FFI,只要ABI时相同或者兼容的。

    虽然原理类似,具体落实在使用上,每种语言使用的具体技术工具可以不同,比如Java中JNI就是Java调用的C的,而Golang可以使用CGO。

    实践

    常见的组合很多,这里看下Java+C和Flutter+Rust。这两种组合也恰好代表了两种使用场景。一般来说Java和C混合编程多是出于性能上的考虑,而Flutter+Rust的组合更多是出于复用逻辑的考虑。

    Java+C

    基于JNI可以实现这点,但是JNA框架可以更方便完成这个事情。

    <dependency<groupIdnet.java.dev.jna</groupId<artifactIdjna-platform</artifactId<version5.6.0</version</dependency

    引用以来后直接根据你的依赖库就行定义就行了。

    public interface CLibrary extends Library {
            CLibrary INSTANCE = (CLibrary) Native.loadLibrary(
                            (Platform.isWindows() 
  • Rust自动生成Dart API供Flutter使用

    最近使用了flutter_rust_bridge,流程上非常好,极大程度简化了操作。

    使用方法如下

    提供Rust API

    为了方便管理和测试,对Flutter暴露的接口单独提供,放在api.rs中。里面提供的都是pub fn,出入参数如果内含其他结构体,加上Option,比如

    pub fn query_report(query: ReportQuery) -ReportPageDTO {
        APP.lock().unwrap().query_report(query)
    }
    
    pub struct ReportPageDTO {
        pub total: i32,
        pub list: Option<Vec<ReportDTO,
    }
    

    准备生成工具

    先安装工具和依赖

    dart pub global activate ffigen
    cargo install flutter_rust_bridge_codegen

    另外还需要LLVM,具体安装看操作系统有一些区别。

    最后运行脚本

    flutter_rust_bridge_codegen 
  • 优化托管于阿里云函数计算的Node.js应用 – 以Parse为例

    本文首发于阿里云开发者社区:https://developer.aliyun.com/article/871751,修改后发布在本博客

    上文介绍了如何快速迁移Parse到阿里云函数计算,但是这只是一个跑起来的例子,还有一些问题需要我们优化。本文会介绍常见的优化点和方法,从方法来看适用于所有Serverless平台的应用。

    Serverless的缺陷

    没有任何技术形态是完美的,Serverless提供了良好的可伸缩性和并发性,提供了细粒度的资源分配,优化了成本,相对的也有难以调试等缺点。

    这些问题是Serverless这种技术形态自身造成的,并不是阿里云函数计算独有的。不同的云厂商可以通过周边建设来弥补一些问题,比如阿里云函数计算的日志和监控相对比较完善,Serverless Devs工具解决了一部分调试问题。

    用更传统的观点来理解Serverless的本质,可以看作扩容缩容策略极端激进的集群,而每个函数都是部署在这一个一个机器上而已。云厂商的机器特别迷你,计价单位颗粒小。而缩容策略可以将为0,扩容策略可以近乎无限大,缩容策略是固定,不可以自定义。

    那么对于一个随时可能创建随时可能被销毁的机器,部署于其中的服务要面临两个方面的问题

    • 服务销毁
    • 服务启动

    服务销毁时内存、文件系统的数据都丢失了。服务启动的时候需要一些必要的初始化,需要启动程序。

    我们先看下销毁引起的持久化问题。

    持久化改进

    Parse是支持文件上传的,存储文件的FileAdapter是可以自定义的。

    一般来说对于文件需求,可以直接使用阿里云对象存储OSS,一般选择标准型就可以了。

    Parse官方不支持阿里云OSS,理论上可以使用parse-server-s3-adapter,但是我之前没有配置过,可以完全可以自定义,直接使用OSS官方的SDK就行了。

    'use strict';
    
    var OSS = require('ali-oss').Wrapper;
    const DEFAULT_OSS_REGION = "oss-cn-hangzhou";
    
    function requiredOrFromEnvironment(options, key, env) {
        options[key] = options[key] || process.env[env];
        if (!options[key]) 
  • 迁移Nodejs项目到阿里云函数计算 – 以Parse为例

    迁移Nodejs项目到阿里云函数计算 – 以Parse为例

    本文首发于:https://developer.aliyun.com/article/869641 修改后发布在本博客。

    Parse Platform是一个开源的BAAS框架,可以大幅加速各类应用快速迭代。官方提供了各位常见SDK,对于开发者也可以缩短了应用的开发周期,实在没有的,还可以用Rest API。

    原来的Parse.Inc还提供托管,但是后面关闭了,国内也有一些托管服务商。

    Parse Platform基于NodeJs,背后的数据库可以选择MongoDB或者PG,文件和PUSH服务可选很多。

    自己搭建一个Parse是相当容易了,官方提供了命令行工具,也有Docker镜像,但是这种模式需要至少一台云主机和一个MongoDB数据库,相对来说有一定成本,特别是对于一些探索性的APP,流量低,访问需求不稳定,后期可能有突发流量。

    综合来看Serverless平台是一个不错的选择,本文以阿里云为例,介绍如何快速基于阿里云函数计算和阿里云Serverless DB搭建一个Parse。

    本文需要基本的Serverless平台使用经验。由于篇幅所限,本文只涉及最基本的跑起来,包括发布和数据库存储,文件存储和其他优化不涉及。

    阿里云函数计算

    阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,用户无需管理服务器等基础设施,只需编写代码并上传。函数计算会准备好计算资源,以弹性、可靠的方式运行代码,另外还提供日志查询、性能监控、报警等功能。

    函数计算的费用由调用次数+函数实例资源使用量+公网出流量组成,另外如果长期使用,还可以购买资源包,相对价格更低。

    阿里云每月为每个账号提供一定的免费资源,对于少量使用可以完全覆盖。

    迁移

    普遍性的来说,迁移一个既有系统到Serverless平台主要有以下几个步骤

    • 识别系统的外部依赖
    • 创建适用于Serverless平台的部署工程
    • 修改即有系统代码
    • 部署

    识别外部依赖

    Parse的外部比较简单,从官方文档的说明和配置示例可见

    • Node 8 or newer
    • MongoDB version 3.6
    • Python 2.x (For Windows users, 2.7.1 is the required
  • 云上部署的便利性和方案

    云上部署的便利性和方案

    本文首发于:https://developer.aliyun.com/article/863742 修改于发布于本博客

    每年双十一的时候,由于云厂商对于新用户的推广和补贴用户,总会有一些非技术的同事买云主机或者其他类似服务,买了以后也没啥诉求,就想看能不能搭个博客或者小网站自己玩玩。除了个人用户以外,也有一些小公司可能也有类似诉求,需要一个简单的项目管理或者其他企业软件,一般的诉求都是免费或者花钱少,简单,后期别出问题。

    我们就用wordpress举例,考虑最简单的wordpress也需要一个PHP环境,一个数据库,如果还需要https,那还需要一个nginx或者caddy。

    手动安装

    手动其实比较麻烦,php环境、数据库和caddy可以使用apt/yum安装,再从wordpress官网下载最新版本,解压使用。数据库需要手动改下密码,然后wordpress配置连接。这么一套,10分钟肯定跑不掉。

    而且这还是最基本的,还需要考虑端口的开放,应用的安全配置等等。

    Docker-compose方案

    docker是最容易想到的方案。

    一般来说为了方便我都会帮忙用docker-compose的方式部署一下,几行命令就能搞定,配置文件如下:

    version: "3.9"
        
    services:
      db:
        image: mysql:5.7
        volumes:
          - db_data:/var/lib/mysql
        restart: always
        environment:
          MYSQL_ROOT_PASSWORD: somewordpress
          MYSQL_DATABASE: wordpress
          MYSQL_USER: wordpress
          MYSQL_PASSWORD: wordpress
        
      wordpress:
        depends_on:
          - db
        image: wordpress:latest
        volumes:
          - wordpress_data:/var/www/html
        ports:
          
  • 谈API网关

    文章首发于阿里云开发者社区:https://developer.aliyun.com/article/862205

    API 网关是位于客户端与后端服务集之间的管理工具/平台。简单来说大致相当于一个反向代理,可以调用并返回结果。

    价值

    API网关并不是必须的,如果处于起步阶段的项目,单体应用还是最直接的,一层Controller就可以解决问题,这个阶段引入过多的复杂性其实没有太大必要。

    但是对于大型应用,特别是微服务化的场景下,API网关可以带来很多价值:

    聚合和解耦

    当用户需要同时访问多个接口的时候,网关可以聚合结果,节约流量。

    网关由于路由规则等比较灵活,可以比较容易的切换网关后的路由,对于一些升级、迁移场景,可以降低影响。特别是业务迭代早期,服务之前的边界和模型不稳定,迭代是极为频繁的。

    非业务功能

    在实际的应用场景中,有很多功能是必要的,但是它不是业务功能,比如限流、降级、熔断等。网关可以用较低代价去实现通用能力,并提供一些通用的模块,比如认证接入等。

    管理

    网关统一了所有对外暴露的接口,也就有条件提供一个统一的管理工具,这样可以给API更统一的管理模型,统一生命周期。

    评价

    评价一款API网关主要看性能和功能,另外个人还觉得和已有生态的结合也是一个考虑点。如果是自建/自部署,还要考虑开源社区的活跃程度和二次开发的复杂度。

    性能测试网上有很多,一般都是使用几台压测机进行施压,观察平均响应时间、吞吐量和响应时间分布。这样测试一般后端都是空接口(没有业务逻辑),无法模拟真实情况下的连接波动等的影响。

    功能的话,大部分流行的API网关功能都是完备的,只要是叫得上名字的,功能都大同小异,这种情况下可以重点关注下监控系统的集成。

    选型

    API网关由于通用性比较强,相对来说选型也比较多。

    常见的开源选择有Kong、Ambassador、Traefik、Tyk等。

    部分开源网关也有商业版/商业支持,如果目前已经上云了,那其实完全可以使用云厂商的网关,差不多都是按流量+次数收费,也有买断式的专享实例,这种按照时长+流量收费。

    对于云厂商来说,API网关属于最基本的功能了,都有提供,但云厂商提供的API网关存在厂商锁定问题,后期切换不容易。

  • Rust工程的分发

    Rust项目直接用Cargo build就可以产出构建物,但是这个构建物不一定真能在部署端运行起来,常见问题有以下几个。

    Glibc版本

    一般我们的构建都是放linux平台的,Glibc是GUN发布的c库,真正的底层依赖。Rust在编译的时候绝大部分都是静态链接,但是对于C标准库,它使用的还是动态链接。Glibc广泛存在,本来也不是啥大问题,但是如果构建的时候使用了相对新的版本,而运行环境是在其他使用低版本Glibc的系统,那么就会有 version `GLIBC_2.18′ not found 这种问题。

    解决方案有很多种,最简单的就是换一个libc的实现,musl是一个相对合理的选择。只要在构建的时候选择x86_64-unknown-linux-musl就行了。

    cargo build --target x86_64-unknown-linux-musl

    构建环境还需要musl-tools环境。

    如果由于一些特殊原因没法用musl,还可以考虑降级Glibc。比如使用低版本的Ubuntu进行构建,也可以使用cross工具。

    外部文件

    如果追求单文件,那么各种配置文件和其他文件依赖就是一个问题。最直接的方法是使用include_str直接把内容放在构建物中,sqlx::migrate就是用的这种方法。如果是其他文件,也可以自己实现,或者使用第三方的库,比如rust-embed等。

    Docker镜像

    Docker镜像更多的问题是在大小上,一般来说采用多步构建就行了。构建镜像可以用rust镜像,运行环境用debian:buster-slim。如果有其他依赖,比如openssl、sqlite也可以自行安装。…

  • 部署Rust工程到阿里云函数计算FC

    本文首发于阿里云开发者社区:https://developer.aliyun.com/article/797279

    阿里云函数计算目前支持C#、Java、Python、Go等大部分语言,对于一些定时任务是非常适合的。最近有一些工程使用Rust重写了也想部署到阿里云函数计算上,这里来看可以使用Custom Runtime/Container。

    Custom Runtime/Container模式

    这个模式本质上是自定义脚本启动一个http服务,然后Serverless平台转发请求到你的http服务,这个请求header中有一些特殊信息,极端的将不考虑启动速度和性能等等,其实是可以无障碍迁移大部分服务的(存储和数据库可能需要改造)。

    这个运行环境默认包含了Python3,Java 8等常见依赖,对于Rust这种编译成二进制文件的就更简单了。最终产物是一个zip包,包含启动文件bootstrap和其他依赖即可。

    Custom Container模式可以理解成增强模式,如果Custom Runtime不可用,一般是由于特殊的本地依赖导致,Custom Container模式的产出是一个镜像,不过由于镜像的大小比代码包大,所以计费上要多一些,包含拉取镜像的费用。所以这两者需要根据情况来决策,尽可能使用Runtime模式。这样费用和速度上都好一些。

    代码改造

    这里使用的是funcraft工具专门用于部署,template.yml文件包含描述信息,代码放在code目录。

    在Cargo.toml中新增两个依赖

    tokio = { version = "1.12.0", features = ["full"] }
    warp = "0.3.0"

    修改main.rs用warp启动一个http服务

    #[tokio::main]
    async fn main() {
        pretty_env_logger::init();
        // POST /invoke
        let 
  • 移除Rust标准库依赖

    现代通用操作系统对于应用程序的支持很多,在硬件平台上操作系统做了管理,操作系统之上还有标准库对应用程序提供系统调用。然后如果只从跑起来的角度来说,只要有最上面和最下面的就行了。

    如下图:

    中间两层提供的都是抽象。而有些时候我们的应用程序需要运行在一个相对底层的情况下,我们就需要移除标准库的依赖,更进一步可能要减少对于部分操作系统的依赖。

    nothing程序

    如果编译一个空的rust程序,比如nothing.rs

    fn main(){}

    在Ubuntu下查看系统调用如下图

    我这里的芯片是riscv的,所以第一步是新增.cargo/config文件

    [build]
    target = "riscv64gc-unknown-none-elf"

    在代码头部取消标准库

    这里编译会失败,因为println!也是std提供的,而core中没有。

    移除后编译也是失败

    panic_handler是致命错误处理函数,默认也是std提供,这里我们加一个空实现

    由于我们的main函数在被调用前,其实有很多初始化工作被做了,现在移除了std也没有了。直接删除main方法,然后加上no_main配置就行了。现在编译可以通过。

    查看产出物的信息,可以看到入口是0,这个产出物本质上是没有用处的。还需要补充上后面需要的内容。

    这里需要加上_start函数

    #[no_mangle]
    extern "C" fn _start() {
        loop{};
    }