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"