Redis 是什么,能做什么
Redis 是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel 提供高可用,通过 Redis Cluster 提供自动分区。(摘自Redis 官网)
作为内存数据库,在现代互联网 web 系统中,还是主要将 Redis 作为缓存使用。大型互联网 Web 系统对性能要求很高,而在前端和数据层之间增加数据缓存已成为必不可少的手段之一,当前比较流行的两个技术就是 Redis 和 Memcached,至于两者有什么区别,不是本文要说的内容。本文主要讲 Java web 如何操作 Redis 及 Redis 集群。
一般 Java 程序操作Redis
Redis 提供了多种语言的客户端,在 Java 中最流行的是 Jedis 。访问可查看源码及使用方式。目前 Jedis 最新版本是2.9.0。无论是单机还是集群,Jedis 都有很详细的说明和实例代码,这里只做简单说明。如果用 Maven 做包管理,需要引用 jedis 包,本例使用最新的2.9.0版本,如下:
1
2
3
4
5
|
<
dependency
>
<
groupId
>redis.clients
groupId
>
<
artifactId
>jedis
artifactId
>
<
version
>2.9.0
version
>
dependency
>
|
操作 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
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
|
import
redis.clients.jedis.Jedis;
import
redis.clients.jedis.JedisPool;
import
redis.clients.jedis.JedisPoolConfig;
/**
* Created by fengdezitai on 2016/10/9.
*/
public
class
JedisClient {
private
static
final
String host=
"192.168.31.121"
;
private
static
final
JedisClient jedisClient =
new
JedisClient();
private
Jedis jedis =
null
;
/**
* 私有构造函数
*/
private
JedisClient(){}
public
static
JedisClient getInstance(){
return
jedisClient;
}
private
JedisPoolConfig getPoolConfig(){
JedisPoolConfig jedisPoolConfig =
new
JedisPoolConfig();
jedisPoolConfig.setMaxIdle(
10
);
jedisPoolConfig.setMaxTotal(
100
);
jedisPoolConfig.setMaxWaitMillis(
3000
);
return
jedisPoolConfig;
}
/**
* 添加
* @param key
* @param value
* @return
* @throws Exception
*/
public
Boolean add(String key,String value)
throws
Exception{
JedisPool pool =
new
JedisPool(getPoolConfig(),host);
Jedis jedis =
null
;
try
{
jedis = pool.getResource();
if
(jedis.exists(key)){
throw
new
Exception(String.format(
"key (%s) 已存在 "
,key));
}
jedis.set(key,value);
}
catch
(Exception e){
throw
e;
}
finally
{
if
(jedis!=
null
){
jedis.close();
}
}
pool.destroy();
return
true
;
}
/**
* 获取值
* @param key
* @return
* @throws Exception
*/
public
String get(String key)
throws
Exception{
JedisPool pool =
new
JedisPool(getPoolConfig(),host);
Jedis jedis =
null
;
String result =
""
;
try
{
jedis = pool.getResource();
result = jedis.get(key);
}
catch
(Exception e){
throw
e;
}
finally
{
if
(jedis!=
null
){
jedis.close();
}
}
pool.destroy();
return
result;
}
public
static
void
main(String[] args) {
JedisClient jedisClient = JedisClient.getInstance();
try
{
/*Boolean result = jedisClient.add("hello", "redis1");
if(result){
System.out.println("success");
}*/
System.out.println(jedisClient.get(
"hello"
));
}
catch
(Exception e){
e.printStackTrace();
}
}
}
|
操作 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
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
|
import
redis.clients.jedis.*;
import
java.util.HashSet;
import
java.util.Set;
/**
* Created by fengdezitai on 2016/10/13.
*/
public
class
JedisClusterClient {
private
static
int
count =
0
;
private
static
final
JedisClusterClient redisClusterClient =
new
JedisClusterClient();
/**
* 私有构造函数
*/
private
JedisClusterClient() {}
public
static
JedisClusterClient getInstance() {
return
redisClusterClient;
}
private
JedisPoolConfig getPoolConfig(){
JedisPoolConfig config =
new
JedisPoolConfig();
config.setMaxTotal(
1000
);
config.setMaxIdle(
100
);
config.setTestOnBorrow(
true
);
return
config;
}
public
void
SaveRedisCluster() {
Set
new
HashSet
jedisClusterNodes.add(
new
HostAndPort(
"192.168.31.245"
,
7000
));
jedisClusterNodes.add(
new
HostAndPort(
"192.168.31.245"
,
7001
));
jedisClusterNodes.add(
new
HostAndPort(
"192.168.31.245"
,
7002
));
jedisClusterNodes.add(
new
HostAndPort(
"192.168.31.210"
,
7003
));
jedisClusterNodes.add(
new
HostAndPort(
"192.168.31.210"
,
7004
));
jedisClusterNodes.add(
new
HostAndPort(
"192.168.31.210"
,
7005
));
JedisCluster jc =
new
JedisCluster(jedisClusterNodes,getPoolConfig());
jc.set(
"cluster"
,
"this is a redis cluster"
);
String result = jc.get(
"cluster"
);
System.out.println(result);
}
public
static
void
main(String[] args) {
JedisClusterClient jedisClusterClient = JedisClusterClient.getInstance();
jedisClusterClient.SaveRedisCluster();
}
}
|
spring mvc 操作 Redis
在 Spring mvc 中操作 Redis ,首先当然要搭好 Spring mvc 框架了。以下是在假设 Spring mvc 环境已经架好的情况下。本例中 Spring 版本为 4.3.2 RELEASE。关于 Spring 的 maven 引用如下:
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
|
/spring
.version>
/groupId
>
/artifactId
>
/version
>
/groupId
>
/artifactId
>
<
/exclusion
>
<
/exclusions
>
<
/dependency
>
/groupId
>
/artifactId
>
/version
>
<
/dependency
>
/groupId
>
/artifactId
>
/version
>
<
/dependency
>
/groupId
>
/artifactId
>
/version
>
<
/dependency
>
/groupId
>
/artifactId
>
/version
>
<
/dependency
>
/groupId
>
/artifactId
>
/version
>
/groupId
>
/artifactId
>
<
/exclusion
>
<
/exclusions
>
<
/dependency
>
/groupId
>
/artifactId
>
/version
>
<
/dependency
>
/groupId
>
/artifactId
>
/version
>
<
/dependency
>
/groupId
>
test
<
/artifactId
>
/version
>
<
/dependency
>
|
操作 Redis 单机
只用 Jedis 自己实现注入(区别于下面的引用spring-data-redis)
把前面的 JedisClient 代码拿过来引用即可,只需实现一个访问 Redis 的 Service ,就可以集成到 Spring mvc 。Service 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import
org.springframework.stereotype.Service;
import
util.JedisClient;
/**
* Created by fengdezitai on 2016/10/9.
*/
@Service
public
class
RedisService {
public
String get(String key)
throws
Exception{
JedisClient jedisClient = JedisClient.getInstance();
//上面实现的JedisClient
String result =
""
;
try
{
result = jedisClient.get(
"hello"
);
}
catch
(Exception e){
throw
e;
}
return
result;
}
}
|
Controller 实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Controller
@RequestMapping
(value =
"redisAllInOne"
)
public
class
RedisAllInOneController {
@Autowired
private
RedisService redisService;
@RequestMapping
(value =
"get"
,method = RequestMethod.GET)
@ResponseBody
public
Object getByMyService(String key){
try
{
String result = redisService.get(key);
return
result;
}
catch
(Exception e){
e.printStackTrace();
}
return
null
;
}
}
|
用 spring-data-redis 包做集成
上面是自己实现的注入,这里用 spring-data-redis 进行集成,只需简单配置即可,需要引用 maven 包如下,版本为目前最新版 1.7.2.RELEASE:
1
2
3
4
5
|
/groupId
>
/artifactId
>
/version
>
<
/dependency
>
|
使用 spring-data-redis ,即省去了自己实现注入的过程,通过它提供的一些配置,即可实现连接池配置、RedisTemplate 配置、JedisConnectionFactory 配置;通过 JedisConnectionFactory 可配置连接池参数、redis 服务器、端口、密码、超时时间、database索引等;RedisTemplate 即注入的bean ,可以使用 RedisTemplate 自动注入的实体进行 redis 的一系列操作,具体看配置;
redis 服务属性配置文件:
1
2
3
4
5
6
7
|
redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=
true
redis.host=192.168.31.121
redis.port=6379
redis.password=password
redis.timeout=3000
|
spring-data-redis xml 配置文件 redis-context.xml:
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
|
<
/bean
>
<
/bean
>
<
/bean
>
<
/bean
>
<
/bean
>
|
之后在 spring 配置文件中引用以上文件:
1
|
<
import
resource=
"redis-context.xml"
/>
|
解释一下上面的配置:
poolConfig 即配置 redis 连接池,之后配置了两个 JedisConnectionFactory 和 RedisTemplate ,一个 RedisTemplate 对应一个 JedisConnectionFactory ,这样可以配置根据场景配置不同的 Redis 连接,比如超时时间要求不一致、database 0-15 可以存储不同的数据等。这里就配置了database 1 和 2 ,调用 commonRedisTemplate 会存到 database1 ,调用 cacheRedisTemplate 会存到 database2。
之后在 Service 层即可注入并引用这两个 RedisTemplate ,如下代码:
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
|
import
org.apache.commons.lang3.StringUtils;
import
org.springframework.dao.DataAccessException;
import
org.springframework.data.redis.connection.RedisConnection;
import
org.springframework.data.redis.core.RedisCallback;
import
org.springframework.data.redis.core.RedisTemplate;
import
org.springframework.stereotype.Repository;
import
javax.annotation.Resource;
import
java.io.*;
@Repository
public
class
RedisCache {
@Resource
(name =
"cacheRedisTemplate"
)
private
RedisTemplate
public
void
put(Object key, Object value) {
if
(
null
== value) {
return
;
}
if
(value
instanceof
String) {
if
(StringUtils.isEmpty(value.toString())) {
return
;
}
}
// TODO Auto-generated method stub
final
String keyf = key +
""
;
final
Object valuef = value;
final
long
liveTime =
86400
;
cacheRedisTemplate.execute(
new
RedisCallback
public
Long doInRedis(RedisConnection connection)
throws
DataAccessException {
byte
[] keyb = keyf.getBytes();
byte
[] valueb = toByteArray(valuef);
connection.set(keyb, valueb);
if
(liveTime >
0
) {
connection.expire(keyb, liveTime);
}
return
1L;
}
});
}
public
Object get(Object key) {
final
String keyf = (String) key;
Object object;
object = cacheRedisTemplate.execute(
new
RedisCallback
public
Object doInRedis(RedisConnection connection)
throws
DataAccessException {
byte
[] key = keyf.getBytes();
byte
[] value = connection.get(key);
if
(value ==
null
) {
return
null
;
}
return
toObject(value);
}
});
return
object;
}
/**
* 描述 :
*
* <使用方法说明>
*
*
* @param bytes
* @return
*/
private
Object toObject(
byte
[] bytes) {
Object obj =
null
;
try
{
ByteArrayInputStream bis =
new
ByteArrayInputStream(bytes);
ObjectInputStream ois =
new
ObjectInputStream(bis);
obj = ois.readObject();
ois.close();
bis.close();
}
catch
(IOException ex) {
ex.printStackTrace();
}
catch
(ClassNotFoundException ex) {
ex.printStackTrace();
}
return
obj;
}
private
byte
[] toByteArray(Object obj) {
byte
[] bytes =
null
;
ByteArrayOutputStream bos =
new
ByteArrayOutputStream();
try
{
ObjectOutputStream oos =
new
ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
oos.close();
bos.close();
}
catch
(IOException ex) {
ex.printStackTrace();
}
return
bytes;
}
}
|
最后在 Controller 中调用即可
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
|
@Autowired
private
RedisCache redisCache;
@RequestMapping
(value =
"get"
, method = RequestMethod.GET)
@ResponseBody
public
Object getByMyService(String key) {
try
{
String result = redisService.get(key);
return
result;
}
catch
(Exception e) {
e.printStackTrace();
}
return
null
;
}
@RequestMapping
(value =
"save"
, method = RequestMethod.GET)
@ResponseBody
public
Object save() {
Token token =
new
Token();
token.setAccess_token(
"token"
);
token.setExpires_in(
1000
);
try
{
redisCache.put(
"token"
, token);
}
catch
(Exception e) {
e.printStackTrace();
}
return
"ok"
;
}
|
操作 Redis 集群
只用 Jedis 自己实现注入(区别于下面的引用spring-data-redis)
把前面的 JedisClusterClient 代码拿过来引用即可,只需实现一个访问 Redis 的 Service ,就可以集成到 Spring mvc 。Service 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import
org.springframework.stereotype.Service;
import
util.JedisClusterClient;
/**
* Created by fengdezitai on 2016/10/13.
*/
@Service
public
class
RedisClusterService {
public
void
save()
throws
Exception{
//调用 JedisClusterClient 中的方法
JedisClusterClient jedisClusterClient = JedisClusterClient.getInstance();
try
{
jedisClusterClient.SaveRedisCluster();
}
catch
(Exception e){
throw
e;
}
}
}
|
最后在 Controller 中调用实现的 Service 即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Controller
@RequestMapping
(value =
"redisCluster"
)
public
class
RedisClusterController {
@Autowired
private
RedisClusterService redisClusterService;
@RequestMapping
(value =
"save"
,method = RequestMethod.GET)
@ResponseBody
public
Object save(){
try
{
redisClusterService.save();
}
catch
(Exception e){
e.printStackTrace();
return
String.format(
"error: %s"
,e.getMessage());
}
return
"ok"
;
}
}
|
用 spring-data-redis 包做集成
Spring 和 spring-data-redis maven 包引用和前面一致,之所以引用 spring-data-redis 1.7.2.RELEASE,是因为目前只有这个最新版本才支持集群操作。
redis 集群服务属性配置
1
2
3
4
|
redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=
false
redis.timeout=3000
|
spring-data-redis xml 集群配置文件 redis-cluster-context.xml
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
|
<
/bean
>
<
set
>
<
/bean
>
<
/bean
>
<
/bean
>
<
/bean
>
<
/bean
>
<
/bean
>
<
/set
>
<
/property
>
<
/bean
>
class=
"org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
>
<
/bean
>
<
/bean
>
|
之后在 Spring 配置文件中引用
1
|
<
import
resource=
"redis-cluster-context.xml"
/>
|
解释以上配置:
poolConfig是连接池配置,redisClusterConfig 配置了 Redis 集群的各个节点(节点 host 和 port 最好写在属性配置文件中),集群搭建可见 我的 另一篇博客 。然后下面和单机配置一样了,一对 JedisConnectionFactory 和 RedisTemplate 。
之后在 Service 层即可注入并引用这个 RedisTemplate,代码如下:
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
|
import
org.apache.commons.lang3.StringUtils;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.dao.DataAccessException;
import
org.springframework.data.redis.connection.RedisConnection;
import
org.springframework.data.redis.core.RedisCallback;
import
org.springframework.data.redis.core.RedisTemplate;
import
org.springframework.stereotype.Repository;
import
java.io.*;
/**
* Created by fengdezitai on 2016/9/29.
*/
@Repository
public
class
RedisClusterCache {
@Autowired
private
RedisTemplate clusterRedisTemplate;
public
void
put(Object key, Object value) {
if
(
null
== value) {
return
;
}
if
(value
instanceof
String) {
if
(StringUtils.isEmpty(value.toString())) {
return
;
}
}
// TODO Auto-generated method stub
final
String keyf = key +
""
;
final
Object valuef = value;
final
long
liveTime =
86400
;
clusterRedisTemplate.execute(
new
RedisCallback
public
Long doInRedis(RedisConnection connection)
throws
DataAccessException {
byte
[] keyb = keyf.getBytes();
byte
[] valueb = toByteArray(valuef);
connection.set(keyb, valueb);
if
(liveTime >
0
) {
connection.expire(keyb, liveTime);
}
return
1L;
}
});
}
public
Object get(Object key) {
final
String keyf = (String) key;
Object object;
object = clusterRedisTemplate.execute(
new
RedisCallback
public
Object doInRedis(RedisConnection connection)
throws
DataAccessException {
byte
[] key = keyf.getBytes();
byte
[] value = connection.get(key);
if
(value ==
null
) {
return
null
;
}
return
toObject(value);
}
});
return
object;
}
/**
* 描述 :
*
* <使用方法说明>
*
*
* @param bytes
* @return
*/
private
Object toObject(
byte
[] bytes) {
Object obj =
null
;
try
{
ByteArrayInputStream bis =
new
ByteArrayInputStream(bytes);
ObjectInputStream ois =
new
ObjectInputStream(bis);
obj = ois.readObject();
ois.close();
bis.close();
}
catch
(IOException ex) {
ex.printStackTrace();
}
catch
(ClassNotFoundException ex) {
ex.printStackTrace();
}
return
obj;
}
private
byte
[] toByteArray(Object obj) {
byte
[] bytes =
null
;
ByteArrayOutputStream bos =
new
ByteArrayOutputStream();
try
{
ObjectOutputStream oos =
new
ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
oos.close();
bos.close();
}
catch
(IOException ex) {
ex.printStackTrace();
}
return
bytes;
}
}
|
最后在 Controller 中调用即可
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
|
@Controller
@RequestMapping
(value =
"redisCluster"
)
public
class
RedisClusterController {
@Autowired
private
RedisClusterCache redisClusterCache;
@RequestMapping
(value =
"clusterSave"
,method = {RequestMethod.GET,RequestMethod.POST})
@ResponseBody
public
Object clusterSave(){
//redisClusterCache.put("cluster","save cluster");
Token token =
new
Token();
token.setExpires_in(
1000
);
token.setAccess_token(
"hello world"
);
redisClusterCache.put(
"token"
,token);
return
"ok"
;
}
@RequestMapping
(value =
"getKey"
,method = RequestMethod.GET)
@ResponseBody
public
Object getCluster(String key){
Object val = redisClusterCache.get(key);
return
val;
}
}
|
注意事项: