博客

  • 从docker hub镜像中拉取镜像

    最近换了Apple M4的MacBook,其他使用都还好,就是镜像拉群经常出问题,主要是docker hub的镜像有问题(忘记了旧电脑怎么配置的),要么就是报no matching manifest。

    Error response from daemon: no matching manifest for linux/arm64/v8 in the manifest list entries: no match for platform in manifest: not found

    查了下M4是ARMv9架构,具体是ARMv9.2-A,如果没有合适的镜像可以用linux/amd64试试。

    专门写了一个脚本来处理这个问题

    #!/bin/bash
    
    # Docker镜像仓库列表
    MIRRORS=(
        这里放自己常用的镜像
    )
    
    # 检查参数
    if [ $# 
  • 用 GitHub Actions 实现 Docker 多架构镜像构建与 Manifest 合并

    用 GitHub Actions 实现 Docker 多架构镜像构建与 Manifest 合并

    最近在折腾一个开源项目的时候,碰到了Docker 镜像构建问题。现在CPU 架构越来越多样,除了我们熟知的 amd64 (或者叫 x86_64),arm64 架构也因为苹果的 M系列芯片、各种云服务器实例以及树莓派等嵌入式设备的普及而变得越来越重要。如果我们的 Docker 镜像只支持单一架构,那显然是不行的。

    最简单的方式就是在docker action中指定platform,搭配QEMU可以全自动的实现多架构镜像构建,但是QEMU很慢,会极大增加构建时间。

    本文介绍依赖原生runner实现多架构镜像构建的方式。

    整个 Workflow 主要包含两个核心的 Job:

    1. build-and-push:这个 Job 会并行地为我们指定的多个平台(例如 linux/amd64 和 linux/arm64)分别构建 Docker 镜像。构建完成后,它会将镜像推送到 GitHub Container Registry (GHCR),并把每个平台镜像的 digest(摘要)作为 artifact 上传。
    2. merge:这个 Job 会在所有平台的 build-and-push Job 成功完成后执行。它会下载之前上传的各个平台的 digest 文件,然后使用 
  • 使用tensorflow检测睡眠鼻鼾情况

    使用tensorflow检测睡眠鼻鼾情况

    最近需要监测下睡眠情况,主要是分析打呼噜的情况。家里有一个小米摄像头,正好利用起来。

    步骤也比较简单,睡觉前摄像头打开,然后随便对着墙(因为我们只要音频),第二天起床后把所有监控文件按照时间顺序合并,并转为wav文件。

    我这里7个小时左右的音频,大小2.4GB。

    由于分析过程中发现有咳嗽的情况,又增加了咳嗽的监测。

    本来想一次性分析的,结果OOM了(我用的虚拟机,分配了48G),只有改成分段处理,内存消耗大概4GB。

    完整代码

    import tensorflow as tf
    import tensorflow_hub as hub
    import numpy as np
    import librosa
    import pandas as pd
    import os
    # import soundfile as sf # librosa.load is generally robust enough
    
    # 可调整参数:
    # 
  • Rust将PDF转为图片

    最近有几张电子发票要报保险,但是腾讯微保上传发票需要上传图片,想着直接转一下,然后手机上各种APP试了一圈要么要收费,要么只能免费转第一页,不巧的是我这几张发票有明细表,都是两页的。网页版本有各种免费的,但是始终比较担心安全性,无奈只有自己搞一下。

    PDF的标准很复杂,自己实现显然不是最佳选择。PDFium 是一个开源的 PDF 渲染引擎,由 Google 开发和维护。它用于解析和渲染 PDF 文档,广泛应用于 Chrome 浏览器和其他项目。PDFium 提供高效的 PDF 处理功能,包括文本提取、注释、表单填充和页面渲染,支持多平台,当然就包括了Android。

    开源社区有Rust的绑定,我们可以直接使用,我这里版本用的6666,社区也有预构建文件可以直接下载。

    这里参考官方例子,唯一的区别的改动是从单页导出改为全部页面导出

    pub struct PdfToImageResult {
        pub image_path: String,
    }
    
    pub fn export_pdf_to_jpegs(path: String, out_dir: String) -PdfToImageResult {
        let p = Pdfium::default();
    
        let document = 
  • 使用Github Action自动发布Jellyfin插件

    Jellyfin自定义插件需要一个meta.json,内容大致如下

    {
        "category": "Metadata",
        "guid": "a3a07da4-ae5a-4d4a-a843-5aa7e3ba0a62",
        "name": "HappyMovie",
        "description": "Get metadata from tmdb.",
        "owner": "htynkn",
        "overview": "Get metadata from tmdb.",
        "targetAbi": "10.9.0.0",
        "timestamp": "2024-10-10T13:46:00Z",
        "version": "1.0.1.4"
    }

    其中targetAbi、timestamp、version字段是每个版本都不同的,其他部分可以写死。由于这些信息都可以从C#项目中获取,这里用py脚本来获取。

    tree = ET.parse("Jellyfin.Plugin.HappyMovie/Jellyfin.Plugin.HappyMovie.csproj")
    version = tree.find("./PropertyGroup/AssemblyVersion").text
    targetAbi = tree.find("./ItemGroup/*[@Include='Jellyfin.Model']").attrib["Version"]
    timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
    
    
  • Rust使用libuv库

    在使用Rust或多或少有需要调用外部库的需求,Rust FFI对于C和C++的处理有一些区别,为了简单快捷可以使用第三方工具来进行跨语言调用,比如autocxx。这里用libuv做一个演示。

    libuv 是一个跨平台的异步 I/O 库,它被设计用来作为 Node.js 的新平台抽象层。libuv 提供了跨所有主要平台的统一的非阻塞 I/O 基础设施,包括 Windows、Linux、macOS 和其他类 Unix 系统。

    我们先在github现在需要版本的libuv源代码,这里核心需要的是h文件,放到项目中,我这里用的third/libuv-1.48.0/include

    在Cargo文件中配置依赖

    [dependencies]
    autocxx = "0.26.0"
    cxx = "1.0"
    
    [build-dependencies]
    autocxx-build = "0.26.0"
    miette = { version = "5", features = ["fancy"] } 

    再新增一个build.rs文件…

  • OpenRewrite复合配方用于自动化迁移

    OpenRewrite复合配方用于自动化迁移

    OpenRewrite是一个源代码的自动重构生态系统,使开发人员能够有效地消除代码中的技术债务。

    OpenRewrite可以实现代码解析和改写,理论上可以适用于很多场景,不仅仅是消除基础债务,只要能够通过固定规则进行代码变化的工作它理论上都能胜任。本文演示用OpenRewrite将已有Spring MVC项目迁移到阿里云云原生网关中。

    背景

    当前我们有若干个微服务工程,由于早期没有网关基建,所有建设了若干个Spring MVC项目对外提供HTTP能力,这些项目运行于Jetty环境,内部通过Dubbo协议调用后端一个或者多个后端服务,由于早期研发规范缺乏,这些HTTP项目包含大量业务逻辑和编排,所以HTTP项目中的逻辑需要保留。

    从微服务长期治理看,我们希望这些逻辑更加内聚,将HTTP项目和后端的微服务合并成一个服务,通过云原生网关的能力实现HTTP和Dubbo项目的转换。

    思路

    如果是我们手动进行相关工作,大体有以下几步骤

    • 找到要迁移的Controller(一般由@Controller或者@RestController作为注解)
    • 找到要迁移的方法(一般由@RequestMapping或者@GetMapping等作为注解)
    • 提取其中的关键信息,包括请求路径和参数
    • 生成Dubbo接口定义和请求参数
    • 拷贝原有Controller方法,尽可能保留原有逻辑,只改写参数相关的逻辑
    • 拷贝原有工程代码到服务化工程中(单独的package包),并处理所有import
    • 在云原生网关配置接口信息,包括HTTP参数和后端Dubbo服务信息

    要自动化这些步骤我们需要以下技术/工具

    无损语义树

    要完成以上工作,我们需要和Java代码打交道,常规的AST解析会丢失一些信息,我们需要保留他们,包括但不限于注释、空格等,这里就需要用到OpenRewrite的无损语义树了。

    OpenRewrite 的 “Lossless Semantic Trees” 是指 OpenRewrite 使用的一种特殊的抽象语法树(AST),它能够在进行代码分析和重构时保留所有源代码的信息,包括注释、格式和空白字符。这种 AST 的设计允许 OpenRewrite 在不丢失任何原始代码细节的情况下,进行精确的代码修改。在传统的 AST 中,通常会忽略空格、换行和注释等信息,因为这些元素对于代码的语义分析不是必需的。

    OpenRewrite 的 Lossless Semantic Trees 保留了这些信息,使得代码重构操作可以像编辑器中手动修改代码一样,保持代码的原始风格和注释。这样,当使用 OpenRewrite …

  • 计算最长路径

    计算最长路径

    微服务场景中很容易出现A-B-C-D-E的情况,现在想找到最长的调用链路,目前已经有从Trace中抽样的数据,遗憾的是只有直接调用关系,比如A-B、C-D、B-C、B-E 这种,所以需要自己加工一下。

    由于场景边界还挺多的,而且不排除后期需要分析其他场景,比如A调用的服务数量等,所以还是考虑使用成熟的工具。

    图形数据库

    第一个想到的就是图形数据库,图形数据库使用图结构进行语义查询的数据库,它使用节点、边和属性来表示和存储数据。这里我们将每个调用拆分为服务作为节点,调用关系作为边来处理。

    为了简单我是用的是Neo4j的嵌入模式,由于一次性需求,每次都新建数据然后查询

    File dataFile = Files.createTempDirectory("neo4j").toFile();
    
    DatabaseManagementService managementService = new DatabaseManagementServiceBuilder(dataFile.toPath()).build();
    GraphDatabaseService graphDb = managementService.database(DEFAULT_DATABASE_NAME);

    为了加快速度,使用ID作为索引

    
    try (Transaction tx = graphDb.beginTx()) {
          Schema schema = tx.schema();
          schema.indexFor(label).on("id").withName("id").create();
          tx.commit();
    
  • 手动运行OpenRewrite配方

    OpenRewrite提供了Maven插件,可以方便运行在Maven管理的项目上,如果需要在其他环境运行,比如自定义的CLI等,就需要手动运行了。

    概念

    先明确下OpenRewrite相关的几个概念

    配方:需要执行的变更,配方可以是内置的,也可以是第三方社区的,更进一步是自定义的。配方的指定主要通过全名完成,比如org.openrewrite.java.OrderImports

    目标项目:要执行变更的项目,一般通过路径表示,也可以通过是远程文件,也可以通过扩展SourceFile适配更多情况

    运行环境:配方执行的基础,提供配方运行、消息处理等,可以扩展ExecutionContext,也可以使用InMemoryExecutionContext

    环境:用于支持运行环境,提供配方管理、资源加载器等,关键类:Environment

    运行流程

    • 一般按照如下流程进行:
    • 初始化环境
    • 激活配方并验证配方
    • 初始化运行环境
    • 解析目标项目,获取源文件集合
    • 运行配方获取变更
    • 根据变更修改项目

    关键代码

    //运行环境准备
    Environment env = Environment.builder().scanRuntimeClasspath().scanUserHome().build();
    ExecutionContext executionContext = initExecutionContext();
    MavenParser.Builder mavenParserBuilder = initMavenRelatedConfig(executionContext);
    
    //指定配方
    Recipe recipe = env.activateRecipes("org.openrewrite.java.RandomizeId");
    Collection<Validated<Objectvalidateds = recipe.validateAll();
    //验证配方
     for 
  • 自定义WildReceipt Paddle Dataset

    自定义WildReceipt Paddle Dataset

    Paddle Dataset是Paddle生态中的数据源抽象,至少需要提供两个方法

    def __getitem__(self, idx):
    
    
    )
    def __len__(self):
    
    )

    将自己的数据封装为Dataset后可以配合高层API使用,也可以享受Paddle生态的各种加强,比如批量加载等。

    Paddle内部包含一些常用的数据集,比如MNIST。常用数据集的封装还包含了自动下载,非常适合新手使用。

    WildReceipt数据集作为文本关键信息提取的基准,无论从数据量还是结构上,都要优于其他公开的数据集。主要用于文档的关键信息提取训练。这里演示下怎么制作Dataset。

    数据集结构

    制作数据集的第一步是了解数据集,了解数据集的结构和需要的输出,这里的输出可能需要关联具体的模型。

    这里使用WildReceipt + SDMGR进行演示。
    WildReceipt数据集主要分两部分,一部分是图片本身,一部分是区域标注。这部分信息存储在txt文件中,图片放在images目录中。由于是在Paddle中,这里直接使用https://paddleocr.bj.bcebos.com/ppstructure/dataset/wildreceipt.tar。

    下面是数据的一些片段

    image_files/Image_12/10/845be0dd6f5b04866a2042abd28d558032ef2576.jpeg	[{"label": "Store_name_value", "transcription": "CHOEUN", "points": [[114.0, 19.0], [230.0, 19.0], [230.0, 1.0], [114.0, 1.0]]}, {"label": "Store_name_value", "transcription": "KOREANRESTAURANT", "points": [[97.0, 35.0],