Go 微服务基础
微服务架构是将单体应用拆分成多个小型、独立的服务,每个服务负责特定的业务功能。
微服务架构特点
- 服务独立:每个服务可以独立开发、部署、扩展
- 技术异构:不同服务可以使用不同的技术栈
- 数据独立:每个服务有自己的数据库
- 通信:服务之间通过 API 通信(HTTP、gRPC 等)
简单的微服务示例
项目结构
microservices/
├── go.mod
├── user-service/
│ ├── main.go
│ └── handler.go
├── order-service/
│ ├── main.go
│ └── handler.go
└── gateway/
└── main.go
用户服务(User Service)
// user-service/main.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var users = []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
func getUserHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "只支持 GET 方法", http.StatusMethodNotAllowed)
return
}
// 获取用户 ID
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "无效的用户 ID", http.StatusBadRequest)
return
}
// 查找用户
var user *User
for _, u := range users {
if u.ID == id {
user = &u
break
}
}
if user == nil {
http.Error(w, "用户不存在", http.StatusNotFound)
return
}
// 返回 JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func listUsersHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func main() {
http.HandleFunc("/user", getUserHandler)
http.HandleFunc("/users", listUsersHandler)
port := ":8001"
log.Printf("用户服务启动在 http://localhost%s", port)
log.Fatal(http.ListenAndServe(port, nil))
}订单服务(Order Service)
// order-service/main.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
)
type Order struct {
ID int `json:"id"`
UserID int `json:"user_id"`
Item string `json:"item"`
Amount float64 `json:"amount"`
}
var orders = []Order{
{ID: 1, UserID: 1, Item: "商品A", Amount: 100.0},
{ID: 2, UserID: 1, Item: "商品B", Amount: 200.0},
{ID: 3, UserID: 2, Item: "商品C", Amount: 150.0},
}
func getOrderHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "只支持 GET 方法", http.StatusMethodNotAllowed)
return
}
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "无效的订单 ID", http.StatusBadRequest)
return
}
var order *Order
for _, o := range orders {
if o.ID == id {
order = &o
break
}
}
if order == nil {
http.Error(w, "订单不存在", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(order)
}
func getUserOrdersHandler(w http.ResponseWriter, r *http.Request) {
userIDStr := r.URL.Query().Get("user_id")
userID, err := strconv.Atoi(userIDStr)
if err != nil {
http.Error(w, "无效的用户 ID", http.StatusBadRequest)
return
}
var userOrders []Order
for _, o := range orders {
if o.UserID == userID {
userOrders = append(userOrders, o)
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(userOrders)
}
func main() {
http.HandleFunc("/order", getOrderHandler)
http.HandleFunc("/orders", getUserOrdersHandler)
port := ":8002"
log.Printf("订单服务启动在 http://localhost%s", port)
log.Fatal(http.ListenAndServe(port, nil))
}API 网关(Gateway)
// gateway/main.go
package main
import (
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
)
// 反向代理到用户服务
func userProxy(w http.ResponseWriter, r *http.Request) {
target, _ := url.Parse("http://localhost:8001")
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.ServeHTTP(w, r)
}
// 反向代理到订单服务
func orderProxy(w http.ResponseWriter, r *http.Request) {
target, _ := url.Parse("http://localhost:8002")
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.ServeHTTP(w, r)
}
// 聚合服务:获取用户及其订单
func userWithOrdersHandler(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
// 调用用户服务
userResp, err := http.Get("http://localhost:8001/user?id=" + userID)
if err != nil {
http.Error(w, "用户服务不可用", http.StatusInternalServerError)
return
}
defer userResp.Body.Close()
// 调用订单服务
ordersResp, err := http.Get("http://localhost:8002/orders?user_id=" + userID)
if err != nil {
http.Error(w, "订单服务不可用", http.StatusInternalServerError)
return
}
defer ordersResp.Body.Close()
// 聚合响应(简化版)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"user\": "))
io.Copy(w, userResp.Body)
w.Write([]byte(", \"orders\": "))
io.Copy(w, ordersResp.Body)
w.Write([]byte("}"))
}
func main() {
// 路由
http.HandleFunc("/api/user/", userProxy)
http.HandleFunc("/api/order/", orderProxy)
http.HandleFunc("/api/user-with-orders", userWithOrdersHandler)
port := ":8080"
log.Printf("API 网关启动在 http://localhost%s", port)
log.Println("可用路由:")
log.Println(" - http://localhost:8080/api/user/?id=1")
log.Println(" - http://localhost:8080/api/order/?id=1")
log.Println(" - http://localhost:8080/api/user-with-orders?user_id=1")
log.Fatal(http.ListenAndServe(port, nil))
}服务发现
简单的服务注册
// service-registry/main.go
package main
import (
"encoding/json"
"log"
"net/http"
"sync"
"time"
)
type Service struct {
Name string `json:"name"`
Address string `json:"address"`
Port int `json:"port"`
LastSeen time.Time `json:"last_seen"`
}
type Registry struct {
services map[string]*Service
mutex sync.RWMutex
}
func NewRegistry() *Registry {
r := &Registry{
services: make(map[string]*Service),
}
// 启动清理过期服务的 goroutine
go r.cleanup()
return r
}
func (r *Registry) Register(name, address string, port int) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.services[name] = &Service{
Name: name,
Address: address,
Port: port,
LastSeen: time.Now(),
}
log.Printf("服务注册: %s -> %s:%d", name, address, port)
}
func (r *Registry) Get(name string) (*Service, bool) {
r.mutex.RLock()
defer r.mutex.RUnlock()
service, exists := r.services[name]
if exists {
service.LastSeen = time.Now() // 更新最后访问时间
}
return service, exists
}
func (r *Registry) List() []*Service {
r.mutex.RLock()
defer r.mutex.RUnlock()
services := make([]*Service, 0, len(r.services))
for _, s := range r.services {
services = append(services, s)
}
return services
}
func (r *Registry) cleanup() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
r.mutex.Lock()
now := time.Now()
for name, service := range r.services {
if now.Sub(service.LastSeen) > 60*time.Second {
delete(r.services, name)
log.Printf("服务过期,已移除: %s", name)
}
}
r.mutex.Unlock()
}
}
func main() {
registry := NewRegistry()
// 注册服务
http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "只支持 POST 方法", http.StatusMethodNotAllowed)
return
}
var service Service
if err := json.NewDecoder(r.Body).Decode(&service); err != nil {
http.Error(w, "无效的请求", http.StatusBadRequest)
return
}
registry.Register(service.Name, service.Address, service.Port)
w.WriteHeader(http.StatusOK)
})
// 获取服务
http.HandleFunc("/service/", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path[len("/service/"):]
service, exists := registry.Get(name)
if !exists {
http.Error(w, "服务不存在", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(service)
})
// 列出所有服务
http.HandleFunc("/services", func(w http.ResponseWriter, r *http.Request) {
services := registry.List()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(services)
})
port := ":8500"
log.Printf("服务注册中心启动在 http://localhost%s", port)
log.Fatal(http.ListenAndServe(port, nil))
}配置管理
环境变量配置
// config/config.go
package config
import (
"os"
"strconv"
)
type Config struct {
ServerPort string
DatabaseURL string
RedisURL string
LogLevel string
MaxWorkers int
}
func Load() *Config {
return &Config{
ServerPort: getEnv("SERVER_PORT", "8080"),
DatabaseURL: getEnv("DATABASE_URL", "localhost:3306"),
RedisURL: getEnv("REDIS_URL", "localhost:6379"),
LogLevel: getEnv("LOG_LEVEL", "info"),
MaxWorkers: getEnvAsInt("MAX_WORKERS", 10),
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvAsInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}健康检查
// health/health.go
package health
import (
"encoding/json"
"net/http"
"time"
)
type HealthStatus struct {
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
Services map[string]string `json:"services,omitempty"`
}
func HealthHandler(w http.ResponseWriter, r *http.Request) {
status := HealthStatus{
Status: "healthy",
Timestamp: time.Now(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}
func ReadinessHandler(w http.ResponseWriter, r *http.Request) {
// 检查依赖服务是否就绪
status := HealthStatus{
Status: "ready",
Timestamp: time.Now(),
Services: map[string]string{
"database": "ok",
"redis": "ok",
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}日志管理
// logger/logger.go
package logger
import (
"log"
"os"
)
var (
InfoLogger *log.Logger
ErrorLogger *log.Logger
)
func Init() {
InfoLogger = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLogger = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
}
func Info(format string, v ...interface{}) {
InfoLogger.Printf(format, v...)
}
func Error(format string, v ...interface{}) {
ErrorLogger.Printf(format, v...)
}总结
微服务架构的关键点:
- 服务拆分:按业务功能拆分服务
- API 网关:统一入口,路由和聚合
- 服务发现:服务注册与发现机制
- 配置管理:集中管理配置
- 健康检查:监控服务状态
- 日志管理:统一日志格式
这些是微服务的基础,实际项目中可以使用:
- gRPC:高性能 RPC 框架
- Consul/Eureka:服务发现
- Kubernetes:容器编排
- Istio:服务网格