题记
本文涵盖了Redis的各种数据结构和命令,Redis的各种常见Java客户端的应用和最佳实践
jedis案例github地址:https://github.com/whltaoin/fedis_java_demo
SpringbootDataRedis案例github地址:https://github.com/whltaoin/springbootDataRedis_demo
一、初始Redis
1. SQL与NoSQL对比
- 结构化(Structured)对比
- SQL具有严格的结构化格式(左图)
- NoSQL的数据存储格式相对随意(右图)
- 关系对比
- SQL在表和表间可能存在联系,在操作一张表时,需要考虑对应关联表的完整性。
- NoSQL数据间是无关联的,(下图为存储方式)
- 查询对比
- SQL查询有固定的语法,只要是SQL都可以使用相同的语句查询。
- NoSQL没有固定的语法,每个NoSQL都有自己的语法糖(下图为查询同一个东西,出现了不同的语法)
- SQL需要满足事务的ACID特性(原子性、一致性、隔离性、持久性)
- NoSQL只是基本满足事务的ACID
- 差异总结:
2. 初始Redis
- 简介
Redis(远程词典服务器),是一个基于内存的键值型NoSQL数据库
- 特征:
- 键值型:key-value
- 单线程:命令具有原子性
- 低延迟、数据块原因:(基于内存、IO多路复用、良好的编码)
- 支持数据持久化
- 支持主从集群(主服务器和副服务器)、分片集群(将数据拆分,分别存在不同的服务器上)
- 支持多语言客户端(java、C...)
3. 安装Redis(Linux安装)
忽略...
二、Redis常见命令
1. 数据类型介绍
- String
- Hash
- 哈希表
- List
- 有序集合,本质是链表
- Set
- 无序集合,不可重复
- SortedSet
- 有序集合,不可重复
- GEO
- 地理坐标
2. 通用命令
- 查询所有通用命令:
help @generic
- 查看符合模版的所有key
- 注意:不建议在生产环境设备上使用该命令,因为模糊查询,效率不高且浪费资源。
# 语法 keys +[pattern]
- 删除key
- 可以单个删除,也可以多个一起删除
del [key]
删除一个或多个示例
- 判断key是否存在
- 可以判断单个,也可以判断多个
exists [key]
判断单个和多个示例
- 给key设置有效期,到期后自动删除(单位秒)
- TTL查看可以的有效时间
expire [key] [seconds] ttl key
示例:设置age1的有效期为10s,到期后自动删除
- 总结
3. String类型
- set
- get
- mset
- mget
- incr
- incrby
- incrbyfloat
- setnx(新增,存在则不创建)
- 等价于:set [key value] nx
- setex
- 等价于:set
4. Key的层级格式
- 思考:
- 解决方法
- 示例
heima:user:1 herma:product:1
层级结构:
5. Hash类型
- Hash类型(散列),其中的value是一个无序字典
- 类似于java 中的HashMap结构
- 使用场景:
- 当要修改JSON数据中某个属性的值时,使用String存储的需要重新覆盖数据,而我们的需求只是想要修改某个值
- 常用命令
- 示例
- hset
- hget
- hmset
- hmget
- hgetall
- hkeys
- hvals
- hincrby
- hsetnx(已存在,不新建)
6. List类型
- Redis中的List类型于java中的LinkedList类似,可以当它是一个双向链表结构。
- 既可以支持正向检索也可以支持反向检索。
- 特征:
- 有序
- 元素可重复
- 插入和删除快
- 查询速度一般
- 使用场景举例:
- 朋友圈点赞列表,评论列表等
- 常用命令:
- 示例:
7. Set类型
- Redis的Set结构和Java中的HashSet类似,可以看做一个value为null的HashMap。
- 特征:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
- 单个set常见命令
- 示例
- 多个set操作命令
- 求交集(sinter):两集合公共的
- 求并集(sunion):所有元素合并
- 求差集(sdiff):set中有,但是set2中没有
- 练习题:
- 张三的好友人数
- 张三和李四的共同好友
- 查询那些人是张三的好友但是不是李四的好友
- 查询张三和李四的共同好友
- 判断李四是否是张三的好友
- 判断张三是否是李四的好友
- 将李四从张三的好友列表中移除
8. SortedSet类型
- 特性:
- 可排序
- 元素不可重复
- 查询快
- 应用场景:
- 因为可排序,常用于实现排行榜功能
- 常用命令
- 练习题:
- 添加数据
- 删除Tom
- 获取Amy分数
- 获取Rose排名
- 查询80分以上人数
- 给Amy加2分
- 查询排名前三的同学
- 查询80以下的同学
三、Redis的Java客户端
1. 常用Java客户端的优缺点对比
2. Jedis使用(单线程)
github地址:https://github.com/whltaoin/fedis_java_demo
- 使用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使用(使用连接池)
github地址:https://github.com/whltaoin/fedis_java_demo
- 使用步骤:
- 创建连接池
- 获取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(); } }
- 测试类代码
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(); } } }
- 执行结果:
4. SpringDataRedis使用
github示例案例地址:https://github.com/whltaoin/springbootDataRedis_demo
4.1. springData介绍
- Redis模版版本信息
4.2. SpringDataRedis快速入门
4.3. SpringbootDataRedis使用步骤
4.4. 基本示例
- 导入依赖
<?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); } }
- 结果:
4.5. 重构redisTemplate序列化和反序列化工具
- 问题
- 在我们直接使用redisTemplate时,存入到redis的内容,是经过编译的字节,
- 影响阅读性
- 增加了存储空间
- 解决方案:
- 自定义序列化和反序列话的编码格式
- 步骤
- 建立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); } }
- 测试结果
4.6. StringRedisTamplate类使用
- 问题:
- 虽然自定义序列化工具可以解决上一问题,但是修改后在JSON字符串中会多存储一个类的包名
- 导致增大存储的空间
- 解决方法,
- 使用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); }