AI 制药平台的架构思想:服务拆分、数据隔离与资源调度
梳理 AI 制药平台的轻量服务化架构,理解 Java 与 Python AI 服务的职责划分、异构资源调度、逻辑删除和多租户数据隔离。
- Java 独占任务状态机是什么意思?
批量任务“现在进行到哪一步、是否成功失败、能不能重试”这些状态,只能由 Java 主后端管理和保存;Python AI 服务只负责计算。
AI制药平台的架构思想
- 分布式 + 服务化架构,不是完整意义上的微服务
- 异构资源调度
[GPU 机器] ai-service (Python 推理) ▲ HTTP [大内存机器] java-backend ──┬──▶ [DB 机器] PostgreSQL + Redis ▲ HTTP [Nginx/CDN] frontend (静态资源)- AI 服务吃 GPU/显存
- Java 业务吃 内存/CPU
- 数据库吃 快磁盘/IO
微服务
- 微服务是按业务领域拆(订单服务、用户服务、支付服务……),你是按技术职责拆(Java 管业务 / Python 管 AI 计算)。你更像"前后端分离 + 后端按技术分层",是 SOA(面向服务架构)的轻量形态。
- 微服务讲究"每个服务一个库"(database per service)
- 真正的微服务通常还配套服务注册发现、API 网关、配置中心、链路追踪、独立 CI/CD。你现在没有这些(也不需要)。
数据库字段
deleted 字段 = 逻辑删除(软删除)
它解决什么问题:普通的 DELETE FROM molecules WHERE id=1 是物理删除,数据真没了,找不回来。逻辑删除是假删——不真删行,只把 deleted 字段从 0 改成 1,标记"这条已删除"。
这个框架的配置(application.yml):
logic-delete-field: deleted # 用 deleted 这一列标记
logic-delete-value: 1 # 1 = 已删除
logic-not-delete-value: 0 # 0 = 正常
机制(对你透明):
- 你调用 MyBatis-Plus 的
deleteById(1),它实际执行的不是 DELETE,而是UPDATE molecules SET deleted=1 WHERE id=1。 - 你写
SELECT * FROM molecules,框架自动改写成SELECT * FROM molecules WHERE deleted=0,已删的行你查不到。 - 所以你平时写代码感觉就是"删了就没了",但数据其实还在库里。
为什么企业都这么做:误删能恢复、保留历史审计、关联数据不断链、满足合规(很多业务数据法律上不允许真删)。
一个你要知道的坑(正好影响 molecules):逻辑删除下,"删掉的旧行还占在表里"。如果你给 canonical_smiles 建普通唯一索引,删掉阿司匹林后再想重新录入,会因为旧的那条(deleted=1)还在而唯一冲突报错。PostgreSQL 的正解是用部分唯一索引 UNIQUE (tenant_id, canonical_smiles) WHERE deleted=0——只对"未删除"的行约束唯一。这点我会写进给 codex 的任务清单。
多租户
多租户是什么:一套系统、一个数据库,同时服务多个"租户"(可以理解成多个公司/组织),租户之间数据互相隔离、彼此看不见。典型场景是 SaaS——A 公司和 B 公司用同一个系统,但各自只能看到自己的数据。
这个框架用的是"行级多租户"(共享库、共享表,靠一列 tenant_id 区分):
- 需要隔离的表加一列
tenant_id - 拦截器(
TenantLineInnerInterceptor+OinsistTenantLineHandler)在每条 SQL 自动加tenant_id条件 - 当前租户哪来的?前端请求头带
Tenant-Id(默认000000),后端取出来用
举例,你写:
SELECT * FROM molecules WHERE mol_weight > 100
拦截器自动改写成:
SELECT * FROM molecules WHERE mol_weight > 100 AND tenant_id = '当前租户'
你写业务代码时几乎感知不到 tenant_id 的存在,它由框架自动加。
是不是每张表都要加?——不是,分两类(这正是 OinsistTenantLineHandler 里那个白名单的意义):
| 类型 | 加 tenant_id 吗 | 例子 |
|---|---|---|
| 租户隔离表(各租户私有的业务数据) | ✅ 加 | sys_user、sys_role、molecules、prediction_tasks |
| 全局共享表(所有租户公用的系统/字典数据) | ❌ 不加,进 IGNORE_TABLES 白名单 | sys_menu(菜单全系统一套)、sys_tenant(租户表自己)、sys_role_menu |
规则:默认就是"加 tenant_id"(拦截器默认拦截所有表),只有当这张表是全系统共享的字典/系统表时,才把它放进白名单豁免。