异步消息队列
- 使用的数据结构: list
- 主要实现: go实现简单消息队列
package main
import (
"encoding/json"
"fmt"
"github.com/gomodule/redigo/redis"
"os"
)
type Producer struct {
// 生产者
}
func (p *Producer)publish(conn redis.Conn, listKey string, data string) (reply interface{}, err error){
return conn.Do("lpush", listKey, data)
}
type Customer struct {
// 消费者
}
func (c *Customer)putMessage(conn redis.Conn, listKey string) (interface{}, error) {
return conn.Do("rpop", listKey)
}
func (c *Customer)getCount(conn redis.Conn, listKey string) (interface{}, error) {
return conn.Do("llen", listKey)
}
func HandlecError(err error, when string) {
if err != nil{
fmt.Println("error happen at", when)
os.Exit(500)
}else {
fmt.Println("连接成功")
}
}
func main() {
// 连接redis操作
conn, err := redis.Dial("tcp","127.0.0.1:6379")
HandlecError(err, "connect")
defer func() {
_ = conn.Close()
}()
producer := Producer{}
personMap := make(map[string]interface{})
personMap["name"] = "hxh"
personMap["work"] = "toDoSomething"
bytes, _ := json.Marshal(personMap)
_,_ = producer.publish(conn, "test_queue", string(bytes))
customer := Customer{}
num, _ := customer.getCount(conn, "test_queue")
fmt.Println("队列数量为", num)
values, err := redis.String(customer.putMessage(conn,"test_queue"))
dataMap := make(map[string]interface{}) // 准备好map来装
_ = json.Unmarshal([]byte(values), &dataMap)
fmt.Println(dataMap["work"])
}
- 问题:
- 空队列: 如果队列为空,客户端会不断pop空轮询,这样拉高客户端的cpu和服务器redis的qps
可以sleep一下
- 队列延迟问题: 使用阻塞读blpop/brpop,在队列没有数据的时候就会进入休眠状态
- 空闲断开问题: 线程一直阻塞在哪里,Redis 的客户端连接就成了闲置连接,闲置过久,服务器一般会主动断开连接,减少闲置资源占用。这个时候 blpop/brpop 会抛出异常来。要对其进行错误处理
- 空队列: 如果队列为空,客户端会不断pop空轮询,这样拉高客户端的cpu和服务器redis的qps
- 缺点: 不能保证消息可靠(没有 ack 保证)
延迟消息队列
- 使用的数据结构: zset
- 主要实现: 将消息序列化成一个字符串作 为 zset 的 value,这个消息的到期处理时间作为 score,然后用多个线程轮询 zset 获取到期 的任务进行处理.
- 问题
- 保障可用性,万一挂了一个线程还有其它线程可以继续处理。
- 并发争抢任务,确保任务不能被多次执行。
- 实现: go实现简单延迟队列
package main
import (
"encoding/json"
"fmt"
"github.com/gomodule/redigo/redis"
uuid "github.com/satori/go.uuid"
"os"
"sync"
"time"
)
type delayQueue struct {
// 延迟队列
}
func (d *delayQueue) publish(conn redis.Conn, zSetKey string, dataMap map[string]interface{}, time int64) (reply interface{}, err error) {
// 生成唯一id,保证zset的每一个value都不一样,time为执行的时间戳
dataMap["uuid"] = uuid.NewV4().String()
bytes, _ := json.Marshal(dataMap)
return conn.Do("zadd", zSetKey, time, string(bytes))
}
func (d *delayQueue) customer(conn redis.Conn, zSetKey string) {
for true {
now := time.Now().Unix()
data, err := redis.Strings(conn.Do("zrangebyscore", zSetKey, 0, now, "limit", 0, 1))
if err == nil && len(data) > 0 {
res, delErr := conn.Do("zrem", "test-delay-queue", data[0])
if res.(int64) >= 1 && delErr == nil {
dataMap := make(map[string]interface{}) // 准备好map来装
_ = json.Unmarshal([]byte(data[0]), &dataMap)
fmt.Println("任务是:", dataMap["work"])
}else {
fmt.Println(delErr)
}
}else {
time.Sleep(time.Second * 10)
continue
}
}
}
func HandlecError(err error, when string) {
if err != nil {
fmt.Println("error happen at", when)
os.Exit(500)
} else {
fmt.Println("连接成功")
}
}
func main() {
var wg sync.WaitGroup
// 连接redis操作
conn, err := redis.Dial("tcp", "127.0.0.1:6379")
HandlecError(err, "connect")
defer func() {
_ = conn.Close()
}()
delayQueue := delayQueue{}
personMap := make(map[string]interface{})
personMap["name"] = "hxh"
personMap["work"] = "toDoSomething"
_,_ = delayQueue.publish(conn, "test-delay-queue", personMap, time.Now().Unix())
wg.Add(1)
go func() {
delayQueue.customer(conn, "test-delay-queue")
wg.Done()
}()
wg.Wait()
}