[TOC]

0x00 前言

描述:Redis是一个开源的Key-Value数据缓存,和Memcached类似。现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。

Redis多种类型的value,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。

Jedis 是 Redis 官方首选的 Java 客户端开发包,其他我们有shardjedis可以进行备选;

环境准备:
开始在 Java 中使用 Redis 前, 我们需要确保已经安装了 redis 服务及 Java redis 驱动,且你的机器上能正常使用 Java。


0x01 基础语法

描述:主要列举了Jedis中常用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#(1) redis的默认端口是6379
Jedis jedis = new Jedis ("localhost",6379);


#(2) 密码验证
jedis.auth("password");


#(3) 连接验证
jedis.connect();
jedis.ping();


#(4) 断开连接
jedis.disconnect();


#(5) 数据库号选择
jedis.select(1);


#(6) 字符串键值设置与获取(增删改查)
jedis.set("Key","Value");
jedis.setex("foo", 5, "tor"); #将值value关联到key,并将key的生存时间设为seconds(秒)。
jedis.get("Key");
Set<String> keys = jedis.keys("*"); #列出所有的key并存入集合中
Set<String> keys = jedis.keys("key"); #查找特定的key并存入集合中
jedis.exists("key1"); #检查给定key是否存在
jedis.expire("key1", 5); #设置key生存时间,当key过期时,它会被自动删除。
jedis.persist("key1"); #移除给定key的生存时间(设置这个key永不过期)
jedis.type("key1"); #返回key所储存的值的类型。
#none(key不存在),string(字符串),list(列表),set(集合),zset(有序集),hash(哈希表)
jedis.rename("key1", "key2"); #将key改名为newkey,当key和newkey相同或者key不存在时,返回一个错误
jedis.del("key1","key2","key3","key4","key5"); #移除给定的一个或多个key不存在则不执行;
jedis.dbSize(); #返回key的个数
jedis.flushAll(); #清空所有的key


#(7) List集合增删改查
jedis.lpush("ListKey1","Value0"); #将值value插入到列表key的表头。
jedis.lpush("ListKey1","Value1");
jedis.llen("key1") #返回列表key的长度。
#//返回列表key中指定区间内的元素,区间以偏移量start和stop指定.
#//下标(index)参数start和stop从0开始;
#//负数下标代表从后开始(-1表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推)
List<String> list = jedis.lrange("ListKey1", 0 ,2); #lrange("key1", 0, -1); stop下标也在取值范围内(闭区间)
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}


#(8) Hash集合增删改查
jedis.hset("hashkey1", "field1", "field1-value"); #设置单个哈希表key中的域field的值设为value
jedis.hset("hashkey1", "field2", "field2-value"); #可以不断向哈希表key中添加键值对

Map<String,String> map = new HashMap<String,String>();
map.put("field1", "field1-value");
map.put("field2", "field2-value");
jedis.hmset("hkey1", map); #哈希表key中的不同域field的值设为value

jedis.hkeys("key1"); #返回哈希表key中的所有域
jedis.hvals("key1"); #返回哈希表key中的所有值

jedis.hget("hkey1", "field1"); #返回哈希表key中给定域field的值
Map<String,String> map = jedis.hgetAll("hashkey1"); #返回哈希表key中所有域和值
for(Map.Entry entry: map.entrySet()) {
System.out.print(entry.getKey() + ":" + entry.getValue() + "\t");
}

jedis.hexists("key1", "field1"); #查看哈希表key中,给定域field是否存在
jedis.hdel("key1", "field1","field2");#删除哈希表key中的一个或多个指定域


#(9) set集合增删改查
jedis.sadd("key1", "value0"); #//将member元素加入到集合key当中。
jedis.sadd("key1", "value1");
jedis.sismember("key1", "value2")); #//判断元素是否是集合key的成员

jedis.scard("key1"); #//返回集合key的元素的数量
Set set = jedis.smembers("key1"); #//返回集合key中的所有成员。
for (Object val : set) {
System.out.println(val);
}

#交、并、补
jedis.sinter("key1","key2") #//返回一个集合的全部成员,该集合是所有给定集合的交集
jedis.sunion("key1","key2") #//返回一个集合的全部成员,该集合是所有给定集合的并集
jedis.sdiff("key1","key2"); #//返回一个集合的全部成员,该集合是所有给定集合的差集

jedis.srem("key1", "value1"); #//移除集合中的member元素。


#(10)额外补充
jedis.set("count","1")
System.out.println("incr key = " + jedis.incr("count"));
System.out.println("incrby key 5 = " + jedis.incrBy("count", 5));


0x02 实际案例

描述: 采用Jedis连接Redis数据库的基础使用案例

Redis连接认证配置文件:config.properties

1
2
3
# Connection Redis Configure
RedisUrl=10.20.10.248:6379
RedisAuth=weiyigeek.top

示例1.基础语法使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package top.weiyigeek.connredis;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import redis.clients.jedis.Jedis;

/**
* Redis 连接测试
* @author WeiyiGeek
*/
public class RedisDemo1 {
public static void main(String[] args) {
String[] RedisUrl=null;
String RedisAuth=null;
try {
//1.创建一个属性配置对象并打开配置文件
Properties prop = new Properties();
InputStream ins = RedisDemo1.class.getClassLoader().getResourceAsStream("config.properties");
prop.load(ins);
RedisUrl=prop.getProperty("RedisUrl").split(":");
RedisAuth=prop.getProperty("RedisAuth");
System.out.println("RedisServer:"+ prop.getProperty("RedisUrl") + "\nPassword:" + RedisAuth );

//2.连接到 redis 服务
Jedis jedis = new Jedis(RedisUrl[0],Integer.parseInt(RedisUrl[1]));
System.out.println("正在Redis认证连接...");
jedis.auth(RedisAuth);

//3.查看链接是否成功并且服务是否运行
System.out.println("服务正在运行: "+jedis.ping()+"\n");

//4.选择一号库
jedis.select(1);

//5.设置 redis 字符串数据与 获取存储的数据并输出
String key = "WeiyiGeek";
jedis.set(key, "www.weiyigeek.top");
jedis.set("count", "1");
jedis.setex("Key", 60, "60 Sec");
System.out.println("当前数据库总键数:"+jedis.dbSize());
if(jedis.exists(key)) {
System.out.println("Redis中WeyiGeek键存储的字符串为:"+ jedis.get(key));
System.out.println("其类型为 : " + jedis.type(key));
}
//value + 1 并返回其其值
System.out.println("incr key = " + jedis.incr("count"));
System.out.println("incrby key 5 = " + jedis.incrBy("count", 5));

//6.采用迭代器进行遍历所有键值
Set<String> keys = jedis.keys("*");
for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
String k = (String) iterator.next();
if (jedis.type(k).equalsIgnoreCase("string")) {
System.out.println(k + " - " + jedis.get(k));
}

}
System.out.println("");

//List集合
RedisListTest(jedis);

//Hash集合
RedisHashTest(jedis);

//Set集合
RedisSetTest(jedis);

//Zset集合
RedisZsetTest(jedis);

//清空所有的key
jedis.flushAll();

//关闭释放jedis连接资源
jedis.disconnect();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

//7.列表集合存储K-V数据到列表
public static void RedisListTest(Jedis jedis) {
jedis.lpush("site-list", "WeiyiGeek");
jedis.lpush("site-list", "Google");
jedis.lpush("site-list", "Taobao");
System.out.println("列表中元素数量: " + jedis.llen("site-list"));
// 获取存储的数据并输出
List<String> list = jedis.lrange("site-list", 0 , 2);
for(int i=0; i<list.size(); i++) {
System.out.println("列表项为: "+list.get(i));
}
}

//8.Hash值
public static void RedisHashTest(Jedis jedis) {
//hash值存入
Map<String, String> map = new HashMap<String, String>();
jedis.hset("hashkey1", "field1", "1024"); //
jedis.hset("hashkey1", "field2", "whoami");
map.put("field3","This");
map.put("field4","10.24");
jedis.hmset("hashkey1", map);

if (jedis.hexists("hashkey1", "field1")) {
System.out.println("\n返回哈希表中给定field的值:" + jedis.hget("hashkey1", "field1"));
System.out.println("哈希表所有中field域名称: " + jedis.hkeys("hashkey1"));
System.out.println("哈希表所有中field域的值: " +jedis.hvals("hashkey1"));
}
//循环遍历读取添加的域与值
Map <String,String> map1 = jedis.hgetAll("hashkey1");
for (Map.Entry<String, String> entry: map1.entrySet()) {
System.out.println("域:" + entry.getKey() + ", 值: " + entry.getValue());
}
}

//9.Set集合
public static void RedisSetTest(Jedis jedis) {
jedis.sadd("setkey1", "Java");
jedis.sadd("setkey1", "Python");
jedis.sadd("setkey2", "Redis");
jedis.sadd("setkey2", "Java");
jedis.sadd("setkey2", "Java"); //自动剔除重复的元素
System.out.println("\nset 集合中setkey2键中元素数量 : " + jedis.scard("setkey2"));

if(!jedis.sismember("setkey1", "setkey2")) {
Set set = jedis.smembers("setkey2"); //返回集合key中的所有成员。
for (Object val : set) {
System.out.println(val);
}
}
//#交、并、补
System.out.println("交集:" + jedis.sinter("setkey1","setkey2")); //返回一个集合的全部成员,该集合是所有给定集合的交集
System.out.println("并集:" + jedis.sunion("setkey1","setkey2")); //返回一个集合的全部成员,该集合是所有给定集合的并集
System.out.println("差集:" + jedis.sdiff("setkey1","setkey2")); //返回一个集合的全部成员,该集合是所有给定集合的差集

}

//10.Zset集合
public static void RedisZsetTest(Jedis jedis) {
jedis.zadd("Zsetkey", 0, "redis");
jedis.zadd("Zsetkey", 0, "java");
jedis.zadd("Zsetkey", 1, "eclipse");
Map<String, Double> map = new HashMap<String, Double>();
map.put("WeiyiGeek", 1.0);
map.put("Weiyi", 3.0);
map.put("极客", 4.0);
jedis.zadd("Zsetkey", map);
System.out.println("\n有序集合元素个数: " + jedis.zcard("Zsetkey"));
System.out.println("指定集合范围内的元素个数: " + jedis.zcount("Zsetkey", 0, 1)); //把1.0 等价于 1
//循环遍历有序计集合
Set set = jedis.zrangeByScore("Zsetkey",0, 100);
for (Object val : set) {
System.out.println(val);
}
}
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
RedisServer:10.20.10.248:6379
Password:weiyigeek.top
正在Redis认证连接...
服务正在运行: PONG

当前数据库总键数:3
Redis中WeyiGeek键存储的字符串为:www.weiyigeek.top
其类型为 : string
incr key = 2
incrby key 5 = 7
count - 7
WeiyiGeek - www.weiyigeek.top
Key - 60 Sec

列表中元素数量: 3
列表项为: Taobao
列表项为: Google
列表项为: WeiyiGeek

返回哈希表中给定field的值:1024
哈希表所有中field域名称: [field1, field3, field2, field4]
哈希表所有中field域的值: [1024, whoami, 10.24, This]
域:field3, 值: This
域:field2, 值: whoami
域:field1, 值: 1024
域:field4, 值: 10.24

set 集合中setkey2键中元素数量 : 2
Java
Redis
交集:[Java]
并集:[Java, Python, Redis]
差集:[Python]

有序集合元素个数: 6
指定集合范围内的元素个数: 4
java
redis
WeiyiGeek
eclipse
Weiyi
极客

WeiyiGeek.Redis

WeiyiGeek.Redis


0x03 工具包

Jedis连接池

描述:jedis连接资源的创建与销毁是很消耗程序性能,所以jedis为我们提供了jedis的池化技术,在创建时初始化一些连接资源存储到连接池中,使用jedis连接资源时不需要创建,而是从连接池中获取一个资源进行redis的操作,使用完毕后不需要销毁该jedis连接资源,而是将该资源归还给连接池供其他请求使用。

注意事项:

  • 1) Redis连接池建立依赖Jedis与commons-pool2-2.8.0.jar包;
  • 2) JedisPoolConfig继承关系,上面我们说到JedisPoolConfig需要依赖Apache common pool,其中pool配置依赖 common pool中的BaseObjectPoolConfig类中定义了相关属性的缺省值,在JedisPoolConfig中定义了相关的属性;
    1
    2
    3
    4
    5
    6
    7
    8
    #Jedispoolconfig继承关系
    JedisPoolConfig -> GenericObjectPoolConfig -> BaseObjectPoolConfig -> Cloneable

    #属性值&缺省值
    setTestWhileIdle(); | true
    setMinEvictableIdleTimeMillis(); | 60000
    setTimeBetweenEvictionRunsMillis(); | 30000
    setNumTestsPerEvictionRun(); | -1
  • 3) JedisPoolConfig参数一览
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    setJmxEnabled                          #设置是否启用JMX,默认true
    setJmxNameBase(String jmxNameBase) #设置JMX基础名
    setJmxNamePrefix(String jmxNamePrefix) #设置JMX前缀名,默认值pool

    setLifo(boolean lifo) #设置连接对象是否后进先出默认true
    setMaxIdle(int maxIdle) #设置最大空闲连接数满了则将逐出,默认为8(一般为最大连接数的45%~50%)
    setMinIdle(int minIdle) #设置无连接时池中最小的连接个数,默认连接0
    setMaxTotal(int maxTotal) #设置最大连接数,默认18个
    setMaxWaitMillis(long maxWaitMillis) #获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1

    setTestOnBorrow(boolean testOnBorrow) #从池中获取连接时是否测试连接的有效性,默认false(会消耗一定的时间但是为了稳定建议开启)
    setTestOnCreate(boolean testOnCreate) #在连接对象创建时测试连接对象的有效性,默认false
    setTestOnReturn(boolean testOnReturn) #在连接对象返回时是否测试对象的有效性,默认false
    setTestWhileIdle(boolean testWhileIdle) #在连接池空闲时是否测试连接对象的有效性,默认false
    setBlockWhenExhausted(boolean blockWhenExhausted) #当池中的资源耗尽时是否进行阻塞,设置false直接报错,true表示会一直等待,直到有可用资源

    setNumTestsPerEvictionRun(int numTestsPerEvictionRun) #每次逐出检查时,逐出连接的个数
    setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) #设置连接最小的逐出间隔时间,默认1800000毫秒
    setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis); #对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断
    setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) #设置连接对象有效性扫描间隔,设置为-1,则不运行逐出线程

    setFairness(boolean fairness) #当从池中获取资源或者将资源还回池中时 是否使用java.util.concurrent.locks.ReentrantLock.ReentrantLock 的公平锁机制,默认为false
    setEvictionPolicyClassName(String evictionPolicyClassName) #设置逐出策略,默认策略为"org.apache.commons.pool2.impl.DefaultEvictionPolicy"

    getNumActive() #当前池中被激活的数量


基础示例1:(单机Redis连接池)
jedis.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
#Jedis Pool Configure
RedisHost=10.20.172.248
RedisPort=6379
RedisAuth=WeiyiGeek.top
RedisDBIndex=1
RedisTimeout=20
RedisMaxIdle=50
RedisMinIdle=100
RedisMaxTotal=100
RedisWaitMillis=3000
RedisTestBorrow=true
RedisTestReturn=true
RedisBlockWhenExhausted=true

/Web/src/top/weiyigeek/utils/RedisPoolUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package top.weiyigeek.utils;

import java.util.ResourceBundle;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
* Redis 连接池工具类建立
* @author WeiyiGeek
* @Date: 2020年5月31日 23:47:36
* @Desc: Jedis(2.9.0)
*/
public class RedisPoolUtil {
private static JedisPool jedisPool; //jedis连接池
private static String host; //redis主机
private static Integer port; //redis端口
private static String auth; //redis认证
private static Integer dbindex; //redis仓库选择(0~15)
private static Integer timeout; //redis连接超时时间(毫秒)
private static Integer maxIdle; //在jedispool中最大的idle状态(空闲的)jedis实例的个数
private static Integer minIdle; //在jedispool中最小的idle状态(空闲的)jedis实例的个数
private static Integer maxTotal; //redis最大连接数
private static Integer maxWaitMillis; //获取连接最大的等待时间(毫秒)
private static Boolean testOnBorrow;//在borow一个jedis实例的时候,是否要进行验证操作,如果赋值true,则得到的jedis实例肯定是可以用的。
private static Boolean testOnReturn; //在return一个jedis实例的时候,是否要进行验证操作,如果赋值true,则放回的jedis实例肯定是可以用的。
private static Boolean blockWhenExhausted; //连接耗尽的时候是否阻塞,false会抛出异常,true阻塞直到超时。默认true



//静态代码块可以在应用启动加载时候进行
/** 读取jedis.properties配置文件 **/
static {
ResourceBundle rb = ResourceBundle.getBundle("jedis");
host = rb.getString("RedisHost");
port = Integer.parseInt(rb.getString("RedisPort"));
auth = rb.getString("RedisAuth");
dbindex = Integer.parseInt(rb.getString("RedisDBIndex"));
timeout = Integer.parseInt(rb.getString("RedisTimeout"));
maxIdle = Integer.parseInt(rb.getString("RedisMaxIdle"));
minIdle = Integer.parseInt(rb.getString("RedisMinIdle"));
maxTotal = Integer.parseInt(rb.getString("RedisMaxTotal"));
maxWaitMillis = Integer.parseInt(rb.getString("RedisWaitMillis"));
testOnBorrow = Boolean.parseBoolean(rb.getString("RedisTestBorrow"));
testOnReturn = Boolean.parseBoolean(rb.getString("RedisTestReturn"));
blockWhenExhausted = Boolean.parseBoolean(rb.getString("RedisBlockWhenExhausted"));
}

/** 方式1.类加载的时候初始化连接池,采用静态内部类中的静态域存储唯一一个实例,
* 既保证了线程安全又保证了懒加载 (重点) **/
static {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMaxWaitMillis(maxWaitMillis);
poolConfig.setTestOnBorrow(testOnBorrow);
poolConfig.setTestOnReturn(testOnReturn);
poolConfig.setBlockWhenExhausted(blockWhenExhausted);
jedisPool = new JedisPool(poolConfig, host, port, timeout, auth, dbindex);
}

/** 获取 jedis 连接池 中连接**/
public static Jedis getJedis(){
System.out.println("当前Redis连接池被使用的数量: "+(jedisPool.getNumActive()+1));
return jedisPool.getResource();
}
/** 补充:关闭 Jedis 将3.0版本开始使用 **/
//Redis JedisPool 的 returnResource 方法遭废弃,改用 close 替代。
public static void close(Jedis jedis){
if(jedis!=null)
jedis.close();
}

/** 返还redis连接到连接池(Jedis 小于 3.0版本使用) **/
// public static void returnResource(Jedis jedis) {
// if (jedis != null)
// jedisPool.returnResource(jedis);
// }
//


/** 方式2.在多线程环境同步初始化,保证不要创建过多的jedispool和 jedis **/
// private static synchronized void poolInit() {
// if (jedisPool == null) {
// initialPool();
// }
// }

/** 同步获取Jedis实例
* @return Jedis */
//public synchronized static Jedis getJedis() {
// if (jedisPool == null) {
// poolInit();
// }
// Jedis jedis = null;
// try {
// if (jedisPool != null) {
// // 从池中获取一个Jedis对象
// jedis = jedisPool.getResource();
// }
// } catch (Exception e) {
// logger.error("Get jedis error : "+e);
// }finally{
// returnResource(jedis);
// }
// return jedis;
//}
//
}


Jedis之Dao类

描述:此处是在于数据库交付操作层进行实现的工具类,只是一部分实现功能,其他功能等遇到的时候在进行补充添加;

/Web/src/top/weiyigeek/connredis/RedisDemo3.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package top.weiyigeek.connredis;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import redis.clients.jedis.Jedis;
public class RedisDemo3 {
/* redis过期时间,以秒为单位 */
public final static int EXRP_HOUR = 60 * 60 ;
public final static int EXRP_DAY = 60 * 60 * 24;
public final static int EXRP_MONTH = 60 * 60 * 24 * 30;

//常用的key类型,用于辅助不区分key类型获取key的value值(非常值得学习)
public enum GetValue {
LIST(){
@Override
public String getValue(Jedis jedis,String key) {
List<String> list = jedis.lrange(key, 0, jedis.llen(key));
return "key="+key+"|type=list|value="+toString(list);
}
},
HASH(){
@Override
public String getValue(Jedis jedis, String key) {
Set<String> keys = jedis.hkeys(key);
return "key="+key+"|type=hash|value="+toString(keys);
}
},
SET(){
@Override
public String getValue(Jedis jedis, String key) {
Set<String> smembers = jedis.smembers(key);
return "key="+key+"|type=set|value="+toString(smembers);
}
},
STRING(){
@Override
public String getValue(Jedis jedis, String key) {
return "key="+key+"|type=string|value="+jedis.get(key);
}

},
ZSET(){
@Override
public String getValue(Jedis jedis, String key) {
Set<String> keys = jedis.zrange(key, 0, jedis.zcard(key));
return "key="+key+"|type=zset|value="+toString(keys);
}
};

// TODO Auto-generated method stub
public abstract String getValue(Jedis jedis,String key);
String toString(Collection<String> collection){
StringBuilder sb = new StringBuilder();
Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()){
sb.append(iterator.next()).append(",");
}
return sb.substring(0,sb.length()-1);
}
}


/**
* 判定指定的key是否存在
* @param jedis
* @param key
* @return
*/
public static boolean exits(Jedis jedis, String key) {
return jedis.exists(key);
}

/**
* 获取key的类型
* @param jedis
* @param key
* @return String
*/
public static String type(Jedis jedis, String key) {
return jedis.type(key);
}

/**
* 设置key的过期时间
* @param key
* @param seconds
*/
public static void expire(String key, final int seconds) {

}

/**
* 删除指定的key(字符串/列表/哈希/集合/有序集合)
* @param jedis
* @param key
* @return
*/
public static boolean del(Jedis jedis, String key) {
try {
if (jedis.exists(key)) {
jedis.del(key);
return true;
}else {
return false;
}
}catch (Exception e) {
return false;
}
}

/**
* 设置与获取String类型的Key
* @param jedis
* @param key
* @param value
* @param seconds
* @throws Exception
*/
public static void setKeyString(Jedis jedis, String key, String value, int seconds) throws Exception {
try {
//判断字符串最安全的方法
value = (value == null ||value.isEmpty()) ? "":value;
jedis.setex(key, seconds, value);
} catch (Exception e) {
throw new Exception("Setting Key Error....");
}
}

public static String getKeyString(Jedis jedis, String key) {
if (jedis == null || !jedis.exists(key)) {
return null;
}
return jedis.get(key);
}
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package top.weiyigeek.connredis;
import redis.clients.jedis.Jedis;
import top.weiyigeek.utils.RedisPoolUtil;
public class RedisDemo2 {
public static void main(String[] args) {
//1.测试工具类
Jedis redis1 = RedisPoolUtil.getJedis();
System.out.println(redis1.ping());

Jedis redis2 = RedisPoolUtil.getJedis();
System.out.println(redis2.ping());

//2.性能测试
Jedis redis = null;
int loop = 1;
while (loop < 3) {
try {
long start = System.currentTimeMillis();
redis = RedisPoolUtil.getJedis();
redis.set("k"+loop, "WeiyiGeek"+loop);
String ret = redis.get("k"+loop);
long end = System.currentTimeMillis();
System.out.printf("Get ret from redis: %s with %d millis\n", ret, end-start);
} finally {
if (redis != null) {
redis.close();
//RedisPoolUtil.close(redis); //已经被close方法替代
}
}
loop++;
}


//3.Redis通用方法测试(值得学习采用枚举的形式+抽象方法实现)
System.out.println(RedisDemo3.GetValue.STRING.getValue(redis, "Name"));
System.out.println(RedisDemo3.GetValue.LIST.getValue(redis, "site-list"));
System.out.println(RedisDemo3.GetValue.HASH.getValue(redis, "hashkey1"));
System.out.println(RedisDemo3.GetValue.SET.getValue(redis, "setkey1"));
System.out.println(RedisDemo3.GetValue.ZSET.getValue(redis, "Zsetkey")+"\n");


//4.redis工具类测试
//String类型(其他类型遇到的时候在写)
long start = System.currentTimeMillis();
try {
RedisDemo3.setKeyString(redis, "Name", "WeiyiGeek", 360);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String value = RedisDemo3.getKeyString(redis, "Name");
long end = System.currentTimeMillis();
System.out.printf("Get ret from redis: %s with %d millis\n", value, end-start);

RedisDemo3.expire("Name", 3600);
System.out.println("key值是否存在 = " + RedisDemo3.exits(redis, "hashkey1"));
System.out.println("key值类型 = "+RedisDemo3.type(redis, "hashkey1"));
System.out.println("删除指定Key值状态 = " + RedisDemo3.del(redis, "hashkey1"));
}
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
当前Redis连接池被使用的数量: 1
PONG
当前Redis连接池被使用的数量: 2
PONG
当前Redis连接池被使用的数量: 3
Get ret from redis: WeiyiGeek1 with 2 millis
当前Redis连接池被使用的数量: 3
Get ret from redis: WeiyiGeek2 with 11 millis
key=Name|type=string|value=WeiyiGeek
key=site-list|type=list|value=Taobao,Google,WeiyiGeek
key=hashkey1|type=hash|value=Love,Name
key=setkey1|type=set|value=Python,Java
key=Zsetkey|type=zset|value=java,redis,WeiyiGeek,eclipse,Weiyi,极客

Get ret from redis: WeiyiGeek with 6 millis
key值是否存在 = true
key值类型 = hash
删除指定Key值状态 = true


Jedis订阅与发布实现

描述:Redis通过publish和subscribe命令实现订阅和发布的功能。

  • 订阅者可以通过subscribe向redis server订阅自己感兴趣的消息类型, redis将信息类型称为通道(channel)
  • 当发布者通过publish命令向redis server发送特定类型的信息时,订阅该消息类型的全部订阅者都会收到此消息。

基础示例:
Redis驱动包提供了一个抽象类JedisPubSub继承这个类就完成了对客户端对订阅的监听

/Web/src/top/weiyigeek/utils/RedisPubSubListener.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package top.weiyigeek.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPubSub;

public class RedisPubSubListener extends JedisPubSub {
//日志打印:Slf4j-simple
private static final Logger logger = LoggerFactory.getLogger(RedisPubSubListener.class);

@Override
public void unsubscribe() {
super.unsubscribe();
}

@Override
public void unsubscribe(String... channels) {
super.unsubscribe(channels);
}


@Override
public void subscribe(String... channels) {
super.subscribe(channels);
}

@Override
public void psubscribe(String... patterns) {
super.psubscribe(patterns);
}

@Override
public void punsubscribe() {
super.punsubscribe();
}

@Override
public void punsubscribe(String... patterns) {
super.punsubscribe(patterns);
}

//监听到订阅频道接受到消息时的回调 (onMessage )
@Override
public void onMessage(String channel, String message) {
logger.info("onMessage: channel[{}], message[{}]",channel, message);
}

//监听到订阅模式接受到消息时的回调 (onPMessage)
@Override
public void onPMessage(String pattern, String channel, String message) {
logger.info("onPMessage: pattern[{}], channel[{}], message[{}]", pattern, channel, message);
}


//订阅频道时的回调( onSubscribe )
@Override
public void onSubscribe(String channel, int subscribedChannels) {
logger.info("onSubscribe: channel[{}], subscribedChannels[{}]", channel, subscribedChannels);
}

//取消订阅频道时的回调( onUnsubscribe )
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
logger.info("channel:{} is been subscribed:{}", channel, subscribedChannels);
}


//订阅频道模式时的回调 ( onPSubscribe )
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
logger.info("onPSubscribe: pattern[{}], subscribedChannels[{}]", pattern, subscribedChannels);
}

//取消订阅模式时的回调 ( onPUnSubscribe ) @Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
logger.info("onPUnsubscribe: pattern[{}], subscribedChannels[{}]", pattern, subscribedChannels);
}
}

基础示例:订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package top.weiyigeek.connredis;

import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import top.weiyigeek.utils.RedisPoolUtil;
import top.weiyigeek.utils.RedisPubSubListener;

//subscribe订阅者
public class RedisSub {
@Test
public void sub() {
System.out.println("--------订阅者------------------- ");
Jedis jedis = RedisPoolUtil.getJedis();
try {
RedisPubSubListener rpsl = new RedisPubSubListener();
//jedis.subscribe(rpsl, "news.blog","news.share"); //方式1.订阅频道
jedis.psubscribe(rpsl,"news.*"); //方式2.订阅频道模式采用通配符模式订阅多个频道
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null)
jedis.close();
}
}
}

基础示例:发布者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package top.weiyigeek.connredis;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import top.weiyigeek.utils.RedisPoolUtil;
//publish 发布者
public class RedisPub {
@Test
public void Pub() {
System.out.println("-------发布者------------- ");
Jedis jedis = RedisPoolUtil.getJedis();
try {
jedis.publish("news.share", "WeiyiGeek-个人分享");
Thread.sleep(2000);
jedis.publish("news.blog", "WeiyiGeek-个人博客");
Thread.sleep(1000);
jedis.publish("new", "最新信息");
} catch (Exception e) {
e.printStackTrace();
} finally {
// TODO: handle finally clause
if (jedis != null)
jedis.close();
}
}
}

执行结果:
此时当在有客户端向new.share或者new.blog通道publish消息时jedis.publish(channel, message),onMessage方法即可被触发。

1
2
3
4
5
6
7
8
9
#--------订阅者------------------- 
#当前Redis连接池被使用的数量: 0
[main] INFO top.weiyigeek.utils.RedisPubSubListener - onPSubscribe: pattern[news.*], subscribedChannels[1]
[main] INFO top.weiyigeek.utils.RedisPubSubListener - onPMessage: pattern[news.*], channel[news.blog], message[www.weiyigeek.top]
[main] INFO top.weiyigeek.utils.RedisPubSubListener - onPMessage: pattern[news.*], channel[news.share], message[WeiyiGeek-个人分享]
[main] INFO top.weiyigeek.utils.RedisPubSubListener - onPMessage: pattern[news.*], channel[news.blog], message[WeiyiGeek-个人博客]

#-------发布者-------------
当前Redis连接池被使用的数量: 0

借鉴参考:



0x04 入坑解决

0) 针对Redis实例JedisPool提示JedisPoolConfig报错问题
答:在进行测试Redis连接池的使用时候,jedisPoolConfig无补充方法且报启动错误信息org.apache.commons.pool2;
解决办法:

  • 1.Apache中下载Commons.pool2.jar包然后导入添加到工程里面;

1) 回收函数returnResource弃用说明
答:其中有个函数returnResource已经deprecated了,现在Jedis的close方法重写了,用Jedis.close来释放资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @deprecated starting from Jedis 3.0 this method will not be exposed.
* Resource cleanup should be done using @see {@link redis.clients.jedis.Jedis#close()}
*/
@Override
@Deprecated
public void returnResource(final Jedis resource) {
if (resource != null) {
try {
resource.resetState();
returnResourceObject(resource);
} catch (Exception e) {
returnBrokenResource(resource);
throw new JedisException("Could not return the resource to the pool", e);
}
}
}

//自Jedis3.0版本后jedisPool.returnResource()遭弃用,官方重写了Jedis的close方法用以代替进行资源回收,官方代码如下:
@Override
public void close() {
if (dataSource != null) {
if (client.isBroken()) {
this.dataSource.returnBrokenResource(this);
} else {
this.dataSource.returnResource(this);
}
} else {
client.close();
}
}

2) 在订阅与发布进行日志输出的时候slf4j的jar包导入错误导致不支持logger.info方法
描述:slf4j-simple-1.7.9下载地址然后导包到工程之中;

1
2
3
4
5
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(RedisPubSubListener.class);
logger.info("onMessage: channel[{}], message[{}]",channel, message); //采用占位符


0x05 补充案例

SpringCloud 框架Until包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

@Value("${spring.redis.database}")
private int index;

@Value("${spring.redis.host}")
private String host;

@Value("${spring.redis.port}")
private int port;

@Value("${spring.redis.timeout}")
private int timeout;

@Value("${spring.redis.pool.max-idle}")
private int maxIdle;

@Value("${spring.redis.pool.max-wait}")
private long maxWaitMillis;

@Value("${spring.redis.password}")
private String password;

@Bean
public JedisPool redisPoolFactory() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password,index);
return jedisPool;
}
}

基础示例:
redis配置

1
2
3
4
5
6
7
#redis实例
redis.info=192.168.127.128:6379,192.168.127.128:6380
#redis最大连接数
redis.pool.maxTotal=50
redis.pool.maxIdle=20
redis.pool.minIdle=10
redis.pool.maxWaitMillis=1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@Data
@PropertySource("config/properties")
public class RedisConfig {

@Value("${redis.info}")
private String redisInfo;

@Value("${redis.pool.maxTotal}")
private int maxTotal;

@Value("${redis.pool.maxIdle}")
private int maxIdle;

@Value("${redis.pool.minIdle}")
private int minIdle;

@Value("${redis.pool.maxWaitMillis}")
private int maxWaitMillis;
}

shardjedis采用一致hash算法实现key的分片,通过计算key的hash值将key分布到不同的redis服务器上,从而达到横向扩展的目的。
以下介绍shardjedis的常用操作。

配置shardjedispool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Configuration
@ComponentScan(basePackages = "config")
public class ShardJedisPool {
@Autowired
RedisConfig redisConfig;

/**
*获取jedisinfo信息
* @return
*/
private List<JedisShardInfo> jedisShardInfos(){
String[] redisInfos = redisConfig.getRedisInfo().split(",");
List<JedisShardInfo> jedisShardInfos = new ArrayList<>();
for(String redisInfo :redisInfos){
String[] part = redisInfo.split(":");
jedisShardInfos.add(new JedisShardInfo(part[0],Integer.parseInt(part[1])));
}
return jedisShardInfos;
}

/**
* 设置redis的参数配置
* @return
*/
@Bean
public GenericObjectPoolConfig genericObjectPoolConfig(){
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxIdle(redisConfig.getMaxIdle());
genericObjectPoolConfig.setMaxTotal(redisConfig.getMaxTotal());
genericObjectPoolConfig.setMinIdle(redisConfig.getMinIdle());
genericObjectPoolConfig.setMaxWaitMillis(redisConfig.getMaxWaitMillis());
return genericObjectPoolConfig;
}

/**
* 获取shardjedis连接池
* @param genericObjectPoolConfig
* @return
*/
@Bean
public ShardedJedisPool shardedJedisPool(GenericObjectPoolConfig genericObjectPoolConfig){
return new ShardedJedisPool(genericObjectPoolConfig,jedisShardInfos());
}

常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@Component
public class JedisAction {

@Autowired
private ShardedJedisPool shardedJedisPool;

/**
* 获取string类型的value
* @param key
* @return
*/
public String get(String key){
try(ShardedJedis shardedJedis = shardedJedisPool.getResource()) {
return shardedJedis.get(key);
}
}

/**
* 设置string类型的值
* @param key
* @param value
*/
public void set(String key,String value){
try (ShardedJedis shardedJedis = shardedJedisPool.getResource()){
shardedJedis.set(key,value);
}
}

/**
* 删除key
* @param key
*/
public void del(String key){
try(ShardedJedis shardedJedis = shardedJedisPool.getResource()){
shardedJedis.del(key);
}
}

/**
* 判断key是否存在
* @param key
* @return
*/
public boolean exits(String key){
try(ShardedJedis shardedJedis = shardedJedisPool.getResource()){
return shardedJedis.exists(key);
}
}

/**
* 获取key的类型
* @param key
* @return
*/
public String type(String key){
try(ShardedJedis shardedJedis = shardedJedisPool.getResource()){
return shardedJedis.type(key);
}
}

/**
* 设置key的过期时间
* @param key
* @param seconds
*/
public void expire(String key,final int seconds){
try(ShardedJedis shardedJedis = shardedJedisPool.getResource()){
shardedJedis.expire(key,seconds);
}
}

/**
* 根据key的模糊类型获取所有的key
* @param pattern
* @return
*/
public List<String> keys(String pattern){
try(ShardedJedis shardedJedis = shardedJedisPool.getResource()){
Collection<Jedis> allShards = shardedJedis.getAllShards();
List<String> keyList = new ArrayList<>();
for(Jedis jedis:allShards){
Set<String> keys = jedis.keys(pattern);
keyList.addAll(keys);
}
return keyList;
}
}

/**
* 获取所有匹配模糊key的value值
* @param pattern
* @return
*/
public List<String> getValues(String pattern){
try(ShardedJedis shardedJedis = shardedJedisPool.getResource()){
Collection<Jedis> allShards = shardedJedis.getAllShards();
List<String> values = new ArrayList<>();
for(Jedis jedis:allShards){
Set<String> keys = jedis.keys(pattern);
for(String key:keys){
values.add(getValue(jedis,key));
}
}
return values;
}
}

/**
* 不区分key的类型获取key的value,可以匹配string,list,hash,set四种类型
* @param jedis
* @param key
* @return
*/
public String getValue(Jedis jedis,String key){
String type = jedis.type(key);
GetValue value = GetValue.valueOf(type.toUpperCase());
return value.getValue(jedis,key);
}
}


jedisCluster连接redis(集群)

描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testJedisCluster()throws Exception{
//创建jedisCluster对象,有一个参数 nodes是Set类型,Set包含若干个HostAndPort对象
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.241.133",7001));
nodes.add(new HostAndPort("192.168.241.133",7002));
nodes.add(new HostAndPort("192.168.241.133",7003));
nodes.add(new HostAndPort("192.168.241.133",7004));
nodes.add(new HostAndPort("192.168.241.133",7005));
nodes.add(new HostAndPort("192.168.241.133",7006));
JedisCluster jedisCluster = new JedisCluster(nodes);
//使用jedisCluster操作redis
jedisCluster.set("test", "my forst jedis");
String str = jedisCluster.get("test");
System.out.println(str);
//关闭连接池
jedisCluster.close();
}

JedisCluster设置密码
1. 修改配置文件,通过requirePass指定密码
2. 通过JedisCluster构造方法指定密码
new JedisCluster(node, connectionTimeout, soTimeout, maxAttempts, password, poolConfig)
new JedisCluster(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, poolConfig)