项目依赖

推荐使用go module, 我选择go module的最主要原因是足够简单,可以脱离gopath,就跟写nodejs一样,随便在一个地方新建一个文件夹就可以撸代码了,clone下来的源码也可以直接跑,不需要设置各种gopath之类的。go-micro原本也是传统管理依赖来写的,然后有一个issue里,作者说他不会把micro项目的依赖管理改成go module,直到go module成为标准。后来,在一夜之间,作者把全部的micro项目都改成了go module。

项目结构

一个模块使用一个大文件夹,其中又分api、cli、srv三个文件夹。srv文件夹用来写后端微服务,供其他微服务内部访问;api文件夹用来写http接口,供用户访问;cli文件夹用来写客户端, 生成command line程序,接口测试等,各种语言都可以

三层架构

在之前的博客里牌类游戏使用微服务重构笔记(二): micro框架简介:micro toolkit提过,搭配使用micro api网关时,推荐使用三层架构组织服务。

这里就拿商城为例分享我的方案,笔者没有做过电商,仅仅是用来举例,无须在意数据结构的合理性。

  • srv 后端服务

提供最小粒度的服务,一般来说尽可能不考虑业务,如某一类数据的crud。在我的项目中没有在这一层使用验证,因为暂时用不到,任何服务都可以直接访问。

商城里有商品, 所以有一个提供商品的服务 go.micro.srv.good

syntax = "proto3";package good;service GoodSrv {// 创建商品rpc CreateGood(CreateGoodRequest) returns (CreateGoodResponse) {}// 查找商品rpc FindGoods(FindGoodsRequest) returns(FindGoodsResponse) {}
}// 创建商品请求
message CreateGoodRequest {string name = 1; // 名称repeated Image images = 2; // 图片float price = 3; // 价格repeated string tagIds = 4; // 标签
}// 创建商品响应
message CreateGoodResponse {Good good = 1;
}// 查找商品请求
message FindGoodsRequest {repeated string goodIds = 1;
}// 查找商品响应
message FindGoodsResponse {repeated Good goods = 1;
}// 商品数据结构
message Good {string id = 1; // idstring name = 2; // 名称repeated Image images = 3; // 图片float price = 4; // 价格repeated string tagIds = 5; // 标签
}// 图片数据结构
message Image {string url = 1;bool default = 2;
}
复制代码

这个服务提供了两个接口,创建商品、查找商品。

商品有各种各样的标签,再写一个标签服务 go.micro.srv.tag

syntax = "proto3";package tag;service TagSrv {// 获取标签rpc FindTags (FindTagsRequest) returns (FindTagsResponse) {}
}// 获取标签请求
message FindTagsRequest {repeated string tagIds = 1;
}// 获取标签响应
message FindTagsResponse {repeated Tag tags = 1;
}// 标签数据结构
message Tag {string id = 1; // idstring tag = 2; // 标签
}
复制代码
  • cli

假如要写一个客户端程序,获取并打印商品列表

python

import requests
import jsondef main():url = "http://localhost:8080/rpc"headers = {'content-type': 'application/json'}# Example echo methodpayload = {"endpoint": "GoodSrv.FindGoods","service": "go.micro.srv.good","request": {}}response = requests.post(url, data=json.dumps(payload), headers=headers).json()print responseif __name__ == "__main__":main()复制代码

运行输出

{u'goods': []}
复制代码

golang

package mainimport ("context""github.com/micro/go-micro""log"pb "micro-blog/micro-shop/srv/good/proto"
)func main() {s := micro.NewService()cli := pb.NewGoodSrvService("go.micro.srv.good", s.Client())response ,err := cli.FindGoods(context.TODO(), &pb.FindGoodsRequest{GoodIds: []string{"1", "2"}})if err != nil {panic(err)}log.Println("response:", response)
}复制代码
  • api http接口服务

api层也是微服务,是组装其他各种微服务,完成业务逻辑的地方。主要提供http接口,如果micro网关设置--handler=web 还可以支持websock。现完成一个获取商品列表的http接口。

proto

syntax = "proto3";import "micro-blog/micro-shop/srv/good/proto/good.proto";
import "micro-blog/micro-shop/srv/tag/proto/tag.proto";package shop;service Shop {// 获取商品rpc GetGood(GetGoodRequest) returns(GetGoodResponse) {}
}// 商城物品
message ShopItem {good.Good good = 1;repeated tag.Tag tags = 2;
}// 获取商品请求
message GetGoodRequest {string goodId = 1;
}// 获取商品响应
message GetGoodResponse {ShopItem item = 1;
}复制代码
使用gin完成api
package mainimport ("context""github.com/gin-gonic/gin""github.com/micro/go-micro/client""github.com/micro/go-micro/errors""github.com/micro/go-web""log""micro-blog/micro-shop/api/proto"pbg "micro-blog/micro-shop/srv/good/proto"pbt "micro-blog/micro-shop/srv/tag/proto"
)// 商城Api
type Shop struct{}// 获取商品
func (s *Shop) GetGood(c *gin.Context) {id := c.Query("id")cli := client.DefaultClientctx := context.TODO()rsp := &shop.GetGoodResponse{}// 获取商品goodsChan := getGoods(cli, ctx, []string{id})goodsReply := <-goodsChanif goodsReply.err != nil {c.Error(goodsReply.err)return}if len(goodsReply.goods) == 0 {c.Error(errors.BadRequest("go.micro.api.shop", "good not found"))return}// 获取标签tagsChan := getTags(cli, ctx, goodsReply.goods[0].TagIds)tagsReply := <-tagsChanif tagsReply.err != nil {c.Error(tagsReply.err)return}rsp.Item = &shop.ShopItem{Good: goodsReply.goods[0],Tags: tagsReply.tags,}c.JSON(200, rsp)
}// 商品获取结果
type goodsResult struct {err errorgoods []*pbg.Good
}// 获取商品
func getGoods(c client.Client, ctx context.Context, goodIds []string) chan goodsResult {cli := pbg.NewGoodSrvService("go.micro.srv.good", c)ch := make(chan goodsResult, 1)go func() {res, err := cli.FindGoods(ctx, &pbg.FindGoodsRequest{GoodIds: goodIds,})ch <- goodsResult{goods: res.Goods, err: err}}()return ch
}// 标签获取结果
type tagsResult struct {err errortags []*pbt.Tag
}// 获取标签
func getTags(c client.Client, ctx context.Context, tagIds []string) chan tagsResult {cli := pbt.NewTagSrvService("go.micro.srv.tag", client.DefaultClient)ch := make(chan tagsResult, 1)go func() {res, err := cli.FindTags(ctx, &pbt.FindTagsRequest{TagIds: tagIds})ch <- tagsResult{tags: res.Tags, err: err}}()return ch
}func main() {// Create serviceservice := web.NewService(web.Name("go.micro.api.shop"),)service.Init()// Create RESTful handler (using Gin)router := gin.Default()// Register Handlershop := &Shop{}router.GET("/shop/goods", shop.GetGood)// 这里的http根路径要与服务名一致service.Handle("/shop", router)// Run serverif err := service.Run(); err != nil {log.Fatal(err)}
}复制代码
执行
curl -H 'Content-Type: application/json' \-s "http://localhost:8080/shop/goods"
复制代码
分析
  • 首先使用gin提供的方法从http请求中获取商品id
  • 向go.micro.srv.good服务发起rpc 获取商品信息
  • 向go.micro.srv.tag服务发起rpc 获取标签信息
  • 返回结果
注意

可以发现,如果使用gin,api中的proto定义貌似就没什么意义了,因为获取http请求参数的方法都是gin提供的。如果要使用上这个proto, 可以将micro网关的处理器设置为api micro api --handler=api,请求将会自动解析成自己写的proto结构,详情可见之前的博客 牌类游戏使用微服务重构笔记(二): micro框架简介:micro toolkit 处理器章节

不过也可以使用gin提供的c.BindJSON c.BindQuery来手动解析成proto结构

Token认证

上文中的获取商品列表的http请求是没有任何认证的, 谁都可以进行访问, 实际项目中可能会有验证。http验证的方式非常多,这里以jsonWebToken举例实现一个简单的验证方法。

实现一个用户微服务, 提供签名token和验证token的rpc方法

proto
syntax = "proto3";
package user;// 用户后端微服务
service UserSrv {// 签名tokenrpc SignToken(SignTokenRequest) returns(PayloadToken) {}// 验证tokenrpc VerifyToken(VerifyTokenRequest) returns(PayloadToken) {}
}// token信息
message PayloadToken {int32 id = 1;string token = 2;int32 expireAt = 3;
}// 签名token请求
message SignTokenRequest {int32 id = 1;
}// 验证token请求
message VerifyTokenRequest {string token = 1;
}
复制代码

代码完成后,在api里就可以进行token验证了

// token 验证
payload, err := s.UserSrvClient.VerifyToken(context.Background(), &pbu.VerifyTokenRequest{Token: c.GetHeader("Authorization")})
if err != nil {c.Error(err)return
}
复制代码

非常的方便,完全不需要了解认证的代码,更没有响应依赖。如果不想写的到处都是可以放在中间件里完成

错误处理

protoc micro插件生成的代码里把原生pb文件包了一层,每个rpc接口都有一个错误返回值,如果要抛出错误只需要return错误即可

func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {return errors.BadRequest("go.micro.srv.greeter", "test error")
}
复制代码

错误的定义使用micro包提供的errors包

错误结构体

// Error implements the error interface.
type Error struct {Id     string `json:"id"` // 错误的id 可根据需求自定义Code   int32  `json:"code"` // 错误码Detail string `json:"detail"` // 详细信息Status string `json:"status"` // http状态码
}// 实现了error接口
func (e *Error) Error() string {b, _ := json.Marshal(e)return string(b)
}
复制代码

同时也提供了经常用到的各种错误类型,如

// BadRequest generates a 400 error.
func BadRequest(id, format string, a ...interface{}) error {return &Error{Id:     id,Code:   400,Detail: fmt.Sprintf(format, a...),Status: http.StatusText(400),}
}// Unauthorized generates a 401 error.
func Unauthorized(id, format string, a ...interface{}) error {return &Error{Id:     id,Code:   401,Detail: fmt.Sprintf(format, a...),Status: http.StatusText(401),}
}// Forbidden generates a 403 error.
func Forbidden(id, format string, a ...interface{}) error {return &Error{Id:     id,Code:   403,Detail: fmt.Sprintf(format, a...),Status: http.StatusText(403),}
}// NotFound generates a 404 error.
func NotFound(id, format string, a ...interface{}) error {return &Error{Id:     id,Code:   404,Detail: fmt.Sprintf(format, a...),Status: http.StatusText(404),}
}
复制代码

跨域支持

本地开发的时候,使用micro toolkit会遇到跨域问题。在早期的micro toolkit版本中可以通过micro api --cors=truemicro web --cors=true来允许跨域,后来因为作者说这个支持并不成熟移除了,见issue。

目前可以通过go-plugins自己编译micro得到支持或者其他方式,自定义header也是一样。micro plugin提供了一些接口,一些特定需求都可以通过插件来解决

package corsimport ("net/http""strings""github.com/micro/cli""github.com/micro/micro/plugin""github.com/rs/cors"
)type allowedCors struct {allowedHeaders []stringallowedOrigins []stringallowedMethods []string
}func (ac *allowedCors) Flags() []cli.Flag {return []cli.Flag{cli.StringFlag{Name:   "cors-allowed-headers",Usage:  "Comma-seperated list of allowed headers",EnvVar: "CORS_ALLOWED_HEADERS",},cli.StringFlag{Name:   "cors-allowed-origins",Usage:  "Comma-seperated list of allowed origins",EnvVar: "CORS_ALLOWED_ORIGINS",},cli.StringFlag{Name:   "cors-allowed-methods",Usage:  "Comma-seperated list of allowed methods",EnvVar: "CORS_ALLOWED_METHODS",},}
}func (ac *allowedCors) Commands() []cli.Command {return nil
}func (ac *allowedCors) Handler() plugin.Handler {return func(ha http.Handler) http.Handler {hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {ha.ServeHTTP(w, r)})return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {cors.New(cors.Options{AllowedOrigins:   ac.allowedOrigins,AllowedMethods:   ac.allowedMethods,AllowedHeaders:   ac.allowedHeaders,AllowCredentials: true,}).ServeHTTP(w, r, hf)})}
}func (ac *allowedCors) Init(ctx *cli.Context) error {ac.allowedHeaders = ac.parseAllowed(ctx, "cors-allowed-headers")ac.allowedMethods = ac.parseAllowed(ctx, "cors-allowed-methods")ac.allowedOrigins = ac.parseAllowed(ctx, "cors-allowed-origins")return nil
}func (ac *allowedCors) parseAllowed(ctx *cli.Context, flagName string) []string {fv := ctx.String(flagName)// no opif len(fv) == 0 {return nil}return strings.Split(fv, ",")
}func (ac *allowedCors) String() string {return "cors-allowed-(headers|origins|methods)"
}// NewPlugin Creates the CORS Plugin
func NewPlugin() plugin.Plugin {return &allowedCors{allowedHeaders: []string{},allowedOrigins: []string{},allowedMethods: []string{},}
}
复制代码

修改micro源码 添加插件

package mainimport ("github.com/micro/micro/plugin""github.com/micro/go-plugins/micro/cors"
)func init() {plugin.Register(cors.NewPlugin())
}
复制代码

使用

micro api \--cors-allowed-headers=X-Custom-Header \--cors-allowed-origins=someotherdomain.com \--cors-allowed-methods=POST
复制代码

令人疑惑的 NewService

之前的博客中创建一个后端服务,我们使用了

micro.NewService(micro.Name("go.micro.srv.greeter"),micro.Version("latest"),
)
复制代码

而在api层的微服务,我们使用了

service := web.NewService(web.Name("go.micro.api.greeter"),
)
复制代码

api层如果使用api处理器

service := micro.NewService(micro.Name("go.micro.api.greeter"),
)
复制代码

而使用使用grpc(后文会讲到,我们又要使用

service := grpc.NewService(micro.Name("go.micro.srv.greeter"),
)
复制代码

~hat the *uck?
其实这都是micro特意这样设计的,目的是为了即使从http传输改变到grpc, 只需要改变一行代码,其他的什么都不用变(感觉很爽...),后面的博客源码分析会详细讲

之前讲过,micro中微服务的名字定义为[命名空间].[资源类型].[服务名]的,而micro api代理访问api类型的资源,比如go.micro.api.greeter,micro web代理访问web类型的资源,比如go.micro.web.greeter

web类型的资源与web.NewService是没什么关系的,还是要看资源类型的定义,上文中我们使用到了gin框架或者websocket不使用service提供的server而使用web提供的server因此使用web.NewService来创建服务,后面分析源码之后就更清楚了

下面以web.NewService创建一个websocket服务并使用micro api代理来举例

package mainimport ("github.com/micro/go-web""gopkg.in/olahol/melody.v1""log""net/http"
)func main() {// New web serviceservice := web.NewService(web.Name("go.micro.api.gateway"),)// parse command lineservice.Init()m := melody.New()m.HandleDisconnect(HandleConnect)// Handle websocket connectionservice.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {m.HandleRequest(w, r)})// run serviceif err := service.Run(); err != nil {log.Fatal("Run: ", err)}
}// 处理用户连接
func HandleConnect(session *melody.Session) {log.Println("new connection ======>>>")
}复制代码

浏览器代码

wsUri  = "ws://" + "localhost:8080/gateway"var print = function(message) {var d       = document.createElement("div");d.innerHTML = message;output.appendChild(d);
};var newSocket = function() {ws           = new WebSocket(wsUri);ws.onopen = function(evt) {print('<span style="color: green;">Connection Open</span>');}ws.onclose = function(evt) {print('<span style="color: red;">Connection Closed</span>');ws = null;}ws.onmessage = function(evt) {print('<span style="color: blue;">Onmessage: </span>' + parseCount(evt));}ws.onerror = function(evt) {print('<span style="color: red;">Error: </span>' + parseCount(evt));}};复制代码

可以正常连接到websocket(我在项目中是使用micro web来代理websocket 这里仅仅是举例)

本章未完待续

一下想不完使用经验,后续想到哪里会再补充

本人学习golang、micro、k8s、grpc、protobuf等知识的时间较短,如果有理解错误的地方,欢迎批评指正,可以加我微信一起探讨学习

牌类游戏使用微服务重构笔记(四): micro框架使用经验相关推荐

  1. 牌类游戏使用微服务重构笔记(八): 游戏网关服务器

    网关服务器 所谓网关,其实就是维持玩家客户端的连接,将玩家发的游戏请求转发到具体后端服务的服务器,具有以下几个功能点: 长期运行,必须具有较高的稳定性和性能 对外开放,即客户端需要知道网关的IP和端口 ...

  2. Spring Cloud微服务开发笔记4——Ribbon框架使用方法

    2019独角兽企业重金招聘Python工程师标准>>> 本文介绍Netfilx的第二个框架--Ribbon. (1)Ribbon是Netflix下的负载均衡框架,支持可插拔式的负载均 ...

  3. 游戏 服务器 微服务_整体服务器与微服务

    游戏 服务器 微服务 介绍 刚开始时,由于要求简单,所以应用程序既简单又小. 随着时间的要求和需求的增长,我们的应用程序变得越来越大,越来越复杂. 这就导致了将单片服务器开发和部署为一个单元. 在某种 ...

  4. Java微服务学习笔记(一):微服务架构的概念理解

    Java微服务学习笔记 Tips:入门学习时粗略整理,仅供参考 (一):架构的基础理解 文章目录 Java微服务学习笔记 前言 一.微服务是什么? 二.常用开源微服务框架演化 1. Dubbo 2. ...

  5. SpringCloudAlibaba系列微服务搭建笔记一_Nacos

    目录儿 二.SpringCloud技术栈 三.环境搭建 3.1 开发环境搭建 3.2 安装部署mysql 3.3 创建 SpringBoot 项目 3.3.1 简介 3.3.2 构建项目 3.3.3 ...

  6. 8 位阿里大佬合著“Dubbo 微服务进阶笔记”

    前言 微服务是近几年流行起来的软件架构风格.回顾历史,从传统的单体应用架构,到面向服务架构 SOA,再到今天逐渐被大众接受的微服务架构 MSA,本质上来说,都是为了解决随着软件复杂度的上升,如何有效提 ...

  7. 8位阿里P8合著“Dubbo微服务进阶笔记”一经面世,Github上标星93K+

    前言 微服务是近几年流行起来的软件架构风格.回顾历史,从传统的单体应用架构,到面向服务架构SOA,再到今天逐渐被大众接受的微服务架构MSA,本质上来说,都是为了解决随着软件复杂度的上升,如何有效提升开 ...

  8. springcloud微服务学习笔记

    此篇内容较长. 目录 一.关于微服务 二.微服务工程的构建 1.新建maven父工程 2.新建公共模块 3.新建微服务提供者8001 4.新建微服务消费者80 三.服务和发现注册中心(Eureka) ...

  9. 13 年 Java 老兵的微服务战地笔记 | 文末有1元福利

    * 文末有仅限 24 小时的 1 元福利,错过别怪我!!! 微服务在业内的实践已经从流行走向成熟,诸多公司(比如 Amazon.Netflix.蚂蚁金服.网易云音乐等)都已经迁移并采用了微服务架构.而 ...

最新文章

  1. tensorflow models 工程解析
  2. VTK:PolyData之DijkstraGraphGeodesicPath
  3. VTK:模型之Delaunay3DDemo
  4. 以太坊测试链环境node.js版本
  5. leetcode1233. 删除子文件夹
  6. Kubernetes Ingress入门指南和实践练习
  7. Midi 乐器set
  8. 百度SEO站群爱客影院v3.5自动采集影视网站源码
  9. Maven dependency plugin使用
  10. 在无参考数据集(比如LIME、MEF、DICM)上使用NIQE指标
  11. 计算机多媒体制作三级证书,多媒体作品制作员(师)国家职业标准
  12. 办公文件实时自动同步工具-FileYee,好用!
  13. 地图-导航(百度/高德)
  14. Oracle的 wm_concat 的排序问题,Oracle的 listagg 函数[转]
  15. CODING 携手 Thoughtworks 助力老百姓大药房打造”自治、自决、自动”的敏捷文化
  16. 网贷公司是什么意思? 网贷公司如何挑选更安心?【理财帮手】
  17. 生信笔记:E值究竟是什么?!!!
  18. 带计算机的笔记本图片,带上这些高性能的轻薄笔记本 出差旅行再也不用怕
  19. Linux ANSYS Fluent计算集群配置
  20. Linux文本文件编辑命令

热门文章

  1. 【APIO2014】Palindromes
  2. Java EE (11) - 影响性能的因素
  3. Windows Server 2008技术概述(自CSDN)
  4. 玩转“网上邻居”之WINS解析(一)
  5. NLPIR-KGB知识图谱引擎突破传统数据挖掘束缚
  6. 深入解析Spring架构与设计原理-AOP
  7. 求从第一列走到第n列的最短路径
  8. 创建 Web 前端开发环境(node和npm)
  9. Windows Server入门系列38 访问网络共享
  10. YII相关知识点记录