Redis基础

redis介绍及安装

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。
redis能做什么?
1. 内存存储,持久化,断电即丢失,所以持久化很重要,redis采用两种机制(RDB和AOF)
2. 效率高,可以用于高速缓存
3. 发布订阅系统
4. 地图信息分析
5. 计数器等,网站浏览量
redis特点/特性
1. 多样的数据类型
2. 持久化
3. 集群
4. 事务等…

Install redis in centos7

Install gcc

linux中的gcc是由GNU推出的一款功能强大的、性能优越的多平台编译器。gcc编译器能将C、C++语言源程序和目标程序编译、连接成可执行文件。
因为redis依赖c++环境 所以我们需要安装c++

yum install gcc-c++

可以通过gcc -v 检查版本

gcc -v

升级版本 因为redis7.* 需要gcc高版本的支持 4个分别执行

yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
#修改使用版本
scl enable devtoolset-9 bash
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile

继续查看gcc版本 升级到9.* 完成安装

下载与安装redis

redis可以在windows和linux下安装使用,但是windows系统版本已经停更很长时间,不建议使用.
通过工具将安装文件放在opt文件夹下:
注意:如果是系统应用如Java我们会安装到/usr/local 下, 如果是用户级的应用,我们就放在opt下
在这里插入图片描述
解压文件夹:
在这里插入图片描述
解压后进入redis文件夹查看文件:
在这里插入图片描述

编译:会将需要的配置帮助我们配置完成

make #注意需要在解压后的redis文件夹中执行

这一步就是编译,大多数的源代码包都经过这一步进行编译(当然有些perl或python编写的软件需要调用perl或python来进行编译)。如果 在 make 过程中出现 error ,就要记下错误代码(注意不仅仅是最后一行),然后可以向开发者提交 bugreport(一般在 INSTALL 里有提交地址)。或者系统少了一些依赖库等,这些需要自己仔细研究错误代码。make 的作用是开始进行源代码编译,以及一些功能的提供,这些功能由他的 Makefile 设置文件提供相关的功能。比如 make install 一般表示进行安装,make uninstall 是卸载,不加参数就是默认的进行源代码编译。make 是 Linux 开发套件里面自动化编译的一个控制程序,他通过借助 Makefile 里面编写的编译规范进行自动化的调用 gcc 、ld 以及运行某些需要的程序进行编译的程序。
安装redis

make install

这条命令来进行安装(当然有些软件需要先运行 make check 或 make test来进行一些测试),这一步一般需要你有 root 权限(因为要向系统写入文件)。
命令拓展 - 软件安装流程

1、解zhi包软件
tar zxf xxxx.tgz
2、配置
cd xxxx
./configure
3、编译
make
4、安装
make install
5、卸载
make uninstall

检查redis环境服务所在的位置
在这里插入图片描述
启动测试

redis-server
redis-server redis-config #后面是配置文件路径

在这里插入图片描述

配置redis

备份文件
在redis配置文件的同级目录创建一个文件夹并且cp一个redis.conf文件到新的文件夹,以后我们使用这个cp的文件进行操作,原生原件不变动。
修改后台启动

vim redis-conf
```![在这里插入图片描述](https://img-blog.csdnimg.cn/3a7e3020df334e8faadbe5c592498ce1.png)daemonize 默认值是no 修改yes 意思是以守护进程方式启动(后台运行)

重新启动redis 并查看测试

```powershell
redis-server /opt/redis-6.0.6/redis.conf

查看进程

ps -aux | grep redis
ps -ef | grep redis

在这里插入图片描述

客户端连接测试

redis-cli -h 127.0.0.1 -p 6379
redis-cli -h 127.0.0.1 -p 6379 --raw #支持中文显示

客户端IO测试

set name 张三
get name
keys *

退出客户端和结束服务

# 退出客户端
exit
# 结束服务方式1 客户端内直接输入 shutdown
shutdown
# 结束服务方式2 客户端外直接输入命令
redis-cli shutdown

防火墙命令拓展

启动防火墙:systemctl start firewalld
关闭防火墙:systemctl stop firewalld
检查防火墙状态:systemctl status firewalld
设置开机启用防火墙:systemctl enable firewalld.service
设置开机禁用防火墙:systemctl disable firewalld.service

关闭防火墙:systemctl stop firewalld
设置开机禁用防火墙:systemctl disable firewalld.service

redis开关

第一种:退出客户端之后

redis-cli shutdown

第二种:直接在客户端内(建议)

127.0.0.1:6379>shutdown

redis入门

关于redis线程问题

redis的单线程的,那么单线程为什么还这么快?
Redis 的数据结构并不全是简单的 Key-Value,还有 list,hash 等复杂的结构。这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在 hash 当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
总之,在单线程的情况下,代码更清晰,处理逻辑更简单,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗,不存在多进程或者多线程导致的切换而消耗 CPU。
单线程多进程的集群方案单线程的威力实际上非常强大,每核效率也非常高。多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。所以单线程、多进程的集群不失为一个时髦的解决方案。
CPU 消耗采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。但是如果 CPU 成为 Redis 瓶颈,或者不想让服务器其他 CPU 核闲置,那怎么办?
以考虑多起几个 Redis 进程,Redis 是 key-value 数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些 key 放在哪个 Redis 进程上就可以了。

redis 基础命令

数据库切换操作

127.0.0.1:6379> PING #检查连接
PONG
127.0.0.1:6379> SELECT 15
OK
127.0.0.1:6379[15]> SELECT 16 #一共16个库 0-15
(error) ERR DB index is out of range
127.0.0.1:6379[15]> SELECT 0 #默认是0 不显示索引
OK

查看当前数据库

127.0.0.1:6379> SET student zhangsan #增加一个数据
OK
127.0.0.1:6379> SET student lisi #相同key增加一个数据 会覆盖掉
OK
127.0.0.1:6379> GET student # 取值
"lisi"
127.0.0.1:6379> KEYS * # 查看所有的key
1) "list11"
2) "student"
3) "user:1:age"
4) "user:1:name"

清除数据库中的数据

flushdb 删除当前库 – 前提进入当前数据库中
flushall 删除所有库

127.0.0.1:6379> SET student zhangsan #在索引0库加信息
OK
127.0.0.1:6379> SET teacher lisi #在索引0库加信息
OK
127.0.0.1:6379> select 5 #切换索引为5的库
OK
127.0.0.1:6379[5]> SET user wangwu #在索引5库加信息
OK
127.0.0.1:6379[5]> KEYS *
1) "user"
127.0.0.1:6379[5]> FLUSHDB #清除当前库
OK
127.0.0.1:6379[5]> keys *
(empty array)
127.0.0.1:6379[5]> SELECT 0
OK
127.0.0.1:6379> KEYS *
1) "teacher"
2) "student"
127.0.0.1:6379> SELECT 5
OK
127.0.0.1:6379[5]> FLUSHALL #清空所有的库信息
OK
127.0.0.1:6379[5]> SELECT 0
OK
127.0.0.1:6379> KEYS *
(empty array)

设置过期时间

127.0.0.1:6379> set student jiazong
OK
127.0.0.1:6379> EXPIRE student 30 #设置过期 秒数
(integer) 1
127.0.0.1:6379> TTL student
(integer) 18
127.0.0.1:6379> TTL student
(integer) 16
127.0.0.1:6379> TTL student #查看过期时间
(integer) 14
127.0.0.1:6379> TTL student
(integer) 7
127.0.0.1:6379> TTL student
(integer) -2
127.0.0.1:6379> get student
(nil)

查看key类型

type key

查看key是否存在

EXISTS key

redis数据类型

redis存在5种基本数据类型和三种特殊类型

String类型

例举:分布式锁、Session相关、验证码相关、计数相关(点击量/阅读量/关注)等单一的值。
取值赋值
关键词:set get append strlen

127.0.0.1:6379> set stu1 zhangsan ##基本使用设置值
OK
127.0.0.1:6379> get stu1
"zhangsan"
127.0.0.1:6379> APPEND stu1 shigehaoren ##追加值
(integer) 19
127.0.0.1:6379> get stu1
"zhangsanshigehaoren"
127.0.0.1:6379> STRLEN stu1 ##获取字符串长度
(integer) 19

加减操作

increment 自增1 decrement 自减
incrby 固定增量 自定义
decrby 固定减量 自定义

##值++
incr key
##值--
decr key
##增长指定的长度
incrby key number
##减少指定的长度
decrby key number
127.0.0.1:6379> set fans 0
OK
127.0.0.1:6379> INCR fans ##值++
(integer) 1
127.0.0.1:6379> INCR fans
(integer) 2
127.0.0.1:6379> INCR fans
(integer) 3
127.0.0.1:6379> get fans
"3"
127.0.0.1:6379> DECR fans ##值--
(integer) 2
127.0.0.1:6379> DECR fans
(integer) 1
127.0.0.1:6379> DECR fans
(integer) 0
127.0.0.1:6379> DECR fans
(integer) -1
127.0.0.1:6379> INCRBY stu1 100 ##值固定+N
(integer) 100
127.0.0.1:6379> INCRBY stu1 100
(integer) 200
127.0.0.1:6379> INCRBY stu1 100
(integer) 300
127.0.0.1:6379> DECRBY stu1 50 ##值固定-N
(integer) 250
127.0.0.1:6379> DECRBY stu1 50
(integer) 200

范围操作 range

getrange key index1 index2 获取当前指定范围 如果最大长度 index2 可替为-1

127.0.0.1:6379> set content hellojiazongnihao
OK
127.0.0.1:6379> get content
"hellojiazongnihao"
127.0.0.1:6379> STRLEN content
(integer) 17
127.0.0.1:6379> GETRANGE content 5 11
"jiazong"
127.0.0.1:6379> get content
"hellojiazongnihao"
127.0.0.1:6379> GETRANGE content 0 -1 # 获取全部字符串
"hellojiazongnihao"

替换操作

setrange key offset index 内容 ##offset 偏移量 指定位置开始替换

127.0.0.1:6379> GETRANGE content 0 -1
"hellojiazongnihao"
127.0.0.1:6379> SETRANGE content 5 sunzong #将sunzong覆盖jiazong
(integer) 17
127.0.0.1:6379> GETRANGE content 0 -1
"hellosunzongnihao"
127.0.0.1:6379> SETRANGE content 5 wang #将wang覆盖jiaz
(integer) 17
127.0.0.1:6379> GETRANGE content 0 -1
"hellowangongnihao"

判断是否存在

EXISTS t6 判断值是否存在
#set with expire #如果存在设置消失时间及信息值 – 订单
setex key time v 既能增加过期时间还能够重新指定新的值,如果当前key不存在创建一个新的key和值并指定过期时间

#如果一个值不想改变只想重新设定时间
127.0.0.1:6379> set message 1089
127.0.0.1:6379> expire message 300
#如果既想刷新时间又想改变值
127.0.0.1:6379> setex message 300 8888

#set if not expire #如果不存在 默认会创建一个新的,存在不操作 / 及判断存在不存在同时也能根据结果进行进一步操作
setnx key v

# 如果值存在 ,不作处理
127.0.0.1:6379> setnx message ooooooooooooooo
(integer) 0
# 如果值不存在, 执行创建
127.0.0.1:6379> setnx mess ooooooooooooooo
(integer) 1

批量值操作
more set
mset k1 v1 k2 v2
mget k1 k2

127.0.0.1:6379> mset b 123 c 123 d 123
OK
127.0.0.1:6379> get c
"123"
127.0.0.1:6379> mget a b c d
1) "123"
2) "123"
3) "123"
4) "123"	

关于对象的存储

#普通的json对象
{id:1,name:zhangsan,age:40}
#redis存储的对象
{class:[{},{}]}
{id:1,name:zhangsan,age:40}
{id:2,name:lisi,age:14}

将这个对象存储到redis中, key user 注意:字符串需要去掉引号

127.0.0.1:6379> set user {id:1,name:"zhangsan",age:40}
Invalid argument(s)
127.0.0.1:6379> set user {id:1,name:zhangsan,age:40}
OK
127.0.0.1:6379> get user
"{id:1,name:zhangsan,age:40}"

希望再存储一个李四,key 希望叫user 但是直接用会被覆盖,取值也不方便。
建议: 组合键(这个组合键目的就是将对象中的属性和值打散,通过组合不同的key到redis中读取不同的值)
方式1 : 可能后面根据ID读取某一个对象的情况

127.0.0.1:6379> set user:1 {id:1,name:zhangsan,age:40}
OK
127.0.0.1:6379> set user:2 {id:2,name:lisi,age:14}
OK
127.0.0.1:6379> keys user*
1) "user:2"
2) "user:1"
127.0.0.1:6379> get user:2
"{id:2,name:lisi,age:14}"

方式2:存在通过对象-id-属性,查询某些数据

set user:1:id 1
set user:1:name zhangsan
set user:1:age 14
set user:2:id 2
set user:2:name lisi
set user:2:age 14
# 查询所有
127.0.0.1:6379> keys user*
1) "user:2:age"
2) "user:1:age"
3) "user:1:id"
4) "user:2:id"
5) "user:1:name"
6) "user:2:name"
# 根据id查询
127.0.0.1:6379> keys user:1:*
1) "user:1:age"
2) "user:1:id"
3) "user:1:name"
# 根据具体条件查询
127.0.0.1:6379> get user:2:name
"lisi"

取值赋值操作

getset 先取值,再赋值

127.0.0.1:6379> getset student zhangsan
(nil)
127.0.0.1:6379> getset student lisi
"zhangsan"
127.0.0.1:6379> getset student wangwu
"lisi"
127.0.0.1:6379> get student
"wangwu"

关于浮点类型的增减操作

incr decr 直接使用会报错

127.0.0.1:6379> incr price
(error) ERR value is not an integer or out of range

关键词: incrbyfloat 不支持直接+1-1操作 支持固定增减量操作

127.0.0.1:6379> incrbyfloat price 9.9
"19.8"

删除数据

del key …

127.0.0.1:6379> del ww
127.0.0.1:6379> del nn oo

List列表类型

list列表类型,特点:所有的命令操作都是使用l开始。 和链表/队列比较相似,可以通过首尾进行操作。
在这里插入图片描述
工作中主要实现一些列表数据(导航、数据集List 队列消息,主要列表相关都可以存储)

基本赋值和取值操作

保留案例:导航信息存储
默认左侧插入值: lpush l意思 list
右侧插入值:rpush r意思 right
查询数据:lrange key start end
获取长度 llen key

127.0.0.1:6379> lpush stu zhangsan lisi
(integer) 2 #返回list长度
127.0.0.1:6379> lrange stu 0 -1 #取值
1) "lisi"
2) "zhangsan"
127.0.0.1:6379> lpush stu wangwu
(integer) 3
127.0.0.1:6379> lrange stu 0 -1
1) "wangwu"
2) "lisi"
3) "zhangsan"
127.0.0.1:6379> rpush stu zhaoliu # 结尾插入值
(integer) 4
127.0.0.1:6379> lrange stu 0 -1
1) "wangwu"
2) "lisi"
3) "zhangsan"
4) "zhaoliu"
127.0.0.1:6379> llen stu # 获取当前列表长度

list结构的删除数据操作

删除整个list列表

del key #这种是直接将整个列表都删除 del删除删除任意类型

删除list列表中的某一些数据
关键词:pop 弹出 抛出
语法:
lpop count 左侧弹出

# 没给count值,默认左 弹出1 返回的信息是弹出的值
127.0.0.1:6379> lpop stu
"lisi"
# count 提供值,可以弹出多个,返回值就是弹出的值得列表
127.0.0.1:6379> lpop stu 10
1) "zhangsan

rpop count 右侧弹出

127.0.0.1:6379> rpop stu
"zhangsan"
127.0.0.1:6379> rpop stu 1
1) "lisi"

删除指定值 list remove

关键词:lrem key count value ---- 默认从左侧开始查询并删除

127.0.0.1:6379> lpush stu wangwu zhaoliu zhangsan lisi wangwu tianqi zhangsan
zhangsan
127.0.0.1:6379> lrem stu 2 zhangsan
# count正数 从左向右删除 count负数, 从右向左删除
127.0.0.1:6379> lrange stu 0 -1

索引相关 index

通过索引获取数据
lindex key index

127.0.0.1:6379> lindex stu 1

截取列表中一部分值trim 在Java和脚本中是去掉前后空格,本身意思 修剪、整理。 在redis中作用域截取部分值,截取完成只保留获取的内容。
ltrim key start end

127.0.0.1:6379> ltrim stu 1 4

列表中替换值

lset

127.0.0.1:6379> lset stu 1 zhaoliu

其他

在指定的一个位置前面插入值(不是索引)
在指定的一个位置后面插入值(不是索引)
linsert key before|after 原始值位置 新的值

127.0.0.1:6379> linsert stu before zhaoliu lisi
127.0.0.1:6379> linsert stu after lisi wangwu

两个列表值交换 需要两个列表, 第一个列表中的最后一个值插入到第二个列表中的第一位
rpoplpush r pop l push (右弹出左推进) 如果list1和list2为同一个,最后一个值置顶
rpoplpush list1[第一个列表] list2[第二个列表]

127.0.0.1:6379> rpoplpush stu teacher
#置顶数据
127.0.0.1:6379> rpoplpush teacherteacher

Set集合类型

特点: 无序、不能重复 所有命令 s 开头
用途:点赞,签到,like等功能、抽奖功能
基本操作
通过测试:重复的值无法添加成功,并且值得顺序也不是固定的。
添加值
sadd key v…

127.0.0.1:6379> sadd book sanguoyanyi jinpingmei hongloumeng

查询值

smembers members 成员 组成

127.0.0.1:6379> smembers book

通用删除 del key 将整个集合直接删除

删除指定指定的元素srem key value

127.0.0.1:6379> srem book jinpingmei

其他操作

判断当前集合中是否存在指定的值 s is member 是否是成员
语法 sismember key v

127.0.0.1:6379> sismember book jinpingmei
(integer) 1
127.0.0.1:6379> sismember book jinpingmei2
(integer) 0
查看当前集合中值个数 s card

语法:sacrd key

127.0.0.1:6379> scard book
(integer) 4
随机获取和随机删除

随机获取: srandmember [ s random member ]
语法:srandmember key count

127.0.0.1:6379> srandmember book
"jinpingmei"
127.0.0.1:6379> srandmember book 2
1) "hongloumeng"
2) "jinpingmei"
随机删除: spop

语法: spop key count

127.0.0.1:6379> spop book
"jinpingmei"
127.0.0.1:6379> spop book 10
1) "hongloumeng"
2) "sanguoyanyi"

特殊操作

差集 diff 差异 只返回第一个集合的差值

127.0.0.1:6379> sadd zb1 xiaozhi dasima caixukun lijiaqi
4
127.0.0.1:6379> sadd zb2 liziqi xiaozhi dasima wangdaxian xuxubaobao
5
127.0.0.1:6379> sdiff zb1 zb2
caixukun
lijiaqi
127.0.0.1:6379> sdiff zb2 zb1
wangdaxian
liziqi
xuxubaobao

交集 sinter 交集

127.0.0.1:6379> sinter zb2 zb1
dasima
xiaozhi

并集 s union 联盟 工会 ---- 去重复

127.0.0.1:6379> sunion zb1 zb2
lijiaqi
dasima
xuxubaobao
xiaozhi
caixukun
wangdaxian
liziqi

Hash类型(map结构)

key-value(map) 比较适合对象类型的数据存储 key -(key-value)

基础语法

存值(key (key - value))

语法:
hset key k1 v1 k2 v2 k3 v3
hget key k – 获取单个值

# book redis的key name 值得key pingandeshijie 值中的值 (key key value)
127.0.0.1:6379> hset book name pingandeshijie
127.0.0.1:6379> hget book name
pingandeshijie
127.0.0.1:6379> hset book name pingandeshijie price 30 author luyao
# 如果存储对象
127.0.0.1:6379> hset student name zhangsan age 19 hobbies chouyanhejiutangtou
# 如果存储对象集合
{id:1,name:zhangsan}
{id:2,name:lisi}
127.0.0.1:6379> hset student 1 {id:1,name:zhangsan} 2 {id:2,name:lisi}
127.0.0.1:6379> hget student 2

hmget 名称k k k #获取多个key

127.0.0.1:6379> hmget student 1 2

获取全部的值 hgetall 名称k

127.0.0.1:6379> hgetall student
1
{id:1,name:zhangsan}
2
{id:2,name:lisi}

通过key删除所有数据 – 通用

127.0.0.1:6379> del student

通过值的key删除指定的数据

127.0.0.1:6379> hdel student 2
1

读取当前hash表(map集合)中数据的个数

127.0.0.1:6379> hlen student

其他用法

判断是否存在

127.0.0.1:6379> hexists student 10
0
127.0.0.1:6379> hexists student 3
1

读取所有的key ----- 1w条数据 取某一些特定的数据,取到所有key 循环 取值 判断 筛选。

127.0.0.1:6379> hkeys student
1
3
2

取所有的value — 案例:Java中想将redis中的hash结构转化为set结构

127.0.0.1:6379> hvals student
{id:1,name:zhangsan}
{id:1,name:zhangsan}
{id:2,name:lisi}

Zset

有序不重复集合,在set的基础上多增加一个值 set k1 * v1 (zset k1 排序的值(score) v1)
有序不可重复的set集合
主要使用方向:工资、班级成绩等等之类的数据。 或者 权重处理 0 普通 1 重要

基本语法

增加和查询
# emps k
{id:1,name:zhangsan,sal:8000}
{id:2,name:lisi,sal:5000}
{id:3,name:wangwu,sal:12000}
#增加数据
127.0.0.1:6379> zadd emps 8000 {id:1,name:zhangsan,sal:8000}
1
127.0.0.1:6379> zadd emps 5000 {id:2,name:lisi,sal:5000}
1
127.0.0.1:6379> zadd emps 12000 {id:3,name:wangwu,sal:12000}
1
127.0.0.1:6379> zrange emps 0 -1 #查询 - 默认升序
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
127.0.0.1:6379> zrandmember emps 1 #随机读取
{id:1,name:zhangsan,sal:8000}
删除数据

删除整个key del key

zrem k v
127.0.0.1:6379> zrem emps {id:2,name:lisi,sal:5000}
1

zset 复杂查询

#显示所有信息,从小到大 (范围) -inf 负无穷 +inf正无穷
语法:** z range by score** key min max — min max使用的是排序字段的值

127.0.0.1:6379> zrangebyscore emps 8000 10000
{id:1,name:zhangsan,sal:8000}
127.0.0.1:6379> zrangebyscore emps -inf +inf
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
升序操作
# 默认取所有数据
127.0.0.1:6379> zrange emps 0 -1
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
# 根据当前排序结果取所有数据
127.0.0.1:6379> zrangebyscore emps -inf +inf
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
# 根据当前排序结果取所有数据 并显示排序字段
127.0.0.1:6379> zrangebyscore emps -inf +inf withscores
{id:2,name:lisi,sal:5000}
5000
{id:1,name:zhangsan,sal:8000}
8000
{id:3,name:wangwu,sal:12000}
12000
# 根据当前排序结果取所有数据 并显示排序字段 并截取某一部分值
127.0.0.1:6379> zrangebyscore emps -inf +inf withscores limit 0 1
{id:2,name:lisi,sal:5000}
5000
# 取排序字段值大于**的数据
127.0.0.1:6379> zrangebyscore emps 8000 +inf
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
# 取排序字段值小于**的数据
127.0.0.1:6379> zrangebyscore emps -inf 8000
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
127.0.0.1:6379>

降序操作

z rev range k start stop start stop取索引的范围。

127.0.0.1:6379> zrange emps 0 -1
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
127.0.0.1:6379> zrangebyscore emps -inf +inf
{id:2,name:lisi,sal:5000}
{id:1,name:zhangsan,sal:8000}
{id:3,name:wangwu,sal:12000}
127.0.0.1:6379> zrevrange emps 0 -1
{id:3,name:wangwu,sal:12000}
{id:1,name:zhangsan,sal:8000}
{id:2,name:lisi,sal:5000}
127.0.0.1:6379> zrevrangebyscore emps +inf -inf
{id:3,name:wangwu,sal:12000}
{id:1,name:zhangsan,sal:8000}
{id:2,name:lisi,sal:5000}
127.0.0.1:6379> zrevrangebyscore emps +inf -inf withscores
{id:3,name:wangwu,sal:12000}
12000
{id:1,name:zhangsan,sal:8000}
8000
{id:2,name:lisi,sal:5000}
5000
127.0.0.1:6379> zrevrangebyscore emps +inf -inf withscores limit 0 1
{id:3,name:wangwu,sal:12000}
12000
127.0.0.1:6379> zrevrangebyscore emps +inf 8000
{id:3,name:wangwu,sal:12000}
{id:1,name:zhangsan,sal:8000}
127.0.0.1:6379> zrevrangebyscore emps 8000 -inf
{id:1,name:zhangsan,sal:8000}
{id:2,name:lisi,sal:5000}
127.0.0.1:6379>

geospatial 地理位置

作用:朋友圈定位,附近的人,打车距离计算
在这里插入图片描述
添加地理位置信息 GEOADD
geoadd key 精度 纬度 名称

# maps
127.0.0.1:6379> geoadd maps 116.22518499110029 39.951383565611934 xuejiaao
127.0.0.1:6379> geoadd maps 116.22119922850416 39.95185236700199 hongyun
127.0.0.1:6379> geoadd maps 116.32169891808317 39.894741892979205 beijingxizhan
127.0.0.1:6379> geoadd maps 116.77067924950407 39.830968132325836 huanhuxiaozhan
127.0.0.1:6379> geoadd maps 116.69798065636442 39.52723389254955 langfang
127.0.0.1:6379> geoadd maps 117.20129181359098 39.083057286402244 tianjing
127.0.0.1:6379> geoadd maps 121.47772003624723 31.19857972418721 shanghai
127.0.0.1:6379> geoadd maps 121.52029205773161 25.050634672686915 taiwan

查询指定位置的经纬度 GEOPOS

127.0.0.1:6379> geopos maps taiwan
121.52029305696487427
25.05063527678047564
127.0.0.1:6379> geopos maps langfang
116.69798165559768677
39.52723385909389719
127.0.0.1:6379>

查询两地点之间的距离 GEODIST
geodist key 地点1 地点2 单位(默认 米)

127.0.0.1:6379> geodist maps xuejiaao hongyun M
343.8236
127.0.0.1:6379> geodist maps xuejiaao hongyun km
0.3438
127.0.0.1:6379> geodist maps beijingxizhan shanghai
1073394.0301
127.0.0.1:6379> geodist maps beijingxizhan shanghai km
1073.3940
127.0.0.1:6379>

查询附近的城市(定位) GEORADIUS

127.0.0.1:6379> georadius maps 116.22518499110029 39.951383565611934 1500 KM

查询附近的城市(定位)–显示到中间的距离 GEORADIUS
关键词: withdist

127.0.0.1:6379> georadius maps 116.22518499110029 39.951383565611934 500 KM withdist
langfang
62.1354
tianjing
127.8360
huanhuxiaozhan
48.4408
beijingxizhan
10.3666
hongyun
0.3440
xuejiaao
0.0002

查询附近的城市(定位)–显示他人精准信息 GEORADIUS
关键词:withcoord

127.0.0.1:6379> georadius maps 116.22518499110029 39.951383565611934 500 KM withdist
withcoord
langfang
62.1354
116.69798165559768677
39.52723385909389719
tianjing
127.8360
117.20129281282424927
39.08305692729481251
huanhuxiaozhan
48.4408

查询附近的城市(定位)–显示他人精准信息 并指定返回个数 GEORADIUS
关键词: count

127.0.0.1:6379> georadius maps 116.22518499110029 39.951383565611934 500 KM withdist
withcoord count 1
xuejiaao
0.0002
116.22518330812454224
39.95138395901303596

GEO RADIUS BY MEMBER 通过元素(非坐标)定位信息

127.0.0.1:6379> georadiusbymember maps hongyun 10 KM
hongyun
xuejiaao

Geohash 返回hash值 作用是将二维写的经纬度转化为一维的字符串,字符串越像表示距离越近

127.0.0.1:6379> geohash maps hongyun
wx4ek9j8qj0
127.0.0.1:6379> geohash maps xuejiaao
wx4ekbbtej0
127.0.0.1:6379> geohash maps shanghai
wtw3kr92uh0

HyperLogLog基数统计

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积
非常非常大时,计算基数所需的空间总是固定的、并且是很小的。在 Redis 里面,每个 HyperLogLog
键只需要花费 12 KB 内存,就可以计算接近 2^64(long类型的最大值) 个不同元素的基数。这和计算基
数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来
计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素.

什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数重复元素为5(个数)。
基数(不估计就是在误差可接受的范围内,快速计算基数。

在这里插入图片描述添加基础数据

127.0.0.1:6379> pfadd clazz zhangsan lisi wangwu zhaoliu tianqi 99 2111 99.9
1
127.0.0.1:6379> pfadd clazz zhangsan lisi wangwu zhaoliu
0
127.0.0.1:6379> pfadd clazz zhangsan lisi wangwu zhaoliu 33
1

获取总数

127.0.0.1:6379> pfcount clazz
11

合并 – 规则 将后面的数据合并到第一个指定的key中–合并之后也是默认去重复。

127.0.0.1:6379> pfadd stu xiongda xionger zhangsan
1
127.0.0.1:6379> pfmerge clazz stu
OK
127.0.0.1:6379> pfcount clazz
11
127.0.0.1:6379> pfcount stu
3

Bitmaps位图

1Byte=8bit(比特)
1KB=1024Byte(字节)=8*1024bit
1MB=1024KB
1GB=1024MB
1TB=1024GB
1PB=1024TB
首先增加基础数据 setbit

127.0.0.1:6379> setbit kq 1 1
0
127.0.0.1:6379> setbit kq 2 1
0
127.0.0.1:6379> setbit kq 2 0
1
127.0.0.1:6379> setbit kq 3 1
0
127.0.0.1:6379> setbit kq 4 1
0
127.0.0.1:6379> setbit kq 5 0
0
127.0.0.1:6379> setbit kq 6 0
0
127.0.0.1:6379> setbit kq 7 0
0
127.0.0.1:6379>

查看某一天的打卡记录 getbit

127.0.0.1:6379> get kq
X
127.0.0.1:6379> getbit kq 1
1
127.0.0.1:6379> getbit kq 6
0

统计打卡的天数 bitcount

127.0.0.1:6379> bitcount kq
3
127.0.0.1:6379>

redis配置详解

# Redis 配置文件
# 当配置中需要配置内存大小时,可以使用 1k, 5GB, 4M 等类似的格式,其转换方式如下(不区分大小写)
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# 内存配置大小写是一样的.比如 1gb 1Gb 1GB 1gB
# daemonize no 默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为yes
daemonize yes
# 当redis在后台运行的时候,Redis默认会把pid文件放在/var/run/redis.pid,你可以配置到其他地址。
# 当运行多个redis服务时,需要指定不同的pid文件和端口
pidfile /var/run/redis.pid
# 指定redis运行的端口,默认是6379
port 6379
# 指定redis只接收来自于该IP地址的请求,如果不进行设置,那么将处理所有请求,
# 在生产环境中最好设置该项
# bind 127.0.0.1
# Specify the path for the unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocketperm 755
# 设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接
# 0是关闭此设置
timeout 0
# 指定日志记录级别
# Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug 记录很多信息,用于开发和测试
# varbose 有用的信息,不像debug会记录那么多
# notice 普通的verbose,常用于生产环境
# warning 只有非常重要或者严重的信息会记录到日志
loglevel debug
# 配置log文件地址
# 默认值为stdout,标准输出,若后台模式会输出到/dev/null
#logfile stdout
logfile /var/log/redis/redis.log
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no
# Specify the syslog identity.
# syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
# 可用数据库数
# 默认值为16,默认数据库为0,数据库范围在0-(database-1)之间
databases 16
################################ 快照RDB #################################
#
# 保存数据到磁盘,格式如下:
#
# save <seconds> <changes>
#
# 指出在多长时间内,有多少次更新操作,就将数据同步到数据文件rdb。
# 相当于条件触发抓取快照,这个可以多个条件配合
#
# 比如默认配置文件中的设置,就设置了三个条件
#
# save 900 1 900秒内至少有1个key被改变
# save 300 10 300秒内至少有300个key被改变
# save 60 10000 60秒内至少有10000个key被改变
save 900 1
save 300 10
save 60 10000
# 存储至本地数据库时(持久化到rdb文件)是否压缩数据,默认为yes
rdbcompression yes
# 本地持久化数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
# 工作目录
#
# 数据库镜像备份的文件放置的路径。
# 这里的路径跟文件名要分开配置是因为redis在进行备份时,先会将当前数据库的状态写入到一个临时文件中,等备份完成时,
# 再把该该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路径当中。
#
# AOF文件也会存放在这个目录下面
#
# 注意这里必须制定一个目录而不是文件
dir ./
################################# 复制 #################################
# 主从复制. 设置该数据库为其他数据库的从数据库.
# 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
# 7.*
# replicaof <masterip> <masterport>
# 当master服务设置了密码保护时(用requirepass制定的密码)
# slav服务连接master的密码
#
# masterauth <master-password>
# 当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:
#
# 1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续相应客户端的请求
#
# 2) 如果slave-serve-stale-data是指为no,出去INFO和SLAVOF命令之外的任何请求都会返回一个
# 错误"SYNC with master in progress"
#
slave-serve-stale-data yes
# 从库会按照一个时间间隔向主库发送PINGs.可以通过repl-ping-slave-period设置这个时间间隔,默认是10秒
#
# repl-ping-slave-period 10
# repl-timeout 设置主库批量数据传输时间或者ping回复时间间隔,默认值是60秒
# 一定要确保repl-timeout大于repl-ping-slave-period
# repl-timeout 60
################################## 安全 ###################################
# 设置客户端连接后进行任何其他指定前需要使用的密码。
# 警告:因为redis速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行150K次的密码尝试,
#这意味着你需要指定非常非常强大的密码来防止暴力破解
#
# requirepass foobared
# 命令重命名.
#
# 在一个共享环境下可以重命名相对危险的命令。比如把CONFIG重名为一个不容易猜测的字符。
#
# 举例:
#
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
# 如果想删除一个命令,直接把它重命名为一个空字符""即可,如下:
#
# rename-command CONFIG ""
################################### 约束 ####################################
# 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置 maxclients 0,表示不作限制。
# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
#
# maxclients 128
# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key
# Redis同时也会移除空的list对象
#
# 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作
#
# 注意:Redis新的vm机制,会把Key存放内存,Value会存放在swap区
#
# maxmemory的设置比较适合于把redis当作于类似memcached的缓存来使用,而不适合当做一个真实的DB。
# 当把Redis当做一个真实的数据库使用的时候,内存使用将是一个很大的开销
# maxmemory <bytes>
# 当内存达到最大值的时候Redis会选择删除哪些数据?有五种方式可供选择
#
# volatile-lru -> 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )
# allkeys-lru -> 利用LRU算法移除任何key
# volatile-random -> 移除设置过过期时间的随机key
# allkeys->random -> remove a random key, any key
# volatile-ttl -> 移除即将过期的key(minor TTL)
# noeviction -> 不移除任何可以,只是返回一个写错误
#
# 注意:对于上面的策略,如果没有合适的key可以移除,当写的时候Redis会返回一个错误
#
# 写命令包括: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
#
# 默认是:
#
# maxmemory-policy volatile-lru
# LRU 和 minimal TTL 算法都不是精准的算法,但是相对精确的算法(为了节省内存),随意你可以选择样本大小进行检测。
# Redis默认的灰选择3个样本进行检测,你可以通过maxmemory-samples进行设置
#
# maxmemory-samples 3
############################## AOF ###############################
# 默认情况下,redis会在后台异步的把数据库镜像备份到磁盘,但是该备份是非常耗时的,而且备份也不能很频繁,
#如果发生诸如拉闸限电、拔插头等状况,那么将造成比较大范围的数据丢失。
# 所以redis提供了另外一种更加高效的数据库备份及灾难恢复方式。
# 开启append only模式之后,redis会把所接收到的每一次写操作请求都追加到appendonly.aof文件中,
#当redis重新启动时,会从该文件恢复出之前的状态。
# 但是这样会造成appendonly.aof文件过大,所以redis还支持了BGREWRITEAOF指令,对appendonly.aof进行重新整理。
# 你可以同时开启asynchronous dumps 和 AOF
appendonly no
# AOF文件名称 (默认: "appendonly.aof")
# appendfilename appendonly.aof
# Redis支持三种同步AOF文件的策略:
#
# no: 不进行同步,系统去操作 . Faster.
# always: always表示每次有写操作都进行同步. Slow, Safest.
# everysec: 表示对写操作进行累积,每秒同步一次. Compromise.
#
# 默认是"everysec",按照速度和安全折中这是最好的。
# 如果想让Redis能更高效的运行,你也可以设置为"no",让操作系统决定什么时候去执行
# 或者相反想让数据更安全你也可以设置为"always"
#
# 如果不确定就用 "everysec".
# appendfsync always
appendfsync everysec
# appendfsync no
# AOF策略设置为always或者everysec时,后台处理进程(后台保存或者AOF日志重写)会执行大量的I/O操作
# 在某些Linux配置中会阻止过长的fsync()请求。注意现在没有任何修复,即使fsync在另外一个线程进行处理
#
# 为了减缓这个问题,可以设置下面这个参数no-appendfsync-on-rewrite
#
# This means that while another child is saving the durability of Redis is
# the same as "appendfsync none", that in pratical terms means that it is
# possible to lost up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.no-appendfsync-on-rewrite no
# Automatic rewrite of the append only file.
# AOF 自动重写
# 当AOF文件增长到一定大小的时候Redis能够调用 BGREWRITEAOF 对日志文件进行重写
#
# 它是这样工作的:Redis会记住上次进行些日志后文件的大小(如果从开机以来还没进行过重写,那日子大小在开
机的时候确定)
#
# 基础大小会同现在的大小进行比较。如果现在的大小比基础大小大制定的百分比,重写功能将启动
# 同时需要指定一个最小大小用于AOF重写,这个用于阻止即使文件很小但是增长幅度很大也去重写AOF文件的情况
# 设置 percentage 为0就关闭这个特性
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
################################## SLOW LOG ###################################
# Redis Slow Log 记录超过特定执行时间的命令。执行时间不包括I/O计算比如连接客户端,返回结果等,只是命令执行时间
#
# 可以通过两个参数设置slow log:一个是告诉Redis执行超过多少时间被记录的参数slowlog-log-slowerthan(微秒),
# 另一个是slow log 的长度。当一个新命令被记录的时候最早的命令将被从队列中移除
# 下面的时间以微妙微单位,因此1000000代表一分钟。
# 注意制定一个负数将关闭慢日志,而设置为0将强制每个命令都会记录
slowlog-log-slower-than 10000
# 对日志长度没有限制,只是要注意它会消耗内存
# 可以通过 SLOWLOG RESET 回收被慢日志消耗的内存
slowlog-max-len 1024
################################ VM ###############################
### WARNING! Virtual Memory is deprecated in Redis 2.4
### The use of Virtual Memory is strongly discouraged.
# Virtual Memory allows Redis to work with datasets bigger than the actual
# amount of RAM needed to hold the whole dataset in memory.
# In order to do so very used keys are taken in memory while the other keys
# are swapped into a swap file, similarly to what operating systems do
# with memory pages.
#
# To enable VM just set 'vm-enabled' to yes, and set the following three
# VM parameters accordingly to your needs.
vm-enabled no
# vm-enabled yes
# This is the path of the Redis swap file. As you can guess, swap files
# can't be shared by different Redis instances, so make sure to use a swap
# file for every redis process you are running. Redis will complain if the
# swap file is already in use.
#
# The best kind of storage for the Redis swap file (that's accessed at random)
# is a Solid State Disk (SSD).
#
# *** WARNING *** if you are using a shared hosting the default of putting
# the swap file under /tmp is not secure. Create a dir with access granted
# only to Redis user and configure Redis to create the swap file there.
vm-swap-file /tmp/redis.swap
# vm-max-memory configures the VM to use at max the specified amount of
# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
# is, if there is still enough contiguous space in the swap file.
#
# With vm-max-memory 0 the system will swap everything it can. Not a good
# default, just specify the max amount of RAM you can in bytes, but it's
# better to leave some margin. For instance specify an amount of RAM
# that's more or less between 60 and 80% of your free RAM.
vm-max-memory 0
# Redis swap files is split into pages. An object can be saved using multiple
# contiguous pages, but pages can't be shared between different objects.
# So if your page is too big, small objects swapped out on disk will waste
# a lot of space. If you page is too small, there is less space in the swap
# file (assuming you configured the same number of total swap file pages).
#
# If you use a lot of small objects, use a page size of 64 or 32 bytes.
# If you use a lot of big objects, use a bigger page size.
# If unsure, use the default :)
vm-page-size 32
# Number of total memory pages in the swap file.
# Given that the page table (a bitmap of free/used pages) is taken in memory,
# every 8 pages on disk will consume 1 byte of RAM.
#
# The total swap size is vm-page-size * vm-pages
#
# With the default of 32-bytes memory pages and 134217728 pages Redis will
# use a 4 GB swap file, that will use 16 MB of RAM for the page table.
#
# It's better to use the smallest acceptable value for your application,
# but the default is large in order to work in most conditions.
vm-pages 134217728
# Max number of VM I/O threads running at the same time.
# This threads are used to read/write data from/to swap file, since they
# also encode and decode objects from disk to memory or the reverse, a bigger
# number of threads can help with big objects even if they can't help with
# I/O itself as the physical device may not be able to couple with many
# reads/writes operations at the same time.
#
# The special value of 0 turn off threaded I/O and enables the blocking
# Virtual Memory implementation.
vm-max-threads 4
############################### ADVANCED CONFIG ###############################
# 当hash中包含超过指定元素个数并且最大的元素没有超过临界时,
# hash将以一种特殊的编码方式(大大减少内存使用)来存储,这里可以设置这两个临界值
# Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,
# 这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,
# 当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
hash-max-zipmap-entries 512
hash-max-zipmap-value 64
# list数据类型多少节点以下会采用去指针的紧凑存储格式。
# list数据类型节点值大小小于多少字节会采用紧凑存储格式。
list-max-ziplist-entries 512
list-max-ziplist-value 64
# set数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。
set-max-intset-entries 512
# zsort数据类型多少节点以下会采用去指针的紧凑存储格式。
# zsort数据类型节点值大小小于多少字节会采用紧凑存储格式。
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用
#
# 当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。
#
# 如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存
activerehashing yes
################################## INCLUDES ###################################
# 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
# include /path/to/local.conf
# include /path/to/other.conf

redis持久化

理论

前面我们说过,Redis 相对于 Memcache 等其他的缓存产品,有一个比较明显的优势就是 Redis 不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。这几种丰富的数据类型我们接下来我们要介绍 Redis 的另外一大优势——持久化。

由于 Redis 是一个内存数据库,所谓内存数据库,就是将数据库中的内容保存在内存中,
这与传统的MySQL,Oracle等关系型数据库直接将内容保存到硬盘中相比,内存数据库的读写效率比传统数据库要快的多(内存的读写效率远远大于硬盘的读写效率)。

但是保存在内存中也随之带来了一个缺点,一旦断电或者宕机,那么内存数据库中的数据将会全部丢失。为了解决这个缺点,Redis提供了将内存数据持久化到硬盘,以及用持久化文件来恢复数据库数据的功能。Redis 支持两种形式的持久化,一种是RDB快照(snapshotting),另外一种是AOF(append-only-file)

数据库存储方式
在这里插入图片描述
NoSQL存储方式
在这里插入图片描述

RDB快照(redis DateBase)

概述

RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。

触发方式

RDB 有两种触发方式,分别是自动触发和手动触发。

自动触发

查看redis配置文件

################################ 快照 #################################
#
# 保存数据到磁盘,格式如下:
#
# save <seconds> <changes>
#
# 指出在多长时间内,有多少次更新操作,就将数据同步到数据文件rdb。
# 相当于条件触发抓取快照,这个可以多个条件配合
#
# 比如默认配置文件中的设置,就设置了三个条件
#
# save 900 1 900秒内至少有1个key被改变
# save 300 10 300秒内至少有10个key被改变
# save 60 10000 60秒内至少有10000个key被改变
save 900 1
save 300 10
save 60 10000
# 存储至本地数据库时(持久化到rdb文件)是否压缩数据,默认为yes
rdbcompression yes
# 本地持久化数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
# 工作目录
#
# 数据库镜像备份的文件放置的路径。
# 这里的路径跟文件名要分开配置是因为redis在进行备份时,先会将当前数据库的状态写入到一个临时文件中,
等备份完成时,
# 再把该该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路
径当中。
#
# AOF文件也会存放在这个目录下面
#
# 注意这里必须制定一个目录而不是文件
dir ./

save: 这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave(这个命令下面会介绍,手动触发RDB持久化的命令)。
rdbcompression :默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。
stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了。
rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
dbfilename :设置快照的文件名,默认是 dump.rdb
dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默认是和当前配置文件保存在同一目录。

手动触发

手动触发Redis进行RDB持久化的命令有两种:
save:该命令会阻塞当前Redis服务器,执行save命令期间,redis不能处理其他命令,直到RDB过程完成为止。显然该命令对于内存比较大的实例会造成长时间阻塞,这是致命的缺陷,为了解决此问题,redis提供了。

第二种方式。(不建议使用)
bgsave:执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在创建子进程fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。

还有两种特殊的操作也能触发RDB的持久化,但是因为情况特殊,所以不作为手动触发条件
执行执行 flushall.flushdb 命令,也会产生dump.rdb文件,但里面是空的,无意义
关闭redis 服务同样会生成 — 规则使用 bgsave 保存数据。

RDB数据恢复(企业管理者经常用的手段)

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可,redis就会自动加载文件数据至内存了。
Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。
启动服务器的当前目录一定是redis-*** 否则快照生成的路径就会发生错误!

127.0.0.1:6379> config get dir
dir
/opt/redis-7.0.4

停止 RDB 持久化

有些情况下,我们只想利用Redis的缓存功能,并不像使用 Redis 的持久化功能,那么这时候我们最好停掉RDB 持久化。可以通过上面讲的在配置文件 redis.conf 中,可以注释掉所有的 save 行来停用保存功能或者直接一个空字符串来实现停用:save “”

# save 60 10000
save ""

也可以执行命令

redis-cli config set save ""

RDB 的优势和劣势

优势

  1. RDB是一个非常紧凑的文件(默认压缩),它保存redis 在某个时间点上的数据集。这种文件非常适合用于备份和灾难恢复。
  2. 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
  3. RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。劣势
  4. RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)
  5. RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题(版本不兼容)
  6. 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有丢失)

RDB 自动保存的原理

redis有个服务器状态结构:

struct redisService{
	#1、记录保存save条件的数组
	struct saveparam *saveparams;
	#2、修改计数器
	long long dirty;
	#3、上一次执行保存的时间
	time_t lastsave;
}

首先看记录保存save条件的数组 saveparam,里面每个元素都是一个 saveparams 结构:

struct saveparam{
	#秒数
	time_t seconds;
	#修改数
	int changes;
};

前面我们在 redis.conf 配置文件中进行了关于save 的配置:

save 3600 1 :表示3600 秒内如果至少有 1 个 key 的值变化,则保存
save 300 100:表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

那么服务器状态中的saveparam 数组将会是如下的样子:
在这里插入图片描述dirty 计数器和lastsave 属性
dirty 计数器记录距离上一次成功执行 save 命令或者 bgsave 命令之后,Redis服务器进行了多少次修改(包括写入、删除、更新等操作)。
lastsave 属性是一个时间戳,记录上一次成功执行 save 命令或者 bgsave 命令的时间。
执行原理:
通过这两个属性,当服务器成功执行一次修改操作,那么dirty 计数器就会加 1,而lastsave 属性记录上一次执行save或bgsave的时间,Redis 服务器还有一个周期性操作函数 severCron ,默认每隔 100 毫秒就会执行一次,该函数会遍历并检查 saveparams 数组中的所有保存条件,只要有一个条件被满足,那么就会执行bgsave 命令。执行完成之后,dirty 计数器更新为 0 ,lastsave 也更新为执行命令的完成时间。

AOF

概述

Redis的持久化方式之一RDB是通过保存数据库中的键值对来记录数据库的状态。而另一种持久化方式 AOF则是通过保存Redis服务器所执行的写命令来记录数据库状态。

AOF以协议文本的方式,将所有对数据库进行写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。

用日志的形式来记录每个操作,将redis执行过的所有的指令都记录下来(读操作不记录),只许追加文件但不可以写文件,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次,已完成数据文件的恢复工作。

比如对于如下命令:
在这里插入图片描述
RDB 持久化方式就是将 str1,str2,str3 这三个键值对保存到 RDB文件中,而 AOF 持久化则是将执行的
set,sadd,lpush 三个命令保存到 AOF 文件中。

AOF配置

############################## AOF ###############################
# 默认情况下,redis会在后台异步的把数据库镜像备份到磁盘,但是该备份是非常耗时的,而且备份也不能很频繁,
#如果发生诸如拉闸限电、拔插头等状况,那么将造成比较大范围的数据丢失。
# 所以redis提供了另外一种更加高效的数据库备份及灾难恢复方式。
# 开启append only模式之后,redis会把所接收到的每一次写操作请求都追加到appendonly.aof文件中,
#当redis重新启动时,会从该文件恢复出之前的状态。
# 但是这样会造成appendonly.aof文件过大,所以redis还支持了BGREWRITEAOF指令,对appendonly.aof进行重新整理。
# 你可以同时开启asynchronous dumps 和 AOF
appendonly no
# AOF文件名称 (默认: "appendonly.aof")
# appendfilename appendonly.aof
# Redis支持三种同步AOF文件的策略:
#
# no: 不进行同步,系统去操作 . Faster.
# always: always表示每次有写操作都进行同步. Slow, Safest.
# everysec: 表示对写操作进行累积,每秒同步一次. Compromise.
#
# 默认是"everysec",按照速度和安全折中这是最好的。
# 如果想让Redis能更高效的运行,你也可以设置为"no",让操作系统决定什么时候去执行
# 或者相反想让数据更安全你也可以设置为"always"
#
# 如果不确定就用 "everysec".
# appendfsync always
appendfsync everysec
# appendfsync no
# AOF策略设置为always或者everysec时,后台处理进程(后台保存或者AOF日志重写)会执行大量的I/O操作
# 在某些Linux配置中会阻止过长的fsync()请求。注意现在没有任何修复,即使fsync在另外一个线程进行处理
#
# 为了减缓这个问题,可以设置下面这个参数no-appendfsync-on-rewrite
#
# This means that while another child is saving the durability of Redis is
# the same as "appendfsync none", that in pratical terms means that it is
# possible to lost up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.
no-appendfsync-on-rewrite no
# Automatic rewrite of the append only file.
# AOF 自动重写
# 当AOF文件增长到一定大小的时候Redis能够调用 BGREWRITEAOF 对日志文件进行重写
#
# 它是这样工作的:Redis会记住上次进行些日志后文件的大小(如果从开机以来还没进行过重写,那日子大小在开机的时候确定)
#
# 基础大小会同现在的大小进行比较。如果现在的大小比基础大小大制定的百分比,重写功能将启动
# 同时需要指定一个最小大小用于AOF重写,这个用于阻止即使文件很小但是增长幅度很大也去重写AOF文件的情况
# 设置 percentage 为0就关闭这个特性
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

appendonly:默认值为no,也就是说redis 默认使用的是rdb方式持久化,如果想要开启 AOF 持久化方
式,需要将appendonly 修改为 yes。
appendfilename :aof文件名,默认是"appendonly.aof"
appendfsync**:**aof持久化策略的配置;
1. no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快,但是不太安全;
2. always表示每次写入都执行fsy
3. everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。通常选择 everysec ,兼顾安全性和效率。(默认)
no-appendfsync-on-rewrite:在aof重写或者写入aof文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。 设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。默认值为no。

auto-aof-rewrite-percentage:默认值为100。aof自动重写配置,当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候,Redis能够调bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。 64M - 40M - 80M(55M) - 110M(70M)

auto-aof-rewrite-min-size:64mb。设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写。

aof-load-truncated:aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,出现这种现象 redis宕机或者异常终止不会造成尾部不完整现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。默认值为 yes。

开启AOF

默认不开启,需要手动开启 - AOF和RDB同时开启 ,默认走AOF策略

appendonly yes

在这里插入图片描述
注意配置完成后重启redis,并set几个值,# vim查看aof文件
7.05 版本 aof文件存储已经更改到appendonlydir文件夹下,对应去查找 appendonly.aof.1.incr.aof
在这里插入图片描述

AOF文件故障修复

# 关闭redis
# 删除dump.rdb
# 随便修改点aof文件
# 重新启动redis

在这里插入图片描述通过redis-check-aof --fix 工具 修复文件
在这里插入图片描述修复后重新启动。

AOF文件重写机制

AOF 文件包含三类文件:基本文件、增量文件与清单文件。其中基本文件一般为 rdb 格式,就是 rdb 持久化的数据文件。
由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用命令 bgrewriteaof 来重写。
比如对于如下命令:
在这里插入图片描述
如果不进行 AOF 文件重写,那么 AOF 文件将保存四条 SADD 命令,如果使用AOF 重写,那么AOF 文件中将只会保留下面一条命令:
sadd animals “dog” “tiger” “panda” “lion” “cat”
也就是说 AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。

AOF 重写触发机制:

通过 redis.conf 配置文件中的 auto-aof-rewrite-percentage:默认值为100,以及auto-aof-rewrite-minsize:64mb 配置,也就是说默认Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。

这里再提一下,我们知道 Redis 是单线程工作,如果重写 AOF 需要比较长的时间,那么在重写 AOF 期间,Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了克服这个问题,解决办法是将 AOF 重写程序放到子程序中进行,这样有两个好处:

  1. 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理其他命令。
  2. 子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。

使用子进程解决了上面的问题,但是新问题也产生了:因为子进程在进行 AOF 重写期间,服务器进程依然在处理其它命令,这新的命令有可能也对数据库进行了修改操作,使得当前数据库状态和重写后的 AOF 文件状态不一致。

为了解决这个数据状态不一致的问题,Redis 服务器设置了一个 AOF 重写缓冲区,这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF 重写缓冲区。当子进程完成 AOF 重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF 重写缓冲区的内容都写到新的 AOF 文件中。

这样将 AOF 重写对服务器造成的影响降到了最低。

AOF的优缺点

优点:

  1. AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,最多也就丢失1秒的数据而已。
  2. AOF 文件使用 Redis 命令追加的形式来构造,因此,即使 Redis 只能向 AOF 文件写入命令的片断,使用 redis-check-aof 工具也很容易修正 AOF 文件。
  3. AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心错用了FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF来恢复数据。
    缺点:
  4. 对于具有相同数据的的 Redis,AOF 文件通常会比 RDB 文件体积更大。
  5. 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis的负载较高时,RDB 比 AOF 具好更好的性能保证。
  6. RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 方式更健壮。官方文档也指出,AOF 的确也存在一些 BUG,这些 BUG 在RDB 没有存在。

关于RDB和AOF的思考和选择

如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB 是最好的,定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,而且使用 RDB 还可以避免 AOF 一些隐藏的 bug;否则就使用 AOF 重写。但是一般情况下建议不要单独使用某一种持久化机制,而是应该两种一起用,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。Redis后期官方可能都有将两种持久化方式整合为一种持久化模型。

RDB VS AOF
在这里插入图片描述

redis发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 的 subscribe 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

在这里插入图片描述
在这里插入图片描述
为什么要用发布订阅?

熟悉消息中间件的同学都知道,针对消息订阅发布功能,市面上很多大厂使用的是kafka、RabbitMQ、ActiveMQ, RocketMQ等这几种,redis的订阅发布功能跟这三者相比,相对轻量,针对数据准确和安全性要求没有那么高可以直接使用。

Redis有两种发布/订阅模式:
基于频道(Channel)的发布/订阅
基于模式(pattern)的发布/订阅(可以自学)
基于频道(Channel)的发布/订阅 操作命令如下
在这里插入图片描述
“发布/订阅” 包含2种角色:发布者和订阅者。发布者可以向指定的频道(channel)发送消息;订阅者可以订阅一个或者多个频道(channel),所有订阅此频道的订阅者都会收到此消息。
在这里插入图片描述订阅者订阅频道 subscribe channel [channel …]

--------------------------客户端1(订阅者) :订阅频道 ---------------------
# 订阅 “mrtt” 和 “csdn” 频道(如果不存在则会创建频道)
127.0.0.1:6379> subscribe mrtt csdn
Reading messages... (press Ctrl-C to quit)
1) "subscribe" -- 返回值类型:表示订阅成功!
2) "mrtt" -- 订阅频道的名称
3) (integer) 1 -- 当前客户端已订阅频道的数量
1) "subscribe"
2) "csdn"
3) (integer) 2
#注意:订阅后,该客户端会一直监听消息,如果发送者有消息发给频道,这里会立刻接收到消息

发布者发布消息 publish channel message

--------------------------客户端2(发布者):发布消息给频道 -------------------
# 给“mrtt”这个频道 发送一条消息:“I am mrtt”
127.0.0.1:6379> publish mrtt "I am mrtt"
(integer) 1 # 接收到信息的订阅者数量,无订阅者返回0

客户端2 (发布者) 发布消息给频道后,此时我们再来观察 客户端1 (订阅者) 的客户端窗口变化:

# --------------------------客户端1(订阅者) :订阅频道 -----------------
127.0.0.1:6379> subscribe mrtt csdn
Reading messages... (press Ctrl-C to quit)
1) "subscribe" -- 返回值类型:表示订阅成功!
2) "mrtt" -- 订阅频道的名称
3) (integer) 1 -- 当前客户端已订阅频道的数量
1) "subscribe"
2) "csdn"
3) (integer) 2
---------------------变化如下:(实时接收到了该频道的发布者的消息)------------
1) "message" -- 返回值类型:消息
2) "mrtt" -- 来源(从哪个频道发过来的)
3) "I am mrtt" -- 消息内容

redis集群

概念

在这里插入图片描述
使用redis的复制功能创建主机和从机(一对多) 主从机支持多个数据库之间的数据同步。一类是主数据库(master主机)一类是从数据库**(slave从机)(主从复制)**

读写分离主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的(读写分离)

从机接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库(只有一个老大)通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

这样大部分80%的操作都是读取数据,所以在之前给大家介绍的架构图中,读写分离的方式,从而减轻服务器压力,这样也就是集群的环境了。
一般推荐搭建方式为1主2从为最低配。
主从复制(redis集群)的作用
1 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式,在大数据领域,冗余一般是指一模一样的数据存储多于一份的情况.

2 数据灾备(故障恢复): 当主节点出现问题时,可以由从节点提供服务,实现快速的故障服务

3 负载均衡:主从复制的基础之上,可以实现读写分离,提高并发了量。

4 高可用(集群)基础:主从复制是哨兵和集群实施的基础,因此说redis的主从复制是高可用的基础(集群环境的基础)

所以在真实的项目中,我们不可能是单机模式,基本都是搭建redis集群,实现高可用和高并发。

集群基础搭建(单机多集群)

基础命令

# info 查看所有配置信息 -- 信息太多。
# info server 服务器信息
# info clients 表示已连接客户端信息`在这里插入代码片`
# info cpu CPU 计算量统计信息
# info replication 主从复制信息 **************************

首先查看当前环境信息

# Replication
role:master #表示当前环境为主机
connected_slaves:0 #集群从机连接的数量
master_failover_state:no-failover
master_replid:a38595b7159c4f304a57e43c6352259afd396799
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

准备工作

将存储方式改为rdb
搭建1主2从集群 6379 6380 6381
多复制2份 redis-config 文件 并修改对应的端口号和dump6379.rdb dump6380.rdb dump6381.rdb
修改pidfile记录文件
修改启动日志文件名

# 主机配置 - 6379
# 主机端口port --> 6379 不用修改
# pidfile --> 守护进程产生的文件 默认redis_6379 主机也不用改
# 日志logfile --> 改成"6379.log"
# 数据库文件dbfilename --> dump6379.rdb
#从机配置-1 6380
# 主机端口port --> 6380
# pidfile --> 守护进程产生的文件 默认redis_6380
# 日志logfile --> 改成"6380.log"
# 数据库文件dbfilename --> dump6380.rdb
#从机配置-2 6381
# 主机端口port --> 6381
# pidfile --> 守护进程产生的文件 默认redis_6381
# 日志logfile --> 改成"6381.log"
# 数据库文件dbfilename --> dump6381.rdb

一主二从配置

默认情况下,每一台redis服务器都是主节点 - 所以我们只要配置从机就可以了!!

分别连接客户端(对应端口登录) 通过 info replication查看情况 — 默认都是主机

配置策略 – 小黄人找大哥(认老大,认主)主(6379) 从(6380,6381)

配置命令
slaveof ip port

#===========6380认老大==============
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
#=============6381认老大===============
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
# ==============查看主机==============
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=168,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=168,lag=1

测试:数据是否同步。
注意:我们这里使用的是命令配置的,如果服务器重启就会消失配置,真是的企业环境都配置到配置文件中,这样就是永久配置了。

读写分离(默认)

redis实现了主从复制之后默认就是读写分离的。如果想set数据,那么只能在master主机中才能进行存储。

# 主机--写
# 从机--读
# 并且主机中所有的数据都会被从机保存

测试从机写数据

127.0.0.1:6380> set k110 ceshi
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380>

测试主机宕机从机是否还能正常读取

127.0.0.1:6380> keys *
1) "k1" #值存在
127.0.0.1:6380> info replication
# Replication
role:slave #并且依然是从机模式
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0

测试恢复主机,测试是否还能同步(可以的)

总结:主机断开,从机依然能正常获取数据,但是没有写操作了,当主机恢复后,依然能够同步数据(高可用)

测试从机宕机,主机继续增加数据,查看从机是否获取,从新连接之后继续增加数据查看是否能获取

127.0.0.1:6380> shutdown
not connected> exit
[root@sunwz redis-7.0.4]# redis-server sunwz/redis6380.conf
[root@sunwz redis-7.0.4]# redis-cli -p 6380 --raw
127.0.0.1:6380> get u
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:d7cead93f6383df180b3c1de6750d425049c8612
master_replid2:c84b86fcd5b2f7d723431edbfe33d1d4abf28152
master_repl_offset:7661
second_repl_offset:7662
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:7662
repl_backlog_histlen:0
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> get u
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_read_repl_offset:7928
slave_repl_offset:7928
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:c84b86fcd5b2f7d723431edbfe33d1d4abf28152
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:7928
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:7929
repl_backlog_histlen:0
127.0.0.1:6380> keys *
stu
u
ww
name
127.0.0.1:6380> get u
123
127.0.0.1:6380>

从机宕机后,此时剩余的一主一从可以正常工作,从机6380恢复之后,不能直接同步数据,原因是通过命令进行挂载的主机,重新启动后默认当前6380是主机,如果继续保持这个集群环境,需要我们再次通过slaveof host port 指定老大,指定之后所有的数据会同步到从机中。

如果我们这个集群通过配置文件搭建的,是否能够避免上述问题。

修改配置文件

# replicaof <masterip> <masterport>
# replicaof 127.0.0.1 6379 #将当前6381 挂载到6379上

重新启动从机

127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_read_repl_offset:8754
slave_repl_offset:8754
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:c84b86fcd5b2f7d723431edbfe33d1d4abf28152
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:8754
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:8279
repl_backlog_histlen:476
127.0.0.1:6381> keys *
ww
name
stu

主从复制原理

全量复制(第一次挂载<手动命令>或从机启动<配置文件>)
1 Slave启动成功连接到master后会发送一个sync(同步)命令;(从机发送sync的同步命令)
2 Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令(收集数据),在后台进程执行完毕之后,

#master将传送整个数据文件到slave,以完成一次完全同步;

增量复制(挂载之后主节点再次新增数据)
1 Master继续将新的所有收集到的修改命令依次传给slave,完成同步
注意:但是只要是重新连接master,一次完全同步(全量复制)自动执行。

哨兵

如果主节点宕机,我们在从节点中可以手动指定一个从机从而成为主机,例如上图中6380 我们可以使用slaveof no one 让自己成为主机!!

当然这种模式依然是手动操作(谋朝篡位)如果6379宕机后恢复,虽然是老大,但是没有从机相连了,因为已经被谋篡!! 只能从新连接了!!

这种情况是比较消耗运维人员的,不及时。

哨兵的优势:# 相比上面手动指定老大,那么哨兵模式就是自动选举老大!

概念

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。

其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

单哨兵模式
在这里插入图片描述哨兵有两个作用

# 1 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
# 2 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
# 然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。
# 各个哨兵之间还会进行监控,这样就形成了多哨兵模式用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,
# 仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。
# 当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。
# 切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

哨兵集群模式
在这里插入图片描述

配置单哨兵(redis一主二从)

在这里插入图片描述

复制这文件到自己的文件夹中。

[root@sunwz redis-7.0.4]# cp sentinel.conf sunwz/
[root@sunwz redis-7.0.4]# ls sunwz/
dump.rdb redis6379.conf redis6380.conf redis6381.conf redis.conf sentinel.conf
[root@sunwz redis-7.0.4]#

配置文件中,默认就是监测6379,单哨兵我们就可以用更改任何信息

# 文件配置内容 monitor(监视器) 1 为故障发起投票 2
# sentinel monitor 哨兵命名 地址 端口 1
sentinel monitor mymaster 127.0.0.1 6379 2

启动哨兵

redis-sentinel /opt/redis-6.0.6/it-sunwz/sentinel.conf
[root@sunwz redis-7.0.4]# redis-sentinel sunwz/sentinel.conf
30370:X 09 Sep 2022 03:12:46.069 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
30370:X 09 Sep 2022 03:12:46.069 # Redis version=7.0.4, bits=64, commit=00000000,
modified=0, pid=30370, just started
30370:X 09 Sep 2022 03:12:46.069 # Configuration loaded
30370:X 09 Sep 2022 03:12:46.070 * Increased maximum number of open files to 10032
(it was originally set to 1024).
30370:X 09 Sep 2022 03:12:46.070 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 7.0.4 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 30370
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
30370:X 09 Sep 2022 03:12:46.071 # WARNING: The TCP backlog setting of 511 cannot be
enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
30370:X 09 Sep 2022 03:12:46.116 * Sentinel new configuration saved on disk
30370:X 09 Sep 2022 03:12:46.116 # Sentinel ID is
20d728622b7d4344067fba4a75a37c91e81d8bef
# 监视主机
30370:X 09 Sep 2022 03:12:46.116 # +monitor master mymaster 127.0.0.1 6379 quorum 2
# 监视从机
30370:X 09 Sep 2022 03:12:46.119 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @
mymaster 127.0.0.1 6379
30370:X 09 Sep 2022 03:12:46.140 * Sentinel new configuration saved on disk
# 监视从机
30370:X 09 Sep 2022 03:15:46.865 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @
mymaster 127.0.0.1 6379

测试主机宕机

81499:X 09 Sep 2022 03:23:59.596 # +monitor master mymaster 127.0.0.1 6379 quorum 1
81499:X 09 Sep 2022 03:25:28.368 # +sdown master mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.368 # +odown master mymaster 127.0.0.1 6379 #quorum 1/1
81499:X 09 Sep 2022 03:25:28.368 # +new-epoch 1
81499:X 09 Sep 2022 03:25:28.368 # +try-failover master mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.371 * Sentinel new configuration saved on disk
81499:X 09 Sep 2022 03:25:28.371 # +vote-for-leader20d728622b7d4344067fba4a75a37c91e81d8bef 1
81499:X 09 Sep 2022 03:25:28.371 # +elected-leader master mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.371 # +failover-state-select-slave master mymaster127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.461 # +selected-slave slave 127.0.0.1:6380 127.0.0.16380 @ mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.461 * +failover-state-send-slaveof-noone slave127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 637981499:X 09 Sep 2022 03:25:28.552 * +failover-state-wait-promotion slave127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.927 * Sentinel new configuration saved on disk
81499:X 09 Sep 2022 03:25:28.927 # +promoted-slave slave 127.0.0.1:6380 127.0.0.16380 @ mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.927 # +failover-state-reconf-slaves master mymaster127.0.0.1 6379
81499:X 09 Sep 2022 03:25:28.992 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.16381 @ mymaster 127.0.0.16379
81499:X 09 Sep 2022 03:25:29.944 * +slave-reconf-inprog slave 127.0.0.1:6381127.0.0.1 6381 @ mymaster127.0.0.1 6379
81499:X 09 Sep 2022 03:25:29.944 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.16381 @ mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:30.012 # +failover-end master mymaster 127.0.0.1 6379
81499:X 09 Sep 2022 03:25:30.012 # +switch-master mymaster 127.0.0.1 6379 127.0.0.16380
81499:X 09 Sep 2022 03:25:30.012 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @mymaster 127.0.0.1 6380
81499:X 09 Sep 2022 03:25:30.012 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @mymaster 127.0.0.1 6380
81499:X 09 Sep 2022 03:25:30.014 * Sentinel new configuration saved on disk
81499:X 09 Sep 2022 03:26:00.030 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @mymaster 127.0.0.1 6380

测试6379恢复

81499:X 09 Sep 2022 05:46:10.213 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.16379 @ mymaster 127.0.0.1 6380

哨兵模式总结

优点
1. 哨兵集群 基于主从复制模式,所有主从复制的优点它全有
2. 主从可以切换,故障可以转移,系统可用性高
3. 哨兵模式就是主从复制的升级,从手动到自动,更加完善。
缺点
1. 一旦配置了哨兵集群,redis的扩容就会比较复杂,一旦容量到达上限在线扩容比较麻烦。
2. 实现哨兵配置集群文件也比较多,类似与redis主从配置,需要很好的设计思路。

缓存穿透、雪崩、击穿

面试高频 + 工作常用!
redis的使用,极大地提升了应用程序的性能和效率,但同时也会遇见一些问题,最重要的就是数据一致性问题,从严格
意义上讲,这个问题是无解的,如果真的要求数据一致性极高,那么就不能使用缓存,另外暴露的一些问题就是缓存穿透、缓存雪崩、缓存击穿问题。目前业界也有比较流行的解决方案。

缓存穿透

在这里插入图片描述查询的数据对应的数据在数据源并不存在,每次针对此这个数据的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。若黑客利用此漏洞进行攻击可能压垮数据库,同时互联网项目瞬间高并发(活动、秒杀等)也会直接压垮数据库,这就是缓存穿透。

解决方案1 - 布隆过滤器
布隆过滤器,英文叫BloomFilter,可以说是一个二进制向量和一系列随机映射函数实现。 可以用于检索一个元素是否在一个集合中,实现原理bitmaps,那么我们就可以在查询之前进行校验,不符合就丢弃或者其他处理,避免了对于服务器的压力。
在这里插入图片描述
解决方案2 - 缓存空对象
在这里插入图片描述当存储层查询不命中的时候,如果MySQL返回空,那么我们也将这个空对象放进缓存中,这样其他认再查询的时候就直接从缓存中读取空对象,从而减轻底层数据库压力。

这种方式有弊端:缓存空值也会占缓存空间,并且缓存了空值的同时可能造成缓存和存储层数据不一致的情况(缓存有空值,持久层有了),会有业务一致性的影响。

缓存击穿
(redis数据过期的瞬间查太多)
在这里插入图片描述
解决方案1

# 设置热点数据永不过期 --- 更推荐 尽量热点数据过期时间长一点。

解决方案2

# 使用分布式锁,保证每一个key只有一个线程去数据库中查询数据,其他没有分布式锁权限的线程等待,这种方式将高并发的压力转移到了分布式锁上,因此对于锁的考验也很大。

缓存雪崩

(数据集中失效)
在这里插入图片描述雪崩就是指缓存中的数据集中过期失效 (或集群宕机–扫地的又把我服务器的电源踢掉了 | 服务器节点宕机 |服务器断网)产生雪崩原因之一,比如马上双12了0点,大家准备好了抢购商品,这波商品集中放进缓存中,假设设定的时间为1小时,那么到了凌晨1点的时候,这批商品的缓存就过期了而对这批商品的查询就落到了数据库上,对于数据库而言,就会产生后周期性的压力波峰,存储层的调用也会暴增,也会造成数据库挂掉,这种现象叫缓存雪崩现象。

解决方案1 - redis高可用

既然可能出现redis挂掉问题,那我就多设几台redis,这样一台挂掉其他依然可以使用,这就是我们上面的redis集群。

解决方案2 - 限流降级(分布锁同理 | 队列)

解决思想:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如某一个key只允许有一个key线程查询和写缓存,其他线程等待。在这里插入代码片

解决方案3 - 数据预热

数据预热含义就是再正式部署之前,先将可能高并发的数据查询一遍,写入到缓存中,这样部分可能大量访问的数据就会加载到缓存中然后设计不同的过期时间,让缓存失效均匀点,避免雪崩。

springboot+redis 整合

整合应用

创建项目添加redis依赖或手动添加。并配置连接信息

单机整合

# redis
spring:
	redis:
		host: 192.168.31.100
		port: 6379

通过单元测试访问redis

@SpringBootTest
public class RedisExample {
	@Autowired
	private RedisTemplate redisTemplate;
	/**
	* 测试连接redis
	*/
	@Test
	public void ts01(){
		//System.out.println("redisTemplate = " + redisTemplate);
		redisTemplate.opsForValue().set("ww","123");
		Object ww = redisTemplate.opsForValue().get("ww");
		System.out.println(ww);
	}
}

无法显示中文
无法直接在redis中通过key获取数据
原因:SpringBoot在存储redis数据的时候使用的是默认JDK序列化进行存储,导致添加到redis中redis不识别。
解决方案:统一做一些序列化的控制 - 在启动类中添加@Bean

package com.its;
@SpringBootApplication
public class RedisWebApplication {
	public static void main(String[] args) {
		SpringApplication.run(RedisWebApplication.class, args);
	}
@Bean
@SuppressWarnings("all")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactoryfactory) {
		RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
		template.setConnectionFactory(factory);
		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
		Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(om);
		StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
		// key采用String的序列化方式
		template.setKeySerializer(stringRedisSerializer);
		// hash的key也采用String的序列化方式
		template.setHashKeySerializer(stringRedisSerializer);
		// value序列化方式采用jackson
		template.setValueSerializer(jackson2JsonRedisSerializer);
		// hash的value序列化方式采用jackson
		template.setHashValueSerializer(jackson2JsonRedisSerializer);
		template.afterPropertiesSet();
		return template;
	}
}

整合redis集群(单哨兵)

添加jar包

				<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

添加redis的链接池

logging:
  level:
    org.springfreamework: debug
    org.apache: debug
    edu.xja: debug
    io.lettuce.core: debug

spring:
  datasource:
    url: jdbc:mysql:///shiyan6?serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
  redis:
    sentinel:
      master: mymaster
      nodes: 192.168.66.66:26379

添加配置类

@Configuration
public class RedisConfiguration {
    /**
     *  配置redis序列化json
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    @Primary    //若有相同类型的Bean时,优先使用此注解标注的Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 为了开发方便,一般直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        // 配置具体的序列化方式
        // JSON解析任意对象
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        // 设置日期格式
        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key采用String的序列化
        template.setKeySerializer(stringRedisSerializer);
        //hash的key也采用String的序列化
        template.setHashKeySerializer(stringRedisSerializer);
        //value的序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        //设置所有配置
        template.afterPropertiesSet();
        return template;
    }
    /**
     * 配置读写分离
     * @param redisProperties
     * @return
     */
    @Bean
    public RedisConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {
        // 配置哨兵节点以及主节点
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
                redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes())
        );
        // 配置读写分离
        LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                // 读写分离,这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择
                // MASTER   仅读取主节点
                // MASTER_PREFERRED   优先读取主节点,如果主节点不可用,则读取从节点
                // REPLICA_PREFERRED   优先读取从节点,如果从节点不可用,则读取主节点
                // REPLICA   仅读取从节点
                // NEAREST   从最近节点读取
                // ANY   从任意一个从节点读取
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();
        return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);
    }
}

启动测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值