go语言项目--实例化(图书管理)--005

v5: 图书微服务 --- 分布式架构

一、版本概述

v5 将 v4 的单体应用拆分为两个独立微服务(book-service 和 borrow-service),通过 gRPC 通信。引入 MySQL 替代内存存储,Redis 实现缓存和分布式锁。

相比 v4 的核心变化

  • 微服务拆分:book-service(:8081/:9081) + borrow-service(:8082)
  • MySQL 持久化:GORM ORM,AutoMigrate 自动建表
  • Redis 缓存:Cache-Aside 模式,5分钟过期
  • 分布式锁:Redis SetNX 实现跨实例互斥
  • gRPC 通信:borrow-service 通过 gRPC 调用 book-service
  • Protobuf 定义:独立 proto/book.proto 定义服务契约
  • Docker 支持:Dockerfile + docker-compose.yml

项目结构

复制代码
v5/
├── book-service/
│   ├── main.go              # HTTP(:8081) + gRPC(:9081)
│   ├── model/book.go        # GORM Book 模型
│   ├── repository/
│   │   ├── db.go            # MySQL 初始化
│   │   └── book_repo.go     # GORM CRUD + 行锁
│   ├── cache/redis.go       # Redis 缓存 + 分布式锁
│   ├── service/book_service.go  # 缓存+锁+业务
│   ├── handler/
│   │   ├── http_handler.go  # HTTP API
│   │   └── grpc_handler.go  # gRPC 服务端
│   └── Dockerfile
├── borrow-service/
│   ├── main.go              # HTTP(:8082) + gRPC Client
│   ├── model/borrow_record.go
│   ├── repository/
│   │   ├── db.go
│   │   └── borrow_repo.go
│   ├── client/grpc_client.go    # gRPC 客户端封装
│   ├── service/borrow_service.go
│   ├── handler/http_handler.go
│   └── Dockerfile
├── proto/
│   ├── book.proto           # Protobuf 服务定义
│   └── book/                # 生成的 Go 代码
└── docker-compose.yml

二、核心代码解读

proto/book.proto --- 服务契约

protobuf 复制代码
service BookService {
  rpc GetBook(GetBookRequest) returns (BookResponse);
  rpc DecreaseStock(DecreaseStockRequest) returns (StockResponse);
  rpc IncreaseStock(IncreaseStockRequest) returns (StockResponse);
}

设计要点

  • 只暴露 borrow-service 需要调用的方法,不暴露 CRUD
  • DecreaseStock/IncreaseStock 原子操作库存,而非先 GetBook 再修改

cache/redis.go --- Cache-Aside + 分布式锁

go 复制代码
// Cache-Aside 读缓存
func GetBookCache(ctx context.Context, id int64) (*model.Book, error) {
	key := fmt.Sprintf("book:%d", id)
	data, err := rdb.Get(ctx, key).Bytes()
	if err != nil {
		return nil, err  // 缓存未命中
	}
	var book model.Book
	json.Unmarshal(data, &book)
	return &book, nil
}

// Cache-Aside 写缓存
func SetBookCache(ctx context.Context, book *model.Book) {
	key := fmt.Sprintf("book:%d", book.ID)
	data, _ := json.Marshal(book)
	rdb.Set(ctx, key, data, 5*time.Minute)
}

// 分布式锁
func AcquireLock(ctx context.Context, key string) (bool, error) {
	lockKey := fmt.Sprintf("lock:%s", key)
	return rdb.SetNX(ctx, lockKey, "1", 5*time.Second).Result()
}

设计要点

  • Cache-Aside:读时先查缓存 → miss 查 DB → 写回缓存;写时更新 DB → 删除缓存
  • 分布式锁:SetNX(不存在才设置)+ 过期时间(防止死锁),比 v4 的 Mutex 支持多实例

repository/book_repo.go --- GORM + 行锁

go 复制代码
func (r *BookRepository) DecreaseStock(bookID int64) (int, error) {
	tx := r.db.Begin()
	var book model.Book
	// SELECT ... FOR UPDATE 行锁
	if err := tx.Set("gorm:query_option", "FOR UPDATE").First(&book, bookID).Error; err != nil {
		tx.Rollback()
		return 0, ErrBookNotFound
	}
	if book.Stock <= 0 {
		tx.Rollback()
		return 0, ErrStockNotEnough
	}
	book.Stock -= 1
	tx.Save(&book)
	tx.Commit()
	return book.Stock, nil
}

设计要点

  • 行锁 FOR UPDATE:在事务内锁定该行,其他事务等待,防止数据库层面超卖
  • 事务:Begin → 操作 → Commit/Rollback,保证原子性
  • 双重保护:分布式锁(应用层)+ 行锁(数据库层)

borrow-service/client/grpc_client.go --- gRPC 调用

go 复制代码
type BookServiceClient struct {
	conn *grpc.ClientConn
	client pb.BookServiceClient
}

func (c *BookServiceClient) DecreaseStock(ctx context.Context, bookID int64) (int, error) {
	resp, err := c.client.DecreaseStock(ctx, &pb.DecreaseStockRequest{BookId: bookID})
	if err != nil {
		return 0, err
	}
	return int(resp.Available), nil
}

borrow_service.go --- 跨服务事务

go 复制代码
func (s *borrowService) BorrowBook(ctx context.Context, ...) (*model.BorrowRecord, error) {
	// 1. 通过 gRPC 调用 book-service 减库存
	if _, err := s.grpcClient.DecreaseStock(ctx, bookID); err != nil {
		return nil, ErrBookNotAvailable
	}

	// 2. 本地创建借阅记录
	record, err := s.repo.Create(bookID, bookTitle, userName)
	if err != nil {
		// 3. 创建失败 → 回滚库存(补偿事务)
		s.grpcClient.IncreaseStock(ctx, bookID)
		return nil, err
	}
	return record, nil
}

设计要点

  • Saga 模式:跨服务没有强一致性事务,使用补偿事务保证最终一致性
  • gRPC 同步调用:borrow-service 先调 book-service 扣库存,成功后再创建本地记录

三、与 v4 的对比

特性 v4 v5
架构 单体 微服务拆分
存储 内存+JSON文件 MySQL + Redis
并发控制 进程内 Mutex 分布式锁 + DB行锁
服务通信 函数调用 gRPC
缓存 Redis Cache-Aside
部署 单进程 Docker Compose
跨服务事务 不需要 补偿事务(Saga)

四、设计思想

思想 体现
微服务拆分 按业务领域拆分,独立部署和扩缩容
服务契约 Protobuf 定义接口,强类型、向后兼容
Cache-Aside 缓存与DB分离,先更新DB再删缓存
分布式锁 Redis SetNX 实现跨实例互斥
双重保护 应用层锁 + 数据库行锁,防御性编程
补偿事务 跨服务无强一致性,通过回滚保证最终一致

五、为什么需要 v6?

v5 实现了微服务架构,但仍存在不足:

  1. 没有用户体系:借阅没有登录认证,任何人可以借任何书
  2. 没有搜索功能:只能遍历查询,不支持关键词搜索
  3. 没有前端界面:只能通过 curl/API 工具操作
  4. gRPC 通信不完整:borrow-service 的 gRPC 客户端存在 placeholder
  5. 缺少 CORS:前端无法跨域访问 API

v6 的改进方向:新增 user-service + JWT 认证 + Bleve 全文搜索 + 前端页面 + 完善 gRPC 调用。

相关推荐
Aspiresky1 小时前
探索Rust语言之引用
开发语言·后端·rust
天空'之城2 小时前
Linux 系统编程 10:线程同步
linux·开发语言·系统编程·线程同步
Vect__2 小时前
Go 数据结构 slice 深度剖析
开发语言·数据结构·golang
想你依然心痛2 小时前
AtomCode在Python数据科学项目中的使用体验:从数据分析到可视化
开发语言·python·数据分析
满天星83035772 小时前
【Qt】控件(二) (geometry及与frameGeometry的区别)
开发语言·qt
冰暮流星2 小时前
flask之app.py讲解
后端·python·flask
Esaka_Forever2 小时前
Python 与 JS (V8) 垃圾回收核心区别 + 底层根源分析
开发语言·javascript·jvm
pp起床2 小时前
黑马点评 - 短信验证码登录实现
java·开发语言·tomcat
芒鸽2 小时前
在仓颉语言里造一个没有反射的服务端框架
开发语言·华为·harmonyos