Redis实现简单的消息队列
下面是自己实现的消息队列, 非常粗糙, 欢迎大家指点.
注: 该实现比较简单, 请谨慎用于生产环境.
"Don't talk, show the code"
1. Redis管理类:
package pk.api.redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import java.io.*; import java.util.ArrayList; import java.util.List; /** * Created by cliff on 17/9/18. */ public class RedisKit { /** * 连接池. */ private JedisPool jedisPool; /** * 默认的redis管理器. */ private static final RedisKit me = new RedisKit("localhost", 6379); /** * 获取默认的管理器. * * @return 默认的redis管理器 */ public static RedisKit me() { return me; } /** * 构造器. * * @param ip 主机 * @param port 端口 */ public RedisKit(String ip, int port) { jedisPool = new JedisPool(ip, port); } /** * 在队列右侧添加. * * @param key 键 * @param values 值 * @return 保存的数量 */ public Long rpush(String key, Object... values) { Jedis jedis = jedisPool.getResource(); try { List<byte[]> list = new ArrayList<>(); for (Object object : values) { list.add(serialize(object)); } return jedis.rpush(key.getBytes(), list.toArray(new byte[values.length][])); } catch (IOException e) { e.printStackTrace(); } finally { jedis.close(); } return 0l; } /** * 从队列左侧取出 @count数量的元素. * * @param key 键 * @param start 索引的开始 * @param count 数量 * @param <T> 对象类型 * @return 列表 */ public <T> List<T> lrange(String key, int start, int count) { Jedis jedis = jedisPool.getResource(); List<T> resultList = new ArrayList<>(); try { List<byte[]> lrange = jedis.lrange(key.getBytes(), start, start + count - 1); for (byte[] bytes : lrange) { resultList.add((T) deserialize(bytes)); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { jedis.close(); } return resultList; } /** * 从队列左侧删除元素. * * @param key 键 * @param count 要删除的等于 @value的个数.大于0 表示从左侧开始, 小于0表示从右侧开始. * @param value 值 * @return 删除的个数 */ public long lrem(String key, long count, Object value) { Jedis jedis = jedisPool.getResource(); try { return jedis.lrem(key.getBytes(), count, serialize(value)); } catch (IOException e) { e.printStackTrace(); } finally { jedis.close(); } return 0l; } //序列化对象 private byte[] serialize(Object object) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutput objectOutput = new ObjectOutputStream(out); objectOutput.writeObject(object); return out.toByteArray(); } //反序列化 private Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream); return inputStream.readObject(); } }
2. 消息队列:
package pk.api.redis; import java.util.List; /** * redis实现的队列. */ public class RedisQueue { /** * 队列名称. */ private String queueName; public RedisQueue(String queueName) { this.queueName = queueName; } /** * 放入消息. */ public void pushMessage(final Message message) { RedisKit.me().rpush(queueName, message); } /** * 取出消息,却不删除. */ List<Message> batchPopMessage(int count) { return RedisKit.me().lrange(queueName, 0, count - 1); } /** * 删除消息. */ void delMsg(Message message, int count) { RedisKit.me().lrem(queueName, count, message); } }
3. 消息对象
package pk.api.redis; import pk.api.uuid.UUIDKit; import java.io.Serializable; /** * Created by cliff on 17/9/14. */ public final class Message implements Serializable { /** * 序列化id. */ private static final long serialVersionUID = -1725153377631087068L; /** * 消息id, 唯一标识. */ private String id; /** * 消息体, json格式的字符串. */ private String messageBody; /** * 延迟执行时间. */ private long delaySeconds; /** * 创建时间. */ private long createTime; /** * 生命时长,单位秒,默认5天. */ private long lifespan = 3600 * 24 * 5l; /** * 构造函数. */ public Message() { this.id = UUIDKit.generateUUID(); this.createTime = System.currentTimeMillis(); } /** * 获取消息体. * * @return 消息体 */ public String getMessageBody() { return messageBody; } public String getId() { return id; } /** * 设置消息体. * * @param messageBody 消息体 */ public void setMessageBody(String messageBody) { if (messageBody == null) { throw new IllegalArgumentException("message body can not be null"); } this.messageBody = messageBody; } /** * 拿到延迟秒数. * * @return */ public long getDelaySeconds() { return delaySeconds; } /** * 设置延迟秒数. * * @param delaySeconds 单位秒 */ public void setDelaySeconds(long delaySeconds) { this.delaySeconds = delaySeconds; } /** * 获取创建时间. * * @return 创建时间 */ public long getCreateTime() { return createTime; } /** * 获取生命时长. * * @return 生命时长 */ public long getLifespan() { return lifespan; } /** * 设置生命时长. * * @param lifespan 单位秒 */ public void setLifespan(long lifespan) { this.lifespan = lifespan; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Message)) { return false; } Message another = (Message) obj; return this.id.equals(another.getId()); } @Override public int hashCode() { return getId().hashCode(); } }
4. 处理消息的线程抽象类, 客户端可继承此类, 实现具体逻辑
package pk.api.redis; import java.util.List; /** * Created by cliff on 17/9/15. */ public abstract class RedisQueueProcessThread extends Thread { // public static final Log LOGGER = Log.getLog(RedisQueueProcessThread.class); /** * 消息队列. */ private RedisQueue queue; /** * 每次请求获取的消息数量. */ private int msgCountPerRequest; /** * 构造器. * * @param queue 消息队列 * @param msgCountPerRequest 每次请求获取的消息数量 */ public RedisQueueProcessThread(final RedisQueue queue, final int msgCountPerRequest) { this.queue = queue; this.msgCountPerRequest = msgCountPerRequest; } @Override public void run() { //1.取出消息 //2.处理消息 //3.处理成功: 删除消息; 失败,跳出 while (true) { List<Message> messages; try { messages = queue.batchPopMessage(msgCountPerRequest); } catch (Exception e) { // LOGGER.error("批量获取消息失败", e); System.out.println("批量获取消息失败"); continue; } for (Message message : messages) { boolean success = true; try { //1.计算消息的执行时间 long executionTime = message.getCreateTime() + message.getDelaySeconds() * 1000; if (System.currentTimeMillis() < executionTime) { continue; } //2.如果生命时长过期, 直接删除 long lifeEndTime = message.getCreateTime() + message.getLifespan() * 1000; if (System.currentTimeMillis() > lifeEndTime) { delMsg(queue, message); continue; } //3.准备执行 processMessage(message); } catch (Exception e) { //消息处理失败, 不做处理 // LOGGER.error("消息处理失败" + message.getMessageBody(), e); System.out.println("消息处理失败" + message.getMessageBody()); success = false; } if (success) { //4.执行成功后,删除消息 delMsg(queue, message); } } } } /** * 删除消息, catch异常. */ private void delMsg(final RedisQueue queue, final Message message) { try { queue.delMsg(message, 1); } catch (Exception e) { // LOGGER.error("消息删除失败" + message.getMessageBody(), e); System.out.println("消息删除失败" + message.getMessageBody()); } } /** * 具体的处理消息逻辑. * * @param message 消息 */ protected abstract void processMessage(Message message); }