基于数据库的分布式消息队列

来源:互联网 发布:java冒烟测试怎么做 编辑:程序博客网 时间:2024/05/16 07:17

需求

    在传统的应用中由于消息队列缺乏,而且为了兼容必要的部署和配置需求,往往想在不太多修改代码的情况下拥有队列功能。常用的消息队列有传统的JMS,amqp以及互联网的mq系列的中间件等。然而在传统的基于数据库的应用中,往往只想拥有简单的队列功能,并不存在着大量并发和分布式的问题,因此构建一个基于数据库表存储结构的队列已经基本能满足业务需求。该库的实现为jdjjob库

    为了满足当前系统中的队列需求:1. 包含入队顺序按照时间或者是序列号之类的; 2. 包含重入队列,拥有记录上次失败原因和失败次数; 3. 支持多机分布式的队列生产和消费; 4. 支持多个队列的绑定和监听。为此设计了对应的表结构如下:

CREATE TABLE `jobs` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,`handler` TEXT NOT NULL,`queue` VARCHAR(255) NOT NULL DEFAULT 'default',`attempts` INT UNSIGNED NOT NULL DEFAULT 0,`run_at` DATETIME NULL,`locked_at` DATETIME NULL,`locked_by` VARCHAR(255) NULL,`failed_at` DATETIME NULL,`error` TEXT NULL,`created_at` DATETIME NOT NULL) ENGINE = INNODB;

对应的字段解释为:

`id`   递增主键序列
`handler`  序列化的处理对象
`queue`  队列名称
`attempts`  尝试次数
`run_at`  开始运行时间
`locked_at`  锁定时间
`locked_by` 被锁定的处理描述符
`failed_at`   失败时间
`error`   错误信息
`created_at`  进入队列时间


工作机制和原理:

核心的代码为队列中消息的锁定和释放,通过使用update 乐观锁的方式进行行的锁定,同时使用随机排序的方式避免过多的竞争,带入如下:

update乐观锁

DJJobDao dao = new DJJobDao();
StringBuilder sb = new StringBuilder();
sb.append(" UPDATE ");
sb.append(DJBase.jobsTable);
sb.append(" SET locked_at=NOW(), locked_by=? ");
sb.append(" WHERE id=? AND (locked_at IS NULL OR locked_by=?) AND failed_at IS NULL");
List<Object> args = new ArrayList<Object>();
args.add(this.workerName);
args.add(this.jobId);
args.add(this.workerName);
boolean lock = dao.execute(sb.toString(), args);


获取消息前十条并进行随机排序

DJJobDao dao = new DJJobDao();


StringBuilder sb = new StringBuilder();
sb.append(" SELECT id FROM ");
sb.append(DJBase.jobsTable);
sb.append(" WHERE  queue = ? ");
sb.append(" AND    (run_at IS NULL OR NOW() >= run_at) ");
sb.append(" AND    (locked_at IS NULL OR locked_by = ?) ");
sb.append(" AND    failed_at IS NULL");
sb.append(" AND    attempts < ? ");
sb.append(" ORDER BY created_at DESC ");
sb.append(" LIMIT  10 ");
List<Object> args = new ArrayList<Object>();
args.add(this.queue);
args.add(this.name);
args.add(this.maxAttempts);


List<Object[]> rs = dao.executeQuery(sb.toString(), args);


// randomly order the 10 to prevent lock contention among workers
Collections.shuffle(rs);

具体的实现和消息对象存储规则:

在消息的处理对象上,使用jackson的库进行对象数据格式化。具体格式为: 类名:{json数据域}, 采用这种格式的好处就是直观并且容易跨不同版本的虚拟机。在对象的字符串序列化的时候,保存了类名和对象数据,使用过程中需要将需要序列化的类中增加 无参数的构造函数和每个数据域都需要必要getter 和 setter方法。


其他和一些思路上的想法,以及改进的空间:

将其中的一些获取的消息每次条数和等待时间等变量进行参数化,同时提高序列化的支持方式。并且将数据源和数据库操作层进行抽象(方便支持其他的持久化框架的使用),保证有更好的可插拔性,或扩展性。

原创粉丝点击