一:介绍
Beanstalk,一个高性能、轻量级的分布式内存队列系统,最初设计的目的是想通过后台异步执行耗时的任务来降低高容量Web应用系统的页面访问延迟,支持过有9.5 million用户的Facebook Causes应用。
github:Beanstalkd · GitHub
官网:About – beanstalkd
二:功能特性
2.1 优先级
任务job可以设置优先级,0 代表最高优先级,默认优先级为1024。
2.2 延迟 delay
指定时间后开始执行
2.3 持续执行,直至成功
可以对一个消息多次消费,只要不进行delete,消息可以一直在队列中
2.4 持久化
可以通过binlog将job及其状态记录到文件里面,在Beanstalkd下次启动时可以通过读取binlog来恢复之前的job及状态。
2.5 超时控制
为了防止某个consumer长时间占用任务但不能处理的情况,Beanstalkd为reserve操作设置了timeout时间,如果该consumer不能在指定时间内完成且删除该job,job将被迁移回READY状态,供其他consumer执行。
2.6 分布式容错
因为它是类Memcached设计,beanstalkd各个server之间并不知道彼此的存在,都是通过client来实现分布式以及根据tube名称去特定server获取job。
三:使用场景
- 用作延时队列:比如可以用于如果用户30分钟内不操作,任务关闭。
- 用作定时任务:比如可以用于专门的后台任务。
- 用作异步操作:这是所有消息队列都最常用的,先将任务仍进去,顺序执行。
- 用作循环队列:用release命令可以循环执行任务,比如可以做负载均衡任务分发。
- 用作兜底机制:比如一个请求有失败的概率,可以用Beanstalk不断重试,设定超时时间,时间内尝试到成功为止。
其实我们最重要的使用场景是把它作为延迟队列类使用,比如:
- 下订单后多长时间没有付款,要取消订单,并退库存
- 用户注册成功后,发送一封邮件
- 定期检查退款状态的订单是否退款成功
四:Beanstalkd设计基本概念
4.1 核心概念
- job:一个需要异步处理的任务,是Beanstalkd中的基本单元,需要放在一个tube中
- tube:一个有名的队列,用来存储统一类型的job,是producer和consumer操作的对象。tube可以称为管道
- producer:Job 的生产者,通过 put 命令来将一个 job 放到一个 tube 中
- consumer:Job的消费者,通过 reserve/release/bury/delete 命令来获取 job 或改变 job 的状态
4.2 job的生命周期
生产者生成任务,并根据业务需求将任务放到不同的管道中。比如与注册有关的任务放到注册管道中,和订单有关的任务放到订单管道中。
任务进入管道到离开管道一共有5个状态 :
(ready,delayed,reserved,buried,delete)
- READY - 需要立即处理的任务,当延时 (DELAYED) 任务到期后会自动成为当前任务;
- DELAYED - 延迟执行的任务, 当消费者处理任务后, 可以用将消息再次放回 DELAYED 队列延迟执行;
- RESERVED - 已经被消费者获取, 正在执行的任务。Beanstalkd 负责检查任务是否在 TTR(time-to-run) 内完成;
- BURIED - 保留的任务: 任务不会被执行,也不会消失,除非有人把它 "踢" 回队列;
- DELETED - 消息被彻底删除。Beanstalkd 不再维持这些消息。
状态流程
1.生产任务:
当producer生产者put一个job到tube时,这个job就处于 ready
状态,等待consumer来消费处理;
producer生产者也可以选择延迟put一个job,这时job就先达到 delayed
状态(比如设置一个5秒延迟的job,那么5秒之后,这个job才会变成 ready 状态,才可以被consumer消费)
2. 消费任务:
consumer获取了当前 ready
的job后,该job就会迁移到 reserved
状态,这样其他的consumer就不能在操作该job
3. 消费完任务后:
当consumer消费完该job后,可以选择delete, release 或者 bury 3种操作。
- delete操作:job从系统消亡,之后不能在获取;
- release操作:可以重新把该job状态迁移回
ready
(也可以延迟状态delayed
操作),使其他的consumer可以继 续获取和执行该job; - bury操作: 把job置为
buried
状态,及是把该job休眠,等到需要的时候,还可以将休眠的 job 重新置为ready
状态, 也可以delete掉 buried 状态的job。
也就是说:当消费者处理完任务后,任务的状态可能是delete(删除,处理成功),可能是buried(预留,意味着先把任务放一边,等待条件成熟还要用),可能是ready,也可能是delayed,需要根据具体业务场景自己进行判断定义
注意点:
1、延迟时间是消费者去获取消息修改消息状态时生效的,比如分别put一个10秒延迟的消息和1秒延迟的消息,如果马上消费那么就是一秒后拿到1秒的消息,十秒后拿到10秒的消息,但是如果是等了10秒以后才去消费,那么两条消息的顺序不变,因为两条消息都已经是ready状态。
示意图:
五:Java简单示例
5.1 pom依赖
<dependency>
<groupId>com.dinstone</groupId>
<artifactId>beanstalkc</artifactId>
<version>2.2.0</version>
</dependency>
5.2 生产者及消费者
Configuration config = new Configuration();
//地址
config.setServiceHost("127.0.0.1");
//端口
config.setServicePort(3097);
BeanstalkClientFactory factory = new BeanstalkClientFactory(config);
//创建生产者
JobProducer producer = factory.createJobProducer("beanstalkd-demo1");
//创建消费者,tube支持多个
JobConsumer consumer = factory.createJobConsumer("beanstalkd-demo1", "beanstalkd-demo2");
//发布消息
String msg = "测试发布消息";
/**
* 四个入参分别是
* 优先级:越小越优先
* 延迟时间:过了该时间,消息状态被置为ready状态,可以消费
* 消费者处理超时时间:如果消费者不能在这个时间内消费完且删除掉,该消息会重新置为ready状态,可以消费
* 消息内容
*/
//返回的是jobId,消费者可根据id修改消息状态
long jobId = producer.putJob(100, 10, 10, msg.getBytes());
//接收消息
//入参为阻塞时间,5秒内取不到消息则job为空
Job job = consumer.reserveJob(5);
if(job != null) {
long id = job.getId(); //jobId
byte[] data = job.getData(); //消息内容
//下面针对消息进行处理逻辑
//处理过直接删除消息
consumer.deleteJob(id);
//也可以重新把该消息置为delay状态
//入参点进去看名词便知
// consumer.releaseJob(id, 100, 10);
}
六、springboot集成(和上面使用一个pom依赖)
6.1 bean生成
import com.dinstone.beanstalkc.BeanstalkClientFactory;
import com.dinstone.beanstalkc.JobConsumer;
import com.dinstone.beanstalkc.JobProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 描述:
*
* @author qiu
* @date 2023/4/13 14:18
*/
@Configuration
public class BeanStalkConfig {
private final Logger LOGGER = LoggerFactory.getLogger(BeanStalkConfig.class);
@Autowired
private BeanStalkProperties properties;
/**
* 创建一个BeanstalkClientFactory
*/
@Bean
public BeanstalkClientFactory getFactory() {
com.dinstone.beanstalkc.Configuration config = new com.dinstone.beanstalkc.Configuration();
config.setServiceHost(properties.getHost());
config.setServicePort(properties.getPort());
BeanstalkClientFactory factory = new BeanstalkClientFactory(config);
return factory;
}
/**
* 创建一个生产者
*/
@Bean
public JobProducer jobProducer() {
return getFactory().createJobProducer(BeanStalkInfo.TUBE_NAME);
}
/**
* 创建一个消费者
*/
@Bean
public JobConsumer jobConsumer() {
return getFactory().createJobConsumer(BeanStalkInfo.TUBE_NAME);
}
}
6.2 配置属性信息
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 描述:
*
* @author qiu
* @date 2023/4/13 14:18
*/
@Component
public class BeanStalkProperties {
@Value("${spring.beanstalk.host}")
private String host;
@Value("${spring.beanstalk.port}")
private Integer port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}
6.3 使用
直接注入JobConsumer和JobProducer使用即可,至于怎么调度根据自己业务场景自行处理。