Hertz 学习笔记(18)

244 阅读2分钟

今天学哨兵结合 Hertz 的例子。

sentinel-golang 结合 hertz 使用的示例

代码分服务端和客户端,服务端的例子可以单独跑,客户端的例子必须配合服务端才能跑。服务端的代码如下:

/*
 * Copyright 2022 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"context"
	"log"

	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/flow"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	hertzSentinel "github.com/hertz-contrib/opensergo/sentinel/adapter"
)

func initSentinel() {
	err := sentinel.InitDefault()
	if err != nil {
		log.Fatalf("Unexpected error: %+v", err)
	}
	_, err = flow.LoadRules([]*flow.Rule{
		{
			Resource:               "server_test",
			Threshold:              0.0,
			TokenCalculateStrategy: flow.Direct,
			ControlBehavior:        flow.Reject,
			StatIntervalInMs:       1000,
		},
	})
	if err != nil {
		log.Fatalf("Unexpected error: %+v", err)
		return
	}
}

func main() {
	initSentinel()
	h := server.Default(server.WithHostPorts(":8081"))
	h.Use(hertzSentinel.SentinelServerMiddleware(
		// customize resource extractor if required
		// method_path by default
		hertzSentinel.WithServerResourceExtractor(func(c context.Context, ctx *app.RequestContext) string {
			return "server_test"
		}),
		// customize block fallback if required
		// abort with status 429 by default
		hertzSentinel.WithServerBlockFallback(func(c context.Context, ctx *app.RequestContext) {
			ctx.AbortWithStatusJSON(400, utils.H{
				"err":  "too many request; the quota used up",
				"code": 10222,
			})
		}),
	))
	h.GET("/server_test", func(c context.Context, ctx *app.RequestContext) {})
	h.Spin()
}

客户端的代码如下:

/*
 * Copyright 2022 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"

	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/flow"
	"github.com/cloudwego/hertz/pkg/app/client"
	"github.com/cloudwego/hertz/pkg/protocol"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	hertzSentinel "github.com/hertz-contrib/opensergo/sentinel/adapter"
)

func initSentinel() {
	err := sentinel.InitDefault()
	if err != nil {
		log.Fatalf("Unexpected error: %+v", err)
	}
	_, err = flow.LoadRules([]*flow.Rule{
		{
			Resource:               "client_test",
			Threshold:              0.0,
			TokenCalculateStrategy: flow.Direct,
			ControlBehavior:        flow.Reject,
			StatIntervalInMs:       1000,
		},
	})
	if err != nil {
		log.Fatalf("Unexpected error: %+v", err)
		return
	}
}

func main() {
	initSentinel()
	c, err := client.NewClient()
	if err != nil {
		log.Fatalf("Unexpected error: %+v", err)
		return
	}
	c.Use(hertzSentinel.SentinelClientMiddleware(
		// customize resource extractor if required
		// method_path by default
		hertzSentinel.WithClientResourceExtractor(func(ctx context.Context,
			request *protocol.Request, response *protocol.Response,
		) string {
			return "client_test"
		}),
		// customize block fallback if required
		// abort with status 429 by default
		hertzSentinel.WithClientBlockFallback(func(ctx context.Context, req *protocol.Request,
			resp *protocol.Response, blockError error,
		) error {
			resp.SetStatusCode(http.StatusBadRequest)
			resp.SetBody([]byte("request failed"))
			return blockError
		}),
	))

	req := &protocol.Request{}
	res := &protocol.Response{}
	req.SetMethod(consts.MethodGet)
	req.SetRequestURI("http://127.0.0.1:8081/client_test")
	err = c.Do(context.Background(), req, res)
	fmt.Printf("response body: %v, code: %v\n", string(res.Body()), res.StatusCode())
	fmt.Printf("error: %v", err)
}

这里面用到了社区里面贡献的哨兵适配器,看样子是把哨兵当成中间件来用了。在其中可以自己写资源提取,也可以用现成的方法,限流的方法也可以自定义或者用现成的。

因为这里只需要看服务端,所以老样子单文件运行,然后新开终端窗口执行 curl --location --request GET 'http://127.0.0.1:8081/server_test' 看结果。