文章目录
- 服务发现
- 集成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 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的会复杂一些,稍后整~
# 配置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通
- 配置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 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,通过服务名称即可发现
- 什么是负载均衡?
- 这个问题说起来挺复杂的,比如我们可以将架构做成如图所示
- 因为一个网关也不能扛得住还得是集群;通过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 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,可以自动监听配置文件的修改,但还是会有一些问题
- 总结起来就是:不改还行,改了必死,肯定会改,必死!所以我们使用配置中心,类似服务注册中心
- 支持的功能:
- 实例可以拉取配置
- 权限控制
- 配置回滚
- 环境隔离
- 搭建集群
- 技术选型,流行的框架都是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 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添加,会报错:
# 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