外观
AI-data — AI 数据标注与模型验证平台
分层:独立遗留平台(非易盾微服务集群) | 部署单元:2 个可构建(
ai-data-admin、docs/database),另 2 个(ai-data-scheduler、ai-data-collector)无构建脚本 | 数据库:MySQL(库ai_data) | base image:tomcat:8.5.79-jdk8-openjdk-buster(admin)·private-registry.yidun.internal/library/mysql:5.7(db-init)
一、模块定位与架构
模块是一套独立的 AI 数据标注与模型验证平台:运营在 Web 后台导入/标注文本、图像、音频、视频样本,调度器周期性做数据收集、导入、清洗与模型验证,采集器从 Kafka 拉取外部上传/厂商回调消息。
它是遗留系统——Spring 4.3.18 + XML 配置 + Tomcat WAR,不是 Spring Boot,与易盾其余微服务(Dubbo/Kafka/Apollo 体系)无代码耦合,部署上也自成一套(docs/部署说明.md 明确只需 MySQL + MinIO 两个外部依赖)。
架构分层
- Web 层:
ai-data-admin(Spring MVC + Spring Security OpenID,Freemarker 视图,Tomcat WAR) - 调度层:
ai-data-scheduler(ElasticJob-Lite,ClassPathXmlApplicationContext启动) - 采集层:
ai-data-collector(Kafka 消费,ClassPathXmlApplicationContext启动) - 数据层:
ai-data-base(base-application-context.xml:dbcp2 数据源 + MyBatis + 事务) - 支撑:
ai-data-common、ai-data-cli、docs/database(MySQL 初始化镜像)、build/(ant 打包脚本)
关键数据流
- 浏览器 →
ai-data-admin的*.html/*.json//api/text/submit//api/image/collect等映射,经DispatcherServlet进入各 Controller ContextLoaderListener加载 root context(web-application-context.xml)→base-application-context.xml(dbcp2 + MyBatis)读写ai_data库- 图片/音频/视频文件走对象存储(MinIO,
object.storage.type=minio,bucketpublic) ai-data-scheduler的 17 个 ElasticJob 按 cron 执行导入/清洗/爬虫/模型验证- 模型验证链:
initializeWorker(复制数据)→executeWorker(验证执行)→analyzeWorker(分析结果),均cron=0/10 * * * * ? ai-data-collector消费 Kafka topicyidun_nos_upload/NeteaseImage_Provider_Message,把样本落盘/入库- 健康检查:
/health/status、/health/active、/health/offline(映射到DispatcherServlet)
二、可部署服务清单
| 部署单元目录 | artifactId/包名 | 镜像名 | 端口 | 服务类型 | 独立 Dockerfile | 是否进 kubernetes.yml/Helm |
|---|---|---|---|---|---|---|
ai-data-admin | com.netease.is.ai.data:ai-data-admin(war) | private-registry.yidun.internal/neteaseis/ai/ai-data-admin:<git8>-private | 8080(Tomcat /=ROOT)/8199(tomcat7-maven-plugin dev) | Spring MVC WAR | 有 | 否(手工 docker build,无清单) |
docs/database | (无 artifactId,MySQL 脚本封装) | private-registry.yidun.internal/neteaseis/ai/ai-data-admin-db-init:<git8>-private | 无(基于 mysql:5.7) | 数据库初始化镜像 | 有 | 否 |
ai-data-scheduler | com.netease.is.ai.data:ai-data-scheduler | — | 无 HTTP | ElasticJob-Lite 调度 | 无(不构建) | 否 |
ai-data-collector | com.netease.is.ai.data:ai-data-collector | — | 无 HTTP | Kafka 采集 | 无(不构建) | 否 |
说明:
ai-data-common、ai-data-base、ai-data-cli为非部署库/工具模块。ai-data-scheduler、ai-data-collector目录下无 Dockerfile、无build.sh,用 ant 脚本(build/javaapp-build.xml)手工打包。
构建入口(无 build.sh):
buildAll.sh=mvn -DskipTests=true -pl ai-data-admin -am package+bash build-docker-image.sh ai-data-adminbuild-docker-image.sh:docker build . -f ${app}/Dockerfile -t private-registry.yidun.internal/neteaseis/ai/${app}:$(git rev-parse HEAD|cut -c1-8)-private(单架构,无 buildx),只构建ai-data-admindocs/database/build-docker-image.sh:单独构建 db-init 镜像- scheduler / collector 用 ant 脚本
build/javaapp-build.xml(mvn=mvn1.8.0_66、dependency:copy-dependencies、产出compressed/lib|conf) - admin 的 ant 打包脚本为
build/web-build.xml(mvn ... war:inplace+compile+compress-web)
三、同模块启动顺序
docs/database— 用初始化镜像导入ai-data-admin.sql/ai-data-admin-init.sql,建库建表;是 DB 前置ai-data-admin— Web/API 入口,先于调度与采集,保证数据导入与标注可用ai-data-scheduler— ElasticJob 依赖 ZK 与 DB;导入/清洗任务需要 admin 已写入的数据ai-data-collector— Kafka 消费位点提交依赖 topic 与 DB/对象存储就绪,最后启动
理由:DDL 先行;Web 入口与调度/采集共享同一 ai_data 库;调度与采集各自还有 ZK / Kafka 硬依赖。
四、逐服务启动逻辑
ai-data-admin
- 无
@SpringBootApplication,入口是 Servlet 容器:src/main/webapp/WEB-INF/web.xml ContextLoaderListener加载classpath:web-application-context.xml(root context):- import
classpath:base-application-context.xml+classpath:web-security-application-context.xml context:component-scan扫com.netease.is.ai.data.admin,use-default-filters=false,include@Component、exclude@ControllerauditPropertyConfigurer(PropertyPlaceholderConfigurer,classpath:web.properties)+util:properties id="webProperties"- 源码中
RedisHttpSessionConfiguration被注释(未启用 Redis Session)
- import
DispatcherServlet(servlet-name=ai-data-admin、load-on-startup=2),默认加载WEB-INF/ai-data-admin-servlet.xml:security:global-method-security(pre-post-annotations/secured-annotations开启)FreeMarkerViewResolver+FreeMarkerConfigurer(templateLoaderPath=/WEB-INF/views/)CommonsMultipartResolver(maxUploadSize=209715200,200MB)context:component-scan扫com.netease.is.ai.data.admin,include@Controller;<mvc:annotation-driven/>+<aop:aspectj-autoproxy/>- 大量
<mvc:view-controller>:tag/label/image/audio/video/text/train/verify/report/sys/dev 各页面
- Filter:
CharacterEncodingFilter(SetCharacterEncoding,UTF-8,forceEncoding=true)、springSecurityFilterChain(DelegatingFilterProxy) - URL 映射:
*.html、*.json、/api/text/submit、/api/text/deduplication/submit、/api/image/collect、/api/audio/task/export、/api/audio/project/export,健康端点/health/status、/health/active、/health/offline session-timeout=60(分钟);错误页统一/error.htmlbase-application-context.xml→ importbase-db-application-context.xml:- import
base-application-context-redis.xml(内容全部注释,Redis 未启用) BasicDataSource(dbcp2):initialSize=10、maxTotal=50、maxIdle=50、minIdle=10、maxWaitMillis=180000、connectionInitSqls=SET NAMES utf8mb4、validationQuery=SELECT 1、removeAbandonedOnMaintenance=true、驱逐线程timeBetweenEvictionRunsMillis=300000SqlSessionFactoryBean(configLocation=base-db-mybatis-config.xml、mapperLocations=classpath*:com/netease/is/ai/data/base/**/*.xml)MapperScannerConfigurer(basePackage=com.netease.is.ai.data.base、annotationClass=com.netease.is.ai.data.base.abst.MyBatisMapper)tx:annotation-driven+DataSourceTransactionManager
- import
- 启动钩子:无(非 Spring Boot)
ai-data-scheduler
- 入口:
com.netease.is.ai.data.scheduler.Main(ai-data-scheduler/src/main/java/.../Main.java) - 启动命令:
java com.netease.is.ai.data.scheduler.Main(脚本/ant 打包后java -cp lib/*) main():new ClassPathXmlApplicationContext("scheduler-application-context.xml"),随后TimeUnitUtils.sleepQuietly(Long.MAX_VALUE, TimeUnit.MILLISECONDS)保活scheduler-application-context.xml:importbase-application-context.xml(DB)+scheduler-application-context-jobs.xml;PropertyPlaceholderConfigurer读classpath:scheduler.properties;component-scan扫com.netease.is.ai.data.scheduler(include@Component、exclude@Controller);aop:aspectj-autoproxyscheduler-application-context-jobs.xml:<reg:zookeeper id="zookeeper" server-lists="${zookeeper.servers}" namespace="ai-data-scheduler-jobs"/>+ 17 个<job:simple>,含:- 导入:
tagImportWorker、audioImportWorker、textImportWorker(0 0 1/2/10 * * ?类 cron) - 清洗:
textTagVoteCleaner、textVerifyTaskCleanWorker、imageProviderLogCleanupWorker、audioFilterLogCleanupWorker - 统计:
operatorStatisticWorker、imageLabelFaceWorker、textVerifyTaskWorker - 爬虫:
simpleSpiderWorker、voaCommentSpiderWorker - 模型验证:
initializeWorker、executeWorker、analyzeWorker(三者cron=0/10 * * * * ?)
- 导入:
- 启动钩子:无(ElasticJob 由 Spring 容器 init 时注册到 ZK)
ai-data-collector
- 入口:
com.netease.is.ai.data.collector.Main(ai-data-collector/src/main/java/.../Main.java) main():new ClassPathXmlApplicationContext("collector-application-context.xml"),随后sleepQuietly(Long.MAX_VALUE)保活collector-application-context.xml:PropertyPlaceholderConfigurer读classpath:collector.properties;component-scan扫com.netease.is.ai.data.collector(include@Component、exclude@Controller);aop:aspectj-autoproxy(未 importbase-application-context.xml)collector.properties:kafka.topic.upload.url=yidun_nos_upload、kafka.topic.image.provider=NeteaseImage_Provider_Message、kafka.upload.bootstrap.servers、kafka.provider.bootstrap.servers、kafka.image.parent.dir=target/images
五、启动前置依赖
| 依赖 | 配置键/地址 | 阻塞 or 弱依赖 | 配置文件 |
|---|---|---|---|
| MySQL | db.url=jdbc:mysql://10.219.17.232:3306/ai_data?...(driver com.mysql.cj.jdbc.Driver,admin/eHSkDv7c77*j);dbcp2 initialSize=10 / maxTotal=50 | 阻塞(admin/scheduler,经 base-application-context.xml) | ai-data-base/src/main/resources/base.properties |
| 对象存储(MinIO) | object.storage.type=minio、object.storage.endpoint=http://minio.app.yidun.internal、object.storage.bucketName=public、object.storage.access/secret | 弱依赖(图片/文件读写) | base.properties |
| ZooKeeper | zookeeper.servers=10.170.164.164:2181,10.170.164.165:2181,10.170.164.166:2181 | 阻塞(仅 scheduler,ElasticJob 注册中心) | ai-data-scheduler/src/main/resources/scheduler.properties |
| Kafka | kafka.upload.bootstrap.servers / kafka.provider.bootstrap.servers(10.165.124.212:9092,10.165.124.49:9192,10.165.124.50:9192) | 阻塞(仅 collector) | ai-data-collector/src/main/resources/collector.properties |
| NOS | nos.hostname=nos2-i.service.163.org | 弱依赖 | scheduler.properties / ai-data-common/.../nos.properties |
| 外部算法/API | text.train.remote.api.server、image.service.api.*、audio.asr.*、huyu.image.*、xunfei.audio.*、evaluate.grpc.api(fake-hostname:31422)、idcard.evaluate.grpc.api、avana.grpc.api | 弱依赖(缺失则对应能力不可用) | base.properties |
| 部署声明依赖 | 仅 mysql + minio(见 docs/部署说明.md,账号 admin,密码 S7Ezia4w) | — | docs/部署说明.md |
六、启动参数与 Profile
- 非 Spring Boot:无
-Dspring.profiles.active。环境切换靠 ant 脚本变量-Denv.type=test|online(build/javaapp-build.xml、build/web-build.xml的env.type/current.env.type,默认test) - 环境覆盖目录:
src/main/resources/conf/{test,online}(如base.properties、web.properties、log4j2.xml),antcp/cp-special用${env.type}覆盖 - 构建命令:
buildAll.sh=mvn -DskipTests=true -pl ai-data-admin -am package+bash build-docker-image.sh ai-data-adminbuild-docker-image.sh:docker build . -f ${app}/Dockerfile,单架构、无 buildx,只构建ai-data-admindocs/database/build-docker-image.sh:docker build . -f Dockerfile -t .../ai-data-admin-db-init:$(git rev-parse HEAD|cut -c1-8)-private
- 镜像 tag:
<git-short-sha8>-private;仓库private-registry.yidun.internal/neteaseis/ai - admin 容器启动:无自定义 CMD,用
tomcat:8.5.79-jdk8默认启动;Dockerfile 额外wget解压 ffmpeg 到/home/nisp/ - 打包工具链:ant 脚本用
mvn1.8.0_66(JDK 8),javaapp-build.xml产出compressed/lib|conf,web-build.xml产出compressed(webapp +WEB-INF/classes) - 依赖版本:Spring
4.3.18.RELEASE、Spring Security4.0.3.RELEASE(含spring-security-openid)、MyBatis3.3.0、dbcp22.1、ElasticJob2.1.5、mysql-connector8.0.33、kafka-clients0.10.0.0
七、启动期踩坑
- admin 不打 war:pom 的
maven-war-plugin把default-war的phase设为none,改执行war-exploded(产物是目录target/ai-data-admin-0.0.1/)。Dockerfile 直接COPY ai-data-admin/target/ai-data-admin-0.0.1 → webapps/ROOT;若本地直接mvn package得到的是 war 而非 exploded 目录,COPY 会失败。 - Dockerfile 把
ai-data-common/base/admin的src/main/resources/*.*扁平拷贝进WEB-INF/classes(只匹配一层),依赖扁平命名的资源;新增子目录资源不会被打进镜像,需改 Dockerfile。 - Spring 4.3.18 +
spring-security-openid:登录依赖网易 OpenID 外部服务,网络不通时登录页可用但认证回不来。 base-application-context-redis.xml内容全部是注释,即 Redis 未启用;误以为需要 Redis 会白配。MapperScannerConfigurer.annotationClass=com.netease.is.ai.data.base.abst.MyBatisMapper→ 只有标注该注解的 mapper 才会被扫描注册,普通 mapper 接口无效。ai-data-scheduler、ai-data-collector无 Dockerfile、无构建脚本,buildAll.sh不会构建它们;且两者配置为硬编码 IP(ZK10.170.164.164等),迁移环境需手工改scheduler.properties/collector.properties。ai-data-collector的collector-application-context.xml未 importbase-application-context.xml,不装配 dbcp2 数据源;若期望它直连 DB 需自行补 import。- 环境切换靠 ant 的
-Denv.type(build/javaapp-build.xml、build/web-build.xml),默认test;忘记传online会打出一份指向测试环境配置的包(conf/test覆盖conf/online)。 - ElasticJob 的 namespace 固定为
ai-data-scheduler-jobs;同 ZK 上多环境共用一个 namespace 时会互相抢分片。
八、跨模块前置
- MySQL(
ai_data库,阻塞)与 MinIO(对象存储,弱依赖)是docs/部署说明.md明确的部署依赖(账号admin,密码S7Ezia4w) - ZooKeeper(仅 scheduler,阻塞)、Kafka(仅 collector,阻塞)
- 外部能力(弱依赖):文本训练远程 API、图像算法 API、ASR/幻听/讯飞等第三方服务、模型验证 gRPC(
evaluate.grpc.api等) - 与易盾其余模块关系:无代码耦合,不使用 Dubbo、Apollo、Redis;属独立交付单元