文章目录
- 服务发现
- 集成consul
- 负载均衡
- 负载均衡算法
- 实现
- 配置中心
- nacos
服务发现
- 我们之前的架构是通过ip+port来调用的python的API,这样做的弊端是
- 如果新加一个服务,就要到某个服务改web(go)层的调用代码,配置IP/Port
- 并发过高需要加机器时,其他服务也要重新部署,否则找不到新加的node
- 如何解决这些问题呢?我们一般将多个服务进行注册
- 所有服务注册到注册中心,获取其他服务时通过这里拉取配置信息;(grpc也就是通过这配置的,传输序列化信息),其实就是将IP+Port放到这统一管理
- 注册中心必须有健康检查的功能,所以不能使用Redis
- 服务网关是对接用户的屏障,但也要能和注册中心交流,才能路由到具体服务
- 技术选型
- 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强)
-
consul提供DNS功能(域名创建/解析),就是可以自定义域名;我们目前写的服务都可以通过ip+port方式注册和拉取,但如果有其他服务,不想通过consul,但希望能拉取各服务,这就需要consul能将自己和各服务域名化,方便外面的服务调用!
-
可以用
dig
命令查看consul的DNS功能,dig @ip -p 8600 cosul1.service.consul SRV
;就是在服务名后跟上service.consul,类似www.baidu.comyum install bind-utils
-
- consul的API接口
- 注册服务;使用PUT请求,路径也指定了,然后就是传递指定的参数了(json格式)
- 类似的,可以删除服务、配置健康检查
- 写个测试,先直接使用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的会复杂一些,稍后整~
# 配置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通
- 配置GRPC的健康检查,假设我们要给python层的user-srv配置
- 参考官方文档,我们需要使用proto文件生成相关的代码;下面是生成好的代码,主要是实现proto文件中的
Check
和Watch
方法
- 当然是注册到当前的服务
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通;测试一下
- 参考官方文档,我们需要使用proto文件生成相关的代码;下面是生成好的代码,主要是实现proto文件中的
- 注册服务;使用PUT请求,路径也指定了,然后就是传递指定的参数了(json格式)
- 上面是容器化的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,通过服务名称即可发现
- 什么是负载均衡?
- 这个问题说起来挺复杂的,比如我们可以将架构做成如图所示
- 因为一个网关也不能扛得住还得是集群;通过NGINX做负载均衡如今是业界公认,可以参考我的笔记
- 这个问题说起来挺复杂的,比如我们可以将架构做成如图所示
- 还有一种方法就是进程内的load balance,将负载均衡的算法以SDK的方式实现在客户端进程内
- 最大的区别就是没有负载均衡器,比如上图的NGINX,这样更稳一点
- 如图,在用户-web(consumer)实现,就事先让web的机器使用协程将所有的能提供用户-srv的机器连上,然后用特定的算法在本地(提供web服务的这个机器自身)安排如何使用服务
- 不太好的就是不同的语言要写不同的SDK(因为依赖web进程的协程),但grpc还是用这种主流方法
- 还有一种改进的方法就是,将load balance算法和服务发现(连接服务)在web机器上以单独的进程实现
- 这个方法避免了写不同的语言的SDK;当然也有缺陷,增加维护成本,还要单独监控
- 这个方法避免了写不同的语言的SDK;当然也有缺陷,增加维护成本,还要单独监控
负载均衡算法
- 最简单的就是轮循法,挨着拿活;还有一致性hash,可以了解
实现
- 还是借助grpc实现负载均衡,根据官方文档的描述,它提供了综合策略
- 均衡算法可以是第三方,也可以自己搞(最好不要使用第三方的服务)
- 本身也没有继承服务注册中心,但是留了接口,可以指定从哪进行服务发现;而且有人写了包用于集成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,可以自动监听配置文件的修改,但还是会有一些问题
- 总结起来就是:不改还行,改了必死,肯定会改,必死!所以我们使用配置中心,类似服务注册中心
- 支持的功能:
- 实例可以拉取配置
- 权限控制
- 配置回滚
- 环境隔离
- 搭建集群
- 技术选型,流行的框架都是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要大写,不然可能
- 登录之后尝试使用,有一些概念
- 命名空间:可以隔离配置集,一般一个微服务一个命名空间
- 组:就是命名空间内,区分测试配置和生产环境配置的
- dataid:一个配置集,其实就是一个配置文件
- 集成到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添加,会报错:
# 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