Redis面试:击穿、穿透、雪崩、分布式锁、API

Redis面试中常见的问题:击穿、穿透、雪崩、分布式锁、API(jedis、luttce、springboot:low/high level)

Posted by Steven on 2021-04-20
Estimated Reading Time 21 Minutes
Words 4.7k In Total
Viewed Times

一、击穿

Redis当用来做缓存使用时候,通常用来缓存热数据,同时会有数据库比如mysql存储所有数据
由于一些数据过期或者清理了某些数据,导致之后的请求进来之后直接穿过redis,请求数据库,这就是 击穿

由于KEY的过期造成并发访问数据库,就是击穿

那该如何解决?

首先我们的出发点是阻止并发去到数据库,但是redis中又没有该KEY
1、首先所有请求都get key
2、发现没有key的时候都去setnx
3、只有一个setnx成功(相当于获取一把锁),然后去数据库获取最新的数据并做更新
4、setnx失败的请求,都sleep等待,稍后再次get key(再从第一步开始)
image-20210422112506478

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}

会存在什么问题?

1、如果第一个人挂了(即死锁),那么其他的请求一直在进行上面的过程,这个时候我们可以设置锁的过期时间,当地一个请求挂了,那么其他肯定会有一个又能抢到锁

2、没挂,但是锁超时了。这种情况可以使用多线程来解决,即第一个请求加了一把锁,设置时间为1s,这是服务中的一个线程,那我再加一个线程,每隔半秒去看看前面那个线程的结果取回来了没,如果没有,延长一下第一个线程锁的超时时间

image-20210422142826835

这个时候客户端的代码逻辑成本已经很高了,技术讨论到这个时候就可以了,项目中可能很少这么去做,因为到分布式的情况下,这样实现成本还是很高的,而说这些就是为了引入zookeeper,即因为实现分布式协调很麻烦,所有就有了zookeeper

二、穿透

从业务接收到的查询是系统根本不存在的,比如你们公司的产品库都是电子产品,但是客户端搜的是母婴的,缓存中没有,数据库也没有

缓存穿透 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

如何解决

布隆过滤器,具体布隆过滤器在这里说说过

image-20210422145405849

存在的问题

只能新增,不能删除

三、雪崩

雪崩和击穿有点像,

击穿是一个key ,加上高并发,因为过期,穿过去了,不是特别容易发生,但是雪崩设计不好的话,就很容易发生

大量的key同时失效 ,间接造成大量的访问到达数据库

如何解决

均匀分布过期时间

这个需要分两种业务场景讨论:

一种是到某个时间点必须过期,比如0点,另外一种就是时点性无所谓

必须过期:比如12点之前,某贷款的系数是x,过了这个点之后,系数是Y

1、必须过期

强依赖击穿方案

可以前置处理,即在业务层加一个判断,也就是零点延时 (这里的零点是指业务中需要更新的某个时间点)

零点延时:即到某个时间点了,随机sleep几秒,不要让流量过来,更轻盈的解决方案,就是在业务层零点延时一下,然后随机睡个几秒,之后再放过去

这种阻塞其实问题不是很大,后面进来的请求,其实发现前面的数据已经更新完了。有点类似于:

客户端的请求就像一个高速公路,然后走着走着变城镇道路,最后变乡间小路

image-20210422175713360

2、时点性无所谓

均匀分布过期时间、随机过期时间

四、分布式锁

根据上面说的,其实我们能想到做分布式锁的办法

1、setnx

2、加过期时间

3、多线程来延长过期时间

Redisson是可以实现上面说的。另外zookeeper也可以,而且用zookeeper做分布式锁是做好的,最容易的,但是是没有redis快的。具体后面分析

一般涉及到分布式锁的话,其实对效率要求,相比准确的来说是没有那么强的,但是准确度和一致性是最强的

五、API

Redis火的其中一个原因就是他支持大部分语言:

image-20210425115525931

可能存在这种情况,就是写数据的时候是Java,取数据的时候是PHP,这就是Redis的二进制安全,存的是字节数组,这里我们重点看Java语言的,点Java可以看到许多的基于Java的客户端

image-20210426061418706

我们主要看常用的jedislettuce

1、jedis

首先直接看jedis的Github

使用Maven使用

use it as a maven dependency:

1
2
3
4
5
6
7
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>

To use it just:

1
2
3
Jedis jedis = new Jedis("localhost");
jedis.set("foo", "bar");
String value = jedis.get("foo");

但是jedis是一个线程不安全的

Redis底层用的是epoll,接受很多的连接,使用多路复用,所有的连接,谁先到谁进。

如何一个客户端只有一个redis线程,那么使用jedis是很方便的,但是如果一个客户端有多个redis线程(或者并发进来了,就会有多个线程),而上面只new了一个jedis,那么这个jedis给哪一个线程使用呢?也就是说这个jedis是可以被两个线程都访问到的

jedis通过创建socket连接连接到redis服务端,然后一个线程开启了事务,并通过jedis发送命令到Redis,如果不加锁,那么另一个客户端也发送命令,但是这个命令会被前面的事务拦住了,那么请求就脏了

那如何解决这个问题?jedis也可以解决这个问题

我们准备一个jedis链接池(pool),每个线程都会拿到一个jedis的连接

注意:是连接池,不是线程池

2、lettuce

Github地址

Maven使用

找到自己想用的版本

1
2
3
4
5
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>x.y.z</version>
</dependency>
1
2
3
4
5
6
7
8
9
StatefulRedisConnection<String, String> connection = client.connect();
RedisStringAsyncCommands<String, String> async = connection.async(); # 异步方式
RedisFuture<String> set = async.set("key", "value")
RedisFuture<String> get = async.get("key")

async.awaitAll(set, get) == true

set.get() == "OK"
get.get() == "value"

3、Redis在常用框架Spring中的使用

因为我们是常在Spring、Spring Boot中使用Redis,那我们可以看一下这两个的文档

Spring中使用RedisSpring Boot中使用Redis

1、在Spring中Redis的连接

在文档中->Redis Requirements,可以看到这样一句话

Spring Redis requires Redis 2.6 or above and Spring Data Redis integrates with Lettuce and Jedis, two popular open-source Java libraries for Redis.

Spring已经集成了Lettuce and Jedis

Lettuce

Lettuce is a Netty-based open-source connector supported by Spring Data Redis through the org.springframework.data.redis.connection.lettuce package.

Add the following to the pom.xml files dependencies element:

1
2
3
4
5
6
7
8
9
10
11
<dependencies>

<!-- other dependency elements omitted -->

<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.1.1.RELEASE</version>
</dependency>

</dependencies>

创建一个工厂类:

1
2
3
4
5
6
7
8
9
@Configuration
class AppConfig {

@Bean
public LettuceConnectionFactory redisConnectionFactory() {

return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
}
}

RedisStandaloneConfiguration这种是简单的Redis,也没有使用哨兵,也没有使用集群

当然可以使用哨兵模式

1
2
3
4
5
6
7
8
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("127.0.0.1", 26379)
.sentinel("127.0.0.1", 26380);
return new LettuceConnectionFactory(sentinelConfig);
}
Jedis

Jedis is a community-driven connector supported by the Spring Data Redis module through the org.springframework.data.redis.connection.jedis package.

Add the following to the pom.xml files dependencies element:

1
2
3
4
5
6
7
8
9
10
11
<dependencies>

<!-- other dependency elements omitted -->

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.0-RC1</version>
</dependency>

</dependencies>

In its simplest form, the Jedis configuration looks as follow:

1
2
3
4
5
6
7
8
@Configuration
class AppConfig {

@Bean
public JedisConnectionFactory redisConnectionFactory() {
return new JedisConnectionFactory();
}
}

For production use, however, you might want to tweak settings such as the host or password, as shown in the following example:

1
2
3
4
5
6
7
8
9
10
@Configuration
class RedisConfiguration {

@Bean
public JedisConnectionFactory redisConnectionFactory() {

RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("server", 6379);
return new JedisConnectionFactory(config);
}
}

当然也可以配置哨兵模式

1
2
3
4
5
6
7
8
9
10
11
 /**
* Jedis
*/
@Bean
public RedisConnectionFactory jedisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("127.0.0.1", 26379)
.sentinel("127.0.0.1", 26380);
return new JedisConnectionFactory(sentinelConfig);
}

另外还有主写从读的方式

2、在Spring中Redis的使用

可以参考文档这里

根据文档看到这里面提供了两种层次的使用

a、高层次的-RedisTemplate

用户易于使用

For cases where you need a certain template view, declare the view as a dependency and inject the template. The container automatically performs the conversion, eliminating the opsFor[X] calls, as shown in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/>
...

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Example {

// inject the actual template
@Autowired
private RedisTemplate<String, String> template;

// inject the template as ListOperations
@Resource(name="redisTemplate")
private ListOperations<String, String> listOps;

public void addLink(String userId, URL url) {
listOps.leftPush(userId, url.toExternalForm());
}
}
b、低层次的-RedisConnection

RedisConnection提供了接受和返回二进制值(字节数组)的低级别方法,但是灵活度高

3、数据加工-Serializers

文档

二进制安全的redis,主要介绍数据是怎么处理的

4、Redis在常用框架Spring Boot中的使用

Spring Boot中使用Redis

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

private StringRedisTemplate template;

@Autowired
public MyBean(StringRedisTemplate template) {
this.template = template;
}

// ...

}

1、引入

我们使用IDEA创建一个项目,同时引入简单的Redis

image-20210426140313590

这个项目暂时没有引入web模块,所以这里演示的不是基于网页的,而是基于功能开发的

2、使用

a、高阶API

当下面的main方法中的run方法执行的时候,Spring的容器的功能才能实现

1
2
3
4
5
6
7
8
9
10
11
package cn.shifpeng.architecturelearning;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ArchitectureLearningApplication {

public static void main(String[] args) {
SpringApplication.run(ArchitectureLearningApplication.class, args);
}

由于没有外界去调用获取bean,所以需要我们手动取,修改如下:

1
2
3
4
5
6
7
public class ArchitectureLearningApplication {

public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(ArchitectureLearningApplication.class, args);
TestRedis redis = ctx.getBean(TestRedis.class); //下面通过@Component,这里就会找到这个类
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package cn.shifpeng.architecturelearning;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component //@Component注解会把这个类扔到容器中,然后在上面的代码中获取到
public class TestRedis {
//这里写一个注入,这里面redis会有一个start,start会把Redis所有的连接和对象准备好,注入到RedisTemplate中,。然后下面就可以使用它了
@Autowired
RedisTemplate redisTemplate;
}

这个时候就可以使用它

image-20210426144859929

opsForValue及字符串操作

1
2
3
4
5
6
7
8
9
10
@Component
public class TestRedis {
@Autowired
RedisTemplate redisTemplate;

public void testRedis() {
redisTemplate.opsForValue().set("k1", "hello world");
System.out.println(redisTemplate.opsForValue().get("k1"));
}
}

写好了之后,只需要在这里调用这个方法,即可打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.shifpeng.architecturelearning;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class ArchitectureLearningApplication {

public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(ArchitectureLearningApplication.class, args);
TestRedis redis = ctx.getBean(TestRedis.class);
redis.testRedis(); //调用方法
}
}

注意:Spring Boot会把各种配置放在配置文件中,如下

image-20210426145244081

我们先去虚拟机中,起一个redis服务

1
2
[root@hadoop01 ~]# cd /test/
[root@hadoop01 test]# redis-server --port 6379

那我们redis的host是多少呢,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@hadoop01 ~]# ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.16.197.200 netmask 255.255.255.0 broadcast 172.16.197.255
inet6 fe80::f65f:b967:78d4:ecac prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:fb:e3:dc txqueuelen 1000 (Ethernet)
RX packets 6457 bytes 8551186 (8.1 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2883 bytes 250136 (244.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 32 bytes 2592 (2.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 32 bytes 2592 (2.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

[root@hadoop01 ~]#

host是172.16.197.200

所以修改application.properties配置

1
2
spring.redis.host=172.16.197.200
spring.redis.port=6379

另外,Redis默认是有安全策略的,默认是禁止远端访问的,可以在redis客户端中查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@hadoop01 ~]# redis-cli -p 6379
127.0.0.1:6379> CONFIG GET *
1) "rdbchecksum"
2) "yes"
3) "daemonize"
4) "no"
...
163) "protected-mode"
164) "yes"
...
127.0.0.1:6379> CONFIG GET protected-mode # 也可以直接指定
1) "protected-mode"
2) "yes"
127.0.0.1:6379>

我们讲它临时关闭,或者直接去修改配置文件

1
2
3
127.0.0.1:6379> CONFIG SET  protected-mode no # 临时修改
OK
127.0.0.1:6379>

然后运行一下:打印出来了

image-20210426153339962

再去redis看看,发现乱码了,这是因为redis是二进制安全的,只存二进制数组,任何客户端连接的时候

1
2
3
127.0.0.1:6379> KEYS *
1) "\xac\xed\x00\x05t\x00\x02k1"
127.0.0.1:6379>

spring中还有一个高阶的API,StringRedisTemplate,将上面的替换

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class TestRedis {
@Autowired
RedisTemplate redisTemplate;

@Autowired
StringRedisTemplate stringRedisTemplate;

public void testRedis() {
stringRedisTemplate.opsForValue().set("k1", "hello world");
System.out.println(redisTemplate.opsForValue().get("k1"));
}
}

执行之后再看:

1
2
3
4
127.0.0.1:6379> KEYS *
1) "k1"
2) "\xac\xed\x00\x05t\x00\x02k1"
127.0.0.1:6379>

这是因为这里使用高阶API,使用了Java序列化方式的,是需要加一些东西的

b、低阶API

也可以使用地阶APIRedisConnection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class TestRedis {
@Autowired
RedisTemplate redisTemplate;
//
// @Autowired
// StringRedisTemplate stringRedisTemplate;

public void testRedis() {
// stringRedisTemplate.opsForValue().set("k1", "hello world");
// System.out.println(redisTemplate.opsForValue().get("k1"));
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// 所有直接在redis中使用的命令,都可以在这里使用
connection.set("Hello".getBytes(StandardCharsets.UTF_8), "world".getBytes(StandardCharsets.UTF_8));
System.out.println(new String(connection.get("Hello".getBytes(StandardCharsets.UTF_8))));
}
}

image-20210426160132123

c、Redis序列化

Redis类型中,可以分为两类,一类是单值,一类是复值的,复值就是hashhash k->v,而value又可以是k->v

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class TestRedis {
@Autowired
RedisTemplate redisTemplate;
//
@Autowired
StringRedisTemplate stringRedisTemplate;

public void testRedis() {
HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();
hashOperations.put("Steven", "name", "shifupeng");
hashOperations.put("Steven", "age", "22");
System.out.println(hashOperations.entries("Steven"));
}
}
//运行结果
//{name=shifupeng, age=22}
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> KEYS *
1) "Hello"
2) "k1"
3) "Steven"
4) "\xac\xed\x00\x05t\x00\x02k1"
127.0.0.1:6379> HGETALL Steven
1) "name"
2) "shifupeng"
3) "age"
4) "22"
127.0.0.1:6379>

如果我有一个对象,要怎么放到hash类型中呢?

文档的这里哈希映射其实有说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.shifpeng.architecturelearning;

public class Person {
private String name;
private Integer age;

public String getName() {
return name;
}

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

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

image-20210426192659042

实现:

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
package cn.shifpeng.architecturelearning;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.hash.Jackson2HashMapper;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.Period;
import java.util.Map;

@Component
public class TestRedis {
@Autowired
RedisTemplate redisTemplate;
//
@Autowired
StringRedisTemplate stringRedisTemplate;

@Autowired
ObjectMapper objectMapper;//需要引入spring-boot-starter-json的maven包

public void testRedis() {
Person p = new Person();
p.setAge(22);
p.setName("zhangsan");

//因为value种存在整形,导致类型不匹配,所以需要序列化
stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));

Jackson2HashMapper jackson2HashMapper = new Jackson2HashMapper(objectMapper, true);//第二个参数可以控制里面的子对象页做map映射
//jackson2HashMapper有tomap方法
//value需要的是一个Map
stringRedisTemplate.opsForHash().putAll("Steven01", jackson2HashMapper.toHash(p));
Map map = stringRedisTemplate.opsForHash().entries("Steven01");
Person person = objectMapper.convertValue(map, Person.class);

System.out.println(person.getName());
}
}

查看redis:

1
2
3
4
5
6
7
8
127.0.0.1:6379> KEYS *
1) "Steven01"
127.0.0.1:6379> HGETALL Steven01
1) "name"
2) "\"zhangsan\""
3) "age"
4) "22"
127.0.0.1:6379>

但是这样写太麻烦了,每次写都需要写序列化等,如何解决

可以通过使用自定义的Template解决

d、自定义的Template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class MyTemplate {

/**
*
* @param factory
* @return
*/
@Bean
public StringRedisTemplate getTemplate(RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(factory);
stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return stringRedisTemplate;
}
}

这样就可以在使用的时候不用做序列化操作了

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
package cn.shifpeng.architecturelearning;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.hash.Jackson2HashMapper;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.Period;
import java.util.Map;

@Component
public class TestRedis {
@Autowired
RedisTemplate redisTemplate;
//
@Autowired
@Qualifier("getTemplate")
StringRedisTemplate stringRedisTemplate;

@Autowired
ObjectMapper objectMapper;//需要引入spring-boot-starter-json的maven包

public void testRedis() {

Person p = new Person();
p.setAge(22);
p.setName("zhangsan");

//因为value种存在整形,导致类型不匹配,所以需要序列化
//stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
Jackson2HashMapper jackson2HashMapper = new Jackson2HashMapper(objectMapper, true);//第二个参数可以控制里面的子对象页做map映射
//value需要的是一个Map
stringRedisTemplate.opsForHash().putAll("Steven01", jackson2HashMapper.toHash(p));
Map map = stringRedisTemplate.opsForHash().entries("Steven01");
Person person = objectMapper.convertValue(map, Person.class);
System.out.println(person.getName());
}
}

再次查看,一样的结果

1
2
3
4
5
6
7
8
127.0.0.1:6379> KEYS *
1) "Steven01"
127.0.0.1:6379> HGETALL Steven01
1) "name"
2) "\"zhangsan\""
3) "age"
4) "22"
127.0.0.1:6379>

5、 Redis的发布/订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stringRedisTemplate.convertAndSend("channel01", "hello"); //发布

//订阅
RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();
connection.subscribe(new MessageListener() {
@Override
public void onMessage(Message message, byte[] bytes) {
byte[] body = message.getBody();
System.out.println(new String(body));
}
}, "channel01".getBytes());


while (true) { //让进程一直存在着

}

如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !