使用go语言基于redis写了一个简单的消息队列

源码地址

使用demo

redis的 list 非常的灵活,可以从左边或者右边添加元素,当然也以从任意一头读取数据

添加数据和获取数据的操作也是非常简单的

LPUSH 从左边插入数据

RPUSH 大右边插入数据

LPOP 从左边取出一个数据

RPOP 从右边取出一个数据

127.0.0.1:6379> LPUSH list1 a
(integer) 1
127.0.0.1:6379> RPUSH list1 b
(integer) 2
127.0.0.1:6379> LPOP list1
"a"
127.0.0.1:6379> RPOP list1
"b"

或者使用 BLPOP BRPOP 来读取数据,不同之处是取数据时,如果没有数据会等待指定的时间,

如果这期间有数据写入,则会读取并返回,没有数据则会返回空

在一个窗口1读取

127.0.0.1:6379> BLPOP list1 10
1) "list1"
2) "a"

在另一个窗口2写入

127.0.0.1:6379> RPUSH list1 a b c
(integer) 3

再开一个窗口3读取,第二次读取时,list是空的,所以等待1秒后返回空。

127.0.0.1:6379> BRPOP list1 1
1) "list1"
2) "c" 127.0.0.1:6379> BRPOP list1 1
(nil)
(1.04s)

简单消息队列的实现

如果我们只从一边新增元素,向另一边取出元素,这就不是一个消息队列么。但我估计你会有一个疑问,在消费数据时,同一个消息会不会同时被多个consumer消费掉?

当然不会,因为redis是单线程的,在从list取数据时天然不会出现并发问题。但是这是一个简单的消息队列,消费不成功怎么处理还是需要我们自己写代码来实现的

下面我说一下使用list实现一个简单的消息队列的整体思路

comsumer的实现

consumer 主要做的就是从list里读取数据,使用LPOP或者BLPOP都可以,

这里做了一个开关 optionsUseBLopp如果为true时会使用BLPOP

type consumer struct {
once sync.Once
redisCmd redis.Cmdable
ctx context.Context
topicName string
handler Handler
rateLimitPeriod time.Duration
options ConsumerOptions
_ struct{}
} type ConsumerOptions struct {
RateLimitPeriod time.Duration
UseBLPop bool
}

看一下创建consumer的代码,最后面的opts参数是可选的配置

type Consumer = *consumer

func NewSimpleMQConsumer(ctx context.Context, redisCmd redis.Cmdable, topicName string, opts ...ConsumerOption) Consumer {
consumer := &consumer{
redisCmd: redisCmd,
ctx: ctx,
topicName: topicName,
}
for _, o := range opts {
o(&consumer.options)
}
if consumer.options.RateLimitPeriod == 0 {
consumer.options.RateLimitPeriod = time.Microsecond * 200
}
return consumer
}

读取数据后具体怎么进行处理调用者可以根据自己的业务逻辑进行相应处理

有一个小的interface调用者根据自己的逻辑去实现

type Handler interface {
HandleMessage(msg *Message)
}

读取数据的逻辑使用一个gorouting实现

func (s *consumer) startGetMessage() {
go func() {
ticker := time.NewTicker(s.options.RateLimitPeriod)
defer func() {
log.Println("stop get message.")
ticker.Stop()
}()
for {
select {
case <-s.ctx.Done():
log.Printf("context Done msg: %#v \n", s.ctx.Err())
return
case <-ticker.C:
var revBody []byte
var err error
if !s.options.UseBLPop {
revBody, err = s.redisCmd.LPop(s.topicName).Bytes()
} else {
revs := s.redisCmd.BLPop(time.Second, s.topicName)
err = revs.Err()
revValues := revs.Val()
if len(revValues) >= 2 {
revBody = []byte(revValues[1])
}
}
if err == redis.Nil {
continue
}
if err != nil {
log.Printf("LPOP error: %#v \n", err)
continue
} if len(revBody) == 0 {
continue
}
msg := &Message{}
json.Unmarshal(revBody, msg)
if s.handler != nil {
s.handler.HandleMessage(msg)
}
}
}
}()
}

Producer 的实现

Producer还是很简单的就是把数据推送到 reids

type Producer struct {
redisCmd redis.Cmdable
_ struct{}
} func NewProducer(cmd redis.Cmdable) *Producer {
return &Producer{redisCmd: cmd}
} func (p *Producer) Publish(topicName string, body []byte) error {
msg := NewMessage("", body)
sendData, _ := json.Marshal(msg)
return p.redisCmd.RPush(topicName, string(sendData)).Err()
}

玩转redis-简单消息队列的更多相关文章

  1. redis简单消息队列

    <?php $redis = new Redis(); $redis->connect('127.0.0.1',6379); $redis->flushall(); $redis-& ...

  2. 玩转redis-延时消息队列

    上一篇基于redis的list实现了一个简单的消息队列:玩转redis-简单消息队列 源码地址 使用demo 产品经理经常说的一句话,我们不光要有X功能,还要Y功能,这样客户才能更满意.同样的,只有简 ...

  3. Redis 做消息队列

    一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现.定义: 生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...

  4. Redis作为消息队列服务场景应用案例

    NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例   一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...

  5. redis resque消息队列

    Resque 目前正在学习使用resque .resque-scheduler来发布异步任务和定时任务,为了方便以后查阅,所以记录一下. resque和resque-scheduler其优点在于功能比 ...

  6. 程序员过关斩将--redis做消息队列,香吗?

    Redis消息队列 在程序员这个圈子打拼了太多年,见过太多的程序员使用redis,其中一部分喜欢把redis做缓存(cache)使用,其中最典型的当属存储用户session,除此之外,把redis作为 ...

  7. 【springboot】【redis】springboot+redis实现发布订阅功能,实现redis的消息队列的功能

    springboot+redis实现发布订阅功能,实现redis的消息队列的功能 参考:https://www.cnblogs.com/cx987514451/p/9529611.html 思考一个问 ...

  8. simple简单消息队列

    一:介绍 1.优缺点 简单,但是耦合性较高. 这种模式是生产者与消费者一一对应,就是一个产生者,有一个消费者来消费. 如果,多个消费者想消费一个队列中的消息就不适合了.这种情况在后面会接着介绍. 2. ...

  9. 【Redis】php+redis实现消息队列

    在项目中使用消息队列一般是有如下几个原因: 把瞬间服务器的请求处理换成异步处理,缓解服务器的压力 实现数据顺序排列获取 redis实现消息队列步骤如下: 1).redis函数rpush,lpop 2) ...

随机推荐

  1. 峰哥说技术:07-SpringBoot 正好Thymeleaf视图

    Spring Boot深度课程系列 峰哥说技术—2020庚子年重磅推出.战胜病毒.我们在行动 07  峰哥说技术:SpringBoot 正好Thymeleaf视图 Spring Boot视图介绍 虽然 ...

  2. Python进阶练习与爬取豆瓣T250的影片相关信息

    (一)Python进阶练习 正所谓要将知识进行实践,才会真正的掌握 于是就练习了几道题:求素数,求奇数,求九九乘法表,字符串练习 import re #求素数 i=1; flag=0 while(i& ...

  3. 简说python之安装

    Python是跨平台程序语言,做为世界流行的语言之一,它可以平滑地部署在Windows,Linux,Mac等平台之上,并有很多第三方模块的函数可供使用. 学习Python,首先需要把Python的编译 ...

  4. MySQL5.7 数据库的my.cnf配置

    [client] port socket=/tmp/mysql.sock default-character-set=utf8 [mysql] no-auto-rehash default-chara ...

  5. js函数的三种成创建方式以及它们各自的不同

    js有三种创建函数的方式: 1.function语句(也叫函数声明) function sum(a, b) { return a + b; } sum(1, 2); // 3 2. 函数直接量,又叫函 ...

  6. 升级cocoapods到指定版本

    把cocoapods升级到1.7.4 sudo gem install -n /usr/local/bin cocoapods -v 1.7.4

  7. Layui select下拉框改变之 change 监听事件(转)

    在layui中使用 jquery 触发select 的 change事件无效 使用layui.use监听select事件 <select lay-filter="demo" ...

  8. 创建Sphinx + GitHub + ReadtheDocs托管文档

    最新博客链接 "Tsanfer's Blog" 创建Sphinx + GitHub + ReadtheDocs托管文档 Readthedocs在线电子书链接

  9. Java序列化和反序列化-(新手)

    实例: lx1: import java.io.*; public class xuliehua { public static void main(String[] args) throws Exc ...

  10. Python第二周作业

    绘制五角星 import turtle turtle.color('black','red') turtle.pensize(10) turtle.begin_fill() for i in rang ...