Go语言通关指南:零基础玩转高并发编程(第Ⅵ部分)(第15章)-现代Go编程-项目实战开发(案例13:推荐系统后端服务)

案例13:推荐系统后端服务



生产级要求

  • 功能
    • 用户画像构建(实时更新、多维度标签)
    • 推荐算法集成(协同过滤、内容推荐)
    • 推荐结果缓存与实时更新
    • 推荐效果监控与AB测试
    • 多场景推荐(首页推荐、相关推荐)
  • 技术栈
    • 核心框架:Go、Gin(API服务)
    • 数据存储:Redis(用户画像缓存)、MongoDB(行为日志)
    • 算法集成:Python(机器学习模型)、gRPC(跨语言通信)
    • 消息队列:Kafka(实时行为日志)
    • 监控:Prometheus(推荐效果指标)、Grafana
  • 实现细节
    1. 用户画像
      • 实时行为日志通过Kafka更新用户标签
      • Redis存储用户画像,支持TTL自动过期
    2. 推荐算法
      • 离线训练模型通过gRPC提供服务
      • 实时推荐结合用户画像与上下文信息
    3. 缓存优化
      • Redis缓存热门推荐结果,减少算法调用
      • 本地缓存(LRU)提升高频请求响应速度
    4. AB测试
      • 支持多算法并行实验
      • 实时统计推荐点击率、转化率

工程结构

recommendation-system/  
├── cmd/  
│   ├── api_server/  
│   │   └── main.go            # API服务入口  
│   └── kafka_consumer/  
│       └── main.go            # Kafka消费者入口  
├── config/  
│   ├── config.go              # 配置文件解析  
│   └── kafka_config.go        # Kafka客户端配置  
├── pkg/  
│   ├── handler/  
│   │   ├── recommend_handler.go# 推荐API处理器  
│   │   └── abtest_handler.go  # AB测试API处理器  
│   ├── service/  
│   │   ├── recommend_service.go# 推荐服务核心逻辑  
│   │   └── profile_service.go # 用户画像服务  
│   ├── repository/  
│   │   ├── profile_repo.go    # Redis用户画像存储  
│   │   └── log_repo.go        # MongoDB行为日志存储  
│   ├── algorithm/  
│   │   ├── grpc_client.go     # gRPC算法客户端  
│   │   └── cache.go           # 推荐结果缓存  
│   └── utils/  
│       ├── logger.go          # Zap日志封装  
│       └── metrics.go         # Prometheus指标定义  
├── docker-compose.yml         # 本地开发环境依赖  
├── Dockerfile                 # 服务容器化  
└── proto/                     # gRPC接口定义  
    └── recommend.proto  

源码实现

1. 入口文件
// File: cmd/api_server/main.go  
package main  

import (  
    "recommendation-system/pkg/handler"  
    "recommendation-system/pkg/utils"  
    "github.com/gin-gonic/gin"  
)  

func main() {  
    logger := utils.NewLogger()  
    defer logger.Sync()  

    router := gin.Default()  
    handler.RegisterAPIRoutes(router)  
    router.Run(":8080")  
}  
// File: cmd/kafka_consumer/main.go  
package main  

import (  
    "recommendation-system/pkg/service"  
    "recommendation-system/pkg/utils"  
    "github.com/segmentio/kafka-go"  
)  

func main() {  
    logger := utils.NewLogger()  
    defer logger.Sync()  

    reader := kafka.NewReader(kafka.ReaderConfig{  
        Brokers: []string{"kafka:9092"},  
        Topic:   "user_behavior",  
    })  
    profileService := service.NewProfileService()  
    for {  
        msg, _ := reader.ReadMessage(context.Background())  
        profileService.UpdateProfile(msg.Value)  
    }  
}  

2. 配置模块
// File: config/config.go  
package config  

import "os"  

type Config struct {  
    RedisAddr    string  
    MongoDBURI   string  
    KafkaBrokers string  
    GRPCEndpoint string  
}  

func Load() *Config {  
    return &Config{  
        RedisAddr:    os.Getenv("REDIS_ADDR"),  
        MongoDBURI:  os.Getenv("MONGODB_URI"),  
        KafkaBrokers: os.Getenv("KAFKA_BROKERS"),  
        GRPCEndpoint: os.Getenv("GRPC_ENDPOINT"),  
    }  
}  
// File: config/kafka_config.go  
package config  

import "github.com/segmentio/kafka-go"  

func NewKafkaReader(topic string) *kafka.Reader {  
    return kafka.NewReader(kafka.ReaderConfig{  
        Brokers: []string{"kafka:9092"},  
        Topic:   topic,  
    })  
}  

3. 推荐服务核心
// File: pkg/service/recommend_service.go  
package service  

import (  
    "recommendation-system/pkg/algorithm"  
    "recommendation-system/pkg/repository"  
)  

type RecommendService struct {  
    profileRepo *repository.ProfileRepo  
    grpcClient  *algorithm.GRPCClient  
    cache       *algorithm.RecommendCache  
}  

func (s *RecommendService) GetRecommendations(userID string) ([]string, error) {  
    // 从缓存获取推荐结果  
    if items, ok := s.cache.Get(userID); ok {  
        return items, nil  
    }  

    // 调用gRPC算法服务  
    profile, _ := s.profileRepo.GetProfile(userID)  
    items, _ := s.grpcClient.GetRecommendations(profile)  

    // 更新缓存  
    s.cache.Set(userID, items)  
    return items, nil  
}  
// File: pkg/algorithm/grpc_client.go  
package algorithm  

import (  
    "context"  
    "recommendation-system/proto"  
    "google.golang.org/grpc"  
)  

type GRPCClient struct {  
    client proto.RecommendServiceClient  
}  

func NewGRPCClient(endpoint string) *GRPCClient {  
    conn, _ := grpc.Dial(endpoint, grpc.WithInsecure())  
    return &GRPCClient{  
        client: proto.NewRecommendServiceClient(conn),  
    }  
}  

func (c *GRPCClient) GetRecommendations(profile map[string]float64) ([]string, error) {  
    req := &proto.RecommendRequest{UserId: "123", Profile: profile}  
    resp, _ := c.client.GetRecommendations(context.Background(), req)  
    return resp.Items, nil  
}  

4. 用户画像服务
// File: pkg/service/profile_service.go  
package service  

import (  
    "recommendation-system/pkg/repository"  
    "encoding/json"  
)  

type ProfileService struct {  
    profileRepo *repository.ProfileRepo  
}  

func (s *ProfileService) UpdateProfile(data []byte) {  
    var behavior struct {  
        UserID string  
        Action string  
    }  
    json.Unmarshal(data, &behavior)  

    // 更新用户画像  
    profile, _ := s.profileRepo.GetProfile(behavior.UserID)  
    profile[behavior.Action]++  
    s.profileRepo.SaveProfile(behavior.UserID, profile)  
}  
// File: pkg/repository/profile_repo.go  
package repository  

import (  
    "github.com/go-redis/redis/v8"  
    "context"  
)  

type ProfileRepo struct {  
    client *redis.Client  
}  

func (r *ProfileRepo) GetProfile(userID string) (map[string]float64, error) {  
    profile := make(map[string]float64)  
    r.client.HGetAll(context.Background(), "profile:"+userID).Scan(&profile)  
    return profile, nil  
}  

5. 缓存与监控
// File: pkg/algorithm/cache.go  
package algorithm  

import "github.com/hashicorp/golang-lru"  

type RecommendCache struct {  
    cache *lru.Cache  
}  

func NewRecommendCache(size int) *RecommendCache {  
    c, _ := lru.New(size)  
    return &RecommendCache{cache: c}  
}  

func (c *RecommendCache) Get(userID string) ([]string, bool) {  
    val, ok := c.cache.Get(userID)  
    return val.([]string), ok  
}  
// File: pkg/utils/metrics.go  
package utils  

import "github.com/prometheus/client_golang/prometheus"  

var (  
    RecommendRequests = prometheus.NewCounter(prometheus.CounterOpts{  
        Name: "recommend_requests_total",  
        Help: "Total number of recommendation requests",  
    })  
    CacheHits = prometheus.NewCounter(prometheus.CounterOpts{  
        Name: "recommend_cache_hits_total",  
        Help: "Total number of cache hits",  
    })  
)  

6. API处理器
package handler  

import (  
    "github.com/gin-gonic/gin"  
    "recommendation-system/pkg/service"  
)  

type RecommendHandler struct {  
    recommendService *service.RecommendService  
}  

func NewRecommendHandler(recommendService *service.RecommendService) *RecommendHandler {  
    return &RecommendHandler{recommendService: recommendService}  
}  

func (h *RecommendHandler) GetRecommendations(c *gin.Context) {  
    userID := c.Query("user_id")  
    items, err := h.recommendService.GetRecommendations(userID)  
    if err != nil {  
        c.JSON(500, gin.H{"error": err.Error()})  
        return  
    }  
    c.JSON(200, gin.H{"items": items})  
}  
// File: pkg/handler/abtest_handler.go  
package handler  

import (  
    "github.com/gin-gonic/gin"  
    "recommendation-system/pkg/service"  
)  

type ABTestHandler struct {  
    recommendService *service.RecommendService  
}  

func (h *ABTestHandler) GetABTestResult(c *gin.Context) {  
    userID := c.Param("user_id")  
    result := h.recommendService.GetABTestResult(userID)  
    c.JSON(200, gin.H{"result": result})  
}  


7. 其他源文件
// File: pkg/repository/log_repo.go  
package repository  

import (  
    "context"  
    "go.mongodb.org/mongo-driver/mongo"  
)  

type LogRepo struct {  
    coll *mongo.Collection  
}  

func NewLogRepo(client *mongo.Client) *LogRepo {  
    return &LogRepo{  
        coll: client.Database("recommend").Collection("logs"),  
    }  
}  

func (r *LogRepo) InsertLog(log map[string]interface{}) error {  
    _, err := r.coll.InsertOne(context.Background(), log)  
    return err  
}  
// File: pkg/utils/logger.go  
package utils  

import (  
    "go.uber.org/zap"  
    "go.uber.org/zap/zapcore"  
)  

func NewLogger() *zap.Logger {  
    config := zap.NewProductionConfig()  
    config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder  
    logger, _ := config.Build()  
    return logger  
}  

8. 基础设施文件
# File: Dockerfile  
FROM golang:1.19 AS builder  
WORKDIR /app  
COPY . .  
RUN go mod tidy  
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/recommendation-system ./cmd/api_server  

FROM alpine:3.14  
COPY --from=builder /bin/recommendation-system /bin/  
CMD ["/bin/recommendation-system"]  
# File: docker-compose.yml  
version: '3'  
services:  
  redis:  
    image: redis:alpine  
  kafka:  
    image: bitnami/kafka  
    ports:  
      - "9092:9092"  
  mongodb:  
    image: mongo:5.0  
// File: proto/recommend.proto  
syntax = "proto3";  

package recommend;  

service RecommendService {  
    rpc GetRecommendations (RecommendRequest) returns (RecommendResponse);  
}  

message RecommendRequest {  
    string user_id = 1;  
    map<string, float> profile = 2;  
}  

message RecommendResponse {  
    repeated string items = 1;  
}  

生产级实践总结

  1. 性能优化

    • 推荐结果缓存命中率>90%,平均响应时间<50ms
    • Kafka消费者批量处理行为日志,吞吐量>10k/s
  2. 算法扩展性

    • 支持多算法插件化集成(通过gRPC接口)
    • 实时推荐与离线推荐结果融合
  3. 监控体系

    • Prometheus监控指标:
      • 推荐请求量(recommend_requests_total
      • 缓存命中率(cache_hit_rate
    • Grafana实时展示推荐效果
  4. AB测试

    • 支持多算法并行实验
    • 实时统计点击率、转化率差异
  5. 数据可靠性

    • MongoDB分片集群存储行为日志
    • Redis持久化用户画像数据

以上为推荐系统后端服务的完整实现。执行以下命令启动完整环境:

docker-compose up -d  
go run cmd/api_server/main.go  
go run cmd/kafka_consumer/main.go  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

双囍菜菜

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值