一、Redis快速入门
1.1 初识redis
1.1.1 认识NoSql
-
Sql -> 关系型数据库
- 结构化(Structured) -> 对于表中的数据,我们可以进行约束,如主键约束,长度约束等。
- 关联的(Relational) -> 表和表之间可以通过某个字段进行关联,如有user用户表和product商品表,有一个order表将这两个表的id关联起来,此时order表存入user的id和product的id,并不用存整个user数据。
- SQL查询 -> 共用查询语句,不同的关系型数据库都适用。
- ACID -> 原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)。
-
NoSql -> 非关系性数据库
- 非结构化
- 无关联的
- 非sql查询
- BASE -> BA基本可用,S允许中间态,E最终一致性
1.1.2 认识redis
Redis诞生于2009年全称是Remote Dictionary Server,远程词典服务器,是一个基于内存的键值型NoSQL数据库。
- 特征:
- 键值(key-value) 型,value支持多种不同数据结构,功能丰富
- 单线程,每个命令具备原子性。redis6.0多线程仅仅对于网络请求处理这部分,核心命令执行依然是单线程
- 低延迟,速度快(基于内存、IO多路复用、良好的编码)
- 支持数据持久化
- 支持主从集群(从节点可以备份主节点的数据,可以进行读写分离,提高效率)、分片集群(如将1TB数据存在不同节点上,存储上限提高)
- 支持多语言客户端(java,python,C等等)
1.2 安装Redis
1.2.1 安装redis依赖
Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖:
yum install -y gcc tcl
1.2.2 上传安装包
然Redis安装包上传到虛拟机的任意目录:如/usr/local/src目录
进入目录解压缩:
tar -zxvf redis-6.2.6.tar.gz
进入redis目录:
cd redis-6.2.6
运行编译命令,make代表编译,make install代表安装:
make && make install
如果没有出错就是安装成功,默认安装路径在/usr/local/bin目录下
- 切换到该目录下查看:
- 该目录以及默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:
- redis-cli: 是redis提供的命令行客户端
- redis-server: 是redis的服务端启动脚本
- redis-sentinel: 是redis的哨兵启动脚本
- 该目录以及默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:
1.2.3 启动
-
redis的启动方式有很多种,例如:
- 默认启动
- 指定配置启动
- 开机自启
-
默认启动:安装完成后,在任意目录输入redis-server命令即可启动Redis:
redis-server
这种启动属于[前台启动],会阻塞整个会话窗口,窗口关闭或者按下( CTRL + C )则Redis停止。不推荐使用。
- 指定配置启动
如果要让Redis以后台]方式启动,则必须修改Redis配置文件,就在我们之前解压的redis安装包下
([/usr/local/src/redis-6.2.6)) ,名字叫redis.conf:
我们先将这个配置文件备份一份(怕弄错了好恢复):
cp redis.conf redis.conf.bck
这时我们就可以修改redis.conf文件:
vim redis.conf
准备修改redis.conf文件中的一些配置:
#监听的地址,默认是127.0.0.1, 会导致只能在本地访问。修改为0. 0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
#守护进程,修改为yes后即可后台运行
deamonize yes
#密码,设置后访问Redis必须输入密码
requirepass 123456
Redis的其它常见配置:
#监听的端口
port 6379
#工作目录,默认是当前目录,也就是运行redis-server时的命令, 日志、持久化等文件会保存在这个目录
dir .
#数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
#设置redis 能够使用的最大内存
maxmemory 512mb
#日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
现在我们开始修改配置:
- 修改第一处:
- 修改第二处:
- 修改第三处
- 后面的根据需求改,也可以加个日志:
这时可以启动:
cd /usr/local/src/redis-6.2.6
redis-server redis.conf
# 查看进程
ps -ef | grep redis
停止服务:
redis-cli -a 123456 shutdown
1.2.4 设置开机自启
- 编辑文件
vi /etc/systemd/system/redis.service
- 编辑内容如下:
[Unit]
Description=redis-server
After=network.target
[Service]
Type= forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
- 然后重载系统服务:
systemctl daemon-reload
# 启动redis
systemctl start redis
# 查看redis状态
systemctl status redis
# 停止redis
systemctl stop redis
# 此时没啥问题就可以设置开机自启了
systemctl enable redis
1.3 redis命令行客户端
Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:
redis-cli [option] [command]
-
其中常见的options有:
- -h 127.0.0.1 :指定要连接的redis节点的IP地址,默认是127.0.0.1
- -p 6379 :指定要连接的redis节点的端口,默认是6379
- -a 123456 : 指定redis的访问密码
-
其中的commonds就是Redis的操作命令,例如:
- ping:与redis服务端做心跳测试,服务端正常会返回
pong
- 不指定commond时,会进入redis-cli 的交互控制台:
- ping:与redis服务端做心跳测试,服务端正常会返回
连接上后会有这个警告,此时可以我们先进入,再输入密码
这时候就没有警告了
二、redis常用命令
2.1 redis数据结构介绍
Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样:
2.2 redis结构及命令
2.2.1 通用命令
- 通用指令是部分数据类型的,都可以使用的指令,常见的有:
- KEYS:查看符合模板的所有key,不建议在生产环境设备上使用
- DEL:删除一个指定的key
- EXISTS: 判断key是否存在
- EXPIRE: 给一个key设置有效期,有效期到期时该key会被自动删除
- TTL:查看一个KEY的剩余有效期
2.2.2 String类型
-
String类型,也就是字符串类型,是Redis中最简单的存储类型。其value是字符串,不过根据字符串的格式不同,又可以分为3类:
- string: 普通字符串
- int: 整数类型,可以做自增、自减操作
- float: 浮点类型,可以做自增、自减操作
-
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过
512m
. -
String常见命令:
- SET:添加或者修改已经存在的一个String类型的键值对,SET会覆盖之前同样KEY的值
SET K1 V1
- GET:根据key获取String类型的value
GET K1
- MSET:批量添加多个String类型的键值对
MSET K1 V1 K2 V2 K3 V3
- MGET:根据多个key获取多个String类型的value
MGET K1 K2 K3
- INCR:让一个整型的key自增1
INCR age
- INCRBY:让一个整型的key自增并指定步长,例如: incrby num 2让num值自增2
INCRBY age 2
- INCRBYFLOAT:让一个浮点类型的数字自增并
必须
指定步长INCRBYFLOAT score 0.5
- SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行
SETNX K1 V1
- SETEX:添加一个String类型的键值对,并且指定有效期
SETEX K1 V1 10
- SET:添加或者修改已经存在的一个String类型的键值对,SET会覆盖之前同样KEY的值
2.2.3 key的层级格式
如果我们有一个user的id为1,product的id为1,那么怎么办呢?
-
Redis的key允许有多个单词形成层级结构,多个单词之间用"隔开,格式如下:
项目名:业务名:类型:id
-
这个格式并非固定,也可以根据自己的需求来删除或添加词条。例如我们的项目名称叫shop,有user和product两种不同类型的数据,我们可以这样定义key:
- user相关的key: shop:user:1
- product相关的key: shop:product:1
-
如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储
KEY | VALUE |
---|---|
shop:user:1 | {“id”:1, “name”:“jack”,“age”:21} |
shop:product:1 | {“id”:1,“name”:“iPhone”,“price”:5999} |
2.2.4 Hash 类型
-
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中的所有的value
- HINCRBY:让一个hash类型key的字段值自增并指定步长
- HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
2.2.5 List类型
-
Redis中的List类型与Java中的LinkedList类似,可以看做是一个
双向链表
结构。既可以支持正向检索和也可以支持反向检索。特征也与LinkedList类似:- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
-
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
-
List的常见命令有:
- LPUSH key element… :向列表左侧插入一个或多个元素
- LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
- RPUSH key element… :向列表右侧插入一个或多个元素
- RPOP key:移除并返回列表右侧的第一 -个元素
- LRANGE key star end:返回一段角标范围内的所有元素
- BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil
-
思考:
- 如何利用List结构模拟一个栈?
- 先进后出,入口和出口在同一边,所以使用 LPUSH,LPOP或者RPUSH,RPOP就可以了
- 如何利用List结构模拟一个队列?
- 先进先出,入口和出口在不同边,所以使用 LPUSH,RPOP或者LPUSH,RPOP就可以了
- 如何利用List结构模拟一个阻塞队列?
- 入口和出口在不同边、出队时采用BLPOP或BRPOP
- 如何利用List结构模拟一个栈?
2.2.6 Set类型
-
Redis的Set结构与Java中的HashSet类似,可以看做是一个value为 null的HashMap。因为也是一个hash表, 因此具备与HashSet类似的特征:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
-
set常用命令有:
-
SADD key member … :向set中添加一个或多个元素
-
SREM key member … :移除set中的指定元素
-
SCARD key:返回set中 元素的个数
-
SISMEMBER key member:判断一个元素是否存在于set中
-
SMEMBERS:获取set中的所有元素
-
SINTER key1 key2… :求key1与key2的交集
-
SDIFF key1 key2 … :求key1与key2的差集
-
SUNION key1 key2 …:求key1和key2的并集
-
2.2.7 SortedSet 类型
-
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList) 加hash表。SortedSet具备下列特性:
-
可排序
-
元素不重复
-
查询速度快
-
-
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
-
SortedSet常用命令有:
- ZADD key score member:添加一个或多个元素到sorted set ,如果已经存在则更新其score值
- ZREM key member:删除sorted set中的一个指定元素
- ZSCORE key member :获取sorted set中的指定元素的score值
- ZRANK key member:获取sorted set中的指定元素的排名
- ZCARD key:获取sorted set中的元素个数
- ZCOUNT key min max:统计score值在给定范围内的所有元素的个数
- ZINCRBY key increment member:让sorted set中的指定元素自增,步长为指定的increment值
- ZRANGE key min max:按照score排序后,获取指定排名范围内的元素
- ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素
- ZDIFF、ZINTER、ZUNION:求差集、交集、并集
-
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可
三、redis的java客户端
3.1 客户端对比
Jedis | 以Redis命令作为方法名称,学习成本低,简单实用。但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用 |
---|---|
lettuce | Lettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。 |
Redisson | Redisson是一个基于Redi s实现的分布式、可伸缩的Java数据结构集合。包含了诸如Map、Queue、Lock、Semaphore、AtomicLong等强大功能 |
3.2 Jedis快速入门
首先我们引入依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
//导入junit-jupiter去测试
写测试类:
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUp(){
//建立连接
jedis = new Jedis("192.168.111.112", 6379);
//设置密码
jedis.auth("123456");
//选择库
jedis.select(0);
}
//这个Test类注意是org.junit.jupiter.api.Test;
@Test
public void testString(){
String result = jedis.set("name", "tom");
System.out.println(result);
System.out.println(jedis.get("name"));
}
@AfterEach
void tearDown(){
if (jedis != null){
jedis.close();
}
}
}
3.2.1 jedis-连接池
- Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此推荐大家使用Jedis连接池代替Jedis的直连方式
//工具类,使用即可
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
//配置连接池
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大连接
jedisPoolConfig.setMaxTotal(8);
//最大空闲连接
jedisPoolConfig.setMaxIdle(8);
//最小空闲连接
jedisPoolConfig.setMinIdle(0);
//等待时长
jedisPoolConfig.setMaxWait(Duration.ofMillis(1000));
//创建连接池对象
jedisPool = new JedisPool(jedisPoolConfig, "192.168.111.112", 6379, 1000, "123456");
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
3.3 Spring Data Redis快速入门
-
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis
-
官网地址:
- 提供了对不同Redis客户端的整合(Lettuce和Jedis )
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
-
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作String类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作Hash类型数据 |
redisTemplate.opsForList() | ListOperations | 操作List类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作Set类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作SortedSet类型数据 |
redisTemplate | 通用的命令 |
- SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:
<!--首先创建springboot项目,然后引入依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- yml配置
spring:
redis :
host: 192.168.111.112
port: 6379
password: 123456
lettuce : # spring默认使用lettuce连接池,如果要使用jedis连接池,就要自己导入jedis连接池依赖
pool :
max-active: 8 #最大连接
max-idle: 8 #最大空闲连接
min-idle: 0 #最小空闲连接
max-wait: 100 #连接等待时间
- 然后就可以使用redisTemplate了
@Autowired
private RedisTemplate redisTemplate;
//后面使用就不多写了
-
但是我们发现一个问题,就是使用
redisTemplate.opsForValue().set("name","tom")
后,去redis-cli上面去get name
,发现并没有这个name,或者这个name并没有被覆盖。输入keys *
查看,增加了一个key(看不懂的key),这是什么原因呢?- 我们跟进RedisTemplate源码,发现有以下序列化器
- 在以下方法内部初始化序列化器:
-
可以看到
new JdkSerializationRedisSerializer(...)
这个jdk序列化器作为默认的序列化器,所以我们存入的都是序列化后的key和value,所以我们get不到,那么怎么解决这个问题呢 -
可以看到我们只想让value序列化,key不要序列化,我们可以自定义序列化方式,如下:
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//创建template
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//key和hashKey采用string序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
//value和hashValue采用JSON序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
return redisTemplate;
}
- 我们还要导入Jackson依赖才能使用:
<!--如果有springmvc的依赖,里面自带就不需要引入这个-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
- 尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,存入键值
key-User对象
后值的内容如下:
{
"@class":"com.example.redis.pojo.User",
"name": "tom",
"age": 20
}
-
为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中(就是第一行的数据),存入Redis,会带来额外的内存开销。
-
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
-
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程,存入User对象,我们就先序列化,取出的都是String,我们就反序列化。
jackson.core
jackson-databind
- 尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,存入键值`key-User对象`后值的内容如下:
```json
{
"@class":"com.example.redis.pojo.User",
"name": "tom",
"age": 20
}
-
为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中(就是第一行的数据),存入Redis,会带来额外的内存开销。
-
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
-
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程,存入User对象,我们就先序列化,取出的都是String,我们就反序列化。