├── doc ├── what-is-etcd-for.md ├── resources │ ├── nsq-nodeid.png │ ├── nsq-redesigned-arch.png │ ├── ordered_topic_create.png │ ├── progress-of-dynamic-isr.png │ ├── 636148072D9879D27B9B8D6DD23FD112.png │ ├── 8612901E4AAC8B3D8E83168EF3B9A64D.png │ ├── 95CD21D7098421C16225C9AD4A71DF7E.png │ ├── 95E00DB10AC70C9E9EDA2B4CCB36E925.jpg │ ├── A3135E75F4A193E9B5C8A0C7DF6463B5.png │ ├── F38B3911792A28534C854AF57882BF1C.png │ ├── nsq-client-overview │ │ └── nsq-client-wf.png │ ├── how-we-redesign-the-nsq-smart-client │ │ ├── lookup-flow.png │ │ ├── order-flow.png │ │ ├── consumer-flow.png │ │ ├── nsq-client-wf.png │ │ ├── producer-flow.png │ │ └── publish-retry.jpeg │ └── building-client-libraries-for-new-nsq │ │ ├── lookup-flow.png │ │ ├── consumer-flow.png │ │ ├── nsq-client-wf.png │ │ └── producer-flow.png ├── NSQ-redesigned-details.pdf ├── README.md └── upgrade-notes.md ├── nsqadmin ├── .gitignore ├── gulp ├── context.go ├── static │ ├── img │ │ ├── favicon.png │ │ └── nsq_blue.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── js │ │ ├── views │ │ │ ├── spinner.hbs │ │ │ ├── topicsFilter.hbs │ │ │ ├── error.hbs │ │ │ ├── warning.hbs │ │ │ ├── node.js │ │ │ ├── topics.js │ │ │ ├── topics.hbs │ │ │ ├── header.js │ │ │ ├── statistics.js │ │ │ ├── statistics.hbs │ │ │ ├── nodes.js │ │ │ ├── nodes.hbs │ │ │ ├── search.js │ │ │ ├── topicsFilter.js │ │ │ ├── header.hbs │ │ │ └── topic.js │ │ ├── lib │ │ │ ├── pubsub.js │ │ │ └── ajax_setup.js │ │ ├── collections │ │ │ ├── ranks.js │ │ │ ├── topics_meta.js │ │ │ ├── nodes.js │ │ │ └── topics.js │ │ ├── main.js │ │ ├── models │ │ │ ├── node.js │ │ │ ├── rank.js │ │ │ ├── topic.js │ │ │ └── channel.js │ │ ├── router.js │ │ └── app_state.js │ └── html │ │ └── index.html ├── logger.go ├── README.md ├── package.json ├── .eslintrc ├── options.go ├── whitelist.go └── notify.go ├── nsqdserver ├── test │ ├── certs │ │ ├── ca.srl │ │ ├── client.req │ │ ├── server.pem │ │ ├── ca.pem │ │ ├── client.pem │ │ ├── cert.pem │ │ ├── client.key │ │ ├── key.pem │ │ ├── server.key │ │ └── ca.key │ ├── openssl.conf │ └── cert.sh ├── fds.go ├── fds_linux.go └── tcp.go ├── fmt.sh ├── bench ├── requirements.txt └── bench_channels │ └── bench_channels.go ├── nsqlookupd ├── context.go ├── README.md ├── client_v1.go ├── logger.go ├── lookup_protocol_v1_test.go ├── tcp.go └── options.go ├── nsqd ├── README.md ├── logger.go ├── dqname.go ├── dqname_windows.go ├── engine │ ├── writebatch.go │ └── pebble_iter.go ├── backend_queue.go ├── in_flight_pqueue_test.go ├── buffer_pool.go └── in_flight_pqueue.go ├── apps ├── nsqd │ ├── README.md │ └── nsqd_test.go ├── nsqlookupd │ └── README.md ├── nsq_pubsub │ └── README.md ├── to_nsq │ └── README.md ├── nsq_to_http │ └── http.go ├── nsq_to_nsq_ordered │ └── nsq_to_nsq_test.go ├── nsq_to_file │ └── strftime.go └── nsqlookupd_migrate_proxy │ └── main.go ├── internal ├── statsd │ ├── host.go │ └── client.go ├── util │ ├── rename.go │ ├── wait_group_wrapper.go │ ├── rand.go │ ├── rename_windows.go │ └── rename_windows_test.go ├── app │ ├── string_array.go │ └── float_array.go ├── dirlock │ ├── dirlock_windows.go │ └── dirlock.go ├── version │ └── binary.go ├── test │ ├── helper.go │ ├── assertions.go │ └── fakes.go ├── protocol │ ├── byte_base10.go │ ├── byte_base10_test.go │ ├── tcp_server.go │ ├── names.go │ ├── protocol.go │ └── errors.go ├── flume_log │ ├── detail_info.go │ ├── flume_sdk.go │ └── flume_client.go ├── stringy │ ├── slice.go │ └── template.go ├── http_api │ ├── http_server.go │ ├── req_params.go │ ├── topic_channel_args.go │ └── compress.go ├── ext │ └── ext.go ├── quantile │ ├── aggregate.go │ └── quantile.go └── auth │ └── authorizations.go ├── Dockerfile ├── nsqlookupd_migrate ├── topics.go ├── context.go ├── topic_migrate_manager.go ├── lookupinfo.go ├── README.md ├── logger.go └── migrate_config.go ├── consistence ├── logger.go ├── struct.go ├── data_placement_mgr_test.go ├── etcd_utils.go ├── nsqd_node_etcd_test.go └── coordgrpc │ └── coord_grpc.proto ├── .travis.yml ├── test.sh ├── pre-dist.sh ├── .gitignore ├── LICENSE ├── contrib ├── nsqadmin.cfg.example ├── nsq.spec └── nsqlookupd.cfg.example ├── .github └── workflows │ └── go.yml ├── jepsen ├── project.clj ├── resources │ └── nsqlookupd.conf └── test │ └── nsq │ └── core_test.clj ├── bench.sh ├── dist.sh ├── CONTRIBUTING.md ├── Makefile ├── CODE_OF_CONDUCT.md ├── go.mod └── Gopkg.toml /doc/what-is-etcd-for.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nsqadmin/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/ca.srl: -------------------------------------------------------------------------------- 1 | 02 2 | -------------------------------------------------------------------------------- /fmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | find . -name "*.go" | xargs goimports -w 3 | -------------------------------------------------------------------------------- /nsqadmin/gulp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec ./node_modules/.bin/gulp $@ 3 | -------------------------------------------------------------------------------- /bench/requirements.txt: -------------------------------------------------------------------------------- 1 | tornado==4.3 2 | paramiko==1.16.0 3 | boto==2.38.0 4 | -------------------------------------------------------------------------------- /nsqdserver/test/openssl.conf: -------------------------------------------------------------------------------- 1 | [ ssl_client ] 2 | extendedKeyUsage = clientAuth -------------------------------------------------------------------------------- /doc/resources/nsq-nodeid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/nsq-nodeid.png -------------------------------------------------------------------------------- /nsqadmin/context.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | type Context struct { 4 | nsqadmin *NSQAdmin 5 | } 6 | -------------------------------------------------------------------------------- /doc/NSQ-redesigned-details.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/NSQ-redesigned-details.pdf -------------------------------------------------------------------------------- /nsqadmin/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/nsqadmin/static/img/favicon.png -------------------------------------------------------------------------------- /nsqadmin/static/img/nsq_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/nsqadmin/static/img/nsq_blue.png -------------------------------------------------------------------------------- /nsqlookupd/context.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | type Context struct { 4 | nsqlookupd *NSQLookupd 5 | } 6 | -------------------------------------------------------------------------------- /doc/resources/nsq-redesigned-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/nsq-redesigned-arch.png -------------------------------------------------------------------------------- /doc/resources/ordered_topic_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/ordered_topic_create.png -------------------------------------------------------------------------------- /doc/resources/progress-of-dynamic-isr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/progress-of-dynamic-isr.png -------------------------------------------------------------------------------- /doc/resources/636148072D9879D27B9B8D6DD23FD112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/636148072D9879D27B9B8D6DD23FD112.png -------------------------------------------------------------------------------- /doc/resources/8612901E4AAC8B3D8E83168EF3B9A64D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/8612901E4AAC8B3D8E83168EF3B9A64D.png -------------------------------------------------------------------------------- /doc/resources/95CD21D7098421C16225C9AD4A71DF7E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/95CD21D7098421C16225C9AD4A71DF7E.png -------------------------------------------------------------------------------- /doc/resources/95E00DB10AC70C9E9EDA2B4CCB36E925.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/95E00DB10AC70C9E9EDA2B4CCB36E925.jpg -------------------------------------------------------------------------------- /doc/resources/A3135E75F4A193E9B5C8A0C7DF6463B5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/A3135E75F4A193E9B5C8A0C7DF6463B5.png -------------------------------------------------------------------------------- /doc/resources/F38B3911792A28534C854AF57882BF1C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/F38B3911792A28534C854AF57882BF1C.png -------------------------------------------------------------------------------- /doc/resources/nsq-client-overview/nsq-client-wf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/nsq-client-overview/nsq-client-wf.png -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/nsqadmin/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/nsqadmin/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/nsqadmin/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /nsqadmin/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/nsqadmin/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /doc/resources/how-we-redesign-the-nsq-smart-client/lookup-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/how-we-redesign-the-nsq-smart-client/lookup-flow.png -------------------------------------------------------------------------------- /doc/resources/how-we-redesign-the-nsq-smart-client/order-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/how-we-redesign-the-nsq-smart-client/order-flow.png -------------------------------------------------------------------------------- /nsqd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqd 2 | 3 | `nsqd` is the daemon that receives, queues, and delivers messages to clients. 4 | 5 | Read the [docs](http://nsq.io/components/nsqd.html) 6 | -------------------------------------------------------------------------------- /doc/resources/building-client-libraries-for-new-nsq/lookup-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/building-client-libraries-for-new-nsq/lookup-flow.png -------------------------------------------------------------------------------- /doc/resources/how-we-redesign-the-nsq-smart-client/consumer-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/how-we-redesign-the-nsq-smart-client/consumer-flow.png -------------------------------------------------------------------------------- /doc/resources/how-we-redesign-the-nsq-smart-client/nsq-client-wf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/how-we-redesign-the-nsq-smart-client/nsq-client-wf.png -------------------------------------------------------------------------------- /doc/resources/how-we-redesign-the-nsq-smart-client/producer-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/how-we-redesign-the-nsq-smart-client/producer-flow.png -------------------------------------------------------------------------------- /apps/nsqd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqd 2 | 3 | `nsqd` is the daemon that receives, queues, and delivers messages to clients. 4 | 5 | Read the [docs](http://nsq.io/components/nsqd.html). 6 | -------------------------------------------------------------------------------- /doc/resources/building-client-libraries-for-new-nsq/consumer-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/building-client-libraries-for-new-nsq/consumer-flow.png -------------------------------------------------------------------------------- /doc/resources/building-client-libraries-for-new-nsq/nsq-client-wf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/building-client-libraries-for-new-nsq/nsq-client-wf.png -------------------------------------------------------------------------------- /doc/resources/building-client-libraries-for-new-nsq/producer-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/building-client-libraries-for-new-nsq/producer-flow.png -------------------------------------------------------------------------------- /doc/resources/how-we-redesign-the-nsq-smart-client/publish-retry.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youzan/nsq/HEAD/doc/resources/how-we-redesign-the-nsq-smart-client/publish-retry.jpeg -------------------------------------------------------------------------------- /nsqadmin/static/js/views/spinner.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /nsqadmin/logger.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | import ( 4 | "github.com/youzan/nsq/internal/levellogger" 5 | ) 6 | 7 | var adminLog = levellogger.NewLevelLogger(levellogger.LOG_INFO, &levellogger.GLogger{}) 8 | -------------------------------------------------------------------------------- /internal/statsd/host.go: -------------------------------------------------------------------------------- 1 | package statsd 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func HostKey(h string) string { 8 | return strings.Replace(strings.Replace(h, ".", "_", -1), ":", "_", -1) 9 | } 10 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topicsFilter.hbs: -------------------------------------------------------------------------------- 1 |
2 | ordered
3 | extend
4 |
-------------------------------------------------------------------------------- /internal/util/rename.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package util 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | func AtomicRename(sourceFile, targetFile string) error { 10 | return os.Rename(sourceFile, targetFile) 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | 3 | ADD dist/docker/bin/ /nsq_bin/ 4 | RUN cd / && ln -s /nsq_bin/* . \ 5 | && cd /bin && ln -s /nsq_bin/* . 6 | 7 | EXPOSE 4150 4151 4160 4161 4170 4171 8 | 9 | VOLUME /data 10 | VOLUME /etc/ssl/certs 11 | -------------------------------------------------------------------------------- /nsqlookupd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqlookupd 2 | 3 | `nsqlookupd` is the daemon that manages topology metadata and serves client requests to 4 | discover the location of topics at runtime. 5 | 6 | Read the [docs](http://nsq.io/components/nsqlookupd.html) 7 | -------------------------------------------------------------------------------- /apps/nsqlookupd/README.md: -------------------------------------------------------------------------------- 1 | ## nsqlookupd 2 | 3 | `nsqlookupd` is the daemon that manages topology metadata and serves client requests to 4 | discover the location of topics at runtime. 5 | 6 | Read the [docs](http://nsq.io/components/nsqlookupd.html). 7 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/error.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{message}} 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/warning.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{message}} 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /nsqdserver/fds.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package nsqdserver 4 | 5 | import ( 6 | "fmt" 7 | "runtime" 8 | ) 9 | 10 | // FDLimit get the open file limit 11 | func FDLimit() (uint64, error) { 12 | return 0, fmt.Errorf("cannot get FDLimit on %s", runtime.GOOS) 13 | } 14 | -------------------------------------------------------------------------------- /internal/util/wait_group_wrapper.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type WaitGroupWrapper struct { 8 | sync.WaitGroup 9 | } 10 | 11 | func (w *WaitGroupWrapper) Wrap(cb func()) { 12 | w.Add(1) 13 | go func() { 14 | defer w.Done() 15 | cb() 16 | }() 17 | } 18 | -------------------------------------------------------------------------------- /nsqadmin/static/js/lib/pubsub.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var Backbone = require('backbone'); 3 | 4 | var Pubsub = _.clone(Backbone.Events); 5 | 6 | // making this global to more easily trigger events from the console 7 | window.Pubsub = Pubsub; 8 | 9 | module.exports = Pubsub; 10 | -------------------------------------------------------------------------------- /nsqlookupd_migrate/topics.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd_migrate 2 | 3 | type Topics_old struct { 4 | StatusCode int `json:"status_code"` 5 | StatusTxt string `json:"status_txt"` 6 | Data *Topics `json:"data"` 7 | } 8 | 9 | type Topics struct { 10 | Topics []string `json:"topics"` 11 | } 12 | 13 | -------------------------------------------------------------------------------- /internal/app/string_array.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type StringArray []string 8 | 9 | func (a *StringArray) Set(s string) error { 10 | *a = append(*a, s) 11 | return nil 12 | } 13 | 14 | func (a *StringArray) String() string { 15 | return strings.Join(*a, ",") 16 | } 17 | -------------------------------------------------------------------------------- /nsqlookupd/client_v1.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type ClientV1 struct { 8 | net.Conn 9 | peerInfo *PeerInfo 10 | } 11 | 12 | func NewClientV1(conn net.Conn) *ClientV1 { 13 | return &ClientV1{ 14 | Conn: conn, 15 | } 16 | } 17 | 18 | func (c *ClientV1) String() string { 19 | return c.RemoteAddr().String() 20 | } 21 | -------------------------------------------------------------------------------- /internal/dirlock/dirlock_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package dirlock 4 | 5 | type DirLock struct { 6 | dir string 7 | } 8 | 9 | func New(dir string) *DirLock { 10 | return &DirLock{ 11 | dir: dir, 12 | } 13 | } 14 | 15 | func (l *DirLock) Lock() error { 16 | return nil 17 | } 18 | 19 | func (l *DirLock) Unlock() error { 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/version/binary.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | const Binary = "0.3.7-HA.1.13.0" 9 | 10 | var ( 11 | Commit = "unset" 12 | BuildTime = "unset" 13 | ) 14 | 15 | func String(app string) string { 16 | return fmt.Sprintf("%s v%s (built w/%s %s %s)", app, Binary, runtime.Version(), Commit, BuildTime) 17 | } 18 | -------------------------------------------------------------------------------- /nsqd/logger.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "github.com/youzan/nsq/internal/levellogger" 5 | ) 6 | 7 | var nsqLog = levellogger.NewLevelLogger(levellogger.LOG_INFO, &levellogger.SimpleLogger{}) 8 | 9 | func SetLogger(log levellogger.Logger) { 10 | nsqLog.Logger = log 11 | } 12 | 13 | func NsqLogger() *levellogger.LevelLogger { 14 | return nsqLog 15 | } 16 | -------------------------------------------------------------------------------- /nsqdserver/fds_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package nsqdserver 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | // FDLimit get the open file limit on linux 10 | func FDLimit() (uint64, error) { 11 | var rlimit syscall.Rlimit 12 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil { 13 | return 0, err 14 | } 15 | return rlimit.Cur, nil 16 | } 17 | -------------------------------------------------------------------------------- /consistence/logger.go: -------------------------------------------------------------------------------- 1 | package consistence 2 | 3 | import ( 4 | "github.com/youzan/nsq/internal/levellogger" 5 | ) 6 | 7 | var coordLog = levellogger.NewLevelLogger(levellogger.LOG_INFO, nil) 8 | 9 | func SetCoordLogger(log levellogger.Logger, level int32) { 10 | coordLog.Logger = log 11 | coordLog.SetLevel(level) 12 | } 13 | 14 | func SetCoordLogLevel(level int32) { 15 | coordLog.SetLevel(level) 16 | } 17 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # 有赞NSQ文档索引 2 | 3 | ## 用户指南 4 | 5 | [有赞NSQ用户指南](NSQ-user-guide.md) 6 | 7 | [有赞NSQ运维指南](nsq-operation-reference.md) 8 | 9 | [JavaSDK使用指南](https://github.com/youzan/nsqJavaSDK/wiki/Get-started) 10 | 11 | ## 内部实现文档 12 | 13 | [重构概览](redesign-overview.md) 14 | 15 | [重构实现细节](The-details-of-the-new-arch.md) 16 | 17 | [客户端SDK实现](how-we-redesign-the-nsq-smart-client.md) 18 | 19 | [扩展消息设计](Message-Extend-Header-Design.md) -------------------------------------------------------------------------------- /internal/util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func UniqRands(l int, n int) []int { 8 | set := make(map[int]struct{}) 9 | nums := make([]int, 0, l) 10 | for { 11 | num := rand.Intn(n) 12 | if _, ok := set[num]; !ok { 13 | set[num] = struct{}{} 14 | nums = append(nums, num) 15 | } 16 | if len(nums) == l { 17 | goto exit 18 | } 19 | } 20 | exit: 21 | return nums 22 | } 23 | -------------------------------------------------------------------------------- /nsqlookupd/logger.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "github.com/youzan/nsq/consistence" 5 | "github.com/youzan/nsq/internal/levellogger" 6 | ) 7 | 8 | var nsqlookupLog = levellogger.NewLevelLogger(1, &levellogger.SimpleLogger{}) 9 | 10 | func SetLogger(logger levellogger.Logger, level int32) { 11 | nsqlookupLog.Logger = logger 12 | nsqlookupLog.SetLevel(level) 13 | consistence.SetCoordLogger(logger, level) 14 | } 15 | -------------------------------------------------------------------------------- /nsqadmin/static/js/lib/ajax_setup.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var _ = require('underscore'); 3 | 4 | // Set up some headers and options for every request. 5 | $.ajaxPrefilter(function(options) { 6 | options['headers'] = _.defaults(options['headers'] || {}, { 7 | 'X-UserAgent': USER_AGENT, 8 | 'Accept': 'application/vnd.nsq; version=1.0' 9 | }); 10 | options['timeout'] = 40 * 1000; 11 | options['contentType'] = 'application/json'; 12 | }); -------------------------------------------------------------------------------- /internal/test/helper.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | type TbLog interface { 4 | Log(...interface{}) 5 | } 6 | 7 | type TestLogger struct { 8 | TbLog 9 | Level int32 10 | } 11 | 12 | func (tl *TestLogger) Output(maxdepth int, s string) error { 13 | tl.Log(s) 14 | return nil 15 | } 16 | 17 | func (tl *TestLogger) OutputErr(maxdepth int, s string) error { 18 | tl.Log(s) 19 | return nil 20 | } 21 | 22 | func (tl *TestLogger) OutputWarning(maxdepth int, s string) error { 23 | tl.Log(s) 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /nsqadmin/static/js/collections/ranks.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var _ = require('underscore'); 3 | var Backbone = require('backbone'); 4 | 5 | var AppState = require('../app_state'); 6 | 7 | var Rank = require('../models/rank'); 8 | 9 | var Ranks = Backbone.Collection.extend({ 10 | model: Rank, 11 | 12 | comparator: 'filter', 13 | 14 | constructor: function Ranks() { 15 | Backbone.Collection.prototype.constructor.apply(this, arguments); 16 | } 17 | }); 18 | 19 | module.exports = Ranks; 20 | -------------------------------------------------------------------------------- /internal/protocol/byte_base10.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var errBase10 = errors.New("failed to convert to Base10") 8 | 9 | func ByteToBase10(b []byte) (n uint64, err error) { 10 | base := uint64(10) 11 | 12 | n = 0 13 | for i := 0; i < len(b); i++ { 14 | var v byte 15 | d := b[i] 16 | switch { 17 | case '0' <= d && d <= '9': 18 | v = d - '0' 19 | default: 20 | n = 0 21 | err = errBase10 22 | return 23 | } 24 | n *= base 25 | n += uint64(v) 26 | } 27 | 28 | return n, err 29 | } 30 | -------------------------------------------------------------------------------- /internal/protocol/byte_base10_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var result uint64 8 | 9 | func BenchmarkByteToBase10Valid(b *testing.B) { 10 | bt := []byte{'3', '1', '4', '1', '5', '9', '2', '5'} 11 | var n uint64 12 | for i := 0; i < b.N; i++ { 13 | n, _ = ByteToBase10(bt) 14 | } 15 | result = n 16 | } 17 | 18 | func BenchmarkByteToBase10Invalid(b *testing.B) { 19 | bt := []byte{'?', '1', '4', '1', '5', '9', '2', '5'} 20 | var n uint64 21 | for i := 0; i < b.N; i++ { 22 | n, _ = ByteToBase10(bt) 23 | } 24 | result = n 25 | } 26 | -------------------------------------------------------------------------------- /internal/flume_log/detail_info.go: -------------------------------------------------------------------------------- 1 | package flume_log 2 | 3 | type DetailInfo struct { 4 | module string 5 | detail map[string]interface{} 6 | } 7 | 8 | type ExtraInfo map[string]interface{} 9 | 10 | func NewDetailInfo(m string) *DetailInfo { 11 | detailInfo := &DetailInfo{ 12 | module: m, 13 | detail: make(map[string]interface{}), 14 | } 15 | return detailInfo 16 | } 17 | 18 | func (d *DetailInfo) SetExtraInfo(extra interface{}) { 19 | d.detail["extra"] = extra 20 | } 21 | 22 | func (d *DetailInfo) AddKeyValue(key string, value interface{}) { 23 | d.detail[key] = value 24 | } 25 | -------------------------------------------------------------------------------- /nsqadmin/static/js/main.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var Backbone = require('backbone'); 3 | 4 | var Pubsub = require('./lib/pubsub'); 5 | var Router = require('./router'); 6 | 7 | var AppView = require('./views/app'); 8 | 9 | // When using browserify, we need to tell Backbone what jQuery to use. 10 | Backbone.$ = $; 11 | 12 | // Side effects: 13 | require('./lib/ajax_setup'); 14 | require('./lib/handlebars_helpers'); 15 | 16 | var start = function() { 17 | new AppView(); 18 | Router.start(); 19 | }; 20 | 21 | // Pubsub.on('all', function() { 22 | // console.log.apply(console, arguments); 23 | // }); 24 | 25 | start(); 26 | -------------------------------------------------------------------------------- /nsqadmin/static/js/collections/topics_meta.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var Backbone = require('backbone'); 3 | 4 | var AppState = require('../app_state'); 5 | 6 | var Topics = require('./topics'); 7 | var Topic = require('../models/topic'); 8 | 9 | var TopicsMeta = Backbone.Collection.extend({ 10 | model: Topic, 11 | 12 | comparator: 'id', 13 | 14 | constructor: function TopicsMeta() { 15 | Backbone.Collection.prototype.constructor.apply(this, arguments); 16 | }, 17 | 18 | url: function() { 19 | return AppState.url('/topics?metaInfo=true'); 20 | }, 21 | 22 | }); 23 | 24 | module.exports = TopicsMeta; -------------------------------------------------------------------------------- /nsqadmin/static/js/collections/nodes.js: -------------------------------------------------------------------------------- 1 | var Backbone = require('backbone'); 2 | 3 | var AppState = require('../app_state'); 4 | 5 | var Node = require('../models/node'); //eslint-disable-line no-undef 6 | 7 | var Nodes = Backbone.Collection.extend({ 8 | model: Node, 9 | 10 | comparator: 'id', 11 | 12 | constructor: function Nodes() { 13 | Backbone.Collection.prototype.constructor.apply(this, arguments); 14 | }, 15 | 16 | url: function() { 17 | return AppState.url('/nodes'); 18 | }, 19 | 20 | parse: function(resp) { 21 | return resp['nodes']; 22 | } 23 | }); 24 | 25 | module.exports = Nodes; 26 | -------------------------------------------------------------------------------- /nsqadmin/static/js/models/node.js: -------------------------------------------------------------------------------- 1 | var AppState = require('../app_state'); 2 | var Backbone = require('backbone'); 3 | 4 | var Node = Backbone.Model.extend({ //eslint-disable-line no-undef 5 | idAttribute: 'name', 6 | 7 | constructor: function Node() { 8 | Backbone.Model.prototype.constructor.apply(this, arguments); 9 | }, 10 | 11 | urlRoot: function() { 12 | return AppState.url('/nodes'); 13 | }, 14 | 15 | tombstoneTopic: function(topic) { 16 | return this.destroy({ 17 | 'data': JSON.stringify({'topic': topic}), 18 | 'dataType': 'text' 19 | }); 20 | } 21 | }); 22 | 23 | module.exports = Node; 24 | -------------------------------------------------------------------------------- /apps/nsq_pubsub/README.md: -------------------------------------------------------------------------------- 1 | nsq_pubsub 2 | ========== 3 | 4 | This provides a HTTP streaming interface to nsq channels. 5 | 6 | 7 | /sub?topic=....&channel=.... 8 | 9 | Each connection will get heartbeats pushed to it in the following format every 30 seconds 10 | 11 | {"_heartbeat_":1343400473} 12 | 13 | There is also a stats endpoint which will list out information about connected clients 14 | 15 | /stats 16 | 17 | It's output looks like this. Total messages is the count of messages delivered to HTTP clients since startup. 18 | 19 | Total Messages: 0 20 | 21 | [127.0.0.1:50386] [test_topic : test_channel] msgs: 0 fin: 0 re-q: 0 connected: 3s 22 | 23 | -------------------------------------------------------------------------------- /nsqlookupd_migrate/context.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd_migrate 2 | 3 | type Context struct { 4 | ProxyHttpAddr string `flag:"http-address" cfg:"http-address"` 5 | ProxyHttpAddrTest string `flag:"http-address-test" cfg:"http-address-test"` 6 | LookupAddrOri string `flag:"origin-lookupd-http" cfg:"origin-lookupd-http"` 7 | LookupAddrTar string `flag:"target-lookupd-http" cfg:"target-lookupd-http"` 8 | Env string `flag:"env" cfg:"env"` 9 | LogLevel int32 `flag:"log-level" cfg:"log-level"` 10 | LogDir string `flag:"log_dir" cfg:"log-dir"` 11 | Migrate_key string `flag:"migrate-key" cfg:"migrate-key"` 12 | Logger *MigrateLogger 13 | } 14 | -------------------------------------------------------------------------------- /nsqadmin/README.md: -------------------------------------------------------------------------------- 1 | ## nsqadmin 2 | 3 | `nsqadmin` is a Web UI to view aggregated cluster stats in realtime and perform various 4 | administrative tasks. 5 | 6 | Read the [docs](http://nsq.io/components/nsqadmin.html) 7 | 8 | ## Working Locally 9 | 10 | using https://github.com/kevinburke/go-bindata for go-bindata 11 | 12 | 1. `$ npm install` 13 | 2. `$ ./gulp clean watch` or `$ ./gulp clean build` 14 | 3. `$ go-bindata --debug --pkg=nsqadmin --prefix=static/build static/build/...` 15 | 4. `$ go build && ./nsqadmin` 16 | 5. make changes (repeat step 5 if you make changes to any Go code) 17 | 6. `$ go-bindata --pkg=nsqadmin --prefix=static/build static/build/...` 18 | 7. commit other changes and `bindata.go` 19 | -------------------------------------------------------------------------------- /internal/dirlock/dirlock.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package dirlock 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | type DirLock struct { 12 | dir string 13 | f *os.File 14 | } 15 | 16 | func New(dir string) *DirLock { 17 | return &DirLock{ 18 | dir: dir, 19 | } 20 | } 21 | 22 | func (l *DirLock) Lock() error { 23 | f, err := os.Open(l.dir) 24 | if err != nil { 25 | return err 26 | } 27 | l.f = f 28 | err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 29 | if err != nil { 30 | return fmt.Errorf("cannot flock directory %s - %s", l.dir, err) 31 | } 32 | return nil 33 | } 34 | 35 | func (l *DirLock) Unlock() error { 36 | defer l.f.Close() 37 | return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN) 38 | } 39 | -------------------------------------------------------------------------------- /apps/to_nsq/README.md: -------------------------------------------------------------------------------- 1 | # to_nsq 2 | 3 | A tool for publishing to an nsq topic data from `stdin`. 4 | 5 | ## Usage 6 | 7 | Publish each line of a file: 8 | 9 | ``` 10 | cat source.txt | to_nsq -topic="topic" -nsqd-tcp-address="127.0.0.1:4150" 11 | ``` 12 | 13 | Publish manually entered lines in a shell: 14 | 15 | ``` 16 | to_nsq -topic="topic" -nsqd-tcp-address="127.0.0.1:4150" 17 | one 18 | two 19 | three 20 | (Ctrl+C to stop) 21 | ``` 22 | 23 | Publish comma separated values from a source file: 24 | 25 | ``` 26 | cat source.txt | to_nsq -delimiter="," -topic="topic" -nsqd-tcp-address="127.0.0.1:4150" 27 | ``` 28 | 29 | Publish three messages, in one go: 30 | 31 | ``` 32 | echo "one,two,three" | to_nsq -delimiter="," -topic="topic" -nsqd-tcp-address="127.0.0.1:4150" 33 | ``` -------------------------------------------------------------------------------- /apps/nsqd/nsqd_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "os" 6 | "testing" 7 | 8 | "github.com/BurntSushi/toml" 9 | "github.com/mreiferson/go-options" 10 | "github.com/youzan/nsq/nsqd" 11 | ) 12 | 13 | func TestConfigFlagParsing(t *testing.T) { 14 | opts := nsqd.NewOptions() 15 | opts.Logger = nil 16 | 17 | flagSet := nsqdFlagSet(opts) 18 | flagSet.Parse([]string{}) 19 | 20 | var cfg config 21 | f, err := os.Open("../../contrib/nsqd.cfg.example") 22 | if err != nil { 23 | t.Fatalf("%s", err) 24 | } 25 | toml.DecodeReader(f, &cfg) 26 | cfg.Validate() 27 | 28 | options.Resolve(opts, flagSet, cfg) 29 | nsqd.New(opts) 30 | 31 | if opts.TLSMinVersion != tls.VersionTLS10 { 32 | t.Errorf("min %#v not expected %#v", opts.TLSMinVersion, tls.VersionTLS10) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/protocol/tcp_server.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | type TCPHandler interface { 11 | Handle(net.Conn) 12 | } 13 | 14 | func TCPServer(listener net.Listener, handler TCPHandler) { 15 | for { 16 | clientConn, err := listener.Accept() 17 | if err != nil { 18 | if nerr, ok := err.(net.Error); ok && nerr.Temporary() { 19 | log.Printf("NOTICE: temporary Accept() failure - %s", err) 20 | runtime.Gosched() 21 | continue 22 | } 23 | // theres no direct way to detect this error because it is not exposed 24 | if !strings.Contains(err.Error(), "use of closed network connection") { 25 | log.Printf("ERROR: listener.Accept() - %s", err) 26 | } 27 | break 28 | } 29 | go handler.Handle(clientConn) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/protocol/names.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var validTopicChannelNameRegex = regexp.MustCompile(`^[\.a-zA-Z0-9_-]+(#ephemeral)?$`) 9 | 10 | // IsValidTopicName checks a topic name for correctness 11 | func IsValidTopicName(name string) bool { 12 | return isValidName(name) 13 | } 14 | 15 | // IsValidChannelName checks a channel name for correctness 16 | func IsValidChannelName(name string) bool { 17 | return isValidName(name) 18 | } 19 | 20 | func isValidName(name string) bool { 21 | if len(name) > 64 || len(name) < 1 { 22 | return false 23 | } 24 | return validTopicChannelNameRegex.MatchString(name) 25 | } 26 | 27 | func IsEphemeral(name string) bool { 28 | if strings.HasSuffix(name, "#ephemeral") { 29 | return true 30 | } 31 | return false 32 | } 33 | -------------------------------------------------------------------------------- /internal/stringy/slice.go: -------------------------------------------------------------------------------- 1 | package stringy 2 | 3 | func Add(s []string, a string) []string { 4 | for _, existing := range s { 5 | if a == existing { 6 | return s 7 | } 8 | } 9 | return append(s, a) 10 | 11 | } 12 | 13 | func Union(s []string, a []string) []string { 14 | for _, entry := range a { 15 | found := false 16 | for _, existing := range s { 17 | if entry == existing { 18 | found = true 19 | break 20 | } 21 | } 22 | if !found { 23 | s = append(s, entry) 24 | } 25 | } 26 | return s 27 | } 28 | 29 | func Uniq(s []string) (r []string) { 30 | for _, entry := range s { 31 | found := false 32 | for _, existing := range r { 33 | if existing == entry { 34 | found = true 35 | break 36 | } 37 | } 38 | if !found { 39 | r = append(r, entry) 40 | } 41 | } 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.13.x 4 | env: 5 | - GOARCH=amd64 TEST_RACE=false 6 | - GOARCH=amd64 TEST_RACE=true 7 | - GOARCH=386 TEST_RACE=false 8 | - GOARCH=386 TEST_RACE=true 9 | sudo: false 10 | script: 11 | - wget -c https://github.com/coreos/etcd/releases/download/v2.3.8/etcd-v2.3.8-linux-amd64.tar.gz 12 | - tar -xvzf etcd-v2.3.8-linux-amd64.tar.gz 13 | - ./etcd-v2.3.8-linux-amd64/etcd -name=test-etcd0 -initial-advertise-peer-urls=http://127.0.0.1:2380 -listen-client-urls=http://127.0.0.1:2379 -advertise-client-urls=http://127.0.0.1:2379 -listen-peer-urls=http://127.0.0.1:2380 -initial-cluster="test-etcd0=http://127.0.0.1:2380" -initial-cluster-state=new --data-dir ./test-etcd > etcd.log 2>&1 & 14 | - travis_wait 25 ./test.sh 15 | notifications: 16 | email: false 17 | 18 | after_success: 19 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /nsqadmin/static/js/views/node.js: -------------------------------------------------------------------------------- 1 | var Pubsub = require('../lib/pubsub'); 2 | var AppState = require('../app_state'); 3 | 4 | var BaseView = require('./base'); 5 | 6 | var NodeView = BaseView.extend({ 7 | className: 'node container-fluid', 8 | 9 | template: require('./spinner.hbs'), 10 | 11 | initialize: function() { 12 | BaseView.prototype.initialize.apply(this, arguments); 13 | this.listenTo(AppState, 'change:graph_interval', this.render); 14 | this.model.fetch() 15 | .done(function(data) { 16 | this.template = require('./node.hbs'); 17 | this.render({'message': data['message']}); 18 | }.bind(this)) 19 | .fail(this.handleViewError.bind(this)) 20 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 21 | } 22 | }); 23 | 24 | module.exports = NodeView; 25 | -------------------------------------------------------------------------------- /internal/app/float_array.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sort" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type FloatArray []float64 12 | 13 | func (a *FloatArray) Set(param string) error { 14 | for _, s := range strings.Split(param, ",") { 15 | v, err := strconv.ParseFloat(s, 64) 16 | if err != nil { 17 | log.Fatalf("Could not parse: %s", s) 18 | return nil 19 | } 20 | *a = append(*a, v) 21 | } 22 | sort.Sort(*a) 23 | return nil 24 | } 25 | 26 | func (a FloatArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 27 | func (a FloatArray) Less(i, j int) bool { return a[i] > a[j] } 28 | func (a FloatArray) Len() int { return len(a) } 29 | 30 | func (a *FloatArray) String() string { 31 | var s []string 32 | for _, v := range *a { 33 | s = append(s, fmt.Sprintf("%f", v)) 34 | } 35 | return strings.Join(s, ",") 36 | } 37 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | echo "" > coverage.txt 4 | 5 | if [ "$TEST_RACE" = "false" ]; then 6 | GOMAXPROCS=1 go test -timeout 1900s `go list ./... | grep -v consistence | grep -v nsqadmin` 7 | else 8 | for d in $(go list ./... | grep -v consistence | grep -v nsqadmin); do 9 | GOMAXPROCS=4 go test -timeout 1900s -race -coverprofile=profile.out -covermode=atomic $d 10 | if [ -f profile.out ]; then 11 | cat profile.out >> coverage.txt 12 | rm profile.out 13 | fi 14 | done 15 | fi 16 | 17 | # no tests, but a build is something 18 | for dir in $(find apps bench -maxdepth 1 -type d) nsqadmin; do 19 | if grep -q '^package main$' $dir/*.go 2>/dev/null; then 20 | echo "building $dir" 21 | go build -o $dir/$(basename $dir) ./$dir 22 | else 23 | echo "(skipped $dir)" 24 | fi 25 | done 26 | -------------------------------------------------------------------------------- /doc/upgrade-notes.md: -------------------------------------------------------------------------------- 1 | # 升级事项 2 | 3 | ## upgrade to 1.5.7+ 4 | 由于1.5.7版本开始引入了新的延时队列, 从1.5.7以下版本升级到1.5.7及更高版本需要参照以下步骤 5 | 6 | 从1.5.7以下版本的集群升级上来, 必须先升级到1.5.8版本, 并启用新的延时队列后, 再升级到1.6之后的版本 7 | 8 | 修改nsqd配置文件的如下两个参数 9 | ``` 10 | ## max allowed delayed req time 11 | max_req_timeout = "24h" 12 | ## req threshold for delayed queue 13 | req_to_end_threshold = "15m" 14 | ``` 15 | 16 | 升级过程中, 部分延迟消费的请求可能会失败, 但是不影响最终消费. 全部升级完毕后, 同时发送API给所有nsqd节点, 启用延时队列新特性. 17 | 18 | ``` 19 | curl -X PUT "http://127.0.0.1:4151/delayqueue/enable?enable=true" 20 | ``` 21 | 22 | 1.6版本开始, 此功能会自动开启, 如果是新集群, 建议直接使用1.6+版本, 无需上述升级操作. 23 | 24 | ## upgrade to 1.12.0+ 25 | 26 | 1.12.0开始队列元数据存储到boltdb, 为了保证兼容性, 1.12.x版本会保留双写元数据到老的文件, 便于升级过程中的回滚操作. 1.12之后的大版本将只保留新的元数据存储方式. 因此如果是从低版本升级上来, 建议先升级到1.12.x版本观察正常后, 再继续升级到更新版本. 27 | 28 | 由于双写会影响部分写入性能, 在确认1.12.x没有问题后, 可以通过API关闭双写元数据, 关闭后将无法降级到老版本. 29 | 30 | ``` 31 | curl -X PUT "http://127.0.0.1:4151/filemeta_writer/enable?state=false" 32 | ``` -------------------------------------------------------------------------------- /nsqadmin/static/js/collections/topics.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var Backbone = require('backbone'); 3 | 4 | var AppState = require('../app_state'); 5 | 6 | var Topic = require('../models/topic'); 7 | 8 | var Topics = Backbone.Collection.extend({ 9 | model: Topic, 10 | 11 | comparator: 'id', 12 | 13 | constructor: function Topics() { 14 | Backbone.Collection.prototype.constructor.apply(this, arguments); 15 | }, 16 | 17 | url: function() { 18 | return AppState.url('/topics'); 19 | }, 20 | 21 | parse: function(resp) { 22 | var topics = _.map(resp['topics'], function(topic) { 23 | return { 24 | 'name': topic['topic_name'], 25 | 'extend_support': topic['extend_support'], 26 | 'ordered': topic['ordered'] 27 | }; 28 | }); 29 | return topics; 30 | } 31 | }); 32 | 33 | module.exports = Topics; 34 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topics.js: -------------------------------------------------------------------------------- 1 | var Pubsub = require('../lib/pubsub'); 2 | var AppState = require('../app_state'); 3 | 4 | var BaseView = require('./base'); 5 | var Topics = require('../collections/topics'); 6 | var TopicsMeta = require('../collections/topics_meta'); 7 | var TopicsView = BaseView.extend({ 8 | className: 'topics container-fluid', 9 | 10 | template: require('./topics.hbs'), 11 | 12 | initialize: function() { 13 | BaseView.prototype.initialize.apply(this, arguments); 14 | this.listenTo(AppState, 'change:graph_interval', this.render); 15 | this.collection = new Topics(); 16 | this.collection.fetch() 17 | .done(function(data) { 18 | this.render({'message': data['message']}); 19 | }.bind(this)) 20 | .fail(this.handleViewError.bind(this)) 21 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 22 | } 23 | }); 24 | 25 | module.exports = TopicsView; -------------------------------------------------------------------------------- /nsqadmin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nsqadmin", 3 | "version": "0.3.0", 4 | "description": "operational dashboard for NSQ (http://nsq.io/)", 5 | "repository": { 6 | "type": "git", 7 | "url": "" 8 | }, 9 | "devDependencies": { 10 | "browserify": "^16.5.2", 11 | "fsevents": "^2.1.2", 12 | "gulp": "^4.0.2", 13 | "gulp-clean": "^0.4.0", 14 | "gulp-notify": "^3.2.0", 15 | "gulp-sass": "^4.0.1", 16 | "gulp-sourcemaps": "^2.6.4", 17 | "gulp-task-listing": "^1.0.0", 18 | "gulp-uglify": "^3.0.1", 19 | "handlebars": "^4.2.0", 20 | "hbsfy": "^2.8.1", 21 | "vinyl-buffer": "^1.0.0", 22 | "vinyl-source-stream": "^2.0.0" 23 | }, 24 | "dependencies": { 25 | "backbone": "^1.4.0", 26 | "bootbox": "^4.4.0", 27 | "bootstrap": "^3.4.1", 28 | "jquery": "^3.4.1", 29 | "moment": "^2.24.0", 30 | "underscore": "^1.8.3" 31 | }, 32 | "browserify": { 33 | "transform": [ 34 | "hbsfy" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pre-dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. commit to bump the version and update the changelog/readme 4 | # 2. tag that commit 5 | # 3. use dist.sh to produce tar.gz for linux and darwin 6 | # 4. upload *.tar.gz to our bitly s3 bucket 7 | # 5. docker push nsqio/nsq 8 | # 6. push to nsqio/master 9 | # 7. update the release metadata on github / upload the binaries there too 10 | # 8. update the gh-pages branch with versions / download links 11 | # 9. update homebrew version 12 | # 10. send release announcement emails 13 | # 11. update IRC channel topic 14 | # 12. tweet 15 | 16 | set -e 17 | 18 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 19 | rm -rf $DIR/dist/docker 20 | mkdir -p $DIR/dist/docker 21 | rm -rf $DIR/.godeps 22 | mkdir -p $DIR/.godeps 23 | dep ensure 24 | 25 | arch=$(go env GOARCH) 26 | version=$(awk '/const Binary/ {print $NF}' < $DIR/internal/version/binary.go | sed 's/"//g') 27 | goversion=$(go version | awk '{print $3}') 28 | 29 | echo "... running tests" 30 | #./test.sh 31 | -------------------------------------------------------------------------------- /consistence/struct.go: -------------------------------------------------------------------------------- 1 | // file: consistence/struct.go 2 | // description: struct of nsq etcd 3 | 4 | // author: reezhou 5 | // email: reechou@gmail.com 6 | // copyright: youzan 7 | 8 | package consistence 9 | 10 | // this is default value 11 | var NSQ_ROOT_DIR = "NSQMetaData" 12 | 13 | const ( 14 | NSQ_TOPIC_DIR = "Topics" 15 | NSQ_TOPIC_META = "TopicMeta" 16 | NSQ_TOPIC_REPLICA_INFO = "ReplicaInfo" 17 | NSQ_TOPIC_LEADER_SESSION = "LeaderSession" 18 | NSQ_NODE_DIR = "NsqdNodes" 19 | NSQ_LOOKUPD_DIR = "NsqlookupdInfo" 20 | NSQ_LOOKUPD_NODE_DIR = "NsqlookupdNodes" 21 | NSQ_LOOKUPD_LEADER_SESSION = "LookupdLeaderSession" 22 | ) 23 | 24 | const ( 25 | ETCD_LOCK_NSQ_NAMESPACE = "nsq" 26 | ) 27 | 28 | type TopicReplicasInfo struct { 29 | Leader string 30 | ISR []string 31 | } 32 | 33 | type TopicCatchupInfo struct { 34 | CatchupList []string 35 | } 36 | 37 | type TopicChannelsInfo struct { 38 | Channels []string 39 | } 40 | -------------------------------------------------------------------------------- /apps/nsq_to_http/http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/youzan/nsq/internal/http_api" 9 | "github.com/youzan/nsq/internal/version" 10 | ) 11 | 12 | var httpclient *http.Client 13 | var userAgent string 14 | 15 | func init() { 16 | httpclient = &http.Client{Transport: http_api.NewDeadlineTransport(*httpTimeout)} 17 | userAgent = fmt.Sprintf("nsq_to_http v%s", version.Binary) 18 | } 19 | 20 | func HTTPGet(endpoint string) (*http.Response, error) { 21 | req, err := http.NewRequest("GET", endpoint, nil) 22 | if err != nil { 23 | return nil, err 24 | } 25 | req.Header.Set("User-Agent", userAgent) 26 | return httpclient.Do(req) 27 | } 28 | 29 | func HTTPPost(endpoint string, body *bytes.Buffer) (*http.Response, error) { 30 | req, err := http.NewRequest("POST", endpoint, body) 31 | if err != nil { 32 | return nil, err 33 | } 34 | req.Header.Set("User-Agent", userAgent) 35 | req.Header.Set("Content-Type", *contentType) 36 | return httpclient.Do(req) 37 | } 38 | -------------------------------------------------------------------------------- /nsqd/dqname.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package nsqd 4 | 5 | func getBackendName(topicName string, part int) string { 6 | backendName := GetTopicFullName(topicName, part) 7 | return backendName 8 | } 9 | 10 | func getBackendReaderName(topicName string, part int, channelName string) string { 11 | // backend names, for uniqueness, automatically include the topic... : 12 | backendName := GetTopicFullName(topicName, part) + ":" + channelName 13 | return backendName 14 | } 15 | 16 | func getDelayQueueBackendName(topicName string, part int) string { 17 | // backend names, for uniqueness, automatically include the topic... : 18 | backendName := GetTopicFullName(topicName, part) + "-[delayed.queue]" 19 | return backendName 20 | } 21 | func getDelayQueueDBName(topicName string, part int) string { 22 | // backend names, for uniqueness, automatically include the topic... : 23 | backendName := GetTopicFullName(topicName, part) + "-[delayed.queue].db" 24 | return backendName 25 | } 26 | -------------------------------------------------------------------------------- /nsqd/dqname_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package nsqd 4 | 5 | // On Windows, file names cannot contain colons. 6 | func getBackendReaderName(topicName string, part int, channelName string) string { 7 | // backend names, for uniqueness, automatically include the topic... ; 8 | backendName := GetTopicFullName(topicName, part) + ";" + channelName 9 | return backendName 10 | } 11 | 12 | func getBackendName(topicName string, part int) string { 13 | backendName := GetTopicFullName(topicName, part) 14 | return backendName 15 | } 16 | 17 | func getDelayQueueBackendName(topicName string, part int) string { 18 | // backend names, for uniqueness, automatically include the topic... 19 | backendName := GetTopicFullName(topicName, part) + ";delayed.queue" 20 | return backendName 21 | } 22 | 23 | func getDelayQueueDBName(topicName string, part int) string { 24 | // backend names, for uniqueness, automatically include the topic... 25 | backendName := GetTopicFullName(topicName, part) + "-[delayed.queue].db" 26 | return backendName 27 | } 28 | -------------------------------------------------------------------------------- /internal/util/rename_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package util 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | var ( 11 | modkernel32 = syscall.NewLazyDLL("kernel32.dll") 12 | procMoveFileExW = modkernel32.NewProc("MoveFileExW") 13 | ) 14 | 15 | const ( 16 | MOVEFILE_REPLACE_EXISTING = 1 17 | ) 18 | 19 | func moveFileEx(sourceFile, targetFile *uint16, flags uint32) error { 20 | ret, _, err := procMoveFileExW.Call(uintptr(unsafe.Pointer(sourceFile)), uintptr(unsafe.Pointer(targetFile)), uintptr(flags)) 21 | if ret == 0 { 22 | if err != nil { 23 | return err 24 | } 25 | return syscall.EINVAL 26 | } 27 | return nil 28 | } 29 | 30 | func AtomicRename(sourceFile, targetFile string) error { 31 | lpReplacedFileName, err := syscall.UTF16PtrFromString(targetFile) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | lpReplacementFileName, err := syscall.UTF16PtrFromString(sourceFile) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return moveFileEx(lpReplacementFileName, lpReplacedFileName, MOVEFILE_REPLACE_EXISTING) 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .godeps 2 | ./build 3 | apps/nsqlookupd/nsqlookupd 4 | apps/nsqd/nsqd 5 | apps/nsqadmin/nsqadmin 6 | bench/bench_reader/bench_reader 7 | bench/bench_writer/bench_writer 8 | bench/bench_channels/bench_channels 9 | bench/multi_bench/multi_bench 10 | apps/nsq_to_nsq/nsq_to_nsq 11 | apps/nsq_to_file/nsq_to_file 12 | apps/nsq_pubsub/nsq_pubsub 13 | apps/nsq_to_http/nsq_to_http 14 | apps/nsq_tail/nsq_tail 15 | apps/nsq_stat/nsq_stat 16 | apps/to_nsq/to_nsq 17 | apps/nsq_data_tool/nsq_data_tool 18 | nsqadmin/static/build 19 | dist 20 | _site 21 | _posts 22 | *.dat 23 | .vscode 24 | 25 | # Go.gitignore 26 | 27 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 28 | *.o 29 | *.a 30 | *.so 31 | 32 | # Folders 33 | _obj 34 | _test 35 | build 36 | .idea 37 | .DS_Store 38 | conf 39 | # Architecture specific extensions/prefixes 40 | *.[568vq] 41 | [568vq].out 42 | 43 | *.cgo1.go 44 | *.cgo2.c 45 | _cgo_defun.c 46 | _cgo_gotypes.go 47 | _cgo_export.* 48 | 49 | _testmain.go 50 | 51 | *.exe 52 | 53 | profile 54 | 55 | # vim stuff 56 | *.sw[op] 57 | /data/ 58 | /logs/ 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/client.req: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICzDCCAbQCAQAwgYYxCzAJBgNVBAYTAkRFMQwwCgYDVQQIDANOUlcxDjAMBgNV 3 | BAcMBUVhcnRoMRcwFQYDVQQKDA5SYW5kb20gQ29tcGFueTELMAkGA1UECwwCSVQx 4 | EzARBgNVBAMMCmNsaWVudC5jb20xHjAcBgkqhkiG9w0BCQEWD3Rlc3RAeW91emFu 5 | LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ1sFkMnxYoxPMJG 6 | 0TOv6LICZr75cmpI15MEzPiLxYoENnCNwycm473GmRLrIQZUpvr4HtNfhm7I+Q19 7 | IuThBuPgUQ4ofpMgDC5ErBeeMvBH6yFYQwAeYc4iPC2QfSlyDxs51kzLTrtLGSzE 8 | 1Xv68cusun+bj3wKyWBGI7rfxxkAsbCbJU5DbLWxObb1U9iSWEl6MgD7v2Z0wO4a 9 | AG9bubzoe2gequroQPsP8Lc4Q085HUzZvwd4Ct9dv3jzqLbKSUNDFqKUX7f9pH6d 10 | /n3e6WK5PYTFSM4rnDl/mBRT0m5X2AxwkqMYwUQY74hZqfCv+9uKoCqmbw0CHYMj 11 | HM3GwwsCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQA2J/l5tYwXX5ZvKUyh751L 12 | s0/dg+irwF4JYPTPFBgLPZew0VfutZ83etnwRv0K5uW5dDHX8xK4uNMGAKS6Kb3h 13 | /e4BuhhOb2ZBqlhrEMh+9GX7BUPovXnWM227g8R+r7vnCU0CAUWBwgO4rqnYqUc9 14 | WbSafaeRiSutwq0oM5mvpzjcnv1pyLoAEohlWpQAUUqSjl2yz4Pt8YSascNmblT2 15 | g9PThEFiNqrJwmNukHo/pem7ezSdAZI7Cc3ZHvawYXoa1ydikiLL/rXfdbVN+L71 16 | CzIHzZ4gksHNQlelYBWczT57hg6pzTfJdXlrHgxjyYksHlHdSuMoGCiap1cTzxGA 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /internal/http_api/http_server.go: -------------------------------------------------------------------------------- 1 | package http_api 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/youzan/nsq/internal/levellogger" 12 | ) 13 | 14 | type logWriter struct { 15 | levellogger.Logger 16 | } 17 | 18 | func (l logWriter) Write(p []byte) (int, error) { 19 | l.Logger.Output(2, string(p)) 20 | return len(p), nil 21 | } 22 | 23 | func Serve(listener net.Listener, handler http.Handler, proto string, l levellogger.Logger) { 24 | l.Output(2, fmt.Sprintf("%s: listening on %s", proto, listener.Addr())) 25 | 26 | server := &http.Server{ 27 | Handler: handler, 28 | ErrorLog: log.New(logWriter{l}, "", 0), 29 | ReadTimeout: 10 * time.Second, 30 | WriteTimeout: 60 * time.Second, 31 | } 32 | err := server.Serve(listener) 33 | // theres no direct way to detect this error because it is not exposed 34 | if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { 35 | l.Output(2, fmt.Sprintf("ERROR: http.Serve() - %s", err)) 36 | } 37 | 38 | l.Output(2, fmt.Sprintf("%s: closing %s", proto, listener.Addr())) 39 | } 40 | -------------------------------------------------------------------------------- /contrib/nsqadmin.cfg.example: -------------------------------------------------------------------------------- 1 | ## : to listen on for HTTP clients 2 | http_address = "0.0.0.0:4171" 3 | 4 | ## graphite HTTP address 5 | graphite_url = "" 6 | 7 | ## proxy HTTP requests to graphite 8 | proxy_graphite = false 9 | 10 | ## prefix used for keys sent to statsd (%s for host replacement, must match nsqd) 11 | statsd_prefix = "nsq.%s" 12 | 13 | ## format of statsd counter stats 14 | statsd_counter_format="%s" 15 | statsd_gauge_format="%s" 16 | 17 | ## time interval nsqd is configured to push to statsd (must match nsqd) 18 | statsd_interval = "60s" 19 | 20 | ## HTTP endpoint (fully qualified) to which POST notifications of admin actions will be sent 21 | notification_http_endpoint = "" 22 | 23 | 24 | ## nsqlookupd HTTP addresses 25 | nsqlookupd_http_addresses = [ 26 | "127.0.0.1:4161" 27 | ] 28 | 29 | ## nsqd HTTP addresses (optional), not used anymore. 30 | #nsqd_http_addresses = [ 31 | # "127.0.0.1:4151" 32 | #] 33 | 34 | log_dir = "/data/logs/nsqadmin" 35 | 36 | #trace_query_url = "" 37 | #trace_app_id = "" 38 | #trace_app_name = "" 39 | #trace_log_index_id = "" 40 | #trace_log_index_name = "" 41 | 42 | -------------------------------------------------------------------------------- /internal/stringy/template.go: -------------------------------------------------------------------------------- 1 | package stringy 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Commafy(i interface{}) string { 8 | var n int64 9 | switch i.(type) { 10 | case int: 11 | n = int64(i.(int)) 12 | case int64: 13 | n = i.(int64) 14 | case int32: 15 | n = int64(i.(int32)) 16 | } 17 | if n > 1000 { 18 | r := n % 1000 19 | n = n / 1000 20 | return fmt.Sprintf("%s,%03d", Commafy(n), r) 21 | } 22 | return fmt.Sprintf("%d", n) 23 | } 24 | 25 | func FloatToPercent(i float64) string { 26 | return fmt.Sprintf("%.0f", i*100.0) 27 | } 28 | 29 | func PercSuffix(i float64) string { 30 | switch int(i*100) % 10 { 31 | case 1: 32 | return "st" 33 | case 2: 34 | return "nd" 35 | case 3: 36 | return "rd" 37 | } 38 | return "th" 39 | } 40 | 41 | func NanoSecondToHuman(v float64) string { 42 | var suffix string 43 | switch { 44 | case v > 1000000000: 45 | v /= 1000000000 46 | suffix = "s" 47 | case v > 1000000: 48 | v /= 1000000 49 | suffix = "ms" 50 | case v > 1000: 51 | v /= 1000 52 | suffix = "us" 53 | default: 54 | suffix = "ns" 55 | } 56 | return fmt.Sprintf("%0.1f%s", v, suffix) 57 | } 58 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topics.hbs: -------------------------------------------------------------------------------- 1 | {{> warning}} 2 | {{> error}} 3 | 4 |
5 |
6 |

Topics

7 |
8 |
9 | 10 |
11 |
12 | {{#if collection.length}} 13 | 14 | 15 | 16 | 17 | {{#each collection}} 18 | 19 | 28 | 29 | {{/each}} 30 |
Topic
20 | {{name}} 21 | {{#if ordered}} 22 | Order 23 | {{/if}} 24 | {{#if extend_support}} 25 | Ext 26 | {{/if}} 27 |
31 | {{else}} 32 |

Notice

No Topics Found
33 | {{/if}} 34 |
35 |
36 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.13.15 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Test 26 | run: | 27 | wget -c https://github.com/coreos/etcd/releases/download/v2.3.8/etcd-v2.3.8-linux-amd64.tar.gz 28 | tar -xvzf etcd-v2.3.8-linux-amd64.tar.gz 29 | ./etcd-v2.3.8-linux-amd64/etcd -name=test-etcd0 -initial-advertise-peer-urls=http://127.0.0.1:2380 -listen-client-urls=http://127.0.0.1:2379 -advertise-client-urls=http://127.0.0.1:2379 -listen-peer-urls=http://127.0.0.1:2380 -initial-cluster="test-etcd0=http://127.0.0.1:2380" -initial-cluster-state=new --data-dir ./test-etcd > etcd.log 2>&1 & 30 | ./test.sh 31 | timeout-minutes: 30 32 | 33 | - name: Codecov 34 | uses: codecov/codecov-action@v1.0.7 35 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/header.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var $ = require('jquery'); 3 | 4 | var AppState = require('../app_state'); 5 | 6 | var BaseView = require('./base'); 7 | 8 | var HeaderView = BaseView.extend({ 9 | className: 'header', 10 | 11 | template: require('./header.hbs'), 12 | 13 | events: { 14 | 'click .dropdown-menu li': 'onGraphIntervalClick' 15 | }, 16 | 17 | initialize: function() { 18 | BaseView.prototype.initialize.apply(this, arguments); 19 | this.listenTo(AppState, 'change:graph_interval', this.render); 20 | }, 21 | 22 | getRenderCtx: function() { 23 | return _.extend(BaseView.prototype.getRenderCtx.apply(this, arguments), { 24 | 'graph_intervals': ['1h', '2h', '12h', '24h', '48h', '168h', 'off'], 25 | 'graph_interval': AppState.get('graph_interval') 26 | }); 27 | }, 28 | 29 | onReset: function() { 30 | this.render(); 31 | this.$('.dropdown-toggle').dropdown(); 32 | }, 33 | 34 | onGraphIntervalClick: function(e) { 35 | e.stopPropagation(); 36 | AppState.set('graph_interval', $(e.target).text()); 37 | } 38 | }); 39 | 40 | module.exports = HeaderView; 41 | -------------------------------------------------------------------------------- /jepsen/project.clj: -------------------------------------------------------------------------------- 1 | (require 'cemerick.pomegranate.aether) 2 | (cemerick.pomegranate.aether/register-wagon-factory! 3 | "http" #(org.apache.maven.wagon.providers.http.HttpWagon.)) 4 | (defproject nsq "0.1.0-SNAPSHOT" 5 | :description "FIXME: write description" 6 | :url "http://example.com/FIXME" 7 | :license {:name "Eclipse Public License" 8 | :url "http://www.eclipse.org/legal/epl-v10.html"} 9 | :repositories [["custom-repo-for-nsq" "http://maven.example-self.com/repositories/"]] 10 | :dependencies [[org.clojure/clojure "1.8.0"] 11 | [org.clojure/core.async "0.2.395"] 12 | [cheshire "5.7.0"] 13 | [jepsen "0.1.4"] 14 | [clj-http "2.3.0"] 15 | [slingshot "0.12.2"] 16 | [com.youzan/NSQ-Client "2.4.1.13-RELEASE"] 17 | [org.apache.httpcomponents/httpclient "4.5.2"] 18 | [org.apache.commons/commons-pool2 "2.4.2"] 19 | [ch.qos.logback/logback-core "1.1.7"] 20 | [ch.qos.logback/logback-classic "1.1.7"] 21 | [org.slf4j/jul-to-slf4j "1.7.21"] 22 | [org.slf4j/jcl-over-slf4j "1.7.21"] 23 | [org.slf4j/slf4j-api "1.7.21"]]) 24 | -------------------------------------------------------------------------------- /nsqlookupd_migrate/topic_migrate_manager.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd_migrate 2 | 3 | import ( 4 | "sync" 5 | "errors" 6 | ) 7 | 8 | var errorMigrateGuardInitErr = errors.New("fail to initialize topic migrate guard") 9 | 10 | type ITopicMigrateGuard interface { 11 | GetTopicSwitch(topic string) int 12 | UpdateTopicSwitches(switches map[string]int) int 13 | Init() 14 | } 15 | 16 | type HttpTopicMigrateGuard struct { 17 | mc *MigrateConfig 18 | lock sync.RWMutex 19 | } 20 | 21 | func (g *HttpTopicMigrateGuard) GetTopicSwitch(topic string) int { 22 | g.lock.RLock() 23 | defer g.lock.RUnlock() 24 | return g.mc.Switches[topic] 25 | } 26 | 27 | func (g *HttpTopicMigrateGuard) UpdateTopicSwitches(switches map[string]int) int { 28 | g.lock.Lock() 29 | defer g.lock.Unlock() 30 | var cnt int 31 | for topic, sw := range switches { 32 | g.mc.Switches[topic] = sw 33 | cnt++ 34 | } 35 | return cnt 36 | } 37 | 38 | func (g *HttpTopicMigrateGuard) Init() { 39 | } 40 | 41 | func NewTopicMigrateGuard(context *Context) (ITopicMigrateGuard, error) { 42 | mc, err := NewMigrateConfig(context) 43 | if err != nil { 44 | return nil, errorMigrateGuardInitErr 45 | } 46 | 47 | guard := &HttpTopicMigrateGuard{ 48 | mc: mc, 49 | } 50 | 51 | return guard, nil 52 | } -------------------------------------------------------------------------------- /consistence/data_placement_mgr_test.go: -------------------------------------------------------------------------------- 1 | package consistence 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestChooseNewLeaderByLeastLoadFactor(t *testing.T) { 10 | dpm := &DataPlacement{} 11 | testCases := []struct { 12 | loadFactors map[string]float64 13 | newLeader []string 14 | }{ 15 | { 16 | loadFactors: map[string]float64{"a": 1}, 17 | newLeader: []string{"a"}, 18 | }, 19 | { 20 | loadFactors: map[string]float64{"a": 96, "b": 35}, 21 | newLeader: []string{"b"}, 22 | }, 23 | { 24 | loadFactors: map[string]float64{"a": 35, "b": 35}, 25 | newLeader: []string{"a", "b"}, 26 | }, 27 | { 28 | loadFactors: map[string]float64{"a": 98, "b": 56, "c": 63}, 29 | newLeader: []string{"b"}, 30 | }, 31 | { 32 | loadFactors: map[string]float64{"a": 98, "b": 56, "c": 56}, 33 | newLeader: []string{"b", "c"}, 34 | }, 35 | { 36 | loadFactors: map[string]float64{"a": 56, "b": 56, "c": 56}, 37 | newLeader: []string{"a", "b", "c"}, 38 | }, 39 | } 40 | for _, tCase := range testCases { 41 | newLeader := dpm.chooseNewLeaderByLeastLoadFactor(tCase.loadFactors) 42 | assert.Containsf(t, tCase.newLeader, newLeader, "not the right leader %s", newLeader) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/statistics.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var _ = require('underscore'); 3 | var Pubsub = require('../lib/pubsub'); 4 | var AppState = require('../app_state'); 5 | var BaseView = require('./base'); 6 | var Rank = require('../models/rank'); 7 | var Ranks = require('../collections/ranks'); 8 | 9 | var StatisticsView = BaseView.extend({ 10 | className: 'statistics container-fluid', 11 | 12 | template: require('./statistics.hbs'), 13 | 14 | initialize: function() { 15 | BaseView.prototype.initialize.apply(this, arguments); 16 | this.listenTo(AppState, 'change:graph_interval', this.render); 17 | var filters; 18 | this.collection = new Ranks(); 19 | this.listenTo(this.collection, 'update', this.render); 20 | var viewCollection = this.collection; 21 | $.get(AppState.url("/statistics"), function(resp){ 22 | filters = _.map(resp['filters'], function(name) { 23 | rank = new Rank({filter: name}); 24 | rank.fetch() 25 | .done(function(data) { 26 | viewCollection.add(data); 27 | }); 28 | return name; 29 | }); 30 | }); 31 | } 32 | }); 33 | 34 | module.exports = StatisticsView; 35 | -------------------------------------------------------------------------------- /apps/nsq_to_nsq_ordered/nsq_to_nsq_test.go: -------------------------------------------------------------------------------- 1 | // This is an NSQ client that reads the specified topic/channel 2 | // and re-publishes the messages to destination nsqd via TCP 3 | 4 | package main 5 | 6 | import ( 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func Test_getMsgKeyFromBody(t *testing.T) { 12 | type args struct { 13 | body []byte 14 | jsonKey string 15 | } 16 | tests := []struct { 17 | name string 18 | args args 19 | want []byte 20 | wantErr bool 21 | }{ 22 | // TODO: Add test cases. 23 | {"test1", args{[]byte("{\"key1\":\"test1\"}"), "key1"}, nil, true}, 24 | {"test2", args{[]byte("{\"key1\":{\"key2\":\"test12\"}}"), "key1"}, []byte("test12"), false}, 25 | {"test3", args{[]byte("{\"key11\":{\"key3\":\"test12\"}}"), "key11"}, nil, true}, 26 | {"test4", args{[]byte("{\"key11\":{\"key2\":12}}"), "key11"}, []byte("12"), false}, 27 | } 28 | for _, tt := range tests { 29 | t.Run(tt.name, func(t *testing.T) { 30 | got, err := getMsgKeyFromBody(tt.args.body, tt.args.jsonKey) 31 | if (err != nil) != tt.wantErr { 32 | t.Errorf("getMsgKeyFromBody() error = %v, wantErr %v", err, tt.wantErr) 33 | return 34 | } 35 | if !reflect.DeepEqual(got, tt.want) { 36 | t.Errorf("getMsgKeyFromBody() = %v, want %v", got, tt.want) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/statistics.hbs: -------------------------------------------------------------------------------- 1 | {{> warning}} 2 | {{> error}} 3 | 4 |
5 |
6 | {{#if collection.length}} 7 | {{#each collection}} 8 |
9 |
10 |

{{rank_name}}

11 |
12 |
13 | 14 | {{#if top10.length}} 15 | 16 | 17 | 18 | 19 | 20 | {{#each top10}} 21 | 22 | 23 | 24 | 25 | {{/each}} 26 |
Name#
{{name}}{{rank_value}}
27 | {{else}} 28 |

Notice

No Topics Found
29 | {{/if}} 30 | {{/each}} 31 | {{else}} 32 |
Loading ranks...
33 | {{/if}} 34 |
35 |
-------------------------------------------------------------------------------- /internal/http_api/req_params.go: -------------------------------------------------------------------------------- 1 | package http_api 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | type ReqParams struct { 11 | url.Values 12 | Body []byte 13 | } 14 | 15 | func NewReqParams(req *http.Request) (*ReqParams, error) { 16 | reqParams, err := url.ParseQuery(req.URL.RawQuery) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | data, err := ioutil.ReadAll(req.Body) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &ReqParams{reqParams, data}, nil 27 | } 28 | 29 | func (r *ReqParams) Get(key string) (string, error) { 30 | v, ok := r.Values[key] 31 | if !ok || len(v) == 0 { 32 | return "", errors.New("key not in query params") 33 | } 34 | return v[0], nil 35 | } 36 | 37 | func (r *ReqParams) GetAll(key string) ([]string, error) { 38 | v, ok := r.Values[key] 39 | if !ok { 40 | return nil, errors.New("key not in query params") 41 | } 42 | return v, nil 43 | } 44 | 45 | type PostParams struct { 46 | *http.Request 47 | } 48 | 49 | func (p *PostParams) Get(key string) (string, error) { 50 | if p.Request.Form == nil { 51 | p.Request.ParseMultipartForm(1 << 20) 52 | } 53 | if vs, ok := p.Request.Form[key]; ok && len(vs) > 0 { 54 | return vs[0], nil 55 | } 56 | return "", errors.New("key not in post params") 57 | } 58 | -------------------------------------------------------------------------------- /internal/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | ) 8 | 9 | // Protocol describes the basic behavior of any protocol in the system 10 | type Protocol interface { 11 | IOLoop(conn net.Conn) error 12 | } 13 | 14 | // SendResponse is a server side utility function to prefix data with a length header 15 | // and write to the supplied Writer 16 | func SendResponse(w io.Writer, data []byte) (int, error) { 17 | err := binary.Write(w, binary.BigEndian, int32(len(data))) 18 | if err != nil { 19 | return 0, err 20 | } 21 | 22 | n, err := w.Write(data) 23 | if err != nil { 24 | return 0, err 25 | } 26 | 27 | return (n + 4), nil 28 | } 29 | 30 | // SendFramedResponse is a server side utility function to prefix data with a length header 31 | // and frame header and write to the supplied Writer 32 | func SendFramedResponse(w io.Writer, frameType int32, data []byte) (int, error) { 33 | beBuf := make([]byte, 4) 34 | size := uint32(len(data)) + 4 35 | 36 | binary.BigEndian.PutUint32(beBuf, size) 37 | n, err := w.Write(beBuf) 38 | if err != nil { 39 | return n, err 40 | } 41 | 42 | binary.BigEndian.PutUint32(beBuf, uint32(frameType)) 43 | n, err = w.Write(beBuf) 44 | if err != nil { 45 | return n + 4, err 46 | } 47 | 48 | n, err = w.Write(data) 49 | return n + 8, err 50 | } 51 | -------------------------------------------------------------------------------- /nsqlookupd_migrate/lookupinfo.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd_migrate 2 | 3 | 4 | type Lookupinfo_old struct { 5 | Status_code int `json:"status_code"` 6 | Status_txt string `json:"status_txt"` 7 | Data *Data `json:"data,omitempty"` 8 | } 9 | 10 | type Data struct { 11 | Channels []string `json:"channels"` 12 | Producers []*Producerinfo `json:"producers"` 13 | Partitions map[string]*Producerinfo `json:"partitions,omitempty"` 14 | Meta *Metainfo `json:"meta,omitempty"` 15 | } 16 | 17 | type Producerinfo struct { 18 | Remote_address string `json:"remote_address"` 19 | Hostname string `json:"hostname"` 20 | Broadcast_address string `json:"broadcast_address"` 21 | Tcp_port int `json:"tcp_port"` 22 | Http_port int `json:"http_port"` 23 | Version string `json:"version"` 24 | } 25 | 26 | type Metainfo struct { 27 | Partition_num int `json:"partition_num"` 28 | Replica int `json:"replica"` 29 | ExtSupport bool `json:"extend_support"` 30 | } 31 | 32 | type Lookupinfo struct { 33 | Channels []string `json:"channels,omitempty"` 34 | Producers []*Producerinfo `json:"producers,omitempty"` 35 | Partitions map[string]*Producerinfo `json:"partitions,omitempty"` 36 | Meta *Metainfo `json:"meta,omitempty"` 37 | Message string `json:"message,omitempty"` 38 | } 39 | 40 | const ( 41 | M_OFF = iota 42 | M_CSR 43 | M_CSR_PDR 44 | M_FIN 45 | ) 46 | 47 | -------------------------------------------------------------------------------- /nsqd/engine/writebatch.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/cockroachdb/pebble" 7 | ) 8 | 9 | type WriteBatch interface { 10 | Destroy() 11 | Clear() 12 | DeleteRange(start, end []byte) 13 | Delete(key []byte) 14 | Put(key []byte, value []byte) 15 | Commit() error 16 | } 17 | 18 | type pebbleWriteBatch struct { 19 | wb *pebble.Batch 20 | wo *pebble.WriteOptions 21 | db *pebble.DB 22 | } 23 | 24 | func newPebbleWriteBatch(db *pebble.DB, wo *pebble.WriteOptions) *pebbleWriteBatch { 25 | return &pebbleWriteBatch{ 26 | wb: db.NewBatch(), 27 | wo: wo, 28 | db: db, 29 | } 30 | } 31 | 32 | func (wb *pebbleWriteBatch) Destroy() { 33 | wb.wb.Close() 34 | } 35 | 36 | func (wb *pebbleWriteBatch) Clear() { 37 | wb.wb.Close() 38 | wb.wb = wb.db.NewBatch() 39 | // TODO: reuse it 40 | //wb.wb.Reset() 41 | } 42 | 43 | func (wb *pebbleWriteBatch) DeleteRange(start, end []byte) { 44 | wb.wb.DeleteRange(start, end, wb.wo) 45 | } 46 | 47 | func (wb *pebbleWriteBatch) Delete(key []byte) { 48 | wb.wb.Delete(key, wb.wo) 49 | } 50 | 51 | func (wb *pebbleWriteBatch) Put(key []byte, value []byte) { 52 | wb.wb.Set(key, value, wb.wo) 53 | } 54 | 55 | func (wb *pebbleWriteBatch) Commit() error { 56 | if wb.db == nil || wb.wo == nil { 57 | return errors.New("nil db or options") 58 | } 59 | return wb.db.Apply(wb.wb, wb.wo) 60 | } 61 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkjCCAnoCCQDHFRdNXjGywzANBgkqhkiG9w0BAQsFADCBijELMAkGA1UEBhMC 3 | REUxDDAKBgNVBAgMA05SVzEOMAwGA1UEBwwFRWFydGgxFzAVBgNVBAoMDlJhbmRv 4 | bSBDb21wYW55MQswCQYDVQQLDAJJVDEXMBUGA1UEAwwOd3d3LnJhbmRvbS5jb20x 5 | HjAcBgkqhkiG9w0BCQEWD3Rlc3RAeW91emFuLmNvbTAeFw0yMjA2MjkxMDA3Mjla 6 | Fw0zMjA2MjYxMDA3MjlaMIGKMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4w 7 | DAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsM 8 | AklUMRcwFQYDVQQDDA53d3cucmFuZG9tLmNvbTEeMBwGCSqGSIb3DQEJARYPdGVz 9 | dEB5b3V6YW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy9o7 10 | anuJeb/YmCSWIqV/0CUVf1Srpc51VBWVtbQR6n4GmfDX/90p9Z6t7Q6w53x2tX1b 11 | r8v1cRNWICa/SuwplhB8S6Q2vFj+EaBA+R+yzPE2ifvvT4d/oZvHd/q9dZCtQ3Wk 12 | ZhAlIOzwnpjM1ilcc8OqrA6trmYWkG7vcYcCYSIOFJXbnfdEXIBQSUu9AVjHOQ7y 13 | StNZ1svA+t4J290pUpnkg0wje4wtaIuAKPeFTyQsZTY/96pfmkR5zqbw2pfSDQkO 14 | EwfF3h96SFuaQaYN/c7n8p2QyW/Xhv9VkmpnenZlrcKJELRSTgekrW0ZMQAJHP8l 15 | TWGQALNNR/BeFIqQaQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQC0H7SiHclD/2Vj 16 | bdtarg+2GJO3RoxqSBG0SypdbgwMdAbxMOTCfwE1VZmROO6x2XcSA2THPQKTkfcX 17 | TUeh0qP8wvYuS/NJ2mBQYrenX8iAGqHNFr8DD03KHii4aBQetuZl3IpB0nED1yQd 18 | 1yVmMlL7V75Z2h557awH/dpwCMpF6A7Bwbh8ZoNOsvw0TaRuhCytDfvumaVScVck 19 | bY36zocoLSM0z+7WQiB4Tv/SJ2pgFFbIV9ZMslChOsERFzs41XI390sFLvgxLYuE 20 | y3BUqEY/rpO8STeFK9VSu+MSSnpob6Re4SSnQIILa6GLljQDt3UGBjcjy/aOy3RC 21 | gy4uvHDd 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDoDCCAogCCQCqna7bGNTlfjANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UEBhMC 3 | REUxDDAKBgNVBAgMA05SVzEOMAwGA1UEBwwFRWFydGgxFzAVBgNVBAoMDlJhbmRv 4 | bSBDb21wYW55MQswCQYDVQQLDAJJVDEXMBUGA1UEAwwOd3d3LnJhbmRvbS5jb20x 5 | JTAjBgkqhkiG9w0BCQEWFktyeXB0b0tpbmdzQHJhbmRvbS5jb20wHhcNMjIwNjI5 6 | MTAwNzI5WhcNMjMwNjI5MTAwNzI5WjCBkTELMAkGA1UEBhMCREUxDDAKBgNVBAgM 7 | A05SVzEOMAwGA1UEBwwFRWFydGgxFzAVBgNVBAoMDlJhbmRvbSBDb21wYW55MQsw 8 | CQYDVQQLDAJJVDEXMBUGA1UEAwwOd3d3LnJhbmRvbS5jb20xJTAjBgkqhkiG9w0B 9 | CQEWFktyeXB0b0tpbmdzQHJhbmRvbS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB 10 | DwAwggEKAoIBAQDM7VvNAjYZIj6kF3RohmPs6CWkULm3Gah4rg5uVUI7uF2IvzXb 11 | xxRBJkRGk7EnDxBMstegSvA7DD4X+Q8YCn+jo26zKHKq2NmwNAAZbwEYoY0Iq/2r 12 | IsDLkI6+aHDg9SsPOr78cfY98kNK8IIDsYEE+8KgF7bLGbA7NpP7uguhGwnQWKaJ 13 | 4WunACUgCieALNK3vzwboFcCzZ7CAT9Ixnrg52p9GeCFyA1MePX82D24Y6ikpaoZ 14 | MrQfXG7pK+6WuRBQgND3AV+UbSTsifV0ekSCMp8DJrqGP7tMuCySmYsSIe8StcWA 15 | CTT/Xzqlef5OSMg8fm0WqhxCW4v+b7DzZe0/AgMBAAEwDQYJKoZIhvcNAQELBQAD 16 | ggEBAJ9AAP6YhImKNhTsthDB5Y8DRUmPmKz+EcN6Y2Xx5S2EHG7TgiBLD7q2Z5n+ 17 | xtYIWN4T5MrVVKWXjqydBBGVYiGpssNlsBDhyWd9HequXl6Tsm7z+nxwcBoLOZgH 18 | cEHKXR7nEoM87yOVf1U+Xd9CoL65kIYfaeiF8Ae1FHUfn4yotF/a5vF3pseGsmn7 19 | p8XU2ZZAE249Mi85Q3rK00itAPOfnMI5lqhcV++h6zGTV2vlBgn1vdf//ne0WgUb 20 | qso9P2cfZrFVhadxoTBhFEm/FRgcW1q5ZmFrkVbKHLV2K+J0tRBKUucoDAth7GWG 21 | mfM9zwHjFg7hqBSulEzR+muGd5w= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /contrib/nsq.spec: -------------------------------------------------------------------------------- 1 | %define name nsq 2 | %define version 0.3.7 3 | %define release 1 4 | %define path usr/local 5 | %define group Database/Applications 6 | %define __os_install_post %{nil} 7 | 8 | Summary: nsq 9 | Name: %{name} 10 | Version: %{version} 11 | Release: %{release} 12 | Group: %{group} 13 | Packager: Matt Reiferson 14 | License: Apache 15 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release} 16 | AutoReqProv: no 17 | # we just assume you have go installed. You may or may not have an RPM to depend on. 18 | # BuildRequires: go 19 | 20 | %description 21 | NSQ - A realtime distributed messaging platform 22 | https://github.com/youzan/nsq 23 | 24 | %prep 25 | mkdir -p $RPM_BUILD_DIR/%{name}-%{version}-%{release} 26 | cd $RPM_BUILD_DIR/%{name}-%{version}-%{release} 27 | git clone git@github.com:youzan/nsq.git 28 | 29 | %build 30 | cd $RPM_BUILD_DIR/%{name}-%{version}-%{release}/nsq 31 | make PREFIX=/%{path} 32 | 33 | %install 34 | export DONT_STRIP=1 35 | rm -rf $RPM_BUILD_ROOT 36 | cd $RPM_BUILD_DIR/%{name}-%{version}-%{release}/nsq 37 | make PREFIX=/${path} DESTDIR=$RPM_BUILD_ROOT install 38 | 39 | %files 40 | /%{path}/bin/nsqadmin 41 | /%{path}/bin/nsqd 42 | /%{path}/bin/nsqlookupd 43 | /%{path}/bin/nsq_pubsub 44 | /%{path}/bin/nsq_to_file 45 | /%{path}/bin/nsq_to_http 46 | /%{path}/bin/nsq_to_nsq 47 | /%{path}/bin/nsq_tail 48 | /%{path}/bin/nsq_stat 49 | /%{path}/bin/to_nsq 50 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDqzCCApOgAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBkTELMAkGA1UEBhMCREUx 3 | DDAKBgNVBAgMA05SVzEOMAwGA1UEBwwFRWFydGgxFzAVBgNVBAoMDlJhbmRvbSBD 4 | b21wYW55MQswCQYDVQQLDAJJVDEXMBUGA1UEAwwOd3d3LnJhbmRvbS5jb20xJTAj 5 | BgkqhkiG9w0BCQEWFktyeXB0b0tpbmdzQHJhbmRvbS5jb20wHhcNMjIwNjI5MTAw 6 | NzMwWhcNMjMwNjI5MTAwNzMwWjCBhjELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05S 7 | VzEOMAwGA1UEBwwFRWFydGgxFzAVBgNVBAoMDlJhbmRvbSBDb21wYW55MQswCQYD 8 | VQQLDAJJVDETMBEGA1UEAwwKY2xpZW50LmNvbTEeMBwGCSqGSIb3DQEJARYPdGVz 9 | dEB5b3V6YW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnWwW 10 | QyfFijE8wkbRM6/osgJmvvlyakjXkwTM+IvFigQ2cI3DJybjvcaZEushBlSm+vge 11 | 01+Gbsj5DX0i5OEG4+BRDih+kyAMLkSsF54y8EfrIVhDAB5hziI8LZB9KXIPGznW 12 | TMtOu0sZLMTVe/rxy6y6f5uPfArJYEYjut/HGQCxsJslTkNstbE5tvVT2JJYSXoy 13 | APu/ZnTA7hoAb1u5vOh7aB6q6uhA+w/wtzhDTzkdTNm/B3gK312/ePOotspJQ0MW 14 | opRft/2kfp3+fd7pYrk9hMVIziucOX+YFFPSblfYDHCSoxjBRBjviFmp8K/724qg 15 | KqZvDQIdgyMczcbDCwIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkq 16 | hkiG9w0BAQUFAAOCAQEAV9WCUydmPOe8QQkua9x8mPGEr3i6+ZRmZWAZg9oOdF8m 17 | OlLFy1+UgWy33dLKIKkzIsbsQvKy4+YuNVAIHNo/JqBS+ldlpo/Xd36E9T7MLHnG 18 | gCVYuxz8SAqvrDTfk9gHCaGofA18jDxs8djTd5d7lo9YOKj54V9jNbg/lxV8o+nq 19 | nRkr4Ltu55YR1nUC5KKPx4fdtOHp5VoeWljFtOooYuQmkmu6Y8VEe0aeeg2B2jqg 20 | 7D7mjDkhcQQ1C8gRVkjljeX6RgH+0HeBQaCL6fE17TlMsMsfDOnabTX//aIYJXOY 21 | liQi0c6Fj3oD4202dTvN0kcXulg+9v8UsbaAujGxdQ== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | readonly messageSize="${1:-200}" 3 | readonly batchSize="${2:-200}" 4 | readonly memQueueSize="${3:-1000000}" 5 | readonly dataPath="${4:-}" 6 | set -e 7 | set -u 8 | 9 | echo "# using --mem-queue-size=$memQueueSize --data-path=$dataPath --size=$messageSize --batch-size=$batchSize" 10 | echo "# compiling/running nsqd" 11 | pushd apps/nsqd >/dev/null 12 | go build 13 | rm -f *.dat 14 | ./nsqd --alsologtostderr=true --log-level=2 --mem-queue-size=$memQueueSize --data-path=$dataPath >./bench-nsqd.log 2>&1 & 15 | nsqd_pid=$! 16 | popd >/dev/null 17 | 18 | cleanup() { 19 | kill -s SIGINT $nsqd_pid 20 | #rm -f nsqd/*.dat 21 | } 22 | trap cleanup INT TERM EXIT 23 | 24 | sleep 0.3 25 | echo "# creating topic/channel" 26 | #curl -X PUT --silent 'http://127.0.0.1:4151/topic/create?topic=sub_bench' >/dev/null 2>&1 27 | #curl --silent 'http://127.0.0.1:4151/create_channel?topic=sub_bench&channel=ch' >/dev/null 2>&1 28 | 29 | echo "# compiling bench_reader/bench_writer" 30 | pushd bench >/dev/null 31 | for app in bench_reader bench_writer; do 32 | pushd $app >/dev/null 33 | go build 34 | popd >/dev/null 35 | done 36 | popd >/dev/null 37 | 38 | echo -n "PUB: " 39 | bench/bench_writer/bench_writer --size=$messageSize --batch-size=$batchSize 2>&1 40 | 41 | curl -s -o cpu.pprof http://127.0.0.1:4151/debug/pprof/profile & 42 | pprof_pid=$! 43 | 44 | echo -n "SUB: " 45 | bench/bench_reader/bench_reader --size=$messageSize --channel=ch 2>&1 46 | 47 | echo "waiting for pprof..." 48 | wait $pprof_pid 49 | -------------------------------------------------------------------------------- /internal/protocol/errors.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type ChildErr interface { 4 | Parent() error 5 | } 6 | 7 | // ClientErr provides a way for NSQ daemons to log a human reabable 8 | // error string and return a machine readable string to the client. 9 | // 10 | // see docs/protocol.md for error codes by command 11 | type ClientErr struct { 12 | ParentErr error 13 | Code string 14 | Desc string 15 | } 16 | 17 | // Error returns the machine readable form 18 | func (e *ClientErr) Error() string { 19 | return e.Code + " " + e.Desc 20 | } 21 | 22 | // Parent returns the parent error 23 | func (e *ClientErr) Parent() error { 24 | return e.ParentErr 25 | } 26 | 27 | // NewClientErr creates a ClientErr with the supplied human and machine readable strings 28 | func NewClientErr(parent error, code string, description string) *ClientErr { 29 | return &ClientErr{parent, code, description} 30 | } 31 | 32 | type FatalClientErr struct { 33 | ParentErr error 34 | Code string 35 | Desc string 36 | } 37 | 38 | // Error returns the machine readable form 39 | func (e *FatalClientErr) Error() string { 40 | return e.Code + " " + e.Desc 41 | } 42 | 43 | // Parent returns the parent error 44 | func (e *FatalClientErr) Parent() error { 45 | return e.ParentErr 46 | } 47 | 48 | // NewClientErr creates a ClientErr with the supplied human and machine readable strings 49 | func NewFatalClientErr(parent error, code string, description string) *FatalClientErr { 50 | return &FatalClientErr{parent, code, description} 51 | } 52 | -------------------------------------------------------------------------------- /internal/statsd/client.go: -------------------------------------------------------------------------------- 1 | package statsd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | type Client struct { 11 | conn net.Conn 12 | addr string 13 | prefix string 14 | } 15 | 16 | func NewClient(addr string, prefix string) *Client { 17 | return &Client{ 18 | addr: addr, 19 | prefix: prefix, 20 | } 21 | } 22 | 23 | func (c *Client) String() string { 24 | return c.addr 25 | } 26 | 27 | func (c *Client) CreateSocket(protocol string) error { 28 | conn, err := net.DialTimeout(protocol, c.addr, time.Second) 29 | if err != nil { 30 | return err 31 | } 32 | c.conn = conn 33 | return nil 34 | } 35 | 36 | func (c *Client) Close() error { 37 | if c.conn == nil { 38 | return errors.New("not connected") 39 | } 40 | return c.conn.Close() 41 | } 42 | 43 | func (c *Client) Incr(stat string, count int64) error { 44 | return c.send(stat, "%d|c", count) 45 | } 46 | 47 | func (c *Client) Decr(stat string, count int64) error { 48 | return c.send(stat, "%d|c", -count) 49 | } 50 | 51 | func (c *Client) Timing(stat string, delta int64) error { 52 | return c.send(stat, "%d|ms", delta) 53 | } 54 | 55 | func (c *Client) Gauge(stat string, value int64) error { 56 | return c.send(stat, "%d|g", value) 57 | } 58 | 59 | func (c *Client) send(stat string, format string, value int64) error { 60 | if c.conn == nil { 61 | return errors.New("not connected") 62 | } 63 | format = fmt.Sprintf("%s%s:%s", c.prefix, stat, format) 64 | _, err := fmt.Fprintf(c.conn, format, value) 65 | return err 66 | } 67 | -------------------------------------------------------------------------------- /jepsen/resources/nsqlookupd.conf: -------------------------------------------------------------------------------- 1 | ## : to listen on for TCP clients 2 | tcp_address = "0.0.0.0:4160" 3 | 4 | ## : to listen on for HTTP clients 5 | http_address = "0.0.0.0:4161" 6 | 7 | ## rpc port used for cluster communication 8 | rpc_port = "4260" 9 | ## address that will be registered with lookupd (defaults to the OS hostname) 10 | #broadcast_address = "" 11 | ## the network interface for broadcast, the ip will be detected automatically. 12 | # use this configure instead of broadcast_address to keep all the configure is the same 13 | broadcast_interface = "eth0" 14 | 15 | cluster_id = "test-jepsen-dev-1" 16 | ## the etcd cluster ip list 17 | cluster_leadership_addresses = "http://etcd0.example.com:2379,http://etcd1.example.com:2379" 18 | # cluster_leadership_username = "username" 19 | # cluster_leadership_password = "password" 20 | ## default value is "NSQMetaData" 21 | # cluster_leadership_root_dir = "etcd_root_dir" 22 | 23 | ## duration of time a producer will remain in the active list since its last ping 24 | inactive_producer_timeout = "100s" 25 | # should at least twice as the ping interval on nsqd 26 | nsqd_ping_timeout= "15s" 27 | 28 | ## duration of time a producer will remain tombstoned if registration remains 29 | tombstone_lifetime = "45s" 30 | 31 | ## the detail of the log, larger number means more details 32 | log_level = 3 33 | 34 | ## if empty, use the default flag value in glog 35 | log_dir = "/var/log" 36 | 37 | ## the time period (in hour) that the balance is allowed. 38 | balance_interval = ["4", "5"] 39 | -------------------------------------------------------------------------------- /internal/flume_log/flume_sdk.go: -------------------------------------------------------------------------------- 1 | package flume_log 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var defaultAgentAddr string 9 | 10 | type FlumeLogger struct { 11 | client *FlumeClient 12 | } 13 | 14 | func init() { 15 | defaultAgentAddr = "127.0.0.1:5140" 16 | } 17 | 18 | func NewFlumeLogger() *FlumeLogger { 19 | c := NewFlumeClient(defaultAgentAddr) 20 | return &FlumeLogger{c} 21 | } 22 | 23 | func NewFlumeLoggerWithAddr(agentAddr string) *FlumeLogger { 24 | c := NewFlumeClient(agentAddr) 25 | return &FlumeLogger{c} 26 | } 27 | 28 | func (l *FlumeLogger) Info(logInfo string, detail *DetailInfo) error { 29 | return l.log("info", logInfo, detail) 30 | } 31 | 32 | func (l *FlumeLogger) Warn(logInfo string, detail *DetailInfo) error { 33 | return l.log("warn", logInfo, detail) 34 | } 35 | 36 | func (l *FlumeLogger) Error(logInfo string, detail *DetailInfo) error { 37 | return l.log("error", logInfo, detail) 38 | } 39 | 40 | func (l *FlumeLogger) log(typeInfo string, logInfo string, detail *DetailInfo) error { 41 | log_info := NewLogInfo() 42 | log_info.module = detail.module 43 | topic := "log." + gAppName + "." + detail.module 44 | log_info.topic = strings.ToLower(topic) 45 | log_info.level = typeInfo 46 | log_info.typ = typeInfo 47 | log_info.tag = logInfo 48 | log_info.detail = detail 49 | if l.client != nil { 50 | return l.client.SendLog(log_info.Serialize()) 51 | } 52 | return errors.New("no client") 53 | } 54 | 55 | func (l *FlumeLogger) Stop() { 56 | if l.client != nil { 57 | l.client.Stop() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /nsqlookupd_migrate/README.md: -------------------------------------------------------------------------------- 1 | #nsqlookupd migrate proxy 2 | Migrate proxy to help migrate from old nsq to youzan nsq. 3 | 4 | Migrate works as proxy of nsqlookupd of nsq and youzan nsq. It delegates lookup requests to nsqlookupd in old and new, 5 | migrate response and return to nsq client, according to migrate status read from memory. 6 | 7 | Migrate status defines as follow: 8 |
const (
 9 |      	M_OFF = iota    //0. migrate not start
10 |      	M_CSR           //1. return old&new lookup response for consumer, and old lookup response for producer
11 |      	M_CSR_PDR       //2. return old&nsq lookup response for consumer, and new lookup response for producer
12 |      	M_FIN           //3. return new lookup response for consumer&producer
13 |      )
14 | 15 | > Note: migrate from youzan nsq to another does not support, since partition info in two youzan nsq cold not mix. 16 | 17 | > As to implementation of topic switches, current version shipped with topic switches in memory, switches could be updated via 18 | POST '$host:$port/switch' with body {"$topic":[0-3]}, and default switches for all topic is 0(M_OFF) 19 | 20 | ##Deploy 21 | start with config file: 22 |
nsqlookupd_migrate -config=conf/nsqlookup_migrate.conf
23 | 24 | ##Config 25 | properties defined in nsqlookup migrate: 26 |
27 | origin-lookupd-http = "http://origin.nsqlookupd:4161"
28 | target-lookupd-http = "http://target.nsqlookupd:4161"
29 | env = "qa"
30 | log-level = 2
31 | log-dir = "/data/logs/nsqlookup_migrate"
32 | migrate-key = "origin.to.target"
33 | 
-------------------------------------------------------------------------------- /consistence/etcd_utils.go: -------------------------------------------------------------------------------- 1 | package consistence 2 | 3 | import ( 4 | "net/url" 5 | "os" 6 | 7 | "github.com/coreos/etcd/client" 8 | ) 9 | 10 | const ( 11 | ErrCodeEtcdNotReachable = 501 12 | ErrCodeUnhandledHTTPStatus = 502 13 | ) 14 | 15 | var ( 16 | hostname string 17 | ip string 18 | ) 19 | 20 | func initEtcdPeers(machines []string) error { 21 | for i, ep := range machines { 22 | u, err := url.Parse(ep) 23 | if err != nil { 24 | return err 25 | } 26 | if u.Scheme == "" { 27 | u.Scheme = "http" 28 | } 29 | machines[i] = u.String() 30 | } 31 | return nil 32 | } 33 | 34 | func IsEtcdNotReachable(err error) bool { 35 | if cErr, ok := err.(client.Error); ok { 36 | return cErr.Code == ErrCodeEtcdNotReachable 37 | } 38 | return false 39 | } 40 | 41 | func IsEtcdWatchExpired(err error) bool { 42 | return isEtcdErrorNum(err, client.ErrorCodeEventIndexCleared) 43 | } 44 | 45 | func CheckKeyIfExist(err error) bool { 46 | return isEtcdErrorNum(err, client.ErrorCodeKeyNotFound) 47 | } 48 | 49 | func IsEtcdNotFile(err error) bool { 50 | return isEtcdErrorNum(err, client.ErrorCodeNotFile) 51 | } 52 | 53 | func IsEtcdNodeExist(err error) bool { 54 | return isEtcdErrorNum(err, client.ErrorCodeNodeExist) 55 | } 56 | 57 | func isEtcdErrorNum(err error, errorCode int) bool { 58 | if err != nil { 59 | if etcdError, ok := err.(client.Error); ok { 60 | return etcdError.Code == errorCode 61 | } 62 | // NOTE: There are other error types returned 63 | } 64 | return false 65 | } 66 | 67 | func init() { 68 | hostname, _ = os.Hostname() 69 | } 70 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. commit to bump the version and update the changelog/readme 4 | # 2. tag that commit 5 | # 3. use dist.sh to produce tar.gz for linux and darwin 6 | # 4. upload *.tar.gz to our bitly s3 bucket 7 | # 5. docker push nsqio/nsq 8 | # 6. push to nsqio/master 9 | # 7. update the release metadata on github / upload the binaries there too 10 | # 8. update the gh-pages branch with versions / download links 11 | # 9. update homebrew version 12 | # 10. send release announcement emails 13 | # 11. update IRC channel topic 14 | # 12. tweet 15 | 16 | set -e 17 | 18 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 19 | echo $GOPATH 20 | 21 | arch='amd64' 22 | version=$(awk '/const Binary/ {print $NF}' < $DIR/internal/version/binary.go | sed 's/"//g') 23 | goversion=$(go version | awk '{print $3}') 24 | 25 | for os in linux darwin windows; do 26 | echo "... building v$version for $os/$arch" 27 | BUILD=$(mktemp -d -t nsqXXXXXX) 28 | TARGET="nsq-$version.$os-$arch.$goversion" 29 | GOOS=$os GOARCH=$arch CGO_ENABLED=0 \ 30 | make DESTDIR=$BUILD PREFIX=/$TARGET install 31 | pushd $BUILD 32 | if [ "$os" == "linux" ]; then 33 | cp -r $TARGET/bin $DIR/dist/docker/ 34 | fi 35 | tar czvf $TARGET.tar.gz $TARGET 36 | mv $TARGET.tar.gz $DIR/dist 37 | popd 38 | make clean 39 | rm -r $BUILD 40 | done 41 | 42 | docker build -t nsqio/nsq:v$version . 43 | if [[ ! $version == *"-"* ]]; then 44 | echo "Tagging nsqio/nsq:v$version as the latest release." 45 | docker tag -f nsqio/nsq:v$version nsqio/nsq:latest 46 | fi 47 | -------------------------------------------------------------------------------- /nsqlookupd_migrate/logger.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd_migrate 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | 7 | "github.com/absolute8511/glog" 8 | ) 9 | 10 | const ( 11 | LOG_ERR int32 = iota 12 | LOG_WARN 13 | LOG_INFO 14 | LOG_DEBUG 15 | ) 16 | 17 | type MigrateLogger struct { 18 | level int32 19 | } 20 | 21 | func (logger *MigrateLogger) SetLevel(level int32) { 22 | atomic.StoreInt32(&logger.level, level) 23 | glog.Infof("Log level set to %v", level) 24 | } 25 | 26 | func (logger *MigrateLogger) Level() int32 { 27 | return atomic.LoadInt32(&logger.level) 28 | } 29 | 30 | func (logger *MigrateLogger) AccessTrace(format string, v ...interface{}) { 31 | if logger.Level() >= LOG_INFO { 32 | accessFmt := fmt.Sprintf("[access] %v", format) 33 | glog.InfoDepth(2, fmt.Sprintf(accessFmt, v...)) 34 | } 35 | } 36 | 37 | func (logger *MigrateLogger) Debug(format string, v ...interface{}) { 38 | if logger.Level() >= LOG_DEBUG { 39 | glog.InfoDepth(2, fmt.Sprintf(format, v...)) 40 | } 41 | } 42 | 43 | func (logger *MigrateLogger) Info(format string, v ...interface{}) { 44 | if logger.Level() >= LOG_INFO { 45 | glog.InfoDepth(2, fmt.Sprintf(format, v...)) 46 | } 47 | } 48 | 49 | func (logger *MigrateLogger) Warn(format string, v ...interface{}) { 50 | glog.WarningDepth(2, fmt.Sprintf(format, v...)) 51 | } 52 | 53 | func (logger *MigrateLogger) Error(format string, v ...interface{}) { 54 | glog.ErrorDepth(2, fmt.Sprintf(format, v...)) 55 | } 56 | 57 | func NewMigrateLogger(level int32) *MigrateLogger { 58 | mLog := MigrateLogger{ 59 | level, 60 | } 61 | return &mLog 62 | } 63 | -------------------------------------------------------------------------------- /nsqlookupd/lookup_protocol_v1_test.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/youzan/nsq/internal/protocol" 9 | "github.com/youzan/nsq/internal/test" 10 | ) 11 | 12 | func TestIOLoopReturnsClientErrWhenSendFails(t *testing.T) { 13 | fakeConn := test.NewFakeNetConn() 14 | fakeConn.WriteFunc = func(b []byte) (int, error) { 15 | return 0, errors.New("write error") 16 | } 17 | 18 | testIOLoopReturnsClientErr(t, fakeConn) 19 | } 20 | 21 | func TestIOLoopReturnsClientErrWhenSendSucceeds(t *testing.T) { 22 | fakeConn := test.NewFakeNetConn() 23 | fakeConn.WriteFunc = func(b []byte) (int, error) { 24 | return len(b), nil 25 | } 26 | 27 | testIOLoopReturnsClientErr(t, fakeConn) 28 | } 29 | 30 | func testIOLoopReturnsClientErr(t *testing.T, fakeConn test.FakeNetConn) { 31 | fakeConn.ReadFunc = func(b []byte) (int, error) { 32 | return copy(b, []byte("INVALID_COMMAND\n")), nil 33 | } 34 | 35 | opts := NewOptions() 36 | opts.Logger = newTestLogger(t) 37 | 38 | prot := &LookupProtocolV1{ctx: &Context{nsqlookupd: New(opts)}} 39 | 40 | errChan := make(chan error) 41 | test := func() { 42 | errChan <- prot.IOLoop(fakeConn) 43 | defer prot.ctx.nsqlookupd.Exit() 44 | } 45 | go test() 46 | 47 | var err error 48 | var timeout bool 49 | 50 | select { 51 | case err = <-errChan: 52 | case <-time.After(2 * time.Second): 53 | timeout = true 54 | } 55 | 56 | equal(t, timeout, false) 57 | 58 | nequal(t, err, nil) 59 | equal(t, err.Error(), "E_INVALID invalid command INVALID_COMMAND") 60 | nequal(t, err.(*protocol.FatalClientErr), nil) 61 | } 62 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/nodes.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | var Pubsub = require('../lib/pubsub'); 4 | var AppState = require('../app_state'); 5 | 6 | var BaseView = require('./base'); 7 | 8 | var Nodes = require('../collections/nodes'); 9 | 10 | var NodesView = BaseView.extend({ 11 | className: 'nodes container-fluid', 12 | 13 | template: require('./spinner.hbs'), 14 | 15 | events: { 16 | 'click .conn-count': 'onClickConnCount' 17 | }, 18 | 19 | initialize: function() { 20 | BaseView.prototype.initialize.apply(this, arguments); 21 | this.listenTo(AppState, 'change:graph_interval', this.render); 22 | this.collection = new Nodes(); 23 | this.collection.fetch() 24 | .done(function(data) { 25 | this.template = require('./nodes.hbs'); 26 | $.ajax({ 27 | url: AppState.url('/cluster/stats'), 28 | success: function(data){ 29 | clustersInfo = data; 30 | }, 31 | async: false 32 | }); 33 | this.render({ 34 | 'message': data['message'], 35 | 'clusters': clustersInfo['clustersInfo'] 36 | }); 37 | }.bind(this)) 38 | .fail(this.handleViewError.bind(this)) 39 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 40 | }, 41 | 42 | onClickConnCount: function(e) { 43 | e.preventDefault(); 44 | $(e.target).next().toggle(); 45 | } 46 | }); 47 | 48 | module.exports = NodesView; 49 | -------------------------------------------------------------------------------- /nsqdserver/test/cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # call this script with an email address (valid or not). 3 | # like: 4 | # ./cert.sh foo@foo.com 5 | # Found: https://gist.github.com/ncw/9253562#file-makecert-sh 6 | 7 | if [ "$1" == "" ]; then 8 | echo "Need email as argument" 9 | exit 1 10 | fi 11 | 12 | EMAIL=$1 13 | 14 | rm -rf tmp 15 | mkdir tmp 16 | cd tmp 17 | 18 | echo "make CA" 19 | PRIVKEY="test" 20 | openssl req -new -x509 -days 365 -keyout ca.key -out ca.pem -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=KryptoKings@random.com" -passout pass:$PRIVKEY 21 | 22 | echo "make server cert" 23 | openssl req -new -nodes -x509 -out server.pem -keyout server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=${EMAIL}" 24 | 25 | echo "make client cert" 26 | #openssl req -new -nodes -x509 -out client.pem -keyout client.key 27 | #-days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random 28 | #Company/OU=IT/CN=www.random.com/emailAddress=${EMAIL}" 29 | 30 | openssl genrsa -out client.key 2048 31 | echo "00" > ca.srl 32 | openssl req -sha1 -key client.key -new -out client.req -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=client.com/emailAddress=${EMAIL}" 33 | # Adding -addtrust clientAuth makes certificates Go can't read 34 | openssl x509 -req -days 365 -in client.req -CA ca.pem -CAkey ca.key -passin pass:$PRIVKEY -out client.pem # -addtrust clientAuth 35 | 36 | openssl x509 -extfile ../openssl.conf -extensions ssl_client -req -days 365 -in client.req -CA ca.pem -CAkey ca.key -passin pass:$PRIVKEY -out client.pem 37 | 38 | cd .. 39 | mv tmp/* certs 40 | rm -rf tmp 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to NSQ! 4 | 5 | ## Code of Conduct 6 | 7 | Help us keep NSQ open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md). 8 | 9 | ## Getting Started 10 | 11 | * make sure you have a [GitHub account](https://github.com/signup/free) 12 | * submit a ticket for your issue, assuming one does not already exist 13 | * clearly describe the issue including steps to reproduce when it is a bug 14 | * identify specific versions of the binaries and client libraries 15 | * fork the repository on GitHub 16 | 17 | ## Making Changes 18 | 19 | * create a branch from where you want to base your work 20 | * we typically name branches according to the following format: `helpful_name_` 21 | * make commits of logical units 22 | * make sure your commit messages are in a clear and readable format, example: 23 | 24 | ``` 25 | nsqd: fixed bug in protocol_v2 26 | 27 | * update the message pump to properly account for RDYness 28 | * cleanup variable names 29 | * ... 30 | ``` 31 | 32 | * if you're fixing a bug or adding functionality it probably makes sense to write a test 33 | * make sure to run `fmt.sh` and `test.sh` in the root of the repo to ensure that your code is 34 | properly formatted and that tests pass (NOTE: we integrate Travis with GitHub for continuous 35 | integration) 36 | 37 | ## Submitting Changes 38 | 39 | * push your changes to your branch in your fork of the repository 40 | * submit a pull request against nsqio's repository 41 | * comment in the pull request when you're ready for the changes to be reviewed: `"ready for review"` 42 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEbjCCA1agAwIBAgIJAK6x7y6AwBmLMA0GCSqGSIb3DQEBBQUAMIGAMQswCQYD 3 | VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxFjAUBgNVBAcTDU5ldyBZb3JrIENp 4 | dHkxDDAKBgNVBAoTA05TUTETMBEGA1UEAxMKdGVzdC5sb2NhbDEjMCEGCSqGSIb3 5 | DQEJARYUbXJlaWZlcnNvbkBnbWFpbC5jb20wHhcNMTMwNjI4MDA0MzQ4WhcNMTYw 6 | NDE3MDA0MzQ4WjCBgDELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMRYw 7 | FAYDVQQHEw1OZXcgWW9yayBDaXR5MQwwCgYDVQQKEwNOU1ExEzARBgNVBAMTCnRl 8 | c3QubG9jYWwxIzAhBgkqhkiG9w0BCQEWFG1yZWlmZXJzb25AZ21haWwuY29tMIIB 9 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnX0KB+svwy+yHU2qggz/EaGg 10 | craKShagKo+9M9y5HLM852ngk5c+t+tJJbx3N954Wr1FXBuGIv1ltU05rU4zhvBS 11 | 25tVP1UIEnT5pBt2TeetLkl199Y7fxh1hKmnwJMG3fy3VZdNXEndBombXMmtXpQY 12 | shuEJHKeUNDbQKz5X+GjEdkTPO/HY/VMHsxS23pbSimQozMg3hvLIdgv0aS3QECz 13 | ydZBgTPThy3uDtHIuCpxCwXd/vDF68ATlYgo3h3lh2vxNwM/pjklIUhzMh4XaKQF 14 | 7m3/0KbtUcXfy0QHueeuMr11E9MAFNyRN4xf9Fk1yB97KJ3PJBTC5WD/m1nW+QID 15 | AQABo4HoMIHlMB0GA1UdDgQWBBR3HMBws4lmYYSIgwoZsfW+bbgaMjCBtQYDVR0j 16 | BIGtMIGqgBR3HMBws4lmYYSIgwoZsfW+bbgaMqGBhqSBgzCBgDELMAkGA1UEBhMC 17 | VVMxETAPBgNVBAgTCE5ldyBZb3JrMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MQww 18 | CgYDVQQKEwNOU1ExEzARBgNVBAMTCnRlc3QubG9jYWwxIzAhBgkqhkiG9w0BCQEW 19 | FG1yZWlmZXJzb25AZ21haWwuY29tggkArrHvLoDAGYswDAYDVR0TBAUwAwEB/zAN 20 | BgkqhkiG9w0BAQUFAAOCAQEANOYTbanW2iyV1v4oYpcM/y3TWcQKzSME8D2SGFZb 21 | dbMYU81hH3TTlQdvyeh3FAcdjhKE8Xi/RfNNjEslTBscdKXePGpZg6eXRNJzPP5K 22 | KZPf5u6tcpAeUOKrMqbGwbE+h2QixxG1EoVQtE421szsU2P7nHRTdHzKFRnOerfl 23 | Phm3NocR0P40Rv7WKdxpOvqc+XKf0onTruoVYoPWGpwcLixCG0zu4ZQ23/L/Dy18 24 | 4u70Hbq6O/6kq9FBFaDNp3IhiEdu2Cq6ZplU6bL9XDF27KIEErHwtuqBHVlMG+zB 25 | oH/k9vZvwH7OwAjHdKp+1yeZFLYC8K5hjFIHqcdwpZCNIg== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /nsqlookupd/tcp.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "time" 7 | 8 | "github.com/youzan/nsq/internal/protocol" 9 | ) 10 | 11 | type tcpServer struct { 12 | ctx *Context 13 | } 14 | 15 | func (p *tcpServer) Handle(clientConn net.Conn) { 16 | nsqlookupLog.Logf("TCP: new client(%s)", clientConn.RemoteAddr()) 17 | 18 | // The client should initialize itself by sending a 4 byte sequence indicating 19 | // the version of the protocol that it intends to communicate, this will allow us 20 | // to gracefully upgrade the protocol away from text/line oriented to whatever... 21 | buf := make([]byte, 4) 22 | clientConn.SetReadDeadline(time.Now().Add(time.Second * 3)) 23 | _, err := io.ReadFull(clientConn, buf) 24 | if err != nil { 25 | nsqlookupLog.Logf(" failed to read protocol version - %s from client: %v", err, clientConn.RemoteAddr()) 26 | clientConn.Close() 27 | return 28 | } 29 | protocolMagic := string(buf) 30 | 31 | nsqlookupLog.Logf("CLIENT(%s): desired protocol magic '%s'", 32 | clientConn.RemoteAddr(), protocolMagic) 33 | 34 | var prot protocol.Protocol 35 | switch protocolMagic { 36 | case " V1": 37 | prot = &LookupProtocolV1{ctx: p.ctx} 38 | default: 39 | protocol.SendResponse(clientConn, []byte("E_BAD_PROTOCOL")) 40 | clientConn.Close() 41 | nsqlookupLog.LogErrorf(" client(%s) bad protocol magic '%s'", 42 | clientConn.RemoteAddr(), protocolMagic) 43 | return 44 | } 45 | 46 | err = prot.IOLoop(clientConn) 47 | if err != nil { 48 | if err == io.EOF { 49 | nsqlookupLog.Logf(" client(%s) - %s", clientConn.RemoteAddr(), err) 50 | } else { 51 | nsqlookupLog.LogWarningf(" client(%s) - %s", clientConn.RemoteAddr(), err) 52 | } 53 | return 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /nsqdserver/tcp.go: -------------------------------------------------------------------------------- 1 | package nsqdserver 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "time" 8 | 9 | "github.com/youzan/nsq/internal/levellogger" 10 | "github.com/youzan/nsq/internal/protocol" 11 | "github.com/youzan/nsq/nsqd" 12 | ) 13 | 14 | type tcpServer struct { 15 | ctx *context 16 | } 17 | 18 | func (p *tcpServer) Handle(clientConn net.Conn) { 19 | // The client should initialize itself by sending a 4 byte sequence indicating 20 | // the version of the protocol that it intends to communicate, this will allow us 21 | // to gracefully upgrade the protocol away from text/line oriented to whatever... 22 | clientConn.SetReadDeadline(time.Now().Add(p.ctx.getOpts().ClientTimeout)) 23 | buf := make([]byte, 4) 24 | _, err := io.ReadFull(clientConn, buf) 25 | if err != nil { 26 | nsqd.NsqLogger().Logf(" failed to read protocol version - %s from client: %v", err, clientConn.RemoteAddr()) 27 | clientConn.Close() 28 | return 29 | } 30 | protocolMagic := string(buf) 31 | 32 | if nsqd.NsqLogger().Level() >= levellogger.LOG_DEBUG { 33 | nsqd.NsqLogger().LogDebugf("new CLIENT(%s): desired protocol magic '%s'", 34 | clientConn.RemoteAddr(), protocolMagic) 35 | } 36 | 37 | var prot protocol.Protocol 38 | switch protocolMagic { 39 | case " V2": 40 | prot = &protocolV2{ctx: p.ctx} 41 | default: 42 | protocol.SendFramedResponse(clientConn, frameTypeError, []byte("E_BAD_PROTOCOL")) 43 | clientConn.Close() 44 | nsqd.NsqLogger().Logf("client(%s) bad protocol magic '%s'", 45 | clientConn.RemoteAddr(), protocolMagic) 46 | return 47 | } 48 | 49 | err = prot.IOLoop(clientConn) 50 | if err != nil { 51 | nsqd.NsqLogger().Logf("client(%s) error - %s", clientConn.RemoteAddr(), err) 52 | return 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contrib/nsqlookupd.cfg.example: -------------------------------------------------------------------------------- 1 | ## : to listen on for TCP clients 2 | tcp_address = "0.0.0.0:4160" 3 | 4 | ## : to listen on for HTTP clients 5 | http_address = "0.0.0.0:4161" 6 | 7 | ## rpc port used for cluster communication 8 | rpc_port = "4260" 9 | ## address that will be registered with lookupd (defaults to the OS hostname) 10 | #broadcast_address = "" 11 | ## the network interface for broadcast, the ip will be detected automatically. 12 | # use this configure instead of broadcast_address to keep all the configure is the same 13 | broadcast_interface = "eth0" 14 | 15 | ## local reverse proxy port, basically used for collecting the stats 16 | # reverse_proxy_port = "4163" 17 | 18 | cluster_id = "test-nsq-cluster-dev-1" 19 | ## the etcd cluster ip list 20 | cluster_leadership_addresses = "http://127.0.0.1:2379" 21 | # cluster_leadership_username = "username" 22 | # cluster_leadership_password = "password" 23 | ## default value is "NSQMetaData" 24 | # cluster_leadership_root_dir = "etcd_root_dir" 25 | 26 | ## duration of time a producer will remain in the active list since its last ping 27 | inactive_producer_timeout = "100s" 28 | # should at least twice as the ping interval on nsqd 29 | nsqd_ping_timeout= "15s" 30 | 31 | ## duration of time a producer will remain tombstoned if registration remains 32 | tombstone_lifetime = "45s" 33 | 34 | ## the detail of the log, larger number means more details 35 | log_level = 2 36 | 37 | ## if empty, use the default flag value in glog 38 | log_dir = "./" 39 | 40 | ## the time period (in hour) that the balance is allowed. 41 | balance_interval = ["4", "5"] 42 | 43 | ## allow return topic as writable while no any channel under the topic 44 | allow_write_with_nochannels = true 45 | -------------------------------------------------------------------------------- /nsqd/backend_queue.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | type BackendOffset int64 4 | 5 | type BackendQueueOffset interface { 6 | Offset() BackendOffset 7 | } 8 | 9 | type BackendQueueEnd interface { 10 | Offset() BackendOffset 11 | TotalMsgCnt() int64 12 | IsSame(BackendQueueEnd) bool 13 | } 14 | 15 | // BackendQueue represents the behavior for the secondary message 16 | // storage system 17 | type BackendQueue interface { 18 | Put([]byte) error 19 | ReadChan() chan []byte // this is expected to be an *unbuffered* channel 20 | Close() error 21 | Delete() error 22 | Depth() int64 23 | Empty() error 24 | } 25 | 26 | // for topic producer 27 | type BackendQueueWriter interface { 28 | Put([]byte) (BackendOffset, int32, int64, error) 29 | Close() error 30 | Delete() error 31 | Empty() error 32 | Flush(bool) error 33 | GetQueueWriteEnd() BackendQueueEnd 34 | GetQueueReadStart() BackendQueueEnd 35 | GetQueueReadEnd() BackendQueueEnd 36 | RollbackWrite(BackendOffset, uint64) error 37 | ResetWriteEnd(BackendOffset, int64) error 38 | } 39 | 40 | type ReadResult struct { 41 | Offset BackendOffset 42 | MovedSize BackendOffset 43 | CurCnt int64 44 | Data []byte 45 | Err error 46 | } 47 | 48 | // for channel consumer 49 | type BackendQueueReader interface { 50 | ConfirmRead(BackendOffset, int64) error 51 | ResetReadToConfirmed() (BackendQueueEnd, error) 52 | SkipReadToOffset(BackendOffset, int64) (BackendQueueEnd, error) 53 | SkipReadToEnd() (BackendQueueEnd, error) 54 | Close() error 55 | // left data to be read 56 | Depth() int64 57 | DepthSize() int64 58 | GetQueueReadEnd() BackendQueueEnd 59 | GetQueueConfirmed() BackendQueueEnd 60 | Delete() error 61 | UpdateQueueEnd(BackendQueueEnd, bool) (bool, error) 62 | TryReadOne() (ReadResult, bool) 63 | } 64 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAnWwWQyfFijE8wkbRM6/osgJmvvlyakjXkwTM+IvFigQ2cI3D 3 | JybjvcaZEushBlSm+vge01+Gbsj5DX0i5OEG4+BRDih+kyAMLkSsF54y8EfrIVhD 4 | AB5hziI8LZB9KXIPGznWTMtOu0sZLMTVe/rxy6y6f5uPfArJYEYjut/HGQCxsJsl 5 | TkNstbE5tvVT2JJYSXoyAPu/ZnTA7hoAb1u5vOh7aB6q6uhA+w/wtzhDTzkdTNm/ 6 | B3gK312/ePOotspJQ0MWopRft/2kfp3+fd7pYrk9hMVIziucOX+YFFPSblfYDHCS 7 | oxjBRBjviFmp8K/724qgKqZvDQIdgyMczcbDCwIDAQABAoIBAHIhXPqPKR6Jl/1a 8 | Dm4kyNXV+9vVSehYq0JBpsFDxJHNgBKWFLib7101UOFwat8GHJGztdWVWgeT2Uy4 9 | PsYlhzNK/DkBjtLn1yzsSMBOx6uA2K2yKdE6NbqXA34dVUDpGp6ojf/bHleXmMbm 10 | ZcifJRJ/Ri8h/yL1Rw5vi5P+jYBH52jfvujnjWUR+Deaz211WyDKT2mYmrRKsugd 11 | a6xvqndez3t7VCHwAFJTKYUAN9JTK2EGt8gwWYDmtpvRB3qlSzN+pzuSXA2NFO2V 12 | J2xjzvTYEOhIiAjae0YwnqID7wifIrWp1hX8EMm8Jji9yjGEZFnbA9MJD3unozFM 13 | f72GH1ECgYEAzGgzhXFl7iSASZEW6TEui9Le1OtMcMh5ZCmPpTRTorg8dzwEfjAN 14 | o2IzV3TTsa/XzUpcurHbz+fjXYIGV0Hj/MXWLKgkti9iJG+NIujdmi28lV8LHBZS 15 | fcyG6l8doRBEJMXEOaspbBkDIB8I7Cxe59y3UQoJs64KgtMllnz3a1cCgYEAxSfz 16 | 5NL9+1Nfvbg4RSyeBFtADzVuHLAp8IgQp+wq033e9aUcGw0B+mV0Cs2CbI/Qh+cQ 17 | QLIFx9pqJW8nfVZbXhqlDUSIVz1SX444MdVyyCPTjzMXnBQK94WCJsJrc6r2D0v+ 18 | V3t+1xqHT8dCLZzBdZcZ7Kh6qrq0wfBE++aYCW0CgYBmFVw1tq5rQnt3QiipCVDv 19 | r75to52nbdeCOGdu4DUsvMol2il8Q89r2208rjHTObiS5CJvcsgOiwa3+mnkRhnw 20 | cXVHFCpRelSPUc6wso+JonHeP3sy6csE3JI8logbmh4OJ16aVmr3e35eGmHAYWB8 21 | d3Rt2o+B1teVApJxF7QRPQKBgAEm9Z7tuLxCRXn3XKTS2O2PlvU/y6/xO52DPg2t 22 | LKblwmrPFzX1QoCzHRV/E/cMdByHNioaoDwFw78KQkHSQmBvaW2FbAKOgi+6xgtM 23 | gwo2Y2Mf2lB4MYQ2zIbrdfNMGXoHaKocN6aMKiRGNtXLZ7oHMwiWB3l989Z6T/zk 24 | 4RiVAoGAPOZfwwqNkNRelQ52907gzRJT8DoGzNdxWdI+u/J8f2TBNHgwnn+Htibe 25 | O8m6qPQb0SbsGl4Ms+F77v1Dfk/VJa6CSFHavogyKg/FlyjIMIb4ptpzjngjDQ2T 26 | Ot2q4vt1Uchx24UL35FjwPzPST3WJDyKxv2d1yVp7sboCJJ2JUQ= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAnX0KB+svwy+yHU2qggz/EaGgcraKShagKo+9M9y5HLM852ng 3 | k5c+t+tJJbx3N954Wr1FXBuGIv1ltU05rU4zhvBS25tVP1UIEnT5pBt2TeetLkl1 4 | 99Y7fxh1hKmnwJMG3fy3VZdNXEndBombXMmtXpQYshuEJHKeUNDbQKz5X+GjEdkT 5 | PO/HY/VMHsxS23pbSimQozMg3hvLIdgv0aS3QECzydZBgTPThy3uDtHIuCpxCwXd 6 | /vDF68ATlYgo3h3lh2vxNwM/pjklIUhzMh4XaKQF7m3/0KbtUcXfy0QHueeuMr11 7 | E9MAFNyRN4xf9Fk1yB97KJ3PJBTC5WD/m1nW+QIDAQABAoIBACvtfKbIywG+hAf4 8 | ad7skRjx5DcbA2e29+XnQfb9UgTXWd2SgrmoLi5OypBkCTzkKN3mfTo70yZfV8dC 9 | Sxwz+9tfnTz0DssjhKThS+CiaFVCkeOfSfBfKSlCQUVHrSrh18CDhP+yvDlJwQTZ 10 | zSQMfPcsh9bmJe2kqtQP7ZgUp1o+vaB8Sju8YYrO6FllxbdLRGm4pfvvrHIRRmXa 11 | oVHn0ei0JpwoTY9kHYht4LNeJnbP/MCWdmcuv3Gnel7jAlhaKab5aNIGr0Xe7aIQ 12 | iX6mpZ0/Rnt8o/XcTOg8l3ruIdVuySX6SYn08JMnfFkXdNYRVhoV1tC5ElWkaZLf 13 | hPmj2yECgYEAyts0R0b8cZ6HTAyuLm3ilw0s0v0/MM9ZtaqMRilr2WEtAhF0GpHG 14 | TzmGnii0WcTNXD7NTsNcECR/0ZpXPRleMczsL2Juwd4FkQ37h7hdKPseJNrfyHRg 15 | VolOFBX9H14C3wMB9cwdsG4Egw7fE27WCoreEquHgwFxl1zBrXKH088CgYEAxr8w 16 | BKZs0bF7LRrFT5pH8hpMLYHMYk8ZIOfgmEGVBKDQCOERPR9a9kqUss7wl/98LVNK 17 | RnFlyWD6Z0/QcQsLL4LjBeZJ25qEMc6JXm9VGAzhXA1ZkUofVoYCnG+f6KUn8CuJ 18 | /AcV2ZDFsEP10IiQG0hKsceXiwFEvEr8306tMrcCgYBLgnscSR0xAeyk71dq6vZc 19 | ecgEpcX+2kAvclOSzlpZ6WVCjtKkDT0/Qk+M0eQIQkybGLl9pxS+4Yc+s2/jy2yX 20 | pwsHvGE0AvwZeZX2eDcdSRR4bYy9ZixyKdwJeAHnyivRbaIuJ5Opl9pQGpoI9snv 21 | 1K9DTdw8dK4exKVHdgl/WwKBgDkmLsuXg4EEtPOyV/xc08VVNIR9Z2T5c7NXmeiO 22 | KyiKiWeUOF3ID2L07S9BfENozq9F3PzGjMtMXJSqibiHwW6nB1rh7mj8VHjx9+Q0 23 | xVZGFeNfX1r84mgB3uxW2LeQDhzsmB/lda37CC14TU3qhu2hawEV8IijE73FHlOk 24 | Dv+fAoGAI4/XO5o5tNn5Djo8gHmGMCbinUE9+VySxl7wd7PK8w2VSofO88ofixDk 25 | NX94yBYhg5WZcLdPm45RyUnq+WVQYz9IKUrdxLFTH+wxyzUqZCW7jgXCvWV+071q 26 | vqm9C+kndq+18/1VKuCSGWnF7Ay4lbsgPXY2s4VKRxcb3QpZSPU= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL2jtqe4l5v9iY 3 | JJYipX/QJRV/VKulznVUFZW1tBHqfgaZ8Nf/3Sn1nq3tDrDnfHa1fVuvy/VxE1Yg 4 | Jr9K7CmWEHxLpDa8WP4RoED5H7LM8TaJ++9Ph3+hm8d3+r11kK1DdaRmECUg7PCe 5 | mMzWKVxzw6qsDq2uZhaQbu9xhwJhIg4Uldud90RcgFBJS70BWMc5DvJK01nWy8D6 6 | 3gnb3SlSmeSDTCN7jC1oi4Ao94VPJCxlNj/3ql+aRHnOpvDal9INCQ4TB8XeH3pI 7 | W5pBpg39zufynZDJb9eG/1WSamd6dmWtwokQtFJOB6StbRkxAAkc/yVNYZAAs01H 8 | 8F4UipBpAgMBAAECggEBALq9ED6sKjR4dK5ynYHhf6MpxXaeFptN9JbxKvKHoP3h 9 | MqTkRsohTGWvVX5aPd3gW1JIIqx4tz89SmErfYLhsfrA4UvdgzKohx2YYsyTqBQr 10 | Rx3BxuhgpJ9jd6dF7zZ6MO4iH1ZLpedyhb2TFdl9jC6T6I2y6JFg/XyT0w55ccmN 11 | ZtKmKcA+WR68uOovuvEJv1SJSFaJdbOd1Z82UkuvLSzNuoImCrFTR1LlGxswKyqN 12 | 0cApMEh/f+rkTJ1XqaJ0lKMII2e52mPxThdfbjqDjjACL+SIh6mNiQAjrTEQ60DN 13 | dzhZB5JnSFSPBk/UDyzvNBR/4iMpOWvHhNhKZ1JpZH0CgYEA/ixMispEtkGAUnwI 14 | 7GXNgOJaDU3u79slyvmrOHNMa/n2fqOOw1bXlQJu+iHhPFpQvgb8DWdhdWhwxPIf 15 | en4T4tSuAojx7/EWN4eIAiPCWww/Bbo0UvP+mR/gBDU+vwNcc67D6lJJewEBEgTp 16 | KrgW21xtoTjdW8TBIwcXi2FDKZcCgYEAzVFWuZD1WP+/jpADB946hb3Xej5RgxK3 17 | F+nJ1O0nXSRl8g4dV3J4rkpZi0ADPnErX89sq7OmCdiReR0Amn/zJNOoc2tt05Tc 18 | 3tlhuJNvzdxn5h1f2MJy6KBAPGKh6knN3jPaMQmVKO0tntq8vkDEXbIWYXr7rOKE 19 | kZedCWFzVf8CgYAhaWNab0JfDvc2YJWvtaYxBhA+ZXs+TnmGWBGY5xcprn8noIp4 20 | xSarsNkzylZYX6rzf+mMPYXDMEp6qTVYHdCxZbHGJYhgOTnpbMiFN3wqCc8TCqos 21 | KWTTbiw9gV5RL7jsgBHC8LQ2Fii03K5l7jMa/OhwZBA9Kv0UhK8kpFF22wKBgQCv 22 | 3Nbi9ZYhmFY0v4fJCoGoItbZf12EXthzZC4b8tJ/xniH360kRQm3iDmwu7DLoFXd 23 | qCbV33f+AQGB8uSccbILPjyQOFmHHIAfZaV9WtQuSXa5NTnuyYdxWCoNTxKPQo5S 24 | oKlozWN0/crQwV1fvMw10R+4eYXvLMsOhgXdjhojcwKBgAMK+ax5BcG64CVBTqVt 25 | VU/pHC9pvS+7pjSySUaiiTtmMRPXg/Yrt6TCgA5JymAmdEro/7+5/s5p4WReNTpF 26 | yxSbkinQ5E4PxIDCwlEQKDz44G7wNOZ9mQn7aBwPlZAN2AJHvN0PXQsfuT+GVb8s 27 | IL4rkoOH4FXBDvqIgFYHe09D 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /consistence/nsqd_node_etcd_test.go: -------------------------------------------------------------------------------- 1 | package consistence 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/youzan/nsq/internal/test" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | func TestNodeRe(t *testing.T) { 14 | ClusterID := "test-nsq-cluster-unit-test-etcd-leadership" 15 | nodeMgr, _ := NewNsqdEtcdMgr(testEtcdServers, testEtcdUsername, testEtcdPassword) 16 | nodeMgr.InitClusterID(ClusterID) 17 | ID := "unit-test-etcd-node1" 18 | nodeInfo := &NsqdNodeInfo{ 19 | ID: ID, 20 | NodeIP: "127.0.0.1", 21 | TcpPort: "2222", 22 | RpcPort: "2223", 23 | } 24 | err := nodeMgr.RegisterNsqd(nodeInfo) 25 | test.Nil(t, err) 26 | time.Sleep(10 * time.Second) 27 | err = nodeMgr.UnregisterNsqd(nodeInfo) 28 | test.Nil(t, err) 29 | } 30 | 31 | func TestETCDWatch(t *testing.T) { 32 | client, _ := NewEClient(testEtcdServers, testEtcdUsername, testEtcdPassword) 33 | watcher := client.Watch("q11", 0, true) 34 | ctx, cancel := context.WithCancel(context.Background()) 35 | go func() { 36 | time.Sleep(10 * time.Second) 37 | cancel() 38 | }() 39 | 40 | for { 41 | rsp, err := watcher.Next(ctx) 42 | if err != nil { 43 | if err == context.Canceled { 44 | fmt.Println("watch canceled") 45 | return 46 | } else { 47 | time.Sleep(5 * time.Second) 48 | } 49 | continue 50 | } 51 | fmt.Println(rsp.Action, rsp.Node.Key, rsp.Node.Value) 52 | } 53 | } 54 | 55 | func TestEqualSesssion(t *testing.T) { 56 | var new TopicLeaderSession 57 | new.LeaderNode = &NsqdNodeInfo{} 58 | v, _ := json.Marshal(new) 59 | var old TopicLeaderSession 60 | json.Unmarshal(v, &old) 61 | if *new.LeaderNode != *old.LeaderNode { 62 | t.Errorf("should equal: %v, %v", new, old) 63 | } 64 | if !new.IsSame(&old) { 65 | t.Errorf("should equal: %v, %v", new, old) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /nsqd/in_flight_pqueue_test.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sort" 7 | "testing" 8 | ) 9 | 10 | func TestPriorityQueue(t *testing.T) { 11 | c := 100 12 | pq := newInFlightPqueue(c) 13 | 14 | for i := 0; i < c+1; i++ { 15 | pq.Push(&Message{belongedConsumer: NewFakeConsumer(int64(i)), pri: int64(i)}) 16 | } 17 | equal(t, len(pq), c+1) 18 | equal(t, cap(pq), c*2) 19 | 20 | for i := 0; i < c+1; i++ { 21 | msg := pq.Pop() 22 | equal(t, msg.GetClientID(), int64(i)) 23 | } 24 | equal(t, cap(pq), c/4) 25 | } 26 | 27 | func TestUnsortedInsert(t *testing.T) { 28 | c := 100 29 | pq := newInFlightPqueue(c) 30 | ints := make([]int, 0, c) 31 | 32 | for i := 0; i < c; i++ { 33 | v := rand.Int() 34 | ints = append(ints, v) 35 | pq.Push(&Message{pri: int64(v)}) 36 | } 37 | equal(t, len(pq), c) 38 | equal(t, cap(pq), c) 39 | 40 | sort.Sort(sort.IntSlice(ints)) 41 | 42 | for i := 0; i < c; i++ { 43 | msg, _ := pq.PeekAndShift(int64(ints[len(ints)-1])) 44 | equal(t, msg.pri, int64(ints[i])) 45 | } 46 | } 47 | 48 | func TestRemove(t *testing.T) { 49 | c := 100 50 | pq := newInFlightPqueue(c) 51 | 52 | msgs := make(map[MessageID]*Message) 53 | for i := 0; i < c; i++ { 54 | m := &Message{pri: int64(rand.Intn(100000000))} 55 | m.ID = MessageID(m.pri) 56 | msgs[m.ID] = m 57 | pq.Push(m) 58 | } 59 | 60 | for i := 0; i < 10; i++ { 61 | idx := rand.Intn((c - 1) - i) 62 | var fm *Message 63 | for _, m := range msgs { 64 | if m.index == idx { 65 | fm = m 66 | break 67 | } 68 | } 69 | rm := pq.Remove(idx) 70 | equal(t, fmt.Sprintf("%v", fm.ID), fmt.Sprintf("%v", rm.ID)) 71 | } 72 | 73 | lastPriority := pq.Pop().pri 74 | for i := 0; i < (c - 10 - 1); i++ { 75 | msg := pq.Pop() 76 | equal(t, lastPriority <= msg.pri, true) 77 | lastPriority = msg.pri 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /internal/test/assertions.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "path/filepath" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | func Assert(t *testing.T, condition bool, msg string, v ...interface{}) { 11 | if !condition { 12 | _, file, line, _ := runtime.Caller(1) 13 | t.Logf("\033[31m%s:%d: "+msg+"\033[39m\n\n", 14 | append([]interface{}{filepath.Base(file), line}, v...)...) 15 | t.FailNow() 16 | } 17 | } 18 | 19 | func Equal(t *testing.T, expected, actual interface{}) { 20 | if !reflect.DeepEqual(expected, actual) { 21 | _, file, line, _ := runtime.Caller(1) 22 | t.Logf("\033[31m%s:%d:\n\n\t %#v (expected)\n\n\t!= %#v (actual)\033[39m\n\n", 23 | filepath.Base(file), line, expected, actual) 24 | t.FailNow() 25 | } 26 | } 27 | 28 | func NotEqual(t *testing.T, expected, actual interface{}) { 29 | if reflect.DeepEqual(expected, actual) { 30 | _, file, line, _ := runtime.Caller(1) 31 | t.Logf("\033[31m%s:%d:\n\n\tvalue should not equal %#v\033[39m\n\n", 32 | filepath.Base(file), line, actual) 33 | t.FailNow() 34 | } 35 | } 36 | 37 | func Nil(t *testing.T, object interface{}) { 38 | if !isNil(object) { 39 | _, file, line, _ := runtime.Caller(1) 40 | t.Logf("\033[31m%s:%d:\n\n\t (expected)\n\n\t!= %#v (actual)\033[39m\n\n", 41 | filepath.Base(file), line, object) 42 | t.FailNow() 43 | } 44 | } 45 | 46 | func NotNil(t *testing.T, object interface{}) { 47 | if isNil(object) { 48 | _, file, line, _ := runtime.Caller(1) 49 | t.Logf("\033[31m%s:%d:\n\n\tExpected value not to be \033[39m\n\n", 50 | filepath.Base(file), line) 51 | t.FailNow() 52 | } 53 | } 54 | 55 | func isNil(object interface{}) bool { 56 | if object == nil { 57 | return true 58 | } 59 | 60 | value := reflect.ValueOf(object) 61 | kind := value.Kind() 62 | if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { 63 | return true 64 | } 65 | 66 | return false 67 | } 68 | -------------------------------------------------------------------------------- /nsqadmin/static/js/router.js: -------------------------------------------------------------------------------- 1 | var Backbone = require('backbone'); 2 | 3 | var Pubsub = require('./lib/pubsub'); 4 | 5 | 6 | var Router = Backbone.Router.extend({ 7 | routes: { 8 | '': 'topics', 9 | 'topics/(:topic)(/:channel)': 'topic', 10 | 'lookup': 'lookup', 11 | 'nodes(/:node)': 'nodes', 12 | 'counter': 'counter', 13 | 'statistics(/:filter)': 'statistics', 14 | 'search': 'search' 15 | }, 16 | 17 | defaultRoute: 'topics', 18 | 19 | initialize: function() { 20 | this.currentRoute = this.defaultRoute; 21 | this.listenTo(this, 'route', function(route, params) { 22 | this.currentRoute = route || this.defaultRoute; 23 | // console.log('Route: %o; params: %o', route, params); 24 | }); 25 | }, 26 | 27 | start: function() { 28 | Backbone.history.start({ 29 | 'pushState': true 30 | }); 31 | }, 32 | 33 | topics: function() { 34 | Pubsub.trigger('topics:show'); 35 | }, 36 | 37 | topic: function(topic, channel) { 38 | if (channel !== null) { 39 | Pubsub.trigger('channel:show', topic, channel); 40 | return; 41 | } 42 | Pubsub.trigger('topic:show', topic); 43 | }, 44 | 45 | lookup: function() { 46 | Pubsub.trigger('lookup:show'); 47 | }, 48 | 49 | nodes: function(node) { 50 | if (node !== null) { 51 | Pubsub.trigger('node:show', node); 52 | return; 53 | } 54 | Pubsub.trigger('nodes:show'); 55 | }, 56 | 57 | counter: function() { 58 | Pubsub.trigger('counter:show'); 59 | }, 60 | 61 | search: function() { 62 | Pubsub.trigger('search:show'); 63 | }, 64 | 65 | statistics: function() { 66 | Pubsub.trigger('statistics:show'); 67 | } 68 | }); 69 | 70 | 71 | module.exports = new Router(); 72 | -------------------------------------------------------------------------------- /nsqadmin/static/js/app_state.js: -------------------------------------------------------------------------------- 1 | var Backbone = require('backbone'); 2 | var _ = require('underscore'); 3 | 4 | var AppState = Backbone.Model.extend({ 5 | defaults: function() { 6 | return { 7 | 'VERSION': VERSION, 8 | 'GRAPHITE_URL': GRAPHITE_URL, 9 | 'GRAPH_ENABLED': GRAPH_ENABLED, 10 | 'STATSD_INTERVAL': STATSD_INTERVAL, 11 | 'USE_STATSD_PREFIXES': USE_STATSD_PREFIXES, 12 | 'STATSD_COUNTER_FORMAT': STATSD_COUNTER_FORMAT, 13 | 'STATSD_GAUGE_FORMAT': STATSD_GAUGE_FORMAT, 14 | 'STATSD_PREFIX': STATSD_PREFIX, 15 | 'NSQLOOKUPD': NSQLOOKUPD, 16 | 'ALLNSQLOOKUPDS': ALLNSQLOOKUPDS, 17 | 'DCNSQLOOKUPD' : DCNSQLOOKUPD, 18 | 'DCALLNSQLOOKUPDS': DCALLNSQLOOKUPDS, 19 | 'graph_interval': '2h', 20 | 'AUTH_URL' : AUTH_URL, 21 | 'LOGOUT_URL' : LOGOUT_URL, 22 | 'LOGIN' : LOGIN, 23 | 'USER' : USER, 24 | 'AUTH_ENABLED' : AUTH_ENABLED, 25 | 'HAS_ENDPOINT' : HAS_ENDPOINT, 26 | 'ENABLE_ZAN_TEST_SKIP' : ENABLE_ZAN_TEST_SKIP, 27 | }; 28 | }, 29 | 30 | initialize: function() { 31 | this.on('change:graph_interval', function(model, v) { 32 | localStorage.setItem('graph_interval', v); 33 | }); 34 | 35 | var qp = _.object(_.compact(_.map(window.location.search.slice(1).split('&'), 36 | function(item) { if (item) { return item.split('='); } }))); 37 | 38 | var def = this.get('GRAPH_ENABLED') ? '2h' : 'off'; 39 | var interval = qp['t'] || localStorage.getItem('graph_interval') || def; 40 | this.set('graph_interval', interval); 41 | }, 42 | 43 | url: function(url) { 44 | return '/api' + url; 45 | } 46 | }); 47 | 48 | var appState = new AppState(); 49 | 50 | window.AppState = appState; 51 | 52 | module.exports = appState; 53 | -------------------------------------------------------------------------------- /nsqdserver/test/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIOnMHjZ/OtboCAggA 3 | MB0GCWCGSAFlAwQBKgQQraghngsT2Jfh2Jl4Bs8LrASCBNAbx/YlZvCgcV9zSkLw 4 | 8kBFtB8+Livnv5BoI0S2p3XddAManDoErdRh2LoAOtNTCDEuiJ3oCHy/DZ1eaDhv 5 | qXJ4fc2JfA5qIMOHmFiCE3fbVoy47wurggcBw7tLAKoAg+Jo2R9j7k++rZeKn6p5 6 | KBHbZpfQyVLczJktwm2l/LLc85tGfhQvgWlnPHg2GFhETmwNvGxGruyelPJizHG0 7 | 6wmAn9eOMycM9YrmSQ6VfMtxza110nPCk0yyjqnDnRgc8wFmMohbXUxnz3HZszhb 8 | /2TRcYACS4Lk0/u0LRWFMLH1IfWd0L0SASL1oDXzMPVCp5vbGSIThZAJGe4ZFel3 9 | rp56A2dX+aXxzZ1FK8CaPsZpHCyv7j/4wxW1M186Yh32QfyhT8tT2KLY2lSepdN2 10 | bPHcXq7hkfJ4xMmWt5qlzOSeDDt3/Z2pHQaQ/Add+JtFZnfvY3QodwnPTe9dQugi 11 | bGQ/UA0SvcWnyLRBznP2fibHAWN/NjNBcMKH9UX8eE4zMx+rbOXaPUqcSkkGquGT 12 | GlBQEVe+9GqbhHRENylgGflDH0PhWQOTOjeFeVKTRA08Zrc2nM55MJgPoou4RE+3 13 | LnFajLFSgUYQOTqkfrOAnNdtS6Aww6agRZYWF3VhGFhK5A9kmwaB0qAr8YVFOhiV 14 | q+BW5bQ72y9jnhqe8MRJM0nrVXmbjd66WIa0jyHpDWoosKF3CvTvQYgrw++p5QS7 15 | b3TVeL0+Gf0EuxD581WVkQz8NK2SnwGIaXN9Aw6bSsKGsUbJwsdThrN4q8D2OPeH 16 | gMCPRmFfFloRdqtK7O7MdtOZ4f7cQ1CpEDLlHNXT/8ULEZJWB1lFy75k9NAzG2Cv 17 | 0fYbX6pIRX90WmfuwXRfHz4ycD/40QCRk+662YGYWXPuJu+anNugzmvqP40QYx3X 18 | cvHk1t0JnwfSUUROzUE38i4VhNffG4mAE5N7mJkmCPr0fDiVu+pOLJkICxi2k4iV 19 | /6qt4XOlWxBAkKnd7McubMvhrZXQEQl2kC/Z0DvJWz+9upkC5KiCrWonr1bwfPWz 20 | 6TV1p2A41fGLTr0UFmYihHLab1G/a+xRqwKpZDK9mVpc9RitOYwH5xaV/rG1h1ea 21 | arH5hKXm33YLBZpyGdquFp23pExtfamLcGpbh6W+iTFeOi5VwT5a7HIHcH2I2j9k 22 | wH1Kb2q0sw2tfmvMV43BsS3JVjpIhrVHEhbSD0ZzmDesjakRwF19X0J9drB0nYO3 23 | Z58uozNLZv1tIp1N4jCma5qirBrkwD8xnq4NdKAKJUlzgDt3RJAbCZMstjlnk+/J 24 | iVXAeeoTlioPnBbga/8ZfN2EUe+EQkuzW1z6ZXZBiYf0++MGCiYYnabOapwKBu08 25 | W3b4/ok5TQlaFeNeBjqSfoKRW6IquwYk0IollBIV2JQ28hIfzr2QwLxESz7CaJa/ 26 | X6qK1RXummwVvoY8rdHHgBmSW29Hu0MEhjlB87UM++U9gD/mQjxp8k4Hb5NfBgjz 27 | UkrPM83JE4wThaJ0WLIYeToHaTJbT8ZyVVgNS2aUK/dWurC35/lhbkC35n7/Z+i4 28 | msUUpETqkQlxffy/Kd9Vc+DfIOYVON/j+kazPGV7xj2q/FxdesmpqSMVdcyR8DQ9 29 | uMgajFUlQJVi0YglX3L5oe0XWQ== 30 | -----END ENCRYPTED PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /nsqadmin/static/js/models/rank.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var AppState = require('../app_state'); 4 | var Backbone = require('backbone'); 5 | 6 | var Rank = Backbone.Model.extend({ 7 | idAttribute: 'filter', 8 | 9 | constructor: function Rank() { 10 | Backbone.Model.prototype.constructor.apply(this, arguments); 11 | }, 12 | 13 | url: function() { 14 | return AppState.url('/statistics/' + encodeURIComponent(this.get('filter'))); 15 | }, 16 | 17 | parse: function(resp) { 18 | var filter = this.get('filter'); 19 | resp['top10'] = _.map(resp['top10'], function(data) { 20 | switch (filter) { 21 | case 'channel-depth': 22 | data['rank_value'] = data['total_channel_depth']; 23 | break; 24 | case 'hourly-pubsize': 25 | data['rank_value'] = data['hourly_pubsize']; 26 | break; 27 | case 'channel-requeue': 28 | data['name'] = data['name'].replace(':', '/'); 29 | data['rank_value'] = data['requeue_count']; 30 | break; 31 | case 'channel-timeout': 32 | data['name'] = data['name'].replace(':', '/'); 33 | data['rank_value'] = data['timeout_count']; 34 | break; 35 | case 'channel-delayedqueue': 36 | data['name'] = data['name'].replace(':', '/'); 37 | data['rank_value'] = data['delayed_queue_count']; 38 | break; 39 | default: 40 | data['rank_value'] = data['message_count']; 41 | } 42 | return data; 43 | }); 44 | resp['top10'] = _.filter(resp['top10'], function(data) { 45 | return (null !== data['rank_value'] && data['rank_value'] > 0); 46 | }); 47 | return resp; 48 | } 49 | }); 50 | 51 | module.exports = Rank; -------------------------------------------------------------------------------- /nsqd/buffer_pool.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io" 7 | "sync" 8 | ) 9 | 10 | var bp sync.Pool 11 | var peekPool sync.Pool 12 | 13 | func init() { 14 | bp.New = func() interface{} { 15 | return &bytes.Buffer{} 16 | } 17 | peekPool.New = func() interface{} { 18 | return make([]Message, MaxWaitingDelayed) 19 | } 20 | } 21 | 22 | func bufferPoolGet() *bytes.Buffer { 23 | return bp.Get().(*bytes.Buffer) 24 | } 25 | 26 | func bufferPoolPut(b *bytes.Buffer) { 27 | b.Reset() 28 | bp.Put(b) 29 | } 30 | 31 | func peekBufPoolGet() []Message { 32 | return peekPool.Get().([]Message) 33 | } 34 | 35 | func peekBufPoolPut(b []Message) { 36 | peekPool.Put(b) 37 | } 38 | 39 | var ( 40 | bufioReaderPool sync.Pool 41 | bufioWriter2kPool sync.Pool 42 | bufioWriter4kPool sync.Pool 43 | bufioWriter8kPool sync.Pool 44 | bufioSmallWriterPool sync.Pool 45 | ) 46 | 47 | func bufioWriterPool(size int) *sync.Pool { 48 | switch size { 49 | case 100: 50 | return &bufioSmallWriterPool 51 | case 2 << 10: 52 | return &bufioWriter2kPool 53 | case 4 << 10: 54 | return &bufioWriter4kPool 55 | case 8 << 10: 56 | return &bufioWriter8kPool 57 | } 58 | return nil 59 | } 60 | 61 | func NewBufioReader(r io.Reader) *bufio.Reader { 62 | if v := bufioReaderPool.Get(); v != nil { 63 | br := v.(*bufio.Reader) 64 | br.Reset(r) 65 | return br 66 | } 67 | return bufio.NewReader(r) 68 | } 69 | 70 | func PutBufioReader(br *bufio.Reader) { 71 | br.Reset(nil) 72 | bufioReaderPool.Put(br) 73 | } 74 | 75 | func newBufioWriterSize(w io.Writer, size int) *bufio.Writer { 76 | pool := bufioWriterPool(size) 77 | if pool != nil { 78 | if v := pool.Get(); v != nil { 79 | bw := v.(*bufio.Writer) 80 | bw.Reset(w) 81 | return bw 82 | } 83 | } 84 | return bufio.NewWriterSize(w, size) 85 | } 86 | 87 | func putBufioWriter(bw *bufio.Writer) { 88 | bw.Reset(nil) 89 | if pool := bufioWriterPool(bw.Available()); pool != nil { 90 | pool.Put(bw) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /internal/test/fakes.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type FakeNetConn struct { 9 | ReadFunc func([]byte) (int, error) 10 | WriteFunc func([]byte) (int, error) 11 | CloseFunc func() error 12 | LocalAddrFunc func() net.Addr 13 | RemoteAddrFunc func() net.Addr 14 | SetDeadlineFunc func(time.Time) error 15 | SetReadDeadlineFunc func(time.Time) error 16 | SetWriteDeadlineFunc func(time.Time) error 17 | } 18 | 19 | func (f FakeNetConn) Read(b []byte) (int, error) { return f.ReadFunc(b) } 20 | func (f FakeNetConn) Write(b []byte) (int, error) { return f.WriteFunc(b) } 21 | func (f FakeNetConn) Close() error { return f.CloseFunc() } 22 | func (f FakeNetConn) LocalAddr() net.Addr { return f.LocalAddrFunc() } 23 | func (f FakeNetConn) RemoteAddr() net.Addr { return f.RemoteAddrFunc() } 24 | func (f FakeNetConn) SetDeadline(t time.Time) error { return f.SetDeadlineFunc(t) } 25 | func (f FakeNetConn) SetReadDeadline(t time.Time) error { return f.SetReadDeadlineFunc(t) } 26 | func (f FakeNetConn) SetWriteDeadline(t time.Time) error { return f.SetWriteDeadlineFunc(t) } 27 | 28 | type fakeNetAddr struct{} 29 | 30 | func (fakeNetAddr) Network() string { return "" } 31 | func (fakeNetAddr) String() string { return "" } 32 | 33 | func NewFakeNetConn() FakeNetConn { 34 | netAddr := fakeNetAddr{} 35 | return FakeNetConn{ 36 | ReadFunc: func(b []byte) (int, error) { return 0, nil }, 37 | WriteFunc: func(b []byte) (int, error) { return len(b), nil }, 38 | CloseFunc: func() error { return nil }, 39 | LocalAddrFunc: func() net.Addr { return netAddr }, 40 | RemoteAddrFunc: func() net.Addr { return netAddr }, 41 | SetDeadlineFunc: func(time.Time) error { return nil }, 42 | SetWriteDeadlineFunc: func(time.Time) error { return nil }, 43 | SetReadDeadlineFunc: func(time.Time) error { return nil }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/nsq_to_file/strftime.go: -------------------------------------------------------------------------------- 1 | // COPIED FROM https://github.com/jehiah/go-strftime 2 | package main 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | // taken from time/format.go 9 | var conversion = map[string]string{ 10 | /*stdLongMonth */ "B": "January", 11 | /*stdMonth */ "b": "Jan", 12 | // stdNumMonth */ "m": "1", 13 | /*stdZeroMonth */ "m": "01", 14 | /*stdLongWeekDay */ "A": "Monday", 15 | /*stdWeekDay */ "a": "Mon", 16 | // stdDay */ "d": "2", 17 | // stdUnderDay */ "d": "_2", 18 | /*stdZeroDay */ "d": "02", 19 | /*stdHour */ "H": "15", 20 | // stdHour12 */ "I": "3", 21 | /*stdZeroHour12 */ "I": "03", 22 | // stdMinute */ "M": "4", 23 | /*stdZeroMinute */ "M": "04", 24 | // stdSecond */ "S": "5", 25 | /*stdZeroSecond */ "S": "05", 26 | /*stdLongYear */ "Y": "2006", 27 | /*stdYear */ "y": "06", 28 | /*stdPM */ "p": "PM", 29 | // stdpm */ "p": "pm", 30 | /*stdTZ */ "Z": "MST", 31 | // stdISO8601TZ */ "z": "Z0700", // prints Z for UTC 32 | // stdISO8601ColonTZ */ "z": "Z07:00", // prints Z for UTC 33 | /*stdNumTZ */ "z": "-0700", // always numeric 34 | // stdNumShortTZ */ "b": "-07", // always numeric 35 | // stdNumColonTZ */ "b": "-07:00", // always numeric 36 | "%": "%", 37 | } 38 | 39 | // This is an alternative to time.Format because no one knows 40 | // what date 040305 is supposed to create when used as a 'layout' string 41 | // this takes standard strftime format options. For a complete list 42 | // of format options see http://strftime.org/ 43 | func strftime(format string, t time.Time) string { 44 | layout := "" 45 | length := len(format) 46 | for i := 0; i < length; i++ { 47 | if format[i] == '%' && i <= length-2 { 48 | if layoutCmd, ok := conversion[format[i+1:i+2]]; ok { 49 | layout = layout + layoutCmd 50 | i++ 51 | continue 52 | } 53 | } 54 | layout = layout + format[i:i+1] 55 | } 56 | return t.Format(layout) 57 | } 58 | -------------------------------------------------------------------------------- /internal/http_api/topic_channel_args.go: -------------------------------------------------------------------------------- 1 | package http_api 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/youzan/nsq/internal/protocol" 7 | "strconv" 8 | ) 9 | 10 | type getter interface { 11 | Get(key string) string 12 | } 13 | 14 | func GetTopicArg(rp getter) (string, error) { 15 | topicName := rp.Get("topic") 16 | if topicName == "" { 17 | return "", errors.New("MISSING_ARG_TOPIC") 18 | } 19 | 20 | if !protocol.IsValidTopicName(topicName) { 21 | return "", errors.New("INVALID_ARG_TOPIC") 22 | } 23 | 24 | return topicName, nil 25 | } 26 | 27 | func GetTopicChannelArgs(rp getter) (string, string, error) { 28 | topicName, err := GetTopicArg(rp) 29 | if err != nil { 30 | return "", "", err 31 | } 32 | 33 | channelName := rp.Get("channel") 34 | if channelName == "" { 35 | return "", "", errors.New("MISSING_ARG_CHANNEL") 36 | } 37 | 38 | if !protocol.IsValidChannelName(channelName) { 39 | return "", "", errors.New("INVALID_ARG_CHANNEL") 40 | } 41 | 42 | return topicName, channelName, nil 43 | } 44 | 45 | // partition can be missing, default as 0. 46 | func GetTopicPartitionArgs(rp getter) (string, int, error) { 47 | topicName, err := GetTopicArg(rp) 48 | if err != nil { 49 | return "", -1, err 50 | } 51 | 52 | topicPartStr := rp.Get("partition") 53 | topicPart := -1 54 | if topicPartStr == "" { 55 | topicPart = -1 56 | } else { 57 | var err error 58 | topicPart, err = strconv.Atoi(topicPartStr) 59 | if err != nil { 60 | return "", -1, err 61 | } 62 | } 63 | return topicName, topicPart, nil 64 | } 65 | 66 | func GetTopicPartitionChannelArgs(rp getter) (string, int, string, error) { 67 | topicName, topicPart, err := GetTopicPartitionArgs(rp) 68 | if err != nil { 69 | return "", -1, "", err 70 | } 71 | channelName := rp.Get("channel") 72 | if channelName == "" { 73 | return "", -1, "", errors.New("MISSING_ARG_CHANNEL") 74 | } 75 | 76 | if !protocol.IsValidChannelName(channelName) { 77 | return "", -1, "", errors.New("INVALID_ARG_CHANNEL") 78 | } 79 | 80 | return topicName, topicPart, channelName, nil 81 | } 82 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr/local 2 | DESTDIR= 3 | PROJECT?=github.com/youzan/nsq 4 | BINDIR=${PREFIX}/bin 5 | COMMIT?=$(shell git rev-parse --short HEAD) 6 | BUILD_TIME?=$(shell date '+%Y-%m-%d_%H:%M:%S-%Z') 7 | GOFLAGS=-ldflags "-X ${PROJECT}/internal/version.Commit=${COMMIT} -X ${PROJECT}/internal/version.BuildTime=${BUILD_TIME}" 8 | 9 | BLDDIR = build 10 | EXT= 11 | ifeq (${GOOS},windows) 12 | EXT=.exe 13 | endif 14 | 15 | APPS = nsqd nsqlookupd nsqadmin nsq_pubsub nsq_to_nsq nsq_to_file nsq_to_http nsq_tail nsq_stat to_nsq nsq_data_tool nsqlookupd_migrate_proxy 16 | all: $(APPS) 17 | 18 | $(BLDDIR)/nsqd: $(wildcard apps/nsqd/*.go nsqd/*.go nsqdserver/*.go consistence/*.go internal/*/*.go) 19 | $(BLDDIR)/nsqlookupd: $(wildcard apps/nsqlookupd/*.go nsqlookupd/*.go consistence/*.go internal/*/*.go) 20 | $(BLDDIR)/nsqadmin: $(wildcard apps/nsqadmin/*.go nsqadmin/*.go nsqadmin/templates/*.go internal/*/*.go) 21 | $(BLDDIR)/nsq_pubsub: $(wildcard apps/nsq_pubsub/*.go internal/*/*.go) 22 | $(BLDDIR)/nsq_to_nsq: $(wildcard apps/nsq_to_nsq/*.go internal/*/*.go) 23 | $(BLDDIR)/nsq_to_file: $(wildcard apps/nsq_to_file/*.go internal/*/*.go) 24 | $(BLDDIR)/nsq_to_http: $(wildcard apps/nsq_to_http/*.go internal/*/*.go) 25 | $(BLDDIR)/nsq_tail: $(wildcard apps/nsq_tail/*.go internal/*/*.go) 26 | $(BLDDIR)/nsq_stat: $(wildcard apps/nsq_stat/*.go internal/*/*.go) 27 | $(BLDDIR)/to_nsq: $(wildcard apps/to_nsq/*.go internal/*/*.go) 28 | $(BLDDIR)/nsq_data_tool: $(wildcard apps/nsq_data_tool/*.go consistence/*.go nsqd/*.go internal/*/*.go) 29 | $(BLDDIR)/nsqlookupd_migrate_proxy: $(wildcard apps/nsqlookupd_migrate_proxy/*.go nsqlookupd_migrate/*.go) 30 | 31 | 32 | $(BLDDIR)/%: 33 | @mkdir -p $(dir $@) 34 | go build ${GOFLAGS} -o $@ ./apps/$* 35 | 36 | $(APPS): %: $(BLDDIR)/% 37 | 38 | clean: 39 | rm -fr $(BLDDIR) 40 | 41 | .PHONY: install clean all 42 | .PHONY: $(APPS) 43 | 44 | install: $(APPS) 45 | install -m 755 -d ${DESTDIR}${BINDIR} 46 | for APP in $^ ; do install -m 755 ${BLDDIR}/$$APP ${DESTDIR}${BINDIR}/$$APP${EXT} ; done 47 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and 4 | welcoming community, we pledge to respect all people who contribute through reporting issues, 5 | posting feature requests, updating documentation, submitting pull requests or patches, and other 6 | activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for everyone, 9 | regardless of level of experience, gender, gender identity and expression, sexual orientation, 10 | disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 11 | 12 | Examples of unacceptable behavior by participants include: 13 | 14 | * The use of sexualized language or imagery 15 | * Personal attacks 16 | * Trolling or insulting/derogatory comments 17 | * Public or private harassment 18 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 19 | * Other unethical or unprofessional conduct. 20 | 21 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, 22 | code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By 23 | adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently 24 | applying these principles to every aspect of managing this project. Project maintainers who do not 25 | follow or enforce the Code of Conduct may be permanently removed from the project team. 26 | 27 | This code of conduct applies both within project spaces and in public spaces when an individual is 28 | representing the project or its community. 29 | 30 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an 31 | issue or contacting one or more of the project maintainers. 32 | 33 | This Code of Conduct is adapted from the [Contributor Covenant][1], version 1.2.0, available at 34 | [http://contributor-covenant.org/version/1/2/0/][2]. 35 | 36 | [1]: http://contributor-covenant.org 37 | [2]: http://contributor-covenant.org/version/1/2/0/ 38 | -------------------------------------------------------------------------------- /nsqadmin/static/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nsqadmin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /nsqlookupd/options.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | 8 | "github.com/youzan/nsq/internal/levellogger" 9 | ) 10 | 11 | type Options struct { 12 | Verbose bool `flag:"verbose"` 13 | 14 | TCPAddress string `flag:"tcp-address"` 15 | HTTPAddress string `flag:"http-address"` 16 | MetricAddress string `flag:"metric-address"` 17 | RPCPort string `flag:"rpc-port"` 18 | BroadcastAddress string `flag:"broadcast-address"` 19 | BroadcastInterface string `flag:"broadcast-interface"` 20 | 21 | ReverseProxyPort string `flag:"reverse-proxy-port"` 22 | 23 | ClusterID string `flag:"cluster-id"` 24 | ClusterLeadershipAddresses string `flag:"cluster-leadership-addresses" cfg:"cluster_leadership_addresses"` 25 | ClusterLeadershipUsername string `flag:"cluster-leadership-username" cfg:"cluster_leadership_username"` 26 | ClusterLeadershipPassword string `flag:"cluster-leadership-password" cfg:"cluster_leadership_password"` 27 | ClusterLeadershipRootDir string `flag:"cluster-leadership-root-dir" cfg:"cluster_leadership_root_dir"` 28 | 29 | InactiveProducerTimeout time.Duration `flag:"inactive-producer-timeout"` 30 | NsqdPingTimeout time.Duration `flag:"nsqd-ping-timeout"` 31 | BalanceInterval []string `flag:"balance-interval"` 32 | AllowWriteWithNoChannels bool `flag:"allow-write-with-nochannels"` 33 | 34 | LogLevel int32 `flag:"log-level" cfg:"log_level"` 35 | LogDir string `flag:"log-dir" cfg:"log_dir"` 36 | Logger levellogger.Logger 37 | } 38 | 39 | func NewOptions() *Options { 40 | hostname, err := os.Hostname() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | return &Options{ 46 | TCPAddress: "0.0.0.0:4160", 47 | HTTPAddress: "0.0.0.0:4161", 48 | BroadcastAddress: hostname, 49 | BroadcastInterface: "eth0", 50 | 51 | ClusterLeadershipAddresses: "", 52 | ClusterID: "nsq-clusterid-test-only", 53 | 54 | InactiveProducerTimeout: 60 * time.Second, 55 | NsqdPingTimeout: 15 * time.Second, 56 | 57 | LogLevel: 1, 58 | LogDir: "", 59 | Logger: &levellogger.GLogger{}, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /nsqd/in_flight_pqueue.go: -------------------------------------------------------------------------------- 1 | package nsqd 2 | 3 | type inFlightPqueue []*Message 4 | 5 | func newInFlightPqueue(capacity int) inFlightPqueue { 6 | return make(inFlightPqueue, 0, capacity) 7 | } 8 | 9 | func (pq inFlightPqueue) Swap(i, j int) { 10 | pq[i], pq[j] = pq[j], pq[i] 11 | pq[i].index = i 12 | pq[j].index = j 13 | } 14 | 15 | func (pq *inFlightPqueue) Push(x *Message) { 16 | n := len(*pq) 17 | c := cap(*pq) 18 | if n+1 > c { 19 | npq := make(inFlightPqueue, n, c*2) 20 | copy(npq, *pq) 21 | *pq = npq 22 | } 23 | *pq = (*pq)[0 : n+1] 24 | x.index = n 25 | (*pq)[n] = x 26 | pq.up(n) 27 | } 28 | 29 | func (pq *inFlightPqueue) Pop() *Message { 30 | n := len(*pq) 31 | c := cap(*pq) 32 | pq.Swap(0, n-1) 33 | pq.down(0, n-1) 34 | if n < (c/2) && c > 25 { 35 | npq := make(inFlightPqueue, n, c/2) 36 | copy(npq, *pq) 37 | *pq = npq 38 | } 39 | x := (*pq)[n-1] 40 | x.index = -1 41 | *pq = (*pq)[0 : n-1] 42 | return x 43 | } 44 | 45 | func (pq *inFlightPqueue) Remove(i int) *Message { 46 | n := len(*pq) 47 | if n-1 != i { 48 | pq.Swap(i, n-1) 49 | pq.down(i, n-1) 50 | pq.up(i) 51 | } 52 | x := (*pq)[n-1] 53 | x.index = -1 54 | *pq = (*pq)[0 : n-1] 55 | return x 56 | } 57 | 58 | func (pq *inFlightPqueue) PeekAndShift(max int64) (*Message, int64) { 59 | if len(*pq) == 0 { 60 | return nil, 0 61 | } 62 | 63 | x := (*pq)[0] 64 | if x.pri > max { 65 | return nil, x.pri - max 66 | } 67 | pq.Pop() 68 | 69 | return x, 0 70 | } 71 | 72 | func (pq *inFlightPqueue) up(j int) { 73 | for { 74 | i := (j - 1) / 2 // parent 75 | if i == j || (*pq)[j].pri >= (*pq)[i].pri { 76 | break 77 | } 78 | pq.Swap(i, j) 79 | j = i 80 | } 81 | } 82 | 83 | func (pq *inFlightPqueue) down(i, n int) { 84 | for { 85 | j1 := 2*i + 1 86 | if j1 >= n || j1 < 0 { // j1 < 0 after int overflow 87 | break 88 | } 89 | j := j1 // left child 90 | if j2 := j1 + 1; j2 < n && (*pq)[j1].pri >= (*pq)[j2].pri { 91 | j = j2 // = 2*i + 2 // right child 92 | } 93 | if (*pq)[j].pri >= (*pq)[i].pri { 94 | break 95 | } 96 | pq.Swap(i, j) 97 | i = j 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /nsqlookupd_migrate/migrate_config.go: -------------------------------------------------------------------------------- 1 | package nsqlookupd_migrate 2 | 3 | import ( 4 | "strings" 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | ) 9 | 10 | type MigrateConfig struct { 11 | App string 12 | Key string 13 | ReqType string 14 | Env string 15 | Switches map[string]int 16 | } 17 | 18 | var cnfLog *MigrateLogger 19 | var keyFormat = "%s.to.%s" 20 | var appFormat = "nsqlookupd.migrate" 21 | var typeFormat = "COMBINATION" 22 | var IP_REGEXP = "(\\d{1,3}\\.){3}\\d{1,3}(:\\d{1,5})?" 23 | func parseClusterName(lookupdAddr string) string { 24 | match, _ := regexp.MatchString(IP_REGEXP, lookupdAddr) 25 | if match == true { 26 | re := regexp.MustCompile(IP_REGEXP) 27 | return re.FindString(lookupdAddr) 28 | } else { 29 | return strings.Split(strings.Split(lookupdAddr, ".")[0], "http://")[1] 30 | } 31 | } 32 | 33 | func initKey(originLookupd, targetLookupd, key_in_cnf string) (string, error) { 34 | var hostOri, hostTar string 35 | if key_in_cnf != "" { 36 | cnfLog.Info("key in config applied: %v", key_in_cnf) 37 | return key_in_cnf, nil 38 | } 39 | if hostOri = parseClusterName(originLookupd); hostOri == "" { 40 | msg := fmt.Sprintf("Fail to parse NSQ cluster id from %v.", originLookupd) 41 | cnfLog.Error(msg) 42 | return "", errors.New(msg) 43 | } 44 | 45 | if hostTar = parseClusterName(targetLookupd); hostTar == "" { 46 | msg := fmt.Sprintf("Fail to parse NSQ cluster id from %v.", targetLookupd) 47 | cnfLog.Error(msg) 48 | return "", errors.New(msg) 49 | } 50 | 51 | return fmt.Sprintf(keyFormat, hostOri, hostTar), nil 52 | } 53 | 54 | /** 55 | init and answer a new migrate config with no topic switches 56 | */ 57 | func NewMigrateConfig(context *Context) (*MigrateConfig, error) { 58 | cnfLog = context.Logger 59 | app := appFormat 60 | key, err := initKey(context.LookupAddrOri, context.LookupAddrTar, context.Migrate_key) 61 | if err != nil { 62 | return nil, err 63 | } 64 | typeVal := typeFormat 65 | cnfLog.Info("New migrate config initialized: %v, %v, %v, %v", app, key, typeVal, context.Env) 66 | return &MigrateConfig{ 67 | App: app, 68 | Key: key, 69 | ReqType: typeVal, 70 | Env: context.Env, 71 | Switches: make(map[string]int), 72 | }, nil 73 | } 74 | 75 | -------------------------------------------------------------------------------- /nsqadmin/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | // Rule docs: http://eslint.org/docs/rules/ 6 | "rules": { 7 | "block-scoped-var": [1], 8 | "brace-style": [1, "1tbs", {"allowSingleLine": true}], 9 | "camelcase": [1], 10 | "comma-spacing": [1], 11 | "comma-style": [1], 12 | "computed-property-spacing": [1, "never"], 13 | "consistent-return": [1], 14 | "consistent-this": [1, "self"], 15 | "curly": [2], 16 | "dot-notation": [0], 17 | "eol-last": [1], 18 | "eqeqeq": [1], 19 | "indent": [1, 4], 20 | "key-spacing": [1], 21 | "max-len": [1, 100], 22 | "max-nested-callbacks": [2, 3], // ???? 23 | "new-cap": [1], 24 | "new-parens": [1], 25 | "no-caller": [2], 26 | "no-console": [0], 27 | "no-eval": [2], 28 | "no-extend-native": [2], 29 | "no-extra-bind": [1], 30 | "no-floating-decimal": [1], 31 | "no-iterator": [1], 32 | "no-lone-blocks": [1], 33 | "no-lonely-if": [1], 34 | "no-mixed-requires": [0], 35 | "no-mixed-spaces-and-tabs": [1], 36 | "no-multi-spaces": [1], 37 | "no-multi-str": [1], 38 | "no-multiple-empty-lines": [2, {"max": 2}], 39 | "no-native-reassign": [1], 40 | "no-new": [0], 41 | "no-redeclare": [1], 42 | "no-shadow": [1], 43 | "no-spaced-func": [1], 44 | "no-throw-literal": [1], 45 | "no-trailing-spaces": [1], 46 | "no-undef": [1], 47 | "no-underscore-dangle": [0], 48 | "no-unneeded-ternary": [1], 49 | "no-unused-vars": [1], 50 | "no-use-before-define": [1, "nofunc"], 51 | "no-with": [2], 52 | "one-var": [1, "never"], 53 | "quotes": [1, "single"], 54 | "radix": [1], 55 | "semi": [1, "always"], 56 | "semi-spacing": [1], 57 | "space-after-function-name": [1, "never"], 58 | "space-after-keywords": [1, "always"], 59 | "space-before-blocks": [1, "always"], 60 | "space-before-function-paren": [1, "never"], 61 | "space-in-parens": [1, "never"], 62 | "space-infix-ops": [1], 63 | "space-return-throw-case": [1], 64 | "space-unary-ops": [1], 65 | "strict": [0], 66 | "wrap-iife": [1] 67 | }, 68 | "globals": { 69 | "module": true, 70 | "require": true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /internal/ext/ext.go: -------------------------------------------------------------------------------- 1 | package ext 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | const ( 9 | E_EXT_NOT_SUPPORT = "E_EXT_NOT_SUPPORT" 10 | E_BAD_TAG = "E_BAD_TAG" 11 | E_INVALID_JSON_HEADER = "E_INVALID_JSON_HEADER" 12 | 13 | CLIENT_DISPATCH_TAG_KEY = "##client_dispatch_tag" 14 | TRACE_ID_KEY = "##trace_id" 15 | MaxExtLen = 65535 16 | ZAN_TEST_KEY = "zan_test" 17 | ) 18 | 19 | var MAX_TAG_LEN = 100 20 | var validTagFmt = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) 21 | 22 | type ExtVer uint8 23 | 24 | //ext versions 25 | // version for message has no ext 26 | var NO_EXT_VER = ExtVer(uint8(0)) 27 | 28 | // version for message has tag ext 29 | var TAG_EXT_VER = ExtVer(uint8(2)) 30 | 31 | // version for message has json header ext 32 | var JSON_HEADER_EXT_VER = ExtVer(uint8(4)) 33 | 34 | var noExt = NoExt{} 35 | 36 | type NoExt []byte 37 | 38 | func NewNoExt() NoExt { 39 | return noExt 40 | } 41 | 42 | func (n NoExt) ExtVersion() ExtVer { 43 | return NO_EXT_VER 44 | } 45 | 46 | func (n NoExt) GetBytes() []byte { 47 | return nil 48 | } 49 | 50 | type TagExt []byte 51 | 52 | func NewTagExt(tagName []byte) (TagExt, error) { 53 | if err := ValidateTag(string(tagName)); err != nil { 54 | return nil, err 55 | } 56 | return TagExt(tagName), nil 57 | } 58 | 59 | func (tag TagExt) GetTagName() string { 60 | return string(tag) 61 | } 62 | 63 | func ValidateTag(beValidated string) error { 64 | lenTag := len(beValidated) 65 | if lenTag > MAX_TAG_LEN { 66 | return fmt.Errorf("invalid tag len %v, exceed limit %v", lenTag, MAX_TAG_LEN) 67 | } 68 | if !validTagFmt.Match([]byte(beValidated)) { 69 | return fmt.Errorf("invalid tag %v", beValidated) 70 | } 71 | return nil 72 | } 73 | 74 | type JsonHeaderExt struct { 75 | bytes []byte 76 | } 77 | 78 | func NewJsonHeaderExt() *JsonHeaderExt { 79 | return &JsonHeaderExt{} 80 | } 81 | 82 | func (self *JsonHeaderExt) SetJsonHeaderBytes(jsonExtBytes []byte) { 83 | self.bytes = jsonExtBytes[0:] 84 | } 85 | 86 | func (self *JsonHeaderExt) ExtVersion() ExtVer { 87 | return JSON_HEADER_EXT_VER 88 | } 89 | 90 | func (self *JsonHeaderExt) GetBytes() []byte { 91 | return self.bytes 92 | } 93 | 94 | type IExtContent interface { 95 | ExtVersion() ExtVer 96 | GetBytes() []byte 97 | } 98 | -------------------------------------------------------------------------------- /apps/nsqlookupd_migrate_proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | migrate "github.com/youzan/nsq/nsqlookupd_migrate" 7 | 8 | "log" 9 | "github.com/BurntSushi/toml" 10 | "github.com/mreiferson/go-options" 11 | "os" 12 | "github.com/absolute8511/glog" 13 | "time" 14 | ) 15 | 16 | var ( 17 | flagSet = flag.NewFlagSet("nsqlookup_migrate", flag.ExitOnError) 18 | port = flagSet.String("http-address", "0.0.0.0:4161", ": to listen on for HTTP clients") 19 | testPort = flagSet.String("http-address-test", "0.0.0.0:4161", ": to listen on for HTTP tester") 20 | originalLookupdHttpAddr = flagSet.String("origin-lookupd-http", "http://0.0.0.0:4161", ": original lookupd to access to fetch lookup info.") 21 | targetLookupdHttpAddr = flagSet.String("target-lookupd-http", "http://0.0.0.0:4161", ": target lookupd to access to fetch lookup info.") 22 | env = flagSet.String("env", "", "env") 23 | log_level = flagSet.Int64("log-level", 2, "log level") 24 | config = flagSet.String("config", "", "path to config file") 25 | migrate_key = flagSet.String("migrate-key", "", "key for migrate switches, it not specified, migrate proxy try parse key from origin&target lookupd addresses.") 26 | log_dir = flagSet.String("log_dir", "", "dir for log files") 27 | ) 28 | 29 | func main() { 30 | glog.InitWithFlag(flagSet) 31 | flagSet.Parse(os.Args[1:]) 32 | var cfg map[string]interface{} 33 | if *config != "" { 34 | _, err := toml.DecodeFile(*config, &cfg) 35 | if err != nil { 36 | log.Fatalf("ERROR: failed to load config file %s - %s", *config, err.Error()) 37 | } 38 | } 39 | 40 | context := &migrate.Context{} 41 | options.Resolve(context, flagSet, cfg) 42 | log.Printf("origin lookup http address: %v", context.LookupAddrOri) 43 | log.Printf("target lookup http address: %v", context.LookupAddrTar) 44 | if context.LogDir != "" { 45 | glog.SetGLogDir(context.LogDir) 46 | log.Printf("log dir: %v", context.LogDir) 47 | } 48 | glog.StartWorker(time.Second * 2) 49 | 50 | httpServer, err := migrate.NewHTTPServer(context) 51 | if err != nil { 52 | panic(err) 53 | } 54 | log.Printf("http server listen on %v", context.ProxyHttpAddr) 55 | log.Fatal(http.ListenAndServe(context.ProxyHttpAddr, httpServer.Router)) 56 | } -------------------------------------------------------------------------------- /nsqadmin/static/js/models/topic.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var AppState = require('../app_state'); 3 | var Backbone = require('backbone'); 4 | 5 | var Topic = Backbone.Model.extend({ 6 | idAttribute: 'name', 7 | 8 | constructor: function Topic() { 9 | Backbone.Model.prototype.constructor.apply(this, arguments); 10 | }, 11 | 12 | url: function() { 13 | return AppState.url('/topics/' + encodeURIComponent(this.get('name'))); 14 | }, 15 | 16 | parse: function(response) { 17 | response['has_client_pub'] = false; 18 | response['nodes'] = _.map(response['nodes'] || [], function(node) { 19 | var nodeAddr = node['node']; 20 | var nodeParts = node['node'].split(':'); 21 | var port = nodeParts.pop(); 22 | var address = nodeParts.join(':'); 23 | var hostname = node['hostname']; 24 | node['show_broadcast_address'] = hostname.toLowerCase() !== address.toLowerCase(); 25 | node['hostname_port'] = hostname + ':' + port; 26 | node['client_pub_stats'] = _.map(node['client_pub_stats'] || [], function(pub_stats){ 27 | var date = new Date(pub_stats['last_pub_ts']*1000); 28 | pub_stats['last_pub_ts'] = date.toString(); 29 | return pub_stats; 30 | }); 31 | if(node['client_pub_stats'].length > 0) { 32 | response['has_client_pub'] = true; 33 | } 34 | if(node['partition_hourly_pubsize']){ 35 | node['partition_hourly_pubsize'].reverse(); 36 | node['hourly_pubsize'] = node['partition_hourly_pubsize'][0]; 37 | } 38 | return node; 39 | }); 40 | 41 | var total_hourly_pubsize = new Array(); 42 | _.each(response['nodes'], function(node, outIdx){ 43 | _.each(node['partition_hourly_pubsize'], function(value, idx){ 44 | if(total_hourly_pubsize[idx]) { 45 | total_hourly_pubsize[idx] += value; 46 | }else{ 47 | total_hourly_pubsize[idx] = value; 48 | } 49 | }); 50 | }); 51 | response['total_partition_hourly_pubsize'] = total_hourly_pubsize; 52 | response['total_hourly_pubsize'] = response['total_partition_hourly_pubsize'][0]; 53 | return response; 54 | } 55 | }); 56 | 57 | module.exports = Topic; 58 | -------------------------------------------------------------------------------- /internal/http_api/compress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // copied from https://github.com/gorilla/handlers/blob/master/compress.go 6 | 7 | package http_api 8 | 9 | import ( 10 | "compress/flate" 11 | "compress/gzip" 12 | "io" 13 | "net/http" 14 | "strings" 15 | ) 16 | 17 | type compressResponseWriter struct { 18 | io.Writer 19 | http.ResponseWriter 20 | http.Hijacker 21 | } 22 | 23 | func (w *compressResponseWriter) Header() http.Header { 24 | return w.ResponseWriter.Header() 25 | } 26 | 27 | func (w *compressResponseWriter) WriteHeader(c int) { 28 | w.ResponseWriter.Header().Del("Content-Length") 29 | w.ResponseWriter.WriteHeader(c) 30 | } 31 | 32 | func (w *compressResponseWriter) Write(b []byte) (int, error) { 33 | h := w.ResponseWriter.Header() 34 | if h.Get("Content-Type") == "" { 35 | h.Set("Content-Type", http.DetectContentType(b)) 36 | } 37 | h.Del("Content-Length") 38 | return w.Writer.Write(b) 39 | } 40 | 41 | // CompressHandler gzip compresses HTTP responses for clients that support it 42 | // via the 'Accept-Encoding' header. 43 | func CompressHandler(h http.Handler) http.Handler { 44 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 | L: 46 | for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 47 | switch strings.TrimSpace(enc) { 48 | case "gzip": 49 | w.Header().Set("Content-Encoding", "gzip") 50 | w.Header().Add("Vary", "Accept-Encoding") 51 | 52 | gw := gzip.NewWriter(w) 53 | defer gw.Close() 54 | 55 | h, hok := w.(http.Hijacker) 56 | if !hok { /* w is not Hijacker... oh well... */ 57 | h = nil 58 | } 59 | 60 | w = &compressResponseWriter{ 61 | Writer: gw, 62 | ResponseWriter: w, 63 | Hijacker: h, 64 | } 65 | 66 | break L 67 | case "deflate": 68 | w.Header().Set("Content-Encoding", "deflate") 69 | w.Header().Add("Vary", "Accept-Encoding") 70 | 71 | fw, _ := flate.NewWriter(w, flate.DefaultCompression) 72 | defer fw.Close() 73 | 74 | h, hok := w.(http.Hijacker) 75 | if !hok { /* w is not Hijacker... oh well... */ 76 | h = nil 77 | } 78 | 79 | w = &compressResponseWriter{ 80 | Writer: fw, 81 | ResponseWriter: w, 82 | Hijacker: h, 83 | } 84 | 85 | break L 86 | } 87 | } 88 | 89 | h.ServeHTTP(w, r) 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /jepsen/test/nsq/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns nsq.core-test 2 | (:use nsq.core 3 | jepsen.tests 4 | clojure.test 5 | clojure.pprint) 6 | (:require [clojure.string :as str] 7 | [jepsen.util :as util] 8 | [jepsen.core :as jepsen] 9 | [jepsen.os.debian :as debian] 10 | [jepsen.checker :as checker] 11 | [jepsen.checker.timeline :as timeline] 12 | [jepsen.model :as model] 13 | [jepsen.generator :as gen] 14 | [jepsen.nemesis :as nemesis] 15 | [jepsen.store :as store] 16 | [jepsen.report :as report])) 17 | 18 | (deftest nsq-test 19 | (let [test (jepsen/run! 20 | (assoc 21 | noop-test 22 | :name "nsq-simple-partition" 23 | :os debian/os 24 | :db db 25 | :client (queue-client) 26 | :nemesis (nemesis/partition-random-halves) 27 | ;:nemesis (nemesis/partition-random-node) 28 | ;:nemesis (nemesis/partition-majorities-ring) 29 | :model (model/unordered-queue) 30 | :checker (checker/compose 31 | {:total-queue checker/total-queue}) 32 | :generator (gen/phases 33 | (->> (gen/queue) 34 | (gen/delay 1/10) 35 | (gen/nemesis 36 | (gen/seq 37 | (cycle [(gen/sleep 60) 38 | {:type :info :f :start} 39 | (gen/sleep 60) 40 | {:type :info :f :stop}]))) 41 | (gen/time-limit 560)) 42 | (gen/nemesis 43 | (gen/once {:type :info, :f :stop})) 44 | (gen/log "waiting for recovery") 45 | (gen/sleep 60) 46 | (gen/clients 47 | (gen/each 48 | (gen/once {:type :invoke 49 | :f :drain}))))))] 50 | (is (:valid? (:results test))) 51 | (report/to "report/queue.txt" 52 | (-> test :results pprint)))) 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/youzan/nsq 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 7 | github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect 8 | github.com/Workiva/go-datastructures v1.0.50 9 | github.com/absolute8511/bolt v1.5.2 10 | github.com/absolute8511/glog v0.4.1 11 | github.com/absolute8511/gorpc v0.0.0-20161203145636-60ee7d4359cb 12 | github.com/absolute8511/goskiplist v0.0.0-20170727031420-3ba6f667c3df 13 | github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 14 | github.com/bitly/go-simplejson v0.5.0 15 | github.com/bitly/timer_metrics v0.0.0-20170606164300-b1c65ca7ae62 16 | github.com/blang/semver v3.5.1+incompatible 17 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 18 | github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b 19 | github.com/cenkalti/backoff v2.1.0+incompatible 20 | github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect 21 | github.com/cockroachdb/pebble v0.0.0-20210322142411-65860c8c27ac 22 | github.com/coreos/etcd v3.3.13+incompatible 23 | github.com/getsentry/raven-go v0.2.0 // indirect 24 | github.com/go-ole/go-ole v1.2.5 // indirect 25 | github.com/gobwas/glob v0.2.3 26 | github.com/gogo/protobuf v1.3.1 27 | github.com/golang/protobuf v1.4.2 28 | github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf 29 | github.com/gorilla/sessions v1.1.3 30 | github.com/hashicorp/golang-lru v0.5.3 31 | github.com/json-iterator/go v1.1.10 32 | github.com/judwhite/go-svc v1.0.0 33 | github.com/julienschmidt/httprouter v1.2.0 34 | github.com/kr/pretty v0.2.0 // indirect 35 | github.com/mreiferson/go-options v0.0.0-20161229190002-77551d20752b 36 | github.com/myesui/uuid v1.0.0 // indirect 37 | github.com/prometheus/client_golang v1.7.1 38 | github.com/shirou/gopsutil v3.21.2+incompatible 39 | github.com/spaolacci/murmur3 v1.1.0 40 | github.com/stretchr/testify v1.6.1 41 | github.com/tidwall/gjson v1.1.3 42 | github.com/tidwall/match v1.0.1 // indirect 43 | github.com/twinj/uuid v1.0.0 44 | github.com/twmb/murmur3 v1.1.5 45 | github.com/valyala/fastjson v1.6.1 46 | github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8 47 | github.com/youzan/go-nsq v1.7.2-HA 48 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 49 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 50 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e 51 | google.golang.org/grpc v1.29.1 52 | gopkg.in/stretchr/testify.v1 v1.2.2 // indirect 53 | gopkg.in/yaml.v2 v2.3.0 54 | ) 55 | 56 | replace github.com/ugorji/go => github.com/ugorji/go/codec v1.1.7 57 | -------------------------------------------------------------------------------- /internal/util/rename_windows_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package util 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "math/rand" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | const TEST_FILE_COUNT = 500 17 | 18 | func TestConcurrentRenames(t *testing.T) { 19 | var waitGroup util.WaitGroupWrapper 20 | 21 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 22 | trigger := make(chan struct{}) 23 | testDir := filepath.Join(os.TempDir(), fmt.Sprintf("nsqd_TestConcurrentRenames_%d", r.Int())) 24 | 25 | err := os.MkdirAll(testDir, 644) 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | 30 | fis, err := ioutil.ReadDir(testDir) 31 | if err != nil { 32 | t.Error(err) 33 | } else if len(fis) > 0 { 34 | t.Errorf("Test directory %s unexpectedly has %d items in it!", testDir, len(fis)) 35 | t.FailNow() 36 | } 37 | 38 | // create a bunch of source files and attempt to concurrently rename them all 39 | for i := 1; i <= TEST_FILE_COUNT; i++ { 40 | //First rename doesn't overwrite/replace; no target present 41 | sourcePath1 := filepath.Join(testDir, fmt.Sprintf("source1_%d.txt", i)) 42 | //Second rename will replace 43 | sourcePath2 := filepath.Join(testDir, fmt.Sprintf("source2_%d.txt", i)) 44 | targetPath := filepath.Join(testDir, fmt.Sprintf("target_%d.txt", i)) 45 | err = ioutil.WriteFile(sourcePath1, []byte(sourcePath1), 0644) 46 | if err != nil { 47 | t.Error(err) 48 | } 49 | err = ioutil.WriteFile(sourcePath2, []byte(sourcePath2), 0644) 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | 54 | waitGroup.Wrap(func() { 55 | _, _ = <-trigger 56 | err := AtomicRename(sourcePath1, targetPath) 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | err = AtomicRename(sourcePath2, targetPath) 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | }) 65 | } 66 | 67 | // start.. they're off to the races! 68 | close(trigger) 69 | 70 | // wait for completion... 71 | waitGroup.Wait() 72 | 73 | // no source files should exist any longer; we should just have 500 target files 74 | fis, err = ioutil.ReadDir(testDir) 75 | if err != nil { 76 | t.Error(err) 77 | } else if len(fis) != TEST_FILE_COUNT { 78 | t.Errorf("Test directory %s unexpectedly has %d items in it!", testDir, len(fis)) 79 | } else { 80 | for _, fi := range fis { 81 | if !strings.HasPrefix(fi.Name(), "target_") { 82 | t.Errorf("Test directory file %s is not expected target file!", fi.Name()) 83 | } 84 | } 85 | } 86 | 87 | // clean up the test directory 88 | err = os.RemoveAll(testDir) 89 | if err != nil { 90 | t.Error(err) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /internal/quantile/aggregate.go: -------------------------------------------------------------------------------- 1 | package quantile 2 | 3 | import ( 4 | "encoding/json" 5 | "math" 6 | "sort" 7 | ) 8 | 9 | type E2eProcessingLatencyAggregate struct { 10 | Count int `json:"count"` 11 | Percentiles []map[string]float64 `json:"percentiles"` 12 | Topic string `json:"topic"` 13 | Channel string `json:"channel"` 14 | Addr string `json:"host"` 15 | } 16 | 17 | func (e *E2eProcessingLatencyAggregate) UnmarshalJSON(b []byte) error { 18 | var resp struct { 19 | Count int `json:"count"` 20 | Percentiles []map[string]float64 `json:"percentiles"` 21 | Topic string `json:"topic"` 22 | Channel string `json:"channel"` 23 | Addr string `json:"host"` 24 | } 25 | err := json.Unmarshal(b, &resp) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | for _, p := range resp.Percentiles { 31 | p["min"] = p["value"] 32 | p["max"] = p["value"] 33 | p["average"] = p["value"] 34 | p["count"] = float64(resp.Count) 35 | } 36 | 37 | e.Count = resp.Count 38 | e.Percentiles = resp.Percentiles 39 | e.Topic = resp.Topic 40 | e.Channel = resp.Channel 41 | e.Addr = resp.Addr 42 | 43 | return nil 44 | } 45 | 46 | func (e *E2eProcessingLatencyAggregate) Len() int { return len(e.Percentiles) } 47 | func (e *E2eProcessingLatencyAggregate) Swap(i, j int) { 48 | e.Percentiles[i], e.Percentiles[j] = e.Percentiles[j], e.Percentiles[i] 49 | } 50 | func (e *E2eProcessingLatencyAggregate) Less(i, j int) bool { 51 | return e.Percentiles[i]["percentile"] > e.Percentiles[j]["percentile"] 52 | } 53 | 54 | // Add merges e2 into e by averaging the percentiles 55 | func (e *E2eProcessingLatencyAggregate) Add(e2 *E2eProcessingLatencyAggregate) { 56 | e.Addr = "*" 57 | p := e.Percentiles 58 | e.Count += e2.Count 59 | for _, value := range e2.Percentiles { 60 | i := -1 61 | for j, v := range p { 62 | if value["quantile"] == v["quantile"] { 63 | i = j 64 | break 65 | } 66 | } 67 | if i == -1 { 68 | i = len(p) 69 | e.Percentiles = append(p, make(map[string]float64)) 70 | p = e.Percentiles 71 | p[i]["quantile"] = value["quantile"] 72 | } 73 | p[i]["max"] = math.Max(value["max"], p[i]["max"]) 74 | p[i]["min"] = math.Min(value["max"], p[i]["max"]) 75 | p[i]["count"] += value["count"] 76 | if p[i]["count"] == 0 { 77 | p[i]["average"] = 0 78 | continue 79 | } 80 | delta := value["average"] - p[i]["average"] 81 | R := delta * value["count"] / p[i]["count"] 82 | p[i]["average"] = p[i]["average"] + R 83 | } 84 | sort.Sort(e) 85 | } 86 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/nodes.hbs: -------------------------------------------------------------------------------- 1 | {{> warning}} 2 | {{> error}} 3 | 4 |
5 |
6 |

Cluster Status

7 |
8 | {{#each clusters}} 9 |

{{dc}} {{#if stable}}Stable {{else}}Not Stable {{/if}}

10 | {{/each}} 11 |
12 |
13 |
14 | 15 |
16 |
17 |

NSQd Nodes ({{collection.length}})

18 |
19 |
20 | 21 |
22 |
23 | 24 | 25 | {{#if dcnsqlookupd.length}} 26 | 27 | {{/if}} 28 | 29 | 30 | 31 | 32 | 33 | {{#if nsqlookupd.length}} 34 | 35 | {{/if}} 36 | 37 | 38 | {{#each collection}} 39 | 40 | {{#if ../dcnsqlookupd.length}} 41 | 42 | {{/if}} 43 | 44 | 45 | 46 | 47 | 48 | {{#if ../nsqlookupd.length}} 49 | 55 | {{/if}} 56 | 64 | 65 | {{/each}} 66 |
DCHostnameBroadcast AddressTCP PortHTTP PortVersionLookupd Conns.Topics
{{dc}}{{hostname}}{{broadcast_address}}{{tcp_port}}{{http_port}}{{version}} 50 | {{remote_addresses.length}} 51 |
52 | {{#each remote_addresses}}{{this}}
{{/each}} 53 |
54 |
57 | {{#if topics.length}} 58 | {{topics.length}} 59 | {{#each topics}} 60 | {{topic}} 61 | {{/each}} 62 | {{/if}} 63 |
67 |
68 |
69 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/search.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var _ = require('underscore'); 3 | var Pubsub = require('../lib/pubsub'); 4 | var AppState = require('../app_state'); 5 | var BaseView = require('./base'); 6 | 7 | var SearchView = BaseView.extend({ 8 | className: 'search container-fluid', 9 | 10 | template: require('./spinner.hbs'), 11 | template: require('./search.hbs'), 12 | events: { 13 | 'click .search-trace button': 'onSearchTopicMessages' 14 | }, 15 | 16 | initialize: function() { 17 | BaseView.prototype.initialize.apply(this, arguments); 18 | }, 19 | 20 | onSearchTopicMessages: function(e) { 21 | e.preventDefault(); 22 | e.stopPropagation(); 23 | $('#loadingmessage').show(); 24 | var topic = $(e.target.form.elements['topic']).val(); 25 | var partition_id = $(e.target.form.elements['partition_id']).val(); 26 | var channel = $(e.target.form.elements['channel']).val(); 27 | var msgid = $(e.target.form.elements['msgid']).val(); 28 | var traceid = $(e.target.form.elements['traceid']).val(); 29 | var hours = $(e.target.form.elements['hours']).val(); 30 | var ishashed = $(e.target.form.elements['hashed']).is(':checked'); 31 | var dc_all = _.filter($(e.target.form.elements['dc_checked']), function(cb){ 32 | return $(cb).is(':checked') 33 | }); 34 | var dc_checked = _.map($(dc_all), function(c){ 35 | return $(c).val() 36 | }) 37 | $.ajax(AppState.url('/search/messages'), { 38 | method: "POST", 39 | data:JSON.stringify({ 40 | 'topic': topic, 41 | 'partition_id': partition_id, 42 | 'channel': channel, 43 | 'msgid': msgid, 44 | 'traceid': traceid, 45 | 'ishashed': ishashed, 46 | 'hours': hours, 47 | 'dc': dc_checked 48 | }), 49 | timeout: 60000 50 | }) 51 | .done(function(data) { 52 | this.template = require('./search.hbs'); 53 | this.render({ 54 | 'messages': data['logDataDtos'], 55 | 'total_cnt': data['totalCount'], 56 | 'request_msg': data['request_msg'], 57 | 'request_msg_dc': data['request_msg_dc'], 58 | 'message': data['message'] 59 | }); 60 | $('#loadingmessage').hide(); 61 | }.bind(this)) 62 | .fail(this.handleViewError.bind(this)) 63 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 64 | }, 65 | 66 | }); 67 | 68 | module.exports = SearchView; 69 | -------------------------------------------------------------------------------- /nsqd/engine/pebble_iter.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "github.com/cockroachdb/pebble" 5 | ) 6 | 7 | type pebbleIterator struct { 8 | *pebble.Iterator 9 | db *PebbleEng 10 | opt *pebble.IterOptions 11 | snap *pebble.Snapshot 12 | removeTsType byte 13 | } 14 | 15 | // low_bound is inclusive 16 | // upper bound is exclusive 17 | func newPebbleIterator(db *PebbleEng, opts IteratorOpts) (*pebbleIterator, error) { 18 | db.rwmutex.RLock() 19 | if db.IsClosed() { 20 | db.rwmutex.RUnlock() 21 | return nil, errDBEngClosed 22 | } 23 | upperBound := opts.Max 24 | lowerBound := opts.Min 25 | if opts.Type&RangeROpen <= 0 && upperBound != nil { 26 | // range right not open, we need inclusive the max, 27 | // however upperBound is exclusive 28 | upperBound = append(upperBound, 0) 29 | } 30 | 31 | opt := &pebble.IterOptions{} 32 | opt.LowerBound = lowerBound 33 | opt.UpperBound = upperBound 34 | dbit := &pebbleIterator{ 35 | db: db, 36 | opt: opt, 37 | } 38 | 39 | if opts.WithSnap { 40 | dbit.snap = db.eng.NewSnapshot() 41 | dbit.Iterator = dbit.snap.NewIter(opt) 42 | } else { 43 | dbit.Iterator = db.eng.NewIter(opt) 44 | } 45 | return dbit, nil 46 | } 47 | 48 | func (it *pebbleIterator) Next() { 49 | it.Iterator.Next() 50 | } 51 | 52 | func (it *pebbleIterator) Prev() { 53 | it.Iterator.Prev() 54 | } 55 | 56 | func (it *pebbleIterator) Seek(key []byte) { 57 | it.Iterator.SeekGE(key) 58 | } 59 | 60 | func (it *pebbleIterator) SeekForPrev(key []byte) { 61 | it.Iterator.SeekLT(key) 62 | } 63 | 64 | func (it *pebbleIterator) SeekToFirst() { 65 | it.Iterator.First() 66 | } 67 | 68 | func (it *pebbleIterator) SeekToLast() { 69 | it.Iterator.Last() 70 | } 71 | 72 | func (it *pebbleIterator) Valid() bool { 73 | if it.Iterator.Error() != nil { 74 | return false 75 | } 76 | return it.Iterator.Valid() 77 | } 78 | 79 | // the bytes returned will be freed after next 80 | func (it *pebbleIterator) RefKey() []byte { 81 | return it.Iterator.Key() 82 | } 83 | 84 | func (it *pebbleIterator) Key() []byte { 85 | v := it.Iterator.Key() 86 | vv := make([]byte, len(v)) 87 | copy(vv, v) 88 | return vv 89 | } 90 | 91 | // the bytes returned will be freed after next 92 | func (it *pebbleIterator) RefValue() []byte { 93 | v := it.Iterator.Value() 94 | return v 95 | } 96 | 97 | func (it *pebbleIterator) Value() []byte { 98 | v := it.RefValue() 99 | vv := make([]byte, len(v)) 100 | copy(vv, v) 101 | return vv 102 | } 103 | 104 | func (it *pebbleIterator) NoTimestamp(vt byte) { 105 | it.removeTsType = vt 106 | } 107 | 108 | func (it *pebbleIterator) Close() { 109 | if it.Iterator != nil { 110 | it.Iterator.Close() 111 | } 112 | if it.snap != nil { 113 | it.snap.Close() 114 | } 115 | it.db.rwmutex.RUnlock() 116 | } 117 | -------------------------------------------------------------------------------- /bench/bench_channels/bench_channels.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "sync" 9 | "time" 10 | 11 | "github.com/youzan/go-nsq" 12 | ) 13 | 14 | var ( 15 | num = flag.Int("num", 10000, "num channels") 16 | tcpAddress = flag.String("nsqd-tcp-address", "127.0.0.1:4150", ": to connect to nsqd") 17 | ) 18 | 19 | func main() { 20 | flag.Parse() 21 | var wg sync.WaitGroup 22 | 23 | goChan := make(chan int) 24 | rdyChan := make(chan int) 25 | conn, err := net.DialTimeout("tcp", *tcpAddress, time.Second) 26 | if err != nil { 27 | panic(err.Error()) 28 | } 29 | conn.Write(nsq.MagicV2) 30 | 31 | for j := 0; j < *num; j++ { 32 | nsq.CreateTopic(fmt.Sprintf("t%d", j), 0).WriteTo(conn) 33 | resp, err := nsq.ReadResponse(conn) 34 | if err != nil { 35 | panic(err.Error()) 36 | } 37 | frameType, data, err := nsq.UnpackResponse(resp) 38 | if err != nil { 39 | panic(err.Error()) 40 | } 41 | if frameType == nsq.FrameTypeError { 42 | panic(string(data)) 43 | } 44 | 45 | } 46 | 47 | for j := 0; j < *num; j++ { 48 | wg.Add(1) 49 | go func(id int) { 50 | subWorker(*num, *tcpAddress, fmt.Sprintf("t%d", j), "ch", rdyChan, goChan, id) 51 | wg.Done() 52 | }(j) 53 | <-rdyChan 54 | time.Sleep(5 * time.Millisecond) 55 | } 56 | 57 | close(goChan) 58 | wg.Wait() 59 | } 60 | 61 | func subWorker(n int, tcpAddr string, 62 | topic string, channel string, 63 | rdyChan chan int, goChan chan int, id int) { 64 | conn, err := net.DialTimeout("tcp", tcpAddr, time.Second) 65 | if err != nil { 66 | panic(err.Error()) 67 | } 68 | conn.Write(nsq.MagicV2) 69 | rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) 70 | ci := make(map[string]interface{}) 71 | ci["short_id"] = "test" 72 | ci["long_id"] = "test" 73 | cmd, _ := nsq.Identify(ci) 74 | cmd.WriteTo(rw) 75 | nsq.Subscribe(topic, channel).WriteTo(rw) 76 | rdyCount := 1 77 | rdy := rdyCount 78 | rdyChan <- 1 79 | <-goChan 80 | nsq.Ready(rdyCount).WriteTo(rw) 81 | rw.Flush() 82 | nsq.ReadResponse(rw) 83 | nsq.ReadResponse(rw) 84 | for { 85 | resp, err := nsq.ReadResponse(rw) 86 | if err != nil { 87 | panic(err.Error()) 88 | } 89 | frameType, data, err := nsq.UnpackResponse(resp) 90 | if err != nil { 91 | panic(err.Error()) 92 | } 93 | if frameType == nsq.FrameTypeError { 94 | panic(string(data)) 95 | } else if frameType == nsq.FrameTypeResponse { 96 | nsq.Nop().WriteTo(rw) 97 | rw.Flush() 98 | continue 99 | } 100 | msg, err := nsq.DecodeMessage(data) 101 | if err != nil { 102 | panic(err.Error()) 103 | } 104 | nsq.Finish(msg.ID).WriteTo(rw) 105 | rdy-- 106 | if rdy == 0 { 107 | nsq.Ready(rdyCount).WriteTo(rw) 108 | rdy = rdyCount 109 | rw.Flush() 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /nsqadmin/static/js/models/channel.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var AppState = require('../app_state'); 4 | var Backbone = require('backbone'); 5 | 6 | var Channel = Backbone.Model.extend({ 7 | idAttribute: 'name', 8 | 9 | constructor: function Channel() { 10 | Backbone.Model.prototype.constructor.apply(this, arguments); 11 | }, 12 | 13 | url: function() { 14 | return AppState.url('/topics/' + 15 | encodeURIComponent(this.get('topic')) + '/' + 16 | encodeURIComponent(this.get('name'))); 17 | }, 18 | 19 | clientUrl: function() { 20 | return AppState.url('/topics/' + 21 | encodeURIComponent(this.get('topic')) + '/' + 22 | encodeURIComponent(this.get('name'))) + '/client'; 23 | }, 24 | 25 | parse: function(response) { 26 | response['nodes'] = _.map(response['nodes'] || [], function(node) { 27 | var nodeParts = node['node'].split(':'); 28 | var port = nodeParts.pop(); 29 | var address = nodeParts.join(':'); 30 | var hostname = node['hostname']; 31 | node['show_broadcast_address'] = hostname.toLowerCase() !== address.toLowerCase(); 32 | node['hostname_port'] = hostname + ':' + port; 33 | //parse node to limit the length 34 | if(!node['msg_consume_latency_stats']) { 35 | node['msg_consume_latency_stats'] = new Array(); 36 | for(i = 0; i < 12; i++) { 37 | node['msg_consume_latency_stats'].push("n/a"); 38 | } 39 | } else { 40 | var diffInLen = 12 - node['msg_consume_latency_stats'].length; 41 | if(diffInLen < 0){ 42 | node['msg_consume_latency_stats'] = node['msg_consume_latency_stats'].slice(0, 12); 43 | } else { 44 | for (j = 0; j < diffInLen; j++) { 45 | node['msg_consume_latency_stats'].push("n/a"); 46 | } 47 | } 48 | } 49 | return node; 50 | }); 51 | 52 | response['clients'] = _.map(response['clients'] || [], function(client) { 53 | var clientId = client['client_id']; 54 | var hostname = client['hostname']; 55 | var shortHostname = hostname.split('.')[0]; 56 | 57 | // ignore client_id if it's duplicative 58 | client['show_client_id'] = (clientId.toLowerCase() !== shortHostname.toLowerCase() 59 | && clientId.toLowerCase() !== hostname.toLowerCase()); 60 | 61 | var port = client['remote_address'].split(':').pop(); 62 | client['hostname_port'] = hostname + ':' + port; 63 | 64 | return client; 65 | }); 66 | 67 | return response; 68 | } 69 | }); 70 | 71 | module.exports = Channel; 72 | -------------------------------------------------------------------------------- /consistence/coordgrpc/coord_grpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package coordgrpc; 3 | 4 | import "gogoproto/gogo.proto"; 5 | 6 | option (gogoproto.marshaler_all) = true; 7 | option (gogoproto.sizer_all) = true; 8 | option (gogoproto.unmarshaler_all) = true; 9 | option (gogoproto.goproto_getters_all) = false; 10 | option (gogoproto.goproto_enum_prefix_all) = false; 11 | 12 | service NsqdCoordRpcV2 { 13 | rpc UpdateChannelOffset(RpcChannelOffsetArg) returns (CoordErr) {} 14 | rpc PutMessage(RpcPutMessage) returns (CoordErr) {} 15 | rpc PutMessages(RpcPutMessages) returns (CoordErr) {} 16 | rpc PullCommitLogsAndData(PullCommitLogsReq) returns (PullCommitLogsRsp) {} 17 | rpc PullDelayedQueueCommitLogsAndData(PullCommitLogsReq) returns (PullCommitLogsRsp) {} 18 | } 19 | 20 | message CoordErr { 21 | string err_msg = 1; 22 | int32 err_code = 2; 23 | int32 err_type = 3; 24 | } 25 | 26 | message RpcTopicData { 27 | string topic_name = 1; 28 | int32 topic_partition = 2; 29 | int64 epoch = 3; 30 | int64 topic_write_epoch = 4; 31 | int64 topic_leader_session_epoch = 5; 32 | string topic_leader_session = 6; 33 | string topic_leader = 7; 34 | } 35 | 36 | message MsgQueueInterval { 37 | int64 start = 1; 38 | int64 end = 2; 39 | uint64 end_cnt = 3; 40 | } 41 | 42 | message ChannelConsumerOffset { 43 | int64 voffset = 1; 44 | bool flush = 2; 45 | bool allow_backward = 3; 46 | int64 vcnt = 4; 47 | bool need_update_confirmed = 5; 48 | repeated MsgQueueInterval confirmed_intervals = 6 [(gogoproto.nullable) = false]; 49 | } 50 | 51 | message CommitLogData { 52 | int64 logID = 1; 53 | int64 epoch = 2; 54 | int64 last_msg_logID = 3; 55 | int64 msg_offset = 4; 56 | int32 msg_size = 5; 57 | int64 msg_cnt = 6; 58 | int32 msg_num = 7; 59 | } 60 | 61 | message NsqdMessage { 62 | uint64 ID = 1; 63 | uint64 trace_ID = 2; 64 | bytes body = 3; 65 | int64 timestamp = 4; 66 | uint32 attemps = 5; 67 | } 68 | 69 | message RpcChannelOffsetArg { 70 | RpcTopicData topic_data = 1; 71 | string channel = 2; 72 | ChannelConsumerOffset channel_offset = 3; 73 | } 74 | 75 | message RpcPutMessage { 76 | RpcTopicData topic_data = 1; 77 | CommitLogData log_data = 2; 78 | NsqdMessage topic_message = 3; 79 | bytes topic_raw_message = 4; 80 | } 81 | 82 | message RpcPutMessages { 83 | RpcTopicData topic_data = 1; 84 | CommitLogData log_data = 2; 85 | repeated NsqdMessage topic_message = 3; 86 | bytes topic_raw_message = 4; 87 | } 88 | 89 | message PullCommitLogsReq { 90 | RpcTopicData topic_data = 1; 91 | int64 start_log_offset = 2; 92 | int32 log_max_num = 3; 93 | int64 start_index_cnt = 4; 94 | int64 log_count_num_index = 5; 95 | bool use_count_index = 6; 96 | } 97 | 98 | message PullCommitLogsRsp { 99 | repeated CommitLogData logs = 1 [(gogoproto.nullable) = false]; 100 | repeated bytes data_list = 2; 101 | } -------------------------------------------------------------------------------- /internal/flume_log/flume_client.go: -------------------------------------------------------------------------------- 1 | package flume_log 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "log" 7 | "net" 8 | "time" 9 | ) 10 | 11 | type FlumeClient struct { 12 | remoteAddr string 13 | conn net.Conn 14 | bw *bufio.Writer 15 | stopChan chan bool 16 | bufList chan []byte 17 | loopDone chan bool 18 | } 19 | 20 | func NewFlumeClient(agentIP string) *FlumeClient { 21 | client := &FlumeClient{ 22 | bufList: make(chan []byte, 1000), 23 | stopChan: make(chan bool), 24 | remoteAddr: agentIP, 25 | loopDone: make(chan bool), 26 | } 27 | 28 | go client.writeLoop() 29 | return client 30 | } 31 | 32 | func readLoop(conn net.Conn) { 33 | buf := make([]byte, 1024) 34 | for { 35 | _, err := conn.Read(buf) 36 | if err != nil { 37 | conn.Close() 38 | return 39 | } 40 | } 41 | } 42 | 43 | func (c *FlumeClient) writeLoop() { 44 | c.reconnect() 45 | ticker := time.NewTicker(time.Second * 3) 46 | defer func() { 47 | ticker.Stop() 48 | select { 49 | case buf := <-c.bufList: 50 | _, err := c.bw.Write(buf) 51 | if err != nil { 52 | log.Printf("write log %v failed: %v, left data: %v", string(buf), err, len(c.bufList)) 53 | break 54 | } 55 | default: 56 | } 57 | if c.conn != nil { 58 | c.bw.Flush() 59 | c.conn.Close() 60 | c.conn = nil 61 | } 62 | close(c.loopDone) 63 | }() 64 | for { 65 | if c.conn == nil { 66 | err := c.reconnect() 67 | if err != nil { 68 | select { 69 | case <-time.After(time.Second): 70 | case <-c.stopChan: 71 | return 72 | } 73 | continue 74 | } 75 | } 76 | select { 77 | case buf := <-c.bufList: 78 | _, err := c.bw.Write(buf) 79 | if err != nil { 80 | log.Printf("write log %v failed: %v", string(buf), err) 81 | c.reconnect() 82 | } 83 | case <-ticker.C: 84 | err := c.bw.Flush() 85 | if err != nil { 86 | log.Printf("flush write log failed: %v", err) 87 | c.reconnect() 88 | } 89 | case <-c.stopChan: 90 | return 91 | } 92 | } 93 | } 94 | 95 | func (c *FlumeClient) SendLog(d []byte) error { 96 | select { 97 | case <-c.stopChan: 98 | return errors.New("flume client stopped") 99 | case c.bufList <- d: 100 | default: 101 | return errors.New("flume client buffer overflowed") 102 | } 103 | return nil 104 | } 105 | 106 | func (c *FlumeClient) reconnect() (err error) { 107 | if c.conn != nil { 108 | c.bw.Flush() 109 | c.conn.Close() 110 | c.conn = nil 111 | c.bw = nil 112 | } 113 | log.Printf("reconnect flumelogger to %v ", c.remoteAddr) 114 | conn, err := net.DialTimeout("tcp", c.remoteAddr, time.Second*5) 115 | if err != nil { 116 | log.Printf("connect to %v failed: %v", c.remoteAddr, err) 117 | return err 118 | } else { 119 | c.conn = conn 120 | c.bw = bufio.NewWriterSize(conn, 1024*8) 121 | go readLoop(conn) 122 | } 123 | return nil 124 | } 125 | 126 | func (c *FlumeClient) Stop() { 127 | if c.stopChan != nil { 128 | close(c.stopChan) 129 | } 130 | <-c.loopDone 131 | } 132 | -------------------------------------------------------------------------------- /nsqadmin/options.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/youzan/nsq/internal/levellogger" 7 | "github.com/youzan/nsq/internal/clusterinfo" 8 | ) 9 | 10 | type Options struct { 11 | HTTPAddress string `flag:"http-address"` 12 | 13 | GraphiteURL string `flag:"graphite-url"` 14 | ProxyGraphite bool `flag:"proxy-graphite"` 15 | 16 | UseStatsdPrefixes bool `flag:"use-statsd-prefixes"` 17 | StatsdPrefix string `flag:"statsd-prefix"` 18 | StatsdCounterFormat string `flag:"statsd-counter-format"` 19 | StatsdGaugeFormat string `flag:"statsd-gauge-format"` 20 | 21 | StatsdInterval time.Duration `flag:"statsd-interval"` 22 | 23 | NSQLookupdHTTPAddresses []string `flag:"lookupd-http-address" cfg:"nsqlookupd_http_addresses"` 24 | NSQDHTTPAddresses []string `flag:"nsqd-http-address" cfg:"nsqd_http_addresses"` 25 | 26 | DCNSQLookupdHTTPAddresses []string `flag:"dc-lookupd-http-address" cfg:"dc_lookupd_http_addresses"` 27 | 28 | NSQLookupdHTTPAddressesDC []clusterinfo.LookupdAddressDC 29 | 30 | HTTPClientTLSInsecureSkipVerify bool `flag:"http-client-tls-insecure-skip-verify"` 31 | HTTPClientTLSRootCAFile string `flag:"http-client-tls-root-ca-file"` 32 | HTTPClientTLSCert string `flag:"http-client-tls-cert"` 33 | HTTPClientTLSKey string `flag:"http-client-tls-key"` 34 | 35 | NotificationHTTPEndpoint string `flag:"notification-http-endpoint"` 36 | TraceQueryURL string `flag:"trace-query-url"` 37 | TraceAppID string `flag:"trace-app-id"` 38 | TraceAppName string `flag:"trace-app-name"` 39 | TraceLogIndexID string `flag:"trace-log-index-id"` 40 | TraceLogIndexName string `flag:"trace-log-index-name"` 41 | TraceLogPageCount int `flag:"trace-log-page-count"` 42 | 43 | ChannelCreationRetry int `flag:"channel-create-retry"` 44 | ChannelCreationBackoffInterval int `flag:"channel-create-backoff-interval"` 45 | 46 | AuthUrl string `flag:"auth-url" cfg:"auth_url"` 47 | AuthSecret string `flag:"auth-secret" cfg:"auth_secret"` 48 | LogoutUrl string `flag:"logout-url" cfg:"logout_url"` 49 | AppName string `flag:"app-name" cfg:"app_name"` 50 | RedirectUrl string `flag:"redirect-url" cfg:"redirect_url"` 51 | LogDir string `flag:"log-dir" cfg:"log_dir"` 52 | Logger levellogger.Logger 53 | AccessTokens []string `flag:"access-tokens" cfg:"access_tokens"` 54 | 55 | AccessControlFile string `flag:"access-control-file"` 56 | EnableZanTestSkip bool `flag:"enable-zan-test-skip"` 57 | } 58 | 59 | func NewOptions() *Options { 60 | return &Options{ 61 | HTTPAddress: "0.0.0.0:4171", 62 | UseStatsdPrefixes: true, 63 | StatsdPrefix: "nsq.%s", 64 | StatsdCounterFormat: "stats.counters.%s.count", 65 | StatsdGaugeFormat: "stats.gauges.%s", 66 | StatsdInterval: 60 * time.Second, 67 | ChannelCreationRetry: 3, 68 | ChannelCreationBackoffInterval: 1000, 69 | Logger: &levellogger.GLogger{}, 70 | TraceLogPageCount: 60, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /internal/quantile/quantile.go: -------------------------------------------------------------------------------- 1 | package quantile 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "time" 7 | 8 | "github.com/bmizerany/perks/quantile" 9 | "github.com/youzan/nsq/internal/stringy" 10 | ) 11 | 12 | type Result struct { 13 | Count int `json:"count"` 14 | Percentiles []map[string]float64 `json:"percentiles"` 15 | } 16 | 17 | func (r *Result) String() string { 18 | var s []string 19 | for _, item := range r.Percentiles { 20 | s = append(s, stringy.NanoSecondToHuman(item["value"])) 21 | } 22 | return strings.Join(s, ", ") 23 | } 24 | 25 | type Quantile struct { 26 | sync.Mutex 27 | streams [2]quantile.Stream 28 | currentIndex uint8 29 | lastMoveWindow time.Time 30 | currentStream *quantile.Stream 31 | 32 | Percentiles []float64 33 | MoveWindowTime time.Duration 34 | } 35 | 36 | func New(WindowTime time.Duration, Percentiles []float64) *Quantile { 37 | q := Quantile{ 38 | currentIndex: 0, 39 | lastMoveWindow: time.Now(), 40 | MoveWindowTime: WindowTime / 2, 41 | Percentiles: Percentiles, 42 | } 43 | for i := 0; i < 2; i++ { 44 | q.streams[i] = *quantile.NewTargeted(Percentiles...) 45 | } 46 | q.currentStream = &q.streams[0] 47 | return &q 48 | } 49 | 50 | func (q *Quantile) Result() *Result { 51 | if q == nil { 52 | return &Result{} 53 | } 54 | queryHandler := q.QueryHandler() 55 | result := Result{ 56 | Count: queryHandler.Count(), 57 | Percentiles: make([]map[string]float64, len(q.Percentiles)), 58 | } 59 | for i, p := range q.Percentiles { 60 | value := queryHandler.Query(p) 61 | result.Percentiles[i] = map[string]float64{"quantile": p, "value": value} 62 | } 63 | return &result 64 | } 65 | 66 | func (q *Quantile) Insert(msgStartTime int64) { 67 | q.Lock() 68 | 69 | now := time.Now() 70 | for q.IsDataStale(now) { 71 | q.moveWindow() 72 | } 73 | 74 | q.currentStream.Insert(float64(now.UnixNano() - msgStartTime)) 75 | q.Unlock() 76 | } 77 | 78 | func (q *Quantile) QueryHandler() *quantile.Stream { 79 | q.Lock() 80 | now := time.Now() 81 | for q.IsDataStale(now) { 82 | q.moveWindow() 83 | } 84 | 85 | merged := quantile.NewTargeted(q.Percentiles...) 86 | merged.Merge(q.streams[0].Samples()) 87 | merged.Merge(q.streams[1].Samples()) 88 | q.Unlock() 89 | return merged 90 | } 91 | 92 | func (q *Quantile) IsDataStale(now time.Time) bool { 93 | return now.After(q.lastMoveWindow.Add(q.MoveWindowTime)) 94 | } 95 | 96 | func (q *Quantile) Merge(them *Quantile) { 97 | q.Lock() 98 | them.Lock() 99 | iUs := q.currentIndex 100 | iThem := them.currentIndex 101 | 102 | q.streams[iUs].Merge(them.streams[iThem].Samples()) 103 | 104 | iUs ^= 0x1 105 | iThem ^= 0x1 106 | q.streams[iUs].Merge(them.streams[iThem].Samples()) 107 | 108 | if q.lastMoveWindow.Before(them.lastMoveWindow) { 109 | q.lastMoveWindow = them.lastMoveWindow 110 | } 111 | q.Unlock() 112 | them.Unlock() 113 | } 114 | 115 | func (q *Quantile) moveWindow() { 116 | q.currentIndex ^= 0x1 117 | q.currentStream = &q.streams[q.currentIndex] 118 | q.lastMoveWindow = q.lastMoveWindow.Add(q.MoveWindowTime) 119 | q.currentStream.Reset() 120 | } 121 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topicsFilter.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var Pubsub = require('../lib/pubsub'); 3 | var AppState = require('../app_state'); 4 | 5 | var BaseView = require('./base'); 6 | var TopicsView = require('./topics'); 7 | var TopicsMeta = require('../collections/topics_meta'); 8 | var metaFetched = false; 9 | var TopicsFilterView = BaseView.extend({ 10 | className: 'topicsFilter container-fluid', 11 | 12 | template: require('./spinner.hbs'), 13 | 14 | events: { 15 | 'click .filters input': 'topicsFilterAction', 16 | }, 17 | 18 | filterTopics: function(e) { 19 | $('.table tr:not(.title)').show(); 20 | 21 | $(e.target).parent().find('input') 22 | .filter(function(){ 23 | if($(this).is(':checked')) { 24 | return this; 25 | } 26 | }) 27 | .each(function() { 28 | var filterValue = $(this).val(); 29 | $('.table tr:not(.title)') 30 | .filter( 31 | function(){ 32 | if ($(this) 33 | .find('td a#' + filterValue).length == 0){ 34 | return this; 35 | } 36 | } 37 | ) 38 | .hide(); 39 | } 40 | ); 41 | }, 42 | 43 | topicsFilterAction: function(e) { 44 | e.stopPropagation(); 45 | if(!metaFetched) { 46 | var topicsView = this.topicsView; 47 | var filter = this.filterTopics; 48 | var topicsMeta = new TopicsMeta(); 49 | topicsMeta.fetch() 50 | .done(function(data){ 51 | var topics = _.map(data['topics'], function(topic) { 52 | return { 53 | 'name': topic['topic_name'], 54 | 'extend_support': topic['extend_support'], 55 | 'ordered': topic['ordered'] 56 | }; 57 | }); 58 | 59 | topicsView.render( 60 | { 61 | 'message': data['message'], 62 | 'collection': topics 63 | }); 64 | metaFetched = true; 65 | console.log("topic meta fetched.") 66 | filter(e); 67 | }); 68 | } else { 69 | this.filterTopics(e); 70 | } 71 | 72 | }, 73 | 74 | initialize: function(options){ 75 | metaFetched = false; 76 | this.template = require('./topicsFilter.hbs'); 77 | this.topicsView = new TopicsView(); 78 | }, 79 | 80 | render: function() { 81 | BaseView.prototype.initialize.apply(this, arguments); 82 | this.$el.html(this.template()); 83 | this.$el.prepend(this.topicsView.render().el); 84 | return this; 85 | } 86 | }); 87 | 88 | module.exports = TopicsFilterView; -------------------------------------------------------------------------------- /nsqadmin/static/js/views/header.hbs: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /nsqadmin/whitelist.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "sync" 7 | "time" 8 | 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type AccessControl interface { 13 | IsAdmin(username string) bool 14 | Start() 15 | Stop() 16 | } 17 | 18 | type YamlAccessControl struct { 19 | filePath string 20 | accessMap map[string]interface{} 21 | lock sync.RWMutex 22 | updateTicker *time.Ticker 23 | tStopChan chan int 24 | wg sync.WaitGroup 25 | lastUpdated time.Time 26 | ctx *Context 27 | } 28 | 29 | func readYml(path string) (map[string]interface{}, error) { 30 | buf, err := ioutil.ReadFile(path) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | m := make(map[string]interface{}) 36 | err = yaml.Unmarshal(buf, &m) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return m, nil 41 | } 42 | 43 | func NewYamlAccessControl(ctx *Context, filePath string) (AccessControl, error) { 44 | if filePath == "" { 45 | return nil, nil 46 | } 47 | acMp, err := readYml(filePath) 48 | if err != nil { 49 | return nil, err 50 | } 51 | info, err := os.Stat(filePath) 52 | if err != nil { 53 | return nil, err 54 | } 55 | mt := info.ModTime() 56 | 57 | return &YamlAccessControl{ 58 | filePath: filePath, 59 | accessMap: acMp, 60 | lastUpdated: mt, 61 | ctx: ctx, 62 | }, nil 63 | } 64 | 65 | func (ac *YamlAccessControl) Start() { 66 | ac.tStopChan = make(chan int, 1) 67 | ac.updateTicker = time.NewTicker(10 * time.Second) 68 | ac.wg.Add(1) 69 | go func() { 70 | defer ac.wg.Done() 71 | defer ac.updateTicker.Stop() 72 | for { 73 | select { 74 | case <-ac.updateTicker.C: 75 | { 76 | //update access control map 77 | ac.tryUpdateControlFile() 78 | } 79 | case <-ac.tStopChan: 80 | { 81 | return 82 | } 83 | } 84 | } 85 | }() 86 | } 87 | 88 | func (ac *YamlAccessControl) Stop() { 89 | close(ac.tStopChan) 90 | ac.wg.Wait() 91 | } 92 | 93 | func (ac *YamlAccessControl) IsAdmin(username string) bool { 94 | ac.lock.RLock() 95 | defer ac.lock.RUnlock() 96 | //anyone is admin if access control file is disabled 97 | if !ac.isEnable() { 98 | return true 99 | } 100 | if admins, exist := ac.accessMap["admin"]; !exist { 101 | return false 102 | } else { 103 | //ac.ctx.nsqadmin.logf("admins map %v", admins) 104 | if isAdmin, exist := admins.(map[string]interface{})[username]; !exist { 105 | return false 106 | } else { 107 | return isAdmin.(bool) 108 | } 109 | } 110 | } 111 | 112 | //read action in access control file DO NOT add read lock 113 | func (ac *YamlAccessControl) isEnable() bool { 114 | if enable, exist := ac.accessMap["enable"]; !exist { 115 | return false 116 | } else { 117 | return enable.(bool) 118 | } 119 | } 120 | 121 | func (ac *YamlAccessControl) tryUpdateControlFile() { 122 | info, err := os.Stat(ac.filePath) 123 | if err != nil { 124 | ac.ctx.nsqadmin.logf("ERROR: fail to fetch info for access control file: %v", ac.filePath) 125 | return 126 | } 127 | mt := info.ModTime() 128 | //compare with last modified time 129 | if mt.After(ac.lastUpdated) { 130 | ac.ctx.nsqadmin.logf("INFO: access control file modified, try updating") 131 | newAcMap, err := readYml(ac.filePath) 132 | if err != nil { 133 | ac.ctx.nsqadmin.logf("ERROR: fail to read access control file from %v", ac.filePath) 134 | return 135 | } 136 | ac.lock.Lock() 137 | defer ac.lock.Unlock() 138 | ac.accessMap = newAcMap 139 | ac.lastUpdated = mt 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /internal/auth/authorizations.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/url" 8 | "regexp" 9 | "time" 10 | 11 | "github.com/youzan/nsq/internal/http_api" 12 | ) 13 | 14 | type Authorization struct { 15 | Topic string `json:"topic"` 16 | Channels []string `json:"channels"` 17 | Permissions []string `json:"permissions"` 18 | } 19 | 20 | type State struct { 21 | TTL int `json:"ttl"` 22 | Authorizations []Authorization `json:"authorizations"` 23 | Identity string `json:"identity"` 24 | IdentityURL string `json:"identity_url"` 25 | Expires time.Time 26 | } 27 | 28 | func (a *Authorization) HasPermission(permission string) bool { 29 | for _, p := range a.Permissions { 30 | if permission == p { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | 37 | func (a *Authorization) IsAllowed(topic, channel string) bool { 38 | if channel != "" { 39 | if !a.HasPermission("subscribe") { 40 | return false 41 | } 42 | } else { 43 | if !a.HasPermission("publish") { 44 | return false 45 | } 46 | } 47 | 48 | topicRegex := regexp.MustCompile(a.Topic) 49 | 50 | if !topicRegex.MatchString(topic) { 51 | return false 52 | } 53 | 54 | for _, c := range a.Channels { 55 | channelRegex := regexp.MustCompile(c) 56 | if channelRegex.MatchString(channel) { 57 | return true 58 | } 59 | } 60 | return false 61 | } 62 | 63 | func (a *State) IsAllowed(topic, channel string) bool { 64 | for _, aa := range a.Authorizations { 65 | if aa.IsAllowed(topic, channel) { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | func (a *State) IsExpired() bool { 73 | if a.Expires.Before(time.Now()) { 74 | return true 75 | } 76 | return false 77 | } 78 | 79 | func QueryAnyAuthd(authd []string, remoteIP, tlsEnabled, authSecret string) (*State, error) { 80 | for _, a := range authd { 81 | authState, err := QueryAuthd(a, remoteIP, tlsEnabled, authSecret) 82 | if err != nil { 83 | log.Printf("Error: failed auth against %s %s", a, err) 84 | continue 85 | } 86 | return authState, nil 87 | } 88 | return nil, errors.New("Unable to access auth server") 89 | } 90 | 91 | func QueryAuthd(authd, remoteIP, tlsEnabled, authSecret string) (*State, error) { 92 | v := url.Values{} 93 | v.Set("remote_ip", remoteIP) 94 | v.Set("tls", tlsEnabled) 95 | v.Set("secret", authSecret) 96 | 97 | endpoint := fmt.Sprintf("http://%s/auth?%s", authd, v.Encode()) 98 | 99 | var authState State 100 | client := http_api.NewClient(nil) 101 | if _, err := client.GETV1(endpoint, &authState); err != nil { 102 | return nil, err 103 | } 104 | 105 | // validation on response 106 | for _, auth := range authState.Authorizations { 107 | for _, p := range auth.Permissions { 108 | switch p { 109 | case "subscribe", "publish": 110 | default: 111 | return nil, fmt.Errorf("unknown permission %s", p) 112 | } 113 | } 114 | 115 | if _, err := regexp.Compile(auth.Topic); err != nil { 116 | return nil, fmt.Errorf("unable to compile topic %q %s", auth.Topic, err) 117 | } 118 | 119 | for _, channel := range auth.Channels { 120 | if _, err := regexp.Compile(channel); err != nil { 121 | return nil, fmt.Errorf("unable to compile channel %q %s", channel, err) 122 | } 123 | } 124 | } 125 | 126 | if authState.TTL <= 0 { 127 | return nil, fmt.Errorf("invalid TTL %d (must be >0)", authState.TTL) 128 | } 129 | 130 | authState.Expires = time.Now().Add(time.Duration(authState.TTL) * time.Second) 131 | return &authState, nil 132 | } 133 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/BurntSushi/toml" 30 | version = "0.3.1" 31 | 32 | [[constraint]] 33 | name = "github.com/Workiva/go-datastructures" 34 | version = "1.0.50" 35 | 36 | [[constraint]] 37 | name = "github.com/absolute8511/bolt" 38 | version = "1.5.0" 39 | 40 | [[constraint]] 41 | name = "github.com/absolute8511/glog" 42 | version = "0.2.0" 43 | 44 | [[constraint]] 45 | branch = "master" 46 | name = "github.com/absolute8511/gorpc" 47 | 48 | [[constraint]] 49 | branch = "master" 50 | name = "github.com/absolute8511/goskiplist" 51 | 52 | [[constraint]] 53 | name = "github.com/astaxie/beego" 54 | version = "1.11.1" 55 | 56 | [[constraint]] 57 | branch = "master" 58 | name = "github.com/bitly/go-hostpool" 59 | 60 | [[constraint]] 61 | name = "github.com/bitly/go-simplejson" 62 | version = "0.5.0" 63 | 64 | [[constraint]] 65 | branch = "master" 66 | name = "github.com/bitly/timer_metrics" 67 | 68 | [[constraint]] 69 | name = "github.com/blang/semver" 70 | version = "3.5.1" 71 | 72 | [[constraint]] 73 | branch = "master" 74 | name = "github.com/bmizerany/perks" 75 | 76 | [[constraint]] 77 | name = "github.com/cenkalti/backoff" 78 | version = "2.1.0" 79 | 80 | [[constraint]] 81 | name = "github.com/coreos/etcd" 82 | version = "2.3.8" 83 | 84 | [[constraint]] 85 | name = "github.com/gobwas/glob" 86 | version = "0.2.3" 87 | 88 | [[constraint]] 89 | name = "github.com/golang/protobuf" 90 | version = "1.2.0" 91 | 92 | [[constraint]] 93 | branch = "master" 94 | name = "github.com/golang/snappy" 95 | 96 | [[constraint]] 97 | name = "github.com/gorilla/sessions" 98 | version = "1.1.3" 99 | 100 | [[constraint]] 101 | name = "github.com/judwhite/go-svc" 102 | version = "1.0.0" 103 | 104 | [[constraint]] 105 | name = "github.com/julienschmidt/httprouter" 106 | version = "1.2.0" 107 | 108 | [[constraint]] 109 | branch = "master" 110 | name = "github.com/mreiferson/go-options" 111 | 112 | [[constraint]] 113 | name = "github.com/spaolacci/murmur3" 114 | version = "1.1.0" 115 | 116 | [[constraint]] 117 | name = "github.com/tidwall/gjson" 118 | version = "1.1.3" 119 | 120 | [[constraint]] 121 | name = "github.com/twinj/uuid" 122 | version = "1.0.0" 123 | 124 | [[constraint]] 125 | branch = "master" 126 | name = "github.com/viki-org/dnscache" 127 | 128 | 129 | [[constraint]] 130 | name = "github.com/hashicorp/golang-lru" 131 | version = "0.5.3" 132 | 133 | [[constraint]] 134 | name = "github.com/youzan/go-nsq" 135 | version = "1.6.1-HA" 136 | 137 | [[constraint]] 138 | branch = "master" 139 | name = "golang.org/x/net" 140 | 141 | [[constraint]] 142 | branch = "master" 143 | name = "golang.org/x/sync" 144 | 145 | [[constraint]] 146 | name = "google.golang.org/grpc" 147 | version = "1.17.0" 148 | 149 | [prune] 150 | go-tests = true 151 | unused-packages = true 152 | -------------------------------------------------------------------------------- /nsqadmin/static/js/views/topic.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | window.jQuery = $; 4 | var bootstrap = require('bootstrap'); //eslint-disable-line no-unused-vars 5 | var bootbox = require('bootbox'); 6 | 7 | var Pubsub = require('../lib/pubsub'); 8 | var AppState = require('../app_state'); 9 | 10 | var BaseView = require('./base'); 11 | 12 | var click2Show=" >>>"; 13 | var click2Hide=" <<<"; 14 | 15 | var TopicView = BaseView.extend({ 16 | className: 'topic container-fluid', 17 | 18 | template: require('./spinner.hbs'), 19 | 20 | events: { 21 | 'click .topic-actions button': 'topicAction', 22 | 'click .channel-action .hierarchy button': 'onCreateTopicChannel', 23 | 'click .toggle h4': 'onToggle', 24 | 'click .toggle h4 span a': 'onToggle', 25 | }, 26 | 27 | initialize: function() { 28 | BaseView.prototype.initialize.apply(this, arguments); 29 | this.listenTo(AppState, 'change:graph_interval', this.render); 30 | this.model.fetch() 31 | .done(function(data) { 32 | this.template = require('./topic.hbs'); 33 | this.render({'message': data['message']}); 34 | }.bind(this)) 35 | .fail(this.handleViewError.bind(this)) 36 | .always(Pubsub.trigger.bind(Pubsub, 'view:ready')); 37 | $('[data-toggle="tooltip"]').tooltip({ 38 | placement : 'top' 39 | }); 40 | }, 41 | 42 | onToggle: function(e) { 43 | e.preventDefault(); 44 | e.stopPropagation(); 45 | var canHideClass = "canHide"; 46 | var parent = $(e.target).parents(".toggle").first(); 47 | var divCanHide = parent.next(); 48 | if(divCanHide != null && divCanHide.attr("class") != null && divCanHide.attr("class").indexOf(canHideClass) !== -1) { 49 | divCanHide.toggle(300); 50 | var anchor = parent.find("a").first(); 51 | if(anchor.text().indexOf(click2Hide) !== -1) { 52 | anchor.text(click2Show); 53 | } else { 54 | anchor.text(click2Hide); 55 | } 56 | } 57 | }, 58 | 59 | topicAction: function(e) { 60 | e.preventDefault(); 61 | e.stopPropagation(); 62 | var action = $(e.currentTarget).data('action'); 63 | var txt = 'Are you sure you want to ' + 64 | action + ' ' + this.model.get('name') + '?'; 65 | bootbox.confirm(txt, function(result) { 66 | if (result !== true) { 67 | return; 68 | } 69 | if (action === 'delete') { 70 | $.ajax(this.model.url(), {'method': 'DELETE'}) 71 | .done(function() { window.location = '/'; }); 72 | } else { 73 | $.post(this.model.url(), JSON.stringify({'action': action})) 74 | .done(function() { window.location.reload(true); }) 75 | .fail(this.handleAJAXError.bind(this)); 76 | } 77 | }.bind(this)); 78 | }, 79 | 80 | onCreateTopicChannel: function(e) { 81 | e.preventDefault(); 82 | e.stopPropagation(); 83 | var topic = $(e.target.form.elements['topic']).val(); 84 | var channel = $(e.target.form.elements['channel']).val(); 85 | if (topic === '' || channel === '') { 86 | return; 87 | } 88 | $.post(AppState.url('/topics/' + topic + '/' + channel), JSON.stringify({ 89 | 'action': 'create' 90 | })) 91 | .done(function() { window.location.reload(true); }) 92 | .fail(this.handleAJAXError.bind(this)); 93 | } 94 | }); 95 | 96 | module.exports = TopicView; 97 | -------------------------------------------------------------------------------- /nsqadmin/notify.go: -------------------------------------------------------------------------------- 1 | package nsqadmin 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "net/url" 7 | "os" 8 | "strings" 9 | "time" 10 | "encoding/json" 11 | ) 12 | 13 | type AdminAction struct { 14 | Action string `json:"action"` 15 | Topic string `json:"topic"` 16 | Order bool `json:"order"` 17 | Channel string `json:"channel,omitempty"` 18 | Node string `json:"node,omitempty"` 19 | Timestamp int64 `json:"timestamp"` 20 | User string `json:"user,omitempty"` 21 | RemoteIP string `json:"remote_ip"` 22 | UserAgent string `json:"user_agent"` 23 | URL string `json:"url"` // The URL of the HTTP request that triggered this action 24 | Via string `json:"via"` // the Hostname of the nsqadmin performing this action 25 | } 26 | 27 | func (a *AdminAction) String() string { 28 | bytes, err := json.Marshal(*a) 29 | if err == nil { 30 | return string(bytes) 31 | } 32 | return "" 33 | } 34 | 35 | func basicAuthUser(req *http.Request) string { 36 | s := strings.SplitN(req.Header.Get("Authorization"), " ", 2) 37 | if len(s) != 2 || s[0] != "Basic" { 38 | return "" 39 | } 40 | b, err := base64.StdEncoding.DecodeString(s[1]) 41 | if err != nil { 42 | return "" 43 | } 44 | pair := strings.SplitN(string(b), ":", 2) 45 | if len(pair) != 2 { 46 | return "" 47 | } 48 | return pair[0] 49 | } 50 | 51 | 52 | func (s *httpServer) notifyAdminActionWithUser(action, topic, channel, node string, req *http.Request) { 53 | s.notifyAdminActionWithUserAndOrder(action, topic, channel, node, false, req) 54 | } 55 | 56 | func (s *httpServer) notifyAdminActionWithUserAndOrder(action, topic, channel, node string, order bool, req *http.Request) { 57 | via, _ := os.Hostname() 58 | u := url.URL{ 59 | Scheme: "http", 60 | Host: req.Host, 61 | Path: req.URL.Path, 62 | RawQuery: req.URL.RawQuery, 63 | } 64 | if req.TLS != nil || req.Header.Get("X-Scheme") == "https" { 65 | u.Scheme = "https" 66 | } 67 | a := &AdminAction{ 68 | Action: action, 69 | Topic: topic, 70 | Order: order, 71 | Channel: channel, 72 | Node: node, 73 | Timestamp: time.Now().Unix(), 74 | RemoteIP: req.RemoteAddr, 75 | UserAgent: req.UserAgent(), 76 | URL: u.String(), 77 | Via: via, 78 | } 79 | user, _ := s.getExistingUserInfo(req) 80 | if user != nil && user.IsLogin() { 81 | a.User = user.GetUserName() 82 | } else { 83 | a.User = basicAuthUser(req) 84 | } 85 | // access log 86 | s.ctx.nsqadmin.logf("ACCESS: %v", a.String()) 87 | if s.ctx.nsqadmin.opts.NotificationHTTPEndpoint == "" { 88 | return 89 | } 90 | 91 | // Perform all work in a new goroutine so this never blocks 92 | go func() { s.ctx.nsqadmin.notifications <- a }() 93 | } 94 | 95 | func (s *httpServer) notifyAdminAction(action, topic, channel, node string, req *http.Request) { 96 | if s.ctx.nsqadmin.opts.NotificationHTTPEndpoint == "" { 97 | return 98 | } 99 | via, _ := os.Hostname() 100 | 101 | u := url.URL{ 102 | Scheme: "http", 103 | Host: req.Host, 104 | Path: req.URL.Path, 105 | RawQuery: req.URL.RawQuery, 106 | } 107 | if req.TLS != nil || req.Header.Get("X-Scheme") == "https" { 108 | u.Scheme = "https" 109 | } 110 | 111 | a := &AdminAction{ 112 | Action: action, 113 | Topic: topic, 114 | Channel: channel, 115 | Node: node, 116 | Timestamp: time.Now().Unix(), 117 | User: basicAuthUser(req), 118 | RemoteIP: req.RemoteAddr, 119 | UserAgent: req.UserAgent(), 120 | URL: u.String(), 121 | Via: via, 122 | } 123 | // Perform all work in a new goroutine so this never blocks 124 | go func() { s.ctx.nsqadmin.notifications <- a }() 125 | } 126 | --------------------------------------------------------------------------------