redis的个人总结
设置redis的dbIndex方法
Redis 本身支持16个数据库,通过 数据库id 设置,默认为0
设置redis的dbIndex方法有2种:
1.通过构造函数设置;
2.通过set方法设置;
构造函数有多个重载函数其中有下面一个:
public JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout, String password, int database);
poolConfig: jedis配置信息,如:maxActive,maxIdle,minIdle,maxWait
host: ip
port: 端口
timeout:超时时间,默认为2000
password:密码,可为null
database:第几个数据库,默认:0
更换以上这个构造函数,就可以设置database了
第二种:通过set方法设置
redis.select(index);
eg:
public static void main(String[] args) throws Exception {
JedisPool pool = new JedisPool("127.0.0.1", 6379);
Jedis redis = pool.getResource();
redis.select(0);
System.out.println(redis.get("zx"));
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.select(0);
System.out.println(redis.get("zx"));
}
在操作过程中遇到了错误: JedisPool pool = new JedisPool(“127.0.0.1”, 6379);这句Eclipse总是报错,鼠标移到小红叉上提示:
The type org.apache.commons.pool2.impl.GenericObjectPoolConfig cannot be res
原因是:原来是忘记添加GenericObjectPool的基本包commons-pool
Jredis操作必须依赖commons-pool和jedis-2.8.1.jar两个jar。
redis的数据类型
Redis的键值可以使用五种数据类型:字符串,散列表,列表,集合,有序集合。
字符串类型Strng
字符串是Redis中最基本的数据类型,它能够存储任何类型的字符串,包含二进制数据。可以用于存储邮箱,JSON化的对象,甚至是一张图片,一个字符串允许存储的最大容量为512MB。字符串是其他四种类型的基础,与其他几种类型的区别从本质上来说只是组织字符串的方式不同而已。
基本命令
字符串操作
SET 赋值,用法: SET key value
GET 取值,用法: GET key
INCR 递增数字,仅仅对数字类型的键有用,相当于Java的i++运算,用法: INCR key
INCRBY 增加指定的数字,仅仅对数字类型的键有用,相当于Java的i+=3,用法:INCRBY key increment,意思是key自增increment,increment可以为负数,表示减少。
DECR 递减数字,仅仅对数字类型的键有用,相当于Java的i–,用法:DECR key
DECRBY 减少指定的数字,仅仅对数字类型的键有用,相当于Java的i-=3,用法:DECRBY key decrement,意思是key自减decrement,decrement可以为正数,表示增加。
INCRBYFLOAT 增加指定浮点数,仅仅对数字类型的键有用,用法:INCRBYFLOAT key increment
APPEND 向尾部追加值,相当于Java中的”hello”.append(“ world”),用法:APPEND key value
STRLEN 获取字符串长度,用法:STRLEN key
MSET 同时设置多个key的值,用法:MSET key1 value1 [key2 value2 ...]
MGET 同时获取多个key的值,用法:MGET key1 [key2 ...]
eg:
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.set("name","xinxin");
System.out.println("name: "+jedis.get("name"));//name: xinxin
jedis.del("name"); //删除某个键
System.out.println("name: "+jedis.get("name"));//name: null
jedis.set("zx","1");
jedis.incr("zx"); //进行加1操作
System.out.println("zx: "+jedis.get("zx"));//zx: 2
//设置多个键值对
jedis.mset("name1","liuling","age","23","qq","476777XXX");
System.out.println(jedis.get("name1") + "-" + jedis.get("age") + "-" + jedis.get("qq"));//liuling-23-476777XXX
jedis.append("name1", "yang");//向尾部追加值
System.out.println("name1: "+jedis.get("name1"));//name1: liulingyang
散列类型HashMap
散列类型相当于Java中的HashMap,他的值是一个字典,保存很多key,value对,每对key,value的值个键都是字符串类型,换句话说,散列类型不能嵌套其他数据类型。一个散列类型键最多可以包含2的32次方-1个字段。
基本命令
HSET 赋值,用法:HSET key field value
HMSET 一次赋值多个字段,用法:HMSET key field1 value1 [field2 values]
HGET 取值,用法:HSET key field
HMGET 一次取多个字段的值,用法:HMSET key field1 [field2]
HGETALL 一次取所有字段的值,用法:HGETALL key
HEXISTS 判断字段是否存在,用法:HEXISTS key field
HSETNX 当字段不存在时赋值,用法:HSETNX key field value
HINCRBY 增加数字,仅对数字类型的值有用,用法:HINCRBY key field increment
HDEL 删除字段,用法:HDEL key field
HKEYS 获取所有字段名,用法:HKEYS key
HVALS 获取所有字段值,用法:HVALS key
HLEN 获取字段数量,用法:HLEN key
eg:
Jedis jedis = new Jedis("127.0.0.1", 6379);
//-----添加数据----------
Map<String, String> map = new HashMap<String, String>();
map.put("name", "xinxin");
map.put("age", "22");
map.put("qq", "123456");
jedis.hmset("user",map);
//System.out.println(jedis.get("user")); //Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: ERR Operation against a key holding the wrong kind of value
//取出user中的name,执行结果:[minxr]-->注意结果是一个泛型的List
//第一个参数是存入redis中map对象的key,后面跟的是放入map中的对象的key,后面的key可以跟多个,是可变参数
List<String> rsmap = jedis.hmget("user", "name", "age", "qq");
System.out.println(rsmap); //[xinxin, 22, 123456]
System.out.println(jedis.hgetAll("user")); //{age=22, name=xinxin, qq=123456}
//删除map中的某个键值
jedis.hdel("user","age");
System.out.println(jedis.hmget("user", "age")); //因为删除了,所以返回的是 null [null]
System.out.println(jedis.hlen("user")); //返回key为user的键中存放的值的个数 2
System.out.println(jedis.exists("user"));//是否存在key为user的记录 返回true
System.out.println(jedis.hkeys("user"));//返回map对象中的所有key [qq, name]
System.out.println(jedis.hvals("user"));//返回map对象中的所有value [xinxin, 123456]
Iterator<String> iter=jedis.hkeys("user").iterator();
while (iter.hasNext()){
String key = iter.next();
System.out.println(key+":"+jedis.hmget("user",key)); //qq:[123456] name:[xinxin]
}
}
列表类型
列表类型(list)用于存储一个有序的字符串列表,常用的操作是向队列两端添加元素或者获得列表的某一片段。列表内部使用的是双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度是O(1),获取越接近列表两端的元素的速度越快。但是缺点是使用列表通过索引访问元素的效率太低(需要从端点开始遍历元素)。所以列表的使用场景一般如:朋友圈新鲜事,只关心最新的一些内容。借助列表类型,Redis还可以作为消息队列使用。
基本命令
LPUSH 向列表左端添加元素,用法:LPUSH key value
RPUSH 向列表右端添加元素,用法:RPUSH key value
LPOP 从列表左端弹出元素,用法:LPOP key
RPOP 从列表右端弹出元素,用法:RPOP key
LLEN 获取列表中元素个数,用法:LLEN key
LRANGE 获取列表中某一片段的元素,用法:LRANGE key start stop,index从0开始,-1表示最后一个元素
LREM 删除列表中指定的值,用法:LREM key count value,删除列表中前count个值为value的元素,当count>0时从左边开始数,count<0时从右边开始数,count=0时会删除所有值为value的元素
LINDEX 获取指定索引的元素值,用法:LINDEX key index
LSET 设置指定索引的元素值,用法:LSET key index value
LTRIM 只保留列表指定片段,用法:LTRIM key start stop,包含start和stop
LINSERT 像列表中插入元素,用法:LINSERT key BEFORE|AFTER privot
value,从左边开始寻找值为privot的第一个元素,然后根据第二个参数是BEFORE还是AFTER决定在该元素的前面还是后面插入value
RPOPLPUSH 将元素从一个列表转义到另一个列表,用法:RPOPLPUSH source destination
eg:
Jedis jedis = new Jedis("127.0.0.1", 6379);
//开始前,先移除所有的内容
jedis.del("java framework");
System.out.println(jedis.lrange("java framework",0,-1)); //[]
//先向key java framework中存放三条数据
jedis.lpush("java framework","spring");
jedis.lpush("java framework","struts");
jedis.lpush("java framework","hibernate");
//再取出所有数据jedis.lrange是按范围取出,
// 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有
System.out.println(jedis.lrange("java framework",0,-1)); //[hibernate, struts, spring]
//从列表左端弹出元素
jedis.lpop("java framework");
System.out.println(jedis.lrange("java framework",0,-1)); //[struts, spring]
jedis.del("java framework");
jedis.rpush("java framework","spring");
jedis.rpush("java framework","struts");
jedis.rpush("java framework","hibernate");
System.out.println(jedis.lrange("java framework",0,-1));//[spring, struts, hibernate]
集合类型
Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
基本命令
SADD 添加元素,用法:SADD key value1 [value2 value3 ...]
SREM 删除元素,用法:SREM key value2 [value2 value3 ...]
SMEMBERS 获得集合中所有元素,用法:SMEMBERS key
SISMEMBER 判断元素是否在集合中,用法:SISMEMBER key value
SDIFF 对集合做差集运算,用法:SDIFF key1 key2 [key3 ...],先计算key1和key2的差集,然后再用结果与key3做差集
SINTER 对集合做交集运算,用法:SINTER key1 key2 [key3 ...]
SUNION 对集合做并集运算,用法:SUNION key1 key2 [key3 ...]
SCARD 获得集合中元素的个数,用法:SCARD key
SDIFFSTORE 对集合做差集并将结果存储,用法:SDIFFSTORE destination key1 key2 [key3 ...]
SINTERSTORE 对集合做交集运算并将结果存储,用法:SINTERSTORE destination key1 key2 [key3 ...]
SUNIONSTORE 对集合做并集运算并将结果存储,用法:SUNIONSTORE destination key1 key2 [key3 ...]
SRANDMEMBER 随机获取集合中的元素,用法:SRANDMEMBER key [count],当count>0时,会随机中集合中获取count个不重复的元素,当count<0时,随机中集合中获取|count|和可能重复的元素。
SPOP 从集合中随机弹出一个元素,用法:SPOP key
eg:
//添加
jedis.sadd("user","liuling");
jedis.sadd("user","xinxin");
jedis.sadd("user","ling");
jedis.sadd("user","zhangxinxin");
jedis.sadd("user","who");
//移除noname
jedis.srem("user","who");
System.out.println(jedis.smembers("user"));//获取所有加入的value //[liuling, zhangxinxin, ling, xinxin]
System.out.println(jedis.sismember("user", "who"));//判断 who 是否是user集合的元素 false
System.out.println(jedis.srandmember("user"));
System.out.println(jedis.scard("user"));//返回集合的元素个数 4
有序集合类型
有序集合类型与集合类型的区别就是他是有序的。有序集合是在集合的基础上为每一个元素关联一个分数,这就让有序集合不仅支持插入,删除,判断元素是否存在等操作外,还支持获取分数最高/最低的前N个元素。有序集合中的每个元素是不同的,但是分数却可以相同。有序集合使用散列表和跳跃表实现,即使读取位于中间部分的数据也很快,时间复杂度为O(log(N)),有序集合比列表更费内存。
基本命令
ZADD 添加元素,用法:ZADD key score1 value1 [score2 value2 score3 value3 ...]
ZSCORE 获取元素的分数,用法:ZSCORE key value
ZRANGE 获取排名在某个范围的元素,用法:ZRANGE key start stop [WITHSCORE],按照元素从小到大的顺序排序,从0开始编号,包含start和stop对应的元素,WITHSCORE选项表示是否返回元素分数
ZREVRANGE 获取排名在某个范围的元素,用法:ZREVRANGE key start stop [WITHSCORE],和上一个命令用法一样,只是这个倒序排序的。
ZRANGEBYSCORE 获取指定分数范围内的元素,用法:ZRANGEBYSCORE key min max,包含min和max,(min表示不包含min,(max表示不包含max,+inf表示无穷大
ZINCRBY 增加某个元素的分数,用法:ZINCRBY key increment value
ZCARD 获取集合中元素的个数,用法:ZCARD key
ZCOUNT 获取指定分数范围内的元素个数,用法:ZCOUNT key min max,min和max的用法和5中的一样
ZREM 删除一个或多个元素,用法:ZREM key value1 [value2 ...]
ZREMRANGEBYRANK 按照排名范围删除元素,用法:ZREMRANGEBYRANK key start stop
ZREMRANGEBYSCORE 按照分数范围删除元素,用法:ZREMRANGEBYSCORE key min max,min和max的用法和4中的一样
ZRANK 获取正序排序的元素的排名,用法:ZRANK key value
ZREVRANK 获取逆序排序的元素的排名,用法:ZREVRANK key value
ZINTERSTORE 计算有序集合的交集并存储结果,用法:ZINTERSTORE destination numbers key1 key2 [key3 key4 ...] WEIGHTS weight1 weight2 [weight3 weight4 ...] AGGREGATE SUM | MIN | MAX,numbers表示参加运算的集合个数,weight表示权重,aggregate表示结果取值
ZUNIONSTORE 计算有序几个的并集并存储结果,用法和14一样,不再赘述。
利用Redis 实现消息队列
用redis中的List可以实现队列,这样可以用来做消息处理和任务调度的队列
代码模拟
生产者模拟程序
package redisQue;
import java.util.Random;
import java.util.UUID;
import redis.clients.jedis.Jedis;
/**
* 生产者模拟程序
* @ClassName: TaskProducer
* @Description: TODO(这里用一句话描述这个类的作用)
* @author zhouxiao
* @date 2017年1月18日 下午3:19:07
*
*/
public class TaskProducer implements Runnable {
Jedis jedis = new Jedis("127.0.0.1", 6379);
@Override
public void run() {
Random random = new Random();
while (true) {
try {
Thread.sleep(random.nextInt(600) + 600);
// 模拟生成一个任务
UUID taskid = UUID.randomUUID();
// 将任务插入任务队列:task-queue
//在名称为key的list头添加一个值为value的 元素
jedis.lpush("task-queue", taskid.toString());
System.out.println("插入了一个新的任务: " + taskid);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
消费者模拟程序
package redisQue;
import java.util.Random;
import redis.clients.jedis.Jedis;
/**
* 消费者模拟程序
*
* @ClassName: TaskConsumer
* @Description: TODO(这里用一句话描述这个类的作用)
* @author zhouxiao
* @date 2017年1月18日 下午3:19:53
*
*/
public class TaskConsumer implements Runnable {
Jedis jedis = new Jedis("127.0.0.1", 6379);
@Override
public void run() {
Random random = new Random();
while (true) {
// 从任务队列"task-queue"中获取一个任务,并将该任务放入暂存队列"tmp-queue"
String taskid = jedis.rpoplpush("task-queue", "tmp-queue");
// 处理任务----纯属业务逻辑,模拟一下:睡觉
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟成功和失败的偶然现象
if (random.nextInt(13) % 7 == 0) {// 模拟失败的情况,概率为2/13
// 将本次处理失败的任务从暂存队列"tmp-queue"中,弹回任务队列"task-queue"
jedis.rpoplpush("tmp-queue", "task-queue");
System.out.println(taskid + "处理失败,被弹回任务队列");
} else {// 模拟成功的情况
// 将本次任务从暂存队列"tmp-queue"中清除
jedis.rpop("tmp-queue");//返回并删除名称为key的list中的尾元素
System.out.println(taskid + "处理成功,被清除");
}
}
}
}
测试:
package redisQue;
public class TaskShedulerSystem {
public static void main(String[] args) throws InterruptedException {
// 启动一个生产者线程,模拟任务的产生
new Thread(new TaskProducer()).start();
Thread.sleep(15000);
// 启动一个线程者线程,模拟任务的处理
new Thread(new TaskConsumer()).start();
// 主线程休眠
//Thread.sleep(Long.MAX_VALUE);
}
}
运行结果:
插入了一个新的任务: af2849c9-8de6-4c48-b8d2-6e27ddf2da85
插入了一个新的任务: 6d717958-9f18-4e6f-9847-4a0af457ddd3
插入了一个新的任务: 0fc79d11-7963-4dca-b2b0-ce61a96fc93e
插入了一个新的任务: f84bb966-fed5-4481-a7f6-ed493d5a947a
插入了一个新的任务: 3fd756ba-e78e-43bb-b45c-ed1f97f76cc6
插入了一个新的任务: cc4e3dbb-badf-4a9c-aa5d-cd99272ac1d0
插入了一个新的任务: 474fbf92-0657-4c01-a0d7-b9775fbf791d
插入了一个新的任务: 56faa59b-9d7b-4231-9536-f1653e3d1b29
插入了一个新的任务: 9a31f80f-368f-4af9-b310-a2870d7d2108
插入了一个新的任务: 54d72a6d-5274-4049-b7f6-bf66dc0a68e5
插入了一个新的任务: 0b9a2e23-a7a1-4273-8d87-1184c88bab0a
插入了一个新的任务: 6cc65e89-f8f3-43b1-b3e5-54f7a0c2a71a
插入了一个新的任务: 32de3666-a2dc-4dc8-bc01-0491a1d60db5
插入了一个新的任务: 320d7743-e18b-43c0-aa50-855664836de2
插入了一个新的任务: da531afb-57bb-4f2e-ae02-8a8c1d024ea7
插入了一个新的任务: 3070f5c8-b357-4917-8623-b9c35270ae28
插入了一个新的任务: 0221334b-ecbc-4d65-aa7d-8cdef9ac168f
4ecf7bc4-9469-49ab-87a7-7ced1e9ef858处理成功,被清除
插入了一个新的任务: 501361b9-220f-46f9-a71c-6d1341e872e9
9d00a95e-2e1d-466e-a505-a9a83bffac63处理成功,被清除
插入了一个新的任务: 216abb32-321f-442c-a040-192091e81b20
插入了一个新的任务: d4ec41ec-9a23-47e6-9e9e-16df2f44abc7
6803cc7b-d277-4b49-b553-26f01afc8a2d处理失败,被弹回任务队列
插入了一个新的任务: 89652f67-e900-4e1b-ad0f-a4574752f7fe
54267a6d-daa6-409a-a600-82597ac76f42处理失败,被弹回任务队列
插入了一个新的任务: 32e05b04-a6d7-4712-b3b4-358a95749cc7
108f4d54-7efb-465a-9ae0-3fcc6d535309处理成功,被清除
插入了一个新的任务: 382f85f0-302b-4edc-b480-24c1579c4a19
b7c0fa98-e745-4923-b5a5-57ba224ea670处理失败,被弹回任务队列
插入了一个新的任务: af34d32c-4e9e-4720-8c7c-d8406c287a87
3a28d71c-9c94-494c-bc36-f2e363ec227e处理失败,被弹回任务队列
插入了一个新的任务: 9818721d-2dcc-41a2-8cf2-88cbd4b50ec1
217c2685-9fde-4fa7-b74e-f44450d59c6f处理成功,被清除
插入了一个新的任务: 9d6f4fcd-ab0f-4f6f-8df5-7493792ba7b2
eef4eed8-1611-4053-8bca-f36743f8cd2d处理成功,被清除
插入了一个新的任务: 093086fc-913a-44ce-afb8-bacb1d8c1942
f6de86c7-3d2d-4f72-8be4-d26a1725e4c8处理成功,被清除
插入了一个新的任务: a6f6e7f4-4f34-4020-8bf1-a3e8bca31d71
978e48d3-c01a-4660-8480-0462c77e2a17处理成功,被清除
插入了一个新的任务: 40e210d8-6650-4f2b-88ac-c5ca209233ce
2e66bc5f-3dac-426a-a4c9-511415eaa327处理失败,被弹回任务队列
插入了一个新的任务: f91687a1-a6ed-4b7b-a5fb-461866f51bda
9e5dd048-986f-4f8c-99ca-b936fe53ca25处理成功,被清除
插入了一个新的任务: 027f52f7-4c0e-4816-af0d-bde8ecfaa83e
6e4e19b9-f422-4fee-afd1-4dfe937c756f处理成功,被清除
插入了一个新的任务: 12bb5466-463f-4204-97e2-42c9a131335d
插入了一个新的任务: 634593ca-1f47-4ecf-8726-faf632ab2d2f
2d599fe6-ecff-4809-af98-d49b58d5752d处理成功,被清除
插入了一个新的任务: d4d57578-882b-491f-bee9-7833184dac3d
1453e09e-1782-4c65-9739-2773169afc66处理成功,被清除
插入了一个新的任务: 904bdfb7-4385-4060-8a2b-565a00396d3b
834af691-69a1-4760-b569-c08e68f193aa处理成功,被清除
插入了一个新的任务: 30c7c8f0-a3c1-4616-aabf-ad252fef802e
1898edb8-893f-4f21-a67a-78c6d7817d18处理成功,被清除
插入了一个新的任务: 019438b4-e91f-4e98-9480-0588176a46c6
2b854514-9b67-49a1-b86a-473bc5b1acfa处理成功,被清除
插入了一个新的任务: d7bde16e-a082-48ca-93b9-6bc3742b42bc
6f45f8d3-7971-470b-a4a2-db5e1a2466ac处理成功,被清除
插入了一个新的任务: ed3e5213-2089-4b30-8ff7-ef71cc954395
7a0f7769-cf5d-4190-b47d-de0f87209f54处理成功,被清除
插入了一个新的任务: f173855a-c28e-428e-920e-d83d8136f5f5
cf5ac975-ec34-4ba2-8a00-f06c61f0badb处理失败,被弹回任务队列
插入了一个新的任务: d9970bfa-c371-43e9-a531-005a906d7598
5e5c4a42-d3f0-43b6-8034-64e620078a46处理成功,被清除
插入了一个新的任务: e9136498-a586-4585-84ec-a1d8119d4577
cf8cd768-46e2-4a04-bb00-16d7fdd36767处理失败,被弹回任务队列
基于聊天内容的消息推送
应用场景
最近在公司做项目,需要对聊天内容进行存储,考虑到数据库查询的IO连接数高、连接频繁的因素,决定利用缓存做。从网上了解到redis可以对所有的内容进行二进制的存储,而java是可以对所有对象进行序列化的,序列化的方法会在下面的代码中提供实现。
序列化
这里我编写了一个java序列化的工具,主要是对对象转换成byte[],和根据byte[]数组反序列化成java对象;
主要是用到了ByteArrayOutputStream和ByteArrayInputStream;
需要注意的是每个自定义的需要序列化的对象都要实现Serializable接口;
其代码如下:
package content;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectUtil {
/**对象转byte[]
* @param obj
* @return
* @throws IOException
*/
public static byte[] objectToBytes(Object obj) throws Exception{
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(obj);
byte[] bytes = bo.toByteArray();
bo.close();
oo.close();
return bytes;
}
/**byte[]转对象
* @param bytes
* @return
* @throws Exception
*/
public static Object bytesToObject(byte[] bytes) throws Exception{
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
ObjectInputStream sIn = new ObjectInputStream(in);
return sIn.readObject();
}
}
定义一个消息类,主要用于接收消息内容和消息下表的设置。
package content;
import java.io.Serializable;
/**
* 定义消息类接收消息内容和设置消息的下标
* @ClassName: Message
* @Description: TODO(这里用一句话描述这个类的作用)
* @author zhouxiao
* @date 2017年4月21日 下午2:42:55
*
*/
public class Message implements Serializable{
/**
* @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 3744820876989572719L;
private int id;
private String content;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
利用redis做队列,我们采用的是redis中list的push和pop操作;
结合队列的特点:
只允许在一端插入新元素只能在队列的尾部FIFO:先进先出原则
redis中lpush(rpop)或rpush(lpop)可以满足要求,而redis中list 里要push或pop的对象仅需要转换成byte[]即可
java采用Jedis进行redis的存储和redis的连接池设置
package content;
import java.util.List;
import java.util.Map;
import java.util.Set;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisUtil {
private static JedisPool jedisPool;
static {
jedisPool = new JedisPool("127.0.0.1", 6379);
}
/**
* 获取数据
*
* @param key
* @return
*/
@SuppressWarnings("deprecation")
public static String get(String key) {
String value = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
value = jedis.get(key);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return value;
}
@SuppressWarnings("deprecation")
public static void close(Jedis jedis) {
try {
jedisPool.returnResource(jedis);
} catch (Exception e) {
if (jedis.isConnected()) {
jedis.quit();
jedis.disconnect();
}
}
}
/**
* 获取数据
*
* @param key
* @return
*/
@SuppressWarnings("deprecation")
public static byte[] get(byte[] key) {
byte[] value = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
value = jedis.get(key);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return value;
}
@SuppressWarnings("deprecation")
public static void set(byte[] key, byte[] value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set(key, value);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
@SuppressWarnings("deprecation")
public static void set(byte[] key, byte[] value, int time) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set(key, value);
jedis.expire(key, time);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
@SuppressWarnings("deprecation")
public static void hset(byte[] key, byte[] field, byte[] value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.hset(key, field, value);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
@SuppressWarnings("deprecation")
public static void hset(String key, String field, String value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.hset(key, field, value);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
/**
* 获取数据
*
* @param key
* @return
*/
@SuppressWarnings("deprecation")
public static String hget(String key, String field) {
String value = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
value = jedis.hget(key, field);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return value;
}
/**
* 获取数据
*
* @param key
* @return
*/
@SuppressWarnings("deprecation")
public static byte[] hget(byte[] key, byte[] field) {
byte[] value = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
value = jedis.hget(key, field);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return value;
}
@SuppressWarnings("deprecation")
public static void hdel(byte[] key, byte[] field) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.hdel(key, field);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
/**
* 存储REDIS队列 顺序存储
*
* @param byte[]
* key reids键名
* @param byte[]
* value 键值
*/
@SuppressWarnings("deprecation")
public static void lpush(byte[] key, byte[] value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.lpush(key, value);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
/**
* 存储REDIS队列 反向存储
*
* @param byte[]
* key reids键名
* @param byte[]
* value 键值
*/
@SuppressWarnings("deprecation")
public static void rpush(byte[] key, byte[] value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.rpush(key, value);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
/**
* 将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端
*
* @param byte[]
* key reids键名
* @param byte[]
* value 键值
*/
@SuppressWarnings("deprecation")
public static void rpoplpush(byte[] key, byte[] destination) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.rpoplpush(key, destination);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
/**
* 获取队列数据
*
* @param byte[]
* key 键名
* @return
*/
@SuppressWarnings("deprecation")
public static List<byte[]> lpopList(byte[] key) {
List<byte[]> list = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
list = jedis.lrange(key, 0, -1);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return list;
}
/**
* 获取队列数据
*
* @param byte[]
* key 键名
* @return
*/
@SuppressWarnings("deprecation")
public static byte[] rpop(byte[] key) {
byte[] bytes = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
bytes = jedis.rpop(key);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return bytes;
}
@SuppressWarnings("deprecation")
public static void hmset(Object key, Map<String,String> hash) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.hmset(key.toString(), hash);
} catch (Exception e) {
//释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
//返还到连接池
close(jedis);
}
}
@SuppressWarnings("deprecation")
public static void hmset(Object key, Map<String,String> hash, int time) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.hmset(key.toString(), hash);
jedis.expire(key.toString(), time);
} catch (Exception e) {
//释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
//返还到连接池
close(jedis);
}
}
@SuppressWarnings("deprecation")
public static List<String> hmget(Object key, String... fields) {
List<String> result = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
result = jedis.hmget(key.toString(), fields);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return result;
}
@SuppressWarnings("deprecation")
public static Set<String> hkeys(String key) {
Set<String> result = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
result = jedis.hkeys(key);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return result;
}
@SuppressWarnings("deprecation")
public static List<byte[]> lrange(byte[] key, int from, int to) {
List<byte[]> result = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
result = jedis.lrange(key, from, to);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return result;
}
@SuppressWarnings("deprecation")
public static Map<byte[], ?> hgetAll(byte[] key) {
Map<byte[],?> result = null;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
result = jedis.hgetAll(key);
} catch (Exception e) {
//释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
//返还到连接池
close(jedis);
}
return result;
}
@SuppressWarnings("deprecation")
public static void del(byte[] key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.del(key);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
}
@SuppressWarnings("deprecation")
public static long llen(byte[] key) {
long len = 0;
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.llen(key);
} catch (Exception e) {
// 释放redis对象
jedisPool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
// 返还到连接池
close(jedis);
}
return len;
}
}
测试redis队列
package content;
public class TestRedisQuene {
public static byte[] redisKey = "key".getBytes();
static{
try {
JedisUtil.del(redisKey);
init();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
pop();
}
private static void pop() throws Exception {
for(int i=0;i<10;i++){
byte[] bytes = JedisUtil.rpop(redisKey);
Message msg = (Message) ObjectUtil.bytesToObject(bytes);
if(msg != null){
System.out.println(msg.getId()+" "+msg.getContent());
}
}
}
private static void init() throws Exception {
for (int i = 0; i < 10; i++) {
Message message = new Message(i, "这是第" + i + "个内容");
JedisUtil.lpush(redisKey, ObjectUtil.objectToBytes(message));
}
}
}
测试结果:
0 这是第0个内容
1 这是第1个内容
2 这是第2个内容
3 这是第3个内容
4 这是第4个内容
5 这是第5个内容
6 这是第6个内容
7 这是第7个内容
8 这是第8个内容
9 这是第9个内容
Jedis的八种调用方式详解
redis是一个著名的key-value存储系统,也是nosql中的最常见的一种。其实,个人认为,redis最强大的地方不在于其存储,而在于其强大的缓存作用。
我们可以把它想象成一个巨大的(多借点集群,聚合多借点的内存)的Map,也就是Key-Value。
所以,我们可以把它做成缓存组件。
官方推荐的java版客户端是jedis,非常强大和稳定,支持事务、管道及有jedis自身实现。我们对redis数据的操作,都可以通过jedis来完成。
那我们就来看一看,jedis不同的调用方式:
普通同步方式
这是一种最简单和最基础的调用方式,对于简单的数据存取需求,我们可以通过这种方式调用。
Jedis jedis = new Jedis("localhost");
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = jedis.set("n" + i, "n" + i);
}
long end = System.currentTimeMillis();
System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
//每次set之后都可以返回结果,标记是否成功。
Simple SET: 20.698 seconds
事务方式(Transactions)
所谓事务,即一个连续操作,是否执行是一个事务,要么完成,要么失败,没有中间状态。
而redis的事务很简单,他主要目的是保障,一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令,也就是事务的连贯性。
Jedis jedis = new Jedis("localhost");
long start = System.currentTimeMillis();
Transaction tx = jedis.multi();
for (int i = 0; i < 100000; i++) {
tx.set("t" + i, "t" + i);
}
List<Object> results = tx.exec();
long end = System.currentTimeMillis();
System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
//我们调用jedis.watch(…)方法来监控key,如果调用后key值发生变化,则整个事务会执行失败。另外,事务中某个操作失败,并不会回滚其他操作。这一点需要注意。还有,我们可以使用discard()方法来取消事务。
Simple SET: 15.52 seconds
管道(Pipelining)
管道是一种两个进程之间单向通信的机制。
那再redis中,为何要使用管道呢?有时候,我们需要采用异步的方式,一次发送多个指令,并且,不同步等待其返回结果。这样可以取得非常好的执行效率。
Jedis jedis = new Jedis("localhost");
Pipeline pipeline = jedis.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("p" + i, "p" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
Pipelined SET: 0.561 seconds
管道中调用事务
对于,事务以及管道,这两个概念我们都清楚了。
在某种需求下,我们需要异步执行命令,但是,又希望多个命令是有连续的,所以,我们就采用管道加事务的调用方式。jedis是支持在管道中调用事务的。
Jedis jedis = new Jedis("localhost");
long start = System.currentTimeMillis();
Pipeline pipeline = jedis.pipelined();
pipeline.multi();
for (int i = 0; i < 100000; i++) {
pipeline.set("" + i, "" + i);
}
pipeline.exec();
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
Pipelined transaction: 0.923 seconds
分布式直连同步调用
个是分布式直接连接,并且是同步调用,每步执行都返回执行结果。类似地,还有异步管道调用。
其实就是分片。
List<JedisShardInfo> shards = Arrays.asList(new JedisShardInfo("localhost",6379));
ShardedJedis sharding = new ShardedJedis(shards);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = sharding.set("sn" + i, "n" + i);
}
long end = System.currentTimeMillis();
System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds");
sharding.disconnect();
Simple@Sharing SET: 15.989 seconds
分布式直连异步调用
List<JedisShardInfo> shards = Arrays.asList(new JedisShardInfo("localhost",6379));
ShardedJedis sharding = new ShardedJedis(shards);
ShardedJedisPipeline pipeline = sharding.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("sp" + i, "p" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds");
sharding.disconnect();
Pipelined@Sharing SET: 1.268 seconds
分布式连接池同步调用
如果,你的分布式调用代码是运行在线程中,那么上面两个直连调用方式就不合适了,因为直连方式是非线程安全的,这个时候,你就必须选择连接池调用。
连接池的调用方式,适合大规模的redis集群,并且多客户端的操作。
List<JedisShardInfo> shards = Arrays.asList(new JedisShardInfo("localhost",6379));
ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards);
ShardedJedis one = pool.getResource();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = one.set("spn" + i, "n" + i);
}
long end = System.currentTimeMillis();
pool.returnResource(one);
System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds");
pool.destroy();
Simple@Pool SET: 16.415 seconds
分布式连接池异步调用
List<JedisShardInfo> shards = Arrays.asList(new JedisShardInfo("localhost",6379));
ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards);
ShardedJedis one = pool.getResource();
ShardedJedisPipeline pipeline = one.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("sppn" + i, "n" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
pool.returnResource(one);
System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds");
pool.destroy();
Pipelined@Pool SET: 1.538 seconds
总结
事务和管道都是异步模式。在事务和管道中不能同步查询结果。比如下面两个调用,都是不允许的:
Transaction tx = jedis.multi();
for (int i = 0; i < 100000; i++) {
tx.set("t" + i, "t" + i);
}
System.out.println(tx.get("t1000").get()); //不允许
List<Object> results = tx.exec();
…
…
Pipeline pipeline = jedis.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("p" + i, "p" + i);
}
System.out.println(pipeline.get("p1000").get()); //不允许
List<Object> results = pipeline.syncAndReturnAll();
事务和管道都是异步的,个人感觉,在管道中再进行事务调用,没有必要,不如直接进行事务模式。
分布式中,连接池的性能比直连的性能略好(见后续测试部分)。
分布式调用中不支持事务。
因为事务是在服务器端实现,而在分布式中,每批次的调用对象都可能访问不同的机器,所以,没法进行事务。
分布式中,连接池方式调用不但线程安全外,根据上面的测试数据,也可以看出连接池比直连的效率更好。
经测试分布式中用到的机器越多,调用会越慢。
发布/订阅系统
package subPub;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
public class TestPubSub extends JedisPubSub {
@Override
public void onMessage(String channel, String message) {
System.out.println("onMessage: channel[" + channel + "], message[" + message + "]");
}
@Override
public void onPMessage(String pattern, String channel, String message) {
System.out.println("onPMessage: channel[" + channel + "], message[" + message + "]");
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
System.out.println("onSubscribe: channel[" + channel + "]," +
"subscribedChannels[" + subscribedChannels + "]");
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
System.out.println("onUnsubscribe: channel[" + channel + "], " +
"subscribedChannels[" + subscribedChannels + "]");
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
System.out.println("onPUnsubscribe: pattern[" + pattern + "]," +
"subscribedChannels[" + subscribedChannels + "]");
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
System.out.println("onPSubscribe: pattern[" + pattern + "], " +
"subscribedChannels[" + subscribedChannels + "]");
}
public static void main(String[] args) {
Jedis jr = null;
try {
jr = new Jedis("127.0.0.1", 6379, 0);//redis服务地址和端口号
TestPubSub sp = new TestPubSub();
sp.proceed(jr.getClient(),"news.share", "news.blog");
//sp.proceedWithPatterns(jr.getClient(), "news.*");
} catch (Exception e) {
e.printStackTrace();
}
finally{
if(jr!=null){
jr.disconnect();
}
}
}
}
代码就是用TestPubSub对象来订阅,对象中的那此onXXX方法监听到相应事件
1 首先运行此java程序;
onSubscribe: channel[news.share], subscribedChannels[1]
onSubscribe: channel[news.blog], subscribedChannels[2]
2 启动redis-cli
redis 127.0.0.1:6379> psubscribe news.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "news.*"
3) (integer) 1
3 再启动一个redis-cli用来发布两条消息:
redis 127.0.0.1:6379> publish news.share "share a link http://www.google.com"
(integer) 2
redis 127.0.0.1:6379> publish news.blog "I post a blog"
(integer) 2
4.查看两个订阅client的输出
此时Java client打印如下内容:
onMessage: channel[news.share], message[share a link http://www.google.com]
onMessage: channel[news.blog], message[I post a blog]
另一个redis-cli输出如下:
1) "pmessage"
2) "news.*"
3) "news.share"
4) "share a link http://www.google.com"
1) "pmessage"
2) "news.*"
3) "news.blog"
4) "I post a blog"
redis client使用psubscribe订阅了一个使用通配符的通道(表示任意字符串),此订阅会收到所有与news.匹配的通道消息。redis- cli打印到控制台的订阅成功消息表示使用psubscribe命令订阅news.*成功后,连接订阅通道总数为1。
当我们在一个client使用publish向news.share和news.blog通道发出两个消息后。redis返回的(integer) 2表示有两个连接收到了此消息。
看完一个小例子后应该对pub/sub功能有了一个感性的认识。需要注意的是当一个连接通过subscribe或者psubscribe订阅通道后就进入订阅模式。在这种模式除了再订阅额外的通道或者用unsubscribe或者punsubscribe命令退出订阅模式,就不能再发送其他命令。另外使用 psubscribe命令订阅多个通配符通道,如果一个消息匹配上了多个通道模式的话,会多次收到同一个消息。redis的pub/sub还是有点太单薄(实现才用150行代码)。在安全,认证,可靠性这方便都没有太多支持
package subPub;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
public class Subscriber extends JedisPubSub{
@Override
public void onSubscribe(String channel, int subscribedChannels) {
System.out.println("subscribe channel : " + channel + ", total channel num : " + subscribedChannels);
}
@Override
public void onMessage(String channel, String message) {
System.out.println("receive message : " + message + " from channel : " + channel);
}
public static void main(String[] args) {
Jedis jedis = null;
try{
jedis = new Jedis("localhost", 6379);
jedis.subscribe(new Subscriber(), "first", "second");
}catch(Exception e){
e.printStackTrace();
}finally {
if(jedis != null){
jedis.close();
}
}
}
}
package subPub;
import redis.clients.jedis.Jedis;
public class Publisher {
public static void main(String[] args) {
Jedis jedis = null;
try{
jedis = new Jedis("localhost", 6379);
jedis.publish("second", "hello world!");
}catch (Exception e){
e.printStackTrace();
}finally {
if(jedis != null){
jedis.close();
}
}
}
}
运行结果
subscribe channel : first, total channel num : 1
subscribe channel : second, total channel num : 2
receive message : hello world! from channel : second
常用命令
Redis清空
进入redis目录下
redis-cli
flushall