外观
antispam-business — 反垃圾业务配置中心(配置写入/事件分发 + 只读缓存开放接口)
分层:平台基础配置层 | 部署单元:2 个(另有 3 个有启动类但不构建)| 数据库:MySQL/TiDB
antispam| base image:private-registry.yidun.internal/yidun/base-image:jdk17(tagmigu-231016)
一、模块定位与架构
它是易盾内容安全平台的“配置底座”:把产品、业务(Target)、业务配置、检测器、标签、密钥、预上线配置、客户场景、策略集等十几类配置统一管起来,替代早期纯 Zookeeper 存储+推送的重方案,再通过 Dubbo 门面、HTTP 开放接口与客户端 SDK 分发给下游约 20 个检测模块(文本/图片/音视频/直播/解决方案/计费)。
架构分层
- 门面层(协议层):
facade/dubbo-provider(Dubbo 写侧:配置 CRUD/上下线)、facade/http-api(HTTP 读侧:api/business/*全量/增量拉取 +BusinessCacheManager) - 逻辑层:
service/business(*ServiceImpl、component/event/OperateEventDispatcher、component/cache/BusinessCache*、AsyncComponent、QpsExcludeComponent) - 数据层:
service/base(*Manager+*Dao,MyBatis)+domain(Model/BO/Request/Response)+common(OperateTargetType/OperateType/ResultStatus等枚举常量) - 分发层:
client/(core/http/zookeeper/spring-starter,下游 SDK:全量/增量同步、本地缓存、事件订阅) - 事件落地与运维:
storage(Kafka 消费配置变更事件)、console/api(控制台)、scheduler(定时任务)—— 三者本仓库不构建部署
关键数据流(写入)
- 运营/CMS/
netease-antispam→ DubboTargetFacade/TargetConfigFacade/ProductFacade→service/business落库 MySQL - 落库后
applicationEventPublisher.publishEvent(OperateEvent) OperateEventDispatcher扇出到*ZookeeperListener(回写 ZK 节点,兼容存量客户端)与*OpRecordListener(写操作流水business_op_record)http-api的BusinessCacheManager周期扫描business_op_record增量刷新内存缓存- 下游客户端凭
lastOperateTime经api/business/fetch拉增量 / 经 init 接口拉全量
关键数据流(读取)
- 下游客户端/HTTP 调用 →
facade/http-api的api/business/* BusinessCacheManager命中内存businessCacheMap(按OperateTargetType索引)- 未命中或时间戳落后时回查
business_op_record并前插历史队列(businessOpRecordQueryBoResponsesDeque,有historicalDataSize上限与lastOperateTimeProtection保护) - 返回
BusinessDataResponse(含serverSideRefreshInterval/serverSideLastRefreshTime供客户端校准)
配置对象生命周期 状态不用“上/下线”二态表达,而由 OperateType(CREATE/UPDATE/DELETE/ONLINE/OFFLINE)表达动作语义:ONLINE = 生效(刷入 ZK / 使配置生效),OFFLINE = 失效(从 ZK 移除);预上线配置(PRE_*)与正式配置共用实体类型,靠 OperateTargetType 区分。
二、可部署服务清单
| 部署单元目录 | artifactId | 镜像名 | 端口 | 服务类型 | 独立 Dockerfile | 是否进 kubernetes.yml |
|---|---|---|---|---|---|---|
facade/dubbo-provider | antispam-business-facade-dubbo-provider | private-registry.yidun.internal/yidun/antispam-business-dubbo-provider:migu-231016 | Dubbo port=-1(随机,无 Web) | Dubbo Provider | 有 | 是(replicas=2) |
facade/http-api | antispam-business-facade-http-api | private-registry.yidun.internal/yidun/antispam-business-http-api:migu-231016 | 18880(base) / 8080(private) | HTTP(Undertow) | 有 | 否 |
console/api | antispam-business-console-api | 无(不构建) | — | 控制台后端 | 无 | 否 |
scheduler | antispam-business-scheduler | 无(不构建) | — | ElasticJob 定时任务 | 无 | 否 |
storage | antispam-business-storage | 无(不构建) | — | Kafka Consumer | 无 | 否 |
kubernetes.yml中唯一 Deployment 为antispam-business-facade-dubbo-provider(namespaceyidun),且未声明 resources 限额,仅注入JAVA_OPTS、ENABLE_SKYWALKING与 SkyWalking OAP 地址。
三、同模块启动顺序
- 先启
facade/dubbo-provider—— 它是 Dubbo 注册主体(dubbo.application.id=antispam-business_dubbo-provider,注册到 ZK group/yidun/antispam/online-new/yidun-antispam-dubbo),也是配置写入与OperateEvent产生者;下游与 http-api 的增量数据都源于它写的business_op_record。 - 后启
facade/http-api—— 读侧,BusinessCacheManager直读同一个 DB 全量构建缓存,进程上不依赖 dubbo-provider,但逻辑上以 dubbo-provider 作为配置写/推源,且拉起最重。
四、逐服务启动逻辑
facade/dubbo-provider
- 启动类:
com.netease.is.antispam.business.facade.dubbo.DubboApplication(facade/dubbo-provider/src/main/java/com/netease/is/antispam/business/facade/dubbo/DubboApplication.java) @SpringBootApplication:无 exclude@Enable*注解:@EnableAspectJAutoProxy(exposeProxy = true)、@EnableTransactionManagement、@EnableApolloConfig、@EnableBusinessClient、@EnableConfigurationProperties({BusinessDynamicConfig, BusinessCacheProperties})@EnableBusinessClient→@Import(BusinessClientConfiguration):按business.client.options.zookeeper.address条件装配BusinessZookeeperClient(@Bean(destroyMethod="destroy"),构造后init()+start()),并把容器内所有Listener<?>注册进BusinessEventBus;仅当存在business.client.options.remote.address时才建BusinessHttpClientDubboFacadeConfiguration(同包配置类):@ComponentScan({"...business.service", "...business.manager", "...business.proxy", "...business.component"})+@MapperScan("com.netease.is.antispam.business.dao");另@Bean(destroyMethod="close") CuratorFramework curatorFramework()带@ConditionalOnProperty("zookeeper.server.url")(private 下该键被注释 → Bean 不创建)main():仅SpringApplication.run(...),无自定义逻辑- 启动钩子:未发现(无
@PostConstruct/ApplicationListener/ApplicationRunner/SmartLifecycle)
facade/http-api
- 启动类:
com.netease.is.antispam.business.facade.http.HttpApplication(facade/http-api/src/main/java/.../HttpApplication.java) @SpringBootApplication:无 exclude;仅@EnableConfigurationProperties({BusinessCacheProperties});无@EnableApolloConfig、无@EnableBusinessClient(源码里 import 了EnableBusinessClient但注解未使用)HttpFacadeConfiguration(同包配置类):@ComponentScan({"...business.service", "...business.component.converter", "...business.manager"})+@MapperScan("...business.dao");注册@Bean AsyncComponent(异步线程池)、@Bean Validator(HibernateValidator,hibernate.validator.fail_fast=true)、@Bean QpsExcludeComponentmain():run之后执行ServiceStatus.setStatus(ServiceStatus.STARTED)(供健康检查读)- 启动钩子:
BusinessCacheManager.@PostConstruct init()是启动期最重一步——按OperateTargetType.ordinal()排序后逐个businessCache.init(true)全量加载并放入businessCacheMap,随后建ScheduledThreadPoolExecutor(2, "business-cache-loader")(守护线程)周期执行refresh()与validate();@PreDestroy destroy()关线程池并销毁各缓存。DB 不可达会直接导致启动失败 - 其它:
HealthCheckApi整类被注释,故该服务对外健康语义弱
console/api、scheduler、storage(有启动类,不构建)
com.netease.is.antispam.business.console.api.ConsoleApplication:控制台后端骨架,含ConsoleConfiguration/SwaggerConfiguration,通过 Dubbo 调业务服务;无 Dockerfile。com.netease.is.antispam.business.scheduler.SchedulerApplication:ElasticJob 定时任务(配SchedulerConfiguration),无 Dockerfile。com.netease.is.antispam.business.storage.StorageApplication:Kafka 消费者,把配置变更事件落地(StorageConfiguration+ 内置YidunQosApplication),无 Dockerfile。- 三者
@SpringBootApplication均无 exclude;启动钩子未逐一核对(因不构建部署,未纳入启动顺序)。
五、启动前置依赖
| 依赖 | 配置键 / 地址 | 阻塞 or 弱依赖 | 配置文件 |
|---|---|---|---|
| MySQL/TiDB | spring.datasource.url=jdbc:mysql://tidb-cluster0-tidb.tidb.svc:4000/antispam | 阻塞(两个单元都强依赖) | application-private.properties |
| ZK — Dubbo 注册中心 | dubbo.registries.antispam.address=zookeeper://zk-0.zookeeper.yidun-infra:2181?backup=...,group /yidun/antispam/online-new/yidun-antispam-dubbo | 阻塞(dubbo-provider) | application-private.properties |
| ZK — business.client | business.client.options.zookeeper.address=zk-0/1/2.zookeeper.yidun-infra:2181,rootPath /netease-antispam/online/config | 阻塞(dubbo-provider 建 ZK 客户端) | application-private.properties |
| Apollo | apollo.meta=http://yd-apollo-config.nistest.netease.com,app.id=antispam-business_dubbo,apollo.bootstrap.namespaces=application,application.yml | 弱(仅 dubbo-provider,拉不到走本地 cache) | application.properties(dubbo-provider) |
| Kafka Producer | spring.kafka.bootstrap-servers=kafka-yidun-0/1/2...-headless.yidun-infra:9092 | 弱 | application-private.properties |
| Redis(Sentinel) | spring.redis.sentinel.master=master01,spring.redis.sentinel.nodes=redis0-redis-ha.yidun-infra.svc:26379 | 弱 | application-private.properties |
| curator(可选 ZK) | zookeeper.server.url | private 未启用(键被注释) | application-private.properties(注释行) |
六、启动参数与 Profile
-Dspring.profiles.active:base 默认dev;仓库含dev/test/private/online/jiande-online/jiande-pre等;容器统一private(Dockerfile ENV 与 k8s env 均写private)- maven profile:本仓库根
pom.xml未自定义 profile,-P private由父/平台 POM 提供;facade/http-api/build.sh额外叠加arch_x86(BUILD_ENV=private,arch_x86) - JAVA_OPTS:Dockerfile
-Xmx512m -Xms512m -XX:+UseG1GC -Dspring.profiles.active=private;k8s 覆写为-Xmx1024m -Xms1024m -XX:+UseG1GC ...(未配 resources,堆上限即容器实际内存需求) - base image 与镜像仓库:
private-registry.yidun.internal/yidun/base-image:jdk17;产出镜像private-registry.yidun.internal/yidun/antispam-business-{dubbo-provider,http-api}:migu-231016 build.sh关键参数:MODEL_NAME=facade/dubbo-provider|facade/http-api、BUILD_ENV=private、DOCKER_REPOSITORY=private-registry.yidun.internal/yidun、DOCKER_IMAGE_TAG=migu-231016、sudo docker build(非 buildx,仅 amd64),可用-e/-r/-t/-c/-p覆盖;buildAll.sh固定GLOBAL_ENV=private,遍历find . -name build.sh逐个执行(-c false -p false)
七、启动期踩坑
- curator Bean 静默缺失:
DubboFacadeConfiguration.curatorFramework()带@ConditionalOnProperty("zookeeper.server.url"),而application-private.properties中该键被注释 → private 环境不创建 CuratorFramework,依赖它的回写链路缺失时不会报错,只会在使用时报 NPE。 business.client.options.server=true误配:http-api/application.properties写了business.client.options.server=true;若同时补上business.client.options.zookeeper.address,BusinessClientConfiguration会额外创建一个 ZK 客户端(该 Bean 条件为@ConditionalOnMissingBean(BusinessClient.class))。BusinessCacheManager同步全量加载:@PostConstruct内按ordinal()串行init(true)全部缓存,数据量大时启动耗时长;DB 慢/不可达直接启动失败(无降级)。- Apollo namespace 未建则拉空:dubbo-provider
app.id=antispam-business_dubbo、namespacesapplication,application.yml,apollo.cache-dir=./apollo/cache;namespace 不存在时配置为空却不报错。 - 三个单元永不被构建:
buildAll.sh只遍历build.sh,而console/api、scheduler、storage均无build.sh/Dockerfile—— 有启动类 ≠ 会被打包。 - http-api 走 Undertow 且线程数极端:base
server.undertow.worker-threads=2、io-threads=1,private 才覆写worker-threads=600;漏配 private 会表现为严重并发瓶颈。 - dubbo-provider
dubbo.protocol.port=-1:随机端口,消费者必须走 ZK 注册发现,直连不可用。 - 相对路径缓存目录:
apollo.cache-dir=./apollo/cache、http-apibusiness.cache.storage.path=./rocksdb_data(private 设business.cache.load-from-storage=false),容器工作目录必须可写。 - 启动即全量从 DB 加载:private 把
business.cache.load-from-storage=false,意味着每次重启都跳过本地存储快照、直接从 MySQL 全量加载,大库场景重启耗时与 DB 压力成正比。 netease.kafka.producer.enable=true是默认值:application.properties打开生产者,Kafka 不可用时启动不失败但配置变更事件会发送失败并写错误日志,排查“配置改了没生效”时要一并看这里。- Hikari 连接池只有 10:两个单元都是
spring.datasource.hikari.maximum-pool-size=10,而 http-api 启动期要做全量加载、dubbo-provider 要并发写,池偏小易成瓶颈(无报错,只变慢)。 - 多环境共用同一 ZK group:dubbo-provider 注册 group 固定为
/yidun/antispam/online-new/yidun-antispam-dubbo,private 与 online 若共用同一 ZK 集群会互相可见,需确认 ZK 实例隔离。 dubbo.consumer.timeout=30000(30s):dubbo-provider 消费端超时较长,下游不可用时会长时间挂住调用线程。
八、跨模块前置
- 本模块是配置源,启动前不依赖任何其它业务模块 —— 无硬依赖。
- 仅需就绪的基础设施:MySQL/TiDB(阻塞)、Zookeeper(阻塞,dubbo-provider)、Apollo(弱)、Kafka(弱)、Redis(弱)。
- 上游写入方(
antispam-cms、netease-antispam)是运行期调用方,不是本模块的启动前置。 - 反向约束:下游约 20 个模块(
antispam-business-*、netease-antispam、antispam-cms、antispam-bill等)把本模块视为跨模块前置,其 ZK 配置源/netease-antispam/online/config就由本模块写入。 - 与
antispam-business-*系列模块的关系是“被复用配置”而非依赖:本模块启动不等待它们。