go-micro + consul 在线上项目中的实践

go-micro 是 go 语言生态中较为成熟,全面的框架,有点类似 spring boot 框架,它支持即插即用的插件,友好的中间件支持,负载均衡,服务注册发现,订阅发布,消息事件,配置中心,而且每个插件都支持多种技术,比如服务注册可以使用 consul,mdns,etcd,kubernetes等,你可以自行选择,切换也非常容易。go-micro 默认是 gRPC 服务,你也可以切换到别的 RPC。

 

我这里将介绍的 go-micro 一种使用方式与官方标准文档上使用的常用方式不一样,因为我不想写 proto,它是件挺麻烦的事儿,你得对消息(message)进行转换,从 proto 消息到你自己的结构体对象,还要从结构体对象转换成 proto 消息。go-micro 提供了对 noproto 的完美支持,而且使用起来比起使用 proto 文件生成 ***.pb.go 更加友好。接下来看看具体如何实现:

 

此线上的全部源码都已经提供在 github 上(https://github.com/iissy/goweb)目前已经有242个星,下面都还有对应的地址,本网站(爱斯园)已经在使用这套框架,性能非常优秀,每个请求都大概在50-500毫秒以内完成,读者可以试试。

 

使用到的 go-micro 包


"github.com/micro/go-micro/v2"
// 从文件加载配置,你也可以将配置写入 consul,或者甚至一个 gRPC 服务
"github.com/micro/go-micro/v2/config"
"github.com/micro/go-micro/v2/config/source/file"

// 服务注册发现
"github.com/micro/go-micro/v2/registry"

// 服务,调用相关
"github.com/micro/go-micro/v2/server"
"github.com/micro/go-micro/v2/client"

// consul 不是默认,需要引入插件
"github.com/micro/go-plugins/registry/consul/v2"

v2是tag,也就是版本,你也可以去掉v2,但是生产环境建议加上,可以减少兼容性问题

 

配置文件


{
  "mysql": {
    "hrefs": "root:123456@tcp(192.168.111.151:3306)/hrefs?charset=utf8",
    "MaxIdleConns": 5,
    "MaxOpenConns": 50,
    "gorp": {
      "trace-on": false
    }
  },
  "redis": {
    "host": "192.168.111.151",
    "port": 6379
  },
  "consul": [{
    "host": "192.168.111.151",
    "port": 8500
  }],
  "srv": "micro.hrefs.srv.test",
  "domain": "localhost"
}

 

初始化配置文件


const (
	defaultConfigPath = "conf/config.json"
)

func init() {
	loadConfig()
}

func loadConfig() {
	if err := config.Load(file.NewSource(
		file.WithPath(defaultConfigPath),
	)); err != nil {
		log.Panic(err)
	}
}

源码:https://github.com/iissy/goweb/blob/master/src/init.go

 

consul 注册


// 从配置文件获取consul地址
urls := utils.GetConsulUrls()
reg := consul.NewRegistry(func(op *registry.Options) {
	op.Addrs = urls
})

源码:https://github.com/iissy/goweb/blob/master/src/srv/run.go

 

服务方法


type Hrefs struct{}

func (s *Hrefs) GetArticle(ctx context.Context, req string, rsp *model.Article) error {
	result, err := domain.GetArticle(req)
	*rsp = *result
	return err
}

源码:https://github.com/iissy/goweb/blob/master/src/srv/web.go

 

启动服务


service := micro.NewService(
	micro.Registry(reg),
	micro.Name(config.Get("srv").String("micro.hrefs.srv")),
	micro.WrapHandler(logWrapper),
)
server.Init()
service.Server().Init(server.Wait(nil))
micro.RegisterHandler(service.Server(), new(Hrefs))
service.Run()

源码:https://github.com/iissy/goweb/blob/master/src/srv/run.go

 

封装一个客服端函数


var cli client.Client
var name string

func init() {
	service := micro.NewService()
	service.Init()
	cli = service.Client()
	name = config.Get("srv").String("micro.hrefs.srv")
}

func Call(method string, req interface{}, rsp interface{}) error {
	request := cli.NewRequest(name, fmt.Sprintf("Hrefs.%s", method), req, client.WithContentType("application/json"))

	if err := cli.Call(context.TODO(), request, &rsp); err != nil {
		fmt.Println(err)
		return err
	}

	return nil
}

源码:https://github.com/iissy/goweb/blob/master/src/cli/cli.go

 

web 端控制器调用


func Detail(ctx iris.Context) {
	id := ctx.Params().Get("id")
	result := new(model.Article)
	err := cli.Call("GetArticle", id, result)
	if ok := utils.WriteErrorLog(ctx.Path(), err); ok {
		ctx.NotFound()
		return
	}

	if result == nil {
		ctx.NotFound()
		return
	}

	ctx.ViewData("title", result.Title)
	ctx.ViewData("body", result)
	ctx.View("detail.html")
}

源码:https://github.com/iissy/goweb/blob/master/src/controller/index.go

 

没有 proto 的gRPC 微服务的优势:

  1. 无需维护一个 proto 文件
  2. 开发效率,使用成本更低
  3. 输入输出类型更加灵活,比如可以直接是 struct,string,bool 等类型
  4. 无需消息转换,可以直接使用 struct,string,bool 对象
  5. 适合少数人维护的项目

 

劣势:

  1. 如果接口经常变动,而使用方不是自己就容易出错
  2. 不方便多人协作和规范化
  3. 没有统一的 proto 文件,对外部使用不友好
Posted by 何敏 on 2020-03-24 08:46:46