Redis 学习笔记
Redis诞生于2009年,作为一个基于内存的键值型NoSQL数据库。其有如下特点
- 键值(key-value)型,value支持多种不同数据结构,
- 单线程,每个命令具有原子性。不存在很多并发带来的问题。但是此单线程只是指代命令是但线程执行的,其他模块还有各自的线程。6.0版本中引入了多线程,但指代的是 IO多线程,如:网络数据的读写和协议解析时多线程。
- 低延迟、速度快(基于内存、IO多路复用、良好的编码)
- 支持数据持久化
- 支持主从集群、分片集群
- 支持多语言客户端
一、Redis 的安装
1.1 Redis 安装
下方提供 Redis 各个版本的下载页面,我这里下载的是 3.2.100 版本。
https://github.com/microsoftarchive/redis/releases
将下载包 解压到本地目录,然后在 redis目录下进行 cmd ,输入不同的命令进行不同的安装方式:
-
临时服务安装
如果你仅仅是用作学习使用,可以选择此安装方式。在 redis目录下 使用cmd 执行以下命令:
redis-server.exe redis.windows.conf
该命令会创建 Redis 临时服务,生成的信息表明了 redis 在本机的 6379 端口提供服务。该种方式,不能关闭此 cmd 窗口,如果关闭则会停止 Redis 服务。
保持 Redis 服务窗口开启状态,双击 redis目录下的 redis-cli.exe 即可使用 命令行操控 redis 。比如这里 使用 set 命令,存储了一个键值对 uid:1,然后通过 get 将键 uid 对应的值取出。
-
默认服务安装
这种方式不用像临时安装方式一样,每次去打开 redis 临时服务,而且像正常服务一样开机自启。进入 Redis 目录下,通过cmd输入
redis-server.exe --service-install redis.windows.conf --loglevel verbose
通过命令行可以发现,我们已经将redis作为服务安装好了。但是你可能不能在window的服务列表中找到,redis服务必须通过命令行启动、暂停和卸载
-
启动服务:
redis-server.exe --service-start
-
暂停服务
redis-server.exe --service-stop
-
卸载服务
redis-server.exe --service-uninstall
-
-
自定义服务安装
自定义服务安装,就是将服务重命名。进入 Redis 安装包下,输入
redis-server.exe --service-install redis.windows.conf --Service-name RedisServer1 --loglevel verbose
这里起的名字是 RedisServer1 。与默认安装一样,不同的是在启动、暂停、卸载服务时 需要加上自定义的 Redis 服务名
redis-server.exe --service-start --Service-name RedisServer1
redis-server.exe --service-stop --Service-name RedisServer1
redis-server.exe --service-uninstall --Service-name RedisServer1
-
主从服务安装
即像一般的数据库的主从库一样,redis也可以配置主从库。配置的方法很简单,就是通过自定义服务器安装方式安装两个服务。
修改两个服务里 redis.windows.conf 文件:
主服务器(RedisServer1):保持其 port 6379
从服务器(RedisServer2):修改
port 6380 slaveof 127.0.0.1 6379
修改配置文件后,依次启动服务。然后可以在 双击主服务文件夹下的 redis-cli,去执行一个添加键值操作。
set name eric // 插入键对值 name:eric get name // 取出键 name 对应的值
双击执行 从服务器文件夹下的 redis-cli,去取出键name 对应的值,你就发现可以取到。
在 Window 上 直接删除服务的方法:
使用管理员权限 打开 cmd ,然后输入
sc delete 服务名
1.2 Redis 配置
redis.config 常见配置:
-
bind 0.0.0.0
监听的地址默认是127.0.0.1,这使得只能本地访问。如果修改成 0.0.0.0 则可以在任意 IP 地址访问。
-
daemonize yes
守护进程,修改为 yes 之后,即可后台运行
-
requirepass 111111
密码,设置后访问 Redis 必须输入密码
-
port 6379
监听的端口,默认就是 6379
-
dir .
工作目录,默认是当前目录,也就是运行 redis-server 时的命令,日志、持久化等文件都会保存在这个目录
-
databases 1
数据库数量,设置为1,代表只使用1个库,默认有16个库,编号 0-15
-
maxmemory 512mb
设置 redis 能够使用的最大内存
-
logfile “redis.log”
日志文件,默认为空,不记录日志,可以指定日志文件名。
启动时,指定配置文件
redis-server redis.conf
二、Redis 的基础篇
2.1 Redis命令
2.1.1 通用命令
Redis通用指令是不分数据类型的,都可以使用的指令,常见的有:
- KEYS:查看符合模板的所有 key,不建议在生产环境设备上使用
- DEL:删除一个指定的 key
- EXISTS:判断 key 是否存在
- EXPIRE:给一个 key 设置有效期,有效期到期时该 key 自动删除。可 通过 TTL KeyName,查看 key 的剩余有效期
通过 help [command] 可以查看一个命令的具体用法、
使用 redis.cli.exe 打开 redis的命令行,实操:
2.1.2 String类型
String 类型,也就是字符串类型,是 Redis 中最简单的存储类型。其 value 是字符串,不过根据字符串的格式不同,又可以分为3类:
- String:普通字符串
- int:整数类型,可以做自增、自减操作
- float:浮点类型,可以做自增、自减操作,但是必须指定 增减的 值。
不管何种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过 512M
String的常见命令有:
- SET:添加或者修改 已经存在的一个 String 类型的键值对
- GET:根据 key 获取 String 类型的 value
- MSET:批量添加多个 String 类型的键值对
- MSET:根据多个 key 获取多个 String 类型的 value
- INCR:让一个整型的 key 自增 1
- INCRBY:让一个整型的 key 自增并指定步长,例如:INCRBY num 2,即可让 key = num 的值,自增2
- SETNX:添加一个 String 类型的键值对,前提是 这个 key 不存在,否则不执行
- SETEX:添加一个String 类型的键值对,并指定有效期
2.1.3 Key的层级格式
Redis没有类似 MySQL 中的 Table 的概念,我们该如何区分不同类型的 key 呢?一般采用将 key 名称进行 分层设计。例如:学生的key,key 以 studen_ 开头。
Redis 的 key 允许有多个单词组成层级结构,多个单词之间用 “:” 隔开,格式如下:
项目名:业务名:类型:id
当然这种格式是可以自己定义的,有些公司是使用 ”__“线间隔。
如果存储对象是 Java对象,则可将对象转化为 JSON 字符串当作value存储下来。
2.1.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 的值
HMSET student_1 name liming sex 男 //为key=student_1 的字段 添加属性 name、sex 值分别为 liming、男
-
HGETALL:获取一个 hash 类型的key中所有的 field和value
-
HKEYS:获取一个 hash 类型的key中所有的 field
-
HVALS:获取一个hash 类型的key中所有的 value
-
HINCRBY:让一个hash类型 key 的字段值自增,并指定步长
-
HSETNX:添加一个 hash 类型的 key 的 field值,前提时 这个 field 不存在,否则不执行
2.1.4 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
2.1.5 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.1.6 SortedSet类型
Redis 的 SortedSet 是一个可排序的 set 集合,与 Java 中的 TreeSet 有些类似,但底层数据结构却差别很大。SortedSet 中的每个元素都带有一个 score 属性,可以基于 score 属性对 元素排序,底层的实现是一个跳表(SkipList)加hash表。
SortedSet具备下列特性:
- 可排序
- 元素不重复
- 查询速度快
因为 SortedSet 的可排序特性,经常被用来实现排行榜这样的功能。
SortedSet的常见命令有:
- ZADD key score member:添加一个或多个元素到 SortedSet,如果已经存在则更新其 score 值
- ZREM key member:删除 SortedSet中的一个指定元素
- ZSCORE key member:获取 SortedSet 中的指定元素的 score 值
- ZRANK key member:获取 SortedSet 中的指定元素的排名
- ZCAED key:获取 SortedSet 中的元素个数
- ZCOUNT key min max:统计 score 值在给定范围内的所有元素的个数
- ZINCRBY key increment member:让 SortedSet中的指定元素自增,步长为指定的increment值
- ZRANGE key min max:按照 score 排序后,获取指定 score 范围内的元素
- ZRANGEBYSCORE key min max:按照 score 排序后,获取指定 score 范围内的元素
- ZDIFF、ZINTER、ZUNION:求差集、交集、并集
注意:所有的排名默认都是 升序,如果要降序则在命令的Z后面添加REV即可
2.2 Redis客户端
Redis的客户端主要有
- Jedis:以Redis命令作为方法名称,学习成本低,简单实用。但是Jedis实例是线程不安全的,多线程情况下需要基于连接池实用
- Lettuce:基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。
- Redisson:是一个基于Redis实现的分布式、可伸缩的 Java 数据结构集合。包含了诸如 Map、Queue、Lock、Semaphore、AtomicLong等强大功能。
而 SpringData Redis 集成了 Jedis、Lettuce
2.2.1 Jedis 客户端
https://github.com/kongxiaoran/redisDemo
可以下拉该项目的 Jedis 分支,该分支已经 实现了 springboot 整合 jedis 。可以下拉看看
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用 Jedis 连接池代替 Jedis 的直连方式。
2.2.2 SpringDataRedis
SpringData 是 Spring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做 SpringDataRedis,官网地址:
- 提供了对不同 Redis 客户端的整合(Lettuce、Jedis)
- 提供了 RedisTemplate 统一 API 来操作
- 支持 Redis 的发布订阅模型
- 支持 Redis 哨兵和 Redis 集群
- 支持基于 Lettuce 的响应式编程
- 支持基于 JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于 Redistribution的 JDK Collection实现
2.3 SpringDataRedis 客户端使用
SpringDataRedis 提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作。并且将不同数据类型的操作API 封装到了不同类型中:
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作 String 类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() | ListIOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate | 通用命令 |
https://github.com/kongxiaoran/redisDemo
该项目的 master 分支,使用 SpringBoot 整合了 SpringDataRedis,可以自行下拉运行。
2.3.1 SpringDataRedis 的默认序列化
RedisTemplate 可以接收任意 Object 作为值 写入 Redis,只不过写入 前会把 Object 序列化为字节形式,默认是采用 JDK 序列化。
redisTemplate.opsForValue().set("springboot","你好呀,springboot");
String springboot = (String) redisTemplate.opsForValue().get("springboot");
System.out.println(springboot);
得到的结果是这样的:
缺点很明显:
- 可读性差
- 内存占用较大
这是因为什么呢?查看 RedisTemplate 类,可以知道 当没有特别配置 key、value、hashKey 的 序列化策略时,
RedisTemplate 会选择使用 JDK序列化器(JdkSerializationRedisSerializer),而此序列化器是不是适合字符串的序列化的。所以如果你的 key 通常是用 字符串格式,那么可以考虑 在序列化key时,采用其他序列化器。比如:String
2.3.2 SpringDataRedis 提供的序列化器
查看 RedisSerializer 的实现,可以看到有 7 种序列化器:
-
ByteArrayRedisSerializer:字节数组序列化
-
GenericJackson2JsonRedisSerializer:同 FastJsonRedisSerializer 类似,而 FastJsonRedisSerializer 是由阿里巴巴FastJson包提供。具有:1. 速度快 2. 兼容性强 3. 占用内存小
- 底层使用Jackson进行序列化并存入Redis。对于普通类型(如数值类型,字符
- 存入对象时由于没有存入类信息,则无法反序列化。
-
GenericToStringSerializer:同StringRedisSerializer一样,但它可以将任何对象泛化为字符串并序列化。
注意事项:GenericToStringSerializer需要调用者给传一个对象到字符串互转的Converter,使用起来其比较麻烦,所以不太推荐使用。
-
Jackson2JsonRedisSerializer:将对象序列化为json字符串
- ·优点:速度快、序列化后的字符串短小精悍、不需要实现 Serializable
- 缺点:必须要提供要序列化对象的类型信息(.class对象)
-
JdkSerializationRedisSerializer:使用Java自带的序列化机制将对象序列化为一个字符串。
- 优点在于:通用性强、反序列化时不需要提供类型信息。、
- 缺点在于:序列化速度慢、序列化内存占用大、序列化对象必须实现 Serializable 接口、可读性差
-
OxmSerializer:将对象序列化为xml字符串。以 xml 格式存储(但还是String类型),解析起来比较复杂,且占用空间大
-
StringRedisSerializer:StringRedisTemplate默认的序列化器。
- 优点:可读性强、不需要转换
- 缺点:只能对字符串序列化,不能对 对象 序列化
2.3.3 自定义序列化器
所以我们可以针对自己的需要,自定义 RedisTemplate,来实现对不同 key、value 使用不同的序列化器
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException{
// 创建 Template
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
// 设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置序列化工具
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
// key 和 hashKey 采用 String序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
// value 和 hashValue 采用 JOSN序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
2.3.4 使用自定义序列化器存储对象
@Test
public void testEmployee(){
Employee employee = new Employee(3,"羽","28235x02x7",1,1);
// 写入数据
redisTemplate.opsForValue().set("user_3",employee);
// 获取数据
Employee getEmployee = (Employee) redisTemplate.opsForValue().get("user_3");
System.out.println(getEmployee.toString());
}
由图可以知道,该序列化器在序列化对象,也会将 对象的字节码名称写入。这样在我们反序列化时知道对象的类型,从而反序列化成对应对象。
2.3.5 使用StringRedisTemplate存储JSON对象
但是这一个存在一个问题,JSON序列化器将类的class类型写入了 JSON结果中,存入了 Redis ,会带来额外的内存开销。为了节省内存空间,我们并不会使用 JSON 序列化器来处理 value,而是统一使用 String 序列化器,要求只能存储 String 类型的 key 和 value。当需要存储 Java 对象时,手动完成对象的序列化和反序列化。
所以还是建议手动完成对象的序列化:
@Test
public void testEmployeeStringRedisTemplate(){
Employee employee = new Employee(3,"羽","2823x302x7",1,1);
// 写入数据 (这里使用的时 fastJson2进行序列化)
stringRedisTemplate.opsForValue().set("user_4", JSON.toJSONString(employee));
// 获取数据
Employee getEmployee = JSON.parseObject(stringRedisTemplate.opsForValue().get("user_4"), Employee.class);
System.out.println(getEmployee.toString());
}
2.3.6 RedisTemplate 操作 Hash 类型
@Test
public void testHash(){
stringRedisTemplate.opsForHash().put("xiucheng","user1","凌霄");
stringRedisTemplate.opsForHash().put("xiucheng","user2","羽");
Map<Object, Object> xiucheng = stringRedisTemplate.opsForHash().entries("xiucheng");
System.out.println(xiucheng.toString());
}