Micro (1)

简单介绍

Micro是一个用来简化微服务开发的框架,提供了如下功能:

  1. Go Micro - 基于Golang的插件式RPC框架,提供服务发现,客户端负载均衡,编码,同步和异步通讯功能。
  2. API - API Gateway(API 网关), 用来提供处理http请求。可以作为一个http的反向代理或者翻译相关的http请求到RPC服务。
  3. Sidecar - 用来接入其他语言编写的应用到Micro中。
  4. Web - 提供一个web dashboard,并且可以为Micro应用提供反向代理。
  5. CLI - 用来跟Micro服务交互的命令行工具。
  6. Bot - 用它我们可以在我们的服务中与Slack, HipChat, XMPP通讯。

    架构如下图所示:
    Micro架构图

安装

  1. 由于Micro的服务发现并没有自己实现,仅仅是提供Plugin来接入第三方服务发现(consul, etcd), 默认使用的是consul
    安装参考: consul installation doc
  2. 安装protobuf
go get github.com/micro/protobuf/{proto,protoc-gen-go}
  1. 安装go-micro, 参考github go-micro
go get github.com/micro/go-micro
  1. 安装micro, 参考github micro
go get github.com/micro/micro

接下来我们简单使用micro来构建一个hello world 应用。

  1. 创建一个proto文件, proto语法请参考google protobuf
// hello_world.proto
syntax = "proto3";

service HelloWorld {
    rpc Hello(HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string greeting = 2;
}
  1. 通常我们需要将上述hello_world.proto文件编译输出相关语言的代码实现文件。
protoc --go_out=plugins=micro:. hello_world.proto

生成文件hello_world.pb.go

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: hello_world.proto

/*
Package hello_world is a generated protocol buffer package.

It is generated from these files:
    hello_world.proto

It has these top-level messages:
    HelloWorldRequest
    HelloWorldResponse
*/
package hello_world

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

import (
    client "github.com/micro/go-micro/client"
    server "github.com/micro/go-micro/server"
    context "golang.org/x/net/context"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package

type HelloWorldRequest struct {
    Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}

func (m *HelloWorldRequest) Reset()                    { *m = HelloWorldRequest{} }
func (m *HelloWorldRequest) String() string            { return proto.CompactTextString(m) }
func (*HelloWorldRequest) ProtoMessage()               {}
func (*HelloWorldRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

func (m *HelloWorldRequest) GetName() string {
    if m != nil {
        return m.Name
    }
    return ""
}

type HelloWorldResponse struct {
    Greeting string `protobuf:"bytes,2,opt,name=greeting" json:"greeting,omitempty"`
}

func (m *HelloWorldResponse) Reset()                    { *m = HelloWorldResponse{} }
func (m *HelloWorldResponse) String() string            { return proto.CompactTextString(m) }
func (*HelloWorldResponse) ProtoMessage()               {}
func (*HelloWorldResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }

func (m *HelloWorldResponse) GetGreeting() string {
    if m != nil {
        return m.Greeting
    }
    return ""
}

func init() {
    proto.RegisterType((*HelloWorldRequest)(nil), "HelloWorldRequest")
    proto.RegisterType((*HelloWorldResponse)(nil), "HelloWorldResponse")
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ client.Option
var _ server.Option

// Client API for HelloWorld service

type HelloWorldClient interface {
    Hello(ctx context.Context, in *HelloWorldRequest, opts ...client.CallOption) (*HelloWorldResponse, error)
}

type helloWorldClient struct {
    c           client.Client
    serviceName string
}

func NewHelloWorldClient(serviceName string, c client.Client) HelloWorldClient {
    if c == nil {
        c = client.NewClient()
    }
    if len(serviceName) == 0 {
        serviceName = "helloworld"
    }
    return &helloWorldClient{
        c:           c,
        serviceName: serviceName,
    }
}

func (c *helloWorldClient) Hello(ctx context.Context, in *HelloWorldRequest, opts ...client.CallOption) (*HelloWorldResponse, error) {
    req := c.c.NewRequest(c.serviceName, "HelloWorld.Hello", in)
    out := new(HelloWorldResponse)
    err := c.c.Call(ctx, req, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// Server API for HelloWorld service

type HelloWorldHandler interface {
    Hello(context.Context, *HelloWorldRequest, *HelloWorldResponse) error
}

func RegisterHelloWorldHandler(s server.Server, hdlr HelloWorldHandler, opts ...server.HandlerOption) {
    s.Handle(s.NewHandler(&HelloWorld{hdlr}, opts...))
}

type HelloWorld struct {
    HelloWorldHandler
}

func (h *HelloWorld) Hello(ctx context.Context, in *HelloWorldRequest, out *HelloWorldResponse) error {
    return h.HelloWorldHandler.Hello(ctx, in, out)
}

func init() { proto.RegisterFile("hello_world.proto", fileDescriptor0) }

var fileDescriptor0 = []byte{
    // 136 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcc, 0x48, 0xcd, 0xc9,
    0xc9, 0x8f, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x52, 0xe7,
    0x12, 0xf4, 0x00, 0x09, 0x86, 0x83, 0xc4, 0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84,
    0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25,
    0x03, 0x2e, 0x21, 0x64, 0x85, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x52, 0x5c, 0x1c, 0xe9,
    0x45, 0xa9, 0xa9, 0x25, 0x99, 0x79, 0xe9, 0x12, 0x4c, 0x60, 0xd5, 0x70, 0xbe, 0x91, 0x03, 0x17,
    0x17, 0x42, 0x87, 0x90, 0x11, 0x17, 0x2b, 0x98, 0x27, 0x24, 0xa4, 0x87, 0x61, 0xa1, 0x94, 0xb0,
    0x1e, 0xa6, 0xd9, 0x4a, 0x0c, 0x49, 0x6c, 0x60, 0x37, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff,
    0x80, 0x8c, 0xf7, 0x9b, 0xb8, 0x00, 0x00, 0x00,
}

下面简述其中几个关键输出:

package hello_world // 消息定义所在package
type HelloWorldRequest struct {
    Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
} //请求消息类

type HelloWorldResponse struct {
    Greeting string `protobuf:"bytes,2,opt,name=greeting" json:"greeting,omitempty"`
} // 响应消息类

type HelloWorldClient interface {
    Hello(ctx context.Context, in *HelloWorldRequest, opts ...client.CallOption) (*HelloWorldResponse, error)
} // 服务请求接口, 定义客户端可调用的请求方法'Hello', 客户端编程使用

type HelloWorldHandler interface {
    Hello(context.Context, *HelloWorldRequest, *HelloWorldResponse) error
} // 服务端interface, 定义服务端需要实现的方法'Hello', 服务端编程使用

func RegisterHelloWorldHandler(s server.Server, hdlr HelloWorldHandler, opts ...server.HandlerOption) {
    s.Handle(s.NewHandler(&HelloWorld{hdlr}, opts...))
} // 服务端使用该方法注册定义的Service, 服务端编程使用

type HelloWorld struct {
    HelloWorldHandler
}
func (h *HelloWorld) Hello(ctx context.Context, in *HelloWorldRequest, out *HelloWorldResponse) error {
    return h.HelloWorldHandler.Hello(ctx, in, out)
}
// 定义HelloWorld类,需要服务端实现其中的Handle的方法'Hello', 服务端编程使用

服务端实现

package main

import (
    "fmt"
    "./hello_world" // import proto生成的类
    "github.com/micro/go-micro"
    "golang.org/x/net/context"
)

type HelloWorld struct{}

func (g *HelloWorld) Hello(ctx context.Context, req *hello_world.HelloWorldRequest, rsp *hello_world.HelloWorldResponse) error {
    rsp.Greeting = "Hello World: " + req.Name
    return nil
} // 实现hello_world service中Hello方法

func main() {
    service := micro.NewService(
        micro.Name("hello_world"), // 定义service的名称为hello_world
        micro.Version("latest"),
        micro.Metadata(map[string]string{
            "type": "helloworld",
        }),
    )

    service.Init() // 初始化service

    hello_world.RegisterHelloWorldHandler(service.Server(), new(HelloWorld)) // 注册服务

    if err := service.Run(); err != nil {
        fmt.Println(err)
    } // 运行服务
}

客户端实现

package main

import (
    "fmt"
    "./hello_world"
    "github.com/micro/go-micro"
    "golang.org/x/net/context"
)

func main() {
    service := micro.NewService(
        micro.Name("hello_world"),
        micro.Version("latest"),
        micro.Metadata(map[string]string{
            "type": "helloworld",
        }),
    )

    service.Init()

    greeter := hello_world.NewHelloWorldClient("hello_world", service.Client()) // 创建服务hello_world的client对象, 以便调用其中定义的RPC方法'Hello'

    rsp, err := greeter.Hello(context.TODO(), &hello_world.HelloWorldRequest{Name: "Alice"}) // 传入HelloWorldRequest对象作为调用RPC方法的参数'Hello'
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(rsp.Greeting)
}

总结开发流程

  1. 公共开发模块
    • 创建消息protobuf文件, 在其中定义RPC方法
    • 根据定义的protobuf文件,生成基于该语言的实现类
  2. 服务端
    • 实现在消息中定义的RPC方法
    • 初始化一个新的Service对象
    • 注册服务
    • 运行服务
  3. 客户端
    • 初始化一个新的Service对象
    • 定义请求消息
    • 传入请求消息调用服务端定义RPC方法
    • 解释响应消息

代码请参考https://github.com/zouqilin/micro_demo/tree/master/hello_world

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 在互联网应用领域,服务的动态性需求十分常见,这就对服务的自动发现和可动态扩展提出了很高的要求。 微服务系统动辄上万...
    Liberalman阅读 8,038评论 23 80
  • 由于工程项目中拟采用一种简便高效的数据交换格式,百度了一下发现除了采用 xml、JSON 还有 ProtoBuf(...
    黄海佳阅读 48,529评论 1 23
  • 最近有朋友问我有没有用过GRPC ,我一直以为RESTful的流行让 RPC(Remote Procedure C...
    dimsky阅读 10,674评论 8 12
  • 找出世界上最成功的公司,研究它们的市场营销方法,调整修改,为你所用。市场营销中,小办法可以解决大问题,但是你必须得...
    农村的留守儿童阅读 285评论 0 0