Go 语言编程高级:微服务架构实战
随着应用的规模不断扩大,单体应用的局限性逐渐显现,越来越多的开发者开始采用微服务架构来提升系统的可扩展性和灵活性。本部分将带你构建一个简单的微服务架构,学习如何使用 Go 语言开发和管理多个微服务。
1. 微服务架构概述
1.1 什么是微服务架构
微服务架构是一种将应用分解为多个小型、独立服务的架构模式,每个服务都运行在自己的进程中,服务之间通过轻量级协议(如 HTTP/REST、gRPC)进行通信。
1.2 微服务的特点
- 松耦合:服务之间相互独立,降低了服务之间的耦合度。
- 独立部署:每个服务可以独立部署、扩展和管理。
- 技术多样性:每个服务可以采用不同的技术栈,适合各自的需求。
2. 微服务案例:用户服务与订单服务
我们将构建一个简单的微服务架构,包含以下两个服务:
- 用户服务:管理用户信息,提供用户的增删改查 API。
- 订单服务:管理用户订单信息,提供订单相关 API,并通过用户服务获取用户信息。
2.1 技术栈
- 通信协议:使用 HTTP/REST 实现服务之间的通信。
- 数据库:使用 GORM 和 SQLite 存储数据。
- 日志记录:使用 Logrus 记录日志。
- 配置管理:使用 Viper 管理配置。
3. 项目结构
微服务架构中的每个服务都是独立的项目,因此我们会为用户服务和订单服务创建各自的项目结构。
microservices/
├── user-service/
│ ├── cmd/
│ │ └── main.go # 用户服务入口
│ ├── internal/
│ │ ├── database/
│ │ │ └── db.go # 数据库操作
│ │ ├── models/
│ │ │ └── user.go # 用户模型
│ │ └── routes/
│ │ └── user_routes.go # 用户 API 路由
│ ├── config/
│ │ └── config.yaml # 配置文件
│ └── logs/
│ └── app.log # 日志文件
├── order-service/
│ ├── cmd/
│ │ └── main.go # 订单服务入口
│ ├── internal/
│ │ ├── database/
│ │ │ └── db.go # 数据库操作
│ │ ├── models/
│ │ │ └── order.go # 订单模型
│ │ └── routes/
│ │ └── order_routes.go # 订单 API 路由
│ ├── config/
│ │ └── config.yaml # 配置文件
│ └── logs/
│ └── app.log # 日志文件
└── go.mod # Go 模块依赖管理
4. 用户服务实现
4.1 用户服务配置文件
在 user-service/config/config.yaml
中定义用户服务的配置:
app:
port: ":8081"
database:
path: "./users.db"
logging:
file: "./logs/app.log"
4.2 用户模型
在 user-service/internal/models/user.go
中定义用户模型:
package models
type User struct {
ID uint `gorm:"primaryKey"`
Name string `json:"name"`
Email string `json:"email"`
}
4.3 数据库初始化
在 user-service/internal/database/db.go
中初始化数据库:
package database
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"user-service/internal/models"
)
var DB *gorm.DB
func InitDatabase(path string) {
var err error
DB, err = gorm.Open(sqlite.Open(path), &gorm.Config{})
if err != nil {
panic("failed to connect to database")
}
DB.AutoMigrate(&models.User{})
}
4.4 用户 API 路由
在 user-service/internal/routes/user_routes.go
中定义用户的 API 路由:
package routes
import (
"net/http"
"user-service/internal/database"
"user-service/internal/models"
"github.com/gin-gonic/gin"
)
// 获取用户列表
func GetUsers(c *gin.Context) {
var users []models.User
database.DB.Find(&users)
c.JSON(http.StatusOK, users)
}
// 创建用户
func CreateUser(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
database.DB.Create(&user)
c.JSON(http.StatusOK, user)
}
4.5 用户服务入口
在 user-service/cmd/main.go
中启动用户服务:
package main
import (
"user-service/internal/database"
"user-service/internal/routes"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"os"
)
func initConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./config")
if err := viper.ReadInConfig(); err != nil {
panic("failed to read config file")
}
}
func setupLogger() *logrus.Logger {
log := logrus.New()
logFile, err := os.OpenFile(viper.GetString("logging.file"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.Out = logFile
} else {
log.Info("Failed to log to file, using default stderr")
}
return log
}
func main() {
initConfig()
database.InitDatabase(viper.GetString("database.path"))
log := setupLogger()
router := gin.Default()
router.GET("/users", routes.GetUsers)
router.POST("/users", routes.CreateUser)
log.Info("Starting user service at", viper.GetString("app.port"))
router.Run(viper.GetString("app.port"))
}
5. 订单服务实现
5.1 订单服务配置文件
在 order-service/config/config.yaml
中定义订单服务的配置:
app:
port: ":8082"
database:
path: "./orders.db"
logging:
file: "./logs/app.log"
user_service:
url: "http://localhost:8081"
5.2 订单模型
在 order-service/internal/models/order.go
中定义订单模型:
package models
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint `json:"user_id"`
Item string `json:"item"`
Price float64 `json:"price"`
}
5.3 数据库初始化
在 order-service/internal/database/db.go
中初始化数据库:
package database
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"order-service/internal/models"
)
var DB *gorm.DB
func InitDatabase(path string) {
var err error
DB, err = gorm.Open(sqlite.Open(path), &gorm.Config{})
if err != nil {
panic("failed to connect to database")
}
DB.AutoMigrate(&models.Order{})
}
5.4 订单 API 路由
在 order-service/internal/routes/order_routes.go
中定义订单的 API 路由:
package routes
import (
"fmt"
"net/http"
"order-service/internal/database"
"order-service/internal/models"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"net/http"
"io/ioutil"
)
// 获取订单列表
func GetOrders(c *gin.Context) {
var orders []models.Order
database.DB.Find(&orders)
c.JSON(http.StatusOK, orders)
}
// 创建订单
func CreateOrder(c *gin.Context) {
var order models.Order
if err := c.ShouldBindJSON(&order); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 获取用户信息
userID := order.UserID
userServiceURL := fmt.Sprintf("%s/users/%d", viper.GetString("user_service.url"), userID)
resp, err := http.Get(userServiceURL)
if err != nil || resp.StatusCode != http.StatusOK {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user"})
return
}
defer resp.Body.Close()
body, _
:= ioutil.ReadAll(resp.Body)
fmt.Println("User data:", string(body))
database.DB.Create(&order)
c.JSON(http.StatusOK, order)
}
5.5 订单服务入口
在 order-service/cmd/main.go
中启动订单服务:
package main
import (
"order-service/internal/database"
"order-service/internal/routes"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"os"
)
func initConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./config")
if err := viper.ReadInConfig(); err != nil {
panic("failed to read config file")
}
}
func setupLogger() *logrus.Logger {
log := logrus.New()
logFile, err := os.OpenFile(viper.GetString("logging.file"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.Out = logFile
} else {
log.Info("Failed to log to file, using default stderr")
}
return log
}
func main() {
initConfig()
database.InitDatabase(viper.GetString("database.path"))
log := setupLogger()
router := gin.Default()
router.GET("/orders", routes.GetOrders)
router.POST("/orders", routes.CreateOrder)
log.Info("Starting order service at", viper.GetString("app.port"))
router.Run(viper.GetString("app.port"))
}
6. 服务通信与测试
6.1 启动用户服务
在 user-service
目录下运行以下命令:
go run cmd/main.go
6.2 启动订单服务
在 order-service
目录下运行以下命令:
go run cmd/main.go
6.3 测试用户服务和订单服务的交互
- 创建用户:
curl -X POST http://localhost:8081/users -H "Content-Type: application/json" -d '{"name": "John Doe", "email": "john@example.com"}'
- 创建订单:
curl -X POST http://localhost:8082/orders -H "Content-Type: application/json" -d '{"user_id": 1, "item": "Laptop", "price": 1200.50}'
7. 小结
- 用户服务与订单服务:我们构建了两个独立的微服务,分别负责用户和订单的管理。
- 服务之间的通信:订单服务通过 HTTP 请求与用户服务进行通信,获取用户信息。
- 配置管理:使用 Viper 管理服务的配置,包括服务端口、数据库路径等。
- 日志记录:通过 Logrus 记录服务的运行日志。