Python+Go实践(电商架构三)

服务发现

  • 我们之前的架构是通过ip+port来调用的python的API,这样做的弊端是
    • 如果新加一个服务,就要到某个服务改web(go)层的调用代码,配置IP/Port
    • 并发过高需要加机器时,其他服务也要重新部署,否则找不到新加的node
      1
    • 如何解决这些问题呢?我们一般将多个服务进行注册
      2
    • 所有服务注册到注册中心,获取其他服务时通过这里拉取配置信息;(grpc也就是通过这配置的,传输序列化信息),其实就是将IP+Port放到这统一管理
    • 注册中心必须有健康检查的功能,所以不能使用Redis
    • 服务网关是对接用户的屏障,但也要能和注册中心交流,才能路由到具体服务
  • 技术选型
    3
    • Paxos和Raft算法可以私下偷偷了解一下
  • consul的安装和配置
    • 能用docker就不搞太多麻烦的步骤

      # 8500和8600端口常用
      docker run -d -p 8500:8500 -p 8301:8301 -p 8302:8302 -p 8600:8600/udp consul consul agent -dev -client=0.0.0.0
      docker container update --restart=always containerName/ID
      
    • 访问consul:ip:8500/ui/dc1/services;可以看出,consul是支持KV存储的(但没有Redis强)
      2

    • consul提供DNS功能(域名创建/解析),就是可以自定义域名;我们目前写的服务都可以通过ip+port方式注册和拉取,但如果有其他服务,不想通过consul,但希望能拉取各服务,这就需要consul能将自己和各服务域名化,方便外面的服务调用!

    • 可以用dig命令查看consul的DNS功能,dig @ip -p 8600 cosul1.service.consul SRV;就是在服务名后跟上service.consul,类似www.baidu.com

      • yum install bind-utils
  • consul的API接口
    • 注册服务;使用PUT请求,路径也指定了,然后就是传递指定的参数了(json格式)
      1
    • 类似的,可以删除服务、配置健康检查
    • 写个测试,先直接使用requests请求consul接口
      import requests
      
      headers = {
          "contentType":"application/json"
      }
      
      def register(name, id, address, port):
          url = "http://192.168.109.129:8500/v1/agent/service/register"
          print(f"http://{address}:{port}/health")
          rsp = requests.put(url, headers=headers, json={
              "Name":name,    # 服务名称  Specifies the logical name of the service.
              "ID":id,    # 服务ID  Specifies a unique ID for this service
              "Tags":["mxshop", "Roy", "veritas", "web"],
              "Address":address,  # 服务IP
              "Port":port,    # 服务Port
          })
          if rsp.status_code == 200:
              print("注册成功")
          else:
              print(f"注册失败:{rsp.status_code}")
             
      # 启动service
      if __name__ == "__main__":
          register("mxshop-python", "mxshop-py", "192.168.109.1", 50051)  # consul在Linux虚拟机(109.129),服务在Windows,用VMnet8的地址
      
    • 删除服务
      # 删除服务,有id即可
      def deregister(id):
          url = f"http://192.168.109.129:8500/v1/agent/service/deregister/{id}"
          rsp = requests.put(url, headers=headers)
          if rsp.status_code == 200:
              print("注销成功")
          else:
              print(f"注销失败:{rsp.status_code}")
      
    • 过滤服务;其实就是服务发现,输入你想找的服务名(这里还没有用DNS域名来搜索)
      # 列出指定的服务
      def filter_service(name):
          url = "http://192.168.109.129:8500/v1/agent/services"
          params = {
              "filter": f'Service == "{name}"'
          }
          rsp = requests.get(url, params=params).json()
          for key, value in rsp.items():
              print(key)
      
    • 健康检查,先配置针对HTTP的,比较简单,grpc的会复杂一些,稍后整~
      3
      # 配置Check即可
      def register(name, id, address, port):
          url = "http://192.168.109.129:8500/v1/agent/service/register"
          print(f"http://{address}:{port}/health")
          rsp = requests.put(url, headers=headers, json={
              "Name":name,    # 服务名称  Specifies the logical name of the service.
              "ID":id,    # 服务ID  Specifies a unique ID for this service
              "Tags":["mxshop", "Roy", "veritas", "web"],
              "Address":address,  # 服务IP
              "Port":port,    # 服务Port
              "Check": {
                  # "GRPC":f"{address}:{port}",
                  # "GRPCUseTLS": False,
                  "HTTP":f"http://{address}:{port}/health",   # 需要在go-web写这个路由的逻辑(handler,直接在main->initialize,不下到router->api了)
                  "Timeout": "5s",
                  "Interval": "5s",
                  "DeregisterCriticalServiceAfter": "15s"
              }
          })
          if rsp.status_code == 200:
              print("注册成功")
          else:
              print(f"注册失败:{rsp.status_code}")
      
      if __name__ == "__main__":
          # 注册go-web
          register("mxshop-go", "mxshop-web", "192.168.109.1", 8021)
          # deregister("mshop-web")
          # 然后给它配上健康检查
          filter_service("mxshop-go") # 传name
      
      // go-web
      package initialize
      
      import (
      	"github.com/gin-gonic/gin"
      	"mxshop/user-web/middlewares"
      	router2 "mxshop/user-web/router"
      	"net/http"
      )
      
      func Routers() *gin.Engine {
      	Router := gin.Default() // 全局 gin-context
      
      	// 定义一个临时的路由,测试consul注册
      	Router.GET("/health", func(context *gin.Context) {
      		context.JSON(http.StatusOK, gin.H{
      			"code":    http.StatusOK,
      			"success": true,
      		})
      	})
      
      	Router.Use(middlewares.Cors()) // 配置跨域
      	// 统一一下路由,传入group前缀,用户服务都用这个!
      	ApiGroup := Router.Group("/u/v1") // 版本号,加个u,方便后续测试
      	router2.InitUserRouter(ApiGroup)
      	return Router
      }
      // 确保consul的容器能和服务ping通
      
      4
      5
    • 配置GRPC的健康检查,假设我们要给python层的user-srv配置
      • 参考官方文档,我们需要使用proto文件生成相关的代码;下面是生成好的代码,主要是实现proto文件中的CheckWatch方法
        6
      • 当然是注册到当前的服务server,用到它这里的proto->pb2_grpc,和注册我们之前写的handler一样UserServicer,这里要注册人家的handler:HealthServicer
      • 代码补充:
        # server.py
        # 注意这个路径,可能要改改
        from common.grpc_health.v1 import health_pb2_grpc, health_pb2
        from common.grpc_health.v1 import health
        
        # 注册健康检查
        health_pb2_grpc.add_HealthServicer_to_server(health.HealthServicer(), server)
        
      • consul中设置端口号,把HTTP换成这两行
        "GRPC":f"{address}:{port}",
        "GRPCUseTLS": False,	// 不检查证书啥的
        
      • 要把服务启动,保证外部能ping通;测试一下
  • 上面是容器化的consul,搞个consul服务,也可以直接在python端,用第三方实现服务注册(还是consul)
    • 安装:pip install -i https://pypi.douban.com/simple python-consul2;官方文档就在GitHub搜吧
    • 这个东西对GRPC支持的不完善,就是比较省事;建议像上面,自己写服务注册的代码
      import consul
      
      c = consul.Consul(host="192.168.109.129")
      
      address = "192.168.109.1"
      port = 50051
      check={
          "GRPC":f"{address}:{port}",
          "GRPCUseTLS": False,
          "Timeout": "5s",
          "Interval": "5s",
          "DeregisterCriticalServiceAfter": "15s"
      }
      
      # rsp = c.agent.service.register(name="user-srv", service_id="user-srv2",
      #                          address=address, port=port, tags=["mxshop"],check=check)
      rsp = c.agent.services()
      for key, val in rsp.items():
          rsp = c.agent.service.deregister(key)
      # print(rsp)
      
  • 在go语言层面使用consul,类似在python写代码那样
    • 包括服务注册(检查)和发现
      package main
      
      import (
      	"fmt"
      
      	"github.com/hashicorp/consul/api"
      )
      
      func Register(address string, port int, name string, tags []string, id string) error {
      	cfg := api.DefaultConfig()
      	cfg.Address = "192.168.1.103:8500"
      
      	client, err := api.NewClient(cfg)
      	if err != nil {
      		panic(err)
      	}
      	//生成对应的检查对象,基于HTTP;直接实例化
      	check := &api.AgentServiceCheck{
      		HTTP:                           "http://192.168.109.129:8021/health",
      		Timeout:                        "5s",
      		Interval:                       "5s",
      		DeregisterCriticalServiceAfter: "10s",
      	}
      
      	//生成注册对象,通过new方法生成对象;有啥区别呢?忘了
      	registration := new(api.AgentServiceRegistration)
      	registration.Name = name
      	registration.ID = id
      	registration.Port = port
      	registration.Tags = tags
      	registration.Address = address
      	registration.Check = check
      
      	err = client.Agent().ServiceRegister(registration)
      	client.Agent().ServiceDeregister()
      	if err != nil {
      		panic(err)
      	}
      	return nil
      }
      
      func AllServices() {
      	cfg := api.DefaultConfig()
      	cfg.Address = "192.168.109.129:8500"
      
      	client, err := api.NewClient(cfg)
      	if err != nil {
      		panic(err)
      	}
      
      	data, err := client.Agent().Services()
      	if err != nil {
      		panic(err)
      	}
      	for key, _ := range data {
      		fmt.Println(key)
      	}
      }
      
      // 服务发现
      func FilterSerivice() {
      	cfg := api.DefaultConfig()
      	cfg.Address = "192.168.109.129:8500"
      
      	client, err := api.NewClient(cfg)
      	if err != nil {
      		panic(err)
      	}
      
      	data, err := client.Agent().ServicesWithFilter(`Service == "user-web"`)	// 写死了sorry
      	if err != nil {
      		panic(err)
      	}
      	for key, _ := range data {
      		fmt.Println(key)
      	}
      }
      
      func main() {
      	//_ = Register("192.168.109.1", 8021, "user-web", []string{"mxshop", "Roy"}, "user-web")
      	//AllServices()
      	//FilterSerivice()
      	fmt.Println(fmt.Sprintf(`Service == "%s"`, "user-srv"))
      }
      
    • python层定义了逻辑提供给go层进行rpc,所以python层需要支持grpc的check,go层只需要支持HTTP的check
    • python层也可以进行HTTP的check(只要是运行在ip+port上的服务),虽然不能通过HTTP直接调用(没路由)
    • 问题:只定义了proto文件和handler注册,go怎么找到python API的?grpc.Dial()

集成consul

  • 上面是测试consul,各自进行了服务注册和模拟发现;现在需要集成到我们的项目,让python层和go-web都能用上
  • 在python,服务注册;common/register,定义抽象类,并实现服务注册、删除、获取所有、发现
    • @abc.abstractmethod
    • 之前引入了grpc的health-check,直接使用(for go-web);如果希望HTTP-DNS形式的调用呢?
  • 在settings中加入consul服务器的配置(这个只能IP+Port,总得有个固定的手动操作的配置)
  • 在server.py中调用,启动
    from common.grpc_health.v1 import health_pb2_grpc, health_pb2
    from common.grpc_health.v1 import health
    from common.register import consul
    from settings import settings
    
    # 注册健康检查
    health_pb2_grpc.add_HealthServicer_to_server(health.HealthServicer(), server)
    
    # consul服务注册
    logger.info(f"服务注册开始")
    register = consul.ConsulRegister(settings.CONSUL_HOST, settings.CONSUL_PORT)
    if not register.register(name=settings.SERVICE_NAME, id=settings.SERVICE_NAME,
                             address=args.ip, port=args.port, tags=settings.SERVICE_TAGS, check=None):
        logger.info(f"服务注册失败")
    
        sys.exit(0)
    logger.info(f"服务注册成功")
    
  • 在go,进行服务发现,但因为逻辑比较繁琐,放在initialize/srv_conn.go,然后做一个全局变量,放在global,这个脚本只需要赋值这个全局变量就好;记得在config加上配置(IP+Port)
    package initialize
    
    import (
    	"fmt"
    	"github.com/hashicorp/consul/api"
    	_ "github.com/mbobakov/grpc-consul-resolver" // It's important
    	"go.uber.org/zap"
    	"google.golang.org/grpc"
    
    	"mxshop/user-web/global"
    	"mxshop/user-web/proto"
    )
    
    func InitSrvConn() {
    	consulInfo := global.ServerConfig.ConsulInfo
    	userConn, err := grpc.Dial(
    		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.UserSrvInfo.Name),
    		grpc.WithInsecure(),
    		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
    	)
    	if err != nil {
    		zap.S().Fatal("[InitSrvConn] 连接 【用户服务失败】")
    	}
    
    	userSrvClient := proto.NewUserClient(userConn)
    	global.UserSrvClient = userSrvClient
    }
    
    func InitSrvConn2() {
    	// 从注册中心获取到python层用户服务的信息
    	cfg := api.DefaultConfig()
    	consulInfo := global.ServerConfig.ConsulInfo
    	cfg.Address = fmt.Sprintf("%s:%d", consulInfo.Host, consulInfo.Port)
    
    	userSrvHost := ""
    	userSrvPort := 0
    	client, err := api.NewClient(cfg)
    	if err != nil {
    		panic(err)
    	}
    	
    	// 也可以使用转义符 \"%s\",用 " 代替 `
    	data, err := client.Agent().ServicesWithFilter(fmt.Sprintf(`Service == "%s"`, global.ServerConfig.UserSrvInfo.Name))
    	//data, err := client.Agent().ServicesWithFilter(fmt.Sprintf(`Service == "%s"`, global.ServerConfig.UserSrvInfo.Name))
    	if err != nil {
    		panic(err)
    	}
    	for _, value := range data {
    		userSrvHost = value.Address
    		userSrvPort = value.Port
    		break
    	}
    	if userSrvHost == "" {
    		zap.S().Fatal("[InitSrvConn] 连接 【用户服务失败】")
    		return
    	}
    
    	// 拨号连接用户grpc服务器 跨域的问题 - 后端解决 也可以前端来解决
    	userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", userSrvHost, userSrvPort), grpc.WithInsecure())
    	if err != nil {
    		zap.S().Errorw("[GetUserList] 连接 【用户服务失败】",
    			"msg", err.Error(),
    		)
    	}
    	//1. 后续的用户服务下线了 2. 改端口了 3. 改ip了 怎么办? 负载均衡来做
    	//2. 已经事先创立好了连接,这样后续就不用进行再次tcp的三次握手
    	//3. 一个连接多个groutine共用,要性能提升 - 可以使用连接池;不是很理解,一个用户难道能同时多个操作?
    	userSrvClient := proto.NewUserClient(userConn)
    	global.UserSrvClient = userSrvClient
    }
    // 记得在main中调用
    
    • grpc.Dial()也放到这,得到python层的服务信息(IP+Port)后直接建立连接
    • 两条主要流程贯穿:main-init-config-yml (viper支持);main-init-router-api/handler(global)
    • 为了方便测试可以先注释掉验证码,我们是基于内存做的,所以只是设置false不管用,除非改成基于Redis的

负载均衡

  • 先解决一个问题,动态获取随机可用端口号
    def get_free_tcp_port():
        tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        tcp.bind(("", 0))
        _, port = tcp.getsockname()
        tcp.close()
        return port
    
  • 这样我们就可以在service运行的时候不必指定端口号
    # 还是在server.py改
    def serve():
        parser = argparse.ArgumentParser()
        parser.add_argument('--ip',
                            nargs="?",
                            type=str,
                            default="192.168.0.103",
                            help="binding ip"
                            )
        parser.add_argument('--port',
                            nargs="?",
                            type=int,
                            default=0,
                            help="the listening port"
                            )
        args = parser.parse_args()
        
        if args.port == 0:
            port = get_free_tcp_port()
        else:
            port = args.port	# 服务注册的时候也要改成port
    
  • 类似的,go-web那边也要改,我们放在utils下
    package utils
    
    import (
    	"net"
    )
    
    func GetFreePort() (int, error) {
    	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
    	if err != nil {
    		return 0, err
    	}
    
    	l, err := net.ListenTCP("tcp", addr)
    	if err != nil {
    		return 0, err
    	}
    	defer l.Close()
    	return l.Addr().(*net.TCPAddr).Port,  nil
    }
    
  • go需要python的服务,port动态了,就必须靠consul的服务发现,任何层的任何服务都注册到consul,通过服务名称即可发现
  • 什么是负载均衡?
    • 这个问题说起来挺复杂的,比如我们可以将架构做成如图所示
      1
    • 因为一个网关也不能扛得住还得是集群;通过NGINX做负载均衡如今是业界公认,可以参考我的笔记
  • 还有一种方法就是进程内的load balance,将负载均衡的算法以SDK的方式实现在客户端进程内
    • 最大的区别就是没有负载均衡器,比如上图的NGINX,这样更稳一点
    • 如图,在用户-web(consumer)实现,就事先让web的机器使用协程将所有的能提供用户-srv的机器连上,然后用特定的算法在本地(提供web服务的这个机器自身)安排如何使用服务
      2
      3
    • 不太好的就是不同的语言要写不同的SDK(因为依赖web进程的协程),但grpc还是用这种主流方法
  • 还有一种改进的方法就是,将load balance算法和服务发现(连接服务)在web机器上以单独的进程实现
    • 这个方法避免了写不同的语言的SDK;当然也有缺陷,增加维护成本,还要单独监控
      4

负载均衡算法

  • 最简单的就是轮循法,挨着拿活;还有一致性hash,可以了解
    5
    6

实现

  • 还是借助grpc实现负载均衡,根据官方文档的描述,它提供了综合策略
    7
  • 均衡算法可以是第三方,也可以自己搞(最好不要使用第三方的服务)
  • 本身也没有继承服务注册中心,但是留了接口,可以指定从哪进行服务发现;而且有人写了包用于集成grpc和consul实现了负载均衡,测试一下,关键是把consul的URL配置写正确
    • 当然,用到grpc必须把那份proto拿进来,这次拨号是拨consul服务的
    • 测试代码,python层启动两个server,注意这里需要import uuid得到service_id,不然consul还是只有一个user_srv;并使用partial()on_exit()包装成其他函数
      package main
      
      import (
      	"context"
      	"fmt"
      	"log"
      	"mxshop/user-web/grpclb_test/proto"
      	// 导入却没有使用,其实底层是在import时,在init中注册到了grpc
      	_ "github.com/mbobakov/grpc-consul-resolver" // It's important
      
      	"google.golang.org/grpc"
      )
      
      func main() {
      	conn, err := grpc.Dial(
      		// 这里的名字要跟consul中注册的一样,tag也必须一样
      		"consul://192.168.109.129:8500/user-srv?wait=14s&tag=srv",
      		grpc.WithInsecure(),
      		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), // 轮询
      	)
      	if err != nil {
      		log.Fatal(err)
      	}
      	defer conn.Close()
      
      	// 为了在一次进程中连续请求,查看负载均衡的效果;如果采用多次启动,每次轮循的起点都是0,没效果
      	for i := 0; i < 10; i++ {
      		userSrvClient := proto.NewUserClient(conn)
      		rsp, err := userSrvClient.GetUserList(context.Background(), &proto.PageInfo{
      			Pn:    1,
      			PSize: 2,
      		})
      		if err != nil {
      			panic(err)
      		}
      		for index, data := range rsp.Data {
      			fmt.Println(index, data)
      		}
      	}
      
      }
      
    • 再温习一下grpc,这里单独测试使用的是Dial(cfg)+proto.NewUserClient();项目里集成使用api.NewUserClient()+cfg的形式(包含了拨号)
  • 集成到项目,更新InitSrvConn(),使用测试形式(手动拨号)
    func InitSrvConn() {
    	consulInfo := global.ServerConfig.ConsulInfo
    	userConn, err := grpc.Dial(
    		fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.UserSrvInfo.Name),
    		grpc.WithInsecure(),
    		grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
    	)
    	if err != nil {
    		zap.S().Fatal("[InitSrvConn] 连接 【用户服务失败】")
    	}
    
    	userSrvClient := proto.NewUserClient(userConn)
    	global.UserSrvClient = userSrvClient
    }
    

配置中心

  • 目前我们的配置是基于本地的配置文件,如果配置文件修改了,需要重启服务,这很不合适,所以引入了viper,可以自动监听配置文件的修改,但还是会有一些问题
    8
  • 总结起来就是:不改还行,改了必死,肯定会改,必死!所以我们使用配置中心,类似服务注册中心
  • 支持的功能:
    • 实例可以拉取配置
    • 权限控制
    • 配置回滚
    • 环境隔离
    • 搭建集群
  • 技术选型,流行的框架都是Java支持的,但是也有多语言的,比如Apollo或者nacos

nacos

  • 学会一个其他就会了,nacos是阿里做的
  • 安装还是使用docker,如果懂点Java也可以手动安装,执行命令
    docker run --name nacos-standalone -e MODE=standalone -e JVM_XMS=512m -e JVM_XMX=512m -e JVM_XMN=256m -p 8848:8848 -d nacos/nacos-server:latest
    # 访问:192.168.109.129:8848/nacos,nacos/nacos
    # 注意上面那个mode要大写,不然可能
    
    9
  • 登录之后尝试使用,有一些概念
    • 命名空间:可以隔离配置集,一般一个微服务一个命名空间
    • 组:就是命名空间内,区分测试配置和生产环境配置的
    • dataid:一个配置集,其实就是一个配置文件
      10
  • 集成到python层
    • 直接安装pip install nacos-sdk-python,因为同步到pypi了

    • 把settings中的字段加上去;使用json很方便,所以我们在nacos尽量使用json;user-srv.json,分group dev/pro

      {
          "name": "user-srv",
          "tags": ["Roy", "mxshop", "python"],
          "mysql": {
              "db": "mxshop_user_srv",
              "host": "192.168.109.129",
              "port": 3306,
              "user": "root",
              "psd": "root"
          },
          "consul": {
              "host": "192.168.109.129",
              "port": 8500
          }
      }
      
    • settings就根据namespace+group+dataid定位并拉取配置,用data赋值

      import json
      
      import nacos
      from playhouse.pool import PooledMySQLDatabase
      from playhouse.shortcuts import ReconnectMixin
      from loguru import logger
      
      
      # 使用peewee的连接池, 使用ReconnectMixin来防止出现连接断开查询失败
      class ReconnectMysqlDatabase(ReconnectMixin, PooledMySQLDatabase):
          pass
      
      
      NACOS = {
          "Host": "192.168.109.129",
          "Port": 8848,
          "NameSpace": "c1872978-d51c-4188-a497-4e0cd20b97d5",	# 新建user namespace
          "User": "nacos",
          "Password": "nacos",
          "DataId": "user-srv.json",
          "Group": "dev"
      }
      
      # 这个client就是NacosClient的实例,包含了我们需要的方法
      client = nacos.NacosClient(f'{NACOS["Host"]}:{NACOS["Port"]}', namespace=NACOS["NameSpace"],
                                 username=NACOS["User"],
                                 password=NACOS["Password"])
      
      # get config
      data = client.get_config(NACOS["DataId"], NACOS["Group"])
      data = json.loads(data)
      logger.info(data)
      
      # 这里
      def update_cfg(args):
          print("配置产生变化")
          print(args)
      
      # consul的配置
      CONSUL_HOST = data["consul"]["host"]
      CONSUL_PORT = data["consul"]["port"]
      
      # 服务相关的配置
      SERVICE_NAME = data["name"]
      SERVICE_TAGS = data["tags"]
      
      DB = ReconnectMysqlDatabase(data["mysql"]["db"], host=data["mysql"]["host"], port=data["mysql"]["port"],
                                  user=data["mysql"]["user"], password=data["mysql"]["password"])
      
    • 这里的update_cfg()函数是添加watcher时使用的,如果监听到config变化,会执行这个函数;官网有介绍:add_config_watchers(data_id, group, cb_list)

    • 这里不能直接在settings添加,会报错:
      11

      # serve.py
      if __name__ == '__main__':
          logging.basicConfig()
          settings.client.add_config_watcher(settings.NACOS["DataId"], settings.NACOS["Group"], settings.update_cfg)
          serve()
      
  • 集成到go-web层(先去nacos发布,这里搞成yaml也行哈),基本上跟着官网的代码就很可以
    • go本身支持json(json-tag),所以我们就把yaml转换一下;
    • 之前的配置字段都放在config/config.go(而且变成了struct方便操作);现在拉取json字符串后想要解析成对应的struct,当然要在struct中加上json-tag(之前的mapstructure:"name"是针对viper的)
    • 然后定义NacosConfig的struct,viper只需要用本地配置文件实例化它就可以
      type NacosConfig struct {
      	Host      string `mapstructure:"host"`
      	Port      uint64    `mapstructure:"port"`
      	Namespace string `mapstructure:"namespace"`
      	User      string `mapstructure:"user"`
      	Password  string `mapstructure:"password"`
      	DataId    string `mapstructure:"dataid"`
      	Group     string `mapstructure:"group"`
      }
      
      // config-debug.yaml 本地配置文件
      host: '192.168.0.104'
      port: 8848
      namespace: 'c1872978-d51c-4188-a497-4e0cd20b97d5'
      user: 'nacos'
      password: 'nacos'
      dataid: 'user-web.json'
      group: 'dev'
      
    • 更新init/config.go;前面的ReadConfig目的是获取Nacos的连接信息,获取配置content
      import (
      	"github.com/nacos-group/nacos-sdk-go/clients"
      	"github.com/nacos-group/nacos-sdk-go/common/constant"
      	"github.com/nacos-group/nacos-sdk-go/vo"
      )
      
      func InitConfig() {
      	debug := GetEnvInfo("IS_DEBUG")
      	configFilePrefix := "config"
      	configFileName := fmt.Sprintf("user-web/%s-pro.yaml", configFilePrefix)
      	if debug {
      		configFileName = fmt.Sprintf("user-web/%s-debug.yaml", configFilePrefix)
      	}
      
      	v := viper.New()
      	// 文件的路径如何设置
      	v.SetConfigFile(configFileName)
      	if err := v.ReadInConfig(); err != nil {
      		panic(err)
      	}
      	// 这个对象如何在其他文件中使用 - 全局变量
      	if err := v.Unmarshal(global.NacosConfig); err != nil { // 解析nacos的连接信息,赋值给NacosConfig
      		panic(err)
      	}
      	zap.S().Infof("配置信息存放在:: &v", global.NacosConfig)
      
      	// 从nacos中读取配置信息
      	sc := []constant.ServerConfig{
      		{
      			IpAddr: global.NacosConfig.Host,
      			Port:   global.NacosConfig.Port,
      		},
      	}
      
      	cc := constant.ClientConfig{
      		NamespaceId:         global.NacosConfig.Namespace, // 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId
      		TimeoutMs:           5000,
      		NotLoadCacheAtStart: true,
      		LogDir:              "tmp/nacos/log",
      		CacheDir:            "tmp/nacos/cache",
      		RotateTime:          "1h",
      		MaxAge:              3,
      		LogLevel:            "debug",
      	}
      
      	configClient, err := clients.CreateConfigClient(map[string]interface{}{
      		"serverConfigs": sc,
      		"clientConfig":  cc,
      	})
      	if err != nil {
      		panic(err)
      	}
      
      	content, err := configClient.GetConfig(vo.ConfigParam{
      		DataId: global.NacosConfig.DataId,
      		Group:  global.NacosConfig.Group})
      
      	if err != nil {
      		panic(err)
      	}
      	//fmt.Println(content) //字符串 - yaml
      	// 想要将一个json字符串转换成struct,需要到struct设置json-tag
      	// 再用拉取到的content,赋值项目的配置信息
      	err = json.Unmarshal([]byte(content), &global.ServerConfig)
      	if err != nil {
      		zap.S().Fatalf("读取nacos配置失败: %s", err.Error())
      	}
      	fmt.Println(&global.ServerConfig)
      }
      
    • 记得在mx-shop下新建tmp/nacos/log和tmp/nacos/cache目录,存放缓存,也便于回滚
  • 接下来就要继续完善其他服务了
  • 先开始Java-Web
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞士_R

修行不易...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值