{"id":20917558,"url":"https://github.com/impact-eintr/nats-server","last_synced_at":"2026-04-21T08:02:49.549Z","repository":{"id":107515152,"uuid":"404923043","full_name":"impact-eintr/nats-server","owner":"impact-eintr","description":"nats-serverv1.0.0源码学习","archived":false,"fork":false,"pushed_at":"2021-09-13T13:53:41.000Z","size":47,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-26T20:13:46.369Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/impact-eintr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-10T01:46:03.000Z","updated_at":"2021-10-21T02:04:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"51eae3da-7ae8-4b76-aab2-d0a51cfcc141","html_url":"https://github.com/impact-eintr/nats-server","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/impact-eintr/nats-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fnats-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fnats-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fnats-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fnats-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/impact-eintr","download_url":"https://codeload.github.com/impact-eintr/nats-server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fnats-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32082780,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-21T06:27:27.065Z","status":"ssl_error","status_checked_at":"2026-04-21T06:27:21.250Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-18T16:34:11.921Z","updated_at":"2026-04-21T08:02:49.533Z","avatar_url":"https://github.com/impact-eintr.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nats-server\nnats-serverv1.0.0源码学习\n\n按照其官网的说法，NATS是一个开源的、高性能的、简洁的、灵活的 适用于现代的可靠灵活的云和分布式系统的中枢系统。 说的很玄乎，实际上就是一个分布式 的消息队列系统，支持PubSub/ReqRsp 模型。其最初由Apcera领导开发，并实现了Ruby版本的服务器和客户端，其主要作者Derek Collison自称做了20多年的MQ，并经历过TIBOC、Rendezvous、EMC公司\n根据github里面ruby-nats的日志显示在11年Derek实现了Ruby版本 的NATS服务器以及对应的客户端。然后在12年末，Derek又用Golang将服务器重写了一遍，并最终发现其效果更好，于是现在慢慢将Ruby版本的服务器淘汰了，现在 官网也只维护一个Golang版本的服务器，也就是我们这里的nats\n\nNATS主要由Golang写的服务器“gnatsd”和一系列的客户端SDK组成，客户端有官方维护Golang、Node.js、Ruby、Java、C、C#以及Nginx C版本，除此之外还有社区贡献的Spring、Lua、PHP、Python、Scala、Haskell版本，基本覆盖了主流语言。\n客户端和服务器之间通过一套本文协议进行通讯（想想Redis也是文本协议），因此可以和Redis一样可以通过Telnet进行调试，也因此只要按照文档中的描述，来 实现一套客户端（想想Redis那么多的客户端）。\n具体的协议罗列在官方手册中,主要分成：\n\n|操作命令|由谁发送|描述|\n|:-:|:-:|:-:|\n|INFO|Server|当TCP握手完成后，由服务器发给客户端|\n|CONNECT|Client|由客户端发送给服务器，带上连接的必要信息|\n|PUB|Client|客户端发送一个发布消息给服务器|\n|SUB|Client|客户端向服务器订阅一条消息|\n|UNSUB|Client|客户端向服务器取消之前的订阅|\n|MSG|Server|服务器发送订阅的内容给客户端|\n|PING|Both|PING keep-alive 消息|\n|PONG|Both|PONG keep-alive 响应|\n|+OK|Server|在verbose模式下，确认正确的协议格式|\n|-ERR|Server|表示协议错误，将端口连接|\n\nNATS实现了三种模式\n- Publish Subscribe\n- Request Reply\n- Queueing\n\n也就是MessageQueue常见的“发布订阅模式”、“请求响应模式”以及“消息队列模式”。\n\n这里可以选择在GOPATH里面\"git clone \"+ \"git checkout \"。也可以利用现在的 go mod 在一个自己想放的目录里面进行编译（GO1.11版本既以上）。 比如这里我们要用来分析源码，所以放到一个\"learn_gnatsd_source\"的目录下。然后执行：\n\n``` sh\ngnatsd-1.0.0 cz$ go mod init github.com/nats-io/gnatsd\ngo: creating new go.mod: module github.com/nats-io/gnatsd\ngo: copying requirements from vendor/manifest\n\n```\n\n然后执行编译：\n\n``` sh\ngnatsd-1.0.0 cz$ go build\ngo: finding github.com/nats-io/nuid v1.0.0\ngo: finding golang.org/x/sys v0.0.0-20170627012538-f7928cfef4d0\ngo: finding golang.org/x/crypto v0.0.0-20161031180806-9477e0b78b9a\ngo: downloading golang.org/x/crypto v0.0.0-20161031180806-9477e0b78b9a\ngo: downloading github.com/nats-io/nuid v1.0.0\ngo: extracting github.com/nats-io/nuid v1.0.0\ngo: extracting golang.org/x/crypto v0.0.0-20161031180806-9477e0b78b9a    \n\n```\n\n这里就可以在当前目录下看到编译好的gnatsd文件了。直接运行不用配置文件可以默认监听4222端口。\n\n``` sh\ngnatsd-1.0.0 cz$ ./gnatsd\n[45939] 2019/03/19 15:40:37.908062 [INF] Starting nats-server version 1.0.0\n[45939] 2019/03/19 15:40:37.908385 [INF] Listening for client connections on 0.0.0.0:4222\n[45939] 2019/03/19 15:40:37.908395 [INF] Server is ready\n然后就可以用go的或者其他语言的客户端来进行连接了。\n\n```\n\n\n# 协议\n在逐一学习代码前，我们来看下NATS支持的各种协议以及格式。\n\nNATS的协议是个纯文本协议，因此可以通过使用类似telnet的方式来进行和上面的gnats之间的交互。比如：\n\n``` sh\ngnatsd-1.0.0 cz$ telnet localhost 4222 Trying ::1... Connected to localhost. Escape character is '^]'. INFO {\"server_id\":\"j2f6ynq4T2K5apG7A9hBud\",\"version\":\"1.0.0\",\"go\":\"go1.12\",\"host\":\"0.0.0.0\",\"port\":4222,\"auth_required\":false,\"ssl_required\":false,\"tls_required\":false,\"tls_verify\":false,\"max_payload\":1048576}\n\n```\n\n可以看到，当客户端和服务器一连接的时候，服务器就会发一条INFO协议下来。\n\n从上面也可以看到，NATS的协议大概是JSON格式（数据部分是byte数组)。基本格式为：\n\n``` sh\nCMD \\t payload \\r\\n\n```\n\n这里CMD可能是INFO/CONNECT/PUB/SUB等，\"\\t\"写出来是表示那里有个空格，然后最后以\"\\r\\n\"来结束。所以本质上来说，NATS协议是和HTTP 类似的一种文本协议。\n\n![](https://gblobscdn.gitbook.com/assets%2F-Latpb5Geat10ZDeDKEF%2F-Latsby53R9BRp3x9zMw%2F-LatsevF42pyvn8Rpcwg%2F00_timeline.jpg?alt=media)\n\nNATS和客户端交互的时序大概如图中。\n- 客户端建立到gnats的TCP链接\n- gnats向客户端发送INFO协议\n- 客户端需要向服务器回一个CONNECT协议\n- 然后根据需要，客户端订阅消息，发送SUB协议\n- 其他客户端在建立链接后，发布消息，发送PUB协议\n- 正常情况下，客户端和服务器间通过PING/PONG维护心跳\n\nNATS就通过这样实现了一个订阅发布的系统。\n\n## INFO\n\n## CONNECT\n\n## PUB\n\n## SUB\n\n## UNSUB\n\n## MSG\n\n## PING/PONG\n\n## +OK\n\n## -ERR\n\n\n\n\n# 代码目录结构\n抛开第一级目录的其他文件，现在开始聚焦到server这个目录来：\n\n``` sh\nserver cz$ find . -name \"*\\.go\" |grep -v test | xargs wc -l | sort -d -k 1\n      12 ./pse/pse_solaris.go\n      13 ./pse/pse_rumprun.go\n      16 ./service.go\n      23 ./pse/pse_darwin.go\n      36 ./errors.go\n      50 ./monitor_sort_opts.go\n      55 ./ciphersuites_1.5.go\n      56 ./util.go\n      64 ./ciphersuites_1.8.go\n      72 ./pse/pse_freebsd.go\n      90 ./signal_windows.go\n      93 ./const.go\n      94 ./service_windows.go\n     115 ./pse/pse_linux.go\n     139 ./signal.go\n     163 ./log.go\n     190 ./auth.go\n     268 ./pse/pse_windows.go\n     527 ./monitor.go\n     640 ./sublist.go\n     648 ./reload.go\n     738 ./parser.go\n     762 ./route.go\n     895 ./opts.go\n    1047 ./server.go\n    1410 ./client.go\n    8216 total\n```\n\n抛开test文件，总共只有26个文件，8K代码。所以gnatsd核心还是比较简单的，休闲之余就可以将其代码通读一遍，跟着我们的文章走也很快。\n\n# Server构造\n\n## 创建Server对象\n在server.go里面有:\n\n``` go\n 100 // New will setup a new server struct after parsing the options.\n 101 func New(opts *Options) *Server {\n...\n\n 123     s := \u0026Server{\n 124         configFile: opts.ConfigFile,\n 125         info:       info,\n 126         sl:         NewSublist(),\n 127         opts:       opts,\n 128         done:       make(chan bool, 1),\n 129         start:      now,\n 130         configTime: now,\n 131     }    \n...     \n 157     return s    \n }\n\n```\n\n创建Server的时候，用选项opts和配置文件opts.ConfigFile初始化一个Server对象，Server为：\n\n``` go\n  47 // Server is our main struct.\n  48 type Server struct {\n  49     gcid uint64\n  50     grid uint64\n  51     stats\n  52     mu            sync.Mutex\n  53     info          Info\n  54     infoJSON      []byte\n  55     sl            *Sublist\n  56     configFile    string\n  57     optsMu        sync.RWMutex\n  58     opts          *Options\n  59     running       bool\n  60     shutdown      bool\n  61     listener      net.Listener\n  62     clients       map[uint64]*client\n  63     routes        map[uint64]*client\n  64     remotes       map[string]*client\n  65     users         map[string]*User\n  66     totalClients  uint64\n  67     done          chan bool\n  68     start         time.Time\n  69     http          net.Listener\n  70     httpHandler   http.Handler\n  71     profiler      net.Listener\n  72     httpReqStats  map[string]uint64\n  73     routeListener net.Listener\n  74     routeInfo     Info\n  75     routeInfoJSON []byte\n  76     rcQuit        chan bool\n  77     grMu          sync.Mutex\n  78     grTmpClients  map[uint64]*client\n  79     grRunning     bool\n  80     grWG          sync.WaitGroup // to wait on various go routines\n  81     cproto        int64          // number of clients supporting async INFO\n  82     configTime    time.Time      // last time config was loaded\n  83     logging       struct {\n  84         sync.RWMutex\n  85         logger Logger\n  86         trace  int32\n  87         debug  int32\n  88     }\n  89 }  \n\n```\n\n这里感性认识一下就好了，后面说到具体逻辑的时候回用到响应的成员。\n运行Server\n在main函数中，我们看到是通过：server.Run(s) 来启动Server的，他实际上在 services.go中：\n\n``` go\n\n  6 // Run starts the NATS server. This wrapper function allows Windows to add a\n  7 // hook for running NATS as a service.\n  8 func Run(server *Server) error {\n  9     server.Start()\n 10     return nil\n 11 }\n\n```\n\n## Server.Start()\n先来看代码：\n\n``` go\n 237 func (s *Server) Start() {\n 241     // Avoid RACE between Start() and Shutdown()\n 242     s.mu.Lock()\n 243     s.running = true\n 244     s.mu.Unlock()\n 245\n 246     s.grMu.Lock()\n 247     s.grRunning = true\n 248     s.grMu.Unlock()\n....\n 259\n 260     // Start monitoring if needed\n 261     if err := s.StartMonitoring(); err != nil {\n...\n 265\n 266     // The Routing routine needs to wait for the client listen\n 267     // port to be opened and potential ephemeral port selected.\n 268     clientListenReady := make(chan struct{})\n 270     // Start up routing as well if needed.\n 271     if opts.Cluster.Port != 0 {\n 272         s.startGoRoutine(func() {\n 273             s.StartRouting(clientListenReady)\n 274         })\n 275     }\n 276\n 277     // Pprof http endpoint for the profiler.\n 278     if opts.ProfPort != 0 {\n 279         s.StartProfiler()\n 280     }\n 281\n 282     // Wait for clients.\n 283     s.AcceptLoop(clientListenReady)\n 284 } \n```\n\n \n最开始的地方通过mutex控制，设置服务状态的标记位。\n然后启动Monitor监控以及接受其他服务消息的Router服务，需要的话启动Profile。\n这里看最后一步s.AcceptLoop ,这里想象普通的网络程序，这里开启了一个Loop来接受客户端的TCP链接。\n## AcceptLoop\n\n来看代码：\n\n``` go\n 370 // AcceptLoop is exported for easier testing.\n 371 func (s *Server) AcceptLoop(clr chan struct{}) {\n ...\n\n 430     for s.isRunning() {\n 431         conn, err := l.Accept()\n 432         if err != nil {\n 433             if ne, ok := err.(net.Error); ok \u0026\u0026 ne.Temporary() {\n 434                 s.Debugf(\"Temporary Client Accept Error(%v), sleeping %dms\",\n 435                     ne, tmpDelay/time.Millisecond)\n 436                 time.Sleep(tmpDelay)\n 437                 tmpDelay *= 2\n 438                 if tmpDelay \u003e ACCEPT_MAX_SLEEP {\n 439                     tmpDelay = ACCEPT_MAX_SLEEP\n 440                 }\n 441             } else if s.isRunning() {\n 442                 s.Noticef(\"Accept error: %v\", err)\n 443             }\n 444             continue\n 445         }\n 446         tmpDelay = ACCEPT_MIN_SLEEP\n 447         s.startGoRoutine(func() {\n 448             s.createClient(conn)\n 449             s.grWG.Done()\n 450         })\n 451     }\n 452     s.Noticef(\"Server Exiting..\")\n 453     s.done \u003c- true\n 454 } \n\n```\n\n这里传入的clr，最终当循环退出时，会传递一个消息到channel中，通知启动Server.Start()的调用者，服务结束了。\n而这里的：for s.isRunning() { 形成了真正的AcceptLoop等待客户端过来创建TCP链接。\n每当Accept一条心链接后，开启一个goroutine用这个链接创建一个Client对象。\nReadLoop\n创建client的代码是这样的：\n\n``` go\n 642 func (s *Server) createClient(conn net.Conn) *client {\n...\n\n 646     c := \u0026client{srv: s, nc: conn, opts: defaultOpts, mpay: int64(opts.MaxPayload), start: time.Now()}\n...\n\n 658\n 659     // Initialize\n 660     c.initClient()\n\n 664     // Send our information.\n 665     c.sendInfo(info)\n...\n\n 743     // Spin up the read loop.\n 744     s.startGoRoutine(func() { c.readLoop() }) \n\n...\n}\n\n```\n\n首先创建一个client对象并将链接conn传个client。然后对client进行初始化，并向客户端发送INFO(想想在NATS 开源学习——0X00：协议 中介绍的协议) 。接着开启一个routinue执行client的readLoop。相关代码，等我们分析client的时候再展开，实际上就是从客户端读消息然后处理消息。\n这块逻辑在1.4.x版本中有些不同，在新版本中还开启了一个writeLoop，用来flush缓存中的数据到客户端，这样做就可以对消息进行读写分离，并提高写的 效率。在1.0.0中还是读写在同一个goroutine里。\n## goroutine管理\n看到上面的代码中，启动一个goroutine都是通过s.startGoRoutine整个函数的：\n\n``` go\n 987 func (s *Server) startGoRoutine(f func()) {\n 988     s.grMu.Lock()\n 989     if s.grRunning {\n 990         s.grWG.Add(1)\n 991         go f()\n 992     }\n 993     s.grMu.Unlock()\n 994 }\n\n```\n\n这里显示用一个锁来控制对s.grWG的修改，然后给waitgroup s.grWG做增1操作. 然后在调用时有：\n\n``` go\n 447         s.startGoRoutine(func() {\n 448             s.createClient(conn)\n 449             s.grWG.Done()\n 450         })\n```\n\n也就是client链接断开是做waitgroup的Done操作。\n在Server的ShutDown里面有\n\n``` go\n\n 366     // Wait for go routines to be done.\n 367     s.grWG.Wait()\n```\n\n等待所有链接断开并回收groutine。\n\n## 时序\n现在我们再回过头来看整个时序关系。\n\n就大概了解了：\n- 服务Server先开一个AcceptLoop用来接收客户端TCP链接。\n- 接收到一个客户端的链接后，启动一个ReadLoop来接收客户端发送过来的消息。\n- 整个readloop负责收消息然后处理消息，直到退出。\n- 服务Server通过WaitGroup来管理所有的客户端链接状况。\n\n# Clinet服务\n\n# 协议解析\n\n# 订阅消息\n\n# 消息存储结构\n\n# 发布消息\n\n# Router转发\n\n# 测试代码\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimpact-eintr%2Fnats-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimpact-eintr%2Fnats-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimpact-eintr%2Fnats-server/lists"}