Redis五种数据结构如下:
1.String 字符串类型
是redis中最基本的数据类型,一个key对应一个value。
String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
使用:get 、 set 、 del 、 incr、 decr 等
2.Hash (哈希)
是一个Mapmap,指值本身又是一种键值对结构,如 value={{field1,value1},......fieldN,valueN}}
使用:所有hash的命令都是 h 开头的 hget 、hset 、 hdel 等
3.List
List 我们也称链表(redis 使用双端链表实现的 List),是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。
使用列表的技巧
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
4.Set
也称无序集合,无序集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中 1. 不允许有重复的元素,2.集合中的元素是无序的,不能通过索引下标获取元素,3.支持集合间的操作,可以取多个集合取交集、并集、差集。
使用:命令都是以s开头的 sset 、srem、scard、smembers、sismember
5.zset 有序集合
有序集合和集合有着必然的联系,保留了集合不能有重复成员的特性,区别是,有序集合中的元素是可以排序的,它给每个元素设置一个分数,作为排序的依据。有序集合中的元素不可以重复。
使用: 有序集合的命令都是 以 z 开头 zadd 、 zrange、 zscore
看完这几种数据结构很明显,List结构满足先进先出的队列模式,可以用作消息队列
JAVA程序实现消息队列
redis.properties
redis.url=localhost
redis.port=6379
redis.maxIdle=30
redis.minIdle=10
redis.maxTotal=100
redis.maxWait=10000
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;/**
* @Author: qlq
* @Description
* @Date: 21:32 2018/10/9
*/
public class JedisPoolUtils {private static JedisPool pool = null;
static {
//加载配置文件
InputStream in = JedisPoolUtils.class.getClassLoader().getResourceAsStream("redis.properties");
Properties pro = new Properties();
try {
pro.load(in);
} catch (IOException e) {
e.printStackTrace();
}//获得池子对象
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(Integer.parseInt(pro.get("redis.maxIdle").toString()));//最大闲置个数
poolConfig.setMaxWaitMillis(Integer.parseInt(pro.get("redis.maxWait").toString()));//最大闲置个数
poolConfig.setMinIdle(Integer.parseInt(pro.get("redis.minIdle").toString()));//最小闲置个数
poolConfig.setMaxTotal(Integer.parseInt(pro.get("redis.maxTotal").toString()));//最大连接数
pool = new JedisPool(poolConfig, pro.getProperty("redis.url"), Integer.parseInt(pro.get("redis.port").toString()));
}//获得jedis资源的方法
public static Jedis getJedis() {
return pool.getResource();
}public static void main(String[] args) {
Jedis jedis = getJedis();
System.out.println(jedis);
}
}
消息生产者:(开启5个线程生产消息)
import redis.clients.jedis.Jedis;
/**
* @Author: qlq
* @Description
* @Date: 21:29 2018/10/9
*/
public class MessageProducer extends Thread {
public static final String MESSAGE_KEY = "message:queue";
private volatile int count;public void putMessage(String message) {
Jedis jedis = JedisPoolUtils.getJedis();
Long size = jedis.lpush(MESSAGE_KEY, message);
System.out.println(Thread.currentThread().getName() + " put message,size=" + size + ",count=" + count);
count++;
}@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
putMessage("message" + count);
}
}public static void main(String[] args) {
MessageProducer messageProducer = new MessageProducer();
Thread t1 = new Thread(messageProducer, "thread1");
Thread t2 = new Thread(messageProducer, "thread2");
Thread t3 = new Thread(messageProducer, "thread3");
Thread t4 = new Thread(messageProducer, "thread4");
Thread t5 = new Thread(messageProducer, "thread5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
(2)消息消费者:(开启两个线程消费消息)
import redis.clients.jedis.Jedis;
/**
* @Author: qlq
* @Description
* @Date: 22:34 2018/10/9
*/
public class MessageConsumer implements Runnable {
public static final String MESSAGE_KEY = "message:queue";
private volatile int count;public void consumerMessage() {
Jedis jedis = JedisPoolUtils.getJedis();
String message = jedis.rpop(MESSAGE_KEY);
System.out.println(Thread.currentThread().getName() + " consumer message,message=" + message + ",count=" + count);
count++;
}@Override
public void run() {
while (true) {
consumerMessage();
}
}public static void main(String[] args) {
MessageConsumer messageConsumer = new MessageConsumer();
Thread t1 = new Thread(messageConsumer, "thread6");
Thread t2 = new Thread(messageConsumer, "thread7");
t1.start();
t2.start();
}
}
上述例子中消息消费者有一个问题存在,即需要不停的调用rpop方法查看List中是否有待处理消息。每调用一次都会发起一次连接,这会造成不必要的浪费。也许你会使用Thread.sleep()等方法让消费者线程隔一段时间再消费,但这样做有两个问题:
1)、如果生产者速度大于消费者消费速度,消息队列长度会一直增大,时间久了会占用大量内存空间。
2)、如果睡眠时间过长,这样不能处理一些时效性的消息,睡眠时间过短,也会在连接上造成比较大的开销。
补充:brpop和blpop实现阻塞读取(重要)
也就是上面的操作需要一直调用rpop命令或者lpop命令才可以实现不停的监听且消费消息。为了解决这一问题,redis提供了阻塞命令 brpop和blpop。下面以brpop命名为例进行试验:
brpop命令可以接收多个键,其完整的命令格式为 BRPOP key [key ...] timeout,如:brpop key1 0。意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中一个有元素则从该键中弹出该元素(会按照key的顺序进行读取,可以实现具有优先级的队列)。例如下面试验:
开启两个客户端,第一个客户端中采用brpop阻塞读取两个键:
127.0.0.1:6379> brpop mylist1 mylist2 0
第二个客户端增加mylist1 :
127.0.0.1:6379> lpush mylist1 1 2 (integer) 2
则在第一个客户端显示:
127.0.0.1:6379> brpop mylist1 mylist2 0 1) "mylist1" 2) "1" (56.31s)
也就是brpop会阻塞队列,并且每次也是弹出一个消息,如果没有消息会阻塞。
如果多个键都有元素则按照从左到右读取第一个键中的一个元素,例如我们现在queue1和queue2各自添加一个元素:
127.0.0.1:6379> lpush queue1 1 2 (integer) 2 127.0.0.1:6379> lpush queue2 3 4 (integer) 2
然后执行brpop命令:(会返回读取的key和value,第一个是返回的key,第二个是value)
127.0.0.1:6379> brpop queue1 queue2 2 1) "queue1" 2) "1"
借此特性可以实现区分优先级的任务队列。也就是brpop会按照key的顺序依次读取一个数据。
改造上面代码实现阻塞读取:
import redis.clients.jedis.Jedis;
import java.util.List;
/**
* @Author: qlq
* @Description
* @Date: 22:34 2018/10/9
*/
public class MessageConsumer implements Runnable {
public static final String MESSAGE_KEY = "message:queue";
private volatile int count;
private Jedis jedis = JedisPoolUtils.getJedis();public void consumerMessage() {
List<String> brpop = jedis.brpop(0, MESSAGE_KEY);//0是timeout,返回的是一个集合,第一个是消息的key,第二个是消息的内容
System.out.println(brpop);
}@Override
public void run() {
while (true) {
consumerMessage();
}
}public static void main(String[] args) {
MessageConsumer messageConsumer = new MessageConsumer();
Thread t1 = new Thread(messageConsumer, "thread6");
Thread t2 = new Thread(messageConsumer, "thread7");
t1.start();
t2.start();
}
}
然后可以运行Customer,清空控制台,可以看到程序没有任何输出,阻塞在了brpop这儿。然后在打开Redis的客户端,输入指令client list,可以查看当前的连接个数。
当启动生产者生产消息之后,消费者会自动消费消息,而且消费者会阻塞直到有消息。