题记
本文涵盖了Redis的各种数据结构和命令,Redis的各种常见Java客户端的应用和最佳实践
jedis案例github地址:https://github.com/whltaoin/fedis_java_demo
SpringbootDataRedis案例github地址:https://github.com/whltaoin/springbootDataRedis_demo
一、初始Redis
SQL与NoSQL对比
- 结构化(Structured)对比
- SQL具有严格的结构化格式(左图)
- NoSQL的数据存储格式相对随意(右图)
- 关系对比
- SQL在表和表间**可能存在联系**,在操作一张表时,需要考虑对应关联表的完整性。
2. NoSQL数据间是**<font style="color:#DF2A3F;">无关联</font>**的,(下图为存储方式)
- 查询对比
- SQL查询有**固定的语法**,只要是SQL都可以使用相同的语句查询。
2. NoSQL没有固定的语法,每个NoSQL都有自己的语法糖(下图为查询同一个东西,出现了不同的语法)
- SQL需要满足**事务的ACID特性**(原子性、一致性、隔离性、持久性)
- NoSQL只是**基本满足事务的ACID**
- 差异总结:
初始Redis
- 简介
Redis(远程词典服务器),是一个基于内存的**键值型NoSQL**数据库
- 特征:
- 键值型:key-value
- 单线程:命令具有原子性
- 低延迟、数据块原因:(基于**内存**、IO多路复用、良好的编码)
- 支持数据持久化
- 支持主从集群(主服务器和副服务器)、分片集群(将数据拆分,分别存在不同的服务器上)
- 支持多语言客户端(java、C…)
安装Redis(Linux安装)
忽略…
二、Redis常见命令
数据类型介绍
- String
1. - Hash
- 哈希表
- List
- 有序集合,本质是链表
- Set
- 无序集合,不可重复
- SortedSet
- 有序集合,不可重复
- GEO
- 地理坐标
通用命令
- 查询所有通用命令:
help @generic
- 查看符合模版的所有key
- 注意:不建议在生产环境设备上使用该命令,因为模糊查询,效率不高且浪费资源。
# 语法
keys +[pattern]
- 删除key
- 可以单个删除,也可以多个一起删除
del [key]
删除一个或多个示例
- 判断key是否存在
- 可以判断单个,也可以判断多个
exists [key]
判断单个和多个示例
- 给key设置有效期,到期后自动删除(单位秒)
- TTL查看可以的有效时间
expire [key] [seconds]
ttl key
示例:设置age1的有效期为10s,到期后自动删除
- 总结
String类型
- set
- get
- mset
- mget
- incr
- incrby
- incrbyfloat
- setnx(新增,存在则不创建)
- 等价于:set [key value] nx
- setex
- 等价于:set
Key的层级格式
- 思考:
- 解决方法
- 示例
heima:user:1
herma:product:1
层级结构:
Hash类型
- Hash类型(散列),其中的value是一个**无序字典**
- 类似于java 中的HashMap结构
- 使用场景:
- 当要修改JSON数据中某个属性的值时,使用String存储的需要重新覆盖数据,而我们的需求只是**想要修改某个值**
- 常用命令
- 示例
- hset
2. hget
3. hmset
4. hmget
5. hgetall
6. hkeys
7. hvals
8. hincrby
9. hsetnx(已存在,不新建)
List类型
- Redis中的List类型于java中的LinkedList类似,可以当它是一个双向链表结构。
- 既可以支持正向检索也可以支持反向检索。
- 特征:
- 有序
- 元素可重复
- 插入和删除快
- 查询速度一般
- 使用场景举例:
- 朋友圈点赞列表,评论列表等
- 常用命令:
1. 示例:
Set类型
- Redis的Set结构和Java中的HashSet类似,可以看做一个value为null的HashMap。
- 特征:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
- 单个set常见命令
1. 示例
- 多个set操作命令
- 求交集(sinter):两集合公共的
2. 求并集(sunion):所有元素合并
3. 求差集(sdiff):set中有,但是set2中没有
- 练习题:
1. 张三的好友人数
2. 张三和李四的共同好友
3. 查询那些人是张三的好友但是不是李四的好友
4. 查询张三和李四的共同好友
5. 判断李四是否是张三的好友
6. 判断张三是否是李四的好友
7. 将李四从张三的好友列表中移除
SortedSet类型
- 特性:
- 可排序
- 元素不可重复
- 查询快
- 应用场景:
- 因为可排序,常用于实现**排行榜**功能
- 常用命令
- 练习题:
1. 添加数据
2. 删除Tom
3. 获取Amy分数
4. 获取Rose排名
5. 查询80分以上人数
6. 给Amy加2分
7. 查询排名前三的同学
8. 查询80以下的同学
三、Redis的Java客户端
常用Java客户端的优缺点对比
Jedis使用(单线程)
- 使用Jedis分为了四步骤:
- 导入依赖
- 初始化Jedis对象
- 执行Jedis中的操作方法
- 释放Jedis对象
- 具体实现
- 导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>jedis-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 核心-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>6.0.0</version>
</dependency>
<!-- 测试类-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
b. 测试类方法内容
package cn.varin;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class JedisTest {
private Jedis jedis;
@Before
public void init(){
jedis = new Jedis("ip",6379);
jedis.auth("密码");
jedis.select(0);
}
@Test
public void StringTest(){
String set = jedis.set("name", "varin");
System.out.println("执行set后结果为:"+set);
String name = jedis.get("name");
System.out.println("key为name的value为:"+name);
}
@After
public void close(){
if(jedis !=null){
jedis.close();
}
}
}
3. 执行结果
Jedis使用(使用连接池)
- 使用步骤:
- 创建连接池
- 获取Jedis对象
- 操作Jedis对象
- 归还连接池对象
- 具体代码
- 创建连接池对象
package cn.varin.jedis.utils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
// Jedis连接池对象
public class JedisConnectionFactory {
//
static public final JedisPool jedisPool;
static {
// 创建配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数
jedisPoolConfig.setMaxTotal(10);
// 最大空闲数
jedisPoolConfig.setMaxIdle(10);
// 最小空闲数
jedisPoolConfig.setMinIdle(2);
// 空闲等待时间
jedisPoolConfig.setMaxWaitMillis(1000);
jedisPool = new JedisPool(jedisPoolConfig,"host",6379,100,"password");
}
public static Jedis getResource(){
return jedisPool.getResource();
}
}
2. 测试类代码
package cn.varin;
import cn.varin.jedis.utils.JedisConnectionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class JedisTest {
private Jedis jedis;
@Before
public void init(){
jedis = JedisConnectionFactory.getResource();
jedis.select(0);
}
@Test
public void StringTest(){
String set = jedis.set("name", "varya");
System.out.println("执行set后结果为:"+set);
String name = jedis.get("name");
System.out.println("key为name的value为:"+name);
}
@After
public void close(){
if(jedis !=null){
jedis.close();
}
}
}
3. 执行结果:
SpringDataRedis使用
github示例案例地址:https://github.com/whltaoin/springbootDataRedis_demo
springData介绍
- Redis模版版本信息
SpringDataRedis快速入门
SpringbootDataRedis使用步骤
基本示例
- 导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.varin</groupId>
<artifactId>springbootDataRedis_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootDataRedis_demo</name>
<description>springbootDataRedis_demo</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- springbootDataRedis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 配置yml
spring:
data:
redis:
port: 6379
password: password
database: 0
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 0
max-wait: 100ms
host: address
- 编写测试类
package cn.varin.springbootdataredis_demo;
import cn.varin.springbootdataredis_demo.pojo.User;
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.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
@SpringBootTest
class SpringbootDataRedisDemoApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
void setTest() {
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("user:1",new User("varin",1).toString());
Object o = valueOperations.get("user:1");
System.out.println(o);
}
}
- 结果:
重构redisTemplate序列化和反序列化工具
- 问题
1. 在我们直接使用redisTemplate时,存入到redis的内容,是经过编译的字节,
2. 影响阅读性
3. 增加了存储空间
- 解决方案:
- 自定义序列化和反序列话的编码格式
- 步骤
- 建立template
- 设置连接工厂
- 设置序列化工具
- 分别对key和value设置不同的格式
package cn.varin.springbootdataredis_demo.config;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class RedisTamplateConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(factory);
// 创建序列化工具
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 对key
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 对value
template.setValueSerializer(genericJackson2JsonRedisSerializer);
template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return template;
}
}
测试类
package cn.varin.springbootdataredis_demo;
import cn.varin.springbootdataredis_demo.pojo.User;
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.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
@SpringBootTest
class SpringbootDataRedisDemoApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
void setTest() {
ValueOperations valueOperations = redisTemplate.opsForValue();
// value为user对象
valueOperations.set("user:2",new User("varin",1));
Object o = valueOperations.get("user:1");
System.out.println(o);
}
}
- 测试结果
StringRedisTamplate类使用
- 问题:
- 虽然自定义序列化工具可以解决上一问题,但是修改后在JSON字符串中会多存储一个类的包名
- 导致增大存储的空间
- 虽然自定义序列化工具可以解决上一问题,但是修改后在JSON字符串中会多存储一个类的包名
- 解决方法,
- 使用StringRedisTamplate类,在加上自己使用第三方的序列化工具进行存储。
- 优点:在存储时不会增加额外的数据
- 缺点:增加少许的代码量
- 使用StringRedisTamplate类,在加上自己使用第三方的序列化工具进行存储。
- 示例代码
@Autowired
StringRedisTemplate stringRedisTemplate;
// 用于转Json格式
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void StringRedisTamplateTest() throws JsonProcessingException {
User user = new User("varya",1);
// 转JSON格式
String s = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:3",s);
// 读取数据
String s1 = stringRedisTemplate.opsForValue().get("user:3");
// 反序列化
User user1 = mapper.readValue(s1, User.class);
System.out.println(user1);
}