1. 导入 Redis 坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置 Redis 数据源
spring:
redis:
host: localhost
port: 6379
password: redis
database: 0
3. 编写配置类,创建RedisTemplate对象
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfiguration {
@Bean
public StringRedisTemplate stringRedisSerializer(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
解释说明:
当前配置类不是必须的,因为 Spring Boot 框架会自动装配 RedisTemplate 对象,但是默认的key序列化器为JdkSerializationRedisSerializer,导致我们存到Redis中后的数据和原始数据有差别,故设置为StringRedisSerializer序列化器。
4. 操作常见类型数据
4.1 操作字符串类型数据
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@SpringBootTest
@Slf4j
public class RedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 字符串操作命令
*/
@Test
public void testValue() {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
/* set 添加 */
ops.set("key12", "value12");
/* get 获取 */
String key1 = ops.get("key1");
// 断言测试
Assertions.assertEquals("value1", key1);
/* set ex 添加设置存在时间 */
ops.set("key2", "value2", Duration.ofMinutes(1));
/* set nx 不存在添加 */
Boolean b1 = ops.setIfAbsent("key3", "value3");
Assertions.assertTrue(b1);
Boolean b2 = ops.setIfAbsent("key3", "value3");
Assertions.assertFalse(b2);
/* set ex nx 不存在添加且设置存在时间 */
Boolean b3 = ops.setIfAbsent("key4", "value4", Duration.ofDays(1));
Assertions.assertTrue(b3);
Boolean b4 = ops.setIfAbsent("key4", "value4", Duration.ofDays(1));
Assertions.assertFalse(b4);
/* ttl 查看存在时间 */
Long ttl = stringRedisTemplate.getExpire("key2");
Assertions.assertTrue(ttl <= Duration.ofMinutes(1).getSeconds());
/* incr 原子性自增1 */
ops.set("key5", "10");
Long key5 = ops.increment("key5");
Assertions.assertEquals(11L, key5);
/* decr 原子性自减1 */
Long key6 = ops.decrement("key5");
Assertions.assertEquals(10L, key6);
/* incrby 原子性自增n */
Long key7 = ops.increment("key5", 10L);
Assertions.assertEquals(20L, key7);
/* decrby 原子性自减n */
Long key8 = ops.decrement("key5", 10L);
Assertions.assertEquals(10L, key8);
}
}
4.2 操作哈希类型数据
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@SpringBootTest
@Slf4j
public class RedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 哈希操作命令
*/
@Test
public void testHash() {
HashOperations<String, String, String> ops = stringRedisTemplate.opsForHash();
/* hset 添加 */
ops.put("user:1", "username", "zhangsan");
ops.put("user:1", "age", "18");
ops.put("user:1", "gender", "1");
ops.put("user:1", "birth", "2000-01-01");
/* hget 获取 */
String username = ops.get("user:1", "username");
Assertions.assertEquals("zhangsan", username);
/* hdel 删除 */
ops.delete("user:1", "age", "gender");
String age = ops.get("user:1", "age");
Assertions.assertNull(age);
/* hkeys 获取所有key */
Set<String> keys = ops.keys("user:1");
Assertions.assertEquals(2, keys.size());
/* hvalues 获取所有value */
List<String> values = ops.values("user:1");
Assertions.assertEquals(2, values.size());
Assertions.assertTrue(values.contains("2000-01-01"));
Assertions.assertTrue(values.contains("zhangsan"));
}
}
4.3 操作列表类型数据
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@SpringBootTest
@Slf4j
public class RedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 哈希操作命令
*/
@Test
public void testHash() {
HashOperations<String, String, String> ops = stringRedisTemplate.opsForHash();
/* hset 添加 */
ops.put("user:1", "username", "zhangsan");
ops.put("user:1", "age", "18");
ops.put("user:1", "gender", "1");
ops.put("user:1", "birth", "2000-01-01");
/* hget 获取 */
String username = ops.get("user:1", "username");
Assertions.assertEquals("zhangsan", username);
/* hdel 删除 */
ops.delete("user:1", "age", "gender");
String age = ops.get("user:1", "age");
Assertions.assertNull(age);
/* hkeys 获取所有key */
Set<String> keys = ops.keys("user:1");
Assertions.assertEquals(2, keys.size());
/* hvalues 获取所有value */
List<String> values = ops.values("user:1");
Assertions.assertEquals(2, values.size());
Assertions.assertTrue(values.contains("2000-01-01"));
Assertions.assertTrue(values.contains("zhangsan"));
}
}
4.4 操作集合类型数据
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@SpringBootTest
@Slf4j
public class RedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 集合命令
*/
@Test
public void testSet() {
SetOperations<String, String> ops = stringRedisTemplate.opsForSet();
/* sadd 添加 */
ops.add("z1", "1", "3", "5", "7", "8");
ops.add("z2", "2", "4", "6", "7", "8");
/* smembers 获取所有元素 */
Set<String> z1 = ops.members("z1");
log.info("z1:{}", z1);
/* scard 获取元素个数 */
Long size = ops.size("z2");
Assertions.assertEquals(5L, size);
/* sinter 交集 */
Set<String> intersect = ops.intersect("z1", "z2");
log.info("交集为:{}", intersect);
/* sunion 并集 */
Set<String> union = ops.union("z1", "z2");
log.info("并集为:{}", union);
/* srem 移除一个 */
ops.remove("z1", "5");
log.info("z1:{}", z1);
/* 去重随机获取5个 */
Set<String> members = ops.distinctRandomMembers(key, 5);
log.info("members:{}",members);
/* 随机获取5个 */
List<String> ranMembers = ops.randomMembers(key, 5);
log.info("ranMembers:{}",ranMembers);
/* 随机获取1个 */
String member = ops.randomMember(key);
log.info("member:{}",member);
}
}
4.5 操作有序集合类型数据
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@SpringBootTest
@Slf4j
public class RedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 有序集合操作命令
*/
@Test
public void testZSet() {
ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
/* zadd 添加 */
ops.add("z1", "ww", 80);
ops.add("z1", "li", 90);
ops.add("z1", "zl", 70);
ops.add("z1", "zs", 100);
/* zrange 根据积分范围查询由小到大 */
Set<String> z1 = ops.range("z1", 0, -1);
log.info("z1:{}", z1);
/* zrevrange 根据积分范围查询由大到小 */
Set<String> z2 = ops.reverseRange("z1", 0, -1);
log.info("z1:{}", z2);
/* zincrby 原子性添加n */
Double score = ops.incrementScore("z1", "zl", 15);
Assertions.assertEquals(85.0, score);
/* zrem 删除 */
ops.remove("z1", "zs");
log.info("z1:{}", z1);
/* 分页查询 */
int page = 1;
int size = 5;
long min = (page - 1) * size;
long max = page * size - 1;
Set<ZSetOperations.TypedTuple<String>> typedTuples = ops.reverseRangeWithScores("z1", min, max);
List<Team> list = new ArrayList<>();
typedTuples.stream().forEach(typedTuple->{
String name = typedTuple.getValue();
Double score = typedTuple.getScore();
log.info("name:{},score:{}",name,score);
});
}
}
4.6 通用命令操作
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@SpringBootTest
@Slf4j
public class RedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 通用命令
*/
@Test
public void testCommon() {
stringRedisTemplate.opsForValue().set("k1", "v1", Duration.ofMinutes(1));
/* ttl 查看存活时间 */
Long ttl = stringRedisTemplate.getExpire("k1");
log.info("ttl:{}", ttl);
/* exits 是否含有key */
Boolean b = stringRedisTemplate.hasKey("k1");
Assertions.assertTrue(b);
/* type 查看类型 */
DataType k1 = stringRedisTemplate.type("k1");
log.info("DataType:{}", k1);
/* del 同步删除 */
stringRedisTemplate.delete("k1");
/* unlink 异步删除 */
stringRedisTemplate.unlink("k1");
}
}
4.7 Bitmap
Bitmap是Redis提供的一种数据结构,用于存储和操作位图数据。位图是一个由二进制位组成的数组,每个二进制位代表一个特定的状态,通常用0和1表示。Redis的位图数据类型可以存储非常大的位图,并提供了一系列的位操作命令来操作位图。
Redis的位图数据类型有以下几个特点:
-
省空间:Redis的位图使用紧凑的数据结构,可以高效地存储大量的位图数据,节省内存。
-
高效:Redis的位图数据类型支持高效的位操作命令,如设置某个位的值、获取某个位的值、计算位图的逻辑运算等。
-
支持位图操作:Redis的位图数据类型提供了一系列的位操作命令,包括设置位、清除位、获取位的值、统计位为1的个数、计算两个位图的交集、并集和差集等。
-
压缩存储:Redis的位图对于稀疏的位图可以进行压缩存储,只存储1的位的位置,可以进一步节省存储空间。
Redis的位图数据类型常见的应用场景包括统计用户在线时长、记录用户的活跃状态、统计用户的点击次数等。因为位图的存储和操作非常高效,所以适用于需要快速统计和查询大量的布尔类型数据的场景。
官方文档:Commands | Redis
Redis最基础的数据类型只有5种:String、List、Set、SortedSet、Hash,其它特殊数据结构大多都是基于以上5这种数据类型。
BitMap也不例外,它是基于String结构的。因为Redis的String类型底层是SDS,也会存在一个字节数组用来保存数据。而Redis就提供了几个按位操作这个数组中数据的命令,实现了BitMap效果。
由于String类型的最大空间是512MB,也就是2的31次幂个bit,因此可以保存的数据量级是十分恐怖的。
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.List;
@Slf4j
@SpringBootTest
public class RedisTemplateTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testBitmap() {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
/* set 添加 0为false,1为true */
Boolean beforeBit = ops.setBit("bm", 0, true);
log.info("0位上被覆盖的bit值:{}",beforeBit);
/* get 获取 0为false,1为true */
Boolean bm = ops.getBit("bm", 0);
log.info("0位上的值:{}",bm);
/* bitfield 批量获取bit值 */
ops.setBit("bm", 1, true);
ops.setBit("bm", 2, false);
ops.setBit("bm", 3, false);
ops.setBit("bm", 4, true);
List<Long> longs = ops.bitField("bm", BitFieldSubCommands.create(BitFieldSubCommands.BitFieldGet.create(BitFieldSubCommands.BitFieldType.unsigned(5), BitFieldSubCommands.Offset.offset(0))));
// 0-4位上的二进制转为10进制的输出结果
int bitmapValue = longs.get(0).intValue();
// 10进制转为2进制
String binaryString = Integer.toBinaryString(bitmapValue);
log.info("0-4位上的bit值为:{}",binaryString);
}
}
5. Redis 管道
Redis管道是一种将多个命令一次性发送给Redis服务器并获取结果的机制。它的作用主要有以下几点:
-
减少网络延迟:通过将多个命令打包在一起发送,可以减少与服务器的通信次数,从而减少了网络延迟,提高了性能。
-
提高吞吐量:由于管道允许并行发送多个命令,服务器可以在接收到命令后立即进行处理,而不需要等待客户端发送完所有命令。这样可以提高服务器的吞吐量。
-
原子性操作:在管道中的所有命令会被作为一个整体进行处理,要么全部成功,要么全部失败。这可以保证一组相关的操作能够以原子方式执行,避免了并发场景下的数据不一致性问题。
需要注意的是,虽然管道可以提高性能,但它并不能改变Redis的单线程特性。在处理管道请求时,Redis仍然是按照顺序逐个执行命令的,不会并行处理。因此,在某些场景下,如果命令之间有数据依赖关系,使用管道可能并不会带来性能的提升。
英文版文档:Redis pipelining
中文版文档:redis使用管道(Pipelining)提高查询速度
注意事项:
不要在一次批处理中传输太多命令,否则单次命令占用带宽过多,会导致网络阻塞
使用示例:
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@SpringBootTest
@Slf4j
public class RedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* Pipelined 管道查询
*/
@Test
public void testPipelined() {
// 向redis中缓存数据
SetOperations<String, String> ops = stringRedisTemplate.opsForSet();
ops.add("z1", "1", "3", "5", "7", "8");
List<String> list = Arrays.asList("1", "2", "12", "8", "15", "5", "45");
// Pipelined 管道查询
List<Object> objects = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection src = (StringRedisConnection) connection;
for (String s : list) {
src.sIsMember("z1", s);
}
return null;
}
});
// 遍历结果
for (int i = 0; i < objects.size(); i++) {
String result = objects.get(i).toString();
if (Boolean.valueOf(result)) {
log.info(list.get(i));
}
}
}
}