1 Star 1 Fork 0

guanzhanyi / blog

Create your Gitee Account
Explore and code with more than 8 million developers,Free private repositories !:)
Sign up
The current repository's web page is accessible only to members. If you need to make it public, please ask author guanzhanyi to submit applications.
This repository doesn't specify license. Please pay attention to the specific project description and its upstream code dependency when using it.
Clone or Download
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README.md

go-kit微服务实践

项目描述

keypoints

go-kit
  • 分为三层:

    service: 定义业务逻辑

    endpoint: 调用service处理请求和返回响应

    transport: 连接endpoint的request和response

grpc
  • 安装

    编译安装protobuf

    安装go-proto go get -u github.com/golang/protobuf/proto

    安装protoc go get -u github.com/golang/protobuf/protoc-gen-go

  • 使用

    编写protobuf文件

    syntax = "proto3";
    //
    package pb;
    //生成的go文件路径
    option go_package = "./";
    //函数
    service UserService{
        rpc CheckPassword(LoginRequest) returns (LoginResponse){}
    }
    //结构体
    message LoginRequest{
        string Username = 1;
        string Passowrd = 2;
    }
    

    生成pb.go文件

    $ protoc --go_out=plugins=grpc:. *.proto

JWT
//生成token
//初始化与token有关的数据结构: 主要有jwt库提供的标准声明和自定义的数据. 标准声明有过期时间, 创建时间,发行商和主题. 自定义的数据一般有鲜明且唯一的用户特征, 比如用户名
type Claims struct{
	Username string `json:"username"`
	jwt.StandardClaims
}

claims:=Claims{
		username,
		jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(),//过期时间
			IssuedAt: time.Now().Unix(),
			Issuer: "gyp",//发行商
			Subject: "user token",//主题
		},
	}
//生成加密后的token
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256,claims)
//加上签名, 认证数据来源
token,err:=tokenClaims.SignedString(jwtSecret)
//验证token
//把token解密
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    return jwtSecret, nil
})
//验证解密后的token是否是Claims结构体
if tokenClaims != nil {
    if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
        return claims, nil
    }
}

使用了中间件的方式嵌入到gin中, JWT()函数返回gin.HandlerFunc, 在路由中通过Use使用

func JWT() gin.HandlerFunc{
	//gin.Context是gin 中最重要的部分, 用于传递变量, 管理流程, 验证json
	return func(c *gin.Context){
        //调用generateToken()和ParseToken()
        ...
    }
}
apiv1:=r.Group("/api/v1")
apiv1.Use(jwt.JWT())
rate
r := rate.NewLimiter(1, 5) //1表示每次放进筒内的数量,桶内的令牌数是5,最大令牌数也是5,这个筒子是自动补充的,你只要取了令牌不管你取多少个,这里都会在每次取完后自动加1个进来,因为我们设置的是1
ctx := context.Background()
r.waitN(ctx,2) //每次消耗两个
wait()

Allow()
r.AllowN(time.Now(), 2) { //AllowN表示取当前的时间,这里是一次取2个,如果当前不够取两个了,本次就不取,再放一个进去,然后返回false

//Allow 无可用token则返回false
//Wait无可用token会阻塞住,直到获取一个token,或者超时或取消
Reserve()
ReserveN()
consul
//安装consul
$ docker pull consul
// 启动consul
// -server表示以服务端的方式启动
// -boostrap 指定自己为leader, 而不需要选举
// -ui 启动内置管理web页面
// -client指定客户端的ip, 0.0.0.0表示本地的都可以访问
// 8500是后台UI端口
// -node 节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名
// -bootstrap-expect 在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用
$ docker run \
    -d \
    -p 8500:8500 \
    -p 8600:8600/udp \
    --name=badger \
    consul agent -server -ui -node=server1 -bootstrap-expect=1 -client=0.0.0.0

//注册可以手动通过json注册, 也可以通过函数注册 //健康检测机制是通过定时发送报文并且得到响应完成的

//手动注册服务
$ vim imagecode.json
{
	"ID":"imagecode",
	"Name":"imagecode",
	"Tags":{
		"iHome"
	},
	"Address":"106.52.19.86",
	"Port":8081,
	"Check":{
		"HTTP":"http://106.52.19.86/api/v1/imagecode-health",
		"InterVal":"5s"
	}
}
//导入服务
$ curl --request PUT --data @imagecode.json localhost:8500/v1/agent/service/register
//反注册服务
$ curl --request PUT --data @imagecode.json loservice/deregister/imagecode
通过函数导入和注册服务
package consul

import (
    consulapi "github.com/hashicorp/consul/api"
    "log"
)

func RegService() {
    config := consulapi.DefaultConfig()
    config.Address = "39.107.76.100:8500"
    reg := consulapi.AgentServiceRegistration{}
    reg.Name = "userservice" //注册service的名字
    reg.Address = "39.107.76.100" //注册service的ip
    reg.Port = 8080//注册service的端口
    reg.Tags = []string{"primary"}

    check := consulapi.AgentServiceCheck{} //创建consul的检查器
    check.Interval="5s" //设置consul心跳检查时间间隔
    check.HTTP = "http://39.107.76.100:8080/health" //设置检查使用的url

    reg.Check = &check

    client, err := consulapi.NewClient(config) //创建客户端
    if err != nil {
        log.Fatal(err)
    }
    err = client.Agent().ServiceRegister(&reg)
    if err != nil {
        log.Fatal(err)
    }
}
//优雅退出consul
  • 结果

consul.png

consul_service.png

hystrix
服务熔断处理

DoGo类似, 不过Go是异步方式, Do是同步方式

Do有三个参数: Command 处理正常逻辑的函数 处理异常逻辑的函数

通过ConfigureCommand函数配置Command的参数

hystrix.ConfigureCommand("wuqq", hystrix.CommandConfig{

    Timeout:                int(3 * time.Second),//超时时间

    MaxConcurrentRequests:  10,                 //最大并发量

    SleepWindow:            5000,               //当熔断器被打开后,SleepWindow 的时间就是控制过多久后去尝试服务是否可用了

    RequestVolumeThreshold: 10,                 //一个统计窗口10秒内请求数量。达到这个请求数量后才去判断是否要开启熔断

    ErrorPercentThreshold:  30,                 //错误百分比,请求数量大于等于RequestVolumeThreshold并且错误率到达这个百分比后就会启动熔断

})

_ = hystrix.Do("wuqq", func() error {

    // talk to other services

    _, err := http.Get("https://www.baidu.com/")

    if err != nil {

        fmt.Println("get error:%v",err)

        return err        }

    return nil

}, func(err error) error {

    fmt.Printf("handle  error:%v\n", err)

    return nil

})
ini
`"github.com/go-ini/ini"` //建一个*.ini文件 //文件格式由的领域[] RUN_MODE = debug

[app] PAGE_SIZE = 10 JWT_SECRET = 23347$040412

[server] HTTP_PORT = 8000 READ_TIMEOUT = 60

//通过ini.Load()初始化 //通过GetSection()获取领域 //通过Key()获取值 //通过Mustxx()判断值类型和匹配

validation
github.com/astaxie/beego/validation

valid := validation.Validation{}
valid.Required(name,"name").Message("名字不能为空")
valid.MaxSize(name, 100, "name").Message("名称最长为100字符")
valid.Required(createdBy, "created_by").Message("创建人不能为空")
valid.MaxSize(createdBy, 100, "created_by").Message("创建人最长为100字符")
valid.Range(state, 0, 1, "state").Message("状态只允许0或1")
valid.HasErrors() 
Jaeger
tracer

span

不太明白链路追踪中client的作用

Swagger
  • 安装swagger

    go get -u github.com/swaggo/swag/cmd/swag@v1.6.5
  • 安装 gin-swagger

    $ go get -u github.com/swaggo/gin-swagger@v1.2.0 
    $ go get -u github.com/swaggo/files
    $ go get -u github.com/alecthomas/template
  • 编写 API 注释

    // @Summary 新增文章标签
    // @Produce  json
    // @Param name query string true "Name"
    // @Param state query int false "State"
    // @Param created_by query int false "CreatedBy"
    // @Success 200 {string} json "{"code":200,"data":{},"msg":"ok"}"
    // @Router /api/v1/tags [post]
    func AddTag(c *gin.Context) {
  • 针对 swagger 新增初始化动作和对应的路由规则

    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  • 生成swag文件

    //进入项目根目录
    > swag init
  • 访问网页

    http://127.0.0.1:8000/swagger/index.html
  • 结果

    swag.png

理解GO CONTEXT机制

context被译为上下文, 一般理解为程序单元的一个运行状态、现场、快照

每个Goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的Goroutine中。

在网络编程下,当接收到一个网络请求Request,处理Request时,我们可能需要开启不同的Goroutine来获取数据与逻辑处理,即一个请求Request,会在多个Goroutine中处理。而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

Deadline会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些io操作设定超时时间。

Done方法返回一个信道(channel),当Context被撤销或过期时,该信道是关闭的,即它是一个表示Context是否已关闭的信号。

当Done信道关闭后,Err方法表明Context被撤的原因。

Value可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。

使用

要创建Context树,第一步就是要得到根节点,context.Background函数的返回值就是根节点:

func Background() Context

该函数返回空的Context,该Context一般由接收请求的第一个Goroutine创建,是与进入请求对应的Context根节点,它不能被取消、没有值、也没有过期时间。它常常作为处理Request的顶层context存在。

有了根节点,又该怎么创建其它的子节点,孙节点呢?context包为我们提供了多个函数来创建他们:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context

函数都接收一个Context类型的参数parent,并返回一个Context类型的值,这样就层层创建出不同的节点。子节点是从复制父节点得到的,并且根据接收参数设定子节点的一些状态值,接着就可以将子节点传递给下层的Goroutine了。

总结

context包通过构建树型关系的Context,来达到上一层Goroutine能对传递给下一层Goroutine的控制。对于处理一个Request请求操作,需要采用context来层层控制Goroutine,以及传递一些变量来共享。

Context对象的生存周期一般仅为一个请求的处理周期。即针对一个请求创建一个Context变量(它为Context树结构的根);在请求处理结束后,撤销此ctx变量,释放资源。

每次创建一个Goroutine,要么将原有的Context传递给Goroutine,要么创建一个子Context并传递给Goroutine。

Context能灵活地存储不同类型、不同数目的值,并且使多个Goroutine安全地读写其中的值。

当通过父Context对象创建子Context对象时,可同时获得子Context的一个撤销函数,这样父Context对象的创建环境就获得了对子Context将要被传递到的Goroutine的撤销权。

Repository Comments ( 0 )

Sign in to post a comment

About

Cancel

Releases

No release

Contributors

All

Activities

Load More
can not load any more
Go
1
https://git.oschina.net/guanzhanyi/blog.git
git@git.oschina.net:guanzhanyi/blog.git
guanzhanyi
blog
blog
master

Search

103111 552b83b3 1850385 103110 ed87a847 1850385