Deployment on Windows
Despite being primarily developed for Unix-like systems, NSQ can be deployed and tested on Windows. A guide exists for installing NSQ on Windows, which details the steps to launch nsqd and connect it to nsqlookupd. Once the services are running, nsq_to_file can be used to write messages to nsqd, with appropriate logs confirming successful operations. The NSQ web interface can be accessed at http://127.0.0.1:4171/ to monitor the system status.
NSQ Architecture Overview
NSQ is composed of several key components:
- nsqlookupd: Maintains cluster metadata and provides both TCP and HTTP services. The TCP service handles connections from
nsqd, while the HTTP service supplies cluster data tonsqadmin. - nsqadmin: A web interface built on HTTP services for querying and visualizing cluster state.
- nsqd: The core message broker offering both TCP and HTTP endpoints for producers and consumers. It also communicates its status to
nsqlookupdvia TCP.
Producers (writers) and consumers (readers) connect directly to nsqd instences. Heartbeat mechanisms ensure nsqd nodes remain registered with nsqlookupd.
Graceful Startup and Shutdown
NSQ utilizes the SVC package for managing service lifecycle, enabling graceful startup and shutdown. This ensures all active connections and background processes are properly handled before termination.
Message ID Generation
Each message in NSQ is assigned a unique identifier. This is critical for tracking and deduplication. The implementation ensures global uniqueness across the system.
Concurrency with WaitGroupWrapper
NSQ wraps Go's sync.WaitGroup to manage concurrent tasks. The WaitGroupWrapper struct allows functions to be executed in goroutines, ensuring the main thread waits until all child tasks complete.
type WaitGroupWrapper struct {
sync.WaitGroup
}
func (w *WaitGroupWrapper) Wrap(cb func()) {
w.Add(1)
go func() {
cb()
w.Done()
}()
}
Go Interfaces in NSQ
NSQ leverages Go's implicit interface implementation. For instance, the httpServer struct implements the http.Handler interface by defining ServeHTTP, allowing it to be used wherever an http.Handler is expected.
func (s *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s.router.ServeHTTP(w, req)
}
Heartbeat Mechanism in NSQD
Each nsqd instance periodically sends a heartbeat to all connected nsqlookupd servers to indicate it's still active. This is implemented using a ticker in the lookupLoop function.
case <-ticker:
for _, lookupPeer := range lookupPeers {
n.logf("LOOKUPD(%s): sending heartbeat", lookupPeer)
cmd := nsq.Ping()
_, err := lookupPeer.Command(cmd)
if err != nil {
n.logf("LOOKUPD(%s): ERROR %s - %s", lookupPeer, cmd, err)
}
}
Decorator Pattern in HTTP Handlers
NSQ uses a decorator pattern to enhance HTTP handlers with additional functionality, such as logging. The Decorator type wraps an APIHandler to add behavior before or after request processing.
type Decorator func(APIHandler) APIHandler
type APIHandler func(http.ResponseWriter, *http.Request, httprouter.Params) (interface{}, error)
func Decorate(f APIHandler, ds ...Decorator) httprouter.Handle {
decorated := f
for _, decorate := range ds {
decorated = decorate(decorated)
}
return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
decorated(w, req, ps)
}
}
func Log(l app.Logger) Decorator {
return func(f APIHandler) APIHandler {
return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) {
start := time.Now()
response, err := f(w, req, ps)
elapsed := time.Since(start)
status := 200
if e, ok := err.(Err); ok {
status = e.Code
}
l.Output(2, fmt.Sprintf("%d %s %s (%s) %s", status, req.Method, req.URL.RequestURI(), req.RemoteAddr, elapsed))
return response, err
}
}
}