go-micro + consul

https://www.jianshu.com/p/a94ca773342a (还没有完全理解透)

验证而tls下使用http2协议(抓包分析)

hystrix-go 服务降级熔断

go-micro支持其他语言,

------------------------------------------------

1.启动micro web服务(这个类似micro的dashboard,可以看到注册的服务,有一个简单的请求发起测试功能)

micro  --registry=consul web

micro注册到consul(要带ttl相关参数,否则在micro异常崩溃后,无法在consul上及时销毁)

//15秒注册一次,30秒超时

./micro --registry=consul --registry_address=192.168.153.128:8500 --register_ttl=30 --register_interval=15 --api_address=0.0.0.0:6565 api --handler=api  (好像参数直接最好一个空格)

 

2. micro new userinfo --namespace "taobao.user" --type="api" 
 (api和srv的区别是?

SRV服务基本上是标准的RPC服务,通常是您要编写的服务。 我们通常称之为RPC或后端服务,因为它作为后台架构的一部分,不应该暴露在最外层,默认使用go.micro.srv作为它的命名空间,在GateWay之外都可以调用到,但是调用url后面,srv要加/rpc

目前看来是api调用srv,搭建3层结构)
 创建的命名空间是taobao.user.srv.userinfo,(Python)客户端调用的时候,url 直接是/userinfo/...方法,并不需要区分srv或者api 
 但是启动micro api网关的时候要带上名称空间  micro --registry=consul --api_namespace="taobao.user.srv"  api  --handler=api(这个handler加上之后,在api真是服务中,才能获得到post等参数,比较坑,
 加上这个参数以后,不影响srv调用)
 greeter案例中,是典型的三层架构(httpcli->网关(http)->具体的服务(http接口api.go)->内部RPC调用(srv目录下的main.go))  
 srv的形式调用也要经过micro网关转发
  url = "http://localhost:8080/rpc"
    headers = {'content-type': 'application/json'}

    # Example echo method
    payload = {
    "service": "go.micro.srv.greeter",
        "method": "Say.Hello",
        "request": {"name": "John"},
    }    
 
 日志如何落盘? 如何查看环境变量?
 (https://www.jianshu.com/p/97157e088566 micro new生成api的时候,要包含"github.com/micro/go-api/proto/api.proto",所以要修改
 默认的protoc编译命令为: protoc --proto_path=/Users/ywy/work/goprj/src/   --proto_path=. --go_out=. --micro_out=. proto/example/example.proto, 添加了依赖路径)

测试命令: curl -H "Content-Type:application/json"  -XPOST "http://localhost:8080/greeter/say/hello?name=John"

https://www.jianshu.com/p/bd7ac51a6c16

python 调用该http api测试代码

import requests
import json

if __name__ == "__main__":    
    url = 'http://localhost:8080/fapitest/example/call'
    body = {"name": "John fish"}
    headers = {'content-type': "application/json"}  
    response = requests.post(url, data = json.dumps(body), headers = headers)
    print response
    print(response.content)

-----------------------

https://www.codercto.com/a/30019.html

go-micro 环境搭建https://www.codercto.com/a/22767.html (参考这个例子最顺利)

创建案例(注意例子中的proto文件相对路径)

https://www.codercto.com/a/49592.html

其中net库等,需要使用git clone下载(使用go module管理依赖export GO111MODULE=on,设置代理export GOPROXY=https://goproxy.io则不需要), 

git clone   https://github.com/golang/net.git

git clone   https://github.com/golang/text.git

git clone  https://github.com/golang/crypto.git

git clone https://github.com/golang/sys.git

 

 

consul集群搭建

https://blog.csdn.net/u010046908/article/details/61916389

mac对应的版本 consul_1.x.x_darwin_amd64.zip

consul 手册https://blog.csdn.net/liuzhuchen/article/details/81913562

consul指定自定义监听端口在concfg目录下创建basic.json

 ./consul agent -dev -config-dir /Users/XXX/work/mydoc/consulcfg

{
    "ports": {
        "server": 9300,
        "serf_lan": 9301,
        "serf_wan": 9302,
        "http": 9500,    //http的端口是consul ui的端口,也是服务端注册API的端口,客户端获取服务信息的端口
        "dns": 9600
    }
}

把配置修改为9500端口以后,添加的demo(如果不指定consul的IP和端口,则例子正确启动方式为:

go run hello.go --registry=consul ,go run client.go --registry=consul)

proto文件编译命令(可以在任意的文件夹里,只要protoc路径加入了环境变量, user.proto是proto文件)

protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. user.proto

服务端代码

package main

import (
	"context"
	"fmt"

	proto "gotest/gomicdemo"
	micro "github.com/micro/go-micro"
	"github.com/micro/go-micro/registry"
    "github.com/micro/go-micro/registry/consul"
)

type User struct{}

func (u *User)Hello(ctx context.Context, req *proto.Request, res *proto.Response)error {
	res.Msg = "Hello " + req.Name
	return nil
}

func main() {
	reg := consul.NewRegistry(func(op *registry.Options){
		op.Addrs = []string{
			"127.0.0.1:9500",   //consul的IP,端口
		}
	})
	
	service := micro.NewService(
		micro.Registry(reg),
		micro.Name("user"),
	)

	service.Init()

	proto.RegisterUserHandler(service.Server(), new(User))

	if err := service.Run(); err != nil {
		fmt.Println(err)
	}
}

客户端代码

package main

import (
	"context"
	"fmt"

	proto "gotest/gomicdemo"
	micro "github.com/micro/go-micro"
	"github.com/micro/go-micro/registry"
    "github.com/micro/go-micro/registry/consul"
)

func main() {

	reg := consul.NewRegistry(func(op *registry.Options){
		op.Addrs = []string{
			"127.0.0.1:9500",   //consul的IP,端口
		}
	})
	service := micro.NewService(micro.Registry(reg),micro.Name("user.client"))
	service.Init()
	user := proto.NewUserService("user", service.Client())
	res, err := user.Hello(context.TODO(), &proto.Request{Name: "World ^_^"})
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(res.Msg)
}

在一个proto文件中定义多个接口

syntax = "proto3";

service User {
    rpc Hello(Request) returns (Response) {}
}

message Request {
    string name = 1;
}

message Response {
    string msg = 1;
}

service Books {
    rpc GetBook(BooksReq) returns (BooksRsp) {}
    rpc AddBook(BooksReq) returns (BooksRsp) {}
}

message BooksReq {
    string name = 1;
}

message BooksRsp{
    string msg = 1;
}

client文件

package main

import (
	"context"
	"fmt"
	"time"
	proto "gotest/gomicdemo"
	micro "github.com/micro/go-micro"
	"github.com/micro/go-micro/registry"
    "github.com/micro/go-micro/registry/consul"
)

func main() {

	reg := consul.NewRegistry(func(op *registry.Options){
		op.Addrs = []string{
			"127.0.0.1:9500",   //consul的IP,端口
		}
	})
	//micro.Name("fish") 这个名字可以任意取
	service := micro.NewService(micro.Registry(reg),micro.Name("fish"))
	service.Init()

	for{
		//"user是服务的名称,和NewService时填入的名称保持一致,这个名称可以自定义,和proto中的接口没关系"
		//service := micro.NewService(micro.Registry(reg),micro.Name("fishserver"),	
		user := proto.NewUserService("user", service.Client())
		res, err := user.Hello(context.TODO(), &proto.Request{Name: "World ^_^"})
		if err != nil {
			fmt.Println(err)
		}
		fmt.Println(res.Msg)	

		booksuser := proto.NewBooksService("books", service.Client())
		res1, err1 := booksuser.GetBook(context.TODO(), &proto.BooksReq{Name: "World ^_^"})
		if err1 != nil {
			fmt.Println(err1)
		}
		fmt.Println(res1.Msg)

		res1, err1 = booksuser.AddBook(context.TODO(), &proto.BooksReq{Name: "World ^_^"})
		if err1 != nil {
			fmt.Println(err1)
		}
		fmt.Println(res1.Msg)
		time.Sleep(1*time.Second)

	}
	
}

Consul

https://www.cnblogs.com/li-peng/p/9598879.html(讲了订阅模式)

分别启动4台虚拟机,搭建集群,

./consul agent -server -bootstrap-expect=3 -data-dir=/alidata/usr/consul -node=server1 -client=0.0.0.0 -datacenter=shenzhen -ui
./consul agent -server -bootstrap-expect=3 -data-dir=/alidata/usr/consul -node=server2 -client=0.0.0.0 -datacenter=shenzhen -ui -join 192.168.153.128

./consul agent -server -bootstrap-expect=3 -data-dir=/alidata/usr/consul -node=server3 -client=0.0.0.0 -datacenter=shenzhen -ui -join 192.168.153.128
./consul agent -server -bootstrap-expect=3 -data-dir=/alidata/usr/consul -node=server4 -client=0.0.0.0 -datacenter=shenzhen -ui -join 192.168.153.128

在单个数据中心中,Consul分为Client和Server两种节点(所有的节点也被称为Agent),Server节点保存数据,
Client负责健康检查及转发数据请求到Server;Server节点有一个Leader和多个Follower,Leader节点会将数据同步到Follower,
Server的数量推荐是3个或者5个,在Leader挂掉的时候会启动选举机制产生一个新的Leader

-server
作用:指定节点为server
每个数据中心(DC)的server数推荐至少为1,至多为5
所有的server都采用raft一致性算法来确保事务的一致性和线性化,事务修改了集群的状态,且集群的状态保存在每一台server上保证可用性
server也是与其他DC交互的门面(gateway)
若不指定为-server,其实就是-client(如下: 不能和bootstrap-expect一起用)

./consul agent  -data-dir=/alidata/usr/consul -node=server -client=0.0.0.0 -datacenter=shenzhen -ui -join 192.168.153.128

-bootstrap-expect
在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用。一个集群可以有多于bootstrap-expect个数的server.当leader挂了之后,只要集群中半数的server还是OK的集群就会重新选举,产生新的leader,集群依然可用
使用 curl http://192.168.153.130:8500/v1/status/leader 来获取当前集群的ip:端口


-bootstrap:用来控制一个server是否在bootstrap模式,在一个datacenter中只能有一个server处于bootstrap模式,当一个server处于bootstrap模式时,
可以自己选举为raft leader。

-data-dir

server会记录API,节点id等信息,保存在data-dir目录下,如果重启修改了-node的的名字,因为data-dir目录下的id和集群中其他节点记录的本节点信息不一致,所以要先删除掉data-dir下的信息

 

API必须加入心跳检测,否则在consul上不会下线:

    registry.DefaultRegistry = consul.NewRegistry(func(op *registry.Options){

        op.Addrs = []string{

            "127.0.0.1:5500", //consul的IP,端口

        }        

 

在同一台电脑上也可以启动多个consul组建集群,但是join的时候要带serf_lan端口

  consul agent -server -bootstrap -data-dir=/Users/ywy/work/mydoc/consulcfg/data  -config-dir /Users/ywy/work/mydoc/consulcfg -node=server0 -bind=127.0.0.1  -client=0.0.0.0 -datacenter=shenzhen -ui 
  
  consul agent -server -bootstrap-expect=3 -data-dir=/Users/ywy/work/mydoc/consulcfg1/data  -config-dir /Users/ywy/work/mydoc/consulcfg1 -node=server1  -bind=127.0.0.1  -client=0.0.0.0 -datacenter=shenzhen -ui -join 127.0.0.1:9301
 
  consul agent -server -bootstrap-expect=3 -data-dir=/Users/ywy/work/mydoc/consulcfg2/data  -config-dir /Users/ywy/work/mydoc/consulcfg2 -node=server2  -bind=127.0.0.1  -client=0.0.0.0 -datacenter=shenzhen -ui -join 127.0.0.1:9301
  
  consul agent -data-dir=/Users/ywy/work/mydoc/conclient/data -node=client1  -config-dir /Users/ywy/work/mydoc/conclient  -bind=127.0.0.1 -client=0.0.0.0 -datacenter=shenzhen -ui -join 127.0.0.1:9301

    },  consul.TCPCheck(2 * time.Second))

这个兄弟文章挺好的:https://juejin.im/post/5c769656f265da2dd639068c

协议抓包分析

Request/Response - RPC通信基于支持双向流的请求/响应方式,我们提供有抽象的同步通信机制。请求发送到服务时,会自动解析、负载均衡、拨号、转成字节流。默认的传输协议是http/1.1,而tls下使用http2协议
异步消息(Async Messaging) - 发布订阅(PubSub)头等功能内置在异步通信与事件驱动架构中。事件通知在微服务开发中处于核心位置。默认的消息传送使用点到点http/1.1,激活tls时则使用http2

每个请求启动一个线程

protoc

protoc协议中消息字段定义(统一使用驼峰格式命名,单词之间不要使用下换线连接),使得生成的消息协议,在rpc api编解码和 json编解码中
json字段名是一样,这样调用方,使用发送kafka消息,或者调用go-micro API中生成json数据时候,只要写一套json就可以了

 


参考文章
https://www.jianshu.com/p/751cd31302e7
https://www.jianshu.com/p/bd7ac51a6c16
https://blog.csdn.net/benben_2015/article/details/92675678


micro启动的时候,启动goroutine负责更新/watch服务,更新路由表。
新的请求上来时候,查询本地路由表,是否有该rpc节点,没有则调用get(service)去服务端获取(注意名称默认是 命名空间. + 对象名.方法名,如果方式改变了,一定要设置命名空间,否则/service/path方法无法调用接口,只能/rpc )
因为/rpc的整个service name是客户端上报的)
也正是因为这样,比如go.micro.srv.service1这样命名的服务,在Micro网关之外,是不能通过/Service/Path这样的方法调用的,被隐藏保护起来了(因为命名空间默认是go.micro.api)
get(service string) ([]*registry.Service, error)

命名空间和micro不一样的服务,micro启动的时候,获取到路由,但是不存储(我们目前服务的命名空间:mr.micro.feed.queue.api,但是在环境变量中并没有配置,所以不支持Rest接口,只能用/rpc方式来调用)
HTTP路径式方法调用的时候,会去consul查询是否存在服务,因为命名空间不一样,服务名也不一样,总是返回500,但是/rpc的方式,会调用成功
if res == nil || res.Service == nil || !strings.HasPrefix(res.Service.Name, r.opts.Namespace){
    return
}


负载均衡 NewSelector client客户端会根据hash,轮询方式

api handler
api的handler指定为: API处理器接收任何的HTTP请求,并且向前转发指定格式的RPC请求,API兼容RPC
     handler指定为:RPC的时候,HTTP请求的Content-Type只能是: application/json or application/protobuf
     
1./rpc只支持Post方法,API都支持
2./rpc的内容只能是'Content-Type: application/json'或application/protobuf,API都可以,API兼容rpc(因为micro api注册了RPC handler和API handler, 如果路由不匹配,就会回退到使用rpc handler)

测试:  
  curl   -d '{"service": "mr.micro.feed.queue.api.collect", "method": "Collect.Init", "request": {"user": {"deviced_id": "5cd0f86e1893be1145bda685"}, "ver" : 2}}'  "http://localhost:6565/collect/Init"  
  消息处理函数 ServeHTTP(w http.ResponseWriter, req *http.Request),API,HTTP等都实现了这个函数
  
  
  //rpc方式
  curl -H 'Content-Type: application/json' -d '{"service": "mr.micro.feed.queue.api.collect", "method": "Collect.Init", "request": {"user": {"deviced_id": "5cd0f86e1893be1145bda685"}, "ver" : 2}}'  http://localhost:6565/rpc
    
 //也可以直接调用srv
 curl -H 'Content-Type: application/json' -d '{"service": "mr.micro.feed.queue.srv.storage", "method": "Storage.AccountInbox", "request": {"user": {"deviced_id": "5cd0f86e1893be1145bda685"}, "timestamp" : 0}}'  http://localhost:6565/rpc
 curl -H 'Content-Type: application/json' -d '{"service": "mr.micro.feed.queue.api.collect", "method": "Collect.Init", "request": {"user": {"deviced_id": "5cd0f86e1893be1145bda685"}, "ver" : 2}}'  http://localhost:6565/rpc
 处理Rpc请求,的代码在func RPC(w http.ResponseWriter, r *http.Request)

curl   -d '{"service": "mr.micro.feed.queue.api.collect", "method": "Collect.Init", "request": {"user": {"deviced_id": "5cd0f86e1893be1145bda685"}, "ver" : 2}}'  "http://localhost:6565/collect/Init78"

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值