Redis是一种键值型的NoSql数据库,这里有两个关键字:
键值型
NoSql
其中键值型,是指Redis中存储的数据都是以key、value对的形式存储,而value的形式多种多样,可以是字符串、数值、甚至json:
而NoSql则是相对于传统关系型数据库而言,有很大差异的一种数据库。
NoSql可以翻译做Not Only Sql(不仅仅是SQL),或者是No Sql(非Sql的)数据库。是相对于传统关系型数据库而言,有很大差异的一种特殊的数据库,因此也称之为非关系型数据库。
传统关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名、字段数据类型、字段约束等等信息,插入的数据必须遵守这些约束:
而NoSql则对数据库格式没有严格约束,往往形式松散,自由。
传统数据库的表与表之间往往存在关联,例如外键:
而非关系型数据库不存在关联关系,要维护关系要么靠代码中的业务逻辑,要么靠数据之间的耦合:
{
id: 1,
name: "张三",
orders: [
{
id: 1,
item: {
id: 10, title: "荣耀6", price: 4999
}
},
{
id: 2,
item: {
id: 20, title: "小米11", price: 3999
}
}
]
}
此处要维护“张三”的订单与商品“荣耀”和“小米11”的关系,不得不冗余的将这两个商品保存在张三的订单文档中,不够优雅。还是建议用业务来维护关联关系。
传统关系型数据库会基于Sql语句做查询,语法有统一标准;
而非关系型数据库往往不支持事务,或者不能严格保证ACID的特性,只能实现基本的一致性。
除了上述四点以外,在存储方式、扩展性、查询性能上关系型与非关系型也都有着显著差异,总结如下:
Redis诞生于2009年全称是Remote Dictionary Server 远程词典服务器,是一个基于内存的键值型NoSQL数据库。
特征:
作者:Antirez
Redis的官方网站地址:https://redis.io/
详细请参考笔者的另一篇博客——《Redis安装说明(黑马程序员)》
https://huanghaoheng.blog.csdn.net/article/details/131275916
Redis是典型的key-value数据库,key一般是字符串,而value包含很多不同的数据类型:
Redis为了方便我们学习,将操作不同数据类型的命令也做了分组,在官网( https://redis.io/commands )可以查看到不同的命令:
不同类型的命令称为一个group,我们也可以通过help命令来查看各种不同group的命令:
接下来,我们就学习常见的五种基本数据类型的相关命令。
通用指令是部分数据类型的,都可以使用的指令,常见的有:
通过help [command] 可以查看一个命令的具体用法,例如:
# 查看keys命令的帮助信息:
127.0.0.1:6379> help keys
KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
keys 查看所有的键
keys a查看以a开头的键
mset k1 v1 k2 v2 k3 v3 批量添加键值对
del k1 k2 k3 k4 批量删除键值对
exits age 判断是否存在该键值 存在返回1,不存在返回0
expire age 20 给对应的键设置过期时间
当返回值为-2,表示这个键已失效
当返回值为-1,表示这个键永久有效
String类型,也就是字符串类型,是Redis中最简单的存储类型。
其value是字符串,不过根据字符串的格式不同,又可以分为3类:
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m.
String的常见命令有:
案例实操
MSET:批量添加多个String类型的键值对
MGET:获取多个key,对应的value
INCR:让一个整型的key自增1
INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2
INCRBY age -1 让age的值自减1
或者用DECR
INCRBYFLOAT:让一个浮点类型的数字自增并指定步长
SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行
setnx name lisi和set name wangwu nx效果是一样的
SETEX:添加一个String类型的键值对,并且指定有效期
setex name 10 jack和set name jack ex 10效果一样
Redis没有类似MySQL中的Table的概念,我们该如何区分不同类型的key呢?
例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,此时如果使用id作为key,那就会冲突了,该怎么办?
我们可以通过给key添加前缀加以区分,不过这个前缀不是随便加的,有一定的规范:
Redis的key允许有多个单词形成层级结构,多个单词之间用’:'隔开,格式如下:
项目名:业务名:类型:id
这个格式并非固定,也可以根据自己的需求来删除或添加词条。这样以来,我们就可以把不同类型的数据区分开了。从而避免了key的冲突问题。
例如我们的项目名称叫 heima,有user和product两种不同类型的数据,我们可以这样定义key:
user相关的key:heima:user:1
product相关的key:heima:product:1
如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储:
KEY | VALUE |
---|---|
heima:user:1 | {“id”:1, “name”: “Jack”, “age”: 21} |
heima:product:1 | {“id”:1, “name”: “小米11”, “price”: 4999} |
set heima:user:1 '{"id":1, "name":"Jack", "age": 21}'
set heima:user:2 '{"id":2, "name":"Rose", "age": 18}'
set heima:product:1 '{"id":1, "name":"小米11", "price": 4999}'
set heima:product:2 '{"id":2, "name":"荣耀6", "price": 2999}'
并且,在Redis的桌面客户端中,还会以相同前缀作为层级结构,让数据看起来层次分明,关系清晰:
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便:
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD:
Hash的常见命令有:
HSET key field value:添加或者修改hash类型key的field的值
HGET key field:获取一个hash类型key的field的值
HMSET:批量添加多个hash类型key的field的值
HMGET:批量获取多个hash类型key的field的值
HGETALL:获取一个hash类型的key中的所有的field和value
HKEYS:获取一个hash类型的key中的所有的field
HVALS:获取一个hash类型的key中的所有的field
HINCRBY:让一个hash类型key的字段值自增并指定步长
HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
案例实操:
hset key filed value 添加或者修改hash类型key的field的值
添加hash类型key的field的值
修改hash类型key的field的值
HGET key field:获取一个hash类型key的field的值
HMSET:批量添加多个hash类型key的field的值
HMGET:批量获取多个hash类型key的field的值
HGETALL:获取一个hash类型的key中的所有的field和value
HKEYS:获取一个hash类型的key中的所有的field
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征也与LinkedList类似:
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
List的常见命令有:
案例实操
LPUSH key element … :向列表左侧插入一个或多个元素
RPUSH key element … :向列表右侧插入一个或多个元素
LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
RPOP key:移除并返回列表右侧的第一个元素
LRANGE key star end:返回一段角标范围内的所有元素
BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil
新开一个会话窗口
在第一个窗口中,做如下操作
阻塞100秒
在第二个窗口中连接redis,从左边新增push一个jack
第一个窗口,移除并返回列表左侧的第一个元素
Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:
无序
元素不可重复
查找快
支持交集、并集、差集等功能
Set的常见命令有:
案例实操
sadd key member … 向set中添加一个或多个元素
smembers s1 获取set中的所有元素
srem s1 a 删除s1集合中的a元素
sismember s1 a : 判断集合s1中是否存在a元素,存在返回1,不存在返回0
sismember s1 b : 判断集合s1中是否存在b元素,存在返回1,不存在返回0
SCARD s1: 返回集合s1中元素的个数
练习:
将李四从张三的好友列表中移除
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
SortedSet具备下列特性:
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
SortedSet的常见命令有:
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可,例如:
升序获取sorted set 中的指定元素的排名:ZRANK key member
降序获取sorted set 中的指定元素的排名:ZREVRANK key memeber
练习题:
将班级的下列学生得分存入Redis的SortedSet中:
Jack 85, Lucy 89, Rose 82, Tom 95, Jerry 78, Amy 92, Miles 76
并实现下列功能:
在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/
其中Java客户端也包含很多:
标记为*的就是推荐使用的java客户端,包括:
Jedis的官网地址: https://github.com/redis/jedis
Jedis案例实操代码:https://download.csdn.net/download/weixin_46411355/87929873
1)引入依赖:
<dependencies> <!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
package com.heima.test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUp(){
//1.建立连接
jedis = new Jedis("192.168.88.129",6379);
//2.设置密码
jedis.auth("123321");
//3.选择库
jedis.select(0);
}
@Test
void testString(){
String result = jedis.set("name", "虎哥");
System.out.printf("result="+result);
//获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
@AfterEach
void tearDown(){
if(jedis!=null){
jedis.close();
}
}
}
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。
package com.heima.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
//配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
poolConfig.setMaxWaitMillis(1000);
// 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
jedisPool = new JedisPool(poolConfig,"192.168.88.129",6379,1000,"123321");
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单。
首先,新建一个SpringBoot项目,然后按照下面步骤执行:
0)创建一个SpringBoot项目
<!--common-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
application.yml
spring:
redis:
host: 192.168.88.129
port: 6379
password: 123321
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100ms
测试类
package com.heima;
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;
@SpringBootTest
class SpringdataredisDemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//写入一条String数据
redisTemplate.opsForValue().set("name","虎哥");
//获取String数据
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}
}
RedisTemplate可以接收任意Object作为值写入Redis:
只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的:
缺点:
我们可以自定义RedisTemplate的序列化方式,代码如下:
package com.heima.redis.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 RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory){
//创建RedisTemplate对象
RedisTemplate template = new RedisTemplate<>();
//设置连接工厂
template.setConnectionFactory(connectionFactory);
//创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//设置key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
//设置value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
//返回
return template;
}
}
com.fasterxml.jackson.core
jackson-databind
package com.heima.redis.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String name;
private Integer age;
}
编写单元测试
@Test
void testSaveUser(){
//写入数据
redisTemplate.opsForValue().set("user:100",new User("虎哥",21));
//获取数据
User user = (User) redisTemplate.opsForValue().get("user:100");
System.out.println("user = " + user);
}
这里采用了JSON序列化来代替默认的JDK序列化方式。最终结果如图:
整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。
为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws JsonProcessingException {
//创建对象
User user = new User("虎哥",21);
//手动序列化
String userJson = mapper.writeValueAsString(user);
//写入数据
stringRedisTemplate.opsForValue().set("user:200",userJson);
//获取数据
String userJson2 = stringRedisTemplate.opsForValue().get("user:200");
//手动反序列化
User user2 = mapper.readValue(userJson2, User.class);
System.out.println("user2 = " + user2);
}
@Test
void testHash(){
stringRedisTemplate.opsForHash().put("users:400","name","虎哥");
stringRedisTemplate.opsForHash().put("users:400","age","21");
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("users:400");
System.out.println("entries = " + entries);
}