当前位置: 首页 > news >正文

Gin Web 服务集成 Consul:从服务注册到服务发现实践指南(下)

在微服务架构中,Web 层作为系统的入口门面,承担着请求路由、权限校验和服务聚合等核心功能。本文将围绕 Gin 框架与 Consul 注册中心的集成展开,详细讲解 Web 服务如何实现服务注册与发现,帮助你构建可扩展的微服务前端架构。
承接微服务注册中心详解

我在前面文章中也写了gRPC服务层集成consul,需要的可以回去看一下。

一、Web 层服务集成 Consul 概述

1.1 Web 层在微服务中的定位

Web 层(如 Gin 服务)通常扮演以下多重角色:

  • API 网关:作为系统统一入口,处理跨域请求、身份认证、流量限流等非业务逻辑,避免底层服务重复实现通用功能
  • 服务聚合器:将多个底层微服务的能力组合成复合 API,减少客户端与后端的交互次数(如同时获取用户信息和订单列表)
  • 请求路由枢纽:根据业务逻辑将请求转发到对应服务,实现前端与后端服务的逻辑解耦
为什么 Web 层需要集成 Consul?

在单体架构中,服务地址通常是硬编码的;但在微服务场景下:

  • 底层服务可能部署多个实例(如用户服务有 3 个副本),需要动态获取可用地址
  • 服务可能因扩缩容、故障转移而变更 IP 端口,手动维护地址列表不现实
  • 负载均衡需要从注册中心获取实时服务列表,实现流量的动态分配

1.2 服务注册与发现的双重需求

1.2.1 服务注册的必要性

即使作为前端服务,Web 层仍需注册到 Consul 的核心原因:

  • 反向调用场景:后台管理系统、数据同步服务可能需要调用 Web 层 API,需通过注册中心获取地址
  • 负载均衡需求:Nginx 等反向代理可从 Consul 动态获取 Web 服务地址,避免手动配置上游服务器
  • 全局服务治理:统一在 Consul 中监控所有服务的健康状态,包括 Web 层的运行情况
1.2.2 服务发现的核心作用

Web 层的服务发现机制解决了三大问题:

  1. 地址动态获取:底层服务(如用户服务)的 IP 端口变更时,Web 层无需重启即可获取新地址
  2. 多实例负载均衡:当同一服务存在多个实例时,自动选择合适的节点调用
  3. 故障自动转移:检测到服务实例不健康时,自动跳过该实例,避免请求失败

二、Consul 配置与服务发现实现

2.2 服务发现核心代码解析

2.2.1 从 Consul 获取服务地址的执行流程

您提供的代码片段展示了从 Consul 获取用户服务地址并建立 gRPC 连接的完整过程。这一过程可以分解为以下关键步骤:

  1. 初始化 Consul 客户端:从配置中读取 Consul 服务器地址并创建客户端
  2. 服务发现查询:通过服务名称过滤获取目标服务实例
  3. 地址解析与连接:提取服务 IP 和端口,建立 gRPC 长连接
  4. 全局客户端管理:将 gRPC 客户端存储为全局变量供后续使用
// service_discovery.go - 服务发现实现
package serviceimport ("fmt""log""github.com/hashicorp/consul/api""your-project/global""zap"
)// InitSrvConn 初始化服务连接
func InitSrvConn() {// 1. 初始化Consul客户端配置cfg := api.DefaultConfig()consulInfo := global.ServerConfig.ConsulInfocfg.Address = fmt.Sprintf("%s:%d", consulInfo.Host, consulInfo.Port)// 2. 创建Consul客户端client, err := api.NewClient(cfg)if err != nil {log.Fatalf("创建Consul客户端失败: %v", err)}// 3. 过滤查询用户服务data, err := client.Agent().ServicesWithFilter(fmt.Sprintf("Service==\"%s\"", global.ServerConfig.UserSrvInfo.Name))if err != nil {log.Fatalf("过滤服务失败: %v", err)}// 4. 解析服务地址userSrvHost := ""userSrvPort := 0fmt.Println("过滤后的服务:")for _, value := range data {userSrvHost = value.AddressuserSrvPort = value.Portbreak}// 5. 验证服务是否找到if userSrvHost == "" {zap.S().Fatal("[InitSrvConn] 连接 用户服务失败")return}// 6. 建立gRPC连接userconn, err := grpc.Dial(fmt.Sprintf("%s:%d", userSrvHost, userSrvPort), grpc.WithInsecure())if err != nil {zap.S().Errorw("[GetUserList] 连接 [用户服务失败]", "message", err.Error())}// 7. 生成gRPC客户端并存储为全局变量userSrvClient := proto.NewUserClient(userconn)global.UserSrvClient = userSrvClientzap.S().Infof("用户服务连接成功: %s:%d", userSrvHost, userSrvPort)
}
2.2.2 在 Gin 中集成服务发现的完整流程

您提供的示例代码展示了在 Gin 路由处理函数中如何使用通过 Consul 发现的服务。下面我将结合您的代码,详细说明服务发现如何与实际业务接口结合:

// main.go - Gin服务启动流程
package mainimport ("context""fmt""github.com/gin-gonic/gin""google.golang.org/grpc""net/http""strconv""time""your-project/global""your-project/proto""your-project/service""your-project/response" // 假设这是模型包"your-project/forms"    // 假设这是表单验证包"your-project/models"   // 假设这是模型包"your-project/utils"    // 假设这是工具包"zap"
)func main() {// 1. 初始化配置initConfig()// 2. 初始化服务连接(从Consul发现服务)service.InitSrvConn()// 3. 初始化Gin引擎r := gin.Default()// 4. 注册中间件setupMiddlewares(r)// 5. 注册路由setupRoutes(r)// 6. 注册Web服务到Consulif err := service.RegisterWebServiceToConsul(); err != nil {panic(fmt.Sprintf("Web服务注册失败: %v", err))}// 7. 启动Gin服务addr := fmt.Sprintf(":%d", global.ServerConfig.Port)zap.S().Infof("服务启动: %s", addr)if err := r.Run(addr); err != nil {panic(fmt.Sprintf("Gin服务启动失败: %v", err))}
}// 用户控制器 - 整合业务接口示例
func setupRoutes(r *gin.Engine) {userGroup := r.Group("/users"){userGroup.GET("/", GetUserList)        // 获取用户列表userGroup.POST("/login", PassWordLogin) // 用户登录}
}// 获取用户列表 - 
func GetUserList(ctx *gin.Context) {// 从JWT中取出用户id(假设JWT验证已通过)claims, exists := ctx.Get("claims")if !exists {ctx.JSON(http.StatusUnauthorized, gin.H{"error": "未授权",})return}currentUser := claims.(*models.CustomClaims)zap.S().Infof("用户信息:%v", currentUser.Id)// 分页参数处理pn := ctx.DefaultQuery("pn", "0")pnInt, _ := strconv.Atoi(pn)pSize := ctx.DefaultQuery("psize", "10")pSizeInt, _ := strconv.Atoi(pSize)// 通过全局变量获取已初始化的gRPC客户端rsp, err := global.UserSrvClient.GetUserList(context.Background(), &proto.PageInfo{Pn:    uint32(pnInt),PSize: uint32(pSizeInt),})if err != nil {zap.S().Errorw("[GetUserList] 查询用户列表失败", "error", err)utils.HandleGrpcErrorToHttp(err, ctx) // 自定义错误处理函数return}// 构建响应数据result := make([]interface{}, 0)for _, value := range rsp.Data {userResponse := response.UserResponse{Id:       value.Id,NickName: value.NickName,Birthday: response.JsonTime(time.Unix(int64(value.BirthDay), 0)),Gender:   value.Gender,Mobile:   value.Mobile,}result = append(result, userResponse)}ctx.JSON(http.StatusOK, result)
}// 用户登录 - 示例接口
func PassWordLogin(c *gin.Context) {zap.S().Infof("用户登录开始")// 表单验证passWordLoginForm := forms.PassWordLoginForm{}if err := c.ShouldBind(&passWordLoginForm); err != nil {utils.HandleValidatorError(c, err) // 自定义验证错误处理return}zap.S().Infof("用户登录表单验证成功")// 验证验证码if !utils.Store.Verify(passWordLoginForm.CaptchaId, passWordLoginForm.Captcha, true) {zap.S().Infof("验证码错误")c.JSON(http.StatusBadRequest, gin.H{"msg": "验证码错误",})return}// 通过全局变量获取gRPC客户端,调用用户服务验证登录rsp, err := global.UserSrvClient.GetUserByMobile(context.Background(), &proto.MobileRequest{Mobile: passWordLoginForm.Mobile,})if err != nil {zap.S().Errorw("[PassWordLogin] 查询用户失败", "error", err)utils.HandleGrpcErrorToHttp(err, c)return}// 验证密码(示例代码,实际应使用加密比较)if passWordLoginForm.PassWord != rsp.PassWord {c.JSON(http.StatusBadRequest, gin.H{"msg": "密码错误",})return}// 生成JWT tokentoken, err := utils.GenerateToken(rsp.Id, rsp.NickName, rsp.Role)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"msg": "生成token失败",})return}// 返回登录成功响应c.JSON(http.StatusOK, gin.H{"id":         rsp.Id,"nick_name":  rsp.NickName,"token":      token,"expired_at": time.Now().Add(time.Hour * 24).Unix(),})
}

三、Gin 服务集成 Consul 的关键流程解析

3.1 服务发现的时机选择

从代码可以看出,服务发现发生在两个关键阶段:

  1. 应用启动阶段:在main函数中调用service.InitSrvConn(),在服务启动时就完成服务发现和连接建立
  2. 请求处理阶段:在具体的路由处理函数(如GetUserList)中使用已初始化的全局客户端

这种设计的优势在于:

  • 避免首次请求时的延迟(冷启动问题)
  • 通过全局变量复用连接,减少 TCP 握手开销
  • 服务启动时若无法连接下游服务,则直接终止,避免提供不可用服务

3.2 服务发现与业务逻辑的结合

示例代码展示了服务发现如何与实际业务逻辑结合:

  1. 获取用户列表接口

    • 从 JWT 中提取用户身份信息
    • 解析分页参数
    • 调用通过 Consul 发现的用户服务
    • 处理返回结果并构建响应
  2. 用户登录接口

    • 表单验证和验证码校验
    • 调用用户服务验证用户信息
    • 生成 JWT 令牌并返回

3.3 代码组织最佳实践

根据示例代码,建议以下代码组织方式:

  1. 服务发现逻辑:封装在service包中,提供初始化函数(如InitSrvConn
  2. 全局客户端:存储在global包中,供所有需要调用服务的地方使用
  3. 路由处理:按业务模块组织路由和处理函数(如user_routes.go
  4. 工具函数:将通用功能(如错误处理、表单验证)封装在utils包中

四、服务注册中心接口与实现

4.1 服务注册核心接口详解

4.1.1 服务注册的关键参数说明
// consul.go - 服务注册实现中的关键配置项
registration := &api.AgentServiceRegistration{ID:      fmt.Sprintf("%s-%d", config.Name, config.Port), // 服务ID必须唯一,建议使用"服务名-端口"格式Name:    config.Name,                                   // 服务名称,用于服务发现时的查询Address: "0.0.0.0",                                     // 服务绑定地址,0.0.0.0表示监听所有网络接口Port:    config.Port,                                   // 服务端口,需与实际监听端口一致Tags:    []string{"gin", "web-service"},                // 服务标签,可用于过滤(如查询所有Gin框架的服务)Check: &api.AgentServiceCheck{// 健康检查配置核心参数:HTTP:                         fmt.Sprintf("http://127.0.0.1:%d/health", config.Port), // 健康检查URL,建议使用回环地址Interval:                     "10s",       // 检查间隔,过短会增加开销,过长会延迟故障发现Timeout:                      "5s",        // 超时时间,应小于间隔时间DeregisterCriticalServiceAfter: "30s",     // 服务不健康后自动注销的时间,避免长时间保留失效实例},
}
4.1.2 服务注册的最佳实践
  • ID 唯一性:使用服务名 + 端口生成 ID,避免多实例部署时 ID 冲突
  • 健康检查类型:Web 服务建议使用 HTTP 检查(相比 TCP 检查可验证应用层逻辑)
  • 注销延迟DeregisterCriticalServiceAfter建议设为 30-60 秒,给服务重启预留时间
  • 标签规范:通过标签区分服务类型(如 web、srv)、环境(dev、prod)、版本(v1、v2)

4.2 健康检查接口实现原理

4.2.1 健康检查的两种模式
  1. 主动检查:Consul 定期向服务发送请求(如本文实现的 HTTP 检查)
  2. 被动检查:服务主动向 Consul 报告健康状态(适用于网络隔离场景)
4.2.2 健康检查接口的设计要点
// health.go - 健康检查路由实现细节
package routerimport ("github.com/gin-gonic/gin""your-project/global""time"
)// SetupHealthRoutes 配置健康检查路由:
// - 路径固定为/health,符合Consul默认检查规范
// - 返回JSON格式状态,便于机器解析和人工查看
func SetupHealthRoutes(r *gin.Engine) {r.GET("/health", func(c *gin.Context) {// 1. 检查自身服务状态://    - 可添加内存、CPU使用率检查//    - 验证数据库连接、Redis连接等基础资源selfStatus := "UP"if global.ServerConfig.Port == 0 {selfStatus = "DOWN" // 配置未正确加载}// 2. 检查依赖服务状态://    - 此处简化为判断客户端是否存在//    - 生产环境应发送实际请求验证服务可用性depStatus := make(map[string]string)if global.UserSrvClient != nil {// 可选:发送轻量级请求验证服务响应ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)defer cancel()_, err := global.UserSrvClient.HealthCheck(ctx, &proto.HealthCheckRequest{})if err != nil {depStatus["user-srv"] = "DOWN"} else {depStatus["user-srv"] = "UP"}} else {depStatus["user-srv"] = "DOWN"}// 3. 返回健康状态://    - 200状态码表示整体健康//    - 503状态码表示部分依赖不可用c.JSON(200, gin.H{"status":       selfStatus,"dependencies": depStatus,"timestamp":    time.Now().Unix(),"service":      global.ServerConfig.Name,})})
}

五、测试调试与错误处理

5.1 常见测试场景与调试方法

5.1.1 服务注册验证的三种方式
  1. Consul UI 可视化验证

    • 访问http://consul-host:8500进入 Web 管理界面
    • 在 "Services" 标签页查看user-web服务是否存在
    • 点击服务名称,查看 "Checks" 标签页确认健康检查状态为 "passing"
  2. API 接口验证

    # 查询所有注册服务(返回JSON格式服务列表)
    curl http://consul-host:8500/v1/catalog/services# 查询指定服务的详细信息
    curl http://consul-host:8500/v1/catalog/service/user-web# 验证健康检查接口(需确保Web服务已启动)
    curl http://web-service-host:8021/health
    
  3. 日志动态验证

    • 启动 Web 服务时观察日志,应输出 "Web 服务已注册到 Consul"
    • 查看 Consul 服务器日志,确认收到注册请求
5.1.2 服务发现调试技巧
// 调试服务发现的辅助函数(生产环境建议注释掉)
func debugServiceDiscovery() {serviceName := "user-srv"host, port, err := service.GetServiceAddress(serviceName)if err != nil {log.Fatalf("服务发现失败: 错误详情, %v", err)}// 打印详细的服务元数据,便于调试log.Printf("发现服务 %s 在 %s:%d", serviceName, host, port)// 可选:打印服务的Tags、Meta等信息
}

5.2 错误处理最佳实践

5.2.1 服务发现异常的分级处理
// 带重试和降级的服务发现函数
func GetServiceWithFallback(serviceName string) (string, int, error) {// 1. 定义重试策略:最多重试3次,间隔递增maxRetries := 3for i := 0; i < maxRetries; i++ {host, port, err := service.GetServiceAddress(serviceName)if err == nil {return host, port, nil // 成功获取直接返回}// 打印带重试次数的错误日志,便于问题追踪log.Printf("服务发现失败(尝试 %d/%d): %v", i+1, maxRetries, err)// 指数退避策略:重试间隔逐渐增加,减少服务器压力time.Sleep(time.Duration(i+1) * 2 * time.Second)}// 2. 服务发现失败后的降级处理://    - 从缓存中获取历史地址(需实现缓存机制)//    - 返回预定义的备用地址(如备用服务节点)fallbackHost, fallbackPort := getFallbackServiceAddress(serviceName)if fallbackHost != "" {log.Printf("使用降级地址: %s:%d", fallbackHost, fallbackPort)return fallbackHost, fallbackPort, nil}return "", 0, fmt.Errorf("服务发现重试 %d 次失败且无降级方案", maxRetries)
}
5.2.2 网络异常的全链路处理
// 在gRPC连接中添加完整的错误处理链
userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", host, port),grpc.WithInsecure(),grpc.WithBlock(),grpc.WithTimeout(5*time.Second), // 连接超时控制grpc.WithKeepaliveParams(keepalive.ClientParameters{Time:                10 * time.Second, // 发送心跳间隔Timeout:             3 * time.Second,  // 心跳超时PermitWithoutStream: true,             // 允许在无流时发送心跳}),
)
if err != nil {// 区分临时性错误和永久性错误if opErr, ok := err.(*net.OpError); ok && opErr.Temporary() {log.Printf("临时性连接错误,将尝试重连: %v", err)// 此处可添加重连逻辑} else {log.Printf("永久性连接错误: %v", err)// 记录错误并返回降级响应return nil, err}
}

六、Web 层服务集成 Consul 的优化策略

6.1 连接池与负载均衡的工程实践

6.1.1 连接池实现原理

连接池解决的核心问题:

  • TCP 连接创建开销:每次调用都创建新连接会导致三次握手和四次挥手的性能损耗
  • 文件句柄限制:大量短连接可能耗尽系统文件句柄资源
  • 连接复用效率:复用已有连接可减少网络延迟和资源占用
// 全局连接池实现细节
var (// 连接池配置参数可通过配置文件动态调整userSrvConnPool = &connPool{maxConn: 10,        // 最大连接数,根据服务负载调整conns:   make(chan *grpc.ClientConn, 10), // 缓冲通道实现连接池mu:      sync.Mutex{},                    // 互斥锁保护连接池状态}
)type connPool struct {maxConn intconns   chan *grpc.ClientConnmu      sync.Mutex// 记录连接创建时间,用于超时回收createTimes map[*grpc.ClientConn]time.Time
}// 从连接池获取连接的流程:
// 1. 先从通道中获取空闲连接
// 2. 若通道为空,创建新连接(不超过maxConn限制)
// 3. 检查连接是否可用(避免使用已关闭的连接)
func (p *connPool) Get() (*grpc.ClientConn, error) {select {case conn := <-p.conns:// 检查连接是否有效if err := p.checkConnValid(conn); err != nil {conn.Close()return p.createNewConn()}return conn, nildefault:return p.createNewConn()}
}// 创建新连接时添加服务发现逻辑,确保获取最新地址
func (p *connPool) createNewConn() (*grpc.ClientConn, error) {p.mu.Lock()defer p.mu.Unlock()if len(p.conns) >= p.maxConn {return nil, fmt.Errorf("连接池已满,无法创建新连接")}host, port, err := service.GetServiceAddress("user-srv")if err != nil {return nil, err}conn, err := grpc.Dial(fmt.Sprintf("%s:%d", host, port),grpc.WithInsecure(),grpc.WithBlock(),)if err != nil {return nil, err}// 记录连接创建时间,用于超时回收if p.createTimes == nil {p.createTimes = make(map[*grpc.ClientConn]time.Time)}p.createTimes[conn] = time.Now()return conn, nil
}// 定期清理超时连接的后台goroutine
func (p *connPool) cleanUpExpiredConns() {ticker := time.NewTicker(30 * time.Second)defer ticker.Stop()for range ticker.C {p.mu.Lock()now := time.Now()for conn, createTime := range p.createTimes {if now.Sub(createTime) > 5*time.Minute { // 5分钟未使用的连接conn.Close()delete(p.createTimes, conn)}}p.mu.Unlock()}
}

6.2 生产环境部署建议

6.2.1 Consul 集群高可用配置
  • 节点数量:建议部署 3 或 5 个节点(奇数个),可容忍 1 或 2 个节点故障
  • 数据加密:启用 TLS 加密通信,防止中间人攻击
  • ACL 权限:配置细粒度访问控制,区分服务注册、查询等操作的权限
  • 数据持久化:配置 Raft 日志持久化,确保节点重启后数据不丢失
6.2.2 连接超时与熔断策略
  • 超时设置原则:下游服务的超时时间应小于上游服务的超时时间
  • 熔断触发条件:连续失败次数超过阈值(如 5 次)则触发熔断
  • 熔断恢复策略:设置半开状态,定期尝试少量请求,成功后恢复正常
  • 降级响应设计:熔断期间返回预定义的降级响应,避免前端报错
6.2.3 配置动态更新机制
  1. 监听 Consul KV 变更

    // 监听配置变更的后台goroutine
    func watchConfigChanges() {client, err := getConsulClient()if err != nil {log.Fatalf("创建Consul客户端失败: %v", err)}kv := client.KV()lastIndex := uint64(0)for {// 查询配置键值对,带阻塞查询options := &api.QueryOptions{WaitIndex: lastIndex,WaitTime:  10 * time.Second,}pair, meta, err := kv.Get("config/web-service", options)if err != nil {log.Printf("查询配置失败: %v", err)time.Sleep(1 * time.Second)continue}if pair != nil {// 解析新配置并更新全局配置if err := updateGlobalConfig(pair.Value); err != nil {log.Printf("更新配置失败: %v", err)}}lastIndex = meta.LastIndex}
    }
    
  2. 配置更新注意事项

    • 避免频繁更新导致服务抖动,设置最小更新间隔
    • 关键配置(如数据库连接)更新时需平滑过渡
    • 配置更新后记录变更日志,便于问题追溯

七、完整项目结构参考

通过本文的实践,我们完成了 Gin Web 服务与 Consul 注册中心的深度集成,覆盖了从服务注册、服务发现到健康检查、错误处理的全流程。在实际项目中,Web 层作为微服务的门面,其稳定性直接影响用户体验,合理利用 Consul 的服务治理能力能够有效提升系统的可靠性和可维护性。建议结合业务特点进一步完善负载均衡策略、连接池管理和动态配置更新机制,打造真正健壮的微服务前端架构。当遇到问题时,可通过 Consul UI、日志分析和服务发现调试工具逐步定位,确保每个环节的稳定性。

web-service/
├── config/                  # 配置文件目录,采用环境隔离设计
│   ├── base.yaml            # 基础公共配置,包含各环境通用参数
│   ├── dev.yaml             # 开发环境配置,包含本地服务地址
│   ├── test.yaml            # 测试环境配置,指向测试集群
│   └── prod.yaml            # 生产环境配置,指向正式集群
├── global/                  # 全局状态管理
│   ├── global.go            # 存储全局配置、客户端等对象
│   └── constants.go         # 定义项目常量
├── initialize/              # 初始化逻辑封装
│   ├── config.go            # 配置文件解析与验证
│   ├── consul.go            # Consul客户端初始化与服务注册
│   ├── logger.go            # 日志系统初始化
│   └── grpc.go              # gRPC客户端连接初始化
├── proto/                   # gRPC服务定义
│   ├── user.proto           # 用户服务接口定义(.proto文件)
│   ├── user_grpc.pb.go      # 生成的gRPC客户端代码
│   └── user.pb.go           # 生成的消息结构体代码
├── service/                 # 核心业务逻辑
│   ├── discovery.go         # 服务发现实现,包含负载均衡
│   ├── registration.go      # 服务注册实现,包含健康检查
│   ├── connection_pool.go   # gRPC连接池实现
│   └── fallback.go          # 服务降级逻辑
├── router/                  # 路由配置
│   ├── routes.go            # 主路由注册,包含中间件
│   ├── health.go            # 健康检查路由
│   ├── user_routes.go       # 用户相关API路由
│   └── auth_routes.go       # 认证授权路由
├── handler/                 # 接口处理函数
│   ├── user_handler.go      # 用户信息查询、修改等接口
│   ├── auth_handler.go      # 登录、token管理等接口
│   ├── order_handler.go     # 订单相关接口(示例)
│   └── middleware.go        # 自定义中间件(如JWT认证)
├── model/                   # 数据模型
│   ├── request.go           # 请求参数结构体
│   ├── response.go          # 响应结果结构体
│   └── entity.go            # 业务实体模型
├── utils/                   # 工具函数
│   ├── jwt.go               # JWT生成与验证
│   ├── redis.go             # Redis操作封装
│   └── error.go             # 错误处理工具
└── main.go                  # 程序入口,包含启动流程


对于这篇文章如果大家对微服务不是很熟练的话,主要前四部分就好。可以看到在第五部分我也写到了负载均衡,对于简化配置来说,这是一个很重要的内容,后面的文章我会尽快写到。

还有就是可以看到在第三部分的举例中,我其实是将InitSrvConn 初始化服务连接这部分代码作为全局变量来写的,这就大大简化了代码量,如果不设一个全局变量,你每次都要建立一次连接,这不符合开发原则,所以关于全局变量的实践我后面会单独出一篇文章来写。

制作不易,大概花了6小时来写,如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力😊!

http://www.dtcms.com/a/268002.html

相关文章:

  • # IS-IS 协议 | LSP 传输与链路状态数据库同步机制
  • 网络爬虫认证的综合分析:从HTTP模拟到浏览器自动化
  • mac中创建 .command 文件,执行node服务
  • 微信小程序71~80
  • 善用关系网络:开源AI大模型、AI智能名片与S2B2C商城小程序赋能下的成功新路径
  • Web后端开发-SpringBootWeb入门、Http协议、Tomcat
  • Gin 框架中如何实现 JWT 鉴权中间件
  • 学习栈和队列的插入和删除操作
  • 网安系列【8】之暴力破解入门
  • 【机器学习深度学习】多分类评估策略
  • Solidity——什么是低级调用(low-level calls)和操作码的内联汇编
  • 一次内存“卡顿”全流程实战分析:从制造问题到优化解决
  • Apache Spark 4.0:将大数据分析提升到新的水平
  • 小架构step系列06:编译配置
  • 在C#中,可以不实例化一个类而直接调用其静态字段
  • 2025年03月 C/C++(四级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • python-转义字符
  • 李宏毅2025《机器学习》第四讲-Transformer架构的演进
  • 力扣971. 寻找图中是否存在路径【simple 拓扑排序/图 Java】
  • 【双向循环带头链表】
  • Java中的抽象类和接口
  • CICD[构建镜像]:构建django使用的docker镜像
  • 【9】用户接入与认证配置
  • 车载智能座舱用户画像系统研究二:子系统构建
  • Linux国产与国外进度对垒
  • GANs环境应用及启发思考
  • java学习——guava并发编程练习
  • 跨平台游戏引擎 Axmol-2.7.0 发布
  • @Data、@AllArgsConstructor、@NoArgsConstructor不生效。lombok不起作用怎么解决?
  • 设置LInux环境变量的方法和区别_Ubuntu/Centos