一:Redis

1. 什么是Redis

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

官网: https://redis.io/

中文教程网: http://www.redis.net.cn/tutorial/3501.html

2. 基本介绍

Redis 简介

  • Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
  • Redis 与其他 key - value 缓存产品有以下三个特点:
  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis 优势

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

Redis与其他key-value存储有什么不同?

  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

3. 安装

3.1 windows环境

下载地址: https://github.com/MicrosoftArchive/redis/releases

3.1.1 修改配置文件

1
2
3
4
5
redis.windows.conf 文件 配置密码 


# requirepass foobared
requirepass shuiyang

3.1.2 常用命令

1
2
3
4
5
6
7
redis服务安装成windows服务: redis-server --service-install redis.windows.conf

开启服务:redis-server --service-start

停止服务:redis-server --service-stop

卸载服务:redis-server --service-uninstall

3.1.3 Redis可视化管理工具

3.2 Linux环境

下载地址:http://www.redis.net.cn/download/,下载最新文档版本。

3.2.1 安装

1
2
3
4
$ wget http://download.redis.io/releases/redis-2.8.17.tar.gz
$ tar xzf redis-2.8.17.tar.gz
$ cd redis-2.8.17
$ make

make完后 redis-2.8.17目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli

3.2.1 启动服务

下面启动redis服务.

1
$ ./redis-server

注意这种方式启动redis 使用的是默认配置。也可以通过启动参数告诉redis使用指定配置文件使用下面命令启动。

1
./redis-server redis.conf

redis.conf是一个默认的配置文件。我们可以根据需要使用自己的配置文件。

3.2.1 测试客户端程序

启动redis服务进程后,就可以使用测试客户端程序redis-cli和redis服务交互了。 比如:

1
2
3
4
5
$ ./redis-cli
redis> set foo bar
OK
redis> get foo
"bar"

4. 与Spring 集成

4.1 导入maven

1
2
3
4
5
6
7
8
9
10
11
<!--redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.2.RELEASE</version>
</dependency>

4.2 新建 redis-config.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Redis settings
redis.host=127.0.0.1
redis.port=6379
redis.pass=shuiyang
redis.dbIndex=0
redis.expiration=3000
#最大空闲数
redis.maxIdle=300
#连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal
redis.maxActive=600
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
redis.maxWait=1000
#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true

4.3 新建 applicationContext-redis.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
57
58
59
60
61
62
63
64
65
66
67
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">


<!-- 启用缓存注解开关 -->
<cache:annotation-driven cache-manager="cacheManager"/>

<!-- 配置JedisPoolConfig实例 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxActive}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>

<!-- 配置JedisConnectionFactory -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.pass}" />
<property name="database" value="${redis.dbIndex}" />
<property name="poolConfig" ref="poolConfig" />
</bean>

<!-- 配置RedisTemplate -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>

<!-- 配置RedisCacheManager -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="redisTemplate" />
<property name="defaultExpiration" value="${redis.expiration}" />
</bean>

<!-- 配置RedisCacheManager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!-- 这里可以配置多个redis -->
<bean class="com.ecut.core.config.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="articlesDetail"/>
</bean>
<bean class="com.ecut.core.config.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="getHotArticlesInCache"/>
</bean>
<bean class="com.ecut.core.config.RedisCache">
<property name="redisTemplate" ref="redisTemplate" />
<property name="name" value="articlesList"/>
</bean>
</set>
</property>
</bean>
</beans>

4.4 引入 applicationContext-redis.xml redis-config.properties

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

<!--使标注Spring注解的类生效-->
<context:component-scan base-package="com.ecut"/>

<!-- 将多个配置文件读取到容器中,交给Spring管理 -->
<bean id="propertyConfigurer" class="com.ecut.core.spring.PropertyPlaceholderConfigurerFilter">
<property name="locations">
<list>
<!-- 这里支持多种寻址方式:classpath和file -->
<value>classpath:project.properties</value>
<!-- 推荐使用file的方式引入,这样可以将配置和代码分离 -->
<value>classpath:jdbc.properties</value>
<value>classpath:redis-config.properties</value>
</list>
</property>
</bean>

<import resource="applicationContext-dao.xml"/>
<import resource="applicationContext-shiro.xml"/>
<!--encache redis选择一种缓存-->
<!--<import resource="applicationContext-encache.xml"/>-->
<import resource="applicationContext-redis.xml"/>
</beans>

4.5 新建 RedisCache.java Cache接口实现类 Spring对于缓存只是提供了抽象的接口,并且通过接口来调用功能,没有具体的实现类,所以需要我们自己实现具体的操作。

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 com.ecut.core.config;

import java.io.Serializable;

import org.apache.commons.lang3.SerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
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 java.io.Serializable;

/**
* Cache接口实现类
*
*  Spring对于缓存只是提供了抽象的接口,并且通过接口来调用功能,没有具体的实现类,所以需要我们自己实现具体的操作。
  在上面配置中可知,每个实现类都会注入一个redisTemplate实例,我们就可以通过redisTemplate来操作redis
* @author songshuiyang
* @date 2018/4/9 20:38
*/
public class RedisCache implements Cache {

private Logger logger = LoggerFactory.getLogger(this.getClass());

private RedisTemplate<String, Object> redisTemplate;

private String name;

@Override
public void clear() {
logger.info("----------------------------RedisCache 緩存清理-------------------------");
redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
connection.flushDb();
return "ok";
}
});
}

@Override
public void evict(Object key) {
logger.info("----------------------------RedisCache 緩存刪除-------------------------");
final String keyf=key.toString();
redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.del(keyf.getBytes());
}

});

}

@Override
public ValueWrapper get(Object key) {
logger.info("----------------------------RedisCache 缓存获取-------------------------");
final String keyf = key.toString();
Object object = null;
object = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[] key = keyf.getBytes();
byte[] value = connection.get(key);
if (value == null) {
logger.info("----------------------------RedisCache 缓存不存在-------------------------");
return null;
}
return SerializationUtils.deserialize(value);
}
});
ValueWrapper obj=(object != null ? new SimpleValueWrapper(object) : null);
logger.info("----------------------------RedisCache 获取到内容-------------------------");
return obj;
}

@Override
public void put(Object key, Object value) {
System.out.println("-------加入缓存------");
System.out.println("key----:"+key);
System.out.println("key----:"+value);
final String keyString = key.toString();
final Object valuef = value;
final long liveTime = 86400;
redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
byte[] keyb = keyString.getBytes();
byte[] valueb = SerializationUtils.serialize((Serializable) valuef);
connection.set(keyb, valueb);
if (liveTime > 0) {
connection.expire(keyb, liveTime);
}
return 1L;
}
});

}

@Override
public <T> T get(Object arg0, Class<T> arg1) {
// TODO Auto-generated method stub
return null;
}

@Override
public String getName() {
return this.name;
}

@Override
public Object getNativeCache() {
return this.redisTemplate;
}

@Override
public ValueWrapper putIfAbsent(Object arg0, Object arg1) {
// TODO Auto-generated method stub
return null;
}

public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}

public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}

public void setName(String name) {
this.name = name;
}
}

4.6 完成以上的配置之后就可以使用 Spring Cache注解来使用缓存了

首先使用注解标记方法,相当于定义了切点,然后使用Aop技术在这个方法的调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。

  • @Cacheable
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
表明所修饰的方法是可以缓存的:当第一次调用这个方法时,它的结果会被缓存下来,在缓存的有效时间内,以后访问这个方法都直接返回缓存结果,不再执行方法中的代码段。 
这个注解可以用condition属性来设置条件,如果不满足条件,就不使用缓存能力,直接执行方法。
可以使用key属性来指定key的生成规则。

@Cacheable 支持如下几个参数:

value:缓存位置名称,不能为空,如果使用EHCache,就是ehcache.xml中声明的cachename, 指明将值缓存到哪个Cache
key:缓存的key,默认为空,既表示使用方法的参数类型及参数值作为key,支持SpEL,如果要引用参数值使用井号加参数名,如:#userId,

一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值 ,
本例子中使用实体加冒号再加ID组合成键的名称,如”user:1”、”order:223123”等

condition:触发条件,只有满足条件的情况才会加入缓存,默认为空,既表示全部都加入缓存,支持SpEL


// 将缓存保存到名称为UserCache中,键为"user:"字符串加上userId值,如 'user:1'
@Cacheable(value="UserCache", key="'user:' + #userId")
public User findById(String userId) {
return (User) new User("1", "mengdee");
}

// 将缓存保存进UserCache中,并当参数userId的长度小于12时才保存进缓存,默认使用参数值及类型作为缓存的key
// 保存缓存需要指定key,value, value的数据类型,不指定key默认和参数名一样如:"1"
@Cacheable(value="UserCache", condition="#userId.length() < 12")
public boolean isReserved(String userId) {
System.out.println("UserCache:"+userId);
return false;
}
  • @CachePut
1
与@Cacheable不同,@CachePut不仅会缓存方法的结果,还会执行方法的代码段。它支持的属性和用法都与@Cacheable一致。
  • @CacheEvict

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    与@Cacheable功能相反,@CacheEvict表明所修饰的方法是用来删除失效或无用的缓存数据。

    @CacheEvict 支持如下几个参数:

    value:缓存位置名称,不能为空,同上
    key:缓存的key,默认为空,同上
    condition:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL
    allEntries:true表示清除value中的全部缓存,默认为false

    //清除掉UserCache中某个指定key的缓存
    @CacheEvict(value="UserCache",key="'user:' + #userId")
    public void removeUser(User user) {
    System.out.println("UserCache"+user.getUserId());
    }

    //清除掉UserCache中全部的缓存
    @CacheEvict(value="UserCache", allEntries=true)
    public final void setReservedUsers(String[] reservedUsers) {
    System.out.println("UserCache deleteall");
    }
  • @Caching

    1
    2
    3
    4
    如果需要使用同一个缓存注解(@Cacheable、@CacheEvict或@CachePut)多次修饰一个方法,就需要用到@Caching。

    @Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
    public Book importBooks(String deposit, Date date)
  • @CacheConfig

    1
    2
    3
    4
    5
    6
    7
    8
    与前面的缓存注解不同,这是一个类级别的注解。
    如果类的所有操作都是缓存操作,你可以使用@CacheConfig来指定类,省去一些配置。

    @CacheConfig("books")
    public class BookRepositoryImpl implements BookRepository {
    @Cacheable
    public Book findBook(ISBN isbn) {...}
    }

遇到的问题:

  • spring+redis报错org.springframework.core.serializer.support.DeserializingConverter.(Ljava/lang/ClassLoader;)V

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    这个问题的原因大概就是spring-data-redis.jar包版本不对 ,下面版本可以正常启动

    <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.6.2.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    </dependency>
  • Spring Cache 注解问题,使用redis缓存会出现类型转化的问题 ,还未解决

参考:

https://www.cnblogs.com/panter/p/6801210.html
http://www.redis.net.cn/tutorial/3503.html
https://www.cnblogs.com/hello-daocaoren/p/7891907.html