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

news2024/9/30 21:27:21

文章目录

  • 服务发现
    • 集成consul
  • 负载均衡
    • 负载均衡算法
    • 实现
  • 配置中心
    • nacos

服务发现

  • 我们之前的架构是通过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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8年软件测试工程师经验感悟

不知不觉在软件测试行业&#xff0c;野蛮生长了8年之久。这一路上拥有了非常多的感受。有迷茫&#xff0c;有踩过坑&#xff0c;有付出有收获&#xff0c; 有坚持&#xff01; 我一直都在软件测试行业奋战&#xff0c; 毕业时一起入职的好友已经公司内部转岗&#xff0c;去选择…

理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?

在面向对象编程中、有一条非常经典的设计原则&#xff0c;那就是&#xff1a;组合优于继承&#xff0c;多用组合少用继承。为什么不推荐使用继承&#xff1f;组合相比继承有哪些优势&#xff1f;如何判断该用组合还是继承&#xff1f;今天,我们就围绕着这三个问题&#xff0c;来…

Windows中安装Docker Desktop 4.16.3(当前最新版本)

前言 docker是一个用Go语言实现的开源项目&#xff0c;它可以很方便的创建和使用容器&#xff0c;docker将程序以及程序所有的依赖都打包到docker container&#xff0c;这样程序可以在任何环境都会有相同的表现&#xff0c;这里程序运行的依赖也就是容器类似集装箱&#xff0…

用于超大图像的训练策略:Patch Gradient Descent

前言 本文旨在计算和内存限制的情况下&#xff0c;解决在大规模图像上训练现有CNN 架构的问题。提出PatchGD&#xff0c;它基于这样的假设&#xff1a;与其一次对整个图像执行基于梯度的更新&#xff0c;不如一次只对图像的一小部分执行模型更新&#xff0c;确保其中的大部分是…

scscanner:一款功能强大的大规模状态码扫描工具

关于scscanner scscanner是一款功能强大的大规模状态码扫描工具&#xff0c;该工具可以帮助广大研究人员从一个URL列表文件中批量读取目标网站的状态码响应信息。除此之外&#xff0c;该工具还可以过滤出指定的状态码&#xff0c;并将结果存储到一个文件中以供后续深入分析使用…

Idea软件——Debug使用方法

Idea软件——Debug 1 Debug概述 Debug:是供程序员使用的程序测试工具&#xff0c;它可以用于查看程序和执行流程&#xff0c;也可以用于追踪程序执行过程来调试程序 1.2 Debug操作流程 Debug调试&#xff1a;又叫断点调试&#xff0c;断点其实是一个标记&#xff0c;告诉从何看…

实战项目-用户评论数据情绪分析

目录1、基于词典的方法2、基于词袋或 Word2Vec 的方法2.1 词袋模型2.2 Word2Vec3、案例&#xff1a;用户评论情绪分析3.1 数据读取3.2 语料库分词处理3.3 Word2Vec 处理3.4 训练情绪分类模型3.5 对评论数据进行情绪判断目的&#xff1a;去判断一段文本、评论的情绪偏向在这里&a…

九龙证券|一夜暴跌36%,美股走势分化,标普指数创近2月最差周度表现

当地时间2月10日&#xff0c;美股三大指数收盘涨跌纷歧。道指涨0.5%&#xff0c;标普500指数涨0.22%&#xff0c;纳指跌0.61%。 受国际油价明显上升影响&#xff0c;动力板块领涨&#xff0c;埃克森美孚、康菲石油涨超4%。大型科技股走低&#xff0c;特斯拉、英伟达跌约5%。热门…

[ 系统安全篇 ] window 命令禁用用户及解禁方法

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

MC0108白给-MC0109新河妇荡杯

MC0108白给 小码哥和小码妹在玩一个游戏&#xff0c;初始小码哥拥有 x的金钱&#xff0c;小码妹拥有 y的金钱。 虽然他们不在同一个队伍中&#xff0c;但他们仍然可以通过游戏的货币系统进行交易&#xff0c;通过互相帮助以达到共赢的目的。具体来说&#xff0c;在每一回合&a…

3.JUC【Java面试第三季】

3.JUC【Java面试第三季】前言推荐3.JUC06_闲聊AQS面试1.题目说明07_可重入锁理论2.可重入锁说明“可重入锁”这四个字分开来解释可重入锁的种类08_可重入锁的代码验证-上09_可重入锁的代码验证-下3.LockSupport10_LockSupport是什么LockSupport是什么11_waitNotify限制线程等待…

(C语言)程序环境和预处理

问&#xff1a;1. 什么是C语言的源代码&#xff1f;2. 由于计算机只认识什么&#xff1f;因此它只能接收与执行什么&#xff1f;也就是什么&#xff1f;3. 在ANSI C的任何一种实现中&#xff0c;存在哪两个不同的环境&#xff1f;在这两种环境里面分别干什么事情&#xff1f;4.…

一款非常不错的微信系统垃圾清理工具:微信清理大师,操作简单,清除较快。

微信清理大师 微信清理大师是一款专为微信所推出的系统垃圾清理工具。它的功能十分强大&#xff0c;可快捷清理微信内储存垃圾文件&#xff0c;操作十分简单&#xff0c;只需要轻轻一点&#xff0c;即可删除清理。 功能特点&#xff1a; 【一键清理】一键搞定无用垃圾&#x…

【计算机网络】Linux环境中的TCP网络编程

文章目录前言一、TCP Socket API1. socket2. bind3. listen4. accept5. connect二、封装TCPSocket三、服务端的实现1. 封装TCP通用服务器2. 封装任务对象3. 实现转换功能的服务器四、客户端的实现1. 封装TCP通用客户端2. 实现转换功能的客户端五、结果演示六、多进程版服务器七…

Kubernetes + Docker 部署一个yolov5检测服务(基于FastDeploy)

Kubernetes Docker 从零部署一个yolov5检测服务&#xff0c;服务基于PaddlePaddle/FastDeploy的服务化部署&#xff1b;所有软件从零安装。 文章目录1.说明2.环境3.安装过程 3.1安装 Docker 3.2安装 minikube 3.3安装 Kubectl4.部署过程 4.1 Docker相关 4.2 k8s相关 4.3 启动服…

开发必备技术--docker(使用篇)

文章目录前言Docker的基本概念概念数据卷虚拟网络镜像操作镜像名称镜像命令容器操作基本操作容器创建数据卷操作创建和查看数据卷其他指令实战前言 续接上一篇博文&#xff1a; 开发必备技术–docker&#xff08;一&#xff09; 这也是开学了&#xff0c;假期的最后一篇博文&a…

minio下载文件速度很慢的原因分析与说明

文章目录1.实战背景2.问题描述3.问题分析4.问题解决1.实战背景 最近在做一个项目&#xff0c;需要用到minio来搭建文件系统&#xff0c;先简单说一下我在项目中设置的上传文件流程&#xff1a; 前端将分块文件逐一传给后端&#xff0c;后端再存储到 linux服务器的minio 当中。…

JAVA集合专题3 —— vector + LinkedList + Set

目录vector的特点LinkedList底层结构模拟双向链表比较ArrayList和LinkedListSet接口基本介绍Set接口的遍历方式Set接口实现类对象的特点Set接口实现类HashSet模拟HashSet/HashMap的底层结构vector的特点 Vector底层是一个对象数组Vector是线程同步的&#xff0c;即线程安全的&…

保姆级 | ChatGPT接入微信教程

文章目录 0x00 前言 0x01 环境说明 0x02 准备工作 0x03 报错 Not available 解决方法 0x04 登录Open AI账号 0x05 获取账号API 0x06 配置阿里云开源项目 0x07 OpenAI接入微信 0x08 ChatGPT微信使用演示 0x09 参考文献 0x10 总结 0x00 前言 ChatGPT 美国 OpenAI 研发…

使用 Sahi 实现 Web 自动化测试

Sahi 是 Tyto Software 旗下的一个基于业务的开源 Web 应用自动化测试工具。Sahi 运行为一个代理服务器&#xff0c;并通过注入 JavaScript 来访问 Web 页面中的元素。Sahi 支持 HTTPS 并且独立于 Web 站点&#xff0c;简单小巧却功能强大。它相对于 Selenium 等自动化测试工具…