﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-金庆的专栏-随笔分类-3. Golang</title><link>http://www.cppblog.com/jinq0123/category/21366.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 22 Feb 2021 15:12:27 GMT</lastBuildDate><pubDate>Mon, 22 Feb 2021 15:12:27 GMT</pubDate><ttl>60</ttl><item><title>net.LookupSRV()查询k8s无头服务</title><link>http://www.cppblog.com/jinq0123/archive/2021/02/22/217615.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Mon, 22 Feb 2021 08:17:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2021/02/22/217615.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217615.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2021/02/22/217615.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217615.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217615.html</trackback:ping><description><![CDATA[net.LookupSRV()查询k8s无头服务<br /><br />(金庆的专栏 2021.2)<br /><br />如下创建 StatefulSet 和 Headless Service: test.yaml<br /><br />```<br />apiVersion: v1<br />kind: Service<br />metadata:<br />&nbsp; name: headless-svc<br />&nbsp; labels:<br />&nbsp;&nbsp;&nbsp; app: headless-svc<br />spec:<br />&nbsp; ports:<br />&nbsp; - port: 80<br />&nbsp;&nbsp;&nbsp; name: aaaa<br />&nbsp; - port: 20080<br />&nbsp;&nbsp;&nbsp; name: bbbb<br />&nbsp; selector:<br />&nbsp;&nbsp;&nbsp; app: headless-pod<br />&nbsp; clusterIP: None<br />&nbsp; <br />---<br />apiVersion: apps/v1<br />kind: StatefulSet<br />metadata:<br />&nbsp; name: statefulset-test<br />spec:<br />&nbsp; serviceName: headless-svc<br />&nbsp; replicas: 3<br />&nbsp; selector:<br />&nbsp;&nbsp;&nbsp; matchLabels:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app: headless-pod<br />&nbsp; template:<br />&nbsp;&nbsp;&nbsp; metadata:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; labels:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app: headless-pod<br />&nbsp;&nbsp;&nbsp; spec:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; containers:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - name: myhttpd<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; image: httpd<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ports:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - containerPort: 80<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; - containerPort: 20080<br />```<br /><br />部署：<br />```<br />kubectl apply -f test.yaml<br />```<br /><br />然后开一个 shell:<br />```<br />kubectl run mygolang -it --image golang -- bash<br />If you don't see a command prompt, try pressing enter.<br />root@mygolang:/go#<br />```<br /><br />先装个 vim:<br />```<br />apt install vim<br />```<br /><br />然后写个 golang 测试程序，向 DNS 查询 bbbb 服务的地址与端口：<br />```<br />root@mygolang:/jinqing# cat main.go<br />package main<br /><br />import (<br />&nbsp;&nbsp;&nbsp; "fmt"<br />&nbsp;&nbsp;&nbsp; "github.com/davecgh/go-spew/spew"<br />&nbsp;&nbsp;&nbsp; "net"<br />)<br /><br />func main() {<br />&nbsp;&nbsp;&nbsp; cname, addresses, err := net.LookupSRV("bbbb", "tcp", "headless-svc.default.svc.cluster.local")<br />&nbsp;&nbsp;&nbsp; if err != nil {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf("failed: %s\n", err)<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; fmt.Printf("cname: %s\n", cname)<br />&nbsp;&nbsp;&nbsp; spew.Dump(addresses)<br />}<br />root@mygolang:/jinqing#<br />```<br /><br />运行结果为：<br />```<br />cname: _bbbb._tcp.headless-svc.default.svc.cluster.local.<br />([]*net.SRV) (len=3 cap=4) {<br />&nbsp;(*net.SRV)(0xc00000e220)({<br />&nbsp; Target: (string) (len=58) "statefulset-test-0.headless-svc.default.svc.cluster.local.",<br />&nbsp; Port: (uint16) 20080,<br />&nbsp; Priority: (uint16) 0,<br />&nbsp; Weight: (uint16) 33<br />&nbsp;}),<br />&nbsp;(*net.SRV)(0xc00000e1e0)({<br />&nbsp; Target: (string) (len=58) "statefulset-test-2.headless-svc.default.svc.cluster.local.",<br />&nbsp; Port: (uint16) 20080,<br />&nbsp; Priority: (uint16) 0,<br />&nbsp; Weight: (uint16) 33<br />&nbsp;}),<br />&nbsp;(*net.SRV)(0xc00000e200)({<br />&nbsp; Target: (string) (len=58) "statefulset-test-1.headless-svc.default.svc.cluster.local.",<br />&nbsp; Port: (uint16) 20080,<br />&nbsp; Priority: (uint16) 0,<br />&nbsp; Weight: (uint16) 33<br />&nbsp;})<br />}<br />```<br /><br />多次运行发现结果项次序固定，并没有按权重随机。<br /><br />Service yaml 定义中，必须为每个端口命名，不然没法查询。<br /><br />```<br />&nbsp;&nbsp;&nbsp; cname, addresses, err := net.LookupSRV("bbbb", "tcp", "headless-svc.default.svc.cluster.local")<br />```<br />可利用默认域名后缀简写为<br />```<br />&nbsp;&nbsp;&nbsp; cname, addresses, err := net.LookupSRV("bbbb", "tcp", "headless-svc")<br />```<br />输出相同。<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217615.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2021-02-22 16:17 <a href="http://www.cppblog.com/jinq0123/archive/2021/02/22/217615.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>rpc应答太快造成请求超时</title><link>http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 17 Sep 2020 07:59:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217453.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217453.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217453.html</trackback:ping><description><![CDATA[rpc应答太快造成请求超时<br /><br />(金庆的专栏 2020.9)<br /><br />在压测中发现总有几个请求超时，超时时长设大也会有，而成功的请求延时远小于超时时间。<br />查错的第一方向是查网络库中有消息丢失。<br />跟踪所有消息，发现超时的消息应该是正常处理并返回了。<br />于是查接收应答消息后的处理，最终找到代码：<br />```go<br />func (c *Client) onRpcRet(cbIndex uint32, ...) {<br />&nbsp;&nbsp; &nbsp;ii, ok := c.callbacks.Load(cbIndex)<br />&nbsp;&nbsp; &nbsp;if !ok {<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; // logger.Errorf("onRpcRet can not find cbIndex %d", cbIndex) // 可能是超时已删<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return<br />&nbsp;&nbsp; &nbsp;}<br />```<br />打开日志，发现超时的请求对应有该条错误日志。<br />此处回调不存在的情况，正常是先超时删除回调，然后再收到应答。<br />现在是先收到了应答，发现找不到回调，然后过了一段时间会被判为超时无响应。<br /><br />将下面代码换个次序就好了：<br />```go<br />&nbsp;&nbsp; &nbsp;if err := c.Session.Send(msg); err != nil {<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; ...<br />&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return<br />&nbsp;&nbsp; &nbsp;}<br />&nbsp;&nbsp; &nbsp;c.callbacks.Store(cbIndex, ...)<br />```<br />改为<br />```go<br />&nbsp;&nbsp; &nbsp;// 必须先设回调，然后发送，因为应答可能会很快<br />&nbsp;&nbsp; &nbsp;c.callbacks.Store(cbIndex, ...)<br />&nbsp;&nbsp; &nbsp;if err := c.Session.Send(msg); err...<br />```<br /><br />压测时因为加压机CPU是满负载运转，所以 Send() 和 Store() 之间可能会间隔数毫秒，<br />足够 rpc 请求处理完成并返回，而应答返回时回调还没设置。<br /><br />先 Send() 后 Store() 写代码会稍微简单点，因为 Send() 失败后可以直接返回。<br />先 Store() 后 Send() 时，Send() 失败则需要相应 Delete().<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217453.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-09-17 15:59 <a href="http://www.cppblog.com/jinq0123/archive/2020/09/17/217453.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>golang各数值类型的最大最小值</title><link>http://www.cppblog.com/jinq0123/archive/2020/09/11/217443.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 11 Sep 2020 06:55:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/09/11/217443.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217443.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/09/11/217443.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217443.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217443.html</trackback:ping><description><![CDATA[# golang各数值类型的最大最小值<br /><br />(金庆的专栏 2020.9)<br /><br />golang 的 math 包已经定义了以下常量：<br /><br />Constants<br /><br />```<br />const (<br />&nbsp;&nbsp;&nbsp; E&nbsp;&nbsp; = 2.71828182845904523536028747135266249775724709369995957496696763 // https://oeis.org/A001113<br />&nbsp;&nbsp;&nbsp; Pi&nbsp; = 3.14159265358979323846264338327950288419716939937510582097494459 // https://oeis.org/A000796<br />&nbsp;&nbsp;&nbsp; Phi = 1.61803398874989484820458683436563811772030917980576286213544862 // https://oeis.org/A001622<br /><br />&nbsp;&nbsp;&nbsp; Sqrt2&nbsp;&nbsp; = 1.41421356237309504880168872420969807856967187537694807317667974 // https://oeis.org/A002193<br />&nbsp;&nbsp;&nbsp; SqrtE&nbsp;&nbsp; = 1.64872127070012814684865078781416357165377610071014801157507931 // https://oeis.org/A019774<br />&nbsp;&nbsp;&nbsp; SqrtPi&nbsp; = 1.77245385090551602729816748334114518279754945612238712821380779 // https://oeis.org/A002161<br />&nbsp;&nbsp;&nbsp; SqrtPhi = 1.27201964951406896425242246173749149171560804184009624861664038 // https://oeis.org/A139339<br /><br />&nbsp;&nbsp;&nbsp; Ln2&nbsp;&nbsp;&nbsp; = 0.693147180559945309417232121458176568075500134360255254120680009 // https://oeis.org/A002162<br />&nbsp;&nbsp;&nbsp; Log2E&nbsp; = 1 / Ln2<br />&nbsp;&nbsp;&nbsp; Ln10&nbsp;&nbsp; = 2.30258509299404568401799145468436420760110148862877297603332790 // https://oeis.org/A002392<br />&nbsp;&nbsp;&nbsp; Log10E = 1 / Ln10<br />)<br />```<br /><br />Mathematical constants.<br /><br />```<br />const (<br />&nbsp;&nbsp;&nbsp; MaxFloat32&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 3.40282346638528859811704183484516925440e+38&nbsp; // 2**127 * (2**24 - 1) / 2**23<br />&nbsp;&nbsp;&nbsp; SmallestNonzeroFloat32 = 1.401298464324817070923729583289916131280e-45 // 1 / 2**(127 - 1 + 23)<br /><br />&nbsp;&nbsp;&nbsp; MaxFloat64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52<br />&nbsp;&nbsp;&nbsp; SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52)<br />)<br />```<br /><br />Floating-point limit values. Max is the largest finite value representable by the type. SmallestNonzero is the smallest positive, non-zero value representable by the type.<br /><br />```<br />const (<br />&nbsp;&nbsp;&nbsp; MaxInt8&nbsp;&nbsp; = 1&lt;&lt;7 - 1<br />&nbsp;&nbsp;&nbsp; MinInt8&nbsp;&nbsp; = -1 &lt;&lt; 7<br />&nbsp;&nbsp;&nbsp; MaxInt16&nbsp; = 1&lt;&lt;15 - 1<br />&nbsp;&nbsp;&nbsp; MinInt16&nbsp; = -1 &lt;&lt; 15<br />&nbsp;&nbsp;&nbsp; MaxInt32&nbsp; = 1&lt;&lt;31 - 1<br />&nbsp;&nbsp;&nbsp; MinInt32&nbsp; = -1 &lt;&lt; 31<br />&nbsp;&nbsp;&nbsp; MaxInt64&nbsp; = 1&lt;&lt;63 - 1<br />&nbsp;&nbsp;&nbsp; MinInt64&nbsp; = -1 &lt;&lt; 63<br />&nbsp;&nbsp;&nbsp; MaxUint8&nbsp; = 1&lt;&lt;8 - 1<br />&nbsp;&nbsp;&nbsp; MaxUint16 = 1&lt;&lt;16 - 1<br />&nbsp;&nbsp;&nbsp; MaxUint32 = 1&lt;&lt;32 - 1<br />&nbsp;&nbsp;&nbsp; MaxUint64 = 1&lt;&lt;64 - 1<br />)<br />```<br />Integer limit values. <img src ="http://www.cppblog.com/jinq0123/aggbug/217443.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-09-11 14:55 <a href="http://www.cppblog.com/jinq0123/archive/2020/09/11/217443.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>参数太灵活容易出错</title><link>http://www.cppblog.com/jinq0123/archive/2020/07/24/217407.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 24 Jul 2020 01:15:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/07/24/217407.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217407.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/07/24/217407.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217407.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217407.html</trackback:ping><description><![CDATA[参数太灵活容易出错<br /><br />(金庆的专栏 2020.7)<br /><br />golang中可以将参数类型设为 interface{}, 这样就可以传入任意类型的参数，<br />和 C++ 中 void* 的作用相似。<br />但是这种万能类型应该尽量少用，尽量使用具体的类型，或者使用一个具体的接口类型。<br />主要的原因是, 让编译期的类型检查挡住编码错误，减少运行期的错误。<br /><br />例如，go-mongo-driver 有个创建索引的参数：<br />```<br />type IndexModel struct {<br />&nbsp;&nbsp;&nbsp; // A document describing which keys should be used for the index. It cannot be nil.<br />&nbsp;&nbsp;&nbsp; // This must be an order-preserving type such as bson.D. Map types such as bson.M are not valid.<br />&nbsp;&nbsp;&nbsp; Keys interface{}<br />&nbsp;&nbsp;&nbsp; ...<br />}<br />```<br /><br />其中 Keys 可以是任意类型，如 1234, "abcd", 当然不符合索引要求的类型会返回失败。<br />但是 bson.M 类型，会创建索引成功，但是索引的次序会有错误。<br />注释中已指出，不要用 bson.M, 应该使用 bson.D.<br /><br />正确的 Keys 如下，表示复合索引 (field1, field2)，1表示正序，-1则反序：<br />```<br />&nbsp;&nbsp;&nbsp; indexModel.Keys := bson.D{{"field1", 1}, {"field2", 1}}<br />```<br /><br />如果使用 bson.M, 实际上是个 map：<br />```<br />&nbsp;&nbsp;&nbsp; indexModel.Keys := bson.M{"field1"：1, "field2": 1}<br />```<br />因为 map 成员的次序不定，最后创建的索引可能是 (field1, field2)，也可能是 (field2, field1)。<br /><br />此处类型允许 interface{} 的想法是，允许任意类型，会 bson 编码后传给 mongo 服务器，并不会进行类型检查。<br />这种灵活性非常容易造成错误，并且如何使用也不明确, 仅靠注释作用很小。<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217407.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-07-24 09:15 <a href="http://www.cppblog.com/jinq0123/archive/2020/07/24/217407.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>grpc外部负载均衡器测试</title><link>http://www.cppblog.com/jinq0123/archive/2020/04/17/217239.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Fri, 17 Apr 2020 02:08:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/04/17/217239.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217239.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/04/17/217239.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217239.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217239.html</trackback:ping><description><![CDATA[# grpc外部负载均衡器测试<br /><br />(金庆的专栏 2020.4)<br /><br />grpc 对每个请求进行负载均衡。负载均衡的方式有：<br /><br />* 代理模式<br />* 客户端实现<br />* 外部负载均衡<br /><br />参考：gRPC LB https://blog.csdn.net/xiaojia1100/article/details/78842295<br /><br />gRPC 中负载均衡的主要机制是外部负载均衡。<br /><br />gRPC 定义了外部负载均衡服务的接口：https://github.com/grpc/grpc/tree/master/src/proto/grpc/lb/v1<br /><br />* load_balancer.proto 客户端向 lb 服查询后端列表<br />* load_reporter.proto lb 服向后端服查询负载<br /><br />https://github.com/bsm/grpclb 实现了一个 grpc 的外部负载均衡服。<br />因为其实现早于负载均衡服的接口规范，所以接口定义与 grpc 规范不同。<br />见 issue#26: https://github.com/bsm/grpclb/issues/26#issuecomment-613873655<br />grpclb 目前仅支持 consul 服务发现。<br /><br />标准的 grpclb 实现目前好像只有 https://github.com/joa/jawlb。<br />jawlb 通过 Kubernetes API 来发现服务。<br /><br />以下测试 grpc 客户端从 jawlb 服查询服务器列表，然后请求服务。<br />首先在本机开了多个 greeter 服实例，端口不同。<br />然后更改 greeter 客户端，不要直接连 greeter 服地址，而是配一个 jawlb 服地址。<br />同时更改 jawlb, 删除服务发现，改为固定输出本机服务列表，定时切换。<br /><br />greeter 是指 grpc-go 中的例子：grpc-go\examples\helloworld\greeter<br /><br />## greeter 服更改<br /><br />添加参数指定服务端口。<br /><br />```<br />package main<br /><br />import (<br />&nbsp;&nbsp;&nbsp; "fmt"<br />&nbsp;&nbsp;&nbsp; "log"<br />&nbsp;&nbsp;&nbsp; "net"<br /><br />&nbsp;&nbsp;&nbsp; "github.com/spf13/pflag"<br />&nbsp;&nbsp;&nbsp; "github.com/spf13/viper"<br />&nbsp;&nbsp;&nbsp; "golang.org/x/net/context"<br />&nbsp;&nbsp;&nbsp; "google.golang.org/grpc"<br />&nbsp;&nbsp;&nbsp; pb "google.golang.org/grpc/examples/helloworld/helloworld"<br />)<br /><br />// GreeterServer is used to implement helloworld.GreeterServer.<br />type GreeterServer struct {<br />}<br /><br />// SayHello implements helloworld.GreeterServer<br />func (s *GreeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {<br />&nbsp;&nbsp;&nbsp; msg := fmt.Sprintf("Hello %s from server-%d", in.Name, viper.GetInt("port"))<br />&nbsp;&nbsp;&nbsp; return &amp;pb.HelloReply{Message: msg}, nil<br />}<br /><br />func main() {<br />&nbsp;&nbsp;&nbsp; pflag.Int("port", 8000, "server bind port")<br />&nbsp;&nbsp;&nbsp; pflag.Parse()<br />&nbsp;&nbsp;&nbsp; viper.BindPFlags(pflag.CommandLine)<br />&nbsp;&nbsp;&nbsp; port := viper.GetInt("port")<br /><br />&nbsp;&nbsp;&nbsp; addr := fmt.Sprintf(":%d", port)<br />&nbsp;&nbsp;&nbsp; lis, err := net.Listen("tcp", addr)<br />&nbsp;&nbsp;&nbsp; if err != nil {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; log.Fatalf("failed to listen: %v", err)<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; s := grpc.NewServer()<br />&nbsp;&nbsp;&nbsp; pb.RegisterGreeterServer(s, &amp;GreeterServer{})<br />&nbsp;&nbsp;&nbsp; s.Serve(lis)<br />}<br />```<br /><br />## greeter 客户端更改<br />```<br />package main<br /><br />import (<br />&nbsp;&nbsp;&nbsp; "context"<br />&nbsp;&nbsp;&nbsp; "log"<br />&nbsp;&nbsp;&nbsp; "os"<br />&nbsp;&nbsp;&nbsp; "time"<br /><br />&nbsp;&nbsp;&nbsp; "github.com/sirupsen/logrus"<br />&nbsp;&nbsp;&nbsp; "google.golang.org/grpc"<br />&nbsp;&nbsp;&nbsp; _ "google.golang.org/grpc/balancer/grpclb"<br />&nbsp;&nbsp;&nbsp; pb "google.golang.org/grpc/examples/helloworld/helloworld"<br />&nbsp;&nbsp;&nbsp; "google.golang.org/grpc/grpclog"<br />&nbsp;&nbsp;&nbsp; "google.golang.org/grpc/resolver"<br />&nbsp;&nbsp;&nbsp; "google.golang.org/grpc/resolver/manual"<br />)<br /><br />const (<br />&nbsp;&nbsp;&nbsp; defaultName = "world"<br />)<br /><br />func init() {<br />&nbsp;&nbsp;&nbsp; grpclog.SetLogger(logrus.New())<br />}<br /><br />func main() {<br />&nbsp;&nbsp;&nbsp; rb := manual.NewBuilderWithScheme("whatever")<br />&nbsp;&nbsp;&nbsp; rb.InitialState(resolver.State{Addresses: []resolver.Address{<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {Addr: "127.0.0.1:8888", Type: resolver.GRPCLB},<br />&nbsp;&nbsp;&nbsp; }})<br /><br />&nbsp;&nbsp;&nbsp; conn, err := grpc.Dial("whatever:///this-gets-overwritten", grpc.WithInsecure(), grpc.WithBlock(),<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; grpc.WithResolvers(rb))<br />&nbsp;&nbsp;&nbsp; if err != nil {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; log.Fatalf("did not connect: %v", err)<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; defer conn.Close()<br />&nbsp;&nbsp;&nbsp; c := pb.NewGreeterClient(conn)<br /><br />&nbsp;&nbsp;&nbsp; name := defaultName<br />&nbsp;&nbsp;&nbsp; if len(os.Args) &gt; 1 {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; name = os.Args[1]<br />&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp; for {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; r, err := c.SayHello(ctx, &amp;pb.HelloRequest{Name: name})<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; cancel()<br /><br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if err != nil {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; log.Fatalf("could not greet: %v", err)<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; time.Sleep(time.Second)<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; continue<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; log.Printf("Greeting: %s", r.GetMessage())<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; time.Sleep(time.Second)<br />&nbsp;&nbsp;&nbsp; }<br />}<br />```<br /><br />有以下更改：<br />* import _ "google.golang.org/grpc/balancer/grpclb"<br />* grpc.Dial("whatever:///this-gets-overwritten", grpc.WithResolvers(rb))<br />&nbsp;&nbsp;&nbsp; + 采用一个自定义解析器，用来获取 jawlb 地址<br />&nbsp;&nbsp;&nbsp; + Scheme("whatever") 可以任意，用作解析器名字<br />&nbsp;&nbsp;&nbsp; + 目标 this-gets-overwritten 可以任意，因为 jawlb 忽略了该名字<br />&nbsp;&nbsp;&nbsp; + 127.0.0.1:8888 是 jawlb 地址<br />* 改为每秒请求一次<br /><br />正常的 grpclb 是在 DNS 中设置 SRV 记录，<br />此处测试避免设置 DNS, 采用了一个自定义解析器，<br />代码上多了几行。<br />用 DNS 设置的好处是, 可以直接解析为后端 IP, 也可以添加 grpclb, 代码上如同直接连接后端：<br />```<br />&nbsp;&nbsp;&nbsp; conn, err := grpc.Dial("dns:///myservice.domain.com", grpc.WithInsecure())<br />```<br /><br />## jawlb 更改<br /><br />### main.go<br /><br />删除所有配置，改为固定本机 8888 端口监听。<br /><br />* 删除 `envconfig.MustProcess("JAWLB", &amp;cfg)`<br />* listen() 改为<br />&nbsp;&nbsp;&nbsp; ```<br />&nbsp;&nbsp;&nbsp; func listen() (conn net.Listener, err error) {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; conn, err = net.Listen("tcp", ":8888")<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; ```<br />&nbsp;&nbsp;&nbsp; <br />### watch.go<br /><br />```<br />package main<br /><br />import (<br />&nbsp;&nbsp;&nbsp; "context"<br />&nbsp;&nbsp;&nbsp; "fmt"<br />&nbsp;&nbsp;&nbsp; "net"<br />&nbsp;&nbsp;&nbsp; "time"<br />)<br /><br />func watchService(ctx context.Context) (_ &lt;-chan ServerList, err error) {<br />&nbsp;&nbsp;&nbsp; ch := make(chan ServerList)<br /><br />&nbsp;&nbsp;&nbsp; go func() {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ticker := time.NewTicker(10 * time.Second)<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; i := 0<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-ctx.Done():<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ticker.Stop()<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; close(ch)<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-ticker.C:<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; i += 1<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Printf("i = %d\n", i)<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ports := []int32{8010, 8020}<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; var servers []Server<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for _, port := range ports {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; servers = append(servers, Server{IP: net.ParseIP("127.0.0.1"), Port: port + int32(i%2)})<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ch &lt;- servers<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } // select<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } // for<br />&nbsp;&nbsp;&nbsp; }()<br /><br />&nbsp;&nbsp;&nbsp; return ch, nil<br />}<br />```<br /><br />删除所有服务发现代码，改为每10秒切换端口：8010,8020 &lt;-&gt; 8011,8021<br /><br />## 运行<br /><br />### jawlb<br />```<br />&#955; jawlb.exe<br />2020/04/16 15:35:17 waiting for TERM<br />i = 1<br />2020/04/16 15:35:27 endpoints:<br />2020/04/16 15:35:27&nbsp;&nbsp;&nbsp;&nbsp; 127.0.0.1:8011<br />2020/04/16 15:35:27&nbsp;&nbsp;&nbsp;&nbsp; 127.0.0.1:8021<br />i = 2<br />2020/04/16 15:35:37 endpoints:<br />2020/04/16 15:35:37&nbsp;&nbsp;&nbsp;&nbsp; 127.0.0.1:8010<br />2020/04/16 15:35:37&nbsp;&nbsp;&nbsp;&nbsp; 127.0.0.1:8020<br />```<br /><br />### server<br /><br />运行 4 个实例：<br />```<br />server --port 8010<br />server --port 8020<br />server --port 8011<br />server --port 8021<br />```<br /><br />### client<br /><br />```<br />&#955; client<br />INFO[0002] lbBalancer: handle SubConn state change: 0xc00008a590, CONNECTING<br />INFO[0002] Channel Connectivity change to CONNECTING<br />INFO[0002] lbBalancer: handle SubConn state change: 0xc00008a5f0, CONNECTING<br />INFO[0002] Subchannel picks a new address "127.0.0.1:8021" to connect<br />INFO[0002] Subchannel Connectivity change to READY<br />INFO[0002] lbBalancer: handle SubConn state change: 0xc00008a590, READY<br />INFO[0002] Channel Connectivity change to READY<br />INFO[0002] Subchannel Connectivity change to READY<br />INFO[0002] lbBalancer: handle SubConn state change: 0xc00008a5f0, READY<br />2020/04/16 15:37:47 Greeting: Hello world from server-8021<br />2020/04/16 15:37:48 Greeting: Hello world from server-8011<br />2020/04/16 15:37:49 Greeting: Hello world from server-8021<br />2020/04/16 15:37:50 Greeting: Hello world from server-8011<br />2020/04/16 15:37:51 Greeting: Hello world from server-8021<br />2020/04/16 15:37:52 Greeting: Hello world from server-8011<br />2020/04/16 15:37:53 Greeting: Hello world from server-8021<br />2020/04/16 15:37:54 Greeting: Hello world from server-8011<br />2020/04/16 15:37:55 Greeting: Hello world from server-8021<br />2020/04/16 15:37:56 Greeting: Hello world from server-8011<br />INFO[0012] lbBalancer: processing server list: servers:&lt;ip_address:"\000\000\000\000\000\000\000\000\000\000\377\377\177\000\000\001" port:8020 &gt; servers:&lt;ip_address:"\000\000\000\000\000\000\000\000\000\000\377\377\177\000\000\001" port:8010 &gt;<br />INFO[0012] lbBalancer: server list entry[0]: ipStr:|127.0.0.1|, port:|8020|, load balancer token:||<br />INFO[0012] lbBalancer: server list entry[1]: ipStr:|127.0.0.1|, port:|8010|, load balancer token:||<br />2020/04/16 15:37:57 Greeting: Hello world from server-8020<br />2020/04/16 15:37:58 Greeting: Hello world from server-8010<br />2020/04/16 15:37:59 Greeting: Hello world from server-8020<br />2020/04/16 15:38:00 Greeting: Hello world from server-8010<br />2020/04/16 15:38:01 Greeting: Hello world from server-8020<br />2020/04/16 15:38:02 Greeting: Hello world from server-8010<br />2020/04/16 15:38:03 Greeting: Hello world from server-8020<br />2020/04/16 15:38:04 Greeting: Hello world from server-8010<br />2020/04/16 15:38:05 Greeting: Hello world from server-8020<br />2020/04/16 15:38:06 Greeting: Hello world from server-8010<br />INFO[0022] lbBalancer: processing server list: servers:&lt;ip_address:"\000\000\000\000\000\000\000\000\000\000\377\377\177\000\000\001" port:8021 &gt; servers:&lt;ip_address:"\000\000\000\000\000\000\000\000\000\000\377\377\177\000\000\001" port:8011 &gt;<br />INFO[0022] lbBalancer: server list entry[0]: ipStr:|127.0.0.1|, port:|8021|, load balancer token:||<br />INFO[0022] lbBalancer: server list entry[1]: ipStr:|127.0.0.1|, port:|8011|, load balancer token:||<br />2020/04/16 15:38:07 Greeting: Hello world from server-8011<br />2020/04/16 15:38:08 Greeting: Hello world from server-8021<br />2020/04/16 15:38:09 Greeting: Hello world from server-8011<br />```<br /><br />## 结论<br /><br />客户端应用一个自定义 resolver 解析 "whatever:///this-gets-overwritten"，<br />获取到 `{Addr: "127.0.0.1:8888", Type: resolver.GRPCLB}`, <br />知道这是一个 grpclb，于是按 load_balancer.proto 的定义查询 jawlb 来获取后端地址列表。<br /><br />jawlb 每 10s 更新一次服务器列表，每次输出多个地址。客户端在多个地址间轮换请求。<br /><br />## 其他测试<br /><br />* 不开 jawlb，客户端将无法成功请求，直到 jawlb 开启才成功<br />* 中途关闭 jawlb, 请求仍会成功，但是保持为最后的服务器列表<br />&nbsp;&nbsp;&nbsp; + 同时会不断尝试重连 jawlb, 但是重连成功后没有切换服务，应该是个错误<br />* Dial() 不加 grpc.WithBlock() 参数, 报错：all SubConns are in TransientFailure <br /><br />```<br />&#955; client<br />INFO[0000] parsed scheme: "whatever"<br />INFO[0000] ccResolverWrapper: sending update to cc: {[{127.0.0.1:8888&nbsp; &lt;nil&gt; 1 &lt;nil&gt;}] &lt;nil&gt; &lt;nil&gt;}<br />INFO[0000] ClientConn switching balancer to "grpclb"<br />INFO[0000] Channel switches to new LB policy "grpclb"<br />INFO[0000] lbBalancer: UpdateClientConnState: {ResolverState:{Addresses:[{Addr:127.0.0.1:8888 ServerName: Attributes:&lt;nil&gt; Type:1 Metadata:&lt;nil&gt;}] ServiceConfig:&lt;nil&gt; Attributes:&lt;nil&gt;} BalancerConfig:&lt;nil&gt;}<br />INFO[0000] parsed scheme: "grpclb-internal"<br />INFO[0000] ccResolverWrapper: sending update to cc: {[{127.0.0.1:8888&nbsp; &lt;nil&gt; 0 &lt;nil&gt;}] &lt;nil&gt; &lt;nil&gt;}<br />INFO[0000] ClientConn switching balancer to "pick_first"<br />INFO[0000] Channel switches to new LB policy "pick_first"<br />INFO[0000] Subchannel Connectivity change to CONNECTING<br />INFO[0000] blockingPicker: the picked transport is not ready, loop back to repick<br />INFO[0000] pickfirstBalancer: HandleSubConnStateChange: 0xc00003fb10, {CONNECTING &lt;nil&gt;}<br />INFO[0000] Channel Connectivity change to CONNECTING<br />INFO[0000] Subchannel picks a new address "127.0.0.1:8888" to connect<br />INFO[0000] CPU time info is unavailable on non-linux or appengine environment.<br />INFO[0000] Subchannel Connectivity change to READY<br />INFO[0000] pickfirstBalancer: HandleSubConnStateChange: 0xc00003fb10, {READY &lt;nil&gt;}<br />INFO[0000] Channel Connectivity change to READY<br />INFO[0000] lbBalancer: processing server list: servers:&lt;ip_address:"\000\000\000\000\000\000\000\000\000\000\377\377\177\000\000\001" port:8010 &gt; servers:&lt;ip_address:"\000\000\000\000\000\000\000\000\000\000\377\377\177\000\000\001" port:8020 &gt;<br />INFO[0000] lbBalancer: server list entry[0]: ipStr:|127.0.0.1|, port:|8010|, load balancer token:||<br />INFO[0000] lbBalancer: server list entry[1]: ipStr:|127.0.0.1|, port:|8020|, load balancer token:||<br />INFO[0000] Subchannel Connectivity change to CONNECTING<br />INFO[0000] Subchannel Connectivity change to CONNECTING<br />INFO[0000] Channel Connectivity change to TRANSIENT_FAILURE<br />INFO[0000] lbBalancer: handle SubConn state change: 0xc00008a220, CONNECTING<br />INFO[0000] Channel Connectivity change to CONNECTING<br />INFO[0000] lbBalancer: handle SubConn state change: 0xc00008a280, CONNECTING<br />2020/04/16 16:40:06 could not greet: rpc error: code = Unavailable desc = all SubConns are in TransientFailure<br />```<br /><br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217239.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-04-17 10:08 <a href="http://www.cppblog.com/jinq0123/archive/2020/04/17/217239.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>数组make参数错误</title><link>http://www.cppblog.com/jinq0123/archive/2020/03/19/217210.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 19 Mar 2020 05:38:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/03/19/217210.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217210.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/03/19/217210.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217210.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217210.html</trackback:ping><description><![CDATA[# 数组make参数错误<br /><br />(金庆的专栏 2020.3)<br /><br />用 mgo 的 Bulk 接口操作 mongodb 的代码，在 mongodb 4.2 正常，<br />而连 mongodb 2.6 报错：<br />```<br />error parsing element 0 of field documents :: caused by :: wrong type for '0' field, expected object, found 0: null<br />```<br /><br />代码如下：<br />```<br />func (u *Util) AddUsers(userIDs []int) error {<br />&nbsp;&nbsp;&nbsp; docs := make([]interface{}, len(userIDs))<br />&nbsp;&nbsp;&nbsp; for _, userID := range userIDs {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; docs = append(docs, dbdoc.UsersDoc{<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; UserID:&nbsp;&nbsp;&nbsp;&nbsp; userID,<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; })<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; bulk := u.c().Bulk()<br />&nbsp;&nbsp;&nbsp; bulk.Insert(docs...)<br />&nbsp;&nbsp;&nbsp; _, err := bulk.Run()<br />&nbsp;&nbsp;&nbsp; return err<br />}<br />```<br /><br />试着改成如下调用就成功：<br />```<br />func (u *Util) AddUsers(userIDs []int) error {<br />&nbsp;&nbsp;&nbsp; bulk := u.c().Bulk()<br />&nbsp;&nbsp;&nbsp; for _, userID := range userIDs {<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; bulk.Insert(dbdoc.UsersDoc{<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; UserID:&nbsp;&nbsp;&nbsp;&nbsp; userID,<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; })<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; _, err := bulk.Run()<br />&nbsp;&nbsp;&nbsp; return err<br />}<br />```<br /><br />而 Insert() 无论是多个还是单个参数，底层实现是一样的。<br /><br />输入单个如 []int{123} 还是出错.<br />将 Insert(docs...) 改成 Insert(docs[0]) 时发现元素为空。<br />最终找到了错误的代码行：<br />```<br />-&nbsp;&nbsp;&nbsp; docs := make([]interface{}, len(userIDs))<br />+&nbsp;&nbsp;&nbsp; docs := make([]interface{}, 0, len(userIDs))<br />```<br /><br />高版本的mongodb结果正确应该是他忽略了空的文档。<br /><br />感觉 golang make() 即可以2个参数也可以3个参数调用的设计是个错误。<br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217210.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-03-19 13:38 <a href="http://www.cppblog.com/jinq0123/archive/2020/03/19/217210.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>go不要导出channel</title><link>http://www.cppblog.com/jinq0123/archive/2020/03/10/217194.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Tue, 10 Mar 2020 07:58:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/03/10/217194.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217194.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/03/10/217194.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217194.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217194.html</trackback:ping><description><![CDATA[# go不要导出channel<br /><br />(金庆的专栏 2020.3)<br /><br />导出 channel 的包都比较难用。平常也不会将接口设计成 channel.<br />以下摘自：https://studygolang.com/articles/12135?fr=sidebar<br /><br />不要导出并发原语<br /><br />Go 提供了非常易于使用的并发原语，这也导致了它被过度的使用。我们主要担心的是 channel 和 sync package 。有的时候我们会导出一个 channel 给用户使用。另外一个常见的错误就是在使用 sync.Mutex 作为结构体字段的时候没有把它设置成私有。这并不总是很糟糕，不过在写测试的时候却需要考虑的更加全面。<br /><br />当我们导出 channel 的时候我们就为这个包的用户带来了测试上的一些不必要的麻烦。你每导出一个 channel 就是在提高用户在测试时候的难度。为了写出正确的测试，用户必须考虑这些：<br /><br />&nbsp;&nbsp;&nbsp; 什么时候数据发送完成。<br />&nbsp;&nbsp;&nbsp; 在接受数据的时候是否会发生错误。<br />&nbsp;&nbsp;&nbsp; 如果需要在包中清理使用过的channel的时候该怎么做。<br />&nbsp;&nbsp;&nbsp; 如何将 API 封装成一个接口，使我们不用直接去调用它。<br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217194.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-03-10 15:58 <a href="http://www.cppblog.com/jinq0123/archive/2020/03/10/217194.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>go代码覆盖测试</title><link>http://www.cppblog.com/jinq0123/archive/2020/02/29/217174.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sat, 29 Feb 2020 01:10:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/02/29/217174.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217174.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/02/29/217174.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217174.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217174.html</trackback:ping><description><![CDATA[<h1> # go代码覆盖测试</h1><br />(金庆的专栏 2020.2)<br /><br />假设应用名为 MyMod, 主函数的文件为 main.go, 那就创建如下的 main_test.go 文件：<br />```<br /><span style="color: #800000;">package main</span><br /><br /><span style="color: #800000;">/* 测试整个服务器。</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; go test -c -covermode=count -coverpkg ./...</span><br /><span style="color: #800000;">生成 MyMod.test.exe. 复制到 bin 目录下运行：</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; MyMod.test.exe --systemTest --test.coverprofile MyMod.cov</span><br /><span style="color: #800000;">生成代码覆盖测试结果 MyMod.cov, 需要在 go.mod 管理的目录下执行</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; go tool cover -html=MyMod.cov -o MyMod.html</span><br /><span style="color: #800000;">打开 MyMod.html 查看结果。</span><br /><span style="color: #800000;">*/</span><br /><br /><span style="color: #800000;">import (</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; "flag"</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; "fmt"</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; "testing"</span><br /><span style="color: #800000;">)</span><br /><br /><span style="color: #800000;">var systemTest *bool</span><br /><br /><span style="color: #800000;">func init() {</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; systemTest = flag.Bool("systemTest", false, "Set to true when running system tests")</span><br /><span style="color: #800000;">}</span><br /><br /><span style="color: #800000;">// Test started when the test binary is started. Only calls main.</span><br /><span style="color: #800000;">func TestSystem(t *testing.T) {</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; if *systemTest {</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println("Test system...")</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; main()</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; }</span><br /><span style="color: #800000;">}</span><br />```<br /><br />MyMod.cov 会积累，可加入 Git. <br />MyMod.cov 提交前，需对其排序，方便查看 diff.<br /><br />排序脚本 sort_cov.bat 如下：<br />```<br /><span style="color: #800000;">head -1 MyMod.cov &gt; tmp.cov</span><br /><span style="color: #800000;">cat MyMod.cov | sed '1d' | sort &gt;&gt; tmp.cov</span><br /><span style="color: #800000;">cp tmp.cov MyMod.cov</span><br /><span style="color: #800000;">del tmp.cov</span><br /><span style="color: #800000;">pause</span><br />```<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217174.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-02-29 09:10 <a href="http://www.cppblog.com/jinq0123/archive/2020/02/29/217174.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>考察go一致性hash库</title><link>http://www.cppblog.com/jinq0123/archive/2020/02/22/217148.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sat, 22 Feb 2020 04:45:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/02/22/217148.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217148.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/02/22/217148.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217148.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217148.html</trackback:ping><description><![CDATA[<h1> # 考察go一致性hash库</h1><br />(金庆的专栏 2020.2)<br /><br />github 搜 consistent, 按星数依次察看<br /><br /><h2>## stathat/consistent</h2><br />Consistent hash package for Go. <br /><br />688星 <br /><br />stathat 应该是个数据统计的云服务。consistent被认为是生产可用的。<br />所有文档都在 godoc. 示例和接口简洁易懂。<br /><br /><h2>## lafikl/consistent</h2><br />A Go library that implements Consistent Hashing and Consistent Hashing With Bounded Loads. <br /><br />575 星<br /><br />有界负载的一致性哈希算法<br /><br />示例中是 c.GetLeast() 获取最小负载，不像是一致性hash的应用。<br />从代码看，应该是最近的未满载，不是最小负载。<br /><br />`MaxLoad()`说明显示最大负载是自动设置为 (total_load/number_of_hosts)*1.25。<br /><br />`const replicationFactor = 10`<br /><br />没有一次获取多个的接口。<br /><br /><h2>## serialx/hashring</h2><br />Consistent hashing "hashring" implementation in golang (using the same algorithm as libketama)<br /><br />367 星<br /><br />是从 Python 库移植的。可以设置 weight.<br /><br />示例<br />```<br /><span style="color: #800000;">replicaCount := 3</span><br /><span style="color: #800000;">ring := hashring.New(serversInRing)</span><br /><span style="color: #800000;">server, _ := ring.GetNodes("my_key", replicaCount)</span><br />```<br />实际上是 GetN 的功能。内部实现缺少 replicationFactor<br /><br /><h2>## buraksezer/consistent</h2><br />Consistent hashing with bounded loads in Golang <br /><br />263 星<br /><br />未提供缺省hash函数。<br /><br />多了一个`PartitionCount`<br /><br />c.loads 仅用于查询负载，并没有用于有界负载。<br /><br />结论是这个不是有界负载的一致性哈希，同时一致性哈希的实现不同寻常。<br /><br /><h2>## 其他</h2><br />jump hash 不能移除节点，不考虑<br /><br /><h3>### dgryski/go-jump</h3><br />go-jump: Jump consistent hashing<br /><br />281 星<br /><br /><h3>### lithammer/go-jump-consistent-hash</h3><br />⚡️ Fast, minimal memory, consistent hash algorithm&nbsp; <br /><br />138 星<img src ="http://www.cppblog.com/jinq0123/aggbug/217148.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-02-22 12:45 <a href="http://www.cppblog.com/jinq0123/archive/2020/02/22/217148.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>将go函数指针转为接口</title><link>http://www.cppblog.com/jinq0123/archive/2020/02/13/217125.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 13 Feb 2020 06:38:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/02/13/217125.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217125.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/02/13/217125.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217125.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217125.html</trackback:ping><description><![CDATA[<h1># 将go函数指针转为接口</h1><br />(金庆的专栏 2020.2)<br /><br />golang 中的接口如下：<br /><br />```<br /><span style="color: #800000;">type Writer interface {</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; Write func(p []byte) (n int, err error)</span><br /><span style="color: #800000;">}</span><br />```<br /><br />一般API参数要求一个接口，而不是一个函数指针，如 io.Copy() 需要输入一个 Writer 和 Reader：<br />```<br /><span style="color: #800000;">func Copy(dst Writer, src Reader) (written int64, err error)</span><br />```<br /><br />而不是这样2个函数指针：<br />```<br /><span style="color: #800000;">func CopyWithFunc(writeFunc func([]byte) (int, error), readRunc func([]byte) (int, error)) (written int64, err error)</span><br />```<br /><br />大家统一使用接口，而不是接口和函数指针混用，可以避免API复杂化。<br />如 io.Copy() 有2个参数，如果要支持接口和函数指针混用，就会变成4个 Copy() 重载。<br />golang 没有重载，就只能用4个不同的函数名。<br /><br />在实际使用中，需要将函数转化成接口，才能调用 io.Copy().<br />如有一个函数: <br />```<br /><span style="color: #800000;">func MyWriteFunction(p []byte) (n int, err error) { </span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; fmt.Print("%v",p)</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; return len(p),nil</span><br /><span style="color: #800000;">}</span><br />```<br />调用 io.Copy() 时需要创建一个 Writer，并将该函数指针转型为Writer后使用。<br />这里用 `WriteFunc` 类型实现 Writer。<br /><br />```<br /><span style="color: #800000;">type WriteFunc func(p []byte) (n int, err error)</span><br /><br /><span style="color: #800000;">func (wf WriteFunc) Write(p []byte) (n int, err error) {</span><br /><span style="color: #800000;">&nbsp;&nbsp;&nbsp; return wf(p)</span><br /><span style="color: #800000;">}</span><br />```<br /><br />WriteFunc 本身是个与 MyWriteFunction 同类型的函数类型，同时实现了 Writer 接口。<br />所以 MyWriteFunction 可以直接转成WriteFunc类型成为一个 Writer.<br />这样就可以调用 io.Copy() 了：<br /><br />```<br /><span style="color: #800000;">io.Copy(WriteFunc(MyWriteFunction), strings.NewReader("Hello world"))</span><br />```<br /><br />参考：https://stackoverflow.com/questions/20728965/golang-function-pointer-as-a-part-of-a-struct<img src ="http://www.cppblog.com/jinq0123/aggbug/217125.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-02-13 14:38 <a href="http://www.cppblog.com/jinq0123/archive/2020/02/13/217125.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>golang chan 关闭时的原则</title><link>http://www.cppblog.com/jinq0123/archive/2020/01/08/217067.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 08 Jan 2020 11:46:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2020/01/08/217067.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/217067.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2020/01/08/217067.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/217067.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/217067.html</trackback:ping><description><![CDATA[golang chan 关闭时的原则<br /><br />(金庆的专栏 2020.1)<br /><br />golang 程序中检测到 DATA RACE, 是 chan 关闭和发送冲突：<br /><br /><span style="color: #0000ff;">==================</span><br /><span style="color: #0000ff;">WARNING: DATA RACE</span><br /><span style="color: #0000ff;">Write at 0x00c000098010 by goroutine 68:</span><br /><span style="color: #0000ff;">&nbsp; runtime.closechan()</span><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/lib/golang/src/runtime/chan.go:327 +0x0</span><br /><span style="color: #0000ff;">&nbsp; valky/common/tcp.(*Session).Close()</span><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /var/tmp/src/f4f4f712-7894-4d98-83dd...</span><br /><span style="color: #0000ff;">&nbsp; valky/common/tcp.(*Session).recvloop()</span><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /var/tmp/src/f4f4f712-7894-4d98-83dd...</span><br /><br /><span style="color: #0000ff;">Previous read at 0x00c000098010 by goroutine 100:</span><br /><span style="color: #0000ff;">&nbsp; runtime.chansend()</span><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /usr/lib/golang/src/runtime/chan.go:140 +0x0</span><br /><span style="color: #0000ff;">&nbsp; valky/common/tcp.(*Session).Send()</span><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /var/tmp/src/f4f4f712-7894-4d98-83dd...</span><br /><span style="color: #0000ff;">&nbsp; main.(*Role).sendMsg()</span><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /var/tmp/src/f4f4f712-7894-4d98-83dd...</span><br /><span style="color: #0000ff;">==================</span><br /><span style="color: #0000ff;">Found 1 data race(s)</span><br /><br />查了一下 chan 关闭的正确做法，发现了一篇非常详细的文章：<br />[How to Gracefully Close Channels](https://go101.org/article/channel-closing.html)<br /><br />文中指出，chan 多次关闭，或者在关闭的 chan 上发送，都会 panic. <br />上面的 DATA RACE 属于幸运，没有 panic。<br /><br />chan 关闭时的原则是：不要在接收协程中关闭，并且，如果有多个发送者时就不要关闭chan了。<br /><br />上面的DATA RACE 是在接收协程中关闭chan.<br /><br />文中详细列出了多种方案关闭chan. <br />如果粗暴点，可以直接加个 recover. 其他方案都是要保证发送完成后再关闭。<br /><br /><img src ="http://www.cppblog.com/jinq0123/aggbug/217067.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2020-01-08 19:46 <a href="http://www.cppblog.com/jinq0123/archive/2020/01/08/217067.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>open-match匹配流程</title><link>http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Thu, 31 Jan 2019 02:21:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/216228.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/216228.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/216228.html</trackback:ping><description><![CDATA[<div><h1># open-match匹配流程</h1><br />(金庆的专栏 2019.1)<br /><br />https://github.com/GoogleCloudPlatform/open-match<br /><br />open-match 是一个通用的游戏匹配框架。<br />由游戏提供自定义的匹配算法（以docker镜像的方式提供）。<br /><br />分为多个进程，各进程之间共享一个 redis.<br /><br />* 前端, 接收玩家加入 redis，成功后通知玩家房间服地址<br />* 后端，设置一局游戏的匹配规则，设置房间服地址<br />* MMFOrc，启动匹配算法(MMF)<br />* MMF, 自定义匹配算法，读取 redis 获取玩家，匹配成功就将结果写入 redis. 仅匹配一局就退出。<br /><br />游戏服中连接 open-match 的前端与后端的进程，分别称为 frontendclient 和 Director。<br />输入分2部份，一是玩家信息，二是对局信息。<br />Director 向后端输入对局信息，就会收到一个接一个的对局人员列表.<br />Director 需要为每个对局开房间，然后通知后端房间地址。<br />后端将房间地址写入 redis, 然后前端读取到房间地址，就通知 frontendclient，让玩家进入房间。<br /><br /><h2>## test/cmd/frontendclient</h2><br />模拟大厅服或组队服，连接前端API, 请求匹配玩家/队伍。成功后将收到房间服(DGS)的地址(Assignment)。<br /><br />Player 实际上是一个队伍，其中ID字段是用空格分隔的多个ID. <br />虽然参数类型都是 Player, CreatePlayer() 参数为整个队伍，而 GetUpdates() 参数是单个玩家。<br /><br />main() 中创建多个玩家，每个玩家调用 GetUpdates() 以获取结果，go waitForResults() 中处理结果。<br />waitForResult() 读取流中的匹配结果，压入 resultsChan（但好像 resultsChan 仅用于打印）。<br />所有玩家合并到 g 实例中，然后调用 CreatePlayer() 请求匹配。<br /><br />cleanup() 调用 DeletePlayer() 来删除匹配请求，不仅需删除整个队伍，也需要删除单个玩家。<br /><br />好像最后取结果没取对地方，应该从 resultChan 中获取 Assignment, 并用该地址 udpClient().<br /><br />看了该示例就可以理解 frontend.proto<br /><br /><h2>## examples/backendclient</h2><br />MatchObject.Properties 是从 testprofile.json 读取的，应该改名为 Profile 是否更好点？<br />pbProfile 是 MatchObject，Profile 等同于 MatchObject?<br />Profile 的定义是 MMF 所需的所有参数。<br />`pbProfile.Properties = jsonProfile` 重复了2遍。<br /><br />ListMatches()列出这个Profile的所有匹配。<br />收到一个匹配后，须用CreateAssignments()将房间服地址, 称为 Assignment, 发送到所有游戏客户端。<br /><br /><h2>## cmd/frontendapi</h2><br />CreatePlayer() 将 Player 对象写入 redis, 键值为 Player.Id, 类型为 HSET。<br />对 Player 的每个 attribute，添加到 ZSET 中去。<br />此处 Player 是一组玩家。<br /><br />GetUpdates() 每隔2s读取redis, Player数据有变化时就发送。此处 Player 是单个玩家。<br /><br />如果CreatePlayer()中队伍只有一个玩家，<br />则写入的Player与GetUpdates()中读取的玩家是同一个redis键。<br /><br /><h2>## cmd/backendapi</h2><br />CreateMatch() 中 profile 类型为 MatchObject, 是一个比赛的限制条件。<br />profile 先写入 redis, 键为 profile.Id.<br />`requestKey := xid() + "." + profile.Id`,<br />并将 requestKey 加入 redis 集合 "profileq"。<br />然后每2s查询 redis, 看是否有 requestKey 键出现，并返回该值。<br /><br />ListMatch() 每2s调用一次 CreateMatch().<br /><br />DeleteMatch() 仅仅删除 Id 这个键。<br /><br />CreateAssignments() 为多个队伍设置Assignment, 即房间地址。<br />遍历所有Roster中的Player对象，在redis中设置Assignment.<br />(Assignment 更改后，会触发前端更新。)<br />将所有 Player.Id 从 "proposed" 移到 "deindexed"，这两个是 ZSET, 分值为加入时间。<br />Roster 应该是比赛中的阵营，如红方，蓝方，每个阵营中可有多个队伍。<br /><br />DeleteAssignments() 仅仅遍历所有 Player 对象来删除 Assignment 字段。<br /><br /><h2>## cmd/mmforc</h2><br />匹配流程是由 mmforc (matchmaking function orchestrator) 控制的。<br /><br />mmforc 每秒从 redis 的 profileq 中取出 100 个成员, 其中 profileq 是个set类型，<br />使用命令为`SPOP profileq 100`.<br /><br />对每个 profile, 创建一个 k8s 任务：<br /><br />```<br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;// Kick off the job asynchrnously</span><br /><span style="color: #800000;">&nbsp;&nbsp; &nbsp;go mmfunc(ctx, profile, cfg, defaultMmfImages, clientset, &amp;pool)</span><br />```<br /><br />每隔10s, 还有所有匹配任务都完成后，需要 `checkProposals`, 即创建 evaluator 任务。<br /><br />profileq 中的元素 profile 为字符串，matchObjectID.profileID。<br />以 profileID 为键，可以从 redis 读取 profile 的内容, profile 是个 MatchObject 对象。<br /><br />profile 的内容为 json 串，其中 "jsonkeys.mmfImages" 为 mmf (matchmaking function) 镜像。<br /><br />如果profile读取失败，或者 mmfImages 为空，则使用默认的镜像。mmfImages 未来会支持多个镜像。<br /><br />通过 MMF_* 环境变量传入各种参数.<br /><br /><h2>## mmf</h2><br />示例：examples\functions\golang\manual-simple<br /><br />从环境变量 "MMF_PROFILE_ID" 解析出 profileID, 并向 redis 查询(HGETALL) profile，HSET 类型。<br /><br />从 profile 中取 pools 字段，即匹配条件。<br />pools 分为多个 pool, 每个 pool 中有多个 filter, 每个 filter 向 redis 取符合的 Player.<br /><br />profile 用到以下字段：<br /><br />* "properties.playerPool"<br />&nbsp; json串，是一些过滤条件，如&#8220;mmr: 100-999&#8221;<br />* "properties.roster"<br />&nbsp; json串, 是多个队伍大小，如 &#8220;red: 4&#8221;<br /><br />示例见：`examples\backendclient\profiles\testprofile.json`<br /><br /><h3>### 简单匹配过程</h3><br />simple mmf 的匹配过程如下：<br /><br />1. 从 redis 查询 profile，获取过滤条件和各队伍大小<br />1. 每个过滤条件向 redis 查询，所有结果的交集为可选成员<br />1. 去除 ignoreList, 即最近 800s 内已匹配成功的成员，即 proposal 和 deindexed ZSET 列表。<br />1. 如果可选成员个数太小，则 insufficient_players 并退出<br />1. 分配各个队伍成员<br />1. 向 redis 记录结果<br /><br /><h3>### 结果</h3><br />profile 中添加 roster，即各阵营成员名单，存入 prososalKey.<br />保存不分队伍的成员名单。<br />然后向 "proposalq" 添加 prososalKey<br /><br /><h3>### 细节</h3><br />poolRosters 以 (pool名, filter attribute) 为键，值为 Player ID 列表. <br />保存从 redis 查询的符合条件的 Player ID.<br /><br />overlaps 以 pool 名为键，保存符合该pool中所有filter的 Player ID 列表，去除 ignore list.<br /><br />rosters 是 profile 中的 "properties.rosters" 字段。不知何用？<br />遍历 rosters, 为每个阵营的每个player找到对应pool的PlayerID, 保存到 mo.Rosters.<br />其中 profileRosters 好像没用。<br /><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/216228.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2019-01-31 10:21 <a href="http://www.cppblog.com/jinq0123/archive/2019/01/31/216228.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>试用 go mod</title><link>http://www.cppblog.com/jinq0123/archive/2018/12/26/216141.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 26 Dec 2018 02:07:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/12/26/216141.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/216141.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/12/26/216141.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/216141.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/216141.html</trackback:ping><description><![CDATA[<div>试用 go mod</div><div></div><div><div>(金庆的专栏 2018.12)</div><br />Go 1.11 支持 module.<br /><br />代码不需要在 GOPATH/src 目录下。<br /><br />先初始化模块，生成 `go.mod`<br /><br /><span style="color: #0000ff;">E:\temp</span><br /><span style="color: #0000ff;">&#955; mkdir -p testmod\hello</span><br /><br /><span style="color: #0000ff;">E:\temp</span><br /><span style="color: #0000ff;">&#955; cd testmod\hello\</span><br /><br /><span style="color: #0000ff;">E:\temp\testmod\hello</span><br /><span style="color: #0000ff;">&#955; go mod init github.com/jinq0123/hello</span><br /><span style="color: #0000ff;">go: creating new go.mod: module github.com/jinq0123/hello</span><br /><br /><br />创建 `hello.go`<br /><br /><span style="color: #800000; font-family: Courier;">package main</span><br /><br /><span style="color: #800000; font-family: Courier;">import (</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;"fmt"</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;"rsc.io/quote"</span><br /><span style="color: #800000; font-family: Courier;">)</span><br /><br /><span style="color: #800000; font-family: Courier;">func main() {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;fmt.Println(quote.Hello())</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><br />构建时报 `golang.org/x/text` 连不上：<br /><br /><span style="color: #0000ff;">E:\temp\testmod\hello</span><br /><span style="color: #0000ff;">&#955; go build</span><br /><span style="color: #0000ff;">go: golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c: unrecog</span><br /><span style="color: #0000ff;">nized import path "golang.org/x/text" (https fetch: Get https://g</span><br /><span style="color: #0000ff;">olang.org/x/text?go-get=1: dial tcp 216.239.37.1:443: connectex:</span><br /><span style="color: #0000ff;">A socket operation was attempted to an unreachable network.)</span><br /><span style="color: #0000ff;">go: error loading module requirements</span><br /><br /><br />`go.mod` 添加<br /><br /><span style="color: #800000;">replace golang.org/x/text =&gt; github.com/golang/text v0.3.0</span><br /><br /><br />然后构建就成功了：<br /><br /><span style="color: #0000ff;">E:\temp\testmod\hello</span><br /><span style="color: #0000ff;">&#955; go build</span><br /><span style="color: #0000ff;">go: finding github.com/golang/text v0.3.0</span><br /><span style="color: #0000ff;">go: downloading rsc.io/sampler v1.3.0</span><br /><span style="color: #0000ff;">go: downloading github.com/golang/text v0.3.0</span><br /><br /><span style="color: #0000ff;">E:\temp\testmod\hello</span><br /><span style="color: #0000ff;">&#955; hello.exe</span><br /><span style="color: #0000ff;">Hello, world.</span><br /><br /><br />如果不加版本号，则会报错：<br /><br /><span style="color: #0000ff;">go.mod:9: replacement module without version must be directory path (rooted or starting with ./ or ../)</span><br /><br /><br />Go 1.11.1 replace 还有问题，仍会试图连接原地址。目前版本 1.11.4 可以用。<br /><br />参考：<br />https://github.com/golang/go/wiki/Modules</div><img src ="http://www.cppblog.com/jinq0123/aggbug/216141.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-12-26 10:07 <a href="http://www.cppblog.com/jinq0123/archive/2018/12/26/216141.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>gotest 是有缓存的</title><link>http://www.cppblog.com/jinq0123/archive/2018/10/29/216026.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Mon, 29 Oct 2018 10:47:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/10/29/216026.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/216026.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/10/29/216026.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/216026.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/216026.html</trackback:ping><description><![CDATA[<div>gotest 是有缓存的<br /><br />(金庆的专栏 2018.10)<br /><br />用 gotest 运行一个测试，往 mongodb 中插入一条，发现有时灵，有时不灵。<br /><br />因为错误地怀疑 mgo 用错了，耗费了不少时间。<br />最终发现是因为 gotest 是有缓存的，输出的是上次运行的结果，但是并没有实际运行代码。<br /><br />运行有效是因为代码刚改过，测试时会实际运行。<br /><br />最终也是无意间发现的。给 mgo 开启了调试日志，然后比较2次运行，发现输出是一样的，<br />只有一行不同：<br /><br /><span style="color: #0000ff;">ok&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mail-server/server&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.519s</span><br /><span style="color: #0000ff;">ok&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mail-server/server&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (cached)</span><br /><br />明确显示了第2次是缓存。前面运行了几十次都忽略了 cached 这个输出。<br /><br />为了禁止缓存，可加上 -count=1 参数：<br /><span style="color: #800000;">go test -count=1</span><br /><br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/216026.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-10-29 18:47 <a href="http://www.cppblog.com/jinq0123/archive/2018/10/29/216026.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>K8s获取NodePort</title><link>http://www.cppblog.com/jinq0123/archive/2018/09/04/215903.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Tue, 04 Sep 2018 07:17:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/09/04/215903.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215903.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/09/04/215903.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215903.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215903.html</trackback:ping><description><![CDATA[<div>K8s获取NodePort<br /><br />(金庆的专栏 2018.9)<br /><br />将服务用NodePort暴露到外网，为避免端口冲突，不指定NodePort,<br />&nbsp;而是让k8s自动选择一个端口。<br />&nbsp;<br /><span style="color: #0000ff; font-family: Courier;">$ cat get_node_port.yaml</span><br /><span style="color: #0000ff; font-family: Courier;">kind: Service</span><br /><span style="color: #0000ff; font-family: Courier;">apiVersion: v1</span><br /><span style="color: #0000ff; font-family: Courier;">metadata:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; name: jq-service</span><br /><span style="color: #0000ff; font-family: Courier;">spec:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; type: NodePort</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; selector:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; app: MyApp</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; ports:</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp; - protocol: TCP</span><br /><span style="color: #0000ff; font-family: Courier;">&nbsp;&nbsp;&nbsp; port: 80</span></div><div></div><div><br /><span style="color: #0000ff; font-family: Courier;">$ kubectl apply -f get_node_port.yaml</span><br /><span style="color: #0000ff; font-family: Courier;">service "jq-service" configured</span><br /><span style="color: #0000ff; font-family: Courier;">$ kubectl describe svc/jq-service</span><br /><span style="color: #0000ff; font-family: Courier;">Name:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jq-service</span><br /><span style="color: #0000ff; font-family: Courier;">Namespace:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default</span><br /><span style="color: #0000ff; font-family: Courier;">Labels:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;none&gt;</span><br /><span style="color: #0000ff; font-family: Courier;">Annotations:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; kubectl...</span><br /><span style="color: #0000ff; font-family: Courier;">Selector:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app=MyApp</span><br /><span style="color: #0000ff; font-family: Courier;">Type:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NodePort</span><br /><span style="color: #0000ff; font-family: Courier;">IP:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.104.228.187</span><br /><span style="color: #0000ff; font-family: Courier;">Port:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;unset&gt;&nbsp; 80/TCP</span><br /><span style="color: #0000ff; font-family: Courier;">TargetPort:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 80/TCP</span><br /><span style="color: #0000ff; font-family: Courier;">NodePort:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;unset&gt;&nbsp; 32115/TCP</span><br /><span style="color: #0000ff; font-family: Courier;">Endpoints:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;none&gt;</span><br /><span style="color: #0000ff; font-family: Courier;">Session Affinity:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; None</span><br /><span style="color: #0000ff; font-family: Courier;">External Traffic Policy:&nbsp; Cluster</span><br /><span style="color: #0000ff; font-family: Courier;">Events:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;none&gt;</span><br /><br /><br />可以看到k8s分配了NodePort 32115。<br /><br />然后需要获取这个动态的NodePort，以通知客户端连接该端口。<br /><br /><span style="color: #993300; font-family: Courier;">package main</span><br /><br /><span style="color: #993300; font-family: Courier;">import (</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; "context"</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; "fmt"</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; "log"</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; "io/ioutil"</span><br /><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; "github.com/ghodss/yaml"</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; "github.com/ericchiang/k8s"</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; corev1 "github.com/ericchiang/k8s/apis/core/v1"</span><br /><span style="color: #993300; font-family: Courier;">)</span><br /><br /><span style="color: #993300; font-family: Courier;">func main() {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; data, err := ioutil.ReadFile("config")</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; if err != nil {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(err)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; }</span><br /><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; // Unmarshal YAML into a Kubernetes config object.</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; var config k8s.Config</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; if err := yaml.Unmarshal(data, &amp;config); err != nil {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(err)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; }</span><br /><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; client, err := k8s.NewClient(&amp;config)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; // client, err := k8s.NewInClusterClient()</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; if err != nil {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; }</span><br /><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; var svc corev1.Service</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; if err := client.Get(context.Background(), "default", "jq-service", &amp;svc); err != nil {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; }</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; fmt.Printf("%d\n", *svc.Spec.Ports[0].NodePort)</span><br /><span style="color: #993300; font-family: Courier;">}</span><br /><br />运行时需要复制config: `cp ~/.kube/config .`<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215903.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-09-04 15:17 <a href="http://www.cppblog.com/jinq0123/archive/2018/09/04/215903.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>grpc中的dns负载均衡</title><link>http://www.cppblog.com/jinq0123/archive/2018/08/29/215886.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Wed, 29 Aug 2018 02:59:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/08/29/215886.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215886.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/08/29/215886.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215886.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215886.html</trackback:ping><description><![CDATA[<div>grpc中的dns负载均衡<br /><br />(金庆的专栏 2018.8)<br /><br />grpc-go 中如下连接服务器，请求将在多个IP之间轮转。<br /><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; conn, err := grpc.Dial(</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;"dns:///rng-headless:8081",</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;grpc.WithBalancerName(roundrobin.Name),</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;grpc.WithInsecure())</span><br /><br />标准的目标名应该是这样的：`"dns://authority/endpoint_name"`,<br />此处 authority 为空，详见：https://github.com/grpc/grpc/blob/master/doc/naming.md<br /><br />服务器开3个实例，所有请求在3个实例上轮转：<br /><br /><span style="color: #0000ff;">[jinqing@host-10-2-3-4 RoundRobin]$ kubectl run -it --rm jinqing-roundrobin --image=jinq0123/roundrobin:4</span><br /><span style="color: #0000ff;">If you don't see a command prompt, try pressing enter.</span><br /><span style="color: #0000ff;">2018/08/28 10:18:01 request 7754383576636566559</span><br /><span style="color: #0000ff;">2018/08/28 10:18:02 request 2543876599219675746</span><br /><span style="color: #0000ff;">2018/08/28 10:18:03 request 927204261937181213</span><br /><span style="color: #0000ff;">2018/08/28 10:18:04 request 7754383576636566559</span><br /><span style="color: #0000ff;">2018/08/28 10:18:05 request 2543876599219675746</span><br /><span style="color: #0000ff;">2018/08/28 10:18:06 request 927204261937181213</span><br /><span style="color: #0000ff;">...</span><br /><br />服务器返回一个随机数，不同实例的随机数不同。代码是从<br />https://github.com/kcollasarundell/balancing-on-k8s 修改的。<br /><br /><span style="color: #993300; font-family: Courier;">...</span><br /><span style="color: #993300; font-family: Courier;">const (</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port = ":8081"</span><br /><span style="color: #993300; font-family: Courier;">)</span><br /><br /><span style="color: #993300; font-family: Courier;">type server struct{}</span><br /><br /><span style="color: #993300; font-family: Courier;">var r int64</span><br /><br /><span style="color: #993300; font-family: Courier;">func init(){</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; rand.Seed(time.Now().UnixNano())</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp; r = rand.Int63()</span><br /><span style="color: #993300; font-family: Courier;">}</span><br /><br /><span style="color: #993300; font-family: Courier;">func (s *server) Rng(context.Context, *rng.Source) (*rng.RN, error) {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &amp;rng.RN{RN: r}, nil</span><br /><span style="color: #993300; font-family: Courier;">}</span><br /><br /><span style="color: #993300; font-family: Courier;">func main() {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lis, err := net.Listen("tcp", port)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatalf("failed to listen: %v", err)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s := grpc.NewServer()</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rng.RegisterRngServer(s, &amp;server{})</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Register reflection service on gRPC server.</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reflection.Register(s)</span><br /><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err := s.Serve(lis); err != nil {</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatalf("failed to serve: %v", err)</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span><br /><span style="color: #993300; font-family: Courier;">}</span><br /><br />先编译，打包成镜像，然后用 `balancing-on-k8s\backend\kube.yaml` 运行：<br /><span style="color: #0000ff;">kubectl apply -f kube.yaml</span><br /><br />`backend\kube.yaml` 创建了一个 ClusterIP 服务和一个 Headless 服务，部署了 3 个服务器实例。<br /><span style="color: #0000ff;">[jinqing@host-10-2-3-4 RoundRobin]$ kubectl get svc</span><br /><span style="color: #0000ff;">NAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TYPE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CLUSTER-IP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EXTERNAL-IP&nbsp;&nbsp; PORT(S)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AGE</span><br /><span style="color: #0000ff;">kubernetes&nbsp;&nbsp;&nbsp;&nbsp; ClusterIP&nbsp;&nbsp; 10.96.0.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;none&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 443/TCP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 93d</span><br /><span style="color: #0000ff;">rng-cluster&nbsp;&nbsp;&nbsp; ClusterIP&nbsp;&nbsp; 10.111.30.205&nbsp;&nbsp; &lt;none&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8081/TCP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4h</span><br /><span style="color: #0000ff;">rng-headless&nbsp;&nbsp; ClusterIP&nbsp;&nbsp; None&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;none&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 8081/TCP,8080/TCP&nbsp;&nbsp; 4h</span><br /><br />客户端是一个简单的grpc, 定时发送请求，打印返回的随机数。<br />`balancing-on-k8s\clientSideBalancer\RoundRobin\main.go`中的地址需要添加端口，<br />不然grpc会去连接 443 端口而失败。<br /><br />扩容后，测到大概3分钟后才看到负载转移。缩容后会立即生效。<br /><span style="color: #0000ff;">kubectl scale --replicas=5 deployment/rng</span><br /><br />如果是 ClusterIP 服务, 则服务名对应一个ClusterIP;<br />如果是 Headless 服务，则服务名对应各个Pod的IP:<br /><br /><span style="color: #0000ff;">/ # nslookup rng-headless</span><br /><span style="color: #0000ff;">Server:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.96.0.10</span><br /><span style="color: #0000ff;">Address:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.96.0.10#53</span><br /><br /><span style="color: #0000ff;">Name:&nbsp;&nbsp; rng-headless.default.svc.cluster.local</span><br /><span style="color: #0000ff;">Address: 10.244.3.27</span><br /><span style="color: #0000ff;">Name:&nbsp;&nbsp; rng-headless.default.svc.cluster.local</span><br /><span style="color: #0000ff;">Address: 10.244.0.108</span><br /><span style="color: #0000ff;">Name:&nbsp;&nbsp; rng-headless.default.svc.cluster.local</span><br /><span style="color: #0000ff;">Address: 10.244.2.66</span><br /><br /><span style="color: #0000ff;">/ # nslookup rng-cluster</span><br /><span style="color: #0000ff;">Server:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.96.0.10</span><br /><span style="color: #0000ff;">Address:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10.96.0.10#53</span><br /><br /><span style="color: #0000ff;">Name:&nbsp;&nbsp; rng-cluster.default.svc.cluster.local</span><br /><span style="color: #0000ff;">Address: 10.111.30.205</span><br /><br /><span style="color: #0000ff;">/ #</span><br /><br />如果去除 "dns:///", 仅仅是域名加端口：<br /><br /><span style="color: #993300; font-family: Courier;">conn, err := grpc.Dial(</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;"rng-headless:8081",</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;grpc.WithBalancerName(roundrobin.Name),</span><br /><span style="color: #993300; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;...</span><br /><br />则只会请求同一个实例。只有当该实例pod被删除后才会切换到另一个实例。<br />使用缩容时发现会优先删除没有客户端连接的实例。<br />用2个客户端连接到不同服务器实例，然后缩容为1实例，就可以看到请求切换。<br /><br />如果客户端和服务器数量很大，这个dns负载均衡就不合适了，因为客户端会连接每个服务器实例。<br /><br />参考：<br />Exploring Kubernetes Service Discovery and loadbalancing ( https://kca.id.au/post/k8s_service/ )<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215886.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-08-29 10:59 <a href="http://www.cppblog.com/jinq0123/archive/2018/08/29/215886.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>k8s集群外go客户端示例</title><link>http://www.cppblog.com/jinq0123/archive/2018/07/22/215796.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Sun, 22 Jul 2018 03:14:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/07/22/215796.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215796.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/07/22/215796.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215796.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215796.html</trackback:ping><description><![CDATA[<div>k8s集群外go客户端示例<br /><br />(金庆的专栏 2018.7)<br /><br />集群内客户端需要打包成docker镜像，上传镜像，然后用 kubectl run 运行，<br />还要设置用户角色，太麻烦，还是用集群外客户端测试比较方便。<br /><br />客户端库使用 ericchiang/k8s, 比官方的 client-go 要简单许多。<br /><br />集群内客户端使用`k8s.NewInClusterClient()`创建，<br />集群外客户端使用 `NewClient(config *Config)`, 需要输入配置，<br />配置就是从 ~/.kube/config 读取的。<br />参考 https://github.com/ericchiang/k8s/issues/79<br /><br />代码如下：<br /><br /><span style="font-family: Courier; color: #800000;">package main</span><br /><br /><span style="font-family: Courier; color: #800000;">import (</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; "context"</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; "fmt"</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; "log"</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; "io/ioutil"</span><br /><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; "github.com/ghodss/yaml"</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; "github.com/ericchiang/k8s"</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; corev1 "github.com/ericchiang/k8s/apis/core/v1"</span><br /><span style="font-family: Courier; color: #800000;">)</span><br /><br /><span style="font-family: Courier; color: #800000;">func main() {</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; data, err := ioutil.ReadFile("config")</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; if err != nil {</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(err)</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; }</span><br /><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; // Unmarshal YAML into a Kubernetes config object.</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; var config k8s.Config</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; if err := yaml.Unmarshal(data, &amp;config); err != nil {</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(err)</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; }</span><br /><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; client, err := k8s.NewClient(&amp;config)</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; // client, err := k8s.NewInClusterClient()</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; if err != nil {</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; }</span><br /><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; var nodes corev1.NodeList</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; if err := client.List(context.Background(), "", &amp;nodes); err != nil {</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log.Fatal(err)</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; }</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; for _, node := range nodes.Items {</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf("name=%q schedulable=%t\n", *node.Metadata.Name, !*node.Spec.Unschedulable)</span><br /><span style="font-family: Courier; color: #800000;">&nbsp;&nbsp;&nbsp; }</span><br /><span style="font-family: Courier; color: #800000;">}</span><br /><br />yaml 库用了 ghodss/yaml，不能用 go-yaml, 不然报错<br />`yaml: unmarshal errors`<br />见：https://github.com/ericchiang/k8s/issues/81<br /><br />复制 .kube/config 到运行目录，运行列出所有节点：<br /><br /><span style="font-family: Courier; color: #0000ff;">[jinqing@host-10-1-2-19 out-cluster]$ cp ~/.kube/config .</span><br /><span style="font-family: Courier; color: #0000ff;">[jinqing@host-10-1-2-19 out-cluster]$ ./out-cluster </span><br /><span style="font-family: Courier; color: #0000ff;">name="host-10-1-2-20" schedulable=true</span><br /><span style="font-family: Courier; color: #0000ff;">name="host-10-1-2-21" schedulable=true</span><br /><span style="font-family: Courier; color: #0000ff;">name="host-10-1-2-22" schedulable=true</span><br /><span style="font-family: Courier; color: #0000ff;">name="host-10-1-2-19" schedulable=true</span></div><div><span style="font-family: Courier; color: #0000ff;"><div><span style="font-family: Courier; color: #0000ff;">name="host-10-1-2-18" schedulable=true</span></div></span></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215796.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-07-22 11:14 <a href="http://www.cppblog.com/jinq0123/archive/2018/07/22/215796.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用目录结构表示go包依赖关系</title><link>http://www.cppblog.com/jinq0123/archive/2018/06/25/215743.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Mon, 25 Jun 2018 09:16:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/06/25/215743.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215743.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/06/25/215743.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215743.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215743.html</trackback:ping><description><![CDATA[<div>用目录结构表示go包依赖关系<br /><br />(金庆的专栏 2018.6)<br /><br />摘自：<br />https://www.ardanlabs.com/blog/2017/02/package-oriented-design.html<br /><br />If a package wants to import another package at the same level:<br /><br />* Question the current design choices of these packages.<br />* If reasonable, move the package inside the source tree for the package that wants to import it.<br />* Use the source tree to show the dependency relationships.<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215743.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-06-25 17:16 <a href="http://www.cppblog.com/jinq0123/archive/2018/06/25/215743.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Golang的包名</title><link>http://www.cppblog.com/jinq0123/archive/2018/06/25/215742.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Mon, 25 Jun 2018 08:51:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/06/25/215742.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215742.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/06/25/215742.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215742.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215742.html</trackback:ping><description><![CDATA[<div>Golang的包名<br /><br />(金庆的专栏 2018.6)<br /><br />摘自：<br /><br />https://talks.golang.org/2014/organizeio.slide#1<br /><br />The name of a package<br /><br />Keep package names short and meaningful.<br />Don't use underscores, they make package names long.<br /><br />&nbsp;&nbsp;&nbsp; io/ioutil not io/util<br />&nbsp;&nbsp;&nbsp; suffixarray not suffix_array<br /><br />Don't overgeneralize. A util package could be anything.<br /><br />The name of a package is part of its type and function names.<br />On its own, type Buffer is ambiguous. But users see:<br /><br />&nbsp;&nbsp;&nbsp; buf := new(bytes.Buffer)<br /><br />Choose package names carefully.<br /><br />Choose good names for users.<br /></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215742.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-06-25 16:51 <a href="http://www.cppblog.com/jinq0123/archive/2018/06/25/215742.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>grpc-go与actor模式</title><link>http://www.cppblog.com/jinq0123/archive/2018/06/12/215720.html</link><dc:creator>金庆</dc:creator><author>金庆</author><pubDate>Tue, 12 Jun 2018 03:15:00 GMT</pubDate><guid>http://www.cppblog.com/jinq0123/archive/2018/06/12/215720.html</guid><wfw:comment>http://www.cppblog.com/jinq0123/comments/215720.html</wfw:comment><comments>http://www.cppblog.com/jinq0123/archive/2018/06/12/215720.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jinq0123/comments/commentRss/215720.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jinq0123/services/trackbacks/215720.html</trackback:ping><description><![CDATA[<div>grpc-go与actor模式<br /><br />(金庆的专栏 2018.6)<br /><br />grpc-go服务器的每个请求都在一个独立的协程中执行。<br />网游服务器中，一般请求会调用游戏房间的方法，而房间是一个独立的协程。<br />可以将房间实现为actor，grpc请求通过Call()或Post()方法来执行。<br />其中Call()会等待返回，而Post()会异步执行无返回值。<br /><br /><span style="color: #800000; font-family: Courier;">type Room struct {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// actC 是其他协程向Room协程发送动作的Channel，协程中将依次执行动作。</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// Action 动作, 是无参数无返回值的函数.</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;actC chan func()</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;...</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">// Run 运行房间协程.</span><br /><span style="color: #800000; font-family: Courier;">func (r *Room) Run() {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;ticker := time.NewTicker(20 * time.Millisecond)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;defer ticker.Stop()</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;for r.running {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;select {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;case act := &lt;-r.actC:</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;act()</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;case &lt;-ticker.C:</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;r.tick()</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">// Call calls a function f and returns the result.</span><br /><span style="color: #800000; font-family: Courier;">// f runs in the Room's goroutine.</span><br /><span style="color: #800000; font-family: Courier;">func (r *Room) Call(f func() interface{}) interface{} {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// 结果从ch返回</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;ch := make(chan interface{}, 1)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;r.actC &lt;- func() {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;ch &lt;- f()</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;// 等待直到返回结果</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;return &lt;-ch</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br /><span style="color: #800000; font-family: Courier;">// Post 将一个动作投递到内部协程中执行.</span><br /><span style="color: #800000; font-family: Courier;">func (r *Room) Post(f func()) {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;r.actC &lt;- f</span><br /><span style="color: #800000; font-family: Courier;">}</span><br /><br />grpc服务方法如：<br /><br /><span style="color: #800000; font-family: Courier;">func (m *RoomService) Test(ctx context.Context, req *pb.TestReq) (*pb.TestResp, error) {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;conn := conn_mgr.GetConn(ctx)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;if conn == nil {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return nil, fmt.Errorf("can not find connection")</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;}</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;room := conn.GetRoom()</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;resp := room.Call(func() interface{} {</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return room.Test(req)</span><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;})</span><br /><br /><span style="color: #800000; font-family: Courier;">&nbsp;&nbsp; &nbsp;return resp.(*pb.TestResp), nil</span><br /><span style="color: #800000; font-family: Courier;">}</span></div><img src ="http://www.cppblog.com/jinq0123/aggbug/215720.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jinq0123/" target="_blank">金庆</a> 2018-06-12 11:15 <a href="http://www.cppblog.com/jinq0123/archive/2018/06/12/215720.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>