玩转redis-简单消息队列
使用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都可以,
这里做了一个开关 options 的UseBLopp如果为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-简单消息队列的更多相关文章
- redis简单消息队列
<?php $redis = new Redis(); $redis->connect('127.0.0.1',6379); $redis->flushall(); $redis-& ...
- 玩转redis-延时消息队列
上一篇基于redis的list实现了一个简单的消息队列:玩转redis-简单消息队列 源码地址 使用demo 产品经理经常说的一句话,我们不光要有X功能,还要Y功能,这样客户才能更满意.同样的,只有简 ...
- Redis 做消息队列
一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现.定义: 生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...
- Redis作为消息队列服务场景应用案例
NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例 一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...
- redis resque消息队列
Resque 目前正在学习使用resque .resque-scheduler来发布异步任务和定时任务,为了方便以后查阅,所以记录一下. resque和resque-scheduler其优点在于功能比 ...
- 程序员过关斩将--redis做消息队列,香吗?
Redis消息队列 在程序员这个圈子打拼了太多年,见过太多的程序员使用redis,其中一部分喜欢把redis做缓存(cache)使用,其中最典型的当属存储用户session,除此之外,把redis作为 ...
- 【springboot】【redis】springboot+redis实现发布订阅功能,实现redis的消息队列的功能
springboot+redis实现发布订阅功能,实现redis的消息队列的功能 参考:https://www.cnblogs.com/cx987514451/p/9529611.html 思考一个问 ...
- simple简单消息队列
一:介绍 1.优缺点 简单,但是耦合性较高. 这种模式是生产者与消费者一一对应,就是一个产生者,有一个消费者来消费. 如果,多个消费者想消费一个队列中的消息就不适合了.这种情况在后面会接着介绍. 2. ...
- 【Redis】php+redis实现消息队列
在项目中使用消息队列一般是有如下几个原因: 把瞬间服务器的请求处理换成异步处理,缓解服务器的压力 实现数据顺序排列获取 redis实现消息队列步骤如下: 1).redis函数rpush,lpop 2) ...
随机推荐
- arm 添加 ftp server 之 bftpd
本来想装vsftp 结果装上以后执行报错 Segmentation fault , 换到几个 其它的小型ftp server 软件 ,试了 Stupid-FTPd,不能用. bftpd 可以使用,Ti ...
- CouchDB的简单使用
一.安装CouchDB 到官网下载CouchDB,在windows下安装CouchDB较为简单,略过. 安装完后,确认CouchDB在运行,然后在浏览器访问http://127.0.0.1:5984/ ...
- Simulink仿真入门到精通(十三) Simulink创建自定义库
当用户自定义了一系列模块之后,可以自定义模块库将同类自定义模块显示到Simulink Browser中,作为库模块方便地拖曳到新建模型中. 建立这样的自定义库需要3个条件: 建立library的mdl ...
- 测试必知必会系列- Linux常用命令 - history
21篇测试必备的Linux常用命令,每天敲一篇,每次敲三遍,每月一循环,全都可记住!! https://www.cnblogs.com/poloyy/category/1672457.html 查看历 ...
- Java的反射基础技术
今天本人给大家讲解一下Java的反射基础技术,如有不对的或者讲的不好的可以多多提出,我会进行相应的更改,先提前感谢提出意见的各位了!!! 什么是反射? 反射它是根据字节码文件可以反射出类的信息.字段. ...
- 记一次nor flash固件烧录速度优化
背景 某个方案使用的是spinor作为存储介质,每次烧录新固件都耗时数分钟,为了提高效率,需要对其进行优化. 分析流程 基本流程 当前烧录流程,有一个可选步骤,全盘擦除,这个步骤耗时达数分钟.不过这是 ...
- div或者p标签单行和多行超出显示省略号
单行文本溢出显示省略号 overflow: hidden;text-overflow:ellipsis;white-space: nowrap;多行文本显示省略号 display: -webkit-b ...
- vue后台管理系统权限处理
vue后台管理系统权限 1.权限问题:用户和管理员进入管理系统看到的模块是不一样的,管理员看的的要比用户看到的多.需要用到动态加载路由,router.addRouters()来动态的挂载路由 // 1 ...
- Spring框架——基于XML/注解开发
IoC的实现方式有两种:XML配置文件.基于注解. MVC开发模式: Controller层 Service层 Repository层 Controller层调用Service,Service调用Re ...
- Journal of Proteome Research | Proteomic Profiling of Rhabdomyosarcoma-Derived Exosomes Yield Insights into Their Functional Role in Paracrine Signaling (解读人:孙国莹)
文献名:Proteomic Profiling of Rhabdomyosarcoma-Derived Exosomes Yield Insights into Their Functional Ro ...