microservices-demo Ad Service 深度解析:Java/gRPC 广告服务的构建、镜像化与部署实践
【免费下载链接】microservices-demoSample cloud-first application with 10 microservices showcasing Kubernetes, Istio, and gRPC.项目地址: https://gitcode.com/GitHub_Trending/mi/microservices-demo
导读
Ad Service(广告服务)是 GoogleCloudPlatform/microservices-demo(本项目仓库根目录即该示例项目)中负责广告分发的核心微服务:它根据前端页面传入的上下文关键词(context keys)返回匹配的广告,若没有提供上下文则返回随机广告。本文以 src/adservice/README.md 为主线,结合 AdService.java、build.gradle、Dockerfile 与 kubernetes-manifests/adservice.yaml 等仓库资源,完整讲解该服务的功能逻辑、本地构建、Docker 镜像制作以及 Kubernetes 部署验证,读完即可独立完成该服务的编译、运行与调试。
一、服务定位与核心功能
根据 README.md 的定义:Ad Service 基于上下文关键词(context keys)提供广告;如果请求中没有提供任何上下文关键词,则返回随机广告。
这一行为在 protos/demo.proto 中定义了对应的 gRPC 契约:
service AdService { rpc GetAds(AdRequest) returns (AdResponse) {} } message AdRequest { // List of important key words from the current page describing the context. repeated string context_keys = 1; } message AdResponse { repeated Ad ads = 1; } message Ad { // url to redirect to when an ad is clicked. string redirect_url = 1; // short advertisement text to display. string text = 2; }接口语义非常清晰:
GetAds接收一个AdRequest,其中context_keys是描述当前页面语义的关键词列表;- 返回
AdResponse,内含若干条Ad; - 每条
Ad由redirect_url(点击广告后跳转的商品页)和text(展示给用户的广告文案)组成。
从 AdService.java 的AdServiceImpl.getAds实现可以看出完整的决策流程:
- 若
req.getContextKeysCount() > 0,则遍历每个上下文关键词,调用getAdsByCategory(category)从内存中的广告映射表取对应分类的广告; - 若请求没有上下文关键词,直接走
getRandomAds()返回随机广告; - 兜底逻辑:即使提供了关键词但该分类下没有广告(
allAds.isEmpty()),也会回退为随机广告,保证调用方始终能拿到可展示的广告内容。
这种"上下文优先、随机兜底"的设计,保证了在商品目录缺失、分类命名变化等场景下,广告位永远不会空置,是演示微服务中典型的容错式降级实现。
二、广告数据模型:分类到商品的映射
广告素材并非从外部数据库读取,而是在服务启动时以静态方式构建在内存中。AdService.java 中的createAdsMap()使用 Guava 的ImmutableListMultimap将"分类 → 广告列表"固化:
| 上下文分类 | 广告商品 | redirect_url | 广告文案 |
|---|---|---|---|
| clothing(服饰) | Tank top(背心) | /product/66VCHSJNUP | Tank top for sale. 20% off. |
| accessories(配饰) | Watch(手表) | /product/1YMWWN1N4O | Watch for sale. Buy one, get second kit for free |
| footwear(鞋履) | Loafers(乐福鞋) | /product/L9ECAV7KIM | Loafers for sale. Buy one, get second one for free |
| hair(美发) | Hairdryer(吹风机) | /product/2ZYFJ3GM2N | Hairdryer for sale. 50% off. |
| decor(家居装饰) | Candle holder(烛台) | /product/0PUK6V6EV0 | Candle holder for sale. 30% off. |
| kitchen(厨房) | Bamboo glass jar(竹盖玻璃罐) | /product/9SIQT8TOJO | Bamboo glass jar for sale. 10% off. |
| kitchen(厨房) | Mug(马克杯) | /product/6E92ZMYYFZ | Mug for sale. Buy two, get third one for free |
其中redirect_url指向的是 src/frontend 渲染的商品详情页路径,商品 ID(如66VCHSJNUP)与 productcatalogservice 的 products.json 中的商品 ID 一一对应,构成了广告点击到商品落地页的完整链路。
随机广告逻辑见 getRandomAds():单次最多返回MAX_ADS_TO_SERVE = 2条广告,从全量广告池中通过java.util.Random随机抽取,这也是 README 中"返回随机广告"一句的实现细节所在。
三、本地构建:基于 Gradle Wrapper
3.1 一键构建./gradlew installDist
README 明确指出:Ad Service 使用 gradlew(Gradle Wrapper)进行 compile / install / distribute,Wrapper 已随源码一起分发,因此本地无需预装 Gradle。执行:
./gradlew installDist该命令执行后会在src/adservice/build/install/hipstershop/bin/下生成可执行脚本AdService(以及配套的AdServiceClient),即为该服务的可运行产物。
从 build.gradle 可以看到支撑该命令的关键配置:
- 项目名由 settings.gradle 定义为
hipstershop,因此分发目录为build/install/hipstershop; application插件配合adService、adServiceClient两个CreateStartScripts任务,分别将主类hipstershop.AdService与hipstershop.AdServiceClient打包为可执行启动脚本(见 build.gradle);- 生成的脚本会装载
build/install/hipstershop/lib下的全部依赖 JAR,直接运行./build/install/hipstershop/bin/AdService即可启动 gRPC 服务,默认监听PORT环境变量,缺省为9555(见 AdService.java)。
构建产物目录结构可预期为:
build/install/hipstershop/ ├── bin/ │ ├── AdService # 服务端启动脚本 │ └── AdServiceClient # 测试用客户端脚本 └── lib/ # 依赖 JAR 包集合3.2 Wrapper 版本信息与升级方式
Wrapper 自身由gradle/wrapper/目录下的gradle-wrapper.jar、gradle-wrapper.properties组成。当前仓库使用的 Gradle 发行版定义在 gradle/wrapper/gradle-wrapper.properties:
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists关键点:
distributionUrl指向gradle-8.14.5-bin.zip,首次执行 gradlew 时会自动下载该发行版;networkTimeout=10000(毫秒)控制下载超时,网络较慢的环境可按需调大;- 不需要手动安装 Gradle,这也是 Wrapper 的核心价值——团队成员与 CI 构建环境使用完全一致的构建版本。
如需升级 Gradle 版本,README 给出的官方命令为:
./gradlew wrapper --gradle-version <new-version>例如升级到 8.15 可执行./gradlew wrapper --gradle-version 8.15。该命令会重写gradle-wrapper.properties中的distributionUrl并同步更新gradle-wrapper.jar,之后所有开发者再运行./gradlew就会使用新版本。注意:升级前建议确认 build.gradle 中声明的 Java 21 兼容性(sourceCompatibility/targetCompatibility均为JavaVersion.VERSION_21)与所用 Gradle 插件版本(protobuf 0.10.0、google-java-format 0.9)是否被目标 Gradle 版本支持。
3.3 构建依赖与编译要点
build.gradle 中值得注意的实现细节:
- gRPC / Protobuf 依赖:
io.grpc:grpc-protobuf、grpc-stub、grpc-netty、grpc-services、grpc-census均锁定版本1.83.1,protoc与protobuf-java锁定4.35.1,protoc-gen-grpc-java与 grpc 同版本,保证生成代码与运行时完全兼容; - protobuf 插件:通过
com.google.protobuf插件在编译期从 src/main/proto/demo.proto 生成 Java 与 gRPC 桩代码,生成目录build/generated/source/proto/main/java/hipstershop与.../grpc/hipstershop被显式加入sourceSets(见 build.gradle),所以仓库不提交生成的桩代码,而是构建时动态生成; downloadRepos任务:将compileClasspath拷贝到build/output/lib,配合-Pspeed属性可实现离线编译(implementation fileTree(dir: offlineCompile, ...)),该机制被 Dockerfile 用来分层缓存依赖,显著加快镜像重复构建速度;- proto 同步脚本:genproto.sh 会把仓库根目录的 protos/demo.proto 复制到
src/main/proto,确保 Ad Service 编译使用的 proto 与整个项目保持单源一致——Docker 构建前会执行该脚本。
四、构建 Docker 镜像
4.1 基本命令
README 给出的镜像构建方式是在src/adservice/目录下执行:
docker build ./最终镜像默认 tag 为空,实际使用时建议显式打 tag,例如:
docker build -t adservice:latest ./注意docker build的构建上下文为src/adservice/目录本身,因此 Dockerfile 中引用的相对路径(如build.gradle、gradlew、gradle/)均相对于该目录。
4.2 Dockerfile 多阶段构建解析
Dockerfile 采用标准的多阶段构建(multi-stage build),兼顾构建速度与运行镜像体积:
阶段一:builder(构建镜像)
FROM --platform=$BUILDPLATFORM eclipse-temurin:24.0.2_12-jdk-noble@sha256:... AS builder WORKDIR /app COPY ["build.gradle", "gradlew", "./"] COPY gradle gradle RUN chmod +x gradlew RUN ./gradlew downloadRepos COPY . . RUN chmod +x gradlew RUN ./gradlew installDist- 先只复制
build.gradle、gradlew与gradle/,执行./gradlew downloadRepos提前缓存依赖层——只要build.gradle未变化,该层即可复用 Docker 缓存; - 再复制全部源码并执行
./gradlew installDist产出可分发的应用目录; - 基础镜像固定到 digest(
@sha256:...)级别,保证可复现构建。
阶段二:运行镜像
FROM eclipse-temurin:25.0.3_9-jre-alpine@sha256:... WORKDIR /app COPY --from=builder /app . EXPOSE 9555 ENTRYPOINT ["/app/build/install/hipstershop/bin/AdService"]- 仅使用精简的 JRE(Alpine)镜像,体积远小于 JDK;
EXPOSE 9555声明服务端口,与 AdService.java 读取的默认端口一致;ENTRYPOINT直接执行上一步installDist生成的AdService启动脚本。
此外,Dockerfile 中被注释掉的 Stackdriver Profiler Java agent 下载步骤(# @TODO: ...)说明该项目曾计划集成 Java Profiler,当前仓库中该能力处于 TODO 状态,不作为现网功能宣传。
五、在 Kubernetes 中部署:manifest 视角
构建出镜像后,即可按仓库提供的 kubernetes-manifests/adservice.yaml 部署到集群。该文件一次声明了三种资源:
Deployment(deployment 关键字段)
- 容器端口
9555,并通过环境变量PORT=9555显式传给服务,与代码默认值保持一致; - 探针使用 gRPC 健康检查:
readinessProbe与livenessProbe均为initialDelaySeconds: 20、periodSeconds: 15的 gRPC 探测,对应 AdService.java 中注册的HealthStatusManager健康服务——服务启动并注册为SERVING后,探针才能通过; - 资源配额:requests
cpu: 200m / memory: 180Mi,limitscpu: 300m / memory: 300Mi; - 安全加固:
runAsNonRoot: true、runAsUser/Group: 1000、readOnlyRootFilesystem: true、drop ALL capabilities,禁止提权,体现生产级最小权限原则。
Service:类型ClusterIP,端口9555,仅集群内部可访问,广告服务由前端(frontend)通过 gRPC 调用,无需对外暴露。
ServiceAccount:名为adservice,供 Deployment 使用,配合网络策略或 RBAC 实现最小权限。
此外,仓库的 helm-chart/templates/adservice.yaml、kustomize/base/adservice.yaml 与 kustomize/components/network-policies/network-policy-adservice.yaml 还提供了 Helm、Kustomize 与网络策略等不同交付方式下的等价编排,可在实际部署时按需选择。
六、本地验证:用 AdServiceClient 请求广告
仓库同时提供了测试用 gRPC 客户端 AdServiceClient.java,便于在本地验证服务行为。
先启动服务端:
./gradlew installDist ./build/install/hipstershop/bin/AdService启动日志会输出Ad Service started, listening on 9555。随后另开终端运行客户端:
./build/install/hipstershop/bin/AdServiceClient客户端默认行为(见 AdServiceClient.java 的main):
- 第 1 个参数:上下文关键词,默认
camera; - 第 2 个参数:服务端 host,默认
localhost; - 第 3 个参数:端口,默认
9555。
因此以下命令等价于默认调用:
./build/install/hipstershop/bin/AdServiceClient camera localhost 9555客户端会构建AdRequest并调用阻塞式 stubgetAds,然后把每条广告的text打印到日志(Ads: ...)。由于广告映射表中没有camera分类,此时可以直观验证 README 描述的兜底逻辑:服务端因getAdsByCategory("camera")返回空集合而回退到getRandomAds(),客户端仍然能收到 2 条随机广告。若换成clothing、kitchen等已存在的分类,则会返回该分类下的定向广告。
七、总结
Ad Service 虽然业务简单,却完整展现了本项目微服务的典型工程模式:
- 契约先行:gRPC/Protobuf 契约统一维护在根目录 protos/demo.proto,通过 genproto.sh 与 Gradle protobuf 插件在各服务内生成代码;
- 零依赖构建:Gradle Wrapper 固化构建版本,
./gradlew installDist一键产出可执行脚本;升级只需./gradlew wrapper --gradle-version <new-version>; - 镜像可复现:多阶段 Docker 构建 + digest 固定基础镜像 +
downloadRepos分层缓存,既快又稳; - 生产可部署:Kubernetes manifest 提供 gRPC 健康探针、资源配额与最小权限安全上下文;
- 上下文优先、随机兜底:即使关键词无法命中广告分类,也不会让广告位空置。
对照 README.md 的三组核心命令——./gradlew installDist、./gradlew wrapper --gradle-version <new-version>、docker build ./——读者现在可以顺藤摸瓜,从源码、构建脚本到部署清单完整复现该服务的全生命周期。
【免费下载链接】microservices-demoSample cloud-first application with 10 microservices showcasing Kubernetes, Istio, and gRPC.项目地址: https://gitcode.com/GitHub_Trending/mi/microservices-demo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考