外观
antispam-keyword — 敏感词中台(词库管理 + 在线命中检测 + 缓存分发)
分层:业务层(关键词/敏感词中台)| 部署单元:5 个有 Dockerfile(k8s 部署其中 3 个)| 数据库:MySQL/TiDB
antispam| base image:private-registry.nis.netease.com/library/base-image:jdk17(tagbase-250409)
一、模块定位与架构
它同时承担两件事:写侧为反垃圾官网/审核后台/私有化客户提供敏感词的增删改查、分组、命中配置与策略同步;读侧为各检测业务(文本/图片/音视频/解决方案/爬虫/LLM)提供在线敏感词命中检测,把命中词、位置、级别、分类、动作回传,供调用方决定先发后审/先审后发/删除。要解决的是“客户自定义词能被可靠录入、秒级生效参与检测,并在私有化场景下不漂移”。
架构分层
- 协议层(写):
facade/http-api/*Controller(/v2/keyword/privatisation/query等,含签名/IP 白名单/限频) +facade/dubbo-api的KeywordFacade/KeywordTargetConfigFacade/KeywordStrategyDataSyncSpi - 业务层:
service/business(KeywordServiceImpl、KeywordTargetConfigServiceImpl、KeywordRefreshServiceImpl、KeywordIncrServiceImpl、KeywordDataUpdater、KeywordCrudFilter) - 数据层:
service/base(DAO/Manager、Matcher 引擎KeywordTree、AcAutoMation、PinyinKeywordAutoMation、SegTool、OhcKeywordKits、RedisFrequencyCheckService)+domain - 协议层(读):
facade/dubbo-check-api的KeywordCheckFacade、facade/dubbo-cache-api的KeywordCacheFacade - 读写实现:
facade/dubbo-provider(管理)、facade/dubbo-check-provider(检测)、facade/dubbo-cache-provider(缓存)、scheduler(ElasticJob)、console/api
关键数据流(写入)
- HTTP/Dubbo 请求 →
KeywordFacadeProvider→KeywordCrudFilter校验 KeywordServiceImpl→KeywordManager写keyword/keyword_set/keyword_target_config- 写
KeywordOpRecord操作流水,推KeywordIncrRecord增量记录 KeywordReleaseTask/KeywordRefreshStrategyVersionTask定时推送策略版本号到 Kafka → 检测侧增量刷新
关键数据流(读取)
- 检测请求 →
KeywordCheckFacadeProvider.check/batchCheck KeywordCheckerFactory取该 target 的TargetKeywordCheckerPretreatmentService预处理(HTML 转义/Unicode/简繁)+ 过滤器链- 多
KeywordCheckerExecutor(普通 AC / 拼音 / 智能词 / 分词)匹配后合并为KeywordCheckResult
二、可部署服务清单
| 部署单元目录 | artifactId | 镜像名 | 端口 | 服务类型 | 独立 Dockerfile | 是否进 kubernetes.yml |
|---|---|---|---|---|---|---|
facade/dubbo-provider | antispam-keyword-facade-dubbo-provider | private-registry.yidun.internal/yidun/antispam-keyword-dubbo-provider:1.0.0.private | Dubbo port=-1 | Dubbo Provider(管理写侧) | 有 | 是(replicas=2) |
facade/dubbo-check-provider | antispam-keyword-facade-dubbo-check-provider | private-registry.yidun.internal/yidun/antispam-keyword-dubbo-check-provider:1.0.0.private | Dubbo port=-1 | Dubbo Provider(在线检测) | 有 | 是(replicas=2) |
facade/dubbo-cache-provider | antispam-keyword-facade-dubbo-cache-provider | 声明 antispam-keyword-dubbo-provider(与 dubbo-provider 同名冲突,实际不产出独立镜像) | Dubbo port=-1 | Dubbo Provider(缓存一致性 Hash 分发) | 有 | 否 |
facade/http-api | antispam-keyword-facade-http | 若构建为 antispam-keyword-http-api:base-250409 | 8700(base) / 8080(private) | HTTP(私有化查询) | 有 | 否 |
scheduler | antispam-keyword-scheduler | private-registry.yidun.internal/yidun/antispam-keyword-scheduler:1.0.0.private | 无 Web | ElasticJob | 有 | 是(replicas=1) |
console/api | antispam-keyword-console-api | 无(不构建) | — | 控制台后端 | 无 | 否 |
三、同模块启动顺序
facade/dubbo-provider—— 词库写入入口,dubbo.application.id=antispam-keyword-dubbo,是其余单元的写入源与 ZK 注册主体。scheduler—— 定时释放到期词(keywordReleaseTask)与推策略版本号;须能读到 dubbo-provider 写入的库与 Kafka。facade/dubbo-check-provider—— 检测侧,启动期预热词表/AC 自动机,读取 scheduler 推送的增量与缓存 provider 的数据。facade/dubbo-cache-provider—— 为检测集群分发本地缓存(一致性 Hash 路由),被 check 侧依赖。facade/http-api—— 私有化查询接口,@DubboReference引PrivatizationKeyFacade且check=false,最可后启。
四、逐服务启动逻辑
facade/dubbo-check-provider
- 启动类:
com.netease.is.antispam.keyword.facade.dubbo.check.CheckDubboApplication(facade/dubbo-check-provider/src/main/java/.../CheckDubboApplication.java) @SpringBootApplication:无 exclude@Enable*:@EnableAspectJAutoProxy、@EnableTransactionManagement、@EnableScheduling、@EnableElasticJob、@EnableBusinessClient、@EnableDistributeId、@ComponentScan("com.netease.is.antispam.keyword")、@MapperScan("com.netease.is.antispam.keyword.dao")、@EnableConfigurationProperties({KeywordProperties.class});无 Apollo- 关键配置:
dubbo.application.id=antispam-keyword-check-dubbo、dubbo.protocol.threads=100(base)→500(private)、dubbo.provider.filter=qos-access、dubbo.protocol.register=false、business.client.autoStart=false、business.client.options.checkerTypes=KEYWORD,OCR_KEYWORD、keyword.refresh.task.cron=0 0/1 * ? * *、keyword.in.heap=false、keyword.dubbo.delay=260000 main():仅SpringApplication.run(...)- 启动钩子:
ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent>(.../dubbo/check/listener/)→ 容器刷新后做词表与 AC 自动机预热;另有PreTargetCheckerListener/TargetCheckerListener(business.client事件监听)
facade/dubbo-provider
- 启动类:
com.netease.is.antispam.keyword.facade.dubbo.DubboApplication @EnableBusinessClient、@SpringBootApplication、@EnableAspectJAutoProxy、@EnableTransactionManagement、@ComponentScan("com.netease.is.antispam.keyword")、@MapperScan("...keyword.dao")、@EnableDistributeId- 关键配置:
dubbo.application.id=antispam-keyword-dubbo、dubbo.consumer.connections=10、dubbo.provider.payload=15728640、business.client.options.operateTargetTypes=LABEL_INFO、distribute.id.namespace=antispam-keyword main():仅 run;启动钩子:未发现(同类中有KeywordStrategySyncService但非启动钩子)
facade/dubbo-cache-provider
- 启动类:
com.netease.is.antispam.keyword.facade.dubbo.cache.CacheDubboApplication @SpringBootApplication、@EnableAspectJAutoProxy、@EnableTransactionManagement、@EnableScheduling、@EnableBusinessClient、@ComponentScan("com.netease.is.antispam.keyword")、@MapperScan("...keyword.dao")、@EnableConfigurationProperties({KeywordProperties.class})- 关键配置:base
spring.profiles.active=test(非 dev)、dubbo.application.id=antispam-keyword-cache-dubbo、dubbo.protocol.dispatcher=message、dubbo.protocol.threads=100、dubbo.protocol.register=false、keyword.refresh.task.cron=0 0/2 * ? * * - 启动钩子:
ContextRefreshedEventListener(.../dubbo/cache/listener/);main()仅 run
facade/http-api
- 启动类:
com.netease.is.antispam.keyword.facade.http.HttpApplication @SpringBootApplication、@EnableTransactionManagement、@EnableBusinessClient、@EnableDistributeId、@ComponentScan("com.netease.is.antispam.keyword")、@MapperScan("...keyword.dao")、@EnableConfigurationProperties(KeywordPrivatisationProperties.class);无 Apollo- 关键配置:
server.port=8700(base)→8080(private)、dubbo.registry.id=keywordRegistry、dubbo.consumer.timeout=5000、business.client.options.operateTargetTypes=PRODUCT,...,CLIENT_SCENE、keyword.batchinsert.generalid=false - 启动钩子:
PrivatisationSecretService.@PostConstruct init()(.../http/controller/privatisation/)建 CaffeineLoadingCache(initialCapacity 50 / maximumSize 2000 / expireAfterWrite 10min);类内@DubboReference(version="1.0.0", check=false, timeout=1000)引PrivatizationKeyFacade
scheduler
- 启动类:
com.netease.is.antispam.keyword.scheduler.ScheduleApplication @SpringBootApplication、@EnableElasticJob、@EnableBusinessClient、@ComponentScan("com.netease.is.antispam.keyword")、@MapperScan("...keyword.dao")- 任务:
keywordReleaseTask(cron0 0/1 * ? * *)、keywordRefreshStrategyVersionTask(0 0/5 * ? * *)、keywordClientIdDataRevisionTask(0 * * ? * *,均overwrite=true)与KeywordDeleteFix修复任务 - 内置
YidunQosApplication;elastic.job.zk.namespace=antispam-keyword-scheduler-jobs-private;main()仅 run
五、启动前置依赖
| 依赖 | 配置键 / 地址 | 阻塞 or 弱依赖 | 配置文件 |
|---|---|---|---|
| MySQL/TiDB | spring.datasource.url=jdbc:mysql://tidb-cluster0-tidb.tidb.svc:4000/antispam(driver com.mysql.cj.jdbc.Driver) | 阻塞 | application-private.properties |
| ZK — Dubbo 注册中心 | dubbo.registry.address=zookeeper://zk-0.zookeeper.yidun-infra:2181?backup=...,group /yidun/antispam/online-new/yidun-antispam-dubbo | 阻塞 | application-private.properties |
| ZK — business.client | business.client.options.zookeeper.address=zk-0/1/2.zookeeper.yidun-infra:2181,rootPath /netease-antispam/online/config | 阻塞 | application-private.properties |
| ZK — ElasticJob | elastic.job.zk.server-lists=...,elastic.job.zk.namespace=antispam-keyword-scheduler-jobs-private | 阻塞(check-provider / scheduler) | application-private.properties |
| ZK — 分布式 ID | distribute.id.namespace=antispam-keyword,distribute.id.zookeeperHost=... | 阻塞 | application.properties + private |
| Redis(Sentinel) | spring.redis.sentinel.master=master01,nodes=redis0-redis-ha.yidun-infra.svc:26379 | 弱(限频/缓存) | application-private.properties |
| Kafka | kafka.bootstrap.servers=kafka-yidun-0/1/2...:9092 | 弱 | application-private.properties |
| Apollo | — | 全模块不使用 Apollo | 无 |
六、启动参数与 Profile
-Dspring.profiles.active:dubbo-provider / http-api 默认dev;dubbo-cache-provider 默认test;容器统一写private- maven profile:本仓库根
pom.xml未自定义 profile,-P private由父/平台 POM 提供 - JAVA_OPTS:Dockerfile 统一
-Xmx512m -Xms512m -XX:+UseG1GC -Dspring.profiles.active=private(dubbo-check-provider 例外:-Xmx1024m -Xms1024m);k8s 覆写全部为-Xmx1024m -Xms1024m -XX:+UseG1GC ... - base image 与镜像仓库:
private-registry.nis.netease.com/library/base-image:jdk17;build.shDOCKER_REPOSITORY=private-registry.nis.netease.com/yidun;k8s 拉取private-registry.yidun.internal/yidun/antispam-keyword-*:1.0.0.private build.sh关键参数(各单元):MODEL_NAME分别为facade/dubbo-provider、facade/dubbo-check-provider、facade/dubbo-provider(cache-provider 误写成 dubbo-provider)、facade/http-api、scheduler;BUILD_ENV=private;DOCKER_IMAGE_TAG=base-250409(dubbo-provider 默认base-250409,dubbo-cache-provider 默认1.0.0.private);docker buildx build --platform linux/amd64,linux/arm64 --push
七、启动期踩坑
- dubbo-cache-provider 实际产不出独立镜像:
facade/dubbo-cache-provider/build.sh里MODEL_NAME=facade/dubbo-provider、DOCKER_IMAGE_NAME=antispam-keyword-dubbo-provider(与 dubbo-provider 完全同名,会互相覆盖),且其 DockerfileCOPY ./target/antispam-keyword-facade-dubbo-provider.jar,而该模块finalName=antispam-keyword-facade-dubbo-cache-provider-${version}-application—— 文件名对不上,构建会失败或错用他人产物。 - ElasticJob namespace 三方共用:
elastic.job.zk.namespace=antispam-keyword-scheduler-jobs-private同时出现在 check-provider、cache-provider、scheduler 的 private 配置里,虽任务名不同,但 namespace 共用意味着任务元数据相互可见,改一处易波及全局。 business.client.autoStart=false:check-provider/cache-provider 显式关闭业务客户端自动启动,若误开会在启动期额外拉取配置并延长启动时间。- 多处
dubbo.protocol.register=false:检测侧与缓存侧 Provider 不注册到注册中心(只做服务端),排查“注册中心查不到某个 Provider”属预期行为,不是故障。 - dubbo-cache-provider base 默认 profile 是
test:与同模块其他单元(dev)不一致,本地直跑会连测试环境。 - check-provider 启动期预热阻塞:
ContextRefreshedEventListener在容器刷新后同步构建词表/AC 自动机,词量大时启动时间长,且期间不对外服务。 - 无 Apollo:全模块零 Apollo 依赖,配置只能改 properties 并重新发布镜像,不要按其他模块习惯去 Apollo 找开关。
八、跨模块前置
- 配置源依赖
antispam-business:所有单元经business.clientZK/netease-antispam/online/config读取 Product/Target/Label/Scene 缓存,属启动期配置读取(阻塞级),建议antispam-business(dubbo-provider/http-api)先运行。 - 下游调用方(
antispam-business-*、antispam-rule、antispam-cms)是运行期消费者,不是本模块启动前置。 - 除此之外无硬依赖。