Redis安装
1.下载安装包
通过上传将tar.gz压缩包放入opt/下的Redis目录中
2.解压Redis安装包
tar -zxvf redis-6.0.9.tar.gz
3.安装编译环境
yum install gcc-c++
之后使用gcc -v
查看版本,再进行make编译,可使用make install
确认。或者直接使用make && make install
make
make install
4.redis默认安装路径
/usr/local/bin
,可进入查看
5.将redis配置文件复制到当前目录下
mkdir reflectconfigs
cp /opt/Redis/redis-6.0.9/redis.conf reflectconfigs
之后使用这个配置启动redis
6.修改配置文件
redis默认不是后台启动的,修改配置文件!
daemonize no 改为 yes,之后启动都是后台启动,保存再退出
7.启动redis
在/usr/local/bin
下启动
cd /usr/local/bin
ls
redis-server reflectconfigs/redis.conf
redis-cli -p 6379
ping #出现PONG即代表成功
通常:
命令和可选项的名字以大写字母形式出现
命令参数和可选项的值则以小写字母形式出现
Redis简单基本知识
redis默认有16个数据库,可通过vim redis.config查看
默认使用第0个
可以使用select进行切换数据库
redis-server
cd /usr/local/bin
ls
redis-server reflectconfigs/redis.conf
redis-cli -p 6379
ping #出现PONG即代表成功
Redis是单线程的
因为单线程,所以不需要像多线程一样切换线程来浪费时间
官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,所以就使用单线程了!
Redis是c语言写的,官方的数据为10万+的QPS,完全不比使用key-value的Memecache差!
Redis是单线程的,但是为什么还那么快?
误区1:高性能的服务器一定是多线程的? 不一定
误区2:多线程一定比单线程效率高
核心:redis是将所有的数据全部放在内存中的,所以使用单线程去操作效率就是最高的,多线程(CPU上下切换:耗时的操作),对于内存系统来说,没有上下切换效率就是最高的!多次读写都是一个CPU上的,在内存情况下,这个就是最佳的方案!
五大数据类型
keys * #查看所有的key
set name reflect #set key
EXISTS name #判断当前的key是否存在
get name #get key
move name 1 #移除当前的key
EXPIRE name 10 #设置key的过期时间,单位是秒
ttl name #查看当前key的剩余时间
type name #查看当前key的基本类型
flushdb #清空当前库
flushall #清空所有
String(字符串)
##############################################################################
set key1 v1 #设置值
get key1 #获得值
keys * #获得所有的key
EXIST key1 #判断一个key是否存在
APPEND key1 "hello" #追加字符串,如果key不存在,就相当于set key
STRLEN key1 #获取字符串的长度
##############################################################################
incr views #自增1
decr views #自减1
INCRBY views 10 #设置步长,指定增量
DECRBY views 5 #设置步长,指定减量
##############################################################################
#字符串范围range
set key1 "hello,reflect" #设置key1的值
get key1
GETRANGE key1 0 3 #截取字符串[0,3]闭区间
GETRANGE key1 0 -1 #获取全部的字符串 和get key是一样的
#替换
set key2 abcdefg
get key2
SETRANGE key2 1 xx #替换指定位置开始的字符串
get key2
##############################################################################
#setex(set with expire) #设置过期时间
#setnx(set if not exist) #不存在再设置(在分布式锁中经常会用到)
setex key3 30 "hello" #设置key3的值为hello,30秒后过期
setnx mykey "redis" #如果mykey不存在,创建mykey
setnx mykey "MongoDB" #如果mykey存在,创建失败!
##############################################################################
#mset
#mget
mset k1 v1 k2 v2 k3 v3 #同时设置多个值
keys *
mget k1 k2 k3 #同时获取多个值
msetnx k1 v1 k4 v4 #msetnx是一个原子性的操作,要么一起成功,要么一起失败!
get k4 #显示为nil
#对象
set user:1{name:reflect,age:3} #设置一个user:1对象 值为json字符串来保存一个对象!
#这里的key是一个巧妙的设计:user:{id}:{filed},如此设计在Redis是完全ok的
mset user:1:name reflect user:1:age 2
mget user:1:name user:1:age
##############################################################################
getset #先get再set
getset db redis #如果不存在值,则返回nil
get db
getset db mongodb #如果存在值,获取原来的值,并设置新的值
get db
##############################################################################
String类似的使用场景:value除了是字符串还可以是数字!
- 计数器
- 统计多单位数量
- 对象缓存存储
List(列表)
在Redis中,list可以当成栈,队列,阻塞队列!
所有的list命令都是用l开头
##############################################################################
127.0.0.1:6379> LPUSH list one #将一个值或多个值,放入列表的头部(左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 #获取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 #获取指定区间具体的值
1) "three"
2) "two"
127.0.0.1:6379> RPUSH list four #将一个值或多个值,放入列表的头部(右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
##############################################################################
LPOP
RPOP
127.0.0.1:6379> LPOP list #移除list的第一个元素
"three"
127.0.0.1:6379> RPOP list #移除list的最后一个元素
"four"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
##############################################################################
Lindex
127.0.0.1:6379> Lindex list 1 #通过下标获得list中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"
##############################################################################
# Llen
Llen list #返回列表的长度
127.0.0.1:6379> LPush list one
(integer) 1
127.0.0.1:6379> LPush list two
(integer) 2
127.0.0.1:6379> LPush list three
(integer) 3
127.0.0.1:6379> Llen list #返回列表的长度
(integer) 3
##############################################################################
#移除指定的值!
lrem
127.0.0.1:6379> lrem list 1 one ##移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
##############################################################################
trim 修剪
ltrim mylist 0 1 #通过下标截取指定长度
##############################################################################
rpoplpush #移除列表的最后一个元素,并将其移动到新的列表中
##############################################################################
lset list 0 item #如果list不存在列表而更新就会报错
lpush list value1
lset list 0 item #如果存在,则更新当前下标的值
##############################################################################
linsert #将某个具体的value插入到列表中某个元素的前面或后面
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "world"
(integer) 2
127.0.0.1:6379> Linsert mylist before world other
(integer) 3
127.0.0.1:6379> linsert mylist after world new
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
小结:
实际上是一个链表,before Node after,left,right都可以插入值
如果key不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在!
在两边插入或者改动值,效率最高!中间元素,相对来说效率会低一点!
消息排队!消息队列(Lpush、Rpop),栈(Lpush,Lpop)
Set(集合)
set中的值不能重复!
##############################################################################
127.0.0.1:6379> sadd myset "hello" #set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "reflect"
(integer) 1
127.0.0.1:6379> sadd myset "helloworld"
(integer) 1
127.0.0.1:6379> smembers myset #查看指定set的所有值
1) "helloworld"
2) "reflect"
3) "hello"
127.0.0.1:6379> sismember myset world #判断某一个值是不是在set集合中!
(integer) 0
127.0.0.1:6379> sismember myset hello
(integer) 1
##############################################################################
127.0.0.1:6379> scard myset #获取set集合中的内容元素个数!
(integer) 3
##############################################################################
127.0.0.1:6379> srem myset hello #移除set集合中指定元素
(integer) 1
##############################################################################
Srandmember myset 2 #随机抽选出指定个数的元素
Srandmember myset #随机抽选出一个元素
##############################################################################
spop myset #随机删除一些set集合中的元素
##############################################################################
smove myset myset2 "reflect" #讲一个指定的值,移动到另外一个set集合
##############################################################################
SDiFF key1 key2 #差集
SINTER key1 key2 #交集
SUNION key1 key2 #交集
Hash(哈希)
Map集合,key-Map集合!本质和String没有太大区别,还是一个简单的key-value!
##############################################################################
127.0.0.1:6379> hset myhash field1 reflect #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 #获取一个字段值
"reflect"
127.0.0.1:6379> hmset myhash field1 hello field2 world #set 多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 #获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "world"
##############################################################################
127.0.0.1:6379> hdel myhash field1 #删除hash指定key字段!对应的value值也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
##############################################################################
hlen myhash #获取hash表的字段数量
##############################################################################
HEXISTS myhash field3 #判断hash中指定字段是否存在
##############################################################################
hkeys myhash #只获得所有filed
hvals myhash #只获得所有value
##############################################################################
HSET myhash field3 5 #指定增量
HINCRBY myhash filed3 1
HINCRBY myhash filed3 -1
hsetnx myhash filed4 hello #如果不存在则可以设置
hsetnx myhash filed4 world #如果存在则不能设置
##############################################################################
##############################################################################
hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串存储!
Zset(有序集合)
在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1
三种特殊数据类型
Geospatial地理位置
朋友定位,附近的人,打车距离计算
其功能在Redis3.2版本就推出了
只有六个命令
GEOADD
GEODIST
GEOHASH
GEOPOS
GEORANDIUS
GEORADIUSBYMEMBER
#geoadd 添加地理位置
# 规则:地球南北极无法直接添加。一般会下载城市数据,直接通过java程序一次性导入!
#类型key值 值(纬度、经度、名称)
# 有效经度为-180至180度。
# 有效纬度为-85.05112878至85.05112878度。
# 当坐标位置超过上述范围时,该命令将会返回一个错误
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqin 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.90 34.26 xian
(integer) 2
# geopos 获得当前定位
127.0.0.1:6379> GEOPOS china:city beijing #获取指定城市的经度和纬度
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city beijing chongqi
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
# geodist
# 单位必须是以下之一,默认为米:
# m为米。
# lm为公里。
# mi为英里。
# ft为英尺。
127.0.0.1:6379> GEODIST china:city beijing shanghai km #查看上海到北京的直线距离
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing hangzhou km #查看北京到杭州的直线距离
"1127.3378"
# georadius 以给定的经纬度为中心,找出某一半径内的元素
# 所有的数据都应该录入: china:city,才会让结果更加精确
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km #以110 30这个经纬度为中心,寻找方圆1000km的城市
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqi"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist #显示到中心的距离
1) 1) "chongqi"
2) "341.9374"
2) 1) "xian"
2) "485.0086"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord #显示出他人的定位位置
1) 1) "chongqi"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.90000075101852417"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1 #筛选出指定的结果
1) 1) "chongqi"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqi"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "485.0086"
3) 1) "108.90000075101852417"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 3
1) 1) "chongqi"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "485.0086"
3) 1) "108.90000075101852417"
2) "34.25999964418929977"
# GEORADIUSBYMEMBER
#找出位于指定元素周围的其他元素!
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
# GEOHASH——返回一个或多个位置元素的Geohash表示
# 该命令返回11个字符的Geohash字符串!
127.0.0.1:6379> GEOHASH china:city beijing hangzhou
1) "wx4fbxxfke0"
2) "wtmkn31bfb0"
GEO底层的实现原理其实就是Zset!我们可以使用Zset命令来操作geo
127.0.0.1:6379> ZRANGE china:city 0 -1 #查看地图中全部的元素
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> ZREM china:city beijing #移除指定的元素
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
Hyperloglog
Redis2.8.9就已经更新了Hyperloglog数据结构
Redis Hyperloglog基数统计的算法!
优点:占用的内存是固定的,2^64不同的元素的技术,只需要花费12KB内存!如果要从内存角度来比较的话,hpyerloglog首选!
网页的UV(一个人访问一个网站多次,但还算作是一个人)
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断!
这个方式如何保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id
0.81%的错误率!统计UV任务,可以忽略不计的!
127.0.0.1:6379> PFADD mykey1 a b c d e f g h i j #创建第一组元素mykey1
(integer) 1
127.0.0.1:6379> PFCOUNT mykey1 #统计mykey1的基数数量
(integer) 10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m #创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2 #统计mykey2的基数数量
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey1 mykey2 #合并两组mykey1 mykey2 => mykey3
OK
127.0.0.1:6379> PFCOUNT mykey3 #统计mykey3的基数数量
(integer) 15
如果允许容错,那么一定可以使用Hyperloglog!
如果不允许容错,就可以使用set或者自己的数据类型
Bitmap
位存储:统计用户信息,活跃|不活跃 登录|未登录 365打卡 两个状态,都可以使用bitmaps!
Bitmap位图,数据结构!都是操作
二进制为来进行记录,就只有0和1两个状态
使用bitmap来记录周一到周日的打卡
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
# 查看某一天是否有打卡
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
# 统计操作,统计打卡的天数!
127.0.0.1:6379> BITCOUNT sign #统计这周的打卡记录,就可以看到是否有全勤
(integer) 3
事务
Redis事务本质:一组命令的集合!
一个事务中所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排他性!执行一系列的命令
Redis单条命令是保证原子性的,但是事务不保证原子性!
没有隔离级别的概念。所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec
Redis的事务:
- 开启事务(multi)
- 命令入队()
- 执行事务(exec)
# 正常执行事务
127.0.0.1:6379> multi #开启事务
OK
#命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
#放弃事务 DISCARD
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD #取消事务
OK
127.0.0.1:6379> get k4 #事务队列中命令都不会被执行!
(nil)
# 编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行!
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 #错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec #执行事务报错!
(error) EXECABORT Transaction discarded because of previous errors
127.0.0.1:6379> get v4 #所有的命令都不会执行
(nil)
# 运行时异常(1/0),如果事务队列中存在语法性错误,那么执行命令的时候,其他命令可以正常执行。错误的命令抛出异常
127.0.0.1:6379> set k1 "v2"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 #会执行失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #索然第一条命令报错了,但是事务依旧正常执行成功。
2) OK
3) OK
4) "v3"
监控!Watch 面试常问
Redis实现乐观锁 watch监控
# Redis监视测试
# 正常执行成功!
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视money对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch可以当做redis的乐观锁操作!
127.0.0.1:6379> watch money #监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY money 10
QUEUED
127.0.0.1:6379> exec #执行之前,另外一个线程修改了我们的值,这个时候就会导致事务执行失败!
(nil)
Jedis
Redis官方推荐的java连接开发工具!使用java操作Redis的中间件!
测试:
1.导入对应的依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
</dependencies>
2.编码测试:
- 连接数据库
- 操作命令
- 断开连接
Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘中,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能!
RDB(Redis DataBase)
什么是RDB
在主从复制中,rdb就是备用的!在从机上面!不占主机内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QvaTTYgC-1618555937426)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201214211933470.png)]
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,等持久化都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDO方式比AOF方式更加高效。RDB的缺点是最后一次持久化后的数据可能丢失。默认的就是RDB,一般情况下不需要修改这个配置!
rdb保存的文件是dump.rdb
触发机制
- save的规则满足的情况下,会自动触发rdb规则
- 执行flushall命令,也会触发我们的rdb规则!
- 退出redis,也会产生rdb文件!
备份就自动生成一个dump.rdb文件
恢复rdb文件
1、只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候回自动检查dump.rdb 恢复其中的数据!
2、查看需要存在的位置!
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" #如果在这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据
优点:
- 适合大规模的数据恢复!
- 对数据的完整性不高!
缺点:
- 需要一定的时间间隔进程操作!如果redis意外宕机了,最后一次修改的数据就没有了
- fork进程的时候,会占用一定的空间
AOF(Append Only File)
将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍!
什么是AOF
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只允许追加文件但不可以改写文件,redis启动之初会读取改文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从签到后执行一次以完成数据的恢复工作
AOF保存的是appendonly.conf文件
默认是不开启的,我们需要手动进行配置!只需要将配置文件中的appendonly改为yes就开启了aof。
重启redis就可以生效了
如果修改了这个aof文件,或者有错误,这时候redis是启动不起来的,我们需要修复这个aof文件。
redis给我们提供了一个工具redis-check-aof --fix
redis-check-aof --fix appendonly.conf
显示成功后,重启redis即可连接成功
重启规则:
默认的就是文件的无限追加,文件会越来越大
如果aof文件大于64M(默认)!fork一个新进程将文件进行重写(重新换一个文件进行写入操作)
优点:
- 每一次修改都同步,文件的完整会更加好!
- 每秒同步一次,可能会丢失一秒的数据!
- 从不同步,效率是最高的!
缺点
- 相当于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢!
- aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化!
扩展
1、RDB持久化方式能够在指定时间间隔内对数据进行快照存储
2、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化。
4、同时开启两种持久化方式
- 在这种情况下,当Redis重启的时候会优先载入AOF文件来回复原始的数据,因为在通常情况下AOF保存的数据集要比RDB文件保存的数据集更加完整。
- RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要使用AOF呢?建议不要,因为RDB跟家适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留作万一的手段
5、性能建议
- 因为RDB文件只用做后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条就够了
Redis主从复制
环境配置
一般至少是一主二从
只配置从库,不用配置主库!因为默认自身就是主库
127.0.0.1:6379> info replication #查看当前库的新消息
# Replication
role:master #角色 master
connected_slaves:0 #没有从机
master_replid:4d0cd7965bcb468192d1972174c72dfe6dc568d8
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
复制三个配置文件,然后修改对应的信息
1、端口
2、pid名字
3、log文件名字
4、dump.rdb名字
修改完毕后,启动3个redis服务器,用命令查看进程
一主二从
默认情况,每台Redis服务器都是主节点
一般情况只用配置从机就好了。相当于认老大!一主(79)二从(80、81)
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #找谁当老大
OK
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:4
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:b9bbc4872dc74fadcc480505e500c20e74bcad72
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
# 在主机中查看!
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 #多了从机的配置
slave0:ip=127.0.0.1,port=6380,state=online,offset=280,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=280,lag=0
master_replid:b9bbc4872dc74fadcc480505e500c20e74bcad72
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:280
真实的主从配置应该在配置文件中配置,这样的话就是永久的。
细节
主机可以写,从机不能写只能读!主机中的所有信息和数据都会自动被从机保存。
测试
- 主机断开连接,从机依旧连接到主机,但是没有写操作,这时候主机如果回来了,从机依旧可以直接获取到主机写的信息!
- 如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机中获取值!
复制原理
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完整一次完全同步。
- 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
- 增量复制:Master继续将新的所有手机到的修改命令依次传给slave,完成同步
但是只要重新连接master,一次完全同步(全量复制)将被自动执行!我们的数据一定可以在从机中看到!
层层链路
这时候也可以完成主从复制!
从79set的值在80、81都可以取得
如果没有了老大,这个时候能不能选一个老大出来?手动!
谋朝篡位
127.0.0.1:6381> SLAVEOF no one #将自身从slave变成为master
如果主机断开了连接,我们可以使用SLAVEOF no one
将自己变为主机!其他节点就可以手动连接到最新的这个主节点(手动)!如果老大修复了,就继续手动重新连接!
哨兵模式
(自动选举老大的模式)
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,有限考虑哨兵模式。Redis从2.8开始正式提供Sentinel(哨兵)架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控多个Redis实例。
测试
1、配置哨兵的配置文件sentinel.conf
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
后面这个数字1,代表主机挂了,给slave投票看让谁接替成为主机,票数最多的,就会成为主机!
2、启动哨兵!
redis-sentinel reflectconfigs/sentinel.conf
如果Master节点断开了,这个时候就会从从机中随机挑选一个服务器!(这里面有一个投票算法!)
3、可查看哨兵日志
如果主机此时回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则!
优点
1、哨兵集群,基于主从复制,所有的主从配置优点,它全有
2、主从可以切换。故障可以转移,系统可用性会更好
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点
1、Redis不好在线扩容,集群容量一旦达到上限,在线扩容十分麻烦
2、实现哨兵模式的配置其实是很麻烦的,里面有很多选择
哨兵模式的全部配置
Redis进阶
使用help命令
可以查看所有的帮助
例如
help # 查看help的所有格式
help 命令 # 查看该命令的语法格式
help Tab键 #一直跳转所属群组
5种常用数据类型
string
基本操作
-
添加/修改数据
-
set key value
-
-
获取数据
-
get key
-
-
删除数据
-
del key
-
# 示例
127.0.0.1:6379> set name reflect
OK
127.0.0.1:6379> get name
"reflect"
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> del age
(integer) 1
127.0.0.1:6379> get age
(nil)
-
添加/多个数据
-
mset key1 value1 key2 value2 …
-
-
获取多个数据
-
mget key1 key2 …
-
-
获取数据字符个数(字符串长度)
-
strlen key
-
-
追加数据(原始key不存在则新建)
-
append key value
-
# 示例
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> mget a b c
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> mget a w c
1) "1"
2) (nil)
3) "3"
127.0.0.1:6379> set age 200
OK
127.0.0.1:6379> strlen age
(integer) 3
127.0.0.1:6379> append age 300
(integer) 6
127.0.0.1:6379> strlen age
(integer) 6
127.0.0.1:6379> get age
"200300"
- set与mset的区别
- 需要存放货修改多条数据时使用mset效率更高
扩展操作
扩展1
-
设置数据增加指定范围的值
-
incr key
incrby key increment
incrbyfloat key increment
-
-
设置数据减少指定范围的值
-
decr key
decrby key increment
-
-
值得注意的是:incrby后可跟负数实现减操作,decrby后可跟负数实现加操作。
# 示例
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> incr num
(integer) 11
127.0.0.1:6379> decr num
(integer) 10
127.0.0.1:6379> incrby num 3
(integer) 13
127.0.0.1:6379> decrby num 3
(integer) 10
127.0.0.1:6379> incrby num -3
(integer) 7
127.0.0.1:6379> decrby num -3
(integer) 10
127.0.0.1:6379> incrbyfloat num 1.5
"11.5"
- redis的所有操作都是原子性的,采用单线程处理所有业务,命令是一个个执行的,所以无需考虑并发带来的影响。
- 按数值进行操作时,如果原始数据不能转成数值,或者超过了redis数值上下限范围,则报错。Java中long的最大值Long.MAX_VALUE
扩展2
-
设置数据具有指定的生命周期
-
setex key seconds value
psetex key milliseconds value
-
# 示例
127.0.0.1:6379> setex tel 10 0714
OK
127.0.0.1:6379> get tel
"0714"
# 十秒之后再次get
127.0.0.1:6379> get tel
(nil)
注意事项
-
数据操作不成功与数据正常操作之间的差异
- 表示操作执行是否成功
- (integer) 0 → false 失败
- (integer) 1 → true 成功
- 表示运行结果值
- (integer)3 → 3 3个
- (integer)1 → 1 1个
- 表示操作执行是否成功
-
数据未获取到
- nil 等同于null
-
数据最大存储量
- 512MB
-
数值最大范围(Java中long的最大值)
-
Long.MAX_VALUE
-
key的命名
-
通常是:
表名 : 主键名 : 主键值 : 字段名
-
user : id : 123 : fans 2000
user : id : 123 : blogs 600
-
json格式
user : id : 123 {id : 123, name : reflect, fans : 2000}
-
# 示例
127.0.0.1:6379> set user:id:123:fans 2000
OK
127.0.0.1:6379> set user:id:123:blogs 600
OK
127.0.0.1:6379> set user:id:123 {id:123,fans:2000,blogs:600}
OK
hash
- 一个key对应一个存储空间,存储空间保存多个键值对(field : value)
- hash类型底层使用哈希表结构实现数据存储
- 优化
- 如果field数量较少,存储结构优化为类数组结构
- 如果field数量较多,存储结构使用HashMap结构
基本操作
-
添加/修改数据
-
hset key field value
-
-
获取数据
-
hget key field
hgetall key
-
-
删除数据
-
hdel key field1 [field2]
-
# 示例
127.0.0.1:6379> hset user name reflect
(integer) 1
127.0.0.1:6379> hset user age 18
(integer) 1
127.0.0.1:6379> hset user weight 80
(integer) 1
127.0.0.1:6379> hgetall user
1) "name"
2) "reflect"
3) "age"
4) "18"
5) "weight"
6) "80"
127.0.0.1:6379> hget user name
"reflect"
127.0.0.1:6379> hdel user weight
(integer) 1
127.0.0.1:6379> hgetall user
1) "name"
2) "reflect"
3) "age"
4) "18"
-
添加/修改多个数据
-
hmset key field1 value1 field2 value2 …
-
-
获取多个数据
-
hmget key field1 field2 …
-
-
获取哈希表中字段的数量
-
hlen key
-
-
获取哈希表中是否存在指定字段
-
hexists key field
-
# 示例
127.0.0.1:6379> hmget user name age
1) "reflect"
2) "18"
127.0.0.1:6379> hmset user name reflect0714 weight 100
OK
127.0.0.1:6379> hgetall user
1) "name"
2) "reflect0714"
3) "age"
4) "18"
5) "weight"
6) "100"
127.0.0.1:6379> hlen user
(integer) 3
127.0.0.1:6379> hexists user age
(integer) 1
127.0.0.1:6379> hexists user hight
(integer) 0
扩展操作
-
获取哈希表中所有的字段名或字段值
-
hkeys key
hvals key
-
-
设置指定字段的数值增加指定范围的值
-
hincrby key field increment
hincrbyfloat key field increment
-
# 示例
127.0.0.1:6379> hgetall user
1) "name"
2) "reflect0714"
3) "age"
4) "18"
5) "weight"
6) "100"
127.0.0.1:6379> hkeys user
1) "name"
2) "age"
3) "weight"
127.0.0.1:6379> hvals user
1) "reflect0714"
2) "18"
3) "100"
127.0.0.1:6379> hincrby user age 1
(integer) 19
注意事项
-
hash类型的value只能存储字符串,不允许存储其它数据类型。如果数据未获取到,对应的为nil
-
每个hash可以存储232 - 1个键值对
-
hash十分贴近对象的数据存储,且可以灵活增删对象属性,但其初衷并不是为了存储大量对象而设计,不可滥用,更不可将hash作为对象列表使用
-
hgetall操作可以获取全部属性,但如果内部field过多,遍历整体数据效率就会很低,有可能成为数据访问瓶颈
-
如果field存在值则不进行修改,不存在则进行添加
hsetnx key field value
list
- 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
- list类型保存多个数据,底层使用双向链表存储结构实现
基本操作
-
添加/修改数据
-
lpush key value1 [value2] …
rpush key value1 [value2] …
-
-
获取数据
-
lrange key start stop
lindex key index
llen key
-
-
获取并移除数据
-
lpop key
rpop key
-
# 示例
127.0.0.1:6379> lpush list1 huawei
(integer) 1
127.0.0.1:6379> lpush list1 apple
(integer) 2
127.0.0.1:6379> lpush list1 microsoft
(integer) 3
127.0.0.1:6379> lrange list1 0 2
1) "microsoft"
2) "apple"
3) "huawei"
127.0.0.1:6379> rpush list2 a b c
(integer) 3
127.0.0.1:6379> lrange list2 0 2
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> lrange list1 0 -1
1) "microsoft"
2) "apple"
3) "huawei"
127.0.0.1:6379> lindex list1 0
"microsoft"
127.0.0.1:6379> lindex list1 2
"huawei"
127.0.0.1:6379> llen list1
(integer) 3
127.0.0.1:6379> lpush list3 a b c
(integer) 3
127.0.0.1:6379> lpop list3
"c"
127.0.0.1:6379> llen list3
(integer) 2
127.0.0.1:6379> lpop list3
"b"
127.0.0.1:6379> lpop list3
"a"
127.0.0.1:6379> lpop list3
(nil)
127.0.0.1:6379> rpush list4 x y z
(integer) 3
127.0.0.1:6379> rpop list4
"z"
127.0.0.1:6379> rpop list4
"y"
127.0.0.1:6379> rpop list4
"x"
127.0.0.1:6379> rpop list4
(nil)
扩展操作
-
规定时间内获取并移除数据
-
blpop key1 [key2] timeout
brpop key1 [key2] timeout
-
-
移除指定数据
-
lrem key count value
-
注意事项
- list中保存的数据都是string类型的,最多232 - 1个元素
- list具有索引,获取全部数据的结束索引设置为-1
- list可以对数据进行分页操作
set
- 查询方面提供更高的效率,便于查询
- set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的
基本操作
-
添加数据
-
sadd key member1 [member2]
-
-
获取全部数据
-
smembers key
-
-
删除数据
-
srem key member1 [member2]
-
# 示例
127.0.0.1:6379> clear
127.0.0.1:6379> sadd users zs
(integer) 1
127.0.0.1:6379> sadd users ls
(integer) 1
127.0.0.1:6379> sadd users ww
(integer) 1
127.0.0.1:6379> smembers users
1) "ww"
2) "zs"
3) "ls"
127.0.0.1:6379> srem users ww
(integer) 1
127.0.0.1:6379> smembers users
1) "zs"
2) "ls"
-
获取集合数据总量
-
scard key
-
-
判断集合中是否包含指定数据
-
sismember key member
-
# 示例
127.0.0.1:6379> scard users
(integer) 2
127.0.0.1:6379> sismember users zs
(integer) 1
127.0.0.1:6379> sismember users ww
(integer) 0
扩展操作
-
随机获取集合中指定数量的数据
-
srandmember key [count]
-
-
随机获取集合中的某个数据并将该数据移出集合
-
spop key
-
# 示例
127.0.0.1:6379> sadd news n1
(integer) 1
127.0.0.1:6379> sadd news n2
(integer) 1
127.0.0.1:6379> sadd news n3
(integer) 1
127.0.0.1:6379> sadd news n4
(integer) 1
127.0.0.1:6379> srandmember news 1
1) "n2"
127.0.0.1:6379> srandmember news 1
1) "n2"
127.0.0.1:6379> scard news
(integer) 4
127.0.0.1:6379> srandmember news 3
1) "n1"
2) "n3"
3) "n4"
127.0.0.1:6379> spop news
"n3"
127.0.0.1:6379> smembers news
1) "n1"
2) "n4"
3) "n2"
127.0.0.1:6379> spop news 2
1) "n1"
2) "n2"
127.0.0.1:6379> smembers news
1) "n4"
-
求两个集合的交集、并集、差集
-
sinter key1 [key2]
sunion key1 [key2]
sdiff key1 [key2]
-
-
求两个集合的交集、并集、差集并存储到指定集合中
-
sinterstore destination key1 [key2]
sunionstore destination key1 [key2]
sdiffstore destination key1 [key2]
-
-
将指定数据从原始集合在移动到目标集合中
-
smove source destination member
-
# 示例
127.0.0.1:6379> sadd u1 a1
(integer) 1
127.0.0.1:6379> sadd u1 s1
(integer) 1
127.0.0.1:6379> sadd u1 b1
(integer) 1
127.0.0.1:6379> sadd u2 s1
(integer) 1
127.0.0.1:6379> sadd u2 w1
(integer) 1
127.0.0.1:6379> sinter u1 u2
1) "s1"
127.0.0.1:6379> sunion u1 u2
1) "b1"
2) "s1"
3) "a1"
4) "w1"
127.0.0.1:6379> sdiff u1 u2
1) "b1"
2) "a1"
127.0.0.1:6379> sdiff u2 u1
1) "w1"
127.0.0.1:6379> sinterstore u3 u1 u2
(integer) 1
127.0.0.1:6379> smembers u3
1) "s1"
127.0.0.1:6379> smove u2 u1 w1
(integer) 1
127.0.0.1:6379> smembers u1
1) "b1"
2) "s1"
3) "a1"
4) "w1"
注意事项
- set类型不允许数据重复,即数据只保留一份
- set虽然与hash存储结构相同,但是无法启用hash中存储值的空间
sorted_set
- 根据自身特征进行排序
- sorted_set类型:在set的存储结构基础上添加可排序字段
基本操作
-
添加数据
-
zadd key score1 member1 [score2 member2]
-
-
获取全部数据
-
zrange key start stop [WITHSCORES]
zrevrange key start stop [WITHSCORES]
-
-
删除数据
-
zrem key member [member …]
-
# 示例
127.0.0.1:6379> zadd scores 94 zs
(integer) 1
127.0.0.1:6379> zadd scores 100 ls
(integer) 1
127.0.0.1:6379> zadd scores 60 ww
(integer) 1
127.0.0.1:6379> zadd scores 47 zl
(integer) 1
127.0.0.1:6379> zrange scores 0 -1
1) "zl"
2) "ww"
3) "zs"
4) "ls"
127.0.0.1:6379> zrange scores 0 -1 withscores
1) "zl"
2) "47"
3) "ww"
4) "60"
5) "zs"
6) "94"
7) "ls"
8) "100"
127.0.0.1:6379> zrevrange scores 0 -1 withscores
1) "ls"
2) "100"
3) "zs"
4) "94"
5) "ww"
6) "60"
7) "zl"
8) "47"
127.0.0.1:6379> zrem scores ww
(integer) 1
127.0.0.1:6379> zrevrange scores 0 -1 withscores
1) "ls"
2) "100"
3) "zs"
4) "94"
5) "zl"
6) "47"
-
按条件获取数据
-
zrangebyscore key min max [WITHSCORES] [LIMIT]
zrevrangebyscore key max min [WITHSCORES]
-
-
按条件删除数据
-
zremrangebyrank key start stop
zremrangebyscore key min max
-
# 示例
127.0.0.1:6379> zadd scores 45 wangwu
(integer) 1
127.0.0.1:6379> zadd scores 67 zhangsan
(integer) 1
127.0.0.1:6379> zadd scores 71 zhouqi
(integer) 1
127.0.0.1:6379> zadd scores 92 qianba
(integer) 1
127.0.0.1:6379> zadd scores 99 lisi
(integer) 1
127.0.0.1:6379> zadd scores 100 zhaoliu
(integer) 1
127.0.0.1:6379> zrange scores 0 -1
1) "wangwu"
2) "zhangsan"
3) "zhouqi"
4) "qianba"
5) "lisi"
6) "zhaoliu"
127.0.0.1:6379> zrangebyscore scores 50 80 withscores
1) "zhangsan"
2) "67"
3) "zhouqi"
4) "71"
127.0.0.1:6379> zrangebyscore scores 50 99 withscores
1) "zhangsan"
2) "67"
3) "zhouqi"
4) "71"
5) "qianba"
6) "92"
7) "lisi"
8) "99"
127.0.0.1:6379> zrangebyscore scores 50 99 withscores limit 0 3
1) "zhangsan"
2) "67"
3) "zhouqi"
4) "71"
5) "qianba"
6) "92"
127.0.0.1:6379> zremrangebyscore scores 50 70
(integer) 1
127.0.0.1:6379> zrange scores 0 -1 withscores
1) "wangwu"
2) "45"
3) "zhouqi"
4) "71"
5) "qianba"
6) "92"
7) "lisi"
8) "99"
9) "zhaoliu"
10) "100"
127.0.0.1:6379> zremrangebyrank scores 0 1
(integer) 2
127.0.0.1:6379> zrange scores 0 -1 withscores
1) "qianba"
2) "92"
3) "lisi"
4) "99"
5) "zhaoliu"
6) "100"
-
注意
- min与max用于限定搜索查询的条件
- start与stop用于限定查询范围,作用于索引,表示开始和结束索引
- offset与count用于限定你查询范围,作用于查询结果,表示开始位置和数据总量
-
获取集合数据总量
-
zcard key
zcount key min max
-
-
集合交集、并集
-
zinterstore destination numkeys key [key …]
zunionstore destination numkeys key [key …]
-
扩展操作
-
获取数据对应的索引
-
zrank key member
zrevrank key member
-
-
score值获取与修改
-
zscore key member
zincrby key increment member
-
# 示例
127.0.0.1:6379> zadd movies 143 aa 97 bb 201 cc
(integer) 3
127.0.0.1:6379> zrank movies bb
(integer) 0
127.0.0.1:6379> zrevrank movies bb
(integer) 2
127.0.0.1:6379> zscore movies aa
"143"
127.0.0.1:6379> zincrby movies 1 aa
"144"
127.0.0.1:6379> zscore movies aa
"144"
注意事项
- score保存的数据存储空间是64位,且范围要在一定范围内
- score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度
- sorted_set底层存储还是基于set结构的,因此数据不能重复,如果添加重复元素,score值将被反复覆盖,保留最后一次修改的结果
通用命令
key通用命令
-
删除指定key
-
del key
-
-
获取key是否存在
-
exists key
-
-
获取key的类型
-
type key
-
# 示例
127.0.0.1:6379> set str str
OK
127.0.0.1:6379> hset hash1 hash1 hash1
(integer) 1
127.0.0.1:6379> lpush list1 list1
(integer) 1
127.0.0.1:6379> sadd set1 set1
(integer) 1
127.0.0.1:6379> zadd zset1 1 zset1
(integer) 1
127.0.0.1:6379> type zset1
zset
127.0.0.1:6379> type hash1
hash
127.0.0.1:6379> exists str
(integer) 1
127.0.0.1:6379> del zset1
(integer) 1
127.0.0.1:6379> del zset1
(integer) 0
127.0.0.1:6379> exists zest1
(integer) 0
-
为指定key设置有效期
-
expire key seconds
pexpire key milliseconds
expireat key timestamp
pexpireat key milliseconds-timestamp
-
-
获取key的有效时间
-
ttl key #-2代表已过期,-1代表永久,其余是代表过期时间
pttl key
-
-
切换key从时效性转换为永久性
-
persist key
-
# 示例
127.0.0.1:6379> set str str
OK
127.0.0.1:6379> lpush list1 list1
(integer) 1
127.0.0.1:6379> lpush list2 list2
(integer) 1
127.0.0.1:6379> expire str 3
(integer) 1
127.0.0.1:6379> get str
"str"
127.0.0.1:6379> get str
(nil)
127.0.0.1:6379> expire list1 30
(integer) 1
127.0.0.1:6379> ttl list1
(integer) 27
127.0.0.1:6379> ttl list1
(integer) 25
127.0.0.1:6379>
127.0.0.1:6379> ttl list1
(integer) 14
127.0.0.1:6379> ttl list1
(integer) -2
127.0.0.1:6379> ttl str
(integer) -2
127.0.0.1:6379> get str
(nil)
127.0.0.1:6379> get list1
(nil)
127.0.0.1:6379> ttl list2
(integer) -1
127.0.0.1:6379> persist list2
(integer) 0
127.0.0.1:6379> expire list2 60
(integer) 1
127.0.0.1:6379> ttl list2
(integer) 55
127.0.0.1:6379> persist list2
(integer) 1
127.0.0.1:6379> ttl list2
(integer) -1
127.0.0.1:6379> persist 11
(integer) 0
-
查询key
-
keys pattern
-
*——匹配任意数量的任意符号
-
?——配合一个任意符号
-
[]——匹配一个指定符号
-
keys * # 查询所有
keys it* # 查询所有以it开头
keys *flect # 查询所有以flect结尾
keys ??flect # 查询所有前面两个字符任意,后面以flect结尾
keys user:? # 查询所有以user:开头,最后一个字符任意
keys u[st]er:1 # 查询所有以u开头,以er:1结尾,中间包含一个字母,s或t
-
为key改名
-
rename key newkey
renamex key newkey
-
-
对所有key排序
-
sort
-
-
其它key通用操作
-
help @generic
-
数据库通用命令
-
切换数据库
-
select index
-
-
其它操作
-
quit
ping
echo message
-
# 示例
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 15
OK
127.0.0.1:6379[15]> select 16
(error) ERR DB index is out of range
127.0.0.1:6379[15]> select 0
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> echo "abcde"
"abcde"
127.0.0.1:6379> quit
-
数据移动
-
move key db
-
-
数据清除
-
dbsize # 查看数据库中的key数量
flushdb # 清除此数据库
flushall # 清除所有库
-
Jedis
HelloWorld
- 准备工作:
- linux中redis配置文件
- 注释掉bind127.0.0.1,
- protected-mode保护从yes改为no
- 开放端口6379
- pom.xml文件中导入Jedis
- linux中redis配置文件
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.4.0</version>
</dependency>
- 连接redis
package com.reflect.jedis;
import org.junit.Test;
import redis.clients.jedis.Jedis;
/**
* @Author: reflect0714
* @Date: 1/29/2021 - 9:14 PM
* @Project: JavaSEDemo
* @Description:
*/
public class JedisDemo {
@Test
public void testJedis() {
// 1.连接redis
Jedis jedis = new Jedis("192.168.122.128",6379);
// 2.操作jedis
jedis.set("name","reflect");
String name = jedis.get("name");
System.out.println(name);
// 3.关闭连接
jedis.close();
}
}
工具包Utils
package com.reflect.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ResourceBundle;
/**
* @Author: reflect0714
* @Date: 1/29/2021 - 9:49 PM
* @Project: JavaSEDemo
* @Description:
*/
public class JedisUtils {
private static JedisPool jedisPool = null;
private static String host = null;
private static int port;
private static int maxTotal;
private static int maxIdle;
static {
ResourceBundle resourceBundle = ResourceBundle.getBundle("redis");
host = resourceBundle.getString("redis.host");
port = Integer.parseInt(resourceBundle.getString("redis.port"));
maxTotal = Integer.parseInt(resourceBundle.getString("redis.maxTotal"));
maxIdle = Integer.parseInt(resourceBundle.getString("redis.maxIdle"));
// Jedis池配置信息
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 设置最大连接数
jedisPoolConfig.setMaxTotal(maxTotal);
// 设置最大活动连接数
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPool = new JedisPool(jedisPoolConfig,host,port);
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
public static void main(String[] args) {
JedisUtils.getJedis();
}
}
redis.properties配置文件
redis.host=192.168.122.128
redis.port=6379
redis.maxTotal=30
redis.maxIdle=10
持久化
RDB
save指令
-
命令
-
save
-
-
作用:手动执行一次保存操作
# 示例
[root@hadoop666 reflectconfigs]# ll
总用量 88
-rw-r--r--. 1 root root 105 1月 31 22:44 dump.rdb
-rw-r--r--. 1 root root 84862 1月 29 21:23 redis.conf
[root@hadoop666 reflectconfigs]# cat dump.rdb
REDIS0009 redis-ver6.0.9
redis-bitsegused-memø3
𮤭preamble~meÿT7u
[root@hadoop666 reflectconfigs]# rm -rf dump*
[root@hadoop666 reflectconfigs]# ls
redis.conf
127.0.0.1:6379> save
OK
[root@hadoop666 reflectconfigs]# ll
总用量 88
-rw-r--r--. 1 root root 105 1月 31 22:50 dump.rdb
-rw-r--r--. 1 root root 84862 1月 29 21:23 redis.conf
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> save
OK
# 与上面文件的大小不同112,上面为105
[root@hadoop666 reflectconfigs]# ll
总用量 88
-rw-r--r--. 1 root root 112 1月 31 22:53 dump.rdb
-rw-r--r--. 1 root root 84862 1月 29 21:23 redis.conf
# 与上面的文件有一行是相同的
[root@hadoop666 reflectconfigs]# cat dump.rdb
REDIS0009 redis-ver6.0.9
redis-bitseUused-memÀ4
𮤭preamble~⠧enameÿ¦C┚I8
-
save指令相关配置
dbfilename dump.rdb 设置本地数据库文件名,默认值为dump.rdb 一般设置为dump-端口号.rdb dir 设置存储.rdb文件的路径 通过设置目录名称为data rdbcompression yes 设置存储至本地数据库时是否压缩数据,默认为yes,采用LZF压缩 通常默认为开启状态,如果为no可以节省CPU运行时间,但是存储文件空间会变大 rdbchecksum yes 设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行 通常默认为开启状态,如果设置为no,可以解决读写过程10%的时间消耗,但是存储一定的数据损坏风险
-
工作原理
- save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,当某一个操作耗时过长,有可能造成长时间阻塞,线上环境不建议使用。
bgsave指令
-
命令
-
bgsave
-
-
作用:手动启用后台保存操作,但不是立即执行
127.0.0.1:6379> set addr hangzhou
OK
127.0.0.1:6379> bgsave
Background saving started
# 与上面文件的大小不同127,上面为112
[root@hadoop666 reflectconfigs]# ll
总用量 88
-rw-r--r--. 1 root root 127 1月 31 23:08 dump.rdb
-rw-r--r--. 1 root root 84862 1月 29 21:23 redis.conf
[root@hadoop666 reflectconfigs]# cat dump.rdb
REDIS0009 redis-ver6.0.9
redis-bitseäŖused-me5
𮤭preamble~dhangzhouagenameÿ钨v'ڒ
- 工作原理
- ①bgsave命令发送指令
- ②redis调用fork函数生成子进程
- ③子进程创建rdb文件
- ④子进程返回成功消息
- ⑤对命令行返回消息
Background saving started
(也可以看作是与②同步的)
bgsave命令是针对save阻塞问题做的优化。Redis内部所有涉及到RDB操作都采用bgsave方式,save命令基本可以放弃使用
-
相关配置
stop-writes-on-bgsave-error yes 后台存储过程中如果出现错误现象,是否停止保存操作 通常默认为开启状态
自动执行
- 配置(不是命令),实际上执行的是bgsave指令
save second changes
满足限定时间范围内key的变化数量达到指定数量即进行持久化
second:监控时间范围
changes:监控key的变化量
在conf文件中进行配置
# redis原配置中自动配置的
# 分别代表900s内1个key变化、300s内10个key变化、60s内10000key变化即保存
save 900 1
save 300 10
save 60 10000
- 工作原理
- ①发送指令
- ②返回结果
- 会对数据产生影响
- 真正产生了影响
- 不进行数据比对
- 上面三者合一,导致影响key的数量发生变化则自动保存
- save配置要根据实际设置频度。通常second与changes是互补关系,即二者要一大一小。实际启动执行的是bgsave操作
AOF
- append only file持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令以达到恢复数据的目的。简单描述为改记录数据为记录数据产生的过程。(要改变数据才能记录操作,如set;而get操作是不会被记录的)
- AOF主要是解决了数据持久化的实时性,目前已是Redis持久化主流方式
当命令执行时,会被放入一个AOF写命令刷新缓存区中,当达到一定限制才会将命令同步到AOF文件中。
AOF写数据的三种策略(appendfsyc)
- always(每次)
- 每次写入操作都同步到AOF文件,数据零误差,但性能较低。不建议使用
- everysec(每秒)
- 每秒将缓冲区中的指令同步到AOF文件,数据准确性较高,性能较高。建议使用,也是默认配置
- 系统宕机情况也只是丢失1秒的数据
- no(系统控制)
- 由操作系统控制每次同步到AOF文件的周期,整体不可控
AOF功能开启
-
配置
-
appendonly yes|no
-
-
作用:是否开启AOF持久化功能,默认不开启
-
配置
-
appendfsync always|everysec|no
-
-
作用:AOF写数据策略
其它相关配置
-
配置
-
appendfilename filename
-
-
作用:AOF持久化文件名,默认文件名为appendonly.aof,建议配置为appendonly-端口号.aof
-
配置
-
dir
-
-
作用:AOF持久化文件保存路径,与RDB持久化文件保持一致即可
AOF重写
随着命令不断执行,AOF文件会越来越大。Redis引入了AOF重写机制压缩文件体积。AOF文件重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单来说就是将对同一个数据的若干条命令执行结果转化为最终结果数据对应的指令进行记录。
- 降低磁盘占用量,提高磁盘利用率
- 提高持久化效率,降低持久化时间,提高IO性能
- 降低数据恢复耗时,提高数据恢复效率
重写规则
- 进程内已超时的数据不再写入文件
- 忽略无效指令,重写时使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令
- 对同一数据的多条写命令合并为一条命令
重写方式
-
手动重写
-
bgrewriteaof
工作原理:与bgsave指令原理相同
-
-
自动重写
-
auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJfYVE6a-1618555937437)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210131235311373.png)]
-
事务
什么是事务?
- redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次性按照添加顺序依次执行,中间不会被打断或者干扰。
- 一个队列中,一次性、顺序性、排他性的执行一系列命令
基本操作
-
开启事务
-
multi
-
-
作用:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
-
执行事务
-
exec
-
-
作用:设定事务的结束位置,同时执行事务。与multi成对出现,成对使用。
-
注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exe命令才开始执行
# 未开启事务之前
# 先在第一台服务端进行设定
127.0.0.1:6379> set name reflect
OK
# 再到第二台服务端进行设定
127.0.0.1:6379> set name reflect0714
OK
# 再到第一台服务端查看
127.0.0.1:6379> get name
"reflect0714"
# 开启事务之后
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
1) OK
2) "18"
3) OK
4) "20"
127.0.0.1:6379> get age
"20"
-
取消事务
-
discard
-
-
作用:终止当前事务的定义,发生在mult之后,exec之前
127.0.0.1:6379> get age
"20"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 30
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> set age 24
QUEUED
# 此处取消事务
127.0.0.1:6379> discard
OK
# 执行事务出现报错
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
事务工作流程
注意事项
-
定义事务的过程中,命令格式输入错误怎么办?
- 语法错误:指命令书写格式有误
- 处理结果
- 如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令。
-
定义事务的过程中,命令执行出现错误怎么办?
- 运行错误:指命令格式正确,但是无法正确的执行。例如对list进行incr操作
- 处理结果
- 能够正确运行的命令会执行,运行错误的命令不会被执行
-
注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚
-
手动进行事务回滚
- 记录操作过程中被影响的数据之前的状态
- 单数据:string
- 多数据:hash、list、set、zset
- 设置指令恢复所有的被修改的项
- 单数据:直接set(注意周边属性,例如时效)
- 多数据:修改对应值或整体克隆复制
- 记录操作过程中被影响的数据之前的状态
锁
-
对key添加监视锁,在执行exec前如果key发生了变化,终止事务执行
-
watch key1 [key2 …]
-
-
取消对所有key的监视
-
unwatch
-
-
watch必须在事务开始前(multi)之前执行,unwatch必须在watch使用之后才能使用。
分布式锁
-
使用setnx设置一个公共锁
-
setnx lock-key value
-
利用 setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
- 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
- 对于返回设置失败的,不具有控制权,排队或等待
-
操作完毕通过del操作释放锁
-
死锁
-
使用expire为锁添加时间限制,到时不释放的话则放弃锁
-
expire lock-key second
pexpire kock-key millseconds
-
删除策略
过期数据
- 可以通过TTL指令获取数据的状态
- XX:具有时效性的数据
- -1:永久有效的数据
- -2:已经过期的数据 或 被删除的数据 或 未定义的数据
过期的数据真的被删除了嘛?
- 并不是,而是还存放在内存中。主要是由删除策略决定的。
数据删除策略
数据删除策略
- 1、定时删除
- 2、惰性删除
- 3、定期删除
数据删除策略的目标:
- 在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或内存泄露。
定时删除
-
创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
-
优点:节约内存,到时就删除,快速释放掉不必要的内存占用
-
缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
-
总结:用处理器性能换取存储空间
惰性删除
-
数据到达过期时间,不做处理。等下次访问该数据时
- 如果未过期,返回数据
- 发现已过期,删除,返回不存在
-
优点:节约CPU性能,发现必须删除的时候才删除
-
缺点:内存压力很大,出现长期占用内存的数据
-
总结:用存储空间换取处理器性能(拿时间换空间)
定期删除
-
上面两种方案都走极端操作,有没有折中方案呢?
-
Redis启动服务器初始化时,读取配置server.hz的值,默认为10
-
每秒钟执行server.hz次serverCron() —> databasesCron() —> activeExpireCycle()
-
**activeExpireCycle()**对每个expires[*]逐一进行检测,每次执行250ms / server.hz
-
对某个expires[*]检测时,随机挑选W个key检测
- 如果key超时,删除key
- 如果一轮中删除的key的数量>W*25%,循环该过程
- 如果一轮中删除的key的数量≤W25%,检查下-个 expires[*],0 - 15循环
- 配置文件中自定义,W取值 = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP属性值
-
参数current_db用于记录**activeExpireCycle()**进入哪个expires[*]执行
-
如果**activeExpireCycle()**执行时间到期,下次从current_db继续向下执行
-
周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
-
特点1:CPU性能占用设置有峰值,检测频度可自定义设置
-
特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
-
总结:周期性抽查存储空间(随机抽查,重点抽查)
删除策略对比
- 1.定时删除:节约内存,无占用;不分时段占用CPU资源,频度高;拿时间换空间
- 2.惰性删除:内存占用严重;延时执行,CPU利用率高;拿空间换时间
- 3.定期删除:内存定期随机清理;每秒花费固定的CPU资源维护内存;随机抽查,重点抽查
redis中采用的方式有2、3。
逐出算法
- 当新数据进入 redis时,如果内存不足怎么办?
- Redis使用内存存储数据,在执行每一个命令前,会调用**freeMemorylfNeeded()**检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。
- 注意:逐出数据的过程不是100%能够清理出足够的可使用的內存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将岀现错误信息
影响数据逐出的相关配置
-
最大可使用内存
-
maxmemory
-
占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上。
-
-
每次选取待删除数据的个数
-
maxmemory-samples
-
选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据
-
-
删除策略
-
maxmemory-policy
-
达到最大内存后的,对被挑选出来的数据进行删除的策略
-
-
检测易失数据(可能会过期的数据集 server.db[i].expires)
- ① volatile-lru:挑选最近最少使用的数据淘汰
- ② volatile-Ifu:挑选最近使用次数最少的数据淘汰
- ③volatile-ttl:挑选将要过期的数据淘汰
- ④volatile-random:任意选择数据淘汰
-
检测全库数据(所有数据集 erver.db[i].dict)
- ⑤ allkeys-lru:挑选最近最少使用的数据淘汰
- ⑥ allkeys-lfu:挑选最近使用次数最少的数据淘汰
- ⑦ allkeys-random:任薏选择数据淘汰
-
放弃数据驱逐
- ⑧no-eviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发错误OOM(Out Of Memory)
配置文件中相关配置
maxmemory-policy volatile-lru
服务器配置
服务器端设定
-
设置服务器以守护进程的方式运行
-
daemonize yes | no
-
-
绑定主机地址
-
bind 127.0.0.1
-
-
设置服务器端口号
-
port 6379
-
-
设置数据库数量
-
databases 16
-
日志配置
-
设置服务器以指定日志记录级别
-
日志级别开发期设置为verbose即可,生产环境中配置为notice,简化日志输出量,降低写日志IO的频度
-
loglevel debug | verbose | notice | warning
-
-
日志记录文件名
-
logfile 端口号.log
-
客户端配置
-
设置同一时间最大客户端连接数,默认无限制。当客户端连接到达上限,Redis会关闭新的连接
-
maxclients 0
-
-
客户端闲置等待最大时长,达到最大值后关闭连接。如需关闭该功能,设置为0
-
timeout 300
-
多服务器快捷配置
-
导入并加载指定配置文件信息,用于快速创建redis公共配置较多的redis实例配置文件,便于维护
-
include /path/server-端口号.conf
-
3种高级数据类型
Bitmaps
-
获取指定key对应偏移量上的bit值
-
getbit key offset
-
-
设置指定key对应偏移量上的bit值,value只能是1或0
-
setbit key offset value
-
127.0.0.1:6379> setbit bits 0 1
(integer) 0
127.0.0.1:6379> getbit bits 0
(integer) 1
127.0.0.1:6379> getbit bits 10
(integer) 0
扩展操作
-
对指定key按位进行交、并、非、异或操作,并将结果保存到desKey中
-
bitop op desKey key1 [key2…J
-
op的取值
- and:交
- or:并
- not:非
- xor:异或
-
-
统计指定key中1的数量
-
bitcount key [start end]
-
HyperLogLog
基数
- 基数是数据集去重后元素个数
- 如{1,3,5,7,5,7,8} 基数集:{1,3,5,7,8} 基数:5
- 如{1,1,1,1,1,7,1} 基数集:{1,7} 基数:2
- HyperLogLog是用来做基数统计的,运用了LogLog算法
LogLog算法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-thinbPnC-1618555937439)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210213090956124.png)]
HyperLogLog类型的基本操作
-
添加数据
-
pfadd key element [element …]
-
-
统计数据
-
pfcount key [key …]
-
-
合并数据
-
pfmerge destkey sourcekey [sourcekey …]
-
只能适用于数据的统计。
相关说明
- 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
- 核心是基数估算算法,最终数值存在一定误差
- 误差范围:基数估计的结果是一个带有0.81%标准错误的近似值
- 耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数
- pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大
- pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少
GEO
基本操作
-
添加坐标点
-
geoadd key longitude latitude member [longitude latitude member …]
-
-
获取坐标点
-
geopos key member [member …]
-
-
计算坐标点距离
-
geodist key member1 member2 [unint]
-
-
根据坐标求范围内的数据
-
geoeadius key longitude latitude radius m | km | ft | mi [withcoord] [withdist] [withhash] [count count]
-
-
根据点求范围内数据
-
geoadiusbymember key member radius m | km | ft | mi [withcoord] [withdist] [withhash] [count count]
-
-
获取指定点对应的坐标hash值
-
geohash key member [member …]
-
主从复制
互联网三高架构
- 高并发
- 高性能
- 高可用
多台服务器连接方案
- 提供数据方:master
- 主服务器,主节点,主库
- 主客户端
- 接收数据方:save
- 从服务器,从节点,从库
- 从客户端
- 需要解决的问题:数据同步
- 核心工作:master的数据复制到 slave中
主从复制
-
主从复制即将 master中的数据即时、有效的复制到save中
-
特征:一个master可以拥有多个slave,一个slave只对应一个master
-
职责
- master
- 写数据
- 执行写操作时,将出现变化的数据自动同步到 slave
- 读数据(可忽略)
- slave
- 读数据
- 写数据(禁止)
- master
主从复制的作用
- 读写分离:master写、slave读,提高务器的读写负载能力
- 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高 Redis服务器并发量与数据吞吐量
- 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
- 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
- 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
主从复制过程
大体可以分为3个阶段
- 建立连接阶段(即准备阶段)
- 数据同步阶段
- 命令传播阶段
建立连接阶段工作流程
- 步骤1:设置master的地址和端口,保存master信息
- 步骤2:建立socket连接
- 步骤3:发送ping命令(定时器任务)
- 步骤4:身份验证
- 步骤5:发送save端口信息至此,主从连接成功!
状态:
slave保存 master的地址与端口
master:保存 slave的端口
总体:之间创建了连接的 socket
主从连接( slave连接 master)
-
方式一:客户端发送命令
-
slaveof <masterip> <masterport>
-
-
方式二:启动服务器参数
-
redis-server -slaveof <masterip> <masterport>
-
-
方式三:服务器配置
-
slaveof <masterip> <masterport>
-
主从断开连接
-
从客户端发送命令
-
sIaveof no one
-
授权访问
-
master配置文件设置密码
-
requirepass <password>
-
-
master客户端发送命令设置密码
-
config set requirepass
config get requirepass
-
-
slave客户端发送命令设置密码
-
auth <password>
-
-
slave配置文件设置密码
-
masterauth <password>
-
-
启动客户端设置密码
-
redis-cli -a <password>
-
数据同步阶段工作流程
- 步骤1:请求同步数据
- 步骤2:创建RDB同步数据
- 步骤3:恢复RDB同步数据
- 步骤4:请求部分同步数据
- 步骤5:恢复部分同步数据
- 至此,数据同步工作完成!
状态
- slave:具有 master端全部数据,包含RDB过程接收的数据
- master:保存save当前数据同步的位置
- 总体:之间完成了数据克隆
数据同步阶段master说明
-
1.如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行
-
2.复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态
-
repl-backlog-size 1mb(默认大小)
-
-
3.master单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执行 bgsave命令和创建复制缓冲区
数据同步阶段slave说明
-
1.为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
-
slave-serve-stale-data yesI no
-
-
2.数据同步阶段, master发送给slave信息可以理解 master是slave的一个客户端,主动向slave发送命令
-
3.多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,因此数据同步需要根据业务需求,适量错峰
-
4.slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大,数据一致性变差,应谨慎选择
命令传播阶段
- 当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播
- master将接收到的数据变更命令发送给slave, slave接收命令后执行命令
命令传播阶段的部分复制
- 命令传播阶段出现了断网现象
- 网络闪断闪连:忽略
- 短时间网络中断:部分复制
- 长时间网络中断:全量复制
- 部分复制的三个核心要素
- 服务器的运行id(runid)
- 主服务器的复制积压缓冲区
- 主从服务器的复制偏移量
服务器运行ID(runid)
- 概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id
- 组成:运行id由40位字符组成,是一个随机的十六进制字符
- 例如:fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce
- 作用:运行id被用于在服务器间进行传输,识别身份
- 如果想两次操作均对同台服务器进行,必须毎次操作携带对应的运行id,用于对方识别
- 实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发送给slave,slave保存此ID,通过info Server命令,可以查看节点的runid
复制缓冲区
- 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
- 复制缓冲区默认数据存储空间大小是1M,由于存储空间大小是固定的,当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列
- 由来:每台服务器启动时,如果开启有AOF或被连接成为master节点,即创建复制缓冲区
- 作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set, select)
- 数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓存区中
主从服务器复制偏移量(offset)
- 概念:一个数字,描述复制缓冲区中的指令字节位置
- 分类
- master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
- slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)
- 数据来源:
- master端:发送一次记录一次
- slave端:接收一次记录一次
- 作用:同步信息,比对master与slave的差异,当 slave断线后,恢复数据使用
数据同步+命令传播阶段工作流程
心跳机制
- 进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线
- master心跳:
- 指令:PING
- 周期:由repl-ping-slave-period决定,默认10
- 作用:判断save是否在线
- 查询:INFO replication 获取save最后一次连接时间可隔,lag项维持在0或1视为正常
- slave心跳任务
- 指令:REPLCONF ACK{offset}
- 周期:1秒
- 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
- 作用2:判断master是否在线
心跳阶段注意事项
-
当 slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作(即写操作)
-
min-slaves-to-write 2
Lin-slaves-max-lag 8 -
slave数量少于2个,或者所有save的延迟都大于等于10秒时,强制关闭master写功能,停止数据同步
-
slave数量由slave发送REPLCONF ACK命令做确认
-
slave延迟由slave发送REPLCONF ACK命令做确认
主从复制常见问题
哨兵模式
哨兵
- 哨兵(sentinel)是一个分布式系统,用于对主从结构中的毎台服务器迸行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master
哨兵的作用
-
监控
- 不断的检查master和save是否正常运行。
- maste存活检测、master与slave运行情况检测
-
通知(提醒)
- 当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
-
自动故障转移
- 断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址
-
注意:哨兵也是一台redis服务器,只是不提供数据服务。通常哨兵配置数量为单数。
主从切换
- 哨兵在进行主从切换过程中经历三个阶段
- 监控:同步信息
- 通知:保持联通
- 故障转移
- 发现问题
- 竞选负责人
- 优选新master
- 新master上任,其他slave切换master,原maste作为slave故障回复后连接
阶段一:监控阶段
- 用于同步各个节点的状态信息
- 获取各个sentinel的状态(是否在线)
- 获取master的状态
- master属性
- runid
- role:master
- 各个slave的详细信息
- master属性
- 获取所有 slave的状态(根据master中的slave信息)
- slave属性
- runid
- role : slave
- master_host, master_port
- offset
- …
- slave属性
阶段二:通知阶段
阶段三:故障转移阶段
集群
-
集群就是使用网络将若干台计算机联通起来,并提供统的管理方式,使其对外呈现单机的服务效果。
-
作用
- 分散单台服务器的访问压力,实现负载均衡
- 分散单台服务器的存储压力,实现可扩展性
- 降低单台服务器宕机带来的业务灾难
企业级解决方案
缓存预热
- 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避兔在用户请求的时候,先査询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存雪崩
- 缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如能够有效避免过期时间集中,可以有效解决雪崩现象的出现(约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。
缓存击穿
- 缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中Redis后,发起了大量对同数据的数据库访问,导致对数据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可。
缓存穿透
- 缓存击穿访问了不存在的数据,跳过了合法数据的Redis数据缓存阶段,每次访问数据库,导致对数据库服务器造成压力。通常此类数据的出现量是一个较低的值,当出现此类情况以毒攻毒,并及时报警。应对策略应该在临时预案防范方面多做文章。
- 无论是黑名单还是白名单,都是对整体系统的压力,警报解除后尽快移除