什么是Redis?
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI (C语言)编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis特点
Redis用途
全页面缓存:存储将长请求的热点页面,减少请求最多的页面的延迟
会话缓存:Redis可以保留数据,以便在缓存停止的情况下,重启时,所有数据仍然存在
消息队列
顺序排列
pub/sub
Redis有16个数据库,默认使用的是第0个,这些都可以在配置文件中看到
使用select切换数据库
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]>
查看数据库大小
127.0.0.1:6379[3]> DBSIZE
(integer) 0
查看数据库所有的key
127.0.0.1:6379[3]> keys *
1) "name"
清空当前数据库
127.0.0.1:6379[3]> flushdb
OK
清空所有数据库
127.0.0.1:6379> flushall
OK
Redis为什么这么快
注意:Redis的单线程指的是处理我们网络请求的时候只有一个线程处理,一个redis-server服务运行的时候肯定不止一个线程
Redis的瓶颈不是CPU,而是内存大小和网路带宽
查看数据库中所有的key
127.0.0.1:6379[3]> keys *
1) "name"
判断数据库中有没有这个key
127.0.0.1:6379> EXISTS name
(integer) 0
移除key
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> move name 1 #将key和它关联的值放到指定的数据库中
(integer) 1
127.0.0.1:6379> keys *
(empty array)
设置key过期时间
设置10s过期
127.0.0.1:6379> EXPIRE name 10
(integer) 0
使用ttl key可以查看当前key的生命周期
查看key的类型
127.0.0.1:6379> type age
string
key - value
常用命令:
set key value #设置值
get key #获取值
keys * #获得所有的key
EXISTS key #判断key是否存在
APPEND key value #往某个key中添加值
STRLEN key #获取字符串的长度
##################################################################
incr key #i++
decr key #i--
incrby key increment #以步长为increment进行增加
decrby key decrement #以步长为decrement进行减小
##################################################################
getrange key start end #截取字符串[start,end]
setrange key offset value #将key中offset起始位置的值换位value 比如对于字符串abcdefg 执行 setrange str 1 xx 结果为 axxdefg
#################################################################
setex key second value #将key的值设置为value,并且在second秒后消失,如果key已存在,那么新值就会覆盖旧值 原子操作(设置值和设置时间同时完成)
setnx key value #如果不存在,就创建,如果存在就创建失败
#################################################################
mset key value [key value ...] #批量设置值
mget key [key ...] #批量获取值
msetnx key val [key val ...] #msetnx批量设置不存在的值,如果有一个出错,那么其他都会失败 原子操作
################################################################
#对象
#比如下面
set user:1 {name:lisi,age:20} #设置对象
mset user:1:name lisi user:1:age 20 #也可以这样设置
################################################################
getset key value #先获取后设置
key - list
LPUSH key value [value ...] #将一个或多个值插入到链表头部,如果是多个值先将前面的值插入到链表头部
lrange key start stop #将链表中[start,end]中的值输出
rpush key value [value ...] #将一个或多个值插入到链表尾部,如果有多个值先插入前面的值
##########################################################################
#移除链表中的值
lpop key [count] #将链表左侧的值弹出count个 默认一个
rpop key [count] #将链表右侧的值弹出count个 默认一个
###########################################################################
lindex key index #得到链表中下标为index的值
###########################################################################
llen key #得到链表的长度
###########################################################################
#移除指定个数的值
lrem key count element #删除指定个数的值
############################################################################
trim 截取list
ltrim key start stop #将[start,stop]的值截取出来
##################
rpoplpush source destination #将source列表的最后一个元素推出放入目标列表destination的头部
lset key index element #设置链表指定位置的值,如果链表不存在或者下标超出链表长度都会设置失败
linsert key before|after piovt element #在链表中指定元素的前面或者后面插入元素
list实际上就是一个链表,左边和右边都可以插入值
如果key不存在就会创建新的链表
如果key存在,就会新增内容
如果移除了链表中所有的元素,就会变成一个空链表,也代表不存在
key - set,无序
set中的值不能重复,基本操作:
sadd key member [member ...] #往set中添加元素
smembers key #查看set中的所有元素
sismember key member #判断member是不是set中的元素
#############################################################
scard key #获取当前set中值的个数
############################################################
srem key element #移除set中的指定元素
############################################################
srandmember key [count] #随机从指定set中抽选出几个元素,默认是1个
############################################################
spop key #随机删除set集合中的元素
############################################################
smove source destination member #将源set集合中指定元素移到目标set集合
############################################################
并交差集:将第一个key和后面的key进行比较,如果只有一个key结果就是其本身
sdiff key [key ...]
sinter key [key ...]
sunion key [key ...]
key - map
hset key field value [field value ...] #设置hash集合,给hash集合中的字段设置值
hget key field #获取hash集合中field字段的值
hmset key field value [field value ...] #设置hash集合,给hash集合中的字段设置值
hmget key field [field ...] #获取hash集合中的多个字段值
hgetall key #获取hash集合中所有字段及其字段对应的值
########################################################
hdel key field [field ...] #删除hash集合中的字段
hlen key #获取hash集合中字段的个数
hexists key field #判断hash集合中是否存在field
########################################################
hkeys key #获取hash集合中的所有字段
hvals key #获取hash集合中的所有value
##########################################################
hincrby key field increment #使得hash集合中的字段值增加increment
hsetnx key field value #如果hash集合中这个字段不存在则创建
hash可以用来存储对象
zset是有序集合,可以进行排序
zadd key score member #设置有序集合key,并给其添加score表示其权重,member是要添加的元素
##########################################################################
zrem key member [member ...] #删除有序集合key中的元素
##########################################################################
zrange key min max #显示有序集合下标在min和max之间的值,如果输入0,-1则为显示全部,按照score顺序显示
zrevrange key start stop #显示有序集合下标在start和stop之间的值,如果输入0,-1则为显示全部,按照score逆序显示
zrangebyscore key min max #显示有序集合中score在min和max之间的值
##########################################################################
zcard key #输出有序集合中元素的个数
zcount key min max #获取指定区间的成员数量
zrange也有很多选项zrange key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
geospatial就是redis中的一个有序集合,通过有序集合的命令可以进行删除
#geoadd添加地理位置
geoadd key longitude latitude member [longitude latitude member ...] #将城市的经度纬度和城市名录入key中
#geopos获取经纬度
geopos key member [member ...] #获取key中的指定member的经纬度
#geodist获取两个位置的距离,如果两个位置中其中一个不存在返回空
geodist key member1 member2 [m|km|ft|mi] #获取两个位置的距离,默认是m
#georadius以给定的经度纬度为中心,找出某一半径内的元素
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
例:
#georadiusbymember找出位于指定元素周围距离内的其他元素
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
例:
#geohash返回11个字符的geohash字符串,表示当前城市的经纬度
#如果两个字符串越接近,距离越近
geohash key member [member ...]
传统的统计基数方法:set,bitmaps。
set存储的元素是不重复的,可以用来统计基数。
bitmaps是一种特殊的数据结构,实质上是一个字符串,操作单元是位。
在允许容错的前提下可以使用hyperloglog进行统计
#创建元素 元素是基数
pfadd key element [element ...]
#统计key中元素的数量
PFCOUNT key [key ...]
#合并多个sourcekey到destkey
pfmerge destkey sourcekey [sourcekey ...]
位存储,通过二进制位来进行记录,就0和1两个状态。使用bitmaps可以节省大量空间,它的最大长度为512mb,因此最多可以存储5112*1024*1024*8≈40亿个不同的比特位。
比如在存储id自增来表示用户的系统中,仅用512mb内存就能存储40亿个用户的单比特信息。比如显示用户是否感染病毒,1表示感染,0表示未感染。用512mb就能存储40个用户的感染状态。
如果是存储在数据库中,不仅要设置用户的id,还要设置是否感染这个字段。
#设置bitmaps中第几位的值
setbit key offset value
#获取bitmaps中第几位的值
getbit key offset
#统计bitmaps中start到end之间值为1的数量
bitcount key [start end]
redis中单条命令保持原子性,但是事务不保持原子性
redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行
redis中事务与msql中的事务是有区别的
事务执行过程:
multi
exec
放弃事务
如果想在事务执行前放弃事务,可以使用discard
命令来放弃事务,这个事务中的命令就不会执行
redis中异常
redis异常有编译型异常和运行时异常:
编译型异常:指命令有问题,整个事务是不会执行的
运行时异常:在运行过程中一条命令出现错误,只有这条命令执行失败,其他命令能正常执行。
监控!watch
通过watch,redis可以实现乐观锁
#对指定的key进行监视,在watch获取key的值,如果有线程在中间修改了值,在事务执行(exec)时检查当前key的值和监视时的值是否相同
#如果相同,事务正常执行,然后自动取消监视
#如果不同,事务执行失败,要手动取消监视
watch key [key ...]
#取消监视
unwatch
使用jedis连接linux中的redis-server需要修改redis.conf文件
另外还要使用防火墙将6379端口号放到开放端口
1.配置文件 unit单位 对大小写不敏感
包含
通过include
可以将其他的配置文件包含进来,就比如spring中的import标签
网络
bind 127.0.0.1 #绑定的ip
protected-mode yes #保护模式
port 6379 #默认端口
通用
daemonize yes #是否以守护进程运行(后台进程),默认为no
pidfile /var/run/reids_6379.pid #如果以后台方式运行,需要指定一个进程文件
loglevel notice #日志
logfile "" #日志的文件名
databases 16 #数据库的数量,默认16个
always-show-logo #是否总是显示log
快照
快照就涉及到持久化操作:在规定的时间内,执行多少次操作,则会持久化到文件.rbd.rof
#如果在900s内至少1个key进行了修改,我们就进行持久化操作
save 900 1
#如果在300s内至少10个key进行了修改,我们就进行持久化操作
save 300 10
#如果在60s内至少10000个key进行了修改,我们就进行持久化操作
save 60 10000
#我们也可以自己在redis.conf定义持久化规则
stop-writes-on-bgsave-error yes #如果持久化出错是否继续工作
rdbcompression yes #是否进行压缩,对文件进行压缩会消耗cpu资源
rdbchechsum yes #保存早rdb文件的时候,进行错误检查校验
dir ./ #rdb文件保存目录
security安全
我们可以在配置文件中设置redis的密码,也可以通过命令来设置密码
配置文件
命令
config get requirepass #获取redis的密码
config set requirepass #设置redis的密码
#设置完密码之后我们登陆的时候就需要密码来进行登陆
auth ******* #使用密码登陆
限制客户端
maxclients 100000 #设置最多客户端连接数
maxmemory <bytes> #设置redis最大容量
maxmemory-policy noeviction #内存到达上限后的处理策略
append only mode aof配置
appendonly模式默认是关闭的:appendonly no
持久化文件名:appendfilename appendonly.aof
appendfsync always #每次修改都会同步,但是消耗性能
appendfsync everysec #每秒同步,可能会丢失这1s数据
appendfsync no #不执行同步,交给操作系统
redis是内存数据库,如果不能将内存中的数据库保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态就会消失,所以redis提供了持久化功能
在redis.conf中可以设置RDB的时间间隔。
在指定的时间内将内存中的数据集写入磁盘,也就是snapshot快照,恢复的时候将快照文件直接写到内存里。
Redis会单独创建一个子进程来进行持久化,先将数据写入到一个临时文件中,待持久化都结束了,再用这个临时文件替换上次持久化号的文件。整个过程中主进程不用进行IO操作的。这样保持了极高的性能。
如果需要进行大规模的数据恢复,且对于恢复数据的完整性不是非常敏感,那么RDB方式要比AOF方式更加高效。
rdb的保存文件是dump.rdb
RDB机制
对于RDB来说,是通过把某个时刻所有数据生成一个快照来保存,那么就有一种触发机制
该命令会阻塞当前redis服务器,执行save命令期间,redis不能处理其他命令,直到RDB过程完成。
但是通常客户端有很多个,这种方式显然是不可取的
执行该命令时,redis会在后台异步进行快照工作,快照同时还可以响应客户端请求。
具体操作就是redis执行fork操作创建子进程,rdb的持久化工作由子进程执行,完成后自动结束
在我们的redis.conf文件中有我们save的条件,比如save 60 10000
:在60s内有1万个key被修改就会触发bgsave
注意:
如何恢复rdb文件
将rdb文件放到redis启动的目录下(通过config get dir
可以查看)即可,redis启动时会自动检查dump.rdb恢复其中的文件
优点和缺点
优点
缺点
因为子进程在进行备份的时候,主进程修改数据子进程不会反映出来,如果这个时候redis服务宕机,那么最后一次的数据就会丢失
AOF会保存(追加)服务器执行的所有写操作到日志文件中,在服务重启以后,会执行这些命令来重新构建数据。
对于AOF形式的持久化操作,redis恢复数据时会根据日志文件的内容将写指令从前到后执行一次完成数据的恢复。
AOF默认文件名是 appendonly.aof
AOF文件默认是不开启的,我们需要手动进行配置,只需将 appendonly 设置为 yes即可
如果AOF文件有错误,redis是启动不起来的,我们可以通过 redis-check-aof 来修复aof文件,命令:redis-check-aof --fix XXX.aof
AOF持久化策略
命令写入后立即同步到aof文件,这种情况下每次写命令都会同步到aof文件,性能会降低,硬盘I/O成为性能瓶颈
命令写入后,同步文件操作由专门的线程每秒调用一次,everysec是其他两种策略的折中,是性能和数据安全性的平衡
命令写入后不会对aof文件进行立即同步,同步由操作系统进行负责,通常同步周期为30s,这种情况下缓冲区中堆的数据会很多,数据安全性无法保证
发布订阅是一种消息通信模式,发送者发送信息,订阅者接受信息。比如:微信公众号,微博
发送者往一个频道中发送信息,接受者从一个频道中接受信息
命令
每个Redis服务器应该运行在不同的ip上,无论是主机还是从机还是一个集群中的服务器
概述
Redis集群实现了对redis的水平扩容,即启动N个节点,将整个数据库分布存储在这N个节点中,每个节点存储中数据的1/N
Redis集群通过分区来提供一定程度的可用性:即使集群中有一部分节点失效或者无法通讯,集群也可以处理命令请求
问题
容量不够,redis如何进行扩容?
并发写操作,redis如何分摊?
设置集群,配备多台服务器,并且在多态服务器上都可以进行写操作。
无中心集群模式
一个集群中的不同的服务器,负责不同的模块,使用无中心集群模式,当客户端请求的不是当前模块,当前模块可以将请求进行转发
redis cluster 配置修改
cluster-enable yes #打开集群模式
cluster-config-file filename.conf #设置节点的配置文件名
cluster-node-timeout seconds #设置节点失联事件,超过该时间,集群自动进行主从切换
合并集群
在服务都启动后,将这个节点进行合并
redis-cli --cluster create --cluster-replicas 1 需要合并的redis服务器(host port形式)
--cluster-replicas 1
表示以最简单的方式配置集群,一台主机一台从机
redis-cli -c -p 6379
任意一个端口都可以cluster nodes
查看集群情况,由于采用最简单方式配置集群即一主一从redis-cli --cluster create --cluster-replicas 1 192.168.116.128:6379 192.168.116.128:6380 192.168.116.128:6381 192.168.116.128:6389 192.168.116.128:6390 192.168.116.128:6391
一个redis集群包括16384个插槽,数据库中每个键都属于这16384个插槽中的其中一个
集群使用公式 CRC16 (key) % 16384 来计算key属于哪个槽,其中CRC16(key)语句用于计算key的CRC16校验和
不同的主机负责不同范围之间的操作
注意:不在一个slot下的键值,是不能用mget,mset等多键操作,想要使用这种操作,需要设置在set或者get的时候使用组,在key的后面添加{组名},然后在set或者get的时候使用组名来计算slot的值
查询集群中的值
#计算key的slot值
cluster keyslot key
#计算插槽中有几个key,只能查看自己插槽中的值
cluster countkeysinslot 插槽值
#返回count个在slot插槽中的键
cluster getkeysinslot slot count
cluster-require-full-coverage
参数,yes为不能提供服务,no为能提供服务指将一台redis服务器的数据,复制到其他的redis服务器。前者称为主节点,后者称为从节点;数据的复制是单向的,只能从主节点到从节点。主节点以写为主,从节点以读为主
作用
读写分离
读和写操作在不同的服务器上进行,以此来减少服务器的压力
主机从机配置
配置主机从机首先要给每一个服务器配置一个配置文件并且修改配置文件,包括端口号,pid名字,log文件名,dump.rdb文件名,因为多个redis服务不能占用同一个端口号,进程,日志文件,dump.rdb文件。
通过命令来配置主机从机
info replication #查看当前redis服务的信息,查看自身是不是主机,或者连接自己的从机数
slaveof host port #将这台redis服务器作为host下port端口redis服务器的从机
通过命令的形式配置的主从机在重启的时候或者服务器宕机的时候就会失效,通过配置文件配置的主从机关系在重启的时候不会失效
通过配置文件配置主机从机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MlREiKm-1641987190493)(C:/Users/26794/AppData/Roaming/Typora/typora-user-images/image-20220110150706894.png)]
replicaof masterip masterport #将自己作为目标主机的从机
masterauth master-password #如果主机有密码,需要配置主机密码
情况分析
slave no one
让自己变为主机,其他的节点就可以手动连接到最新的这个主节点。此时即使以前的主节点重启后,它也不会是主节点复制原理
从机连接到主机的时候,会发送一个同步命令
主机接收到同步命令后会启动存盘过程,同时收集所有接收到的会修改数据集的命令,在后台进程执行完之后,主机会将整个文件传送到从机,完成一次完全同步
全量复值:从机在接收到数据库文件数据后,会将其存储到磁盘并加载到内存中
增量复制:主机继续将所有收集到修改命令依次传给从机,完成同步
只要从机连接到主机就会执行一次完全同步
概述
哨兵模式是一种特殊的模式,哨兵是一个独立的进程,作为进程,它会独立运行。原理是通过哨兵给各个Redis服务器发送命令,等待Redis服务器响应,从而监控多个Redis实例
redis sentinel 配置文件
sentinel monitor mymaster ip port 1
,其中mymaster为监控对象启的名字,1位至少有多少个哨兵同意迁移的数量redis-sentinel sentinel.conf
单哨兵模式
作用
多哨兵模式
既然我们知道redis服务会存在宕机而需要设置多个redis服务器,那么肯定也需要设置多个哨兵,防止一个哨兵出问题不能进行主机切换,也就产生了多哨兵模式
多哨兵模式中哨兵会监控redis服务器的状态并且哨兵之间也会互相监控
故障切换过程:
假设主服务器宕机,哨兵1先检测到这个现象,系统并不会立即进行failover过程,仅仅是哨兵1认为主服务器宕机,这称为主观下线。当其他的哨兵也检测到主服务不可用时,并且到达一定数量时,那么哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
选举规则
在redis.conf中有slave-priority ,值越小优先级越高
从机中那个机器中的数据和主机中的数据同步值最高
每个redis实例启动后都会随机产生一个40位的runid
概念
用户想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库发出查询请求,发现也没有,于是本次查询失败。当多用户要查的数据都没有在缓存中命中,都去访问持久层数据库,这时候会给持久层带来很大的压力,甚至坏掉。这时候就相当于出现了缓存穿透
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash的形式存储,在控制层先进行校验,不符合要求则丢弃,从而大大减少底层系统的压力
缓存空对象
当存储层不命中时,即使返回的是空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据会从缓存中获取,保护后端数据源
存在问题:
概念
比如一个热点key,在一段时间内访问这个key的次数非常多,在这个过程中,某一时刻这个key过期,在下一时刻key又恢复,那么在这两个时刻的间隙中或有大量的数据因为缓存中查不到而去查找持久层数据库,对数据库造成大量请求。
解决方案
设置热点数据用户过期
加互斥锁:使用分布式锁,在每一时刻保证每个key只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待
缓存雪崩是指缓存中大量数据到期,而查询数据量过大,引起数据库压力过大而宕机,和缓存击穿不同的是缓存击穿是大批量差同一个数据,缓存雪崩是很多数据都过期了,缓存中查不到而去数据库中查
解决方案
由于单机部署的系统演化成分布式集群系统后,由于项目部署在不同的机器上,这就使得单机下部署的策略失效,为了解决这个问题,就需要一种跨jvm的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题
Redis分布式锁
设置值并且设置过期时间:set key value nx ex 12
UUID防止误删
lua脚本
由于删除的操作不是原子性,在判断完uuid之后准备删除的时候,这时锁到期自动删除了,那么这时候删除的不是自己的锁,删的是别人的锁。所以就要使用lua脚本保证删除操作的原子性
为了确保分布式锁可用,至少要确保锁的实现同时满足以下四个条件