├── version ├── broker ├── store.go ├── broker_test.go ├── broker_store_test.go ├── broker.go ├── store_pool.go ├── message_store.go ├── meta.go └── broker_store.go ├── simpleq.conf ├── client ├── conn.go ├── conn_pool.go ├── client_test.go └── client.go ├── README.md ├── simpleq.go ├── config └── Config.go └── server └── server.go /version: -------------------------------------------------------------------------------- 1 | 0.0.5 -------------------------------------------------------------------------------- /broker/store.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | type Store interface { 4 | Write(data []byte) error 5 | Read(group []byte, size int) ([][]byte, error) 6 | Close() error 7 | } 8 | -------------------------------------------------------------------------------- /simpleq.conf: -------------------------------------------------------------------------------- 1 | 2 | #server conf 3 | server.host:0.0.0.0 4 | server.port:9090 5 | 6 | #data dir 7 | data.dir:/data/simpleq 8 | 9 | #log file 10 | #log.file:/data/logs/simpleq.log 11 | -------------------------------------------------------------------------------- /broker/broker_test.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBrokerWrite(t *testing.T) { 8 | b := NewBroker("/home/wens/data") 9 | 10 | if err := b.Write([]byte("topic2"), []byte("hi wens!")); err != nil { 11 | t.Error("err:", err) 12 | } 13 | 14 | } 15 | 16 | func TestBrokerRead(t *testing.T) { 17 | b := NewBroker("/home/wens/data") 18 | 19 | data, _ := b.Read([]byte("t2"), []byte("my"), 10) 20 | 21 | for _, m := range data { 22 | t.Log(string(m)) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /client/conn.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "net" 8 | ) 9 | 10 | var ACK_ERR = errors.New("ack error") 11 | 12 | type Conn struct { 13 | conn *net.TCPConn 14 | rw *bufio.ReadWriter 15 | } 16 | 17 | func OpenConn(host string, port int) (*Conn, error) { 18 | addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port)) 19 | 20 | if err != nil { 21 | return nil, err 22 | } 23 | listener, err := net.DialTCP("tcp", nil, addr) 24 | 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &Conn{conn: listener, rw: bufio.NewReadWriter(bufio.NewReader(listener), bufio.NewWriter(listener))}, nil 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simpleq 2 | golang实现的消息队列服务,支持消费主题(topic)以及消费者分组(group) 3 | 4 | 目前实现了基本的功能模块,后面的精力主要放在主从复制,数据清除,还有性能优化。 5 | 6 | ## 使用方式 7 | 8 | ### 安装 9 | 10 | 需要go1.2或以上版本 11 | ```sh 12 | go get github.com/wenzuojing/simpleq 13 | cd $GOPATH/src/github.com/wenzuojing/simpleq 14 | sh build.sh 15 | ``` 16 | 17 | ### 启动 18 | 19 | ```sh 20 | ./simpleq -conf simpleq.conf 21 | ``` 22 | 23 | ### 发布消息 24 | 25 | ```golang 26 | 27 | client, _ := SimpleqClient("localhost", 9090, 1) 28 | 29 | client.Publish([]byte("topic1"), []byte("hi wens")) 30 | ``` 31 | 32 | ### 消费消息 33 | 34 | ```golang 35 | 36 | client, _ := SimpleqClient("localhost", 9090, 1) 37 | 38 | msgs, _ := client.Consume([]byte("topic1"), []byte("g2"), 20) 39 | 40 | for _ , msg := range msgs { 41 | //do something 42 | } 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /client/conn_pool.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type ConnPool struct { 8 | poolSize int 9 | host string 10 | port int 11 | pool chan *Conn 12 | } 13 | 14 | func (pool *ConnPool) BorrowConn() (*Conn, error) { 15 | 16 | conn := <-pool.pool 17 | 18 | if conn == nil { 19 | return OpenConn(pool.host, pool.port) 20 | } 21 | return conn, nil 22 | } 23 | 24 | func (pool *ConnPool) ReturnConn(conn *Conn) { 25 | select { 26 | case pool.pool <- conn: 27 | case <-time.After(time.Second): 28 | conn.conn.Close() 29 | } 30 | 31 | } 32 | 33 | func (pool *ConnPool) GetPoolSize() int { 34 | return pool.poolSize 35 | } 36 | 37 | func NewConnPool(host string, port, poolSize int) *ConnPool { 38 | 39 | p := make(chan *Conn, poolSize) 40 | 41 | for i := 0; i < poolSize; i++ { 42 | p <- nil 43 | } 44 | 45 | return &ConnPool{host: host, port: port, poolSize: poolSize, pool: p} 46 | } 47 | -------------------------------------------------------------------------------- /simpleq.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/wenzuojing/simpleq/config" 6 | "github.com/wenzuojing/simpleq/server" 7 | "log" 8 | "os" 9 | "os/signal" 10 | ) 11 | 12 | func main() { 13 | 14 | configPath := flag.String("conf", "/home/wens/go/src/github.com/wenzuojing/simpleq/simpleq.conf", "-conf simpleq.conf") 15 | 16 | flag.Parse() 17 | log.SetPrefix("[simpleq]") 18 | log.SetFlags(log.Lshortfile) 19 | 20 | config, err := config.LoadConfig(*configPath) 21 | 22 | if err != nil { 23 | log.Fatalf("Read config fail. %v", err) 24 | } 25 | 26 | if logFile, found := config.String("log.file"); found { 27 | if file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666); err == nil { 28 | defer file.Close() 29 | log.SetOutput(file) 30 | } 31 | 32 | } 33 | 34 | go func() { 35 | err := server.StartServer(config) 36 | if err != nil { 37 | log.Fatalf("[error] start server fail.\n%v\n", err) 38 | } 39 | 40 | }() 41 | 42 | //Kill the app on signal. 43 | ch := make(chan os.Signal) 44 | signal.Notify(ch, os.Interrupt, os.Kill) 45 | <-ch 46 | os.Exit(1) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /broker/broker_store_test.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestWrite(t *testing.T) { 11 | 12 | brokerStore := NewBrokerStore("/home/wens/data", "topic") 13 | 14 | start := time.Now() 15 | for i := 1; i <= 10001; i++ { 16 | err := brokerStore.Write([]byte("hello word !" + strconv.Itoa(i))) 17 | if err != nil { 18 | t.Errorf("Put fail : %v ", err) 19 | } 20 | } 21 | 22 | end := time.Now() 23 | 24 | t.Log(end.Sub(start).Seconds()) 25 | 26 | err := brokerStore.Close() 27 | 28 | if err != nil { 29 | t.Errorf("Close fail : %v ", err) 30 | } 31 | 32 | } 33 | 34 | func TestRead(t *testing.T) { 35 | 36 | brokerStore := NewBrokerStore("/home/wens/data", "t1") 37 | 38 | start := time.Now() 39 | 40 | for { 41 | 42 | bb, err := brokerStore.Read([]byte("my2"), 10) 43 | 44 | if err != nil { 45 | t.Errorf("Get fail : %v ", err) 46 | } 47 | 48 | if len(bb) == 0 { 49 | log.Println("not data") 50 | time.Sleep(time.Second) 51 | 52 | } else { 53 | for _, d := range bb { 54 | log.Println(string(d)) 55 | } 56 | } 57 | 58 | } 59 | 60 | end := time.Now() 61 | 62 | t.Log(end.Sub(start).Seconds()) 63 | 64 | err := brokerStore.Close() 65 | 66 | if err != nil { 67 | t.Errorf("Close fail : %v ", err) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /broker/broker.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "sync" 7 | ) 8 | 9 | type Broker struct { 10 | mu sync.Mutex 11 | topicStore map[string]Store 12 | dataDir string 13 | } 14 | 15 | func (broker *Broker) Write(topic, data []byte) error { 16 | 17 | if store, err := broker.getStore(topic, true); err == nil { 18 | return store.Write(data) 19 | } else { 20 | return err 21 | } 22 | } 23 | 24 | func (broker *Broker) Read(topic, group []byte, size int) ([][]byte, error) { 25 | 26 | if store, err := broker.getStore(topic, false); err == nil { 27 | var data [][]byte 28 | if store != nil { 29 | if data, err = store.Read(group, size); err != nil { 30 | return nil, err 31 | } 32 | } 33 | return data, nil 34 | 35 | } else { 36 | return nil, err 37 | } 38 | 39 | } 40 | 41 | func (broker *Broker) getStore(topic []byte, createIfNotExit bool) (Store, error) { 42 | broker.mu.Lock() 43 | defer broker.mu.Unlock() 44 | if store, ok := broker.topicStore[string(topic)]; ok { 45 | return store, nil 46 | } 47 | 48 | if _, err := os.Stat(path.Join(broker.dataDir, string(topic))); createIfNotExit || err == nil { 49 | store := NewBrokerStore(broker.dataDir, string(topic)) 50 | broker.topicStore[string(topic)] = store 51 | return store, nil 52 | } 53 | 54 | return nil, nil 55 | } 56 | 57 | func NewBroker(dataDir string) *Broker { 58 | return &Broker{dataDir: dataDir, topicStore: make(map[string]Store)} 59 | } 60 | -------------------------------------------------------------------------------- /client/client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestPublish(t *testing.T) { 9 | client, err := SimpleqClient("localhost", 9090, 1) 10 | 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | 15 | err = client.Publish([]byte("topic1"), []byte("hi wens")) 16 | 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | 21 | } 22 | 23 | func TestPublish2(t *testing.T) { 24 | 25 | n := 100000 26 | c := 100 27 | 28 | client, err := SimpleqClient("192.168.9.52", 9090, c) 29 | 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | 34 | for i := 0; i < n; i++ { 35 | err = client.Publish([]byte("topic1"), []byte("hi wens")) 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | } 40 | 41 | t.Log("Completed") 42 | 43 | } 44 | 45 | func TestPublish3(t *testing.T) { 46 | 47 | n := 100000 48 | c := 100 49 | 50 | client, err := SimpleqClient("192.168.9.52", 9090, c) 51 | 52 | ch := make(chan int, c) 53 | 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | 58 | var wg sync.WaitGroup 59 | 60 | for i := 0; i < n; i++ { 61 | wg.Add(1) 62 | ch <- 1 63 | go func() { 64 | 65 | err = client.Publish([]byte("topic1"), []byte("hi wens")) 66 | <-ch 67 | wg.Done() 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | }() 72 | 73 | } 74 | 75 | wg.Wait() 76 | 77 | t.Log("Completed") 78 | 79 | } 80 | 81 | func TestConsume(t *testing.T) { 82 | client, err := SimpleqClient("localhost", 9090, 1) 83 | 84 | if err != nil { 85 | t.Error(err) 86 | } 87 | 88 | msg, err := client.Consume([]byte("topic1"), []byte("g2"), 20) 89 | 90 | if err != nil { 91 | t.Error(err) 92 | } 93 | 94 | t.Log(len(msg)) 95 | 96 | } 97 | -------------------------------------------------------------------------------- /config/Config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/revel/config" 5 | ) 6 | 7 | type SimpleqConfig struct { 8 | config *config.Config 9 | section string 10 | } 11 | 12 | func LoadConfig(configPath string) (*SimpleqConfig, error) { 13 | 14 | config, err := config.ReadDefault(configPath) 15 | 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return &SimpleqConfig{config, ""}, nil 21 | 22 | } 23 | 24 | func (c *SimpleqConfig) SetSection(section string) { 25 | c.section = section 26 | } 27 | 28 | func (c *SimpleqConfig) Int(option string) (result int, found bool) { 29 | result, err := c.config.Int(c.section, option) 30 | if err == nil { 31 | return result, true 32 | } else { 33 | return 0, false 34 | } 35 | 36 | } 37 | 38 | func (c *SimpleqConfig) IntDefault(option string, dfault int) int { 39 | if r, found := c.Int(option); found { 40 | return r 41 | } 42 | return dfault 43 | } 44 | 45 | func (c *SimpleqConfig) Bool(option string) (result, found bool) { 46 | result, err := c.config.Bool(c.section, option) 47 | if err == nil { 48 | return result, true 49 | } else { 50 | return false, false 51 | } 52 | } 53 | 54 | func (c *SimpleqConfig) BoolDefault(option string, dfault bool) bool { 55 | if r, found := c.Bool(option); found { 56 | return r 57 | } 58 | return dfault 59 | } 60 | 61 | func (c *SimpleqConfig) String(option string) (result string, found bool) { 62 | if r, err := c.config.String(c.section, option); err == nil { 63 | return r, true 64 | } 65 | return "", false 66 | } 67 | 68 | func (c *SimpleqConfig) StringDefault(option, dfault string) string { 69 | if r, found := c.String(option); found { 70 | return r 71 | } 72 | return dfault 73 | } 74 | -------------------------------------------------------------------------------- /broker/store_pool.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "github.com/syndtr/goleveldb/leveldb" 5 | "log" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type WrapStore struct { 11 | store Store 12 | lastUsed time.Time 13 | } 14 | 15 | func (wq *WrapStore) Write(data []byte) error { 16 | wq.lastUsed = time.Now() 17 | return wq.store.Write(data) 18 | } 19 | 20 | func (wq *WrapStore) Read(group []byte, size int) ([][]byte, error) { 21 | wq.lastUsed = time.Now() 22 | return wq.store.Read(group, size) 23 | } 24 | 25 | func (wq *WrapStore) Close() error { 26 | wq.lastUsed = time.Now() 27 | return wq.store.Close() 28 | } 29 | 30 | var openedStores = make(map[string]*WrapStore) 31 | 32 | var mu sync.Mutex 33 | 34 | func init() { 35 | 36 | go func() { 37 | ticker := time.NewTicker(time.Minute) 38 | for { 39 | <-ticker.C 40 | log.Println("bengin check unuse store ") 41 | if err := closeUnuseStore(); err != nil { 42 | log.Println("check unuse store fail . ", err) 43 | } 44 | log.Println("complete check unuse store ") 45 | 46 | } 47 | }() 48 | } 49 | 50 | func closeUnuseStore() error { 51 | mu.Lock() 52 | defer mu.Unlock() 53 | 54 | removed := make([]string, 0) 55 | for d, q := range openedStores { 56 | if time.Now().Sub(q.lastUsed).Minutes() > 30 { 57 | if err := q.Close(); err != nil { 58 | return err 59 | } 60 | removed = append(removed, d) 61 | log.Printf("close store :%s\n", d) 62 | 63 | } 64 | } 65 | 66 | for _, d := range removed { 67 | delete(openedStores, d) 68 | } 69 | 70 | return nil 71 | 72 | } 73 | 74 | func GetStore(dataDir string, meta *Meta) *WrapStore { 75 | mu.Lock() 76 | defer mu.Unlock() 77 | if store, ok := openedStores[dataDir]; ok { 78 | return store 79 | } 80 | db, err := leveldb.OpenFile(dataDir, nil) 81 | 82 | if err != nil { 83 | panic(err) 84 | } 85 | messageStore := &MessageStore{meta: meta, db: db} 86 | store := &WrapStore{store: messageStore} 87 | openedStores[dataDir] = store 88 | return store 89 | } 90 | -------------------------------------------------------------------------------- /broker/message_store.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/syndtr/goleveldb/leveldb" 7 | "github.com/syndtr/goleveldb/leveldb/util" 8 | "sync" 9 | ) 10 | 11 | var MAX_SIZE uint64 = 1000000 12 | var FULL_SIZE_ERROR = errors.New("store full size") 13 | var STORE_CLOSED_ERROR = errors.New("store closed") 14 | var EOF_ERROR = errors.New("read store end") 15 | 16 | type MessageStore struct { 17 | mu sync.Mutex 18 | meta *Meta 19 | db *leveldb.DB 20 | } 21 | 22 | func (store *MessageStore) Write(data []byte) error { 23 | 24 | store.mu.Lock() 25 | defer store.mu.Unlock() 26 | 27 | size, err := store.meta.Size() 28 | 29 | if err != nil { 30 | return err 31 | } 32 | 33 | if size >= MAX_SIZE { 34 | 35 | return FULL_SIZE_ERROR 36 | } 37 | 38 | key := fmt.Sprintf("%020d", size) 39 | 40 | err = store.db.Put([]byte(key), data, nil) 41 | 42 | if err != nil { 43 | if err == leveldb.ErrClosed { 44 | return STORE_CLOSED_ERROR 45 | } 46 | return err 47 | } 48 | 49 | return store.meta.SetSize(size + 1) 50 | } 51 | 52 | func (store *MessageStore) Read(group []byte, size int) ([][]byte, error) { 53 | store.mu.Lock() 54 | defer store.mu.Unlock() 55 | 56 | position, err := store.meta.GetReadPosition(group) 57 | 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | if position >= MAX_SIZE { 63 | return nil, EOF_ERROR 64 | } 65 | 66 | start := fmt.Sprintf("%020d", position) 67 | limit := fmt.Sprintf("%020d", position+uint64(size)) 68 | 69 | it := store.db.NewIterator(&util.Range{Start: []byte(start), Limit: []byte(limit)}, nil) 70 | defer it.Release() 71 | 72 | result := make([][]byte, 0, size) 73 | for it.Next() { 74 | value := make([]byte, len(it.Value())) 75 | copy(value, it.Value()) 76 | result = append(result, value) 77 | } 78 | newPosition := position + uint64(len(result)) 79 | err = store.meta.SetReadPosition(group, newPosition) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return result, nil 84 | 85 | } 86 | 87 | func (store *MessageStore) Close() error { 88 | return store.db.Close() 89 | } 90 | -------------------------------------------------------------------------------- /broker/meta.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/syndtr/goleveldb/leveldb" 6 | "path" 7 | "sync" 8 | ) 9 | 10 | type Meta struct { 11 | mu sync.Mutex 12 | db *leveldb.DB 13 | } 14 | 15 | func (meta *Meta) Size() (uint64, error) { 16 | 17 | value, err := meta.db.Get([]byte("size"), nil) 18 | 19 | if err != nil { 20 | if leveldb.ErrNotFound == err { 21 | return 0, nil 22 | } 23 | return 0, nil 24 | } 25 | 26 | return binary.BigEndian.Uint64(value), nil 27 | } 28 | 29 | func (meta *Meta) SetSize(size uint64) error { 30 | bb := make([]byte, 8) 31 | binary.BigEndian.PutUint64(bb, size) 32 | return meta.db.Put([]byte("size"), bb, nil) 33 | } 34 | 35 | func (meta *Meta) GetReadPosition(group []byte) (uint64, error) { 36 | 37 | position, err := meta.db.Get(group, nil) 38 | if err != nil { 39 | if leveldb.ErrNotFound == err { 40 | return uint64(0), nil 41 | } 42 | return 0, err 43 | } 44 | 45 | return binary.BigEndian.Uint64(position), nil 46 | } 47 | 48 | func (meta *Meta) SetReadPosition(group []byte, position uint64) error { 49 | bb := make([]byte, 8) 50 | binary.BigEndian.PutUint64(bb, position) 51 | return meta.db.Put(group, bb, nil) 52 | } 53 | 54 | func (meta *Meta) GetWriteStoreSeq() (uint32, error) { 55 | seq, err := meta.db.Get([]byte("write_seq"), nil) 56 | if err != nil { 57 | if leveldb.ErrNotFound == err { 58 | err := meta.SetWriteStoreSeq(uint32(0)) 59 | 60 | if err != nil { 61 | return uint32(0), err 62 | } 63 | 64 | return uint32(0), nil 65 | } 66 | return 0, err 67 | } 68 | 69 | return binary.BigEndian.Uint32(seq), nil 70 | } 71 | 72 | func (meta *Meta) SetWriteStoreSeq(seq uint32) error { 73 | bb := make([]byte, 4) 74 | binary.BigEndian.PutUint32(bb, seq) 75 | return meta.db.Put([]byte("write_seq"), bb, nil) 76 | } 77 | 78 | func (meta *Meta) GetReadStoreSeq(group []byte) (uint32, error) { 79 | seq, err := meta.db.Get([]byte(string(group)+"read_seq"), nil) 80 | if err != nil { 81 | if leveldb.ErrNotFound == err { 82 | return uint32(0), nil 83 | } 84 | return 0, err 85 | } 86 | 87 | return binary.BigEndian.Uint32(seq), nil 88 | } 89 | 90 | func (meta *Meta) SetReadStoreSeq(group []byte, seq uint32) error { 91 | bb := make([]byte, 4) 92 | binary.BigEndian.PutUint32(bb, seq) 93 | return meta.db.Put([]byte(string(group)+"read_seq"), bb, nil) 94 | } 95 | 96 | func (meta Meta) Close() { 97 | meta.db.Close() 98 | } 99 | 100 | func NewMeta(dataDir string) *Meta { 101 | db, err := leveldb.OpenFile(path.Join(dataDir, "_meta"), nil) 102 | if err != nil { 103 | panic(err) 104 | } 105 | return &Meta{db: db} 106 | 107 | } 108 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "strconv" 9 | ) 10 | 11 | type Client struct { 12 | host string 13 | port int 14 | connPool *ConnPool 15 | } 16 | 17 | var ( 18 | PUBLISH_FAIL = errors.New("publish fail") 19 | CONSUME_FAIL = errors.New("consume fail") 20 | 21 | HEARTBEAT_ERR = errors.New("heartbeat error") 22 | ) 23 | 24 | func (client *Client) Publish(topic, msg []byte) error { 25 | 26 | conn, err := client.connPool.BorrowConn() 27 | defer client.connPool.ReturnConn(conn) 28 | 29 | if err != nil { 30 | return err 31 | } 32 | 33 | bb := commandBytes("publish", topic, msg) 34 | 35 | _, err = conn.rw.Write(bb) 36 | 37 | if err != nil { 38 | return err 39 | } 40 | conn.rw.Flush() 41 | _, err = readResponse(conn.rw) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | return nil 47 | } 48 | 49 | func (client *Client) Consume(topic, group []byte, maxSize int) ([][]byte, error) { 50 | 51 | conn, err := client.connPool.BorrowConn() 52 | defer client.connPool.ReturnConn(conn) 53 | 54 | bb := commandBytes("consume", topic, group, []byte(fmt.Sprintf("%d", maxSize))) 55 | 56 | _, err = conn.rw.Write(bb) 57 | 58 | if err != nil { 59 | return nil, err 60 | } 61 | conn.rw.Flush() 62 | 63 | result, err := readResponse(conn.rw) 64 | 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return result.([][]byte), nil 70 | } 71 | 72 | func commandBytes(cmd string, args ...[]byte) []byte { 73 | var buffer bytes.Buffer 74 | 75 | fmt.Fprintf(&buffer, "*%d\r\n$%d\r\n%s\r\n", len(args)+1, len(cmd), cmd) 76 | 77 | for _, arg := range args { 78 | fmt.Fprintf(&buffer, "$%d\r\n", len(arg)) 79 | buffer.Write(arg) 80 | buffer.Write([]byte("\r\n")) 81 | } 82 | return buffer.Bytes() 83 | } 84 | 85 | func readResponse(rw *bufio.ReadWriter) (interface{}, error) { 86 | 87 | header, err := rw.ReadBytes('\n') 88 | 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | header = trimRightCRLF(header) 94 | 95 | if bytes.HasPrefix(header, []byte("+")) { 96 | return header[1:], nil 97 | } 98 | 99 | if bytes.HasPrefix(header, []byte("-")) { 100 | return nil, errors.New(string(header[1:])) 101 | } 102 | 103 | if bytes.HasPrefix(header, []byte("$")) { 104 | length, _ := strconv.Atoi(string(header[1])) 105 | 106 | bb := make([]byte, length) 107 | _, err := rw.Read(bb) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | _, err = rw.ReadBytes('\n') 113 | 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | return bb, nil 119 | } 120 | 121 | if bytes.HasPrefix(header, []byte("*")) { 122 | mLen, _ := strconv.Atoi(string(header[1:])) 123 | 124 | res := make([][]byte, 0, mLen/2) 125 | for i := 0; i < mLen; i++ { 126 | lenBytes, err := rw.ReadBytes('\n') 127 | if err != nil { 128 | return nil, err 129 | } 130 | lenBytes = trimRightCRLF(lenBytes) 131 | length, _ := strconv.Atoi(string(lenBytes[1:])) 132 | bb := make([]byte, length) 133 | _, err = rw.Read(bb) 134 | if err != nil { 135 | return nil, err 136 | } 137 | res = append(res, bb) 138 | 139 | _, err = rw.ReadBytes('\n') 140 | 141 | if err != nil { 142 | return nil, err 143 | } 144 | } 145 | 146 | return res, nil 147 | } 148 | return nil, errors.New("Error response.") 149 | 150 | } 151 | 152 | func trimRightCRLF(src []byte) []byte { 153 | if bytes.HasSuffix(src, []byte("\r\n")) { 154 | return bytes.TrimRight(src, "\r\n") 155 | } 156 | 157 | if bytes.HasSuffix(src, []byte("\n")) { 158 | return bytes.TrimRight(src, "\n") 159 | } 160 | 161 | return src 162 | 163 | } 164 | 165 | func SimpleqClient(host string, port, connSize int) (*Client, error) { 166 | 167 | pool := NewConnPool(host, port, connSize) 168 | 169 | return &Client{host: host, port: port, connPool: pool}, nil 170 | 171 | } 172 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "github.com/wenzuojing/simpleq/broker" 9 | "github.com/wenzuojing/simpleq/config" 10 | "io" 11 | "log" 12 | "net" 13 | "runtime" 14 | "strconv" 15 | "time" 16 | ) 17 | 18 | type brokerService struct { 19 | broker *broker.Broker 20 | } 21 | 22 | func (bs *brokerService) handlePublish(args [][]byte, rw *bufio.ReadWriter) error { 23 | 24 | if len(args) != 2 { 25 | return errors.New("Bad parameter.") 26 | } 27 | 28 | topic := args[0] 29 | msg := args[1] 30 | 31 | err := bs.broker.Write(topic, msg) 32 | 33 | if err != nil { 34 | return err 35 | } 36 | 37 | _, err = rw.Write([]byte("+ok\r\n")) 38 | 39 | if err != nil { 40 | return err 41 | } 42 | 43 | rw.Flush() 44 | return nil 45 | 46 | } 47 | 48 | func (bs *brokerService) handleConsume(args [][]byte, rw *bufio.ReadWriter) error { 49 | 50 | if len(args) != 3 { 51 | return errors.New("Bad parameter.") 52 | } 53 | 54 | topic := args[0] 55 | group := args[1] 56 | size, _ := strconv.Atoi(string(args[2])) 57 | 58 | msgs, err := bs.broker.Read(topic, group, size) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | var buffer bytes.Buffer 64 | 65 | buffer.Write([]byte(fmt.Sprintf("*%d\r\n", len(msgs)))) 66 | 67 | for _, msg := range msgs { 68 | 69 | buffer.Write([]byte(fmt.Sprintf("$%d\r\n", len(msg)))) 70 | buffer.Write(msg) 71 | buffer.Write([]byte("\r\n")) 72 | 73 | } 74 | _, err = rw.Write(buffer.Bytes()) 75 | 76 | if err != nil { 77 | return err 78 | } 79 | rw.Flush() 80 | return nil 81 | } 82 | 83 | func (c *brokerService) handleConn(conn *net.TCPConn) { 84 | defer func() { 85 | if x := recover(); x != nil { 86 | log.Printf("[error] %v\r\n", x) 87 | } 88 | }() 89 | defer conn.Close() 90 | 91 | rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) 92 | 93 | for { 94 | conn.SetReadDeadline(time.Now().Add(time.Minute * 10)) 95 | 96 | cmd, args, err := parseRequest(rw) 97 | 98 | if err != nil { 99 | panic(err) 100 | } 101 | 102 | switch string(cmd) { 103 | 104 | case "publish": 105 | 106 | err := c.handlePublish(args, rw) 107 | 108 | if err != nil { 109 | panic(err) 110 | } 111 | 112 | break 113 | case "consume": 114 | err := c.handleConsume(args, rw) 115 | if err != nil { 116 | panic(err) 117 | } 118 | break 119 | case "heartbeat": 120 | if err := writeBytes(rw, []byte("ok\n")); err != nil { 121 | panic(err) 122 | } 123 | 124 | rw.Flush() 125 | 126 | default: 127 | panic("unknow cmd :" + string(cmd)) 128 | } 129 | 130 | } 131 | 132 | } 133 | 134 | func StartServer(config *config.SimpleqConfig) error { 135 | runtime.GOMAXPROCS(runtime.NumCPU()) 136 | host := config.StringDefault("server.host", "0.0.0.0.0") 137 | port := config.IntDefault("server.port", 9090) 138 | 139 | dataDir := config.StringDefault("data.dir", "/tmp/simpleq") 140 | brokerService := &brokerService{broker.NewBroker(dataDir)} 141 | 142 | addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port)) 143 | if err != nil { 144 | return err 145 | } 146 | listener, err := net.ListenTCP("tcp", addr) 147 | if err != nil { 148 | return err 149 | } 150 | go func() { 151 | for { 152 | conn, err := listener.AcceptTCP() 153 | if err != nil { 154 | log.Fatalf("[error]%v", err) 155 | } 156 | go brokerService.handleConn(conn) 157 | } 158 | }() 159 | return nil 160 | } 161 | 162 | func parseRequest(rw *bufio.ReadWriter) (cmd []byte, args [][]byte, err error) { 163 | 164 | header, err := rw.ReadBytes('\n') 165 | if err != nil { 166 | return 167 | } 168 | 169 | header = trimRightCRLF(header) 170 | 171 | if bytes.HasPrefix(header, []byte("*")) { 172 | 173 | mLen, _ := strconv.Atoi(string(header[1])) 174 | args = make([][]byte, 0, mLen/2) 175 | 176 | for i := 0; i < mLen; i++ { 177 | lenBytes, err := rw.ReadBytes('\n') 178 | 179 | if err != nil { 180 | return nil, nil, err 181 | } 182 | 183 | lenBytes = trimRightCRLF(lenBytes) 184 | length, _ := strconv.Atoi(string(lenBytes[1:])) 185 | 186 | bb := make([]byte, length) 187 | _, err = rw.Read(bb) 188 | 189 | if err != nil { 190 | return nil, nil, err 191 | } 192 | 193 | bb = trimRightCRLF(bb) 194 | args = append(args, bb) 195 | 196 | _, err = rw.ReadBytes('\n') 197 | 198 | if err != nil { 199 | return nil, nil, err 200 | } 201 | } 202 | 203 | cmd = args[0] 204 | args = args[1:] 205 | return 206 | } else { 207 | err = errors.New("Error protocol") 208 | return 209 | } 210 | 211 | } 212 | 213 | func trimRightCRLF(src []byte) []byte { 214 | if bytes.HasSuffix(src, []byte("\r\n")) { 215 | return bytes.TrimRight(src, "\r\n") 216 | } 217 | 218 | if bytes.HasSuffix(src, []byte("\n")) { 219 | return bytes.TrimRight(src, "\n") 220 | } 221 | 222 | return src 223 | 224 | } 225 | 226 | func writeBytes(writer io.Writer, data []byte) error { 227 | _, err := writer.Write(data) 228 | if err != nil { 229 | return err 230 | } 231 | return nil 232 | } 233 | -------------------------------------------------------------------------------- /broker/broker_store.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "path" 8 | "sync" 9 | ) 10 | 11 | var NO_READ_STORE_ERROR = errors.New("no read store") 12 | 13 | type BrokerStore struct { 14 | mu sync.Mutex 15 | writeStore Store 16 | writeStoreSeq uint32 17 | meta *Meta 18 | dataDir string 19 | name string 20 | mur sync.Mutex 21 | readStores map[string]Store 22 | } 23 | 24 | func (brokerStore *BrokerStore) Write(data []byte) error { 25 | 26 | brokerStore.mu.Lock() 27 | defer brokerStore.mu.Unlock() 28 | 29 | err := brokerStore.doWrite(data) 30 | 31 | if err != nil { 32 | if err == STORE_CLOSED_ERROR { 33 | brokerStore.writeStore = GetStore(path.Join(brokerStore.dataDir, brokerStore.name, fmt.Sprintf("%06d", brokerStore.writeStoreSeq)), brokerStore.meta) 34 | return brokerStore.doWrite(data) 35 | } 36 | 37 | if err == FULL_SIZE_ERROR { 38 | err = brokerStore.rollingWriteStore() 39 | if err != nil { 40 | return err 41 | } 42 | return brokerStore.doWrite(data) 43 | } 44 | 45 | return err 46 | } 47 | 48 | return nil 49 | 50 | } 51 | 52 | func (brokerStore *BrokerStore) doWrite(data []byte) error { 53 | return brokerStore.writeStore.Write(data) 54 | } 55 | 56 | func (brokerStore *BrokerStore) Read(group []byte, size int) ([][]byte, error) { 57 | 58 | store, err := brokerStore.getReadStore(group) 59 | if err != nil { 60 | if err == NO_READ_STORE_ERROR { 61 | return make([][]byte, 0), nil 62 | } 63 | return nil, err 64 | } 65 | 66 | data, err := store.Read(group, size) 67 | 68 | if err != nil { 69 | if err == EOF_ERROR { 70 | if err := brokerStore.rollingReadStore(group); err != nil { 71 | return nil, err 72 | } else { 73 | return brokerStore.Read(group, size) 74 | } 75 | } 76 | return nil, err 77 | } 78 | return data, nil 79 | } 80 | 81 | func (brokerStore *BrokerStore) Close() error { 82 | if brokerStore.writeStore != nil { 83 | err := brokerStore.writeStore.Close() 84 | return err 85 | } 86 | 87 | for _, v := range brokerStore.readStores { 88 | err := v.Close() 89 | if err != nil { 90 | return err 91 | } 92 | } 93 | 94 | return nil 95 | 96 | } 97 | 98 | func (brokerStore *BrokerStore) getReadStore(group []byte) (Store, error) { 99 | 100 | brokerStore.mur.Lock() 101 | defer brokerStore.mur.Unlock() 102 | brokerStoreueue, ok := brokerStore.readStores[string(group)] 103 | 104 | if !ok { 105 | brokerStoreueue, ok = brokerStore.readStores[string(group)] 106 | readStoreSeq, err := brokerStore.meta.GetReadStoreSeq(group) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | dataDir := path.Join(brokerStore.dataDir, brokerStore.name, fmt.Sprintf("%06d", readStoreSeq)) 112 | log.Printf("Open read brokerStoreueue[%s] for %s", dataDir, group) 113 | brokerStoreueue = GetStore(dataDir, brokerStore.meta) 114 | brokerStore.readStores[string(group)] = brokerStoreueue 115 | } 116 | 117 | return brokerStoreueue, nil 118 | 119 | } 120 | 121 | func (brokerStore *BrokerStore) rollingReadStore(group []byte) error { 122 | brokerStore.mur.Lock() 123 | defer brokerStore.mur.Unlock() 124 | 125 | readStoreSeq, err := brokerStore.meta.GetReadStoreSeq(group) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | if err := brokerStore.meta.SetReadPosition(group, uint64(0)); err != nil { 131 | return err 132 | } 133 | 134 | if readStoreSeq+1 > brokerStore.writeStoreSeq { 135 | return NO_READ_STORE_ERROR 136 | } 137 | newReadSebrokerStore := readStoreSeq + 1 138 | if err := brokerStore.meta.SetReadStoreSeq(group, newReadSebrokerStore); err != nil { 139 | return err 140 | } 141 | log.Printf("rolling read brokerStoreueue for %s to from %d to %d ", group, readStoreSeq, newReadSebrokerStore) 142 | 143 | delete(brokerStore.readStores, string(group)) 144 | 145 | return nil 146 | 147 | } 148 | 149 | func (brokerStore *BrokerStore) rollingWriteStore() error { 150 | writeStoreSeq, err := brokerStore.meta.GetWriteStoreSeq() 151 | if err != nil { 152 | return err 153 | } 154 | newDataDir := path.Join(brokerStore.dataDir, brokerStore.name, fmt.Sprintf("%06d", writeStoreSeq+1)) 155 | writeStore := GetStore(newDataDir, brokerStore.meta) 156 | 157 | if err := brokerStore.meta.SetSize(uint64(0)); err != nil { 158 | return err 159 | } 160 | 161 | if err := brokerStore.meta.SetWriteStoreSeq(writeStoreSeq + 1); err != nil { 162 | return err 163 | } 164 | 165 | brokerStore.writeStore = writeStore 166 | brokerStore.writeStoreSeq = writeStoreSeq 167 | 168 | return nil 169 | } 170 | 171 | func NewBrokerStore(dataDir, name string) *BrokerStore { 172 | 173 | meta := NewMeta(path.Join(dataDir, name)) 174 | 175 | writeStoreSeq, err := meta.GetWriteStoreSeq() 176 | 177 | if err != nil { 178 | panic(err) 179 | } 180 | 181 | writeStore := GetStore(path.Join(dataDir, name, fmt.Sprintf("%06d", writeStoreSeq)), meta) 182 | 183 | return &BrokerStore{writeStore: writeStore, writeStoreSeq: writeStoreSeq, meta: meta, name: name, dataDir: dataDir, readStores: make(map[string]Store)} 184 | } 185 | --------------------------------------------------------------------------------