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

news/2024/11/20 0:36:04/

文章目录

  • 服务发现
    • 集成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 requestsheaders = {"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-webregister("mxshop-go", "mxshop-web", "192.168.109.1", 8021)# deregister("mshop-web")# 然后给它配上健康检查filter_service("mxshop-go") # 传name
      
      // go-web
      package initializeimport ("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 consulc = 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 mainimport ("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 = nameregistration.ID = idregistration.Port = portregistration.Tags = tagsregistration.Address = addressregistration.Check = checkerr = 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"`)	// 写死了sorryif 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 initializeimport ("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.ConsulInfouserConn, 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.ConsulInfocfg.Address = fmt.Sprintf("%s:%d", consulInfo.Host, consulInfo.Port)userSrvHost := ""userSrvPort := 0client, 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.AddressuserSrvPort = value.Portbreak}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 utilsimport ("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 mainimport ("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.ConsulInfouserConn, 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 jsonimport nacos
      from playhouse.pool import PooledMySQLDatabase
      from playhouse.shortcuts import ReconnectMixin
      from loguru import logger# 使用peewee的连接池, 使用ReconnectMixin来防止出现连接断开查询失败
      class ReconnectMysqlDatabase(ReconnectMixin, PooledMySQLDatabase):passNACOS = {"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的连接信息,赋值给NacosConfigpanic(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,它们有不同的NamespaceIdTimeoutMs:           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.ppmy.cn/news/24199.html

相关文章

结构体变量

C语言允许用户自己建立由不同类型数据组成的组合型的数据结构&#xff0c;它称为结构体&#xff08;structre&#xff09;。 在程序中建立一个结构体类型&#xff1a; 1.结构体 建立结构体 struct Student { int num; //学号为整型 char name[20]; //姓名为字符串 char se…

English Learning - Day5 L1考前复习 2023.2.10 周五

English Learning - Day5 L1考前复习 2023.2.10 周五1 单选题&#xff1a;She has the face _________.2 单选题&#xff1a; The goals ________ he fought all his life no longer seemed important to him.3 单选题&#xff1a;Sales director is a position ______ communi…

有趣的Hack-A-Sat黑掉卫星挑战赛——定位卫星Jackson

国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加&#xff0c;太空已经成为国家赖以生存与发展的命脉之一&#xff0c;凝聚着巨大的国家利益&#xff0c;太空安全的重要性日益凸显[1]。而在信息化时代&#xff0c;太空安…

电子电路中的各种接地(接地保护与GND)

前言多年以前&#xff0c;雷雨天气下&#xff0c;建筑会遭遇雷击&#xff0c;从而破坏建筑以及伤害建筑内的人&#xff0c;为了避免雷击的伤害&#xff0c;人们发明了避雷针&#xff0c;并将避雷针接地线&#xff0c;从而引导雷击产生的电流经过地线流入到地下。地线&#xff1…

事务基础知识与执行计划

事务基础知识 数据库事务的概念 数据库事务是什么&#xff1f; 事务是一组原子性的SQL操作。事务由事务开始与事务结束之间执行的全部数据库操作组成。A&#xff08;原子性&#xff09;、&#xff08;C一致性&#xff09;、I&#xff08;隔离性&#xff09;、D&#xff08;持久…

Vue3 如何实现一个函数式右键菜单(ContextMenus)

前言: 最近在公司 PC 端的项目中使用到了右键出现菜单选项这样的一个工作需求&#xff0c;并且自己现在也在实现一个偶然迸发的 idea&#xff08; 想用前端实现一个 windows 系统从开机到桌面的 UI&#xff09;&#xff0c;其中也要用到右键弹出菜单这样的一个功能&#xff0c;…

【C++】模板初阶STL简介

今天&#xff0c;你内卷了吗&#xff1f; 文章目录一、泛型编程二、函数模板&#xff08;显示实例化和隐式实例化&#xff09;1.函数模板格式2.单参数模板3.多参数模板4.模板参数的匹配原则三、类模板&#xff08;没有推演的时机&#xff0c;统一显示实例化&#xff09;1.类模…

NSSCTF Round#8 Basic

from:http://v2ish1yan.top MyDoor 使用php伪协议读取index.php的代码 php://filter/readconvert.base64-encode/resourceindex.php<?php error_reporting(0);if (isset($_GET[N_S.S])) {eval($_GET[N_S.S]); }if(!isset($_GET[file])) {header(Location:/index.php?fi…