首页 文章 精选 留言 我的

精选列表

搜索[天玑9500],共10000篇文章
优秀的个人博客,低调大师

Hadoop 学习第三天之集群安装(上)

Hadoop 学习之集群安装(上) 将配置的伪分布模式的虚拟机克隆两份并更名为centos_node2和centos_note3 分别打开三个虚拟机,第一个基本不需要配置了,现在主要配置centos_note2和centos_note3的配置。 1.首先修改IP地址和主机名 因为centos是hadoop1和192.168.100.11,在这里设置centos_note2和centos_note3的主机名和IP地址分别为:hadoop2/192.168.100.12和hadoop3/192.168.100.13。并把主机名和ip地址绑定 centos_note2的修改主机名: #vim /etc/sysconfig/network 下的HOSTNAME=hadoop2,centos_note3的相应的改为hadoop3 再通过#hostnamehadoop2。设置临时的主机名那么机器就不用重新启动了。 Note2的修改ip地址:在右上角点编辑链接,然后编辑将ipv4改为手动,地址192.168.100.12 掩码:255.255.255.0 网关:192.168.100.1。 Note3的改为相应的ip 绑定ip和主机名: #vim /etc/hosts Ip hostname 192.168.100.12 hadoop2 保存并重启网络服务: #service network restart 在宿主机上ping以下看是否三台机器都能ping通。一般情况都能通的。只要和宿主机上的VMnet1网关在同一网段一般没问题。 通过pietty远程连接三台虚拟机,进行下一步的操作 2.将note2和note3中配置数据重置 a)删掉远程连接ssh免登陆: #rm –rf /root/.ssh/* b)删掉原来的hadoop安装程序 #rm –rf /usr/local/*(如果该目录下还有其他的有用数据,则只删除hadoop的即可) c)删掉环境变量中的hadoop和jdk的配置 #vim /etc/profile 将JAVA_HOME和HADOOP_HOM的那三行都删掉 #source /etc/profile 使得环境变量配置文件重新生效 3.重新配置三台虚拟机,搭建集群环境前的系统环境 a)配置每台机器上的ssh免密码登录(以主机名为hadoop1的centos为例) #ssh-keygen –t rsa 一路回车,在/root/.ssh/下生成id_rsa.pub 生成公钥信息 #cp id_rsa.pubauthorized_keys (不能写错否则就不能免密码登录了) #登录本机,免密码登录 #ssh hadoop1 就能免密码登录了 其他两台也是同样的操作 b)三台机器间的免密码登录 将自己的公钥复制到允许需要免密码登录的其他主机上 #ssh-copy-id -i hadoop2 输入hadoop2的密码 下次就可以免密码登录了。 如果主机名和ip没有绑定会出现这样的错误 在hadoop2和hadoop3上分别执行 ##ssh-copy-id -iip(hadoop1的ip) 完事后hadoop2和hadoop3就可以免密码登录hadoop1了。 将hadoop1下的ssh免密码许可文件复制到hadoop2和hadoop3上 #scp /root/.ssh/authorized_keys root@hadoop2:/root/.ssh/ 如果之前做过hadoop1到相应主机的免密码登录就不需要密码,没做过就需要输入密码。分别复制完成后,现在三台虚拟机之间就可以互相免密码登录了,但还不能使用主机名登录,现在讲ip和主机名绑定的hosts文件也在三台虚拟机之间同步 #scp /etc/hostsroot@hadoop2:/etc/hosts 这样三台虚拟机之间就可以通过主机名相互免密码登录了。 c)为hadoop2和hadoop3复制安装文件和配置文件 前提:为了保持干净和便于查找错误,将hadoop下的logs和tmp目录删除 #cd /usr/local/hadoop/ #rm –rf ./{logs/,tmp/} 将jdk和hadoop安装文件复制都hadoop2和hadoop3 # scp -r /usr/local/{jdk,hadoop} hadoop2:/usr/local/ # scp -r /usr/local/{jdk,hadoop} hadoop3:/usr/local/ 将profile配置文件复制到hadoop2和hadoop3 # scp -r /etc/profile hadoop2:/etc/ 在每个节点使profile生效: #source /etc/profile 4.搭建集群环境 a)修改hadoop1下的hadoop的配置文件slaves,将另两台主机名加进去作为从节点,hadoop1作为主节点。每个主机名占一行 b)启动hadoop集群环境 i.先格式化namenode #hadoop namenote –format ii.启动hadoop集群 #start-all start 这样在主节点上就启动了是哪个进程NameNode、JobTracker、SecondarynameNode。在从节点启动了DataNode和TaskTracker 点击Live Bodes

优秀的个人博客,低调大师

Hadoop 学习第三天之集群安装(下)

Hadoop 学习之集群安装(下) Hadoop集群的其他配置步骤(可选) 1.将secondaryNameNode和NameNode及JobTracker分开,放在不同的主机上 修改hadoop的配置目录conf下的masters root@conf#vim master 将secondaryNameNode放在hadoop2上 重新启动服务 2.将NameNode和JobTracker放在不同的主机上 修改的代价比较大,最好在机器搭建时就规划好 修改nameNode的地址,如下图的红框中的value中的hadoop1为其他机器的即可 修改Jobtracker的地址,但必须在每个从节点都配置为一样的参数 修改后需要在所在节点单独起进程 PS:对于配置文件core-site.xml和mapred-site.xml在所有节点都是相同的内容,所以说要在hadoop集群搭建之时就要规划好,然后复制出从节点时这两个文件就不用修改了 3.动态增加一个hadoop从节点 3.1配置新节点的环境,如上的配置步骤:在主节点hadoop1 #vim /usr/local/hadoop/conf/slaves 添加一个节点hadoop1 3.2在新的节点使用hadoop的hadoop-daemon.sh 分别动态扫描datanote和tasktracker #hadoop-daemon.sh start datanote #hadoop-daemon.sh start tasktracker 进程存在,先杀掉该进程 #kill -9 2458 再次执行上面两步 3.3在主节点hadoop1执行脚本:hadoop dfsadmin –refreshNodes 4.动态删除一个hadoop从节点 4.1直接关闭一个从几点 #kill -9 dataNode的进程id 5.Hadoop的安全模式 5.1安全模式下只可查询,不能增删改 5.2在集群启动时会有30秒的安全模式时间 5.3进入安全模式和查看安全模式状态 查询安全模式状态 #hadop dfsadmin –safemode get 进入安全模式 #hadop dfsadmin –safemode enter 离开安全模式 #hadop dfsadmin –safemode leave

优秀的个人博客,低调大师

带你十天轻松搞定 Go 微服务系列(七)

序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务 订单服务 支付服务 RPC 服务 Auth 验证(本文) 服务监控 链路追踪 分布式事务 期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。 完整示例代码:https://github.com/nivin-studio/go-zero-mall 首先,我们来看一下整体的服务拆分图: 7 RPC服务 Auth 验证 在前面几章我们已经分别实现了 user product order pay 的 rpc 服务和 api 服务。在 api 服务中我们使用 go-zero 框架自带的 jwt 实现鉴权验证。那么接下里我们就说说 rpc 服务的 auth 验证。 go-zero 框架 rpc 服务的 auth 验证原理是,客户端访问 rpc 服务需要携带 App 标识以及 Token 值,rpc 服务会从指定的 Redis 服务中验证 App 标识和 Token 值是否正确。所以客户端的 App 标识,Token 值,是需要提前打入 Redis 服务中。 7.1 开启 rpc 服务 auth 验证 下面我们以 user rpc 服务,和 user api 服务为例,来开启并使用 rpc 服务的 auth 验证 进入服务工作区 $ cd mall/service/user 修改 user rpc user.yaml 配置文件 $ vim rpc/etc/user.yaml Name: user.rpc ListenOn: 0.0.0.0:9000 ... Auth: true # 是否开启 Auth 验证 StrictControl: true # 是否开启严格模式 Redis: # 指定 Redis 服务 Key: rpc:auth:user # 指定 Key 应为 hash 类型 Host: redis:6379 Type: node Pass: 修改 user api user.yaml 配置文件 $ vim api/etc/user.yaml Name: User Host: 0.0.0.0 Port: 8000 ... UserRpc: App: userapi # App 标识 Token: 6jKNZbEpYGeUMAifz10gOnmoty3TV # Token 值 Etcd: Hosts: - etcd:2379 Key: user.rpc 将 App 标识, Token 值写入 Redis 服务 App 标识作为 rpc 指定 key 的 hash key,Token 值作为 hash key 的值。 重启 user rpc 服务 $ cd mall/service/user/rpc $ go run user.go -f etc/user.yaml Starting rpc server at 127.0.0.1:9000... 重启 user api 服务 $ cd mall/service/user/api $ go run user.go -f etc/user.yaml Starting server at 0.0.0.0:8000... 7.2 调试 rpc 服务 auth 验证 访问 user api 的 login 接口,我们可以看到接口能正常的返回结果值。 那么我们修改,user api user.yaml 配置文件中的 Token 值再次请求接口试试。 提示:修改 yaml 配置文件需要重启服务才有效 我们可以从返回的结果中看出,rpc 服务报错了,未经认证,拒绝访问。 大家可以自己再尝试修改 user rpc user.yaml 配置文件中 StrictControl 为 false 看看效果。 项目地址 https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

优秀的个人博客,低调大师

带你十天轻松搞定 Go 微服务系列(六)

序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务 订单服务 支付服务(本文) RPC 服务 Auth 验证 服务监控 链路追踪 分布式事务 期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。 完整示例代码:https://github.com/nivin-studio/go-zero-mall 首先,我们来看一下整体的服务拆分图: 6 支付服务(pay) 进入服务工作区 $ cd mall/service/pay 6.1 生成 pay model 模型 创建 sql 文件 $ vim model/pay.sql 编写 sql 文件 CREATE TABLE `pay` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', `oid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '订单ID', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品金额', `source` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付方式', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_uid` (`uid`), KEY `idx_oid` (`oid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 运行模板生成命令 $ goctl model mysql ddl -src ./model/pay.sql -dir ./model -c 6.2 生成 pay api 服务 创建 api 文件 $ vim api/pay.api 编写 api 文件 type ( // 支付创建 CreateRequest { Uid int64 `json:"uid"` Oid int64 `json:"oid"` Amount int64 `json:"amount"` } CreateResponse { Id int64 `json:"id"` } // 支付创建 // 支付详情 DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Oid int64 `json:"oid"` Amount int64 `json:"amount"` Source int64 `json:"source"` Status int64 `json:"status"` } // 支付详情 // 支付回调 CallbackRequest { Id int64 `json:"id"` Uid int64 `json:"uid"` Oid int64 `json:"oid"` Amount int64 `json:"amount"` Source int64 `json:"source"` Status int64 `json:"status"` } CallbackResponse { } // 支付回调 ) @server( jwt: Auth ) service Pay { @handler Create post /api/pay/create(CreateRequest) returns (CreateResponse) @handler Detail post /api/pay/detail(DetailRequest) returns (DetailResponse) @handler Callback post /api/pay/callback(CallbackRequest) returns (CallbackResponse) } 运行模板生成命令 $ goctl api go -api ./api/pay.api -dir ./api 6.3 生成 pay rpc 服务 创建 proto 文件 $ vim rpc/pay.proto 编写 proto 文件 syntax = "proto3"; package payclient; option go_package = "pay"; // 支付创建 message CreateRequest { int64 Uid = 1; int64 Oid = 2; int64 Amount = 3; } message CreateResponse { int64 id = 1; } // 支付创建 // 支付详情 message DetailRequest { int64 id = 1; } message DetailResponse { int64 id = 1; int64 Uid = 2; int64 Oid = 3; int64 Amount = 4; int64 Source = 5; int64 Status = 6; } // 支付详情 // 支付详情 message CallbackRequest { int64 id = 1; int64 Uid = 2; int64 Oid = 3; int64 Amount = 4; int64 Source = 5; int64 Status = 6; } message CallbackResponse { } // 支付详情 service Pay { rpc Create(CreateRequest) returns(CreateResponse); rpc Detail(DetailRequest) returns(DetailResponse); rpc Callback(CallbackRequest) returns(CallbackResponse); } 运行模板生成命令 $ goctl rpc proto -src ./rpc/pay.proto -dir ./rpc 6.4 编写 pay rpc 服务 6.4.1 修改配置文件 修改 pay.yaml 配置文件 $ vim rpc/etc/pay.yaml 修改服务监听地址,端口号为0.0.0.0:9003,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置 Name: pay.rpc ListenOn: 0.0.0.0:9003 Etcd: Hosts: - etcd:2379 Key: pay.rpc Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node Pass: 6.4.2 添加 pay model 依赖 添加 Mysql 服务配置,CacheRedis 服务配置的实例化 $ vim rpc/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf } 注册服务上下文 pay model 的依赖 $ vim rpc/internal/svc/servicecontext.go package svc import ( "mall/service/pay/model" "mall/service/pay/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx" ) type ServiceContext struct { Config config.Config PayModel model.PayModel } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, PayModel: model.NewPayModel(conn, c.CacheRedis), } } 6.4.3 添加 user rpc,order rpc 依赖 添加 user rpc, order rpc 服务配置 $ vim rpc/etc/pay.yaml Name: pay.rpc ListenOn: 0.0.0.0:9003 Etcd: Hosts: - etcd:2379 Key: pay.rpc ... UserRpc: Etcd: Hosts: - etcd:2379 Key: user.rpc OrderRpc: Etcd: Hosts: - etcd:2379 Key: order.rpc 添加 user rpc, order rpc 服务配置的实例化 $ vim rpc/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf UserRpc zrpc.RpcClientConf OrderRpc zrpc.RpcClientConf } 注册服务上下文 user rpc, order rpc 的依赖 $ vim rpc/internal/svc/servicecontext.go package svc import ( "mall/service/order/rpc/orderclient" "mall/service/pay/model" "mall/service/pay/rpc/internal/config" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config PayModel model.PayModel UserRpc userclient.User OrderRpc orderclient.Order } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, PayModel: model.NewPayModel(conn, c.CacheRedis), UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), OrderRpc: orderclient.NewOrder(zrpc.MustNewClient(c.OrderRpc)), } } 6.4.4 添加支付创建逻辑 Create 添加根据 oid 查询订单支付记录 PayModel 方法 FindOneByOid $ vim model/paymodel.go package model ... var ( ... cachePayIdPrefix = "cache:pay:id:" cachePayOidPrefix = "cache:pay:oid:" ) type ( PayModel interface { Insert(data *Pay) (sql.Result, error) FindOne(id int64) (*Pay, error) FindOneByOid(oid int64) (*Pay, error) Update(data *Pay) error Delete(id int64) error } ... ) ... func (m *defaultPayModel) FindOneByOid(oid int64) (*Pay, error) { payOidKey := fmt.Sprintf("%s%v", cachePayOidPrefix, oid) var resp Pay err := m.QueryRow(&resp, payOidKey, func(conn sqlx.SqlConn, v interface{}) error { query := fmt.Sprintf("select %s from %s where `oid` = ? limit 1", payRows, m.table) return conn.QueryRow(v, query, oid) }) switch err { case nil: return &resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err } } ...... 添加支付创建逻辑 支付流水创建流程,通过调用 user rpc 服务查询验证用户是否存在,再通过调用 order rpc 服务查询验证订单是否存在,然后通过查询库判断此订单是否已经创建过支付流水,最后创建落库。 $ vim rpc/internal/logic/createlogic.go package logic import ( "context" "mall/service/order/rpc/order" "mall/service/pay/model" "mall/service/pay/rpc/internal/svc" "mall/service/pay/rpc/pay" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type CreateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic { return &CreateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *CreateLogic) Create(in *pay.CreateRequest) (*pay.CreateResponse, error) { // 查询用户是否存在 _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // 查询订单是否存在 _, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{ Id: in.Oid, }) if err != nil { return nil, err } // 查询订单是否已经创建支付 _, err = l.svcCtx.PayModel.FindOneByOid(in.Oid) if err == nil { return nil, status.Error(100, "订单已创建支付") } newPay := model.Pay{ Uid: in.Uid, Oid: in.Oid, Amount: in.Amount, Source: 0, Status: 0, } res, err := l.svcCtx.PayModel.Insert(&newPay) if err != nil { return nil, status.Error(500, err.Error()) } newPay.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } return &pay.CreateResponse{ Id: newPay.Id, }, nil } 6.4.5 添加支付详情逻辑 Detail $ vim rpc/internal/logic/detaillogic.go package logic import ( "context" "mall/service/pay/model" "mall/service/pay/rpc/internal/svc" "mall/service/pay/rpc/pay" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type DetailLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic { return &DetailLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *DetailLogic) Detail(in *pay.DetailRequest) (*pay.DetailResponse, error) { // 查询支付是否存在 res, err := l.svcCtx.PayModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "支付不存在") } return nil, status.Error(500, err.Error()) } return &pay.DetailResponse{ Id: res.Id, Uid: res.Uid, Oid: res.Oid, Amount: res.Amount, Source: res.Source, Status: res.Status, }, nil } 6.4.6 添加支付回调逻辑 Callback 支付流水回调流程,通过调用 user rpc 服务查询验证用户是否存在,再通过调用 order rpc 服务查询验证订单是否存在,然后通过查询库判断此订单支付流水是否存在,以及回调支付金额和库中流水支付金额是否一致,最后更新支付流水状态和通过调用 order rpc 服务更新订单状态。 $ vim rpc/internal/logic/callbacklogic.go package logic import ( "context" "mall/service/order/rpc/order" "mall/service/pay/model" "mall/service/pay/rpc/internal/svc" "mall/service/pay/rpc/pay" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type CallbackLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CallbackLogic { return &CallbackLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *CallbackLogic) Callback(in *pay.CallbackRequest) (*pay.CallbackResponse, error) { // 查询用户是否存在 _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // 查询订单是否存在 _, err = l.svcCtx.OrderRpc.Detail(l.ctx, &order.DetailRequest{ Id: in.Oid, }) if err != nil { return nil, err } // 查询支付是否存在 res, err := l.svcCtx.PayModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "支付不存在") } return nil, status.Error(500, err.Error()) } // 支付金额与订单金额不符 if in.Amount != res.Amount { return nil, status.Error(100, "支付金额与订单金额不符") } res.Source = in.Source res.Status = in.Status err = l.svcCtx.PayModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } // 更新订单支付状态 _, err = l.svcCtx.OrderRpc.Paid(l.ctx, &order.PaidRequest{ Id: in.Oid, }) if err != nil { return nil, status.Error(500, err.Error()) } return &pay.CallbackResponse{}, nil } 6.5 编写 pay api 服务 6.5.1 修改配置文件 修改 pay.yaml 配置文件 $ vim api/etc/pay.yaml 修改服务地址,端口号为0.0.0.0:8003,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置 Name: Pay Host: 0.0.0.0 Port: 8003 Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node Pass: Auth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 86400 6.5.2 添加 pay rpc 依赖 添加 pay rpc 服务配置 $ vim api/etc/pay.yaml Name: Pay Host: 0.0.0.0 Port: 8003 ...... PayRpc: Etcd: Hosts: - etcd:2379 Key: pay.rpc 添加 pay rpc 服务配置的实例化 $ vim api/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/rest" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { rest.RestConf Auth struct { AccessSecret string AccessExpire int64 } PayRpc zrpc.RpcClientConf } 注册服务上下文 pay rpc 的依赖 $ vim api/internal/svc/servicecontext.go package svc import ( "mall/service/pay/api/internal/config" "mall/service/pay/rpc/payclient" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config PayRpc payclient.Pay } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, PayRpc: payclient.NewPay(zrpc.MustNewClient(c.PayRpc)), } } 6.5.3 添加支付创建逻辑 Create $ vim api/internal/logic/createlogic.go package logic import ( "context" "mall/service/pay/api/internal/svc" "mall/service/pay/api/internal/types" "mall/service/pay/rpc/pay" "github.com/tal-tech/go-zero/core/logx" ) type CreateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateLogic { return CreateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *CreateLogic) Create(req types.CreateRequest) (resp *types.CreateResponse, err error) { res, err := l.svcCtx.PayRpc.Create(l.ctx, &pay.CreateRequest{ Uid: req.Uid, Oid: req.Oid, Amount: req.Amount, }) if err != nil { return nil, err } return &types.CreateResponse{ Id: res.Id, }, nil } 6.5.4 添加支付详情逻辑 Detail $ vim api/internal/logic/detaillogic.go package logic import ( "context" "mall/service/pay/api/internal/svc" "mall/service/pay/api/internal/types" "mall/service/pay/rpc/pay" "github.com/tal-tech/go-zero/core/logx" ) type DetailLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) DetailLogic { return DetailLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *DetailLogic) Detail(req types.DetailRequest) (resp *types.DetailResponse, err error) { res, err := l.svcCtx.PayRpc.Detail(l.ctx, &pay.DetailRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.DetailResponse{ Id: req.Id, Uid: res.Uid, Oid: res.Oid, Amount: res.Amount, Source: res.Source, Status: res.Status, }, nil } 6.5.5 添加支付回调逻辑 Callback $ vim api/internal/logic/callbacklogic.go package logic import ( "context" "mall/service/pay/api/internal/svc" "mall/service/pay/api/internal/types" "mall/service/pay/rpc/pay" "github.com/tal-tech/go-zero/core/logx" ) type CallbackLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) CallbackLogic { return CallbackLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *CallbackLogic) Callback(req types.CallbackRequest) (resp *types.CallbackResponse, err error) { _, err = l.svcCtx.PayRpc.Callback(l.ctx, &pay.CallbackRequest{ Id: req.Id, Uid: req.Uid, Oid: req.Oid, Amount: req.Amount, Source: req.Source, Status: req.Status, }) if err != nil { return nil, err } return &types.CallbackResponse{}, nil } 6.6 启动 pay rpc 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/pay/rpc $ go run pay.go -f etc/pay.yaml Starting rpc server at 127.0.0.1:9003... 6.7 启动 pay api 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/pay/api $ go run pay.go -f etc/pay.yaml Starting server at 0.0.0.0:8003... 项目地址 https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

优秀的个人博客,低调大师

带你十天轻松搞定 Go 微服务系列(五)

序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务 订单服务(本文) 支付服务 RPC 服务 Auth 验证 服务监控 链路追踪 分布式事务 期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。 完整示例代码:https://github.com/nivin-studio/go-zero-mall 首先,我们来看一下整体的服务拆分图: 5 订单服务(order) 进入服务工作区 $ cd mall/service/order 5.1 生成 order model 模型 创建 sql 文件 $ vim model/order.sql 编写 sql 文件 CREATE TABLE `order` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `uid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', `pid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '产品ID', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '订单金额', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_uid` (`uid`), KEY `idx_pid` (`pid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 运行模板生成命令 $ goctl model mysql ddl -src ./model/order.sql -dir ./model -c 5.2 生成 order api 服务 创建 api 文件 $ vim api/order.api 编写 api 文件 type ( // 订单创建 CreateRequest { Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } CreateResponse { Id int64 `json:"id"` } // 订单创建 // 订单修改 UpdateRequest { Id int64 `json:"id"` Uid int64 `json:"uid,optional"` Pid int64 `json:"pid,optional"` Amount int64 `json:"amount,optional"` Status int64 `json:"status,optional"` } UpdateResponse { } // 订单修改 // 订单删除 RemoveRequest { Id int64 `json:"id"` } RemoveResponse { } // 订单删除 // 订单详情 DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // 订单详情 // 订单列表 ListRequest { Uid int64 `json:"uid"` } ListResponse { Id int64 `json:"id"` Uid int64 `json:"uid"` Pid int64 `json:"pid"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // 订单列表 ) @server( jwt: Auth ) service Order { @handler Create post /api/order/create(CreateRequest) returns (CreateResponse) @handler Update post /api/order/update(UpdateRequest) returns (UpdateResponse) @handler Remove post /api/order/remove(RemoveRequest) returns (RemoveResponse) @handler Detail post /api/order/detail(DetailRequest) returns (DetailResponse) @handler List post /api/order/list(ListRequest) returns (ListResponse) } 运行模板生成命令 $ goctl api go -api ./api/order.api -dir ./api 5.3 生成 order rpc 服务 创建 proto 文件 $ vim rpc/order.proto 编写 proto 文件 syntax = "proto3"; package orderclient; option go_package = "order"; // 订单创建 message CreateRequest { int64 Uid = 1; int64 Pid = 2; int64 Amount = 3; int64 Status = 4; } message CreateResponse { int64 id = 1; } // 订单创建 // 订单修改 message UpdateRequest { int64 id = 1; int64 Uid = 2; int64 Pid = 3; int64 Amount = 4; int64 Status = 5; } message UpdateResponse { } // 订单修改 // 订单删除 message RemoveRequest { int64 id = 1; } message RemoveResponse { } // 订单删除 // 订单详情 message DetailRequest { int64 id = 1; } message DetailResponse { int64 id = 1; int64 Uid = 2; int64 Pid = 3; int64 Amount = 4; int64 Status = 5; } // 订单详情 // 订单列表 message ListRequest { int64 uid = 1; } message ListResponse { repeated DetailResponse data = 1; } // 订单列表 // 订单支付 message PaidRequest { int64 id = 1; } message PaidResponse { } // 订单支付 service Order { rpc Create(CreateRequest) returns(CreateResponse); rpc Update(UpdateRequest) returns(UpdateResponse); rpc Remove(RemoveRequest) returns(RemoveResponse); rpc Detail(DetailRequest) returns(DetailResponse); rpc List(ListRequest) returns(ListResponse); rpc Paid(PaidRequest) returns(PaidResponse); } 运行模板生成命令 $ goctl rpc proto -src ./rpc/order.proto -dir ./rpc 5.4 编写 order rpc 服务 5.4.1 修改配置文件 修改 order.yaml 配置文件 $ vim rpc/etc/order.yaml 修改服务监听地址,端口号为0.0.0.0:9002,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置 Name: order.rpc ListenOn: 0.0.0.0:9002 Etcd: Hosts: - etcd:2379 Key: order.rpc Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node Pass: 5.4.2 添加 order model 依赖 添加 Mysql 服务配置,CacheRedis 服务配置的实例化 $ vim rpc/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf } 注册服务上下文 order model 的依赖 $ vim rpc/internal/svc/servicecontext.go package svc import ( "mall/service/order/model" "mall/service/order/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx" ) type ServiceContext struct { Config config.Config OrderModel model.OrderModel } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, OrderModel: model.NewOrderModel(conn, c.CacheRedis), } } 5.4.3 添加 user rpc,product rpc 依赖 添加 user rpc, product rpc 服务配置 $ vim rpc/etc/order.yaml Name: order.rpc ListenOn: 0.0.0.0:9002 Etcd: Hosts: - etcd:2379 Key: order.rpc ...... UserRpc: Etcd: Hosts: - etcd:2379 Key: user.rpc ProductRpc: Etcd: Hosts: - etcd:2379 Key: product.rpc 添加 user rpc, product rpc 服务配置的实例化 $ vim rpc/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf UserRpc zrpc.RpcClientConf ProductRpc zrpc.RpcClientConf } 注册服务上下文 user rpc, product rpc 的依赖 $ vim rpc/internal/svc/servicecontext.go package svc import ( "mall/service/order/model" "mall/service/order/rpc/internal/config" "mall/service/product/rpc/productclient" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config OrderModel model.OrderModel UserRpc userclient.User ProductRpc productclient.Product } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, OrderModel: model.NewOrderModel(conn, c.CacheRedis), UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)), } } 5.4.4 添加订单创建逻辑 Create 订单创建流程,通过调用 user rpc 服务查询验证用户是否存在,再通过调用 product rpc 服务查询验证产品是否存在,以及判断产品库存是否充足。验证通过后,创建用户订单,并通过调用 product rpc 服务更新产品库存。 $ vim rpc/internal/logic/createlogic.go package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "mall/service/product/rpc/product" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type CreateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic { return &CreateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *CreateLogic) Create(in *order.CreateRequest) (*order.CreateResponse, error) { // 查询用户是否存在 _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // 查询产品是否存在 productRes, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{ Id: in.Pid, }) if err != nil { return nil, err } // 判断产品库存是否充足 if productRes.Stock <= 0 { return nil, status.Error(500, "产品库存不足") } newOrder := model.Order{ Uid: in.Uid, Pid: in.Pid, Amount: in.Amount, Status: 0, } // 创建订单 res, err := l.svcCtx.OrderModel.Insert(&newOrder) if err != nil { return nil, status.Error(500, err.Error()) } newOrder.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } // 更新产品库存 _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{ Id: productRes.Id, Name: productRes.Name, Desc: productRes.Desc, Stock: productRes.Stock - 1, Amount: productRes.Amount, Status: productRes.Status, }) if err != nil { return nil, err } return &order.CreateResponse{ Id: newOrder.Id, }, nil } 注意:这里的产品库存更新存在数据一致性问题,在以往的项目中我们会使用数据库的事务进行这一系列的操作来保证数据的一致性。但是因为我们这边把“订单”和“产品”分成了不同的微服务,在实际的项目中他们可能拥有不同的数据库,所以我们要考虑在跨服务的情况下还能保证数据的一致性,这就涉及到了分布式事务的使用,在后面的章节中我们将介绍使用分布式事务来修改这个下单的逻辑。 5.4.5 添加订单详情逻辑 Detail $ vim rpc/internal/logic/detaillogic.go package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type DetailLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic { return &DetailLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *DetailLogic) Detail(in *order.DetailRequest) (*order.DetailResponse, error) { // 查询订单是否存在 res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "订单不存在") } return nil, status.Error(500, err.Error()) } return &order.DetailResponse{ Id: res.Id, Uid: res.Uid, Pid: res.Pid, Amount: res.Amount, Status: res.Status, }, nil } 5.4.6 添加订单更新逻辑 Update $ vim rpc/internal/logic/updatelogic.go package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type UpdateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogic { return &UpdateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *UpdateLogic) Update(in *order.UpdateRequest) (*order.UpdateResponse, error) { // 查询订单是否存在 res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "订单不存在") } return nil, status.Error(500, err.Error()) } if in.Uid != 0 { res.Uid = in.Uid } if in.Pid != 0 { res.Pid = in.Pid } if in.Amount != 0 { res.Amount = in.Amount } if in.Status != 0 { res.Status = in.Status } err = l.svcCtx.OrderModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } return &order.UpdateResponse{}, nil } 5.4.7 添加订单删除逻辑 Remove $ vim rpc/internal/logic/removelogic.go package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type RemoveLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveLogic { return &RemoveLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *RemoveLogic) Remove(in *order.RemoveRequest) (*order.RemoveResponse, error) { // 查询订单是否存在 res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "订单不存在") } return nil, status.Error(500, err.Error()) } err = l.svcCtx.OrderModel.Delete(res.Id) if err != nil { return nil, status.Error(500, err.Error()) } return &order.RemoveResponse{}, nil } 5.4.8 添加订单列表逻辑 List 添加根据 uid 查询用户所有订单的 OrderModel 方法 FindAllByUid $ vim model/ordermodel.go package model ...... type ( OrderModel interface { Insert(data *Order) (sql.Result, error) FindOne(id int64) (*Order, error) FindAllByUid(uid int64) ([]*Order, error) Update(data *Order) error Delete(id int64) error } ...... ) ...... func (m *defaultOrderModel) FindAllByUid(uid int64) ([]*Order, error) { var resp []*Order query := fmt.Sprintf("select %s from %s where `uid` = ?", orderRows, m.table) err := m.QueryRowsNoCache(&resp, query, uid) switch err { case nil: return resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err } } ...... 添加订单列表逻辑 $ vim rpc/internal/logic/listlogic.go package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type ListLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListLogic { return &ListLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *ListLogic) List(in *order.ListRequest) (*order.ListResponse, error) { // 查询用户是否存在 _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{ Id: in.Uid, }) if err != nil { return nil, err } // 查询订单是否存在 list, err := l.svcCtx.OrderModel.FindAllByUid(in.Uid) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "订单不存在") } return nil, status.Error(500, err.Error()) } orderList := make([]*order.DetailResponse, 0) for _, item := range list { orderList = append(orderList, &order.DetailResponse{ Id: item.Id, Uid: item.Uid, Pid: item.Pid, Amount: item.Amount, Status: item.Status, }) } return &order.ListResponse{ Data: orderList, }, nil } 5.4.9 添加订单支付逻辑 Paid $ vim rpc/internal/logic/paidlogic.go package logic import ( "context" "mall/service/order/model" "mall/service/order/rpc/internal/svc" "mall/service/order/rpc/order" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type PaidLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewPaidLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaidLogic { return &PaidLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *PaidLogic) Paid(in *order.PaidRequest) (*order.PaidResponse, error) { // 查询订单是否存在 res, err := l.svcCtx.OrderModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "订单不存在") } return nil, status.Error(500, err.Error()) } res.Status = 1 err = l.svcCtx.OrderModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } return &order.PaidResponse{}, nil } 5.5 编写 order api 服务 5.5.1 修改配置文件 修改 order.yaml 配置文件 $ vim api/etc/order.yaml 修改服务地址,端口号为0.0.0.0:8002,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置 Name: Order Host: 0.0.0.0 Port: 8002 Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node Pass: Auth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 86400 5.5.2 添加 order rpc 依赖 添加 order rpc 服务配置 $ vim api/etc/order.yaml Name: Order Host: 0.0.0.0 Port: 8002 ...... OrderRpc: Etcd: Hosts: - etcd:2379 Key: order.rpc 添加 order rpc 服务配置的实例化 $ vim api/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/rest" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { rest.RestConf Auth struct { AccessSecret string AccessExpire int64 } OrderRpc zrpc.RpcClientConf } 注册服务上下文 user rpc 的依赖 $ vim api/internal/svc/servicecontext.go package svc import ( "mall/service/order/api/internal/config" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config OrderRpc orderclient.Order } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, OrderRpc: orderclient.NewOrder(zrpc.MustNewClient(c.OrderRpc)), } } 5.5.3 添加订单创建逻辑 Create $ vim api/internal/logic/createlogic.go package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/core/logx" ) type CreateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateLogic { return CreateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *CreateLogic) Create(req types.CreateRequest) (resp *types.CreateResponse, err error) { res, err := l.svcCtx.OrderRpc.Create(l.ctx, &orderclient.CreateRequest{ Uid: req.Uid, Pid: req.Pid, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.CreateResponse{ Id: res.Id, }, nil } 5.5.4 添加订单详情逻辑 Detail $ vim api/internal/logic/detaillogic.go package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/core/logx" ) type DetailLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) DetailLogic { return DetailLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *DetailLogic) Detail(req types.DetailRequest) (resp *types.DetailResponse, err error) { res, err := l.svcCtx.OrderRpc.Detail(l.ctx, &orderclient.DetailRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.DetailResponse{ Id: res.Id, Uid: res.Uid, Pid: res.Pid, Amount: res.Amount, Status: res.Status, }, nil } 5.5.5 添加订单更新逻辑 Update $ vim api/internal/logic/updatelogic.go package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/core/logx" ) type UpdateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) UpdateLogic { return UpdateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *UpdateLogic) Update(req types.UpdateRequest) (resp *types.UpdateResponse, err error) { _, err = l.svcCtx.OrderRpc.Update(l.ctx, &orderclient.UpdateRequest{ Id: req.Id, Uid: req.Uid, Pid: req.Pid, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.UpdateResponse{}, nil } 5.5.6 添加订单删除逻辑 Remove $ vim api/internal/logic/removelogic.go package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/core/logx" ) type RemoveLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) RemoveLogic { return RemoveLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *RemoveLogic) Remove(req types.RemoveRequest) (resp *types.RemoveResponse, err error) { _, err = l.svcCtx.OrderRpc.Remove(l.ctx, &orderclient.RemoveRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.RemoveResponse{}, nil } 5.5.7 添加订单列表逻辑 List $ vim api/internal/logic/listlogic.go package logic import ( "context" "mall/service/order/api/internal/svc" "mall/service/order/api/internal/types" "mall/service/order/rpc/orderclient" "github.com/tal-tech/go-zero/core/logx" ) type ListLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewListLogic(ctx context.Context, svcCtx *svc.ServiceContext) ListLogic { return ListLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *ListLogic) List(req types.ListRequest) (resp []*types.ListResponse, err error) { res, err := l.svcCtx.OrderRpc.List(l.ctx, &orderclient.ListRequest{ Uid: req.Uid, }) if err != nil { return nil, err } orderList := make([]*types.ListResponse, 0) for _, item := range res.Data { orderList = append(orderList, &types.ListResponse{ Id: item.Id, Uid: item.Uid, Pid: item.Pid, Amount: item.Amount, Status: item.Status, }) } return orderList, nil } 5.6 启动 order rpc 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/order/rpc $ go run order.go -f etc/order.yaml Starting rpc server at 127.0.0.1:9002... 5.7 启动 order api 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/order/api $ go run order.go -f etc/order.yaml Starting server at 0.0.0.0:8002... 项目地址 https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

优秀的个人博客,低调大师

带你十天轻松搞定 Go 微服务系列(四)

序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务(本文) 订单服务 支付服务 RPC 服务 Auth 验证 服务监控 链路追踪 分布式事务 期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。 完整示例代码:https://github.com/nivin-studio/go-zero-mall 首先,我们来看一下整体的服务拆分图: 4. 产品服务(product) 进入服务工作区 $ cd mall/service/product 4.1 生成 product model 模型 创建 sql 文件 $ vim model/product.sql 编写 sql 文件 CREATE TABLE `product` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '' COMMENT '产品名称', `desc` varchar(255) NOT NULL DEFAULT '' COMMENT '产品描述', `stock` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品库存', `amount` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品金额', `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '产品状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 运行模板生成命令 $ goctl model mysql ddl -src ./model/product.sql -dir ./model -c 4.2 生成 product api 服务 创建 api 文件 $ vim api/product.api 编写 api 文件 type ( // 产品创建 CreateRequest { Name string `json:"name"` Desc string `json:"desc"` Stock int64 `json:"stock"` Amount int64 `json:"amount"` Status int64 `json:"status"` } CreateResponse { Id int64 `json:"id"` } // 产品创建 // 产品修改 UpdateRequest { Id int64 `json:"id"` Name string `json:"name,optional"` Desc string `json:"desc,optional"` Stock int64 `json:"stock"` Amount int64 `json:"amount,optional"` Status int64 `json:"status,optional"` } UpdateResponse { } // 产品修改 // 产品删除 RemoveRequest { Id int64 `json:"id"` } RemoveResponse { } // 产品删除 // 产品详情 DetailRequest { Id int64 `json:"id"` } DetailResponse { Id int64 `json:"id"` Name string `json:"name"` Desc string `json:"desc"` Stock int64 `json:"stock"` Amount int64 `json:"amount"` Status int64 `json:"status"` } // 产品详情 ) @server( jwt: Auth ) service Product { @handler Create post /api/product/create(CreateRequest) returns (CreateResponse) @handler Update post /api/product/update(UpdateRequest) returns (UpdateResponse) @handler Remove post /api/product/remove(RemoveRequest) returns (RemoveResponse) @handler Detail post /api/product/detail(DetailRequest) returns (DetailResponse) } 运行模板生成命令 $ goctl api go -api ./api/product.api -dir ./api 4.3 生成 product rpc 服务 创建 proto 文件 $ vim rpc/product.proto 编写 proto 文件 syntax = "proto3"; package productclient; option go_package = "product"; // 产品创建 message CreateRequest { string Name = 1; string Desc = 2; int64 Stock = 3; int64 Amount = 4; int64 Status = 5; } message CreateResponse { int64 id = 1; } // 产品创建 // 产品修改 message UpdateRequest { int64 id = 1; string Name = 2; string Desc = 3; int64 Stock = 4; int64 Amount = 5; int64 Status = 6; } message UpdateResponse { } // 产品修改 // 产品删除 message RemoveRequest { int64 id = 1; } message RemoveResponse { } // 产品删除 // 产品详情 message DetailRequest { int64 id = 1; } message DetailResponse { int64 id = 1; string Name = 2; string Desc = 3; int64 Stock = 4; int64 Amount = 5; int64 Status = 6; } // 产品详情 service Product { rpc Create(CreateRequest) returns(CreateResponse); rpc Update(UpdateRequest) returns(UpdateResponse); rpc Remove(RemoveRequest) returns(RemoveResponse); rpc Detail(DetailRequest) returns(DetailResponse); } 运行模板生成命令 $ goctl rpc proto -src ./rpc/product.proto -dir ./rpc 4.4 编写 product rpc 服务 4.4.1 修改配置文件 修改 product.yaml 配置文件 $ vim rpc/etc/product.yaml 修改服务监听地址,端口号为0.0.0.0:9001,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置 Name: product.rpc ListenOn: 0.0.0.0:9001 Etcd: Hosts: - etcd:2379 Key: product.rpc Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node # node可以不写,可以设为cluster # Pass: xxx # 如果有密码 4.4.2 添加 product model 依赖 添加 Mysql 服务配置,CacheRedis 服务配置的实例化 $ vim rpc/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf } 注册服务上下文 product model 的依赖 $ vim rpc/internal/svc/servicecontext.go package svc import ( "mall/service/product/model" "mall/service/product/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx" ) type ServiceContext struct { Config config.Config ProductModel model.ProductModel } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, ProductModel: model.NewProductModel(conn, c.CacheRedis), } } 4.4.3 添加产品创建逻辑 Create $ vim rpc/internal/logic/createlogic.go package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type CreateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic { return &CreateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *CreateLogic) Create(in *product.CreateRequest) (*product.CreateResponse, error) { newProduct := model.Product{ Name: in.Name, Desc: in.Desc, Stock: in.Stock, Amount: in.Amount, Status: in.Status, } res, err := l.svcCtx.ProductModel.Insert(&newProduct) if err != nil { return nil, status.Error(500, err.Error()) } newProduct.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } return &product.CreateResponse{ Id: newProduct.Id, }, nil } 4.4.4 添加产品详情逻辑 Detail $ vim rpc/internal/logic/detaillogic.go package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type DetailLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic { return &DetailLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *DetailLogic) Detail(in *product.DetailRequest) (*product.DetailResponse, error) { // 查询产品是否存在 res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "产品不存在") } return nil, status.Error(500, err.Error()) } return &product.DetailResponse{ Id: res.Id, Name: res.Name, Desc: res.Desc, Stock: res.Stock, Amount: res.Amount, Status: res.Status, }, nil } 4.4.5 添加产品更新逻辑 Update $ vim rpc/internal/logic/updatelogic.go package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type UpdateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogic { return &UpdateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *UpdateLogic) Update(in *product.UpdateRequest) (*product.UpdateResponse, error) { // 查询产品是否存在 res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "产品不存在") } return nil, status.Error(500, err.Error()) } if in.Name != "" { res.Name = in.Name } if in.Desc != "" { res.Desc = in.Desc } if in.Stock != 0 { res.Stock = in.Stock } if in.Amount != 0 { res.Amount = in.Amount } if in.Status != 0 { res.Status = in.Status } err = l.svcCtx.ProductModel.Update(res) if err != nil { return nil, status.Error(500, err.Error()) } return &product.UpdateResponse{}, nil } 4.4.6 添加产品删除逻辑 Remove $ vim rpc/internal/logic/removelogic.go package logic import ( "context" "mall/service/product/model" "mall/service/product/rpc/internal/svc" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type RemoveLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveLogic { return &RemoveLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *RemoveLogic) Remove(in *product.RemoveRequest) (*product.RemoveResponse, error) { // 查询产品是否存在 res, err := l.svcCtx.ProductModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "产品不存在") } return nil, status.Error(500, err.Error()) } err = l.svcCtx.ProductModel.Delete(res.Id) if err != nil { return nil, status.Error(500, err.Error()) } return &product.RemoveResponse{}, nil } 4.5 编写 product api 服务 4.5.1 修改配置文件 修改 product.yaml 配置文件 $ vim api/etc/product.yaml 修改服务地址,端口号为0.0.0.0:8001,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置 Name: Product Host: 0.0.0.0 Port: 8001 Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node # node可以不写,可以设为cluster # Pass: xxx # 如果有密码 Auth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 86400 4.5.2 添加 product rpc 依赖 添加 product rpc 服务配置 $ vim api/etc/product.yaml Name: Product Host: 0.0.0.0 Port: 8001 ... ProductRpc: Etcd: Hosts: - etcd:2379 Key: product.rpc 添加 product rpc 服务配置的实例化 $ vim api/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/rest" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { rest.RestConf Auth struct { AccessSecret string AccessExpire int64 } ProductRpc zrpc.RpcClientConf } 注册服务上下文 product rpc 的依赖 $ vim api/internal/svc/servicecontext.go package svc import ( "mall/service/product/api/internal/config" "mall/service/product/rpc/productclient" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config ProductRpc productclient.Product } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)), } } 4.5.3 添加产品创建逻辑 Create $ vim api/internal/logic/createlogic.go package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type CreateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateLogic { return CreateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *CreateLogic) Create(req types.CreateRequest) (resp *types.CreateResponse, err error) { res, err := l.svcCtx.ProductRpc.Create(l.ctx, &product.CreateRequest{ Name: req.Name, Desc: req.Desc, Stock: req.Stock, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.CreateResponse{ Id: res.Id, }, nil } 4.5.4 添加产品详情逻辑 Detail $ vim api/internal/logic/detaillogic.go package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type DetailLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) DetailLogic { return DetailLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *DetailLogic) Detail(req types.DetailRequest) (resp *types.DetailResponse, err error) { res, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.DetailResponse{ Id: res.Id, Name: res.Name, Desc: res.Desc, Stock: res.Stock, Amount: res.Amount, Status: res.Status, }, nil } 4.5.5 添加产品更新逻辑 Update $ vim api/internal/logic/updatelogic.go package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type UpdateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) UpdateLogic { return UpdateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *UpdateLogic) Update(req types.UpdateRequest) (resp *types.UpdateResponse, err error) { _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{ Id: req.Id, Name: req.Name, Desc: req.Desc, Stock: req.Stock, Amount: req.Amount, Status: req.Status, }) if err != nil { return nil, err } return &types.UpdateResponse{}, nil } 4.5.6 添加产品删除逻辑 Remove $ vim api/internal/logic/removelogic.go package logic import ( "context" "mall/service/product/api/internal/svc" "mall/service/product/api/internal/types" "mall/service/product/rpc/product" "github.com/tal-tech/go-zero/core/logx" ) type RemoveLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) RemoveLogic { return RemoveLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *RemoveLogic) Remove(req types.RemoveRequest) (resp *types.RemoveResponse, err error) { _, err = l.svcCtx.ProductRpc.Remove(l.ctx, &product.RemoveRequest{ Id: req.Id, }) if err != nil { return nil, err } return &types.RemoveResponse{}, nil } 4.6 启动 product rpc 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/product/rpc $ go run product.go -f etc/product.yaml Starting rpc server at 127.0.0.1:9001... 4.7 启动 product api 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/product/api $ go run product.go -f etc/product.yaml Starting server at 0.0.0.0:8001... 项目地址 https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

优秀的个人博客,低调大师

带你十天轻松搞定 Go 微服务系列(三)

序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务(本文) 产品服务 订单服务 支付服务 RPC 服务 Auth 验证 服务监控 链路追踪 分布式事务 期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。 完整示例代码:https://github.com/nivin-studio/go-zero-mall 首先,我们来更新一下上篇文章中的服务拆分图片,由于微信公众号手机和电脑端不同步,导致美化的图片没有跟大家见面,特此补上,如图: 3 用户服务(user) 进入服务工作区 $ cd mall/service/user 3.1 生成 user model 模型 创建 sql 文件 $ vim model/user.sql 编写 sql 文件 CREATE TABLE `user` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户姓名', `gender` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户性别', `mobile` varchar(255) NOT NULL DEFAULT '' COMMENT '用户电话', `password` varchar(255) NOT NULL DEFAULT '' COMMENT '用户密码', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_mobile_unique` (`mobile`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 运行模板生成命令 $ goctl model mysql ddl -src ./model/user.sql -dir ./model -c 3.2 生成 user api 服务 创建 api 文件 $ vim api/user.api 编写 api 文件 type ( // 用户登录 LoginRequest { Mobile string `json:"mobile"` Password string `json:"password"` } LoginResponse { AccessToken string `json:"accessToken"` AccessExpire int64 `json:"accessExpire"` } // 用户登录 // 用户注册 RegisterRequest { Name string `json:"name"` Gender int64 `json:"gender"` Mobile string `json:"mobile"` Password string `json:"password"` } RegisterResponse { Id int64 `json:"id"` Name string `json:"name"` Gender int64 `json:"gender"` Mobile string `json:"mobile"` } // 用户注册 // 用户信息 UserInfoResponse { Id int64 `json:"id"` Name string `json:"name"` Gender int64 `json:"gender"` Mobile string `json:"mobile"` } // 用户信息 ) service User { @handler Login post /api/user/login(LoginRequest) returns (LoginResponse) @handler Register post /api/user/register(RegisterRequest) returns (RegisterResponse) } @server( jwt: Auth ) service User { @handler UserInfo post /api/user/userinfo() returns (UserInfoResponse) } 运行模板生成命令 $ goctl api go -api ./api/user.api -dir ./api 3.3 生成 user rpc 服务 创建 proto 文件 $ vim rpc/user.proto 编写 proto 文件 syntax = "proto3"; package userclient; option go_package = "user"; // 用户登录 message LoginRequest { string Mobile = 1; string Password = 2; } message LoginResponse { int64 Id = 1; string Name = 2; int64 Gender = 3; string Mobile = 4; } // 用户登录 // 用户注册 message RegisterRequest { string Name = 1; int64 Gender = 2; string Mobile = 3; string Password = 4; } message RegisterResponse { int64 Id = 1; string Name = 2; int64 Gender = 3; string Mobile = 4; } // 用户注册 // 用户信息 message UserInfoRequest { int64 Id = 1; } message UserInfoResponse { int64 Id = 1; string Name = 2; int64 Gender = 3; string Mobile = 4; } // 用户信息 service User { rpc Login(LoginRequest) returns(LoginResponse); rpc Register(RegisterRequest) returns(RegisterResponse); rpc UserInfo(UserInfoRequest) returns(UserInfoResponse); } 运行模板生成命令 $ goctl rpc proto -src ./rpc/user.proto -dir ./rpc 添加下载依赖包 回到 mall 项目根目录执行如下命令: $ go mod tidy 3.4 编写 user rpc 服务 3.4.1 修改配置文件 修改 user.yaml 配置文件 $ vim rpc/etc/user.yaml 修改服务监听地址,端口号为0.0.0.0:9000,Etcd 服务配置,Mysql 服务配置,CacheRedis 服务配置 Name: user.rpc ListenOn: 0.0.0.0:9000 Etcd: Hosts: - etcd:2379 Key: user.rpc Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Type: node Pass: 3.4.2 添加 user model 依赖 添加 Mysql 服务配置,CacheRedis 服务配置的实例化 $ vim rpc/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Mysql struct { DataSource string } CacheRedis cache.CacheConf } 注册服务上下文 user model 的依赖 $ vim rpc/internal/svc/servicecontext.go package svc import ( "mall/service/user/model" "mall/service/user/rpc/internal/config" "github.com/tal-tech/go-zero/core/stores/sqlx" ) type ServiceContext struct { Config config.Config UserModel model.UserModel } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) return &ServiceContext{ Config: c, UserModel: model.NewUserModel(conn, c.CacheRedis), } } 3.4.3 添加用户注册逻辑 Register 添加密码加密工具 在根目录 common 新建 crypt 工具库,此工具方法主要用于密码的加密处理。 $ vim common/cryptx/crypt.go package cryptx import ( "fmt" "golang.org/x/crypto/scrypt" ) func PasswordEncrypt(salt, password string) string { dk, _ := scrypt.Key([]byte(password), []byte(salt), 32768, 8, 1, 32) return fmt.Sprintf("%x", string(dk)) } 添加密码加密 Salt 配置 $ vim rpc/etc/user.yaml Name: user.rpc ListenOn: 0.0.0.0:9000 ... Salt: HWVOFkGgPTryzICwd7qnJaZR9KQ2i8xe $ vim rpc/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { ... Salt string } 添加用户注册逻辑 用户注册流程,先判断注册手机号是否已经被注册,手机号未被注册,将用户信息写入数据库,用户密码需要进行加密存储。 $ vim rpc/internal/logic/registerlogic.go package logic import ( "context" "mall/common/cryptx" "mall/service/user/model" "mall/service/user/rpc/internal/svc" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type RegisterLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic { return &RegisterLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *RegisterLogic) Register(in *user.RegisterRequest) (*user.RegisterResponse, error) { // 判断手机号是否已经注册 _, err := l.svcCtx.UserModel.FindOneByMobile(in.Mobile) if err == nil { return nil, status.Error(100, "该用户已存在") } if err == model.ErrNotFound { newUser := model.User{ Name: in.Name, Gender: in.Gender, Mobile: in.Mobile, Password: cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, in.Password), } res, err := l.svcCtx.UserModel.Insert(&newUser) if err != nil { return nil, status.Error(500, err.Error()) } newUser.Id, err = res.LastInsertId() if err != nil { return nil, status.Error(500, err.Error()) } return &user.RegisterResponse{ Id: newUser.Id, Name: newUser.Name, Gender: newUser.Gender, Mobile: newUser.Mobile, }, nil } return nil, status.Error(500, err.Error()) } 3.4.4 添加用户登录逻辑 Login 用户登录流程,通过手机号查询判断用户是否是注册用户,如果是注册用户,需要将用户输入的密码进行加密与数据库中用户加密密码进行对比验证。 $ vim rpc/internal/logic/loginlogic.go package logic import ( "context" "mall/common/cryptx" "mall/service/user/model" "mall/service/user/rpc/internal/svc" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type LoginLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { return &LoginLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *LoginLogic) Login(in *user.LoginRequest) (*user.LoginResponse, error) { // 查询用户是否存在 res, err := l.svcCtx.UserModel.FindOneByMobile(in.Mobile) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "用户不存在") } return nil, status.Error(500, err.Error()) } // 判断密码是否正确 password := cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, in.Password) if password != res.Password { return nil, status.Error(100, "密码错误") } return &user.LoginResponse{ Id: res.Id, Name: res.Name, Gender: res.Gender, Mobile: res.Mobile, }, nil } 3.4.5 添加用户信息逻辑 UserInfo $ vim rpc/internal/logic/userinfologic.go package logic import ( "context" "mall/service/user/model" "mall/service/user/rpc/internal/svc" "mall/service/user/rpc/user" "github.com/tal-tech/go-zero/core/logx" "google.golang.org/grpc/status" ) type UserInfoLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserInfoLogic { return &UserInfoLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *UserInfoLogic) UserInfo(in *user.UserInfoRequest) (*user.UserInfoResponse, error) { // 查询用户是否存在 res, err := l.svcCtx.UserModel.FindOne(in.Id) if err != nil { if err == model.ErrNotFound { return nil, status.Error(100, "用户不存在") } return nil, status.Error(500, err.Error()) } return &user.UserInfoResponse{ Id: res.Id, Name: res.Name, Gender: res.Gender, Mobile: res.Mobile, }, nil } 3.5 编写 user api 服务 3.5.1 修改配置文件 修改 user.yaml 配置文件 $ vim api/etc/user.yaml 修改服务地址,端口号为0.0.0.0:8000,Mysql 服务配置,CacheRedis 服务配置,Auth 验证配置 Name: User Host: 0.0.0.0 Port: 8000 Mysql: DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai CacheRedis: - Host: redis:6379 Pass: Type: node Auth: AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl AccessExpire: 86400 3.5.2 添加 user rpc 依赖 添加 user rpc 服务配置 $ vim api/etc/user.yaml Name: User Host: 0.0.0.0 Port: 8000 ...... UserRpc: Etcd: Hosts: - etcd:2379 Key: user.rpc 添加 user rpc 服务配置的实例化 $ vim api/internal/config/config.go package config import ( "github.com/tal-tech/go-zero/rest" "github.com/tal-tech/go-zero/zrpc" ) type Config struct { rest.RestConf Auth struct { AccessSecret string AccessExpire int64 } UserRpc zrpc.RpcClientConf } 注册服务上下文 user rpc 的依赖 $ vim api/internal/svc/servicecontext.go package svc import ( "mall/service/user/api/internal/config" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/zrpc" ) type ServiceContext struct { Config config.Config UserRpc userclient.User } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)), } } 3.5.3 添加用户注册逻辑 Register $ vim api/internal/logic/registerlogic.go package logic import ( "context" "mall/service/user/api/internal/svc" "mall/service/user/api/internal/types" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/logx" ) type RegisterLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) RegisterLogic { return RegisterLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *RegisterLogic) Register(req types.RegisterRequest) (resp *types.RegisterResponse, err error) { res, err := l.svcCtx.UserRpc.Register(l.ctx, &userclient.RegisterRequest{ Name: req.Name, Gender: req.Gender, Mobile: req.Mobile, Password: req.Password, }) if err != nil { return nil, err } return &types.RegisterResponse{ Id: res.Id, Name: res.Name, Gender: res.Gender, Mobile: res.Mobile, }, nil } 3.5.4 添加用户登录逻辑 Login 添加 JWT 工具 在根目录 common 新建 jwtx 工具库,用于生成用户 token。 $ vim common/jwtx/jwt.go package jwtx import "github.com/golang-jwt/jwt" func GetToken(secretKey string, iat, seconds, uid int64) (string, error) { claims := make(jwt.MapClaims) claims["exp"] = iat + seconds claims["iat"] = iat claims["uid"] = uid token := jwt.New(jwt.SigningMethodHS256) token.Claims = claims return token.SignedString([]byte(secretKey)) } 添加用户登录逻辑 通过调用 user rpc 服务进行登录验证,登录成功后,使用用户信息生成对应的 token 以及 token 的有效期。 $ vim api/internal/logic/loginlogic.go package logic import ( "context" "time" "mall/common/jwtx" "mall/service/user/api/internal/svc" "mall/service/user/api/internal/types" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/logx" ) type LoginLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) LoginLogic { return LoginLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *LoginLogic) Login(req types.LoginRequest) (resp *types.LoginResponse, err error) { res, err := l.svcCtx.UserRpc.Login(l.ctx, &userclient.LoginRequest{ Mobile: req.Mobile, Password: req.Password, }) if err != nil { return nil, err } now := time.Now().Unix() accessExpire := l.svcCtx.Config.Auth.AccessExpire accessToken, err := jwtx.GetToken(l.svcCtx.Config.Auth.AccessSecret, now, accessExpire, res.Id) if err != nil { return nil, err } return &types.LoginResponse{ AccessToken: accessToken, AccessExpire: now + accessExpire, }, nil } 3.5.5 添加用户信息逻辑 UserInfo $ vim api/internal/logic/userinfologic.go package logic import ( "context" "encoding/json" "mall/service/user/api/internal/svc" "mall/service/user/api/internal/types" "mall/service/user/rpc/userclient" "github.com/tal-tech/go-zero/core/logx" ) type UserInfoLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) UserInfoLogic { return UserInfoLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *UserInfoLogic) UserInfo() (resp *types.UserInfoResponse, err error) { uid, _ := l.ctx.Value("uid").(json.Number).Int64() res, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &userclient.UserInfoRequest{ Id: uid, }) if err != nil { return nil, err } return &types.UserInfoResponse{ Id: res.Id, Name: res.Name, Gender: res.Gender, Mobile: res.Mobile, }, nil } 通过 l.ctx.Value("uid") 可获取 jwt token 中自定义的参数 3.6 启动 user rpc 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/user/rpc $ go run user.go -f etc/user.yaml Starting rpc server at 127.0.0.1:9000... 3.7 启动 user api 服务 提示:启动服务需要在 golang 容器中启动 $ cd mall/service/user/api $ go run user.go -f etc/user.yaml Starting server at 0.0.0.0:8000... 项目地址 https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

优秀的个人博客,低调大师

带你十天轻松搞定 Go 微服务系列(二)

上篇文章开始,我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分(本文) 用户服务 产品服务 订单服务 支付服务 RPC 服务 Auth 验证 服务监控 链路追踪 分布式事务 期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。 完整示例代码:https://github.com/nivin-studio/go-zero-mall 服务拆分 一个商城项目可拆分用户服务(user)、订单服务(order)、产品服务(product)、支付服务(pay)、售后服务(afterSale)... 每个服务都可以再分为 api 服务和 rpc 服务。api 服务对外,可提供给 app 调用。rpc 服务是对内的,可提供给内部 api 服务或者其他 rpc 服务调用。整个项目服务依赖流程图大致如下: 1 用户服务(user) api 服务 端口:8000 rpc 服务 端口:9000 login 用户登录接口 login 用户登录接口 register 用户注册接口 register 用户注册接口 userinfo 用户信息接口 userinfo 用户信息接口 ... ... ... ... 2 产品服务(product) api 服务 端口:8001 rpc 服务 端口:9001 create 产品创建接口 create 产品创建接口 update 产品修改接口 update 产品修改接口 remove 产品删除接口 remove 产品删除接口 detail 产品详情接口 detail 产品详情接口 ... ... ... ... 3 订单服务(order) api 服务 端口:8002 rpc 服务 端口:9002 create 订单创建接口 create 订单创建接口 update 订单修改接口 update 订单修改接口 remove 订单删除接口 remove 订单删除接口 detail 订单详情接口 detail 订单详情接口 list 订单列表接口 list 订单列表接口 paid 订单支付接口 ... ... ... ... 4 支付服务(pay) api 服务 端口:8003 rpc 服务 端口:9003 create 支付创建接口 create 支付创建接口 detail 支付详情接口 detail 支付详情接口 callback 支付回调接口 callback 支付回调接口 ... ... ... ... 5 创建项目目录 创建 mall 工程 $ mkdir mall &amp;&amp; cd mall $ go mod init mall 创建 common 目录 $ mkdir common 创建 service 目录 $ mkdir service &amp;&amp; cd service 创建 user api,user rpc,user model 目录 $ mkdir -p user/api $ mkdir -p user/rpc $ mkdir -p user/model 创建 product api,product rpc,product model 目录 $ mkdir -p product/api $ mkdir -p product/rpc $ mkdir -p product/model 创建 order api,order rpc,order model 目录 $ mkdir -p order/api $ mkdir -p order/rpc $ mkdir -p order/model 创建 pay api,pay rpc,pay model 目录 $ mkdir -p pay/api $ mkdir -p pay/rpc $ mkdir -p pay/model 最终项目目录 ├── common # 通用库 ├── service # 服务 │ ├── order │ │ ├── api # order api 服务 │ │ ├── model # order 数据模型 │ │ └── rpc # order rpc 服务 │ ├── pay │ │ ├── api # pay api 服务 │ │ ├── model # pay 数据模型 │ │ └── rpc # pay rpc 服务 │ ├── product │ │ ├── api # product api 服务 │ │ ├── model # product 数据模型 │ │ └── rpc # product rpc 服务 │ └── user │ ├── api # user api 服务 │ ├── model # user 数据模型 │ └── rpc # user rpc 服务 └── go.mod 一些思考 微服务拆分并没有统一的标准,相同的业务在不同的公司很可能拆分方式会有所区别,用户规模、团队大小、组员能力等都会是考虑因素。但我们还是有一些基本原则可以遵循: 由粗到细,避免过度拆分,遵循渐进式演进的原则 不同服务之间应该是正交的,不要你中有我我中有你 避免环形依赖,服务依赖关系应该是有向无环图 避免不同服务之间共享同一个数据库 go-zero 也是一个渐进式微服务框架,你可以在业务早期使用单体来快速满足业务,当业务增长并有需要的时候,做最小的改动即可做到渐进式的服务拆分。 此类话题也可以在 go-zero 社区群里一起讨论。 项目地址 https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

优秀的个人博客,低调大师

带你十天轻松搞定 Go 微服务系列(一)

本文开始,我们会出一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建(本文) 服务拆分 用户服务 产品服务 订单服务 支付服务 RPC 服务 Auth 验证 服务监控 链路追踪 分布式事务 期望通过本系列带你在本机利用 Docker 环境利用 go-zero 快速开发一个商城系统,让你快速上手微服务。 完整示例代码:https://github.com/nivin-studio/go-zero-mall 1 环境要求 Golang 1.15+ Etcd Redis Mysql Prometheus Grafana Jaeger DTM 2 Docker 本地开发环境搭建 为了方便开发调试,我们使用 Docker 构建本地开发环境。Windows 和 macOS 系统可下载 Docker Desktop 安装使用,具体下载安装方法可自行搜索相关教程。 这里我们使用 Docker Compose 来编排管理我们的容器,创建如下目录: gonivinck ├── dtm # DTM 分布式事务管理器 │ ├── config.yml # DTM 配置文件 │ └── Dockerfile ├── etcd # Etcd 服务注册发现 │ └── Dockerfile ├── golang # Golang 运行环境 │ └── Dockerfile ├── grafana # Grafana 可视化数据监控 │ └── Dockerfile ├── jaeger # Jaeger 链路追踪 │ └── Dockerfile ├── mysql # Mysql 服务 │ └── Dockerfile ├── mysql-manage # Mysql 可视化管理 │ └── Dockerfile ├── prometheus # Prometheus 服务监控 │ ├── Dockerfile │ └── prometheus.yml # Prometheus 配置文件 ├── redis # Redis 服务 │ └── Dockerfile ├── redis-manage # Redis 可视化管理 │ └── Dockerfile ├── .env # env 配置 └── docker-compose.yml 2.1 编写 Dockerfile 在 go-zero 的微服务中采用 grpc 进行服务间的通信,而 grpc 的编写就需要用到 protoc 和翻译成 go 语言 rpc stub 代码的插件 protoc-gen-go。 为了提高开发效率,减少代码的出错率,缩短业务开发的工作量,go-zero 还提供了 goctl 代码生成工具。 因此,我们需要将 protoc, protoc-gen-go, goctl, 给提前安装到 golang 的容器中,以便后续使用。 所以 golang 容器的 Dockerfile 代码如下: FROM golang:1.17 LABEL maintainer="Ving <ving@nivin.cn>" ENV GOPROXY https://goproxy.cn,direct # 安装必要的软件包和依赖包 USER root RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list && \ sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list && \ sed -i 's/security-cdn.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list && \ apt-get update && \ apt-get upgrade -y && \ apt-get install -y --no-install-recommends \ curl \ zip \ unzip \ git \ vim # 安装 goctl USER root RUN GOPROXY=https://goproxy.cn/,direct go install github.com/tal-tech/go-zero/tools/goctl@cli # 安装 protoc USER root RUN curl -L -o /tmp/protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protoc-3.19.1-linux-x86_64.zip && \ unzip -d /tmp/protoc /tmp/protoc.zip && \ mv /tmp/protoc/bin/protoc $GOPATH/bin # 安装 protoc-gen-go USER root RUN go get -u github.com/golang/protobuf/protoc-gen-go@v1.4.0 # $GOPATH/bin添加到环境变量中 ENV PATH $GOPATH/bin:$PATH # 清理垃圾 USER root RUN apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ rm /var/log/lastlog /var/log/faillog # 设置工作目录 WORKDIR /usr/src/code EXPOSE 8000 EXPOSE 8001 EXPOSE 8002 EXPOSE 8003 EXPOSE 9000 EXPOSE 9001 EXPOSE 9002 EXPOSE 9003 其他服务容器 Dockerfile 无需特殊处理,只要基于现有的镜像即可。 服务 基于的镜像 DTM yedf/dtm Etcd bitnami/etcd Mysql mysql:5.7 Redis redis:5.0 Mysql Manage phpmyadmin/phpmyadmin Redis Manage erikdubbelboer/phpredisadmin Prometheus bitnami/prometheus Grafana grafana/grafana Jaeger jaegertracing/all-in-one:1.28 2.2 编写 .env 配置文件 # 设置时区 TZ=Asia/Shanghai # 设置网络模式 NETWORKS_DRIVER=bridge # PATHS ########################################## # 宿主机上代码存放的目录路径 CODE_PATH_HOST=./code # 宿主机上Mysql Reids数据存放的目录路径 DATA_PATH_HOST=./data # MYSQL ########################################## # Mysql 服务映射宿主机端口号,可在宿主机127.0.0.1:3306访问 MYSQL_PORT=3306 MYSQL_USERNAME=admin MYSQL_PASSWORD=123456 MYSQL_ROOT_PASSWORD=123456 # Mysql 可视化管理用户名称,同 MYSQL_USERNAME MYSQL_MANAGE_USERNAME=admin # Mysql 可视化管理用户密码,同 MYSQL_PASSWORD MYSQL_MANAGE_PASSWORD=123456 # Mysql 可视化管理ROOT用户密码,同 MYSQL_ROOT_PASSWORD MYSQL_MANAGE_ROOT_PASSWORD=123456 # Mysql 服务地址 MYSQL_MANAGE_CONNECT_HOST=mysql # Mysql 服务端口号 MYSQL_MANAGE_CONNECT_PORT=3306 # Mysql 可视化管理映射宿主机端口号,可在宿主机127.0.0.1:1000访问 MYSQL_MANAGE_PORT=1000 # REDIS ########################################## # Redis 服务映射宿主机端口号,可在宿主机127.0.0.1:6379访问 REDIS_PORT=6379 # Redis 可视化管理用户名称 REDIS_MANAGE_USERNAME=admin # Redis 可视化管理用户密码 REDIS_MANAGE_PASSWORD=123456 # Redis 服务地址 REDIS_MANAGE_CONNECT_HOST=redis # Redis 服务端口号 REDIS_MANAGE_CONNECT_PORT=6379 # Redis 可视化管理映射宿主机端口号,可在宿主机127.0.0.1:2000访问 REDIS_MANAGE_PORT=2000 # ETCD ########################################### # Etcd 服务映射宿主机端口号,可在宿主机127.0.0.1:2379访问 ETCD_PORT=2379 # PROMETHEUS ##################################### # Prometheus 服务映射宿主机端口号,可在宿主机127.0.0.1:3000访问 PROMETHEUS_PORT=3000 # GRAFANA ######################################## # Grafana 服务映射宿主机端口号,可在宿主机127.0.0.1:4000访问 GRAFANA_PORT=4000 # JAEGER ######################################### # Jaeger 服务映射宿主机端口号,可在宿主机127.0.0.1:5000访问 JAEGER_PORT=5000 # DTM ######################################### # DTM HTTP 协议端口号 DTM_HTTP_PORT=36789 # DTM gRPC 协议端口号 DTM_GRPC_PORT=36790 2.3 编写 docker-compose.yml 配置文件 version: '3.5' # 网络配置 networks: backend: driver: ${NETWORKS_DRIVER} # 服务容器配置 services: golang: # 自定义容器名称 build: context: ./golang # 指定构建使用的 Dockerfile 文件 environment: # 设置环境变量 - TZ=${TZ} volumes: # 设置挂载目录 - ${CODE_PATH_HOST}:/usr/src/code # 引用 .env 配置中 CODE_PATH_HOST 变量,将宿主机上代码存放的目录挂载到容器中 /usr/src/code 目录 ports: # 设置端口映射 - "8000:8000" - "8001:8001" - "8002:8002" - "8003:8003" - "9000:9000" - "9001:9001" - "9002:9002" - "9003:9003" stdin_open: true # 打开标准输入,可以接受外部输入 tty: true networks: - backend restart: always # 指定容器退出后的重启策略为始终重启 etcd: # 自定义容器名称 build: context: ./etcd # 指定构建使用的 Dockerfile 文件 environment: - TZ=${TZ} - ALLOW_NONE_AUTHENTICATION=yes ports: # 设置端口映射 - "${ETCD_PORT}:2379" networks: - backend restart: always mysql: build: context: ./mysql environment: - TZ=${TZ} - MYSQL_USER=${MYSQL_USERNAME} # 设置 Mysql 用户名称 - MYSQL_PASSWORD=${MYSQL_PASSWORD} # 设置 Mysql 用户密码 - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} # 设置 Mysql root 用户密码 volumes: - ${DATA_PATH_HOST}/mysql:/var/lib/mysql # 引用 .env 配置中 DATA_PATH_HOST 变量,将宿主机上存放 Mysql 数据的目录挂载到容器中 /var/lib/mysql 目录 ports: - "${MYSQL_PORT}:3306" # 设置容器3306端口映射指定宿主机端口 networks: - backend restart: always redis: build: context: ./redis environment: - TZ=${TZ} volumes: - ${DATA_PATH_HOST}/redis:/data # 引用 .env 配置中 DATA_PATH_HOST 变量,将宿主机上存放 Redis 数据的目录挂载到容器中 /data 目录 ports: - "${REDIS_PORT}:6379" # 设置容器6379端口映射指定宿主机端口 networks: - backend restart: always mysql-manage: build: context: ./mysql-manage environment: - TZ=${TZ} - PMA_ARBITRARY=1 - MYSQL_USER=${MYSQL_MANAGE_USERNAME} # 设置连接的 Mysql 服务用户名称 - MYSQL_PASSWORD=${MYSQL_MANAGE_PASSWORD} # 设置连接的 Mysql 服务用户密码 - MYSQL_ROOT_PASSWORD=${MYSQL_MANAGE_ROOT_PASSWORD} # 设置连接的 Mysql 服务 root 用户密码 - PMA_HOST=${MYSQL_MANAGE_CONNECT_HOST} # 设置连接的 Mysql 服务 host,可以是 Mysql 服务容器的名称,也可以是 Mysql 服务容器的 ip 地址 - PMA_PORT=${MYSQL_MANAGE_CONNECT_PORT} # 设置连接的 Mysql 服务端口号 ports: - "${MYSQL_MANAGE_PORT}:80" # 设置容器80端口映射指定宿主机端口,用于宿主机访问可视化web depends_on: # 依赖容器 - mysql # 在 Mysql 服务容器启动后启动 networks: - backend restart: always redis-manage: build: context: ./redis-manage environment: - TZ=${TZ} - ADMIN_USER=${REDIS_MANAGE_USERNAME} # 设置 Redis 可视化管理的用户名称 - ADMIN_PASS=${REDIS_MANAGE_PASSWORD} # 设置 Redis 可视化管理的用户密码 - REDIS_1_HOST=${REDIS_MANAGE_CONNECT_HOST} # 设置连接的 Redis 服务 host,可以是 Redis 服务容器的名称,也可以是 Redis 服务容器的 ip 地址 - REDIS_1_PORT=${REDIS_MANAGE_CONNECT_PORT} # 设置连接的 Redis 服务端口号 ports: - "${REDIS_MANAGE_PORT}:80" # 设置容器80端口映射指定宿主机端口,用于宿主机访问可视化web depends_on: # 依赖容器 - redis # 在 Redis 服务容器启动后启动 networks: - backend restart: always prometheus: build: context: ./prometheus environment: - TZ=${TZ} volumes: - ./prometheus/prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml # 将 prometheus 配置文件挂载到容器里 ports: - "${PROMETHEUS_PORT}:9090" # 设置容器9090端口映射指定宿主机端口,用于宿主机访问可视化web networks: - backend restart: always grafana: build: context: ./grafana environment: - TZ=${TZ} ports: - "${GRAFANA_PORT}:3000" # 设置容器3000端口映射指定宿主机端口,用于宿主机访问可视化web networks: - backend restart: always jaeger: build: context: ./jaeger environment: - TZ=${TZ} ports: - "${JAEGER_PORT}:16686" # 设置容器16686端口映射指定宿主机端口,用于宿主机访问可视化web networks: - backend restart: always dtm: build: context: ./dtm environment: - TZ=${TZ} entrypoint: - "/app/dtm/dtm" - "-c=/app/dtm/configs/config.yaml" volumes: - ./dtm/config.yml:/app/dtm/configs/config.yaml # 将 dtm 配置文件挂载到容器里 ports: - "${DTM_HTTP_PORT}:36789" - "${DTM_GRPC_PORT}:36790" networks: - backend restart: always 2.4 构建与运行 使用 docker-compose 命令来构建和启动运行我们的服务容器,在根目录执行如下命令: $ docker-compose up -d 容器构建中 在 Windows 系统容器构建中出现如下图所示,请选择 Share it 这将允许 Windows 的文件目录挂载到容器目录中。 容器已启动运行 2.5 容器说明 容器名称 暴露端口 host地址 说明 golang 8000:8000<br>8001:8001<br>8002:8002<br>8003:8003<br>9000:9000<br>9001:9001<br>9002:9002<br>9003:9003 golang 在生产环境中微服务一般都是集群部署,可能一个微服务一台服务器,也可能一个微服务一个容器。为了方便开发调试,我们将在 golang 容器中启动所有微服务,并为它们分配监听不同的端口号以示区分。<br>80:开头的端口号我们将用于 api 服务 <br>90:开头的端口号我们将用于 rpc 服务 dtm 36789:36789<br>36790:36790 dtm dtm 的 http 协议和 grpc 协议服务端口号,供客户端交互使用。<br>此项目中我们只在 Docker 内部容器之间访问使用,所以也可以不暴露端口号给宿主机 etcd 2379:2379 etcd Etcd http api 服务端口号,供客户端交互使用。<br>此项目中我们只在 Docker 内部容器之间访问使用,所以也可以不暴露端口号给宿主机 mysql 3306:3306 mysql Mysql 服务默认端口号,宿主机可通过 127.0.0.1:3306 进行数据库的连接 redis 6379:6379 redis Redis 服务默认端口号,宿主机可通过 127.0.0.1:6379 进行数据库的连接 mysql-manage 1000:80 mysql-manage phpMyAdmin web 服务端口号,可以在宿主机 127.0.0.1:1000 访问 redis-manage 2000:80 redis-manage phpRedisAdmin web 服务端口号,可以在宿主机 127.0.0.1:2000 访问 prometheus 3000:9090 prometheus Prometheus web 服务端口号,可以在宿主机 127.0.0.1:3000 访问 grafana 4000:3000 grafana Grafana web 服务端口号,可以在宿主机 127.0.0.1:4000 访问 jaeger 5000:16686 jaeger Jaeger web 服务端口号,可以在宿主机 127.0.0.1:5000 访问 2.6 访问验证 Mysql 访问验证 Redis 访问验证 Prometheus 访问验证 Grafana 访问验证 Jaeger 访问验证 项目地址 https://github.com/zeromicro/go-zero https://gitee.com/kevwan/go-zero 欢迎使用 go-zero 并 star 支持我们! 微信交流群 关注『微服务实践』公众号并点击 交流群 获取社区群二维码。/ving@nivin.cn

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册