文章目录
Redis
前言
关系型数据库:Sql Server MySql Oracle…
关系:表与表之间的联系
传统的关系型数据库存储在硬盘上,关系是建立在业务的基础上的。
关系型数据库存在的问题:
- 关系型数据库存储在硬盘上,磁盘的IO性能低下,不能满足高频访问及实效要求高的应用,特别是海
量用户高并发访问的情况
- 关系型数据库表关系复杂,查询效率低,不易扩展 (表连接查询不能超过7张表)
缓存:(高速缓存)可以高速访问的,用于临时存储的数据存储区
缓存使用场合
缓存一般用于在较短时间段对相同数据频繁读取的场合,将读取频度较高的数据放入缓存,直接从缓存中取出数据以提高效率
NOSQL数据库
NOSQL(Not only SQL),即"不仅仅是SQL",泛指非关系型数据库。
NOSQL不依赖于业务逻辑方式存储,而以简单的"key-value"模式存储,大大增加了数据库的扩展能力
-
不遵循SQL标准
-
不支持ACID(支持事务但是不支持事务的四大特性<原子性,隔离性,一致性,持久性>)
-
远超SQL性能
NOSQL使用场景
- 数据的高并发读写
- 海量数据读写
- 对数据的高可扩展性
NOSQL数据库
-
Redis:数据存储在内存中,支持持久化,以key-value模式存储数据,支持多种数据类型,一般作为缓存数据辅助持久化的数据化
-
MongoDB:高性能,开源的,文档型数6据库,存储在内存中,也可以存储在硬盘中,以Key-value的模式存储数据,value是json的格式存储,提供了丰富的查询功能
-
Hbase:HBase是Hadoop项目中的数据库,它主要用于对大量数据进行随机,事实的读写场景,使用列式存储的数据模式存储数据
1 Redis介绍
Redis(Remote Diconary Server)是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言API。
Redis本身是一个内存数据库,在应用中可以充当缓存,提高系统数据查询性能的非关系型数据库,统称:NOSQL。
1.1 Redis的特征
- 数据间没有必然的联系
- 内部采用单线程多路IO复用技术
- 高性能。(官方测试:50个并发执行十万次请求,读的速度每秒十一万次,写的速度每秒八万次)
- 多数据类型支持;
数据类型 | 存储的值 | 对存储的值的操作 | 使用场景 |
string | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作; | 把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。例如:token |
hash | 包含键值对的无序散列表 | 包含方法有添加、获取、删除单个元素 | 缓存 : 能直观,相比string更节省空间的维护缓存信息,如用户信息,视频信息等 |
list | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素 | 消息队列,任务队列,如秒杀,抢购,抢票等 |
set | 包含字符串的无序集合 | 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 | 标签(tag) :给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。 点赞,或点踩,收藏等,可以放到set中实现 |
z_set | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射; 元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 |
排行榜 :有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行 |
- 注:Redis所有数据都是根据key和value来保存的,key永远都是字符串
- 持久化支持,可以进行数据灾难恢复
1.2 Redis应用场景
- 热点数据的加速查询,如热点商品,热点新闻,热点资讯等高访问量信息
- 任务队列,如秒杀,抢购,抢票等
- 即时信息查询,如排行榜,站点访问统计,在线人数统计,公交到站信息,设备信号等
- 时效性信息控制,如限时验证码,限时投票等。
- 分布式数据共享,如分布式集群架构中的session分离
- 消息队列
- 分布式锁
1.3 Redis的安装
- 下载地址(Windows版)
- 下载zip版本或者安装版(建议zip版) ,解压
- 配置环境变量
- 打开一个 cmd 窗口, 运行
redis-server
命令,启动redis服务端
- 另打开一个cmd窗口,运行
redis-cli
命令,进入客户端
- 安装成功。
1.4 Redis不同版本的线程问题
Redis不同版本中所使用的线程处理方式不同,以下就三个版本进行简要说明
-
Redis4以前的版本使用单线程多路IO复用技术,也就是说Redis 4之前的版本使用的是单线程
-
Redis4.0开始支持多线程,但仅仅是针对删除操作使用多线程处理,也就是在Redis4.0版本中引入
的惰性删除(异步删除)的方式,将删除操作交给后台线程处理,以解决主线程卡顿的问题
-
Redis6.0的版本才开始真正的使用多线程进行I/O操作,但Redis的命令依旧是主线程串行执行的,
Redis6.0以后的版本虽然支持多线程,但默认情况下是禁用的,需要开发者在配置文件中开启并指
定所使用的线程数量
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O # threads, if you have a 8 cores, try to use 6 threads. In order to # enable I/O threads use the following configuration directive: # # io-threads 4 设置线程的数量 # # Setting io-threads to 1 will just use the main thread as usual. # When I/O threads are enabled, we only use threads for writes, that is # to thread the write(2) syscall and transfer the client buffers to the # socket. However it is also possible to enable threading of reads and # protocol parsing using the following configuration directive, by setting # it to yes: # # io-threads-do-reads no yes为启动多线程
2 Redis常用命令
传统的关系型数据库是使用表来存储数据,在Redis中存储数据使用类似于JavaMap集合的方式存储,使 用key-value存储,key不允许重复,值可以重复
- 在Redis中存储的数据都依赖于传统数据库(如MySQL/Oracle等)
- 对Redis内存数据库的操作不在使用SQL语句,在Redis中主要使用各种命令
具体命令 | 功能 |
---|---|
select index |
切换数据库(默认15个数据库,编号为0-15) |
clear |
清屏命令 |
set key value |
添加一个数据value对应数据类型为string |
get key |
根据key获得value |
lpush key value.... |
添加一个数据value对应数据类型为list |
lrange key 0 -1 |
遍历集合 |
keys pattern keys * keys j* keys *zhong keys ?ava keys u[se]r |
获取当前数据库中的key 获取当前数据库所有key 获取所有以j开头的key 获取所有以中结尾的key 获取前面有一个任意字符,并且以ava结尾的key 获得第一个字符为u,第二个字符为,s或者e,结尾字符为r的key ps:"*"匹配任意字符 "?"匹配一个字符 "[]"匹配其中任意字符 |
exists key |
判断key是否存在 |
type key |
判断key的类型 |
del key |
删除指定 key |
fulshdb |
清除库中所有数据 |
fulshall |
清除所有数据命令 |
dbsize |
查看库中key的数量 |
expire key 1 pexpire key 1 |
设置key的过期时间 以秒为单位 以毫秒为单位 |
ttl key |
查看过期剩余时间(-1表示永不过期,-2表示已过期) |
persist key |
设置永不过期 |
rename key newkey |
修改key的名字 |
renamex key newkey |
修改key的名字,如果newkey不存在才修改,否则修改失败 |
sort key `sort list[asc |
desc]<br/> sort list[limit 0 3] [asc |
quit exit ESC键 |
退出命令 |
ping |
检查与服务器连通命令 |
echo message |
控制台打印命令 |
move key db |
数据在不同的库中移动 当前库存在key数据且1号库不存在同名数据 |
info |
或得当前Redis属性值 |
3 Redis的数据类型
redis自身是一个Map,其中所有的数据都是采用 key : value 的形式存储
key:是一个字符串
value:是具有具体类型的数据
Redis中包含5种基本数据类型和3中特殊类型
3.1 string(字符串String)
string是Redis最基本数据类型,String二进制是安全的,redis的string可以存储任何数据,如图片,对象等
-
单个数据,一个存储空间保存一个数据,如果其中存储的数字则可以当数字来用
redis中最大可以存储512M大小的字符串,如果是数字,取值范围为java总的lang类型取值范围
命令 | 规则 | 功能 |
set key value | 如果key存在替换原有value,如果不存在则创建新的数据 | 存储单个数据 |
mset key1 value1 key2 value2 ... | 同时存储多个数据 | |
get key | 获取数据时返回值为nil表示空,没有数据 | 获取一个数据 |
mget key1 key2 ... | 同时获取多个数据 | |
strlen | 获得字符串长度 | |
append key value | 如果数据存在追加数据,如果不存在新建数据 | 向value中增加新数据 |
getrange key startIndex endIndex | startIndex:开始截取,索引从0开始 endIndex:终止截取 |
截取字符串 |
setrang key offset value | offset:从下标为多少的字符开始替换 | 字符串某字符替换 |
setnx key value | 如果key存在存值失败,如果不存在则存值 | 存储一个数据 |
incr key | 如果字符串中存储的是数字则可以进行数值运算,但其数据类型还是string 必须确保值为数字格式,否则会报错,在redis中incr具有原子性. |
对key的值自加1,等同于i++ |
incrby key number | 对key的值加上指定的数值(负值进行减法运算),等同于i=i+n | |
incrbyfloat key number | 对key的值加上或减去浮点数 | |
decr key | 对key的值自减1,等同于i++ | |
incrby key number | 对可以的值减去指定的数值 | |
setex key seconds value | 以秒为单位设置存活时间 | |
psetex key millseconds value | 以毫秒为单位设置存活时间 | |
set key value [ex seconds][px milliseconds] | 使用set方式设置key的存活期,如果使用nx选项则表示setnx+过期时间 | |
set key value [ex seconds][px milliseconds][nx] | ||
incrby key number | 对可以的值减去指定的数值 | |
expire key seconds | 单独设置过期时间 | 以秒为单位 |
set key value [ex seconds][px milliseconds][nx] | 以毫秒为单位 |
存储对象
我们可以使用字符串存储一个对象,但在使用时不方便,所以存储对象一般不使用字符串
string中key的命名规范
redis中常用于存储表中的一条数据(对象),所以key命名规则体现唯一原则:
表名:主键名:主键值:字段名
set user:user_id:1001{username:admin,password}
3.2 hash (哈希HashMap)
hash类型主要用于存储对象,以key field value 的形式存储
hash中的value只能为string不能在value中嵌套hash或list等
- 常用指令
命令 | 规则 | 功能 |
hset key field value | 设置数据 | 设置单个数据 |
hmset key field value | 同时设置多个数据 | |
hget key | 获取数据 | 获得key对应的整体数据 |
hget key filed | 获得hash单个字段数据 | |
hmget key1 filed1 filed2 ... | 同时获取多个数据 | |
hdel key filed | 删除数据 | 删除指定字段 |
hlen key | 获取hash中的字段数量 | |
hexists key filed | 存在返回1,不存在返回0 | 获取hash中是否包含指定字段 |
hexists key filed | 获取hash中的所有字段名和字段值 | 获取所有字段值 |
hkeys key | 获取所有字段名 | |
hincrby key filed number | 设置指定字段数值增加或减少 | 增加或减少整数(负数为减少) |
hincrbyfloat key filed number | 增加或减少浮点数(负数为减少) |
3.3 list (列表LinkedList)
在Redis中可以把list用作栈、队列、阻塞队列
list中允许存放重复数据
list中存储的数据有序(指进入顺序<分左右>)
-
常用指令
① 向列表添加数据
- 左边开始添加:
lpush key value
- 右边开始添加:
rpush key value
② 从list中获取元素
- 根据起始和结束下标获取此范围内的列表元素:
lrange key startIndex endIndex
③ 从list中弹出元素
- 获得列表头部的第一个元素并从列表中移除:
lpop key
- 获得列表尾部的第一个元素并从列表中移除:
rpop key
④ 通过下标获取list中的某个元素
lindex key index
⑤ 获得列表中元素的数量(长度)
llen key
⑥ 根据元素值从列表中移除指定数量的元素
lrem key count value
⑦ 截取子list(截断子元素)
- 截断后list中剩余的数据为截断数据 :
ltrim key start stop
⑧ 将原列表中最后一个元素移到新列表中
source:列表
destination:新列表
rpoplpush source destination
⑨ 根据下标重置list中的一个元素(根据下标修改list中的一个元素)
lset key index value
⑩ 向某个元素前或后插入一个元素
linsert key BEFORE|AFTER pivot value
- 左边开始添加:
3.4 set (集合HashSet)
不保证顺序,集合不能存放重复元素
-
常用指令
① 向set集合中添加一个元素
sadd key value1 value2...
② 查看set集合中所有元素
smembers key
③判断一个元素是否存在于set集合中
0表示不存在 1表示存在
sismembers key value
④获取set集合元素个数
scard key
⑤ 移除一个元素
srem key value
⑥ 随机抽取一个元素
- 随机抽取一个或者多个元素:
srandmember key [count]
⑦ 随机删除元素
- 随机删除一个或者多个元素:
spop key [count]
⑧将一个特定的值,移动到另一个set集合中
destination:另外一个集合
smove source destination member
⑨ 集合操作
- 差集:
sdiff key [key ...]
- 交集:
sinter key [key...]
- 并集:
sunion key [key..]
- 随机抽取一个或者多个元素:
3.5 sorted_set又称z_set(有序集合TreeSet)
该集合是对set集合的改造,在set集合中加入了一个字段值,用于存储排序规则数据,该数据只负责排序不起其他作用
- zset存储结构
-
常用指令
① 向z_set集合中添加元素
zadd key score1 value1 score2 value2...
② 获取z_set集合中的元素
- 从小到大的顺序显示元素信息,withscores显示排序规则字段:
zrange key start stop [WITHSCORES]
- 从大到小的顺序显示元素信息,withscores显示排序规则字段:
zrevrangekey start end [WITHSCORES]
③ 按条件获取z_set中的元素
- 获取min-max之间的元素可用limit限制:
zrevrangebyscore key min max [limit] [withscores]
- 降序:
zrevrangebyscore key max min [limit] [withscores]
④ 增加或减少z_set中元素score
zincrby key increment value
⑤ 删除z_set中的元素
- 根据元素名删除一个或多个元素 :
zrem key member1 member2...
- 删除指定下标范围的元素:
zremrangebyrank key start stop
- 根据socres指定范围删除元素 :
zremrangebyscore key min max
⑥ 获得元素在集合中的排名
- 升序排名:
zrank key member
- 降序排名:
zrevrank key member
⑦ 获得集合中元素数量
- 获得集合中元素数量:
zcard key
- 获得指定范围的元素数量:
zcard key min max
⑧ 集合交集和并集
-
集合交集操作,将多个集合的交集存 入到newset集合中,相交集合的数量个setcount指定的数量要一致,默认对交集数据进行求和运算, 也可以获得最大值或最小值等运算
zinterstore newset setcount set1 set2 ...
-
集合并集操作:
zunionstore newset setcount set1 set2 ...
- 从小到大的顺序显示元素信息,withscores显示排序规则字段:
3.6 特殊类型
1.geospatial :地理位置
城市经纬度查询: 经纬度查询
- 两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
- m 为米。km 为千米。mi 为英里。ft 为英尺。
2.hyperloglog : 基数
数学层面上可以说是:两个数据集中不重复的元素
但是再Redis中,可能会有一定的误差性。 官方给出的误差率是0.81%。
Hyperloglog的优点: 占用的内存是固定的,2^64个元素,相当于只需要12kb的内存即可。效率极高!
3.bitmap 位图
都是操作二进制位来进行记录,只有0 和 1 两个状态
4 Jedis
4.1 Jedis简介
jedis是Java操作redis的一个工具,等同于jdbc,可用使用jedis实现java操作redis的目的。
Spring中操作Redis的技术是Spring Data Redis
4.2 Jedis操作Redis
- 引入Jedis的依赖包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
- 连接Redis
/**
* 创建jedis对象并指定服务器ip地址及端口号
* 参数1:Redis服务器地址
* 参数2:Redis服务器端口号
*/
Jedis jedis=new Jedis("127.0.0.1",6379);
- 操作Redis
jedis.set("num1","0");
String value = jedis.get("num1");//根据key获得对应的value
- 关闭连接
//关闭连接
jedis.close();
-
Jedis操作Sting
-
向redis添加数据
//添加一条数据 jedis.set("num1","0"); //添加多条数据 jedis.mset("num3","2","num4","3");
-
获得redis中元素
①根据key获得对应的value
//获取一个数据 String value = jedis.get("num1"); System.out.println(value); //获取多个数据 List<String> mget = jedis.mget("num1", "num2", "num3"); System.out.println(mget);
②获取redis所有元素的key
//获取redis所有元素的key Set<String> keys = jedis.keys("*"); System.out.println(keys);
-
设置时效
jedis.expire("num1",1000); //获得剩余时效 long num11 = jedis.ttl("num1"); System.out.println(num11);
-
-
Jedis操作List
public static void jedisForList(){ /** * 创建jedis对象并指定服务器ip地址及端口号 */ Jedis jedis = new Jedis("127.0.0.1", 6379); //向Redis中添加list数据 jedis.lpush("list","a","b","c"); jedis.rpush("list","x"); //从Redis中将key为list1的数据获取,返回java.util.List集合 List<String> list1 = jedis.lrange("list1", 0, -1); //遍历List集合 for(String str : list1){ System.out.println(str); } //关闭Redis的连接 jedis.close(); }
-
Jedis操作set集合数据
public static void jedisForSet(){ /** * 创建jedis对象并指定服务器ip地址及端口号 */ Jedis jedis = new Jedis("127.0.0.1", 6379); //向Redis中添加set集合数据 jedis.sadd("set1","v1","v2","v3"); //从Redis中获取key为set1的数据,返回值自动封装到java.util.Set集合中 Set<String> set1 = jedis.smembers("set1"); for(String set : set1){ System.out.println(set); } //关闭与Redis的连接 jedis.close(); }
-
Jedis操作Map
public static void jedisForMap(){ /** * 创建jedis对象并指定服务器ip地址及端口号 */ Jedis jedis = new Jedis("127.0.0.1", 6379); //向hash中添加数据 Map valMap = new HashMap<String,Object>(); valMap.put("id","001"); valMap.put("name","小明"); valMap.put("age","20"); /** * 参数1:key * 参数2:value对应的是map集合 */ jedis.hmset("user",valMap); Map<String,String> user = jedis.hgetAll("user"); System.out.println(user); //关闭与Redis的连接 jedis.close(); }
5 Redis基本配置
-
redis启动时会读取相应配置文件,我们可以通过修改redis的配置文件对redis进行配置,同时也
可以设置多个不同的配置文件,启动多个redis服务,如
redis-6379.conf
,redis-6380.conf
。① Redis服务端启动方式
-
默认启动方式:
redis-server
读取的配置文件为默认的件redis.windows-service.conf -
指定配置文件启动方式:
redis-server (xxx/redis-6380.conf)
在redis目录里:
redis-server redis-6380.conf
配置文件在指定文件夹里(此处为redis目录下的config文件夹):
redis-server config/redis-6381.conf
② Redis客户端启动方式
- 默认启动方式:
redis-cli
访问默认服务器 - 访问指定端口的服务:
redis-cli -p 6380
- 访问指定服务器上特定端口的redis服务:
redis-cli -h 127.0.0.1 -p 6381
基本配置
#服务器绑定的ip地址 bind 127.0.0.1 #Redis服务器绑定的服务器端口号 port 6380 #超时时间 0表示不超时 timeout 0 # redis是否使用守护线程,设置为no表示不使用启动时会在控制台上打印启动信息 # 设置为yes表示使用守护线程,后台启动,不打印启动信息(该配置在windows上不支持) # daemonize no #日志文件,该文件配置后,redis相关的日志信息会输出到指定的配置文件中,便于查看 logfile "E:/Redis/Redis-x64-3.2.100/config/log/redis-6380.log" #Redis服务器数据库的数量 databases 16 # 指定存储到本地的数据库文件时是否对数据进行压缩,默认为yes,采用LZF压缩(一般开启) rdbcompression yes # 设置是否对rdb文件格式进行校验,该校验在写文件和读文件时均会进行,默认为yes(一般开启,如 果设置为no读写性能会提升但存在数据损坏风险) rdbchecksum yes # 当后台保存数据时如果出现错误是否停止保存操作 stop-writes-on-bgsave-error yes
-
6 Redis持久化
-
redis是基于内存的数据库,它是存储在内存上,如果突然断电或遇到其他意外情况把内存中的数
据清除,就会造成redis中数据的丢失,为避免这样的情况出现而操作损失,在redis中提供了持久
化机制。
-
redis持久化指将redis中的数据保存到可以永久存储的存储介质中,在需要时将保存的数据进行恢复。
Redis中提供了两种持久化方案:
(1) RDB方式(快照方式):将当前数据状态以快照的形式进行保存,存储格式简单,关注点在数据
(2) AOF方式(日志方式):将操作数据的过程以日志的方式记录下来,存储格式复杂,关注点在操作
数据的过程。
7.1 RDB实现持久化
RDB方式实现持久化,使用
save命令
可以将当前的redis中的数据存储到磁盘上,存储文件的默认名称为dump.rdb
-
当redis服务启动时,会自动读取rdb文件,将已存储的备份进行恢复
-
save
指令是指示redis立即将数据存储到磁盘上进行备份,但如果存储的数据量过大,而且同时又有其他客户端也要访问redis时就会造成堵塞状态,影响性能(不建议使用),redis提供了后台执行命
令
bgsave
来解决这个问题 -
save
和bgsave
两个命令都会调用rdbSave
函数将当前Redis中的数据存入到rdb文件中,但两者调用的方式不同
save
直接调用rdbSave
,会阻塞Redis主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处 理客户端的任何请求。bgsave
则调用fork函数生成一个子进程,子进程负责调用rdbSave
,并在保存完成之后向主进程发送 信号,通知保存已完成。Redis服务器在bgsave
执行期间仍然可以继续处理客户端的请求。
-
在配置文件里配置rdb的路径和文件名
#指定持久化文件存储路径
dir E:/Redis/Redis-x64-3.2.100/config/data
#指定持久化文件文件名
dbfilename dump-6380.rdb
ps:为什么在配置文件里持久化文件名不在
dir
(持久化文件存储路径)里配置?答:因为持久化方式不止一种
- 手动保存过于繁琐,Redis里面提供了一种"自动保存快照"的方式,当达到存储Redis要求时,Redis就会自动调用
bgsave
指令,自动保存当前数据,我们需要在配置文件里配置自动保存的时间及数据变化量(不配置不会自动保存)
#该配置表示开启Redis的自动备份功能
#配置rdb快照方式自动保存时间及数据变化量
#每隔900s内有一个数据发送改变则自动保存
save 900 1
#每隔300s内有10个数据发送改变则自动保存
save 300 10
#每隔60s内有10000个数据发送改变则自动保存
save 60 10000
PS:
根据使用场景进行设置,频率过高和过低都会使性能受到影响
调用
debug reload
指令重启服务器时会自动保存数据服务器关闭时(
shutdown
指令)会自动保存数据
-
RDB持久化方式的优点
RDB是一个紧凑压缩的二进制文件,存储效率高
RDB内部存储的是Redis在某个时间点的数据快照,非常适用于数据备份,数据整体复制等场景
RDB恢复数据的速度要比AOF快
-
RDB持久化方式的缺点
RDB方式基于快照思想,每次读写全部数据,当数据量巨大时效率低、IO读写性能低下
RDB方式无法实时备份数据,数据丢失的可能性大
bgsave
指令每次执行要创建子进程,内存会产生额外的消耗
7.2 AOF实现持久化
AOF持久化是以独立日志方式记录每次写命令,重启时在重新执行AOF文件中的命令以恢复数据
与RDB相比AOF不是存储所有数据而是存储操作数据的过程,实时性更高
AOF解决了数据持久化的持久性,目前AOF已经是数据持久化的主流方式
-
AOF持久化方式写数据的过程:
- Redis将写命令写入AOF写缓冲区中
- 达到一定程度,将AOF缓冲区中数据一次性写入AOF文件中
-
AOF写数据的三种策略:
-
always(每次写都存入)
每次写都存入,虽然保证了数据存储的实时性,准确性,但是当写入数据过多,每次写入都需要IO操作,就会产生大量IO操作,效率低下(不建议使用)
-
everysec(每秒写入)
每秒将缓冲区中的写操作同步到AOF文件中,数据实时性,准确性,性能较高(建议使用,Redis默认使用方式)
-
no(系统写入)
由操作系统控制每次同步到AOF的周期
-
-
AOF持久化的使用
-
AOF默认在Redis中是禁用的,需要在配置文件中打开,设置为yes,开启后服务器会自动生产一个aof文件,用于备份数据
#配置是否使用AOF存储数据,默认为no appendonly no|yes
-
在配置文件里指定aof文件名
#配置aof文件名 appendfilename "appendonly-6380.aof"
-
在配置文件中配置AOF存储所使用的策略,默认为everysec策略
# 配置AOF存储的写入策略 # appendfsync always appendfsync everysec # appendfsync no
-
-
AOF重写
随着不断向aof写入数据,aof文件会越来越大,且操作中会有大量重复且无效的操作,替换过程,Redis引入了AOF重写机制,将对AOF压缩,将AOF中原有指令进行重组,将无效,多余的指令移除,多条追加指令进行合并,转换为最终数据对应的指令进行记录。以实现压缩aof文件的目的
-
AOF重写的作用
① 降低磁盘占用量,提高磁盘使用率
② 提高持久化效率,降低持久化时间,提高IO性能
③ 提高数据恢复效率
-
AOF重写规则
① 超时数据不再写入文件
② 忽略无效指令,重写时使用进程内的数据直接生成,这样新的aof文件只保存最终数据的写入命令
③ 对同一数据的多条命令进行合并
- 如:
lpush list a
,lpush list b
,lpush list c
转换为l push a,b,c
- 如:
-
AOF重写的使用
① 使用
bgrewriteaof
手动重写127.0.0.1:6380> set name xiaowang OK 127.0.0.1:6380> set age 20 OK 127.0.0.1:6380> set sex 1 OK 127.0.0.1:6380> set num01 10 OK 127.0.0.1:6380> set num02 10 OK 127.0.0.1:6380> delete num01 (error) ERR unknown command 'delete' 127.0.0.1:6380> del num01 (integer) 1 127.0.0.1:6380> set num02 20 OK #查看aof文件 127.0.0.1:6380> bgrewriteaof Background append only file rewriting started 127.0.0.1:6380> #查看重写的aof文件
② 在配置文件里进行配置实现自动重写
Redis中配置好AOF自动重写的触发条件后,当条件满足自动触发,在Redis中有两个触发条件配置,当达到两个条件就会自动触发AOF重写
# AOF自动触发百分比 auto-aof-rewrite-percentage 100 # AOF自动触发最小尺寸 auto-aof-rewrite-min-size 64mb
当Reids中自动触发条件大于触发参考值时,执行AOF重写(参考条件可根据
info
指令查看,Redis中已配置好)# AOF自动触发参考值 aof_current_size:60 #当AOF触发条件的最小尺寸大于该值自动执行AOF重写 aof_base_size:60 #AOF触发基础尺寸 # 当前使用百分比大于或等于自动触发百分比时会自动执行AOF重写 # 公式:aof_current_size-auto-aof-rewrite-min-size/aof_base_size>=autoaof-rewrite-percentage
-
7 Redis中的事务
传统的事务:事务对应一个sql集,其中包含sql语句,这些sql语句要么全部执行,要么不执行,必须符合事务的(ACID)特性
Redis中的事务:不需要符合所谓(ACID)四大特性。
- Redis中事务是为了保证多条执行的指令在执行过程中不被打断
-
开启事务:
multi
指令,该指令用于设置事务的开始位置,该指令后的所有指令都会加入Redis的事务中,形成一个指令队列 -
执行事务:
exec
指令,该指令用于执行事务,表示事务的结束和指令队列的执行,与multi
成对使用 -
取消事务:
discard
指令,该指令必须在multi
之后,在exec
之前,该指令用于取消当前事务
PS:
Redis事务中如果出现指令语法错误,则会自动取消当前事务
在事务执行过程中,即使出现错误,事务也会继续执行
8 Redis中锁的概念
-
watch监控锁
当同一个数据需要改变时,可能会出现多个人同时修改该数据,数据可能发生错乱,为了避免发生数据修改错乱,此时加入watch锁,只允许数据修改一次数据,当一个人修改后其他人则不能改变,其他人修改该数据,则终止事务的执行
-
加入watch锁,执行事务前,如果数据发生变化,则终止事务执行
watch key[key1 key2....]
-
关闭watch锁
unwatch
-
-
分布式锁
当多个线程同时操作一个数据数,可能出现数据错乱,为避免该情况,我们需要将该数据锁起来,当一个线程操作完成,才允许另外一个线程操作该数据,Redis使用分布式锁实现此场景
Redis中没有分布式锁,我们可以使用
setnx
设计一个分布式锁-
添加一个key,该key为分布式锁,我们知道setnx在设置数据时如果数据存在则返回0
-
设置该数据为锁,其他客户端要操作数据前先通过该指令的返回值检测如果返回值为0
则表示当前数据已被锁定不能操作,如果返回值为1表示加锁,然后操作。setnx lock-num 1
-
对加锁的数据使用后要解锁,通过del lock-num移除数据的方式实现解锁过程
del lock-num
-
-
死锁
通过分布式锁的机制可以实现对数据操作的排他性,但数据使用结束后需要解锁(谁加锁,谁解锁),但有时可能会忘记解锁或发送意外无法解锁,就成了死锁。。。
设计分布式锁时不允许出现死锁
在设置分布式锁就需要添加时效,在一定时间后自动解锁
set key value [ex seconds][px milliseconds][nx]
:设置分布式锁
9 Springboot整合Redis
Spring使用Spring Data与Redis进行整合,在SpringData中提供了一个模板类
RedisTemplate
来实现redis的相关操作
9.1 添加相关依赖和设置配置
-
添加相关依赖
- 引入springBoot父项目
<!--引入springBoot父项目 springboot对spring相关版本进行规定 也对spring基础配置进行了约定 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.9</version> </parent>
- 引入spring-boot-start-web
<!-- spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-web</artifactId> <version>2.7.9</version> </dependency>
- 引入spring-boot-start-data-redis
<!-- spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.7.9</version> </dependency>
-
设置配置
server.context: port: 80 spring: redis: host : 127.0.0.1 #配置ip地址 port : 6380 #配置端口号 jedis: #配置连接池 pool: max-active: 10 # 最大活动连接 min-idle: 2 # 最小空闲连接 max-idle: 5 # 最大空闲连接 max-wait: 1000 # 最大等待时间
-
创建Bean
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer userId; private String username; private String password; }
9.2 SpringDataRedis对象的序列化
要将程序中的数据进行外部存储,都必须将数据进行序列化。
数据从外部存储到程序中,也需要将数据进行反序列化
-
SpringDataRedis默认使用JDK方式将程序中的数据进行序列化,要求类需要实现序列化接口
该种方法传到redis中会有富余数据,所以我们使用下列方式设置序列化
private RedisTemplate redisTemplate; @Autowired public ApplicationTest(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; /** * 设置redisTemplate中key和value的序列化 * SpringDateRedis默认使用JDKSerializationRedisSerializer对key和value进行序列化 * GenericJackson2JsonRedisSerializer:将任意类型的数据转化为json串进行序列化到redis中 * Jackson2JsonRedisSerializer<User>(User.class):将指定类型转化json并序列化到redis中 * GenericToStringSerializer<User>(User.class):将任意类型数据转化为字符串 */ //设置key序列化 //StringRedisSerializer:只支持字符串 redisTemplate.setKeySerializer(new StringRedisSerializer()); //设置value序列化 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); //redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class)); //redisTemplate.setValueSerializer(new GenericToStringSerializer<User>(User.class)); }
9.3 SpringBoot整合Redis
-
操作字符串
@Test void operateString() { //获得字符串操作对象 ValueOperations valueOperations = redisTemplate.opsForValue(); // valueOperations.set("user",new User());//添加对象 // valueOperations.set("num",100); // Integer num = (Integer) valueOperations.get("num"); // Long num = valueOperations.increment("num");//incr命令 // Long num = valueOperations.increment("num", 10);//incrby命令 // User user = (User) valueOperations.get("user"); // valueOperations.set("username","admin",100, TimeUnit.SECONDS);//添加字符串并设置过期时间 // valueOperations.set("age",18, Duration.ofSeconds(10)); // Map<String,Object> map=new HashMap<>(); // map.put("username","admin"); // map.put("age",88); // valueOperations.multiSet(map);//mset命令 // ArrayList arrayList=new ArrayList(); // Collections.addAll(arrayList,"username","age"); // List list = valueOperations.multiGet(arrayList);//mget命令 // valueOperations.setIfAbsent("age",18);//setnx命令 // valueOperations.setIfAbsent("age1",18); // System.out.println(user); }
-
操作list
@Test void operateList() { ListOperations listOperations = redisTemplate.opsForList(); // listOperations.leftPush("list","aaa");//lpush // listOperations.leftPush("list","bbb");//lpush // listOperations.rightPush("list","ccc");//rpush // listOperations.leftPushAll("list","ddd","eee","fff"); // listOperations.leftPop("list");//lpop // List list = listOperations.range("list", 0, -1); //lrange key start end // list.forEach(System.out::println ); }
-
操作hash
@Test void operateHash() { HashOperations hashOperations = redisTemplate.opsForHash(); // hashOperations.put("hash","hashKey","value");//hset Map<String,Object> map=new HashMap<>(); map.put("hashKey01","value01"); map.put("hashKey02","value02"); map.put("hashKey03","value03"); map.put("hashKey04","value04"); hashOperations.putAll("hash",map);//hmset // System.out.println(hashOperations.get("hash", "hashKey"));//hget System.out.println(hashOperations.entries("hash"));//hmget }
-
操作set
@Test void operateSet() { SetOperations setOperations = redisTemplate.opsForSet(); // setOperations.add("key1","value1","value2","value1");//sadd // System.out.println(setOperations.isMember("key1", "value"));//sismenmbers setOperations.add("key1",new User()); System.out.println(setOperations.members("key1"));//smembers }
-
操作Z_set
@Test void operateZSet() { ZSetOperations zSetOperations = redisTemplate.opsForZSet(); // zSetOperations.add("scores","小明",90.9);//zadd // zSetOperations.add("scores","花花",88.6); // Set scores = zSetOperations.range("scores", 0, -1);//zrange // Set <ZSetOperations.TypedTuple> scoreSet=new HashSet<>(); // scoreSet.add(ZSetOperations.TypedTuple.of("张三",100d)); // scoreSet.add(ZSetOperations.TypedTuple.of("李四",60.55)); // zSetOperations.add("scores",scoreSet); /** * 根据z_set的key获得value和filed,filed和value被封装在TypedTuple中 */ // Set<ZSetOperations.TypedTuple> scores = zSetOperations.reverseRange("scores", 0, -1);//zrevrange // scores.forEach(System.out::println); // for (ZSetOperations.TypedTuple typedTuple : scores) { // System.out.println(typedTuple.getScore() + ":" + typedTuple.getValue()); // } //获得前三名 // Set<ZSetOperations.TypedTuple> scores = zSetOperations.reverseRangeByScoreWithScores("scores", 0, 100, 0, 3); // for (ZSetOperations.TypedTuple typedTuple:scores){ // System.out.println(typedTuple.getScore()+":"+typedTuple.getValue()); // } // Long rank = zSetOperations.rank("scores", "小明");//获得该值得排名 // System.out.println(rank+1); Long scores = zSetOperations.zCard("scores");//获得元素数量 System.out.println(scores); }
-
学习来自于西安加中实训