microservices-demo Ad Service 深度解析:Java/gRPC 广告服务的构建、镜像化与部署实践
2026/9/13 21:19:16 网站建设 项目流程

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
  • 每条Adredirect_url(点击广告后跳转的商品页)和text(展示给用户的广告文案)组成。

从 AdService.java 的AdServiceImpl.getAds实现可以看出完整的决策流程:

  1. req.getContextKeysCount() > 0,则遍历每个上下文关键词,调用getAdsByCategory(category)从内存中的广告映射表取对应分类的广告;
  2. 若请求没有上下文关键词,直接走getRandomAds()返回随机广告;
  3. 兜底逻辑:即使提供了关键词但该分类下没有广告(allAds.isEmpty()),也会回退为随机广告,保证调用方始终能拿到可展示的广告内容。

这种"上下文优先、随机兜底"的设计,保证了在商品目录缺失、分类命名变化等场景下,广告位永远不会空置,是演示微服务中典型的容错式降级实现。

二、广告数据模型:分类到商品的映射

广告素材并非从外部数据库读取,而是在服务启动时以静态方式构建在内存中。AdService.java 中的createAdsMap()使用 Guava 的ImmutableListMultimap将"分类 → 广告列表"固化:

上下文分类广告商品redirect_url广告文案
clothing(服饰)Tank top(背心)/product/66VCHSJNUPTank top for sale. 20% off.
accessories(配饰)Watch(手表)/product/1YMWWN1N4OWatch for sale. Buy one, get second kit for free
footwear(鞋履)Loafers(乐福鞋)/product/L9ECAV7KIMLoafers for sale. Buy one, get second one for free
hair(美发)Hairdryer(吹风机)/product/2ZYFJ3GM2NHairdryer for sale. 50% off.
decor(家居装饰)Candle holder(烛台)/product/0PUK6V6EV0Candle holder for sale. 30% off.
kitchen(厨房)Bamboo glass jar(竹盖玻璃罐)/product/9SIQT8TOJOBamboo glass jar for sale. 10% off.
kitchen(厨房)Mug(马克杯)/product/6E92ZMYYFZMug 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插件配合adServiceadServiceClient两个CreateStartScripts任务,分别将主类hipstershop.AdServicehipstershop.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.jargradle-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-protobufgrpc-stubgrpc-nettygrpc-servicesgrpc-census均锁定版本1.83.1protocprotobuf-java锁定4.35.1protoc-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.gradlegradlewgradle/)均相对于该目录。

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.gradlegradlewgradle/,执行./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 健康检查:readinessProbelivenessProbe均为initialDelaySeconds: 20periodSeconds: 15的 gRPC 探测,对应 AdService.java 中注册的HealthStatusManager健康服务——服务启动并注册为SERVING后,探针才能通过;
  • 资源配额:requestscpu: 200m / memory: 180Mi,limitscpu: 300m / memory: 300Mi
  • 安全加固:runAsNonRoot: truerunAsUser/Group: 1000readOnlyRootFilesystem: 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 条随机广告。若换成clothingkitchen等已存在的分类,则会返回该分类下的定向广告。

七、总结

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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询