国密 SM2 签名在 1 万 TPS 下 CPU 打满,如何用硬件加速卡优化

解读

  1. 场景定位:金融、政务、运营商等国产化环境,网关/核心服务需 1 万 TPS 持续 SM2 签名,单 CPU 核 100% 占用,RT 抖动大,已触发 SLA 告警。
  2. 瓶颈本质:SM2 是椭圆曲线公钥算法,签名流程含大数模乘、模逆、点乘,纯软件实现每条签名约 1.2~1.5 ms(2.5 GHz 主频),1 万 TPS 需要 10 000×1.2 ms = 12 s 的 CPU 时间,远超单核 1 s 上限,多核水平扩展又受锁竞争与上下文切换放大。
  3. 硬件加速卡选型:国内合规场景只能采用已过国家密码管理局检测的 PCIe 密码卡(型号目录可在国家密码管理局官网查询),常见形态为 PCIe 3.0 x8、x16,单卡理论 SM2 签名性能 15k50k ops/s,延迟 0.050.15 ms,功耗 25~45 W,支持 SR-IOV 或多队列。
  4. 优化目标:在 1 万 TPS 下把 CPU 占用降到 30% 以内,P99 延迟 < 20 ms,同时满足“国密合规、可运维、可灰度、可回滚”。

知识点

  1. SM2 签名流程:ZA‖M → e=Hash → 随机 k → (x1,y1)=k·G → r=(e+x1) mod n → s=(1+dA)⁻¹·(k-r·dA) mod n → (r,s)。
  2. 密码卡卸载接口:
    a. 同步阻塞:如 PKCS#11 C_Sign,调用线程阻塞,适合低并发。
    b. 异步批量:如 SDF(《GMT 0016-2012》)SDF_InternalSign_ECC_Async,一次下发 128256 条,中断或轮询完成。
    c. 多队列:SR-IOV 虚拟出 8
    16 个 VF,每个 VF 独立队列,NUMA 绑核后可线性扩展。
  3. 性能测试指标:
    TPS、Latency(P50/P99/P999)、CPU 利用率(usr/sys/steal)、QPS per Watt、密码卡队列堆积深度、PCIe 带宽占用、上下文切换、softirq、numa_miss。
  4. 常用工具:
    perf top -p pid 看热点函数;sar -I SUM 看硬中断分布;tuned-adm 选 network-latency;taskset/numactl 绑核;echo 1 > /sys/bus/pci/devices/…/sriov_numvfs 开 VF;dmesg | grep hwrng 确认卡内随机源。
  5. 合规要求:
    密钥必须在卡内生成且不可明文导出;随机数须通过《GMT 0005-2012》检测;FIPS 140-2/3 与国密双证;生产环境需启用卡内门限签名或密钥分片,防止单卡失效导致业务中断。

答案

  1. 选型与采购
    根据 1 万 TPS 目标留 50% 余量,按单卡 20k ops/s 选型,需 1 张即可;若高可用部署,采用双机双卡主备,用 Keepalived+VRRP 或 Kubernetes 自定义调度器实现故障 30 s 内切换。

  2. 驱动与固件
    安装厂商提供的 UAC(Unified Accelerator Card)驱动,版本与内核严格对应(CentOS 7.9 用 3.10.0-1160.el7,Ubuntu 20.04 用 5.4.0-100-generic)。升级固件至 2023Q4 之后版本,修复 SM2 批量签名 DMA 泄漏 bug。

  3. 接口改造
    业务层原来调用 BouncyCastle 的 SM2Signer,改为通过 JCA Provider 桥接厂商的 PKCS#11 .so;或者使用 JNI 直接调用 SDF 接口。
    关键代码片段(伪):

    // 初始化
    hSession = PKCS11.C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, null, null);
    PKCS11.C_Login(hSession, CKU_USER, pin);
    // 异步批量签名
    for (int i = 0; i < 128; i++) {
        mechanism = new CK_MECHANISM(CKM_SM2_SIGN, null);
        PKCS11.C_SignInit(hSession, mechanism, hPrivateKey);
        PKCS11.C_SignUpdate(hSession, hash[i], 0, hash[i].length);
        future[i] = executor.submit(() -> PKCS11.C_SignFinal(hSession, out[i]));
    }
    

    批量深度 128 可在 1 万 TPS 下把调用次数降到 78 ops,上下文切换下降 90%。

  4. 线程模型与绑核
    采用“1 个加密线程池 + N 个 IO 线程”模型:

    • 加密线程池大小 = 密码卡队列深度 / 批量大小 = 1024 / 128 = 8 线程;
    • 用 numactl ‑C 8-15 ‑m 0 将加密线程绑到 NUMA0 核心,与网卡 RX 队列同 NUMA,减少跨节点内存访问;
    • 打开驱动 irq affinity,把卡中断绑到剩余核心,避免抢占加密线程。
  5. 性能验证
    基准测试:
    a. 单卡单队列:openssl speed -sm2 -elapsed -multi 1,记录 18k ops/s。
    b. 业务压测:用 JMeter 模拟 1 万 TPS,持续 30 min,观察 CPU usr<20%、sys<10%、P99=12 ms、密码卡队列深度<20、零错误。
    c. 异常注入:拔卡、降速到 PCIe 2.0、随机 reboot,验证 Keepalived 切换与零丢包。

  6. 灰度与回滚
    在 Kubernetes 中通过 ConfigMap 控制 feature flag:hardware.sm2=on|off;回滚时只需滚动重启 Pod,切回 BouncyCastle 软件实现,5 min 内完成。

  7. 交付文档
    输出《SM2 硬件加速性能测试报告》,含测试环境拓扑、版本矩阵、基准数据、瓶颈分析、合规证书编号、运维手册(驱动升级步骤、密码卡替换 SOP)、应急预案,评审通过后方可上线。

拓展思考

  1. 若未来 TPS 涨到 5 万,单卡已无法满足,如何横向扩展?
    思路:

    • 采用多卡并联,每张卡映射为独立 Kubernetes Extended Resource,调度器按“cryptocard/sm2”资源维度分配;
    • 在 Service Mesh 中做一致性哈希,按交易号分片,保证同一客户的签名请求落到同一卡,避免跨卡状态同步;
    • 引入卡内密钥分片+门限签名(GMT 0016 附录 D),2-of-3 模式,即使 1 卡故障业务无感。
  2. 如果业务同时需要 SM2 签名与 TLS 国密握手,如何复用加速卡?
    思路:

    • 采用国密双证书 SSL(ECC-SM2-WITH-SM4-SM3),握手阶段私钥运算占 70%,可把 TLS 层也 offload 到同一卡;
    • 使用 nginx+TaSSL 或 Tengine+gmtls 模块,配置 ssl_engine cryptodev;压测时需关注并发 8k 长连接下会话复用率,确保 1-RTT 比例 > 95%。
  3. 如何量化 ROI?
    公式:
    CPU 节省核数 = (软件 CPU 占用 – 硬件 CPU 占用) / 每核算力
    电费节省 = 节省核数 × 每核功耗 × PUE × 电价
    若节省 8 核,每核 10 W,PUE=1.6,电价 0.6 元/度,一年电费 = 8×10×1.6×24×365×0.6/1000 ≈ 672 元;
    单卡采购价 1.2 万元,则约 18 个月回本,再加上 SLA 提升带来的监管罚款避免,ROI 可接受。