├── README.md ├── grpc-go └── grpc-go.md ├── kernel └── README.md ├── nsq ├── nsq_to_file.md ├── nsqd.md └── nsqlookupd.md └── rpc └── README.md /README.md: -------------------------------------------------------------------------------- 1 |
失业了,没事干读些源码。分析的有错,欢迎指正,免得让我这个菜b误入歧途。感谢!
2 | 3 | * 1.nsq 4 | 5 | * 2.grpc-go 6 | 7 | === 8 | 9 | #### 使用godep 10 | 11 | [godep源码](https://github.com/tools/godep) 12 | 13 | [godep依赖](https://github.com/golang/go/wiki/GoGetTools) 14 | 15 | export $GOPATH=workspace 16 | cd $GOPATH 17 | go get github.com/tools/godep 18 | sudo atp-get install mercurial 19 | 20 | 21 | #### go使用proto buff 22 | 23 | [下载](https://github.com/google/protobuf/tree/v3.0.0-alpha-3.1) 24 | 25 | $ ./configure 26 | $ make 27 | $ make check 28 | $ make install 29 | $ go get -a github.com/golang/protobuf/protoc-gen-go 30 | 31 | [依赖] 32 | 33 | $ sudo apt-get install autoconf 34 | $ sudo apt-get install libtool 35 | 36 | for example 37 | 38 | $ sudo ldconfig 39 | # or 40 | $ export LD_LIBRARY_PATH=/usr/local/lib 41 | 42 | # from the grpc-common/go dir; invoke protoc 43 | $ protoc -I ../protos ../protos/helloworld.proto --go_out=plugins=grpc:helloworld 44 | 45 | -------------------------------------------------------------------------------- /grpc-go/grpc-go.md: -------------------------------------------------------------------------------- 1 | ## grpc-go 2 | 3 | grpc是HTTP/2 RPC的框架。基于HTTP/2标准 4 | 5 | ### client 6 | 7 | * client的基本行为: 8 | 9 | * 1.建立连接 10 | 11 | * 2.生成client 12 | 13 | ``` 14 | // 调用newHTTP2Client() 15 | conn,err := grpc.Dail(serverAddr, opts) 16 | 17 | // client 为proto buff 的封装。包含conn结构 18 | client := pb.NewXXXClient(conn) 19 | 20 | // 调用某个具体rpc 21 | ... 22 | 23 | ``` 24 | 25 | newHTTP2Client 发送和接受数据.将接受的数据填入每个rpc的stream中 26 | 27 | ``` 28 | // 设置默认的dialer 29 | if opts.Dialer == nil { 30 | opts.Dialer = func(addr string, timeout time.Duration) (net.Conn,error) { 31 | return net.DialTimeout("tcp",addr,timeout) 32 | } 33 | } 34 | conn,connErr := opts.Dialer(addr,timeout) 35 | 36 | // send http2 header for server 37 | n,err := conn.Write(clientPreface) 38 | 39 | // 创建http2 reader,writer 40 | framer := newFramer(conn) 41 | 42 | // 创建http2Client 43 | t := &http2Client{ 44 | conn: conn, 45 | framer: framer, 46 | // client streams起始id为1 47 | nextId: 1, 48 | activeStreams: make(map[uint32]*Stream), 49 | // only one 50 | writableChan: make(chan int,1), 51 | } 52 | 53 | // http2Client控制信息处理 54 | go t.controller() 55 | 56 | // 同时只能有一个writer 进入transport 57 | t.writableChan <- 0 58 | 59 | // 接受in message.将数据copy到stream中 60 | // for { 61 | // frame,err := t.framer.readFrame() 62 | // switch frame := frame.(type) { 63 | // case *.... 64 | // case *http2.DataFrame: 65 | // // s, ok := t.getStream(f) 66 | // // copy(data, f.Data()) 67 | // // s.write(recvMsg{data: data}) 68 | // t.handleData(frame) 69 | // } 70 | // } 71 | go t.reader() 72 | ``` 73 | 74 | ### 总结client功能 75 | 76 | protobuff中定义service xxx。service xxx被proto buff封装为XXXClient{conn *grpc.ClientConn}。grpc.ClientCon被封装为ClientConn{transport transport.ClientTransport}。ClientTransport为Interface{},对应的实际结构为http2Client。这才是grpc中实际存储conn等所有信息的结构。但http2Client中数据的读写却是http2Client.framer。每个rpc call时都会生成stream并且在http2Client.activeStreams中注册。server返回frame信息中有stream_id以此确定具体rpc。frame将数据copy到rpc对应的stream中。 77 | 78 | ``` 79 | type http2Client struct { 80 | target string // server name/addr 81 | conn net.Conn // underlying communication channel 82 | nextID uint32 // the next stream ID to be used 83 | 84 | // writableChan synchronizes write access to the transport. 85 | // A writer acquires the write lock by sending a value on writableChan 86 | // and releases it by receiving from writableChan. 87 | writableChan chan int 88 | // shutdownChan is closed when Close is called. 89 | // Blocking operations should select on shutdownChan to avoid 90 | // blocking forever after Close. 91 | // TODO(zhaoq): Maybe have a channel context? 92 | shutdownChan chan struct{} 93 | // errorChan is closed to notify the I/O error to the caller. 94 | errorChan chan struct{} 95 | 96 | framer *framer 97 | hBuf *bytes.Buffer // the buffer for HPACK encoding 98 | hEnc *hpack.Encoder // HPACK encoder 99 | 100 | // controlBuf delivers all the control related tasks (e.g., window 101 | // updates, reset streams, and various settings) to the controller. 102 | controlBuf *recvBuffer 103 | fc *inFlow 104 | // sendQuotaPool provides flow control to outbound message. 105 | sendQuotaPool *quotaPool 106 | // streamsQuota limits the max number of concurrent streams. 107 | streamsQuota *quotaPool 108 | 109 | // The scheme used: https if TLS is on, http otherwise. 110 | scheme string 111 | 112 | authCreds []credentials.Credentials 113 | 114 | mu sync.Mutex // guard the following variables 115 | state transportState // the state of underlying connection 116 | activeStreams map[uint32]*Stream 117 | // The max number of concurrent streams 118 | maxStreams int 119 | // the per-stream outbound flow control window size set by the peer. 120 | streamSendQuota uint32 121 | } 122 | ``` 123 | ### server 124 | 125 | * 1.创建一个proto定义的,与service相关的server 126 | 127 | * 2.server注册service的所有方法 128 | 129 | * 3.开始监听 130 | 131 | ``` 132 | // type Server struct { 133 | // opts options 134 | // mu sync.Mutex 135 | // lis map[net.Listener]bool 136 | // conns map[transport.ServerTransport]bool 137 | // m map[string]*service // service name -> service info 138 | // } 139 | grpcServer := grpc.NewServer(opts...) 140 | 141 | // 构建service信息存入grpcServer 142 | // for i := range sd.Methods { // rpc 普通 143 | // d := &sd.Methods[i] 144 | // srv.md[d.MethodName] = d 145 | // } 146 | // for i := range sd.Streams { // stream rpc 147 | // d := &sd.Streams[i] 148 | // srv.sd[d.StreamName] = d 149 | // } 150 | // s.m[sd.ServiceName] = srv // service name -> service info 151 | pb.RegisterXXXServer(grpcServer, newServer()) 152 | 153 | // 开始监听 154 | // for { 155 | // c, err := lis.Accept() 156 | // st, err := transport.NewServerTransport("http2", c, s.opts.maxConcurrentStreams) 157 | // s.conns[st] = true // 一个conn,一个transport 158 | // go func() { 159 | // st.HandleStreams(func(stream *transport.Stream) { // create stream && handle && send resp 160 | // s.handleStream(st, stream) 161 | // }) 162 | // delete(s.conns, st) 163 | // }() 164 | // } 165 | grpcServer.Serve(lis) 166 | ``` 167 | 168 | #### func (t *http2Server) HandleStreams(handle func(*Stream)){} 169 | 170 | * 1.为rpc在http2Server创建一个stream并且save 171 | * 2.从client读取REQ message copy至1中创建的stream 172 | * 3.执行下面的handle函数 173 | 174 | #### func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream){} 175 | 176 | * 1.执行rpc handle 177 | * 2.返回resp 178 | 179 | ``` 180 | // Unary RPC or Streaming RPC? 181 | if md, ok := srv.md[method]; ok { 182 | s.processUnaryRPC(t, stream, srv, md) 183 | return 184 | } 185 | if sd, ok := srv.sd[method]; ok { 186 | s.processStreamingRPC(t, stream, srv, sd) 187 | return 188 | } 189 | ``` 190 | #### func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, md *MethodDesc) 191 | 192 | * 1.从stream获取Req 193 | 194 | * 2.执行methodDesc.handle获得resp 195 | 196 | * 3.通过ServerTransport发送resp 197 | 198 | ``` 199 | p := &parser{s: stream} 200 | for { 201 | pf, req, err := p.recvMsg() 202 | if err == io.EOF { 203 | // The entire stream is done (for unary RPC only). 204 | return 205 | } 206 | ppErr := md.Handler(srv.server, stream.Context(), s.opts.codec, req) 207 | s.sendResponse(t, stream, reply, compressionNone, opts) 208 | } 209 | ``` 210 | 211 | #### func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, sd *StreamDesc) 212 | 213 | * 1.将ServerTransport,Stream封装为ServerStream. 214 | 215 | * 2.执行handle 216 | 217 | ``` 218 | s := &serverStream{ 219 | t: t, 220 | s: stream, 221 | p: &parser{s: stream}, 222 | codec: s.opts.codec, 223 | } 224 | // resp 通过 t.Write() 发送 225 | sd.Handler(srv.server, ss) 226 | 227 | ``` 228 | 229 | ### Server总结 230 | 231 | 创建Server,为每个conn创建一个transport并保持在Server中。执行rpc function。将resp 通过transport发送给client 232 | 233 | ### 还有transport close()的问题,这个待老夫在抓磨抓膜 234 | -------------------------------------------------------------------------------- /kernel/README.md: -------------------------------------------------------------------------------- 1 | ### linux内核知识点记录 2 | 3 | ####1.memery barried(内存屏蔽) 4 | 5 | 首先需要了解乱序执行。乱序是指cpu最后执行代码的顺序不是按照程序中代码书写的顺序执行。 6 | 7 | ``` 8 | // 代码书写顺序 9 | int a = 0; 10 | int c = 1; 11 | 12 | // cpu执行顺序可能为 13 | int c = 1; 14 | int a = 0; 15 | ``` 16 | 17 | 乱序执行的原因: 18 | * 1.编译器优化导致代码顺序改变。 19 | * 2.cpu乱序执行代码。(cpu流水线导致指令被分为几个并行阶段) 20 | 21 | 当然这并不是我们就应该担忧乱序的问题。毫无关联代码的乱序执行,并不会影响最后的结果。其次在大多数情况下,存在显性因果关系的代码。编译器和cpu都会处理,不会让这种乱序发生。 22 | 23 | ``` 24 | // 只有a被赋值,b才会被赋值! 25 | // 不会被乱序执行! 26 | int *a = &p; 27 | int *b = &a; 28 | ``` 29 | 30 | 但如果是隐性的因果关系了? 31 | 32 | ``` 33 | // 看起来没有任何关系的两句代码。但如果addr 代表了一个设备的端口 34 | // data代表了写入该设备的数据。可想而知。这两句代码是不能乱序执行的 35 | int *addr = &p; 36 | int *data = &d; 37 | ``` 38 | 39 | 所以才会有内存屏蔽出现,来避免这种情况的发生。代码中出现内存屏蔽。表示之前的代码一定优先于之后的代码执行。但内存屏蔽同侧则顺序不保证 40 | 41 | ``` 42 | // mb()之前的代码,一定先于mb()之后的执行。但tmp和addr的赋值顺序不保证! 43 | int tmp = a; 44 | int *addr = &p; 45 | mb(); 46 | int *data = &d; 47 | ``` 48 | 49 | 内存屏蔽分为读屏蔽,写屏蔽,通用屏蔽,优化屏蔽等。通用屏蔽包括了读写屏蔽。 50 | 51 | 我们再看SMP,对称多处理器,需不需要内存屏蔽!前面已经说了单个cpu需要用内存屏蔽来保证。那么SMP中,如果CPU-1使用了内存屏蔽。那么CPU-2看到CPU-1的内存操作一定是有序的。那么CPU-2是不是就不需要再使用内存屏蔽了?对于大多数系统来说。确实不必要!但某些cpu是需要的。 52 | 53 | ``` 54 | CPU-1 CPU-2 55 | int data = 2; if(ok) { 56 | int ok = false; int a = data; 57 | mb() } 58 | bool ok = false; 59 | ``` 60 | 61 | CPU-1按照顺序发出了写data和写ok的指令。但如果CPU使用了分列cache。导致cache可以并行操作。则虽然CPU-1的指令有序的发出。但实际中CPU-2.因为data缓存比较繁忙,而先更新了ok缓存。所以CPU-2 也需要使用匹配的内存屏蔽. 62 | 63 | ``` 64 | CPU-1 CPU-2 65 | int data = 2; if(ok) { 66 | int ok = false; rmb(); 67 | wmb() int a = data; 68 | bool ok = false; } 69 | ``` 70 | -------------------------------------------------------------------------------- /nsq/nsq_to_file.md: -------------------------------------------------------------------------------- 1 | ## nsq_to_file 2 | 3 | **nsq_to_file 是一个client,将文件写入指定的路径。nsq_to_file 可以指定从nsqlookupd或者nsqd中获得topic,channel网络地址。** 4 | -------------------------------------------------------------------------------- /nsq/nsqd.md: -------------------------------------------------------------------------------- 1 | ## nsqd 2 | 3 | **nsqd扮演的是接收,分发消息角色。nsqd中有三个重要的结构topic,channel,client。nsqd可以比作一台电视,topic是其中某个频道。channel就是位置,占到一个位置就可以看电视节目。几个人同时占一个位置时,只能大家轮流看。所以当有多个client同时sub一个channel时,同一个message只有一个client收到。** 4 | 5 | **topic** 6 | 7 | 每个topic创建时,都会go messagePump(),它负责将client发送的message分发给每个channel。 8 | 9 | **channel** 10 | 11 | 每个channel创建时,都会go messagePump(),它负责接收message,然后push到channel.clientMsgChan。channel.clientMsgChan是channel暴露给client唯一的go-chan。 12 | 13 | channel维护两个队列InFlightQueue和DeferredQueue。client 发布的有timeout的message会被channel push到DeferredQueue等待timeout后处理 14 | 15 | **client** 16 | 17 | nsqd会为主动连接的conn创建client。client从channel.clientMsgChan中得到message。当client接收到消息后,会将message在再次放入到channel的InFlightQueue。以防止由于网络问题,client没有收到message。只有channel收到client发送的FIN时,才会删除message。 18 | 19 | 20 | ===================================================================== 21 | 22 | ### 1. nsqd的main()函数在apps/nsqd/nsqd.go. 23 | 24 | main()函数的主要工作: 25 | 26 | * 创建nsqd。 27 | * 监听端口。 28 | 29 | func main() { 30 | // 设置默认配置 31 | // 从stdin读取配置 32 | ... 33 | 34 | // 设置捕获终端信号 35 | ... 36 | 37 | // resolve ops from config 38 | ... 39 | 40 | // 创建nsqd 41 | nsqd := nsqd.NewNSQD(opts) 42 | 43 | // 看起来好像是读取opts.DataPath路径的文件。实际上就是为nsqd的配置文件创建topics和channels 44 | nsqd.LoadMetadata() 45 | 46 | // 记录topics/channels 47 | // topics 记录topic.Name,topic.IsPaused() 48 | // channel 记录channel.Name,channel.IsPaused() 49 | err := nsqd.PersistMetadata() 50 | 51 | // Main主要做三件事 52 | // 1.监听 tcp/http/https 端口 53 | // 2.为每个连接到nsqd的conn建立一个client 54 | // 3.client负责处理conn传输的命令 55 | nsqd.Main() 56 | 57 | // 阻塞!等待终端信号 58 | <- signalChan 59 | 60 | // 等待nsqd.Main()所有的wg.Done()完成 61 | nsqd.Exit() 62 | } 63 | 64 | ### 2. nsqd.LoadMetadata()在nsqd/nsqd.go. 65 | * 读取dat文件,检查nsqd中是否存在dat中的topic和channel,没有则创建 66 | 67 | func (n *NSQD) LoadMetadata() { 68 | n.setFlag(flagLoading, true) 69 | defer n.setFlag(flagLoading, false) 70 | fn := fmt.Sprintf(path.Join(n.opts.DataPath, "nsqd.%d.dat"), n.opts.ID) 71 | data, err := ioutil.ReadFile(fn) 72 | if err != nil { 73 | return 74 | } 75 | 76 | js, err := simplejson.NewJson(data) 77 | if err != nil { 78 | return 79 | } 80 | 81 | // 从json中获得topics 82 | topics, err := js.Get("topics").Array() 83 | if err != nil { 84 | return 85 | } 86 | 87 | // 如果nsqd中没有该topic则创建 88 | for ti := range topics { 89 | topicJs := js.Get("topics").GetIndex(ti) 90 | 91 | topicName, err := topicJs.Get("name").String() 92 | if err != nil { 93 | return 94 | } 95 | 96 | topic := n.GetTopic(topicName) 97 | paused, _ := topicJs.Get("paused").Bool() 98 | // 我的理解是如果nsqd刚启动的时候,设置了pause=true,那么就会永远阻塞在这里 99 | if paused { 100 | topic.Pause() 101 | } 102 | 103 | channels, err := topicJs.Get("channels").Array() 104 | if err != nil { 105 | return 106 | } 107 | 108 | // 没有该channel,则创建 109 | for ci := range channels { 110 | channelJs := topicJs.Get("channels").GetIndex(ci) 111 | channelName, err := channelJs.Get("name").String() 112 | if err != nil { 113 | return 114 | } 115 | channel := topic.GetChannel(channelName) 116 | 117 | paused, _ = channelJs.Get("paused").Bool() 118 | // nsqd启动时,设置了paused=true,那么就玩完了,因为此时连client都没有! 119 | if paused { 120 | channel.Pause() 121 | } 122 | } 123 | } 124 | } 125 | 126 | nsqd.LoadMetadata()和nsqd.Main()函数中均会涉及到NewTopic()和NewChannel(). 127 | #### (1).NewTopic()和messagePump()在nsqd/topic.go. 128 | * 创建topic对象,开启goroutine处理chans. 129 | * topic.messagePump()将topic的数据分发给channels 130 | 131 | func NewTopic(topicName string, ...) *Topic { 132 | t := &Topic{} 133 | // 处理topic里面的chans 134 | go t.messagePump() 135 | return t 136 | } 137 | 138 | func (t *Topic)messagePump() { 139 | for { 140 | select { 141 | case msg = <- memoryMsgChan: // 获得内存中的message 142 | case buf = <- backendChan: // 获得 backend队列的message 143 | msg,err = decodeMessage(buf) 144 | case <- t.ChannelUpdateChan: // 更新channel 145 | continue 146 | case pause := <- t.PauseChan: // 暂停或者开始 147 | continue 148 | case <- t.exitChan: // 退出 149 | goto exit 150 | } 151 | 152 | // topic 的数据分发给channels 153 | for i,channel := range chans { 154 | chanMsg := msg 155 | // 当channel数大于1时,拷贝message发送给每个channel 156 | if i > 0 { 157 | chanMsg = NewMessage(msg.ID,msg.Body) 158 | } 159 | // channel.memoryMsgChan容量未满,则chanMsg 放入到channel.memoryMsgChan。否则存入c.backend 160 | channel.PutMessage(chanMsg) 161 | } 162 | } 163 | } 164 | 165 | #### (2).NewChannel()和messagePump()在nsqd/channel.go. 166 | * 创建channel对象,开启goroutine处理chans 167 | 168 | func (c *Channel)NewChannel(topicName string,channelName string,...) *Channel{ 169 | c := &Channel{} 170 | go c.messagePump() 171 | } 172 | 173 | func (c *Channel) messagePump() { 174 | for { 175 | select { 176 | case msg = <- c.memoryMsgChan: 177 | case buf = <- c.backend.ReadChan(): 178 | msg,err = decodeMessage(buff) 179 | case <- c.exitChan: // 退出 180 | goto exit 181 | } 182 | 183 | atomic.StoreInt32(&c.bufferedCount, 1) 184 | // c.clientMsgChan会随机分配数据给连接到channel的clients 185 | c.clientMsgChan <- msg 186 | atomic.StoreInt32(&c.bufferedCount, 0) 187 | } 188 | } 189 | 190 | 191 | ### 3. nsqd.Main() 在nsqd/nsqd.go 192 | * 主要功能为监听端口,为每个连接创建client。可以通过tcp,http,https连接到nsqd,这里只分析tcp。 193 | 194 | func (n *NSQD) Main() { 195 | // 监听tcp 196 | tcpListener, err := net.Listen("tcp", n.opts.TCPAddress) 197 | if err != nil { 198 | os.Exit(1) 199 | } 200 | 201 | n.Lock() 202 | n.tcpListener = tcpListener 203 | n.Unlock() 204 | tcpServer := &tcpServer{ctx: ctx} 205 | // goroutine 206 | n.waitGroup.Wrap(func() { 207 | protocol.TCPServer(n.tcpListener, tcpServer, n.opts.Logger) 208 | }) 209 | 210 | // 监听http 211 | ... 212 | 213 | // 监听https 214 | ... 215 | 216 | // nsqd为每个channel建立一个queueWorkerLoop(),专门处理channel InFlightQueue和DeferredQueue 的message 217 | n.waitGroup.Wrap(func() { n.queueScanLoop() }) 218 | } 219 | 220 | #### TCPServer()在internal/protocol/tcp_server.go 221 | * 连接的实际处理函数 222 | 223 | func TCPServer(listener net.Listener, handler TCPHandler, l app.Logger) { 224 | for { 225 | clientConn, err := listener.Accept() 226 | if err != nil { 227 | if nerr, ok := err.(net.Error); ok && nerr.Temporary() { 228 | continue 229 | } 230 | break 231 | } 232 | // handler函数 233 | go handler.Handle(clientConn) 234 | } 235 | } 236 | 237 | ##### Handle()在nsqd/tcp.go 238 | * 检查协议版本,调用IOLoop() 239 | 240 | func (p *tcpServer) Handle(clientConn net.Conn) { 241 | 242 | // 封装又封装。。。 243 | // 循环处理clinetConn数据 244 | err = prot.IOLoop(clientConn) 245 | if err != nil { 246 | return 247 | } 248 | } 249 | 250 | ###### IOLoop()在nsqd/protocol_v2.go 251 | * 解析clientConn协议,并执行协议。 252 | 253 | func (p *protocolV2) IOLoop(conn net.Conn) error { 254 | clientID := atomic.AddInt64(&p.ctx.nsqd.clientIDSequence, 1) 255 | client := newClientV2(clientID, conn, p.ctx) 256 | 257 | messagePumpStartedChan := make(chan bool) 258 | go p.messagePump(client, messagePumpStartedChan) 259 | // 同步 !防止messagePump中client处理chan的函数还没有执行 260 | <-messagePumpStartedChan 261 | 262 | for { 263 | // 读取一行数据,去除回车换行符 264 | line, err = client.Reader.ReadSlice('\n') 265 | if err != nil { 266 | if atomic.LoadInt32(&client.State) == stateClosing { 267 | err = nil 268 | } else { 269 | err = fmt.Errorf("failed to read command - %s", err) 270 | } 271 | break 272 | } 273 | line = line[:len(line)-1] 274 | if len(line) > 0 && line[len(line)-1] == '\r' { 275 | line = line[:len(line)-1] 276 | } 277 | params := bytes.Split(line, separatorBytes) 278 | 279 | // 执行params对于的命令 280 | response, err := p.Exec(client, params) 281 | } 282 | } 283 | 284 | 285 | p.messagePump()在nsqd/protocol_v2.go 286 | * 处理client channels 287 | 288 | func (p *protocolV2) messagePump(client *clientV2, startedChan chan bool) { 289 | var clientMsgChan chan *Message 290 | var subChannel *Channel 291 | var flusherChan <-chan time.Time 292 | 293 | subEventChan := client.SubEventChan 294 | identifyEventChan := client.IdentifyEventChan 295 | 296 | outputBufferTicker := time.NewTicker(client.OutputBufferTimeout) 297 | heartbeatTicker := time.NewTicker(client.HeartbeatInterval) 298 | heartbeatChan := heartbeatTicker.C 299 | msgTimeout := client.MsgTimeout 300 | 301 | flushed := true 302 | 303 | close(startedChan) 304 | 305 | for { 306 | if subChannel == nil || !client.IsReadyForMessages() { // 刷新client之前的数据 307 | clientMsgChan = nil 308 | flusherChan = nil 309 | client.Lock() 310 | err = client.Flush() 311 | client.Unlock() 312 | if err != nil { 313 | goto exit 314 | } 315 | flushed = true 316 | } else if flushed { // 刷新之后,等待client连接的channel的数据。 317 | clientMsgChan = subChannel.clientMsgChan 318 | flusherChan = nil 319 | } else { // 设置一个刷新timer 320 | clientMsgChan = subChannel.clientMsgChan 321 | flusherChan = outputBufferTicker.C 322 | } 323 | 324 | select { 325 | case <-flusherChan: // 通知刷新 326 | client.Lock() 327 | err = client.Flush() 328 | client.Unlock() 329 | if err != nil { 330 | goto exit 331 | } 332 | flushed = true 333 | case <-client.ReadyStateChan: // client 准备好了。 334 | case subChannel = <-subEventChan: // 已经给client 分配了一个channel 335 | // 一个client 只能分配一次 336 | subEventChan = nil 337 | case identifyData := <-identifyEventChan: // 设置identify 338 | // 只能设置一次 339 | identifyEventChan = nil 340 | 341 | outputBufferTicker.Stop() 342 | if identifyData.OutputBufferTimeout > 0 { 343 | outputBufferTicker = time.NewTicker(identifyData.OutputBufferTimeout) 344 | } 345 | 346 | heartbeatTicker.Stop() 347 | heartbeatChan = nil 348 | if identifyData.HeartbeatInterval > 0 { 349 | heartbeatTicker = time.NewTicker(identifyData.HeartbeatInterval) 350 | heartbeatChan = heartbeatTicker.C 351 | } 352 | 353 | if identifyData.SampleRate > 0 { 354 | sampleRate = identifyData.SampleRate 355 | } 356 | 357 | msgTimeout = identifyData.MsgTimeout 358 | case <-heartbeatChan: // 心跳检查 359 | err = p.Send(client, frameTypeResponse, heartbeatBytes) 360 | if err != nil { 361 | goto exit 362 | } 363 | case msg, ok := <-clientMsgChan: // channel的数据 364 | if !ok { 365 | goto exit 366 | } 367 | // 再次保存message,防止client并没有收到message 368 | subChannel.StartInFlightTimeout(msg, client.ID, msgTimeout) 369 | // 计数器 + 1 370 | client.SendingMessage() 371 | err = p.SendMessage(client, msg, &buf) 372 | if err != nil { 373 | goto exit 374 | } 375 | flushed = false 376 | case <-client.ExitChan: 377 | goto exit 378 | } 379 | } 380 | } 381 | 382 | -------------------------------------------------------------------------------- /nsq/nsqlookupd.md: -------------------------------------------------------------------------------- 1 | ## nsqlookupd 2 | 3 | **nsqlookupd记录nsqd的网络拓扑结构。记录nsqd的网络地址,拥有topic,channel信息** 4 | 5 | **nsqd** 6 | 7 | nsqd启动后,会有一个goroutine lookupLoop()函数,将nsqd中topic,channel信息向nsqdlookupd 注册。 8 | 9 | **client** 10 | 11 | client可以向nsqlookupd 请求获得topic,channel信息 12 | -------------------------------------------------------------------------------- /rpc/README.md: -------------------------------------------------------------------------------- 1 | 简单比较thritf,grpc,nsq得出的几点 2 | 3 | * 1.高并发的游戏服务器,使用短链接的rpc,简直蠢爆了!! 4 | 5 | * 2.thrift 有三种服务端模式TSimpleServer,TThreadPoolServer,TNonblockingServer。TSimpleServer就是阻塞的单线程io模式。蠢!后两种都是基于线程池的实现。特别是TNonblockingServer是基于Java NIO模式(单独使用一个线程来处理事件的阻塞,linux上NIO 使用epoll来实现)。所以高并发,长连接还是不适合用thrift。 6 | 7 | * 3.thrift的开发文档太少了。我是没找到有什么好的开发社区. 8 | 9 | * 4.nsq这类消息队列。首先需要额外的协议开发,其次还需要考虑服务器有持久化的需求,因为nsq需要订阅方回覆ok. 10 | 11 | 所以俺还是选择grpc吧。再说一次短链接真的很蠢! 12 | --------------------------------------------------------------------------------