教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang微服务开发实践

Golang微服务开发实践

发布时间:2022-01-06   编辑:jiaochengji.com
教程集为您提供Golang微服务开发实践等资源,欢迎您收藏本站,我们将为您提供最新的Golang微服务开发实践资源

github: github.com/yun-mu/Micr…

微服务概念学习:可参考 Nginx 的微服务文章

微服务最佳实践:可参考 微服务最佳实践

<h2 class="heading">demo 简介</h2>

服务:

<ul><li>consignment-service(货运服务)</li><li>user-service(用户服务)</li><li>log-service (日志服务)</li><li>vessel-service(货船服务)</li><li>api-service (API 服务)</li></ul>

用到的技术栈如下:

<pre><code class="hljs yaml copyable"><span class="hljs-attr">framework:</span> <span class="hljs-string">go-micro,</span> <span class="hljs-string">gin</span> <span class="hljs-attr">Transport:</span> <span class="hljs-string">tcp</span> <span class="hljs-attr">Server:</span> <span class="hljs-string">rpc</span> <span class="hljs-attr">Client:</span> <span class="hljs-string">rpc</span> <span class="hljs-attr">RegisterTTL:</span> <span class="hljs-number">30</span><span class="hljs-string">s</span> <span class="hljs-attr">RegisterInterval:</span> <span class="hljs-number">20</span><span class="hljs-string">s</span> <span class="hljs-attr">Registry:</span> <span class="hljs-string">consul,</span> <span class="hljs-string">服务发现和注册</span> <span class="hljs-attr">Broker:</span> <span class="hljs-string">kafka,</span> <span class="hljs-string">消息队列</span> <span class="hljs-attr">Selector:</span> <span class="hljs-string">cache,</span> <span class="hljs-string">负载均衡</span> <span class="hljs-attr">Codec:</span> <span class="hljs-string">protobuf,</span> <span class="hljs-string">编码</span> <span class="hljs-attr">Tracing:</span> <span class="hljs-string">jaeger,</span> <span class="hljs-string">链路追踪</span> <span class="hljs-attr">Metrics:</span> <span class="hljs-string">jaeger</span> <span class="hljs-attr">breaker:</span> <span class="hljs-string">hystrix,</span> <span class="hljs-string">熔断</span> <span class="hljs-attr">ratelimit:</span> <span class="hljs-string">uber/ratelimit,</span> <span class="hljs-string">限流</span> <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">服务关系图</h3>

<figure><figcaption/></figure>

<h3 class="heading">实体关系图</h3>

<figure><figcaption/></figure>

<h3 class="heading">服务流程示例</h3>

<figure><figcaption/></figure>

<h3 class="heading">认证</h3>

采用 JWT

<figure><figcaption/></figure>

<h3 class="heading">发布订阅模式</h3>

<figure><figcaption/></figure>

<h3 class="heading">demo 运行</h3>

前提工具:<code>go, dep, docker, docker-compose, mongo</code>

首先初始化:<code>make init</code>

Makefile 部分代码如下:

<pre><code class="hljs makefile copyable"><span class="hljs-section">init:</span> cd .. mv MicroServicePractice ${GOPATH}/src/Ethan/ ./pull.sh <span class="hljs-comment"># 安装 go 依赖</span> cd plugins docker-compose -f docker-compose.yml up -d <span class="hljs-comment"># 安装插件,如:kafka, consul, zookeeper, jaeger</span> <span class="copy-code-btn">复制代码</span></code></pre>

之后就可以运行代码了:

注:建议自己开多个终端 <code>go run</code> ,这样可以看日志

<pre><code class="hljs shell copyable">make run # 允许 服务 server <span class="copy-code-btn">复制代码</span></code></pre>

测试:

注:注意顺序,刚开始啥数据都没有的

<pre><code class="hljs shell copyable">go run user-cli/cli.go export Token=$Token # 注意换成前面生成的Token go run vessel-cli/cli.go go run consignment-cli/cli.go <span class="copy-code-btn">复制代码</span></code></pre>

<figure><figcaption/></figure>

<figure><figcaption/></figure>

<figure><figcaption/></figure>

<figure><figcaption/></figure>

<figure><figcaption/></figure>

<h2 class="heading">开发详解</h2> <h3 class="heading">proto 代码生成</h3>

安装工具:

<em>protoc</em> 安装:google.github.io/proto-lens/…

<em>protoc-gen-go</em> 和 <em>protoc-gen-micro</em>

<pre><code class="hljs shell copyable">go get -u -v google.golang.org/grpc go get -u -v github.com/golang/protobuf/protoc-gen-go go get -u -v github.com/micro/protoc-gen-micro <span class="copy-code-btn">复制代码</span></code></pre>

生成的脚本我已经写好 <em>Makefile</em>, 进入 <em>interface-center</em> 目录,执行<code>make build</code> 即可

内部示例如下:

<pre><code class="hljs shell copyable">protoc --proto_path=proto:. --go_out=plugins=micro:out/ proto/vessel/vessel.proto <span class="copy-code-btn">复制代码</span></code></pre>

这里使用 micro 插件,若想和不使用插件对比,可使用如下命令:

<pre><code class="hljs shell copyable">protoc --proto_path=proto:. --go_out=out/ --micro_out=out/ proto/vessel/vessel.proto <span class="copy-code-btn">复制代码</span></code></pre>

这样会生成两个文件,一个为 <em>.micro.go</em> 一个为 <em>.pb.go</em>

这里顺便看一下 生成的 pb 文件里是如何进行 rpc 调用的,我们随便看一个 方法,如:<em>vessel</em> 的 <em>FindAvailable</em>

<pre><code class="hljs go copyable" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *vesselServiceClient)</span> <span class="hljs-title">FindAvailable</span><span class="hljs-params">(ctx context.Context, in *Specification, opts ...client.CallOption)</span> <span class="hljs-params">(*Response, error)</span></span> { req := c.c.NewRequest(c.serviceName, <span class="hljs-string">"VesselService.FindAvailable"</span>, in) out := <span class="hljs-built_in">new</span>(Response) err := c.c.Call(ctx, req, out, opts...) <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err } <span class="hljs-keyword">return</span> out, <span class="hljs-literal">nil</span> } <span class="copy-code-btn">复制代码</span></code></pre> <pre><code class="hljs bash copyable">func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request { var opts RequestOptions <span class="hljs-keyword">for</span> _, o := range reqOpts { o(&opts) } // <span class="hljs-built_in">set</span> the content-type specified <span class="hljs-keyword">if</span> len(opts.ContentType) > 0 { contentType = opts.ContentType } <span class="hljs-built_in">return</span> &rpcRequest{ service: service, method: endpoint, endpoint: endpoint, body: request, contentType: contentType, opts: opts, } } <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">微服务开发流程</h3>

如果使用 <em>grpc</em> 作为 <em>server</em> 和 <em>client</em>,开发流程如下:

注:<em>server</em> 和 <em>client</em> 必须相同,如:我的代码中 <em>server</em> 和 <em>client</em> 使用的都是 <em>rpc</em>, <em>transport</em> 是 <em>tcp</em>

<figure><figcaption/></figure>

<h3 class="heading">目录简介</h3> <ul><li>api:对外暴露的HTTP web 接口,可以理解为 网关</li><li>common:所有服务都能调用的东西,如 <em>GetMicroClient, GetMicroServer</em></li><li>config:配置中心,其他服务的启动都依赖的配置</li><li>consignment</li><li>consignment-cli:cli 测试</li><li>interface-center:proto 文件中心,同时生成的 .go 文件也在这里</li><li>shippy-ui:前端测试 ui 代码,对接API,API还没写完</li><li>user</li><li>user-cli</li><li>vessel</li><li>vessel-cli</li></ul><h3 class="heading">初始化</h3>

示例代码:consignment/main.go, common/service.go

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// 直接调用自己写的公有的库获取 server,保持配置同步 </span> <span class="hljs-comment">// common.AuthWrapper 为前置认证,采用JWT</span> srv := common.GetMicroServer(service, micro.WrapHandler(common.AuthWrapper)) <span class="copy-code-btn">复制代码</span></code></pre>

common.GetMicroServer

<pre><code class="hljs go copyable" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetMicroServer</span><span class="hljs-params">(service <span class="hljs-keyword">string</span>, exOpts ...micro.Option)</span> <span class="hljs-title">micro</span>.<span class="hljs-title">Service</span></span> { opts := getOpts(service) <span class="hljs-keyword">if</span> defaultServer != <span class="hljs-literal">nil</span> { opts = <span class="hljs-built_in">append</span>(opts, defaultServer) } <span class="hljs-comment">// ...</span> <span class="hljs-comment">// 注意顺序,同样的配置后面的会将前面的覆盖</span> opts = <span class="hljs-built_in">append</span>(opts, exOpts...) srv := micro.NewService(opts...) <span class="hljs-comment">// 初始化,解析命令行参数</span> srv.Init() <span class="hljs-keyword">return</span> srv } <span class="copy-code-btn">复制代码</span></code></pre>

注:调用者的 client, transport 应当和 server 的 client, transport 配置相同,所以开发 micro web的时候要注意!micro web 全是 HTTP 或者 ws,需要自己使用和后面服务相同的 client 来完成转发。

<figure><figcaption/></figure>

<h3 class="heading">服务注册</h3>

这里我的demo中采用了 consul,consul 自带了 UI和健康检查,consul UI 端口为:8500

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// 注册延迟,30s 内没有注册则失效,consul 会自动删除服务</span> micro.RegisterTTL(time.Second * <span class="hljs-number">30</span>), <span class="hljs-comment">// 注册间隔,每隔 20s 注册一次</span> micro.RegisterInterval(time.Second * <span class="hljs-number">20</span>) <span class="hljs-comment">// ...</span> <span class="hljs-comment">// opts 中添加如下配置即可</span> micro.Registry(consul.NewRegistry(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(op *registry.Options)</span></span> { op.Addrs = config.GetRegistryAddrs(service) })) <span class="copy-code-btn">复制代码</span></code></pre>

docker-compose.yml 中已经定义,这里测试用,因此只采用单节点(server)的形式,consul 采用 Raft 算法,为了保证选主无误,节点(server)数必须是奇数,<em>bootstrap-expect</em> 表示节点数量

<pre><code class="hljs yaml copyable"><span class="hljs-attr"> consul:</span> <span class="hljs-attr"> image:</span> <span class="hljs-attr">consul:1.5</span> <span class="hljs-attr"> container_name:</span> <span class="hljs-string">consul-node1</span> <span class="hljs-attr"> command:</span> <span class="hljs-string">agent</span> <span class="hljs-bullet">-server</span> <span class="hljs-bullet">-bootstrap-expect=1</span> <span class="hljs-bullet">-node=node1</span> <span class="hljs-bullet">-bind=0.0.0.0</span> <span class="hljs-bullet">-client=0.0.0.0</span> <span class="hljs-bullet">-datacenter=dc1</span> <span class="hljs-attr"> volumes:</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">./consul/node1:/consul/data</span> <span class="hljs-attr"> consul-client:</span> <span class="hljs-attr"> image:</span> <span class="hljs-attr">consul:1.5</span> <span class="hljs-attr"> container_name:</span> <span class="hljs-string">consul-client1</span> <span class="hljs-attr"> command:</span> <span class="hljs-string">agent</span> <span class="hljs-bullet">-retry-join=consul</span> <span class="hljs-bullet">-node=client1</span> <span class="hljs-bullet">-bind=0.0.0.0</span> <span class="hljs-bullet">-client=0.0.0.0</span> <span class="hljs-bullet">-datacenter=dc1</span> <span class="hljs-bullet">-ui</span> <span class="hljs-attr"> ports:</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"8500:8500"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"8600:8600"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"8300:8300"</span> <span class="hljs-attr"> depends_on:</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">consul</span> <span class="hljs-attr"> volumes:</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">./consul/client1:/consul/data</span> <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">watch</h3> <pre><code class="hljs go copyable" lang="go"> <span class="hljs-comment">// 这里的 handler 应当实现 pb 中定义的调用</span> h := handler.GetHandler(session, vClient, uClient, bk) <span class="hljs-comment">// 将 server 作为微服务的服务端</span> pb.RegisterShippingServiceHandler(srv.Server(), h) <span class="hljs-keyword">if</span> err := srv.Run(); err != <span class="hljs-literal">nil</span> { log.Fatalf(<span class="hljs-string">"failed to serve: %v"</span>, err) } <span class="copy-code-btn">复制代码</span></code></pre>

pb

<pre><code class="hljs protobuf copyable"><span class="hljs-comment">// 货轮微服务</span> <span class="hljs-class"><span class="hljs-keyword">service</span> <span class="hljs-title">ShippingService</span> </span>{ <span class="hljs-comment">// 托运一批货物</span> <span class="hljs-function"><span class="hljs-keyword">rpc</span> CreateConsignment (Consignment) <span class="hljs-keyword">returns</span> (Response) { } // 查看托运货物的信息 <span class="hljs-keyword">rpc</span> GetConsignments (GetRequest) <span class="hljs-keyword">returns</span> (Response) { } } </span><span class="copy-code-btn">复制代码</span></code></pre>

handler

<pre><code class="hljs go copyable" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Handler)</span> <span class="hljs-title">CreateConsignment</span><span class="hljs-params">(ctx context.Context, req *pb.Consignment, resp *pb.Response)</span> <span class="hljs-title">error</span></span> { } <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Handler)</span> <span class="hljs-title">GetConsignments</span><span class="hljs-params">(ctx context.Context, req *pb.GetRequest, resp *pb.Response)</span> <span class="hljs-title">error</span></span> { } <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">上下文 context</h3>

在我们的 AuthWrapper 中,ctx 作为上下文信息传递的方式,可在 ctx 中添加信息

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">//</span> <span class="hljs-comment">// AuthWrapper 是一个高阶函数,入参是 "下一步" 函数,出参是认证函数</span> <span class="hljs-comment">// 在返回的函数内部处理完认证逻辑后,再手动调用 fn() 进行下一步处理</span> <span class="hljs-comment">// token 是从 上下文中取出的,再调用 user-service 将其做验证</span> <span class="hljs-comment">// 认证通过则 fn() 继续执行,否则报错</span> <span class="hljs-comment">//</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">AuthWrapper</span><span class="hljs-params">(fn server.HandlerFunc)</span> <span class="hljs-title">server</span>.<span class="hljs-title">HandlerFunc</span></span> { log.Println(<span class="hljs-string">"AuthWrapper"</span>) <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(ctx context.Context, req server.Request, resp <span class="hljs-keyword">interface</span>{})</span> <span class="hljs-title">error</span></span> { <span class="hljs-comment">// consignment-service 独立测试时不进行认证</span> <span class="hljs-keyword">if</span> os.Getenv(<span class="hljs-string">"DISABLE_AUTH"</span>) == <span class="hljs-string">"true"</span> { <span class="hljs-keyword">return</span> fn(ctx, req, resp) } meta, ok := metadata.FromContext(ctx) <span class="hljs-keyword">if</span> !ok { <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"no auth meta-data found in request"</span>) } token := meta[<span class="hljs-string">"token"</span>] <span class="hljs-comment">// Auth here</span> authResp, err := GetUserClient().ValidateToken(context.Background(), &userPb.Token{ Token: token, }) log.Println(<span class="hljs-string">"Auth Resp:"</span>, authResp) <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> err } <span class="hljs-comment">// 这里将 JWT 解析出来的 user_id 传递下去</span> ctx = context.WithValue(ctx, <span class="hljs-string">"user_id"</span>, authResp.UserId) err = fn(ctx, req, resp) <span class="hljs-keyword">return</span> err } } <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">handler</h3>

在所有前置操作执行完毕之后,开始执行 handler 真正的 Call,handler 的函数定义必须和 pb 中一模一样。

处理完之后直接编辑 resp 即可,之后返回 nil,resp 是一个指针,直接传递了返回信息。

<pre><code class="hljs go copyable" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(h *Handler)</span> <span class="hljs-title">CreateConsignment</span><span class="hljs-params">(ctx context.Context, req *pb.Consignment, resp *pb.Response)</span> <span class="hljs-title">error</span></span> { <span class="hljs-comment">// ... 处理</span> resp.Created = <span class="hljs-literal">true</span> resp.Consignment = req <span class="hljs-comment">// 后置操作</span> <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">// ...</span> h.pubLog(userID, <span class="hljs-string">"CreateConsignment"</span>, msg) }() <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> } <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">过滤器</h3>

这里以版本过滤器为例:

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// Filter will filter the version of the service</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Filter</span><span class="hljs-params">(v <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">client</span>.<span class="hljs-title">CallOption</span></span> { <span class="hljs-keyword">if</span> v == <span class="hljs-string">""</span> { v = <span class="hljs-string">"latest"</span> } filter := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(services []*registry.Service)</span> []*<span class="hljs-title">registry</span>.<span class="hljs-title">Service</span></span> { <span class="hljs-keyword">var</span> filtered []*registry.Service <span class="hljs-keyword">for</span> _, service := <span class="hljs-keyword">range</span> services { <span class="hljs-keyword">if</span> service.Version == v { filtered = <span class="hljs-built_in">append</span>(filtered, service) } } <span class="hljs-keyword">return</span> filtered } <span class="hljs-keyword">return</span> client.WithSelectOption(selector.WithFilter(filter)) } <span class="copy-code-btn">复制代码</span></code></pre>

之后在进行 client.Call 的时候可以使用

<pre><code class="hljs go copyable" lang="go">vResp, err := h.vesselClient.FindAvailable(ctx, vReq, common.Filter(version)) <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">db 交互</h3>

一般而言不要把 pb 结构体直接插入 数据库中,最好有一个 中间转换层。示例如下:

<pre><code class="hljs go copyable" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(repo *ConsignmentRepository)</span> <span class="hljs-title">Create</span><span class="hljs-params">(con *pb.Consignment)</span> <span class="hljs-title">error</span></span> { <span class="hljs-comment">// 这里将PB转换为想要的结构体,之后再插入</span> data := PBConsignment2Consignment(con) <span class="hljs-comment">// dao 层直接对接DB操作</span> <span class="hljs-keyword">return</span> dao.Insert(repo.collection(), &data) } <span class="hljs-comment">// 在外面记得把 Session Close</span> <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">broker</h3>

消息队列

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// common/service.go/GetMicroServer()</span> <span class="hljs-comment">// 注册</span> brokerKafka := kafka.NewBroker(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(options *broker.Options)</span></span> { <span class="hljs-comment">// eg: []{"127.0.0.1:9092"}</span> options.Addrs = config.GetBrokerAddrs(service) }) <span class="hljs-keyword">if</span> err := brokerKafka.Connect(); err != <span class="hljs-literal">nil</span> { log.Fatalf(<span class="hljs-string">"Broker Connect error: %v"</span>, err) } <span class="hljs-comment">// ... micro.Broker(brokerKafka)</span> <span class="copy-code-btn">复制代码</span></code></pre>

注册完之后就开始定义 接口了

<pre><code class="hljs go copyable" lang="go"> <span class="hljs-comment">// 这里我将 kafka broker 传入 handler 中</span> bk := srv.Server().Options().Broker h := handler.GetHandler(session, vClient, uClient, bk) <span class="copy-code-btn">复制代码</span></code></pre>

发布消息:

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// 发送log</span> <span class="hljs-comment">// ...</span> data := &broker.Message{ Header: <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>{ <span class="hljs-string">"user_id"</span>: userID, }, Body: body, } <span class="hljs-keyword">if</span> err := h.Broker.Publish(topic, data); err != <span class="hljs-literal">nil</span> { log.Printf(<span class="hljs-string">"[pub] failed: %v\n"</span>, err) } <span class="copy-code-btn">复制代码</span></code></pre>

订阅消息:

<pre><code class="hljs go copyable" lang="go"> bk := srv.Server().Options().Broker <span class="hljs-comment">// 这里订阅了 一个 topic, 并提供接口处理</span> _, err := bk.Subscribe(topic, subLog) <span class="copy-code-btn">复制代码</span></code></pre> <pre><code class="hljs go copyable" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">subLog</span><span class="hljs-params">(pub broker.Publication)</span> <span class="hljs-title">error</span></span> { <span class="hljs-keyword">var</span> logPB *pb.Log <span class="hljs-comment">// 自行解析 body 即可</span> <span class="hljs-keyword">if</span> err := json.Unmarshal(pub.Message().Body, &logPB); err != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> err } log.Printf(<span class="hljs-string">"[Log]: user_id: %s, Msg: %v\n"</span>, pub.Message().Header[<span class="hljs-string">"user_id"</span>], logPB) <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> } <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">熔断</h3>

Micro提供了两种实现,gobreaker和hystrix,熔断是在客户端实现。

来看看 hystrix:

<pre><code class="hljs go copyable" lang="go"><span class="hljs-keyword">var</span> ( <span class="hljs-comment">// DefaultTimeout is how long to wait for command to complete, in milliseconds</span> DefaultTimeout = <span class="hljs-number">1000</span> <span class="hljs-comment">// DefaultMaxConcurrent is how many commands of the same type can run at the same time</span> DefaultMaxConcurrent = <span class="hljs-number">10</span> <span class="hljs-comment">// DefaultVolumeThreshold is the minimum number of requests needed before a circuit can be tripped due to health</span> DefaultVolumeThreshold = <span class="hljs-number">20</span> <span class="hljs-comment">// DefaultSleepWindow is how long, in milliseconds, to wait after a circuit opens before testing for recovery</span> DefaultSleepWindow = <span class="hljs-number">5000</span> <span class="hljs-comment">// DefaultErrorPercentThreshold causes circuits to open once the rolling measure of errors exceeds this percent of requests</span> DefaultErrorPercentThreshold = <span class="hljs-number">50</span> <span class="hljs-comment">// DefaultLogger is the default logger that will be used in the Hystrix package. By default prints nothing.</span> DefaultLogger = NoopLogger{} ) <span class="hljs-keyword">type</span> Settings <span class="hljs-keyword">struct</span> { Timeout time.Duration MaxConcurrentRequests <span class="hljs-keyword">int</span> RequestVolumeThreshold <span class="hljs-keyword">uint64</span> SleepWindow time.Duration ErrorPercentThreshold <span class="hljs-keyword">int</span> } <span class="copy-code-btn">复制代码</span></code></pre>

若想修改参数,hystrix 没有提供全局的接口修改,这里我直接修改默认参数

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// "github.com/afex/hystrix-go/hystrix" </span> hystrix.DefaultMaxConcurrent = <span class="hljs-number">100</span> hystrix.DefaultVolumeThreshold = <span class="hljs-number">50</span> <span class="copy-code-btn">复制代码</span></code></pre>

注册:

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// "github.com/micro/go-plugins/wrapper/breaker/hystrix"</span> <span class="hljs-comment">// 添加如下配置即可</span> micro.WrapClient(hystrix.NewClientWrapper() <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">限流</h3>

<em>ratelimit</em> 可以在客户端做,也可以在服务端做;micro提供了两种方案:<code>juju/ratelimit</code>、<code>uber/ratelimit</code>。

我们看看 uber 的:

<pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// "github.com/micro/go-plugins/wrapper/ratelimiter/uber"</span> <span class="hljs-comment">// 添加如下配置即可</span> <span class="hljs-comment">// ratelimit 的配置可自行查看 API 修改</span> micro.WrapClient(ratelimit.NewHandlerWrapper(<span class="hljs-number">1024</span>)) <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">链路追踪</h3>

这里使用 <em>jaeger</em> , <em>jaeger</em> 提供了UI界面,端口为16686

docker-compose.yml 中已经定义

<pre><code class="hljs yaml copyable"><span class="hljs-attr"> jaeger:</span> <span class="hljs-attr"> image:</span> <span class="hljs-string">jaegertracing/all-in-one:1.12</span> <span class="hljs-attr"> container_name:</span> <span class="hljs-string">tracing</span> <span class="hljs-attr"> environment:</span> <span class="hljs-attr"> COLLECTOR_ZIPKIN_HTTP_PORT:</span> <span class="hljs-number">9411</span> <span class="hljs-attr"> ports:</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"5775:5775/udp"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"6831:6831/udp"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"6832:6832/udp"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"5778:5778"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"16686:16686"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"14268:14268"</span> <span class="hljs-bullet"> -</span> <span class="hljs-string">"9411:9411"</span> <span class="copy-code-btn">复制代码</span></code></pre> <pre><code class="hljs go copyable" lang="go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewJaegerTracer</span><span class="hljs-params">(serviceName, addr <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(opentracing.Tracer, io.Closer, error)</span></span> { <span class="hljs-comment">// Sample configuration for testing. Use constant sampling to sample every trace</span> <span class="hljs-comment">// and enable LogSpan to log every span via configured Logger.</span> cfg := jaegercfg.Configuration{ Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: <span class="hljs-number">1</span>, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: <span class="hljs-literal">true</span>, BufferFlushInterval: <span class="hljs-number">1</span> * time.Second, }, } cfg.ServiceName = serviceName <span class="hljs-comment">// Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log</span> <span class="hljs-comment">// and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics</span> <span class="hljs-comment">// frameworks.</span> jLogger := &jaegerLogger{} jMetricsFactory := metrics.NullFactory metricsFactory := metrics.NullFactory metrics := jaeger.NewMetrics(metricsFactory, <span class="hljs-literal">nil</span>) sender, err := jaeger.NewUDPTransport(addr, <span class="hljs-number">0</span>) <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { log.Logf(<span class="hljs-string">"could not initialize jaeger sender: %s"</span>, err.Error()) <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, <span class="hljs-literal">nil</span>, err } repoter := jaeger.NewRemoteReporter(sender, jaeger.ReporterOptions.Metrics(metrics)) <span class="hljs-keyword">return</span> cfg.NewTracer( jaegercfg.Logger(jLogger), jaegercfg.Metrics(jMetricsFactory), jaegercfg.Reporter(repoter), ) } <span class="hljs-keyword">type</span> jaegerLogger <span class="hljs-keyword">struct</span>{} <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(l *jaegerLogger)</span> <span class="hljs-title">Error</span><span class="hljs-params">(msg <span class="hljs-keyword">string</span>)</span></span> { log.Logf(<span class="hljs-string">"ERROR: %s"</span>, msg) } <span class="hljs-comment">// Infof logs a message at info priority</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(l *jaegerLogger)</span> <span class="hljs-title">Infof</span><span class="hljs-params">(msg <span class="hljs-keyword">string</span>, args ...<span class="hljs-keyword">interface</span>{})</span></span> { log.Logf(msg, args...) } <span class="copy-code-btn">复制代码</span></code></pre> <pre><code class="hljs go copyable" lang="go"><span class="hljs-comment">// "github.com/micro/go-plugins/wrapper/trace/opentracing"</span> opentracing.NewClientWrapper(t) <span class="copy-code-btn">复制代码</span></code></pre> <h3 class="heading">API</h3>

这里结合大名鼎鼎的 HTTP Restful 框架 gin 使用

主要代码如下:

<pre><code class="hljs go copyable" lang="go"> <span class="hljs-comment">// web 的初始化不太一样 micro -> web</span> srv := common.GetMicroWeb(service) <span class="hljs-comment">// ...</span> router := gin.Default() <span class="hljs-comment">// ... 正常 gin router 绑定操作</span> <span class="hljs-comment">// 最后直接将 服务 / 绑定到 gin 的router上,交给 gin 处理</span> srv.Handle(<span class="hljs-string">"/"</span>, router) <span class="copy-code-btn">复制代码</span></code></pre> <h2 class="heading">go-micro 详解</h2>

<em>micro</em> 文档:micro.mu/docs/index.…

参见另一篇 go-micro详解

<h2 class="heading">micro</h2>

参见另一篇 micro 工具箱

转载于:https://juejin.im/post/5cfa1b5b6fb9a07ecf721696

到此这篇关于“Golang微服务开发实践”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
【GoLang】GoLang 微服务、开源库等参考资料
如何使用 Go 语言进行微服务开发
golang微服务框架对比_最强开源微服务框架,全网独家整理
Go Golang Beego微服务基础实战视频教程
GO语言:微服务简介--定义和标准
go 微服务框架_Go版微服务开发框架Micro及标准2019年大整合
Golang笔记:语法,并发思想,web开发,Go微服务相关
Go语言微服务开发框架实践-go chassis(中篇)
golang微服务框架对比_golang微服务框架gozero系列4:gozero文件服务
支持多语言的微服务框架Tars-Go

[关闭]
~ ~