分为三层:
service: 定义业务逻辑
endpoint: 调用service处理请求和返回响应
transport: 连接endpoint的request和response
安装
编译安装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
//生成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())
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
$ 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(®)
if err != nil {
log.Fatal(err)
}
}
//优雅退出consul
Do
和Go
类似, 不过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
})
[app] PAGE_SIZE = 10 JWT_SECRET = 23347$040412
[server] HTTP_PORT = 8000 READ_TIMEOUT = 60
//通过ini.Load()初始化 //通过GetSection()获取领域 //通过Key()获取值 //通过Mustxx()判断值类型和匹配
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()
span
不太明白链路追踪中client的作用
安装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
结果
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的撤销权。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。