├── .gitignore ├── .travis.yml ├── README-zh_CN.md ├── README.md ├── command.go ├── conn.go ├── conn_test.go └── put.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | *.iml 4 | out 5 | gen -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7 5 | - 1.6 6 | - 1.7.1 7 | - tip 8 | 9 | before_install: 10 | - git clone https://github.com/kr/beanstalkd && cd beanstalkd 11 | - ls -al 12 | - make 13 | - nohup ./beanstalkd & 14 | - cd .. 15 | 16 | script: go test -v ./... -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 | # [go-beanstalk](https://github.com/liuzhengyang/gobeanstalk) 是[beanstalkd](https://github.com/kr/beanstalkd) 的GO语言的一个客户端. 2 | 项目还在开发中,欢迎大家提意见 3 | 4 | # 介绍 5 | [beanstalkd](https://github.com/kr/beanstalkd)是一个快速的、有各种用途的延迟队列 6 | 和定时任务的不同点: 7 | 定时任务以一定的周期或者在某个特定的时间运行。beanstalk可以在延迟一段时间执行。 8 | 一些使用场景: 9 | * 用户下单5分钟后检查用户是否完成了支付 10 | * 一分钟后开始一个新的程序 11 | 12 | # 如何使用 13 | 14 | ## Mac&Linux 15 | ### 安装并启动beantalkd服务器 16 | ``` 17 | git clone https://github.com/kr/beanstalkd 18 | cd beanstalkd 19 | make 20 | ./beanstalkd 21 | ``` 22 | 23 | # 使用示例 24 | ``` 25 | go get github.com/liuzhengyang/gobeanstalk 26 | ``` 27 | 28 | create a test.go file 29 | ``` 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | "github.com/liuzhengyang/gobeanstalk" 35 | ) 36 | 37 | func main() { 38 | addr := "localhost:11300" // define server address 39 | newConn := gobeanstalk.NewConnection(addr) // create new connection 40 | channel := make(chan int) // create int channel 41 | putFunc := func() { 42 | // define a function which put some message to one tube 43 | id, _ := newConn.PutWithTube("hello", "test2", 1) 44 | channel <- id 45 | } 46 | go putFunc() // run previous function in a go-routine 47 | id := <-channel // wait until we finish putting 48 | fmt.Printf("Receive from channel message of another goroutine %d\n", id) 49 | listenChannel := make(chan string) // make a listen channel for receiving results 50 | dealFunc := func(body string) bool { 51 | // define a function to deal with tube messages 52 | fmt.Printf("receive %s\n", body) 53 | listenChannel <- body 54 | return true 55 | } 56 | go newConn.Listen("test2", dealFunc) // run deal function in a specified go-routing 57 | body := <-listenChannel // wait our message 58 | fmt.Printf("Listen once %s\n", body) 59 | newConn.Close() // Close connection 60 | } 61 | 62 | ``` 63 | 64 | And run this 65 | ``` 66 | go run test.go 67 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [gobeanstalk](https://github.com/liuzhengyang/gobeanstalk) is a [beanstalkd](https://github.com/kr/beanstalkd) client for Go. 2 | 3 | [![Join the chat at https://gitter.im/go-beanstalk/Lobby](https://badges.gitter.im/go-beanstalk/Lobby.svg)](https://gitter.im/go-beanstalk/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![travis status](https://travis-ci.org/liuzhengyang/gobeanstalk.svg?branch=master)](https://travis-ci.org/liuzhengyang/gobeanstalk) 5 | 6 | [中文文档] (https://github.com/liuzhengyang/gobeanstalk/blob/master/README-zh_CN.md) 7 | 8 | Now it's a quite simple implementation. I'll keep improving and refactoring it. 9 | 10 | # Introduction 11 | [beanstalkd](https://github.com/kr/beanstalkd) is a fast, general-purpose work queue. 12 | Difference with crontab jobs: 13 | Contab job run with specified period or at some point. But beanstalk can run with a delayed time. 14 | 15 | Some use scenarios: 16 | * Check whether user finish the order in 5 minutes. 17 | * Start a process in one minutes. 18 | 19 | # Implemented Commands 20 | * use 21 | * watch 22 | * delete 23 | * put 24 | * put-with-tube 25 | * reserve 26 | * reserve-with-timeout 27 | * quit 28 | * ignore 29 | 30 | # How to use 31 | 32 | ## Mac 33 | ### Install And Start Beanstalk server 34 | ``` 35 | git clone https://github.com/kr/beanstalkd 36 | cd beanstalkd 37 | make 38 | ./beanstalkd 39 | ``` 40 | 41 | # Examples 42 | ``` 43 | go get github.com/liuzhengyang/gobeanstalk 44 | ``` 45 | 46 | create a test.go file 47 | ``` 48 | package main 49 | 50 | import ( 51 | "fmt" 52 | "github.com/liuzhengyang/gobeanstalk" 53 | ) 54 | 55 | func main() { 56 | addr := "localhost:11300" // define server address 57 | newConn := gobeanstalk.NewConnection(addr) // create new connection 58 | channel := make(chan int) // create int channel 59 | putFunc := func() { 60 | // define a function which put some message to one tube 61 | id, _ := newConn.PutWithTube("hello", "test2", 1) 62 | channel <- id 63 | } 64 | go putFunc() // run previous function in a go-routine 65 | id := <-channel // wait until we finish putting 66 | fmt.Printf("Receive from channel message of another goroutine %d\n", id) 67 | listenChannel := make(chan string) // make a listen channel for receiving results 68 | dealFunc := func(body string) bool { 69 | // define a function to deal with tube messages 70 | fmt.Printf("receive %s\n", body) 71 | listenChannel <- body 72 | return true 73 | } 74 | go newConn.Listen("test2", dealFunc) // run deal function in a specified go-routing 75 | body := <-listenChannel // wait our message 76 | fmt.Printf("Listen once %s\n", body) 77 | newConn.Close() // Close connection 78 | } 79 | 80 | ``` 81 | 82 | And run this 83 | ``` 84 | go run test.go 85 | ``` -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package gobeanstalk 2 | 3 | type Command interface { 4 | GetBytes() []byte 5 | } 6 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package gobeanstalk 2 | 3 | import ( 4 | "strconv" 5 | "fmt" 6 | "net" 7 | "bufio" 8 | "strings" 9 | "errors" 10 | "bytes" 11 | ) 12 | 13 | type Conn struct { 14 | conn net.Conn 15 | addr string 16 | bufReader *bufio.Reader 17 | bufWriter *bufio.Writer 18 | } 19 | 20 | func (this *Conn) WriteAndFlush(data []byte) (int, error) { 21 | num, err := this.bufWriter.Write(data) 22 | this.bufWriter.Flush() 23 | 24 | return num, err 25 | } 26 | 27 | func (this *Conn) WriteAndFlushString(data string) (int, error) { 28 | return this.WriteAndFlush([]byte(data)) 29 | } 30 | 31 | func NewConnection(hostAndIp string) *Conn { 32 | conn, _ := net.Dial("tcp", hostAndIp) 33 | c := new(Conn) 34 | c.conn = conn 35 | c.addr = hostAndIp 36 | c.bufReader = bufio.NewReader(conn) 37 | c.bufWriter = bufio.NewWriter(conn) 38 | 39 | return c 40 | } 41 | 42 | func (this *Conn) Close() { 43 | this.conn.Close() 44 | } 45 | 46 | // haven't deal with fail conditions 47 | func sendAndGetOneLine(conn *Conn, command string) string { 48 | conn.WriteAndFlushString(command) 49 | line, _, _ := conn.bufReader.ReadLine() 50 | res := string(line) 51 | fmt.Printf("res : %s\n", res) 52 | return res 53 | } 54 | 55 | func (this *Conn) PutWithTube(body string, tube string, delay int) (int, error) { 56 | this.Use(tube) 57 | return this.Put(body, delay) 58 | } 59 | 60 | func NewConn(conn net.Conn, addr string) (*Conn, error) { 61 | c := new(Conn) 62 | c.conn = conn 63 | c.addr = addr 64 | c.bufReader = bufio.NewReader(conn) 65 | c.bufWriter = bufio.NewWriter(conn) 66 | return c, nil 67 | } 68 | 69 | func (this *Conn) Use(tube string) (bool, error) { 70 | command := fmt.Sprintf("use %s\r\n", tube) 71 | res := sendAndGetOneLine(this, command) 72 | if strings.HasPrefix(res, "USING") { 73 | return true, nil 74 | } else { 75 | return false, errors.New(res) 76 | } 77 | } 78 | 79 | func (this *Conn) Watch(tube string) (bool, int, error) { 80 | command := fmt.Sprintf("watch %s\r\n", tube) 81 | res := sendAndGetOneLine(this, command) 82 | if strings.HasPrefix(res, "WATCHING") { 83 | fmt.Println(len(res)) 84 | numStr := res[9:] 85 | count, _ := strconv.Atoi(numStr) 86 | fmt.Printf("Watching count %d\n", count) 87 | return true, count, nil 88 | } else { 89 | return false, 0, errors.New(res) 90 | } 91 | } 92 | 93 | func (this *Conn) Put(body string, delay int) (int, error) { 94 | command := NewPut(1, delay, 100, []byte(body)).GetBytes() 95 | this.WriteAndFlush(command) 96 | line, _, _ := this.bufReader.ReadLine() 97 | fmt.Printf("Put answer %s\n", line) 98 | token := strings.Split(string(line), " ") 99 | fmt.Println(string(line)) 100 | jobId, _ := strconv.Atoi(token[1]) 101 | return jobId, nil 102 | } 103 | 104 | func Reserve(conn *Conn) (int, string) { 105 | command := "reserve\r\n" 106 | conn.WriteAndFlushString(command) 107 | line, _, _ := conn.bufReader.ReadLine() 108 | dataline, _, _ := conn.bufReader.ReadLine() 109 | tokens := strings.Split(string(line), " ") 110 | idstr := tokens[1] 111 | id, _ := strconv.Atoi(idstr) 112 | fmt.Printf("Reserve %s\n", string(line)) 113 | fmt.Printf("Reserve %s\n", string(dataline)) 114 | return id, string(dataline) 115 | } 116 | 117 | func (this *Conn) deleteMessage(id int) { 118 | commandStr := fmt.Sprintf("delete %d\r\n", id) 119 | this.WriteAndFlushString(commandStr) 120 | line, _, _ := this.bufReader.ReadLine() 121 | fmt.Printf("delete %s\n", string(line)) 122 | } 123 | 124 | func (this *Conn) Listen(tube string, fun func(body string) bool) { 125 | listenConnection, _ := net.Dial("tcp", this.addr) 126 | newConn, _ := NewConn(listenConnection, this.addr) 127 | newConn.Use(tube) 128 | newConn.Watch(tube) 129 | for { 130 | id, data := Reserve(newConn) 131 | fmt.Printf("Receive %s\n", data) 132 | success := fun(data) 133 | fmt.Printf("Deal Result %s\n", success) 134 | newConn.deleteMessage(id) 135 | } 136 | } 137 | 138 | //func (this *Conn) Stats() map[string] string { 139 | // command := "stats\r\n" 140 | // this.bufWriter.Write([]byte(command)) 141 | // this.bufWriter.Flush() 142 | // line, _ := this.bufReader.ReadSlice('\r') 143 | // numTotal := line[:3] 144 | // this.bufReader.rea 145 | //} 146 | 147 | func (this *Conn) Quit() error { 148 | command := "quit\r\n" 149 | _, err := this.WriteAndFlushString(command) 150 | return err 151 | } 152 | 153 | func (this *Conn) Ignore(tube string) { 154 | command := fmt.Sprintf("ignore %s\r\n", tube) 155 | this.WriteAndFlushString(command) 156 | line, _ := this.bufReader.ReadSlice('\r') 157 | fmt.Printf("Ignore resp: %s\n", line) 158 | } 159 | 160 | 161 | // TODO read dedicated num of bytes, this solution has problems 162 | func (this *Conn) ListTubes() []string { 163 | command := "list-tubes\r\n" 164 | this.WriteAndFlushString(command) 165 | firstLine, _, _ := this.bufReader.ReadLine() 166 | bytesNum, _ := strconv.Atoi(string(firstLine[3:])) 167 | fmt.Printf("BytesNum: %d\n", bytesNum) 168 | res := []byte{} 169 | readData := make([]byte, bytesNum) 170 | fmt.Println("Readd") 171 | num, _ := this.bufReader.Read(readData) 172 | fmt.Printf("Read %d", num) 173 | res = append(res, readData[:num]...) 174 | return ParseYamlList(res) 175 | } 176 | 177 | //func (this *bufio.Writer) WriteAndFlush(data string) { 178 | // 179 | //} 180 | 181 | var ( 182 | yamlHead = []byte{'-', '-', '-', '\n'} 183 | ) 184 | 185 | func ParseYamlList(data []byte) []string { 186 | list := []string{} 187 | fmt.Printf("Receive %s\n", string(data)) 188 | if bytes.HasPrefix(data, yamlHead) { 189 | data = data[4:] 190 | } 191 | for _, s := range bytes.Split(data, []byte{'\n'}) { 192 | if (!bytes.HasPrefix(s, []byte{'-', ' '})) { 193 | continue 194 | } 195 | list = append(list, string(s[2:])) 196 | } 197 | return list 198 | } 199 | 200 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | package gobeanstalk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNewConnection(t *testing.T) { 9 | addr := "localhost:11300" // define server address 10 | newConn := NewConnection(addr) // create new connection 11 | channel := make(chan int) // create int channel 12 | putFunc := func() { 13 | // define a function which put some message to one tube 14 | id, _ := newConn.PutWithTube("hello", "test2", 1) 15 | channel <- id 16 | } 17 | go putFunc() // run previous function in a go-routine 18 | id := <-channel // wait until we finish putting 19 | fmt.Printf("Receive from channel message of another goroutine %d\n", id) 20 | listenChannel := make(chan string) // make a listen channel for receiving results 21 | dealFunc := func(body string) bool { 22 | // define a function to deal with tube messages 23 | fmt.Printf("receive %s\n", body) 24 | listenChannel <- body 25 | return true 26 | } 27 | go newConn.Listen("test2", dealFunc) // run deal function in a specified go-routing 28 | body := <-listenChannel // wait our message 29 | fmt.Printf("Listen once %s\n", body) 30 | newConn.Close() // Close connection 31 | } 32 | 33 | func TestConn_Quit(t *testing.T) { 34 | addr := "localhost:11300" // define server address 35 | newConn := NewConnection(addr) 36 | fmt.Printf("Close %s", newConn) 37 | newConn.Quit() 38 | } 39 | 40 | func TestConn_Ignore(t *testing.T) { 41 | addr := "localhost:11300" // define server address 42 | newConn := NewConnection(addr) 43 | newConn.Watch("test2") 44 | newConn.Ignore("default") 45 | //assertions.ShouldEqual() 46 | } 47 | 48 | func TestConn_ListTubes(t *testing.T) { 49 | addr := "localhost:11300" // define server address 50 | newConn := NewConnection(addr) 51 | newConn.Use("test2") 52 | newConn.Watch("test1") 53 | newConn.Watch("test3") 54 | tubes := newConn.ListTubes() 55 | fmt.Printf("%v\n", tubes) 56 | } 57 | 58 | func TestParseYamlList(t *testing.T) { 59 | data := `--- 60 | - default` 61 | list := ParseYamlList([]byte(data)) 62 | fmt.Printf("%v", list) 63 | } 64 | 65 | -------------------------------------------------------------------------------- /put.go: -------------------------------------------------------------------------------- 1 | package gobeanstalk 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Put struct { 8 | pri int 9 | delay int 10 | ttr int 11 | //bytes int32 12 | body []byte 13 | } 14 | 15 | func NewPut(pri int, delay int, ttr int, body []byte) *Put { 16 | return &Put{pri:pri, delay:delay, ttr:ttr, body:body} 17 | } 18 | 19 | func (this *Put) GetBytes() []byte { 20 | str := fmt.Sprintf("put %d %d %d %d\r\n", this.pri, this.delay, this.ttr, len(this.body)) 21 | res := []byte(str) 22 | res2 := append(res, this.body...) 23 | res3 := append(res2, []byte("\r\n")...) 24 | return res3 25 | } 26 | --------------------------------------------------------------------------------