redis入门到进阶



redis基本认识

1.Redis为什么是单线程

在Redis4.0之前,Redis是 单线程运行的。redis 单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块仍用了多个线程。对于Redis来说,主要的性能瓶颈是内存或者网络带宽,而并非CPU。

2.Redis为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

redis key值操作

1、列出所有的key

1
redis> keys *

2、列出匹配的key

1
2
3
redis>keys apple*
1) apple1
2) apple2

3、查找key

1
get key

4、删除key

1
del key

5、添加值

1
set [key] [value]
  1. 清屏
1
2
// 功能:清除屏幕中的信息
clear
  1. 退出客户端命令行模式
1
2
3
quit
exit
<ESC>
  1. 多数据操作
1
2
3
4
// 修改多个数据 --> Multiple
mset key1 value1 key2 value2
// 获取多个数据
mget key1 key2
  1. string 类型数据的基本操作
  • 获取字符长度(string类型)
1
strlen key
  • 追加信息到原始信息后部(如果原始信息存在就追加,否则新建)
1
append key value
  • 数据库中的热点数据key命名惯例
1
表名:主键名:主键值:字段名

redis 清空缓存命令

flushall ——> 清空整个 Redis 服务器的数据(删除所有数据库的所有 key )

flushdb ——> 清空当前数据库中的所有 key

Linux下redis启动命令

1、redis目录下启动

1
/usr/local/bin/redis-server  /home/data/redis-3.2.1/redis.conf

2、倘若不知道redis-server文件位置输入如下命令查询位置

1
find / -name redis-server

3、查看是否启动成功

1
netstat -nplt

注意:默认端口号是6379

参考-redis文档

redis分布式锁

先定义两个所需的redis操作类和常量

1
2
3
4
5
6
7
8
9
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedissonClient redissonClient;

// 锁
private final String LOCK_KEY = "test-redis-lock";
// redis的key
private final String REDIS_LOCK = "redisLock";

基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author coderblue
*/
@Configuration
public class RedissonConfig {

@Bean
public RedissonClient redissonClient() {
// Cluster集群
// Config config = new Config();
// config.useClusterServers()
// .setScanInterval(2000)
// .addNodeAddress("redis://127.0.0.1:6379");
//
// RedissonClient redisson = Redisson.create(config);
// return redisson;

// 单机模式,不然就会启动报错:非集群
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return Redisson.create(config);
}
}

引入POM依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 引入操作redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<!-- 引入redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.6</version>
</dependency>

使用synchronized实现(仅限单应用部署下)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 锁住此class实例
synchronized (this) {
// 将redis存的key当成锁
Integer count = Integer.parseInt(stringRedisTemplate.opsForValue().get(this.REDIS_LOCK));
if (count > 0) {
int realCount = count - 1;
// jedis.set(key, value)
stringRedisTemplate.opsForValue().set(this.REDIS_LOCK, realCount + "");
System.out.println("扣减成功,剩余库存:" + realCount);
} else {
System.out.println("扣减失败,库存不够");
}
}

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
// 没有不存在此key就进行操作返回true,否则不操作,返回false。jedis.setnx(key, value)
// 设置10s过期时间
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(this.REDIS_LOCK, value, 10, TimeUnit.MINUTES);
if (!result) {
return "error";
}

// 版本号
String clientId = UUID.randomUUID().toString().replace("-", "");
try {
Integer count = Integer.parseInt(stringRedisTemplate.opsForValue().get(this.REDIS_LOCK));
if (count > 0) {
int realCount = count - 1;
stringRedisTemplate.opsForValue().set(this.REDIS_LOCK, realCount + "");
System.out.println("扣减成功,剩余库存:" + realCount);
} else {
System.out.println("扣减失败,库存不够");
}
} finally {
if (Objects.equals(clientId, stringRedisTemplate.opsForValue().get("redisLock"))) {
// 符合对应的版本号才可以释放锁,不然很容易因为设置的过期时间比较短,
// 还没进行到这锁就被释放了。然后第二个已经进来获取锁后才调用这个方法。这样无线循环下去,
// 导致永远都是前一个的把后面进来加的锁释放掉了,就永久失效!
// =========================
// 当主节点挂掉时,从节点会取而代之,但客户端无明显感知。当客户端 A 成功加锁,指令还未同步,
// 此时主节点挂掉,从节点提升为主节点,新的主节点没有锁的数据,当客户端 B 加锁时就会成功。
stringRedisTemplate.delete(this.LOCK_KEY);
}
}

这个基本可以满足我们需求,但是希望在设置过期时间的时候,我们可以判断下如果快要过期了,但是还没结束,那么我们可以延长时间,比如延长1/3的时间。

使用redisson实现分布式锁

1.redisson基本介绍

2.主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
RLock redissonLock = redissonClient.getLock(this.LOCK_KEY);

try {
// 给redis加把锁,过期时间30 S
redissonLock.lock(30, TimeUnit.SECONDS);
Integer count = Integer.parseInt(stringRedisTemplate.opsForValue().get(this.REDIS_LOCK));
if (count > 0) {
int realCount = count - 1;
stringRedisTemplate.opsForValue().set(this.REDIS_LOCK, realCount + "");
System.out.println("扣减成功,剩余库存:" + realCount);
} else {
System.out.println("扣减失败,库存不够");
}
} finally {
redissonLock.unlock();
}

redis主从分布

主从模式

1.基本原理

主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(slave)。主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。

2.工作机制

  • slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过bgsave保存快照(即上文所介绍的RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令
  • master将保存的快照文件发送给slave,并继续记录执行的写命令
  • slave接收到快照文件后,加载快照文件,载入数据
  • master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
  • 此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性

3.主从复制的优缺点

优点:

  • master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
  • master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求

缺点:

  • 不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复
  • master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题

哨兵模式

1.基本原理

主从模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器。然后在哨兵模式下,master宕机,哨兵会 自动选举master并将其他的slave指向新的master。

在主从模式下,redis同时提供了哨兵命令 redis-sentinel,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵进程向所有的redis机器发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

哨兵可以有多个,一般为了便于决策选举,使用奇数个哨兵。哨兵可以和redis机器部署在一起,也可以部署在其他的机器上。多个哨兵构成一个哨兵集群,哨兵直接也会相互通信,检查哨兵是否正常运行,同时发现master宕机哨兵之间会进行决策选举新的master

2.作用

哨兵模式的作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器;

  • 当哨兵监测到master宕机,会自动将slave切换到master,然后通过发布订阅模式通过其他的从服务器,修改配置文件,让它们切换主机;

  • 然而一个哨兵进程对Redis服务器进行监控,也可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

假设master宕机,sentinel 1先检测到这个结果,系统并不会马上进行 failover(故障转移)选出新的master,仅仅是sentinel 1主观的认为master不可用,这个现象成为 主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由sentinel 1发起,进行 failover 操作。切换成功后,就会通过 发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为 客观下线。这样对于客户端而言,一切都是透明的。

优缺点

优点

  • 哨兵模式是 基于主从模式的,所有主从的优点,哨兵模式都具有。
  • 主从可以 自动切换,系统更健壮,可用性更高。

缺点

  • 具有主从模式的缺点,每台机器上的数据是一样的,内存的可用性较低。
  • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

Cluster集群模式

1.基本介绍

Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,对数据进行分片,也就是说每台 Redis 节点上存储不同的内容;

  • 自动将数据进行分片,每个 master 上放一部分数据
  • 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的

在 redis cluster 架构下,每个 redis 要放开 两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node,这有点儿像浏览器页面的302 redirect跳转。

3.总结

数据量比较大,QPS要求较高的时候使用。 Redis Cluster是Redis 3.0以后才正式推出,时间较晚,目前能证明在大规模生产环境下成功的案例还不是很多,需要时间检验。

redis发布订阅模式

1.基本介绍

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。一个redis客户端可以订阅任意多个频道channel,一个频道也可以被多个客户端订阅。

2.代码实现

引入POM依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

定义通道名

1
2
3
4
5
6
7
8
9
10
11
/**
* @author coderblue
*/
public interface Const {

/**
* 通道名称
*/
String CHANNEL = "rides_channel";

}

发布者

定义向通道发送消息方法

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
/**
* @author coderblue
*/
public interface PublisherService {
/**
* 发布消息
* @param params
* @return
*/
String pushMsg(String params);
}

------------------------------------------

@Slf4j
@Service
public class PublisherServiceImpl implements PublisherService {
@Autowired
private RedisService redisService;

@Override
public String pushMsg(String params) {
log.info(" 又开始发布消息 .......... ");
// 直接使用convertAndSend方法即可向指定的通道发布消息
new Thread(() -> {
for (int i = 0; i < 5; i++) {
redisService.sendChannelMess(Const.CHANNEL, System.currentTimeMillis() + ":" + "redis消息队列-线程一");
}
}, "线程一测试").start();

new Thread(() -> {
for (int i = 0; i < 5; i++) {
redisService.sendChannelMess(Const.CHANNEL, System.currentTimeMillis() + ":" + "redis消息队列-线程二");
}
}, "线程二测试").start();
return "success";
}
}

实际通道发送消息方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class RedisService {

@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
* 向通道发送消息的方法
* @param channel
* @param message
*/
public void sendChannelMess(String channel, String message) {
stringRedisTemplate.convertAndSend(channel, message);
}
}

订阅者

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
/***
* redis 监听配置
* @author coderblue
*/
@Configuration
public class RedisSubListenerConfig {

/**
* 初始化监听器
*
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// new PatternTopic("这里是监听的通道的名字") 通道要和发布者发布消息的通道一致
container.addMessageListener(listenerAdapter, new PatternTopic(Const.CHANNEL));
return container;
}

/**
* 绑定消息监听者和接收监听的方法
*
* @param redisReceiver
* @return
*/
@Bean
MessageListenerAdapter listenerAdapter(RedisReceiver redisReceiver) {
// redisReceiver 消息接收者
// receiveMessage 消息接收后的方法
return new MessageListenerAdapter(redisReceiver, "receiveMessage");
}


@Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}

/**
* 注册订阅者
*
* @param latch
* @return
*/
@Bean
RedisReceiver receiver(CountDownLatch latch) {
return new RedisReceiver(latch);
}

/**
* 计数器,用来控制线程
*
* @return
*/
@Bean
CountDownLatch latch() {
// 指定了计数的次数 1
return new CountDownLatch(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
/***
* 消息接收者(订阅者) 需要注入到springboot中
* @author coderblue
*/
@Slf4j
@Component
public class RedisReceiver {

private CountDownLatch latch;

@Autowired
public RedisReceiver(CountDownLatch latch) {
this.latch = latch;
}

/**
* 收到通道的消息之后执行的方法
* @param message
*/
public void receiveMessage(String message) {
//这里是收到通道的消息之后执行的方法
log.info("我收到通道里你发的的消息了....." + message);

latch.countDown();
}
}

Springboot使用缓存

使用redis存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Resource
private StringRedisTemplate stringRedisTemplate;
private final String REDIS_LOCK = "redisLock";

// jedis.set(key, value)
stringRedisTemplate.opsForValue().set(this.REDIS_LOCK, realCount + "");

// 没有不存在此key就进行操作返回true,否则不操作,返回false。jedis.setnx(key, value)
stringRedisTemplate.opsForValue().setIfAbsent(this.REDIS_LOCK, value, 10, TimeUnit.MINUTES);

// 根据键名获取值
stringRedisTemplate.opsForValue().get(this.REDIS_LOCK)

// 释放锁
stringRedisTemplate.delete(this.LOCK_KEY);

使用redis + cache缓存

1.添加POM文件依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>

2.yml文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
# 缓存
redis:
database: 0
host: 127.0.0.1
lettuce:
pool:
max-active: 8 #最大连接数据库连接数,设 0 为没有限制
max-idle: 8 #最大等待连接中的数量,设 0 为没有限制
max-wait: -1ms #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
min-idle: 0 #最小等待连接中的数量,设 0 为没有限制
shutdown-timeout: 100ms
password: ''
port: 6379

3.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
@EnableCaching //开启缓存
@Configuration //配置类
public class RedisConfig extends CachingConfigurerSupport {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
StringRedisSerializer redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
// 取值多双引号问题: value序列化用的是jackson2JsonRedisSerializer,改成StringRedisSerializer就行。
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(redisSerializer);
//value hashmap序列化
template.setHashValueSerializer(redisSerializer);
template.setHashValueSerializer(redisSerializer);
template.afterPropertiesSet();
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}

4.常用注解缓存

@Cacheable:对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@GetMapping
@Cacheable(value = "UserInfo", key = "'selectUserInfo'")
@ApiOperation(value = "查询", notes = "mongo查询")
public Result find() {
List<UserInfo> all = mongoTemplate.findAll(UserInfo.class);
log.info("用户信息:" + all);
return Result.success().data("findAll", all);
}

注意 key = "'selectUserInfo'",需要加单引号成字符串,不然就会寻找有无 selectUserInfo 此值,没有就报错!!!
======================================================
存储如下:缓存存放的命名空间UserInfo + "::" + key
127.0.0.1:6379> keys *
1) "UserInfo::selectUserInfo"
127.0.0.1:6379>

@CachePut:每次都会执行,并将结果存入指定的缓存中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@PutMapping("/update")
@ApiOperation("更新对象")
@CachePut(value = "UserInfo", key = "'selectUserInfo'")
public Result update(UserInfo userInfo) {
Query query = new Query(Criteria.where("id").is(userInfo.getId()));
Update update = new Update().set("age", userInfo.getAge()).set("name", userInfo.getName());
//更新查询返回结果集的第一条
UpdateResult result = mongoTemplate.updateFirst(query, update, UserInfo.class);
// 将修改后的返回值作为value
UserInfo info = this.findByName(userInfo.getName());
return Result.success().data("data", info);
// 更新查询返回结果集的所有
// mongoTemplate.updateMulti(query,update,UserInfo.class);
}

@CacheEvict:使用该注解标志的方法,会 清空指定的缓存。一般用在 更新或者删除方法上

1
2
3
4
5
6
7
@ApiOperation("根据id删除对象")
@DeleteMapping("deleteById")
@CacheEvict(value = "UserInfo", key = "#id")
public void deleteById(Integer id) {
Query query = new Query(Criteria.where("id").is(id));
mongoTemplate.remove(query, UserInfo.class);
}

使用ehcache缓存

1.引入POM文件依赖

1
2
3
4
5
6
7
8
9
10
11
<!--开启 cache 缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- ehcache缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>

2.yml文件配置

1
2
3
4
5
6
spring:
# 缓存
cache:
type: ehcache
ehcache:
config: classpath:ehcache.xml

3.ehcache.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
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- <diskStore path="java.io.tmpdir/ehcache" /> -->
<defaultCache maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120"
memoryStoreEvictionPolicy="LRU" />

<defaultCache
name="后续需删除"
eternal="false" <!--意味着该缓存会死亡-->
maxElementsInMemory="900"<!--缓存的最大数目-->
overflowToDisk="false" <!--内存不足时,是否启用磁盘缓存,如果为true则表示启动磁盘来存储,如果为false则表示不启动磁盘-->
diskPersistent="false"
timeToIdleSeconds="0" <!--当缓存的内容闲置多少时间销毁-->
timeToLiveSeconds="60" <!--当缓存存活多少时间销毁(单位是秒,如果我们想设置2分钟的缓存存活时间,那么这个值我们需要设置120)-->
memoryStoreEvictionPolicy="LRU" /> <!--自动销毁策略-->

<!-- 登录记录缓存 锁定10分钟 -->
<cache name="passwordRetryCache" maxEntriesLocalHeap="2000"
eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0"
overflowToDisk="false" statistics="true">
</cache>
<!-- session缓存 -->
<cache name="sessionCache" maxEntriesLocalHeap="10000"
overflowToDisk="false" eternal="false" diskPersistent="false"
timeToLiveSeconds="0" timeToIdleSeconds="0" statistics="true" />
<!-- 认证缓存 -->
<cache name="AuthenticationCache" maxElementsInMemory="10000"
eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="1200"
overflowToDisk="true" />
<!-- 授权缓存 -->
<cache name="AuthorizationCache" maxElementsInMemory="10000"
eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="1200"
overflowToDisk="true" />

<!-- 对应查询的UserInfo的返回结果集 -->
<cache name="UserInfo" maxElementsInMemory="1000"
eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="1200"
overflowToDisk="true" />

</ehcache>

后续缓存同redis操作雷同,注意 value = “UserInfo” 是需要 ehcache.xml 中有对应的 name

1
2
3
4
5
6
7
8
@GetMapping
@Cacheable(value = "UserInfo", key = "'selectUserInfo'")
@ApiOperation(value = "查询", notes = "mongo查询")
public Result find() {
List<UserInfo> all = mongoTemplate.findAll(UserInfo.class);
log.info("用户信息:" + all);
return Result.success().data("findAll", all);
}

问题

RedisTemplate取值多双引号问题

原因是value序列化用的是jackson2JsonRedisSerializer,改成StringRedisSerializer就行。因为它会自动为String类型的键和值添加双引号,这也是Jackson2JsonRedisSerializer特性,

两种序列化方式:

  1. Jackson2JsonRedisSerializer
  2. StringRedisSerializer (推荐)

修改后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(stringRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;

Github仓库传送门

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  1. © 2020 coderblue    湘ICP备20003709号

请我喝杯咖啡吧~

支付宝
微信