案例13:推荐系统后端服务
文章目录
生产级要求
- 功能:
- 用户画像构建(实时更新、多维度标签)
- 推荐算法集成(协同过滤、内容推荐)
- 推荐结果缓存与实时更新
- 推荐效果监控与AB测试
- 多场景推荐(首页推荐、相关推荐)
- 技术栈:
- 核心框架:Go、Gin(API服务)
- 数据存储:Redis(用户画像缓存)、MongoDB(行为日志)
- 算法集成:Python(机器学习模型)、gRPC(跨语言通信)
- 消息队列:Kafka(实时行为日志)
- 监控:Prometheus(推荐效果指标)、Grafana
- 实现细节:
- 用户画像:
- 实时行为日志通过Kafka更新用户标签
- Redis存储用户画像,支持TTL自动过期
- 推荐算法:
- 离线训练模型通过gRPC提供服务
- 实时推荐结合用户画像与上下文信息
- 缓存优化:
- Redis缓存热门推荐结果,减少算法调用
- 本地缓存(LRU)提升高频请求响应速度
- 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;
}
生产级实践总结
-
性能优化:
- 推荐结果缓存命中率>90%,平均响应时间<50ms
- Kafka消费者批量处理行为日志,吞吐量>10k/s
-
算法扩展性:
- 支持多算法插件化集成(通过gRPC接口)
- 实时推荐与离线推荐结果融合
-
监控体系:
- Prometheus监控指标:
- 推荐请求量(
recommend_requests_total
) - 缓存命中率(
cache_hit_rate
)
- 推荐请求量(
- Grafana实时展示推荐效果
- Prometheus监控指标:
-
AB测试:
- 支持多算法并行实验
- 实时统计点击率、转化率差异
-
数据可靠性:
- MongoDB分片集群存储行为日志
- Redis持久化用户画像数据
以上为推荐系统后端服务的完整实现。执行以下命令启动完整环境:
docker-compose up -d
go run cmd/api_server/main.go
go run cmd/kafka_consumer/main.go