Redis
remote dictional server 远程字典服务
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
安装(Linux环境下)
1、下载压缩包,网址:redis.io
2、放在Linux中,默认在/opt/目录下解压
tar -zxvf 安装包
3、在 /usr/local/bin 建立一个 文件夹(rain),将opt 文件下的redis.conf复制一份到新建的文件夹中
cp /opt/redis/redis.conf /usr/local/bin/rain
4、修改rain文件夹下面的redis.conf ,将deamon 改为 yes
5、使用redis-server /rain/reids.conf 启动服务
6、使用redis-cli -p 连接服务 具体的option网站上有
7、使用ping 测试联通与否
基础知识
1、使用 redis-benchmark 来进行压力测试,具体的option

2、使用selcect 选择数据库(redis )有16个数据库

3、
flushall : clear all database
flushdb : 清空当前数据库
redis为什么是单线程的
从官方文档得知,redis是基于内存操作的,所以cpu的性能瓶颈并不是redis的瓶颈,reids的瓶颈是内存和网络带宽
既然可以使用单线程,就不使用多线程
其次,多线程操作会频繁的进行切换上下文的操作,这是一个非常耗时的操作,由于redis的操作的数据是全部放在内存中的,所以使用单线程就是最快的。
数据类型
redis-key
expire 设置过期时间 使用ttl查看剩余的时间
exists 查看键是否存在
相关的命令查询在redis.cn 的命令页面

string
一种基础类型,常用的命令
[root@keleshushu /]# redis-cli -p 6379
127.0.0.1:6379> ping # 测试是否联通redis
PONG # 返回PONE 说明联通
127.0.0.1:6379> clear
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name xiaogang # 设置一个键,键值是xiaogang
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> set male man
OK
127.0.0.1:6379> keys * # 查看当前数据库中所有的键
1) "age"
2) "name"
3) "male"
4) "handsome"
5) "is"
127.0.0.1:6379> get name # 获取键为name的键值
"xiaogang"
#########################################################################
127.0.0.1:6379> EXPIRE male 10 # 设置过期时间,当前设置male的过期时间 10秒后就不存在
(integer) 1
127.0.0.1:6379> ttl male # 使用ttl查看剩余时间
(integer) 3
127.0.0.1:6379> ttl male
(integer) -2
127.0.0.1:6379> exists name # 判断键是否存在,存在返回1
(integer) 1
127.0.0.1:6379> strlen name # 获取键值的长度
(integer) 8
127.0.0.1:6379> append name 'is handsome boy' # 向指定的键追加值
(integer) 23
127.0.0.1:6379> get name
"xiaogangis handsome boy"
#########################################################################
127.0.0.1:6379> incr age # 使用incr命令增加一
(integer) 19
127.0.0.1:6379> incr age
(integer) 20
127.0.0.1:6379> get age
"20"
127.0.0.1:6379> incr age 1
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379> decr age # 使用decr 减一 应用在浏览量方面
(integer) 19
127.0.0.1:6379> decr age
(integer) 18
127.0.0.1:6379> incrby 2 # 使用incrby 增加一个具体的数值
(error) ERR wrong number of arguments for 'incrby' command
127.0.0.1:6379> incrby age 2
(integer) 20
127.0.0.1:6379> decrby 2 # 使用decrby 减少一个具体的数值
(error) ERR wrong number of arguments for 'decrby' command
127.0.0.1:6379> decrby age 2
(integer) 18
127.0.0.1:6379> getrange name 0,5 # 使用getrange获取键值的一个片段
(error) ERR wrong number of arguments for 'getrange' command
127.0.0.1:6379> getrange name 0 5
"xiaoga"
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> setrange name 10 '' # 使用setrange将具体下标后面的内容更换为指定内容
(integer) 23
127.0.0.1:6379> get name
"xiaogangis handsome boy"
127.0.0.1:6379> setrange name 11 'very'
(integer) 23
127.0.0.1:6379> get name
"xiaogangis verysome boy"
127.0.0.1:6379> setex male 5
(error) ERR wrong number of arguments for 'setex' command
127.0.0.1:6379> setex male 5 man # 使用setex设定一个值,并制定过期时间
OK
127.0.0.1:6379> ttl
(error) ERR wrong number of arguments for 'ttl' command
127.0.0.1:6379> ttl male
(integer) -2
127.0.0.1:6379> setnx male man # 使用setn如果值不存在就设置,存在就不设置
(integer) 1
127.0.0.1:6379> setnx age 18
(integer) 0
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> setnx age 20
(integer) 0
127.0.0.1:6379> get age
"18"
127.0.0.1:6379>
127.0.0.1:6379> flushdb # flushdb清除当前的数据库,flushall清除所有的数据库
OK
127.0.0.1:6379> mset name xiaogang is very handsome boys age 18 male man
OK # 使用mset 批量设置数据
127.0.0.1:6379> mget name age male # 使用mget 批量获取数据
1) "xiaogang"
2) "18"
3) "man"
127.0.0.1:6379> get is
"very"
127.0.0.1:6379> keys *
1) "age"
2) "name"
3) "male"
4) "handsome"
5) "is"
127.0.0.1:6379> getset xiaohuanghua ilove # 使用getset,不存在就设置,返回Null
(nil)
127.0.0.1:6379> get xiaohuanghua
"ilove"
127.0.0.1:6379> getset xiaohuanghua dontlove # 使用getset ,存在就返回旧的value ,将
"ilove" # value设置为新的值
127.0.0.1:6379> get xiaohuanghua
"dontlove"
list
可以看做队列(先进先出),栈(先进后出)来进行相应的操作
由于是list,所以命令加上 L

使用lpush 和 rpush 进行左插入和右插入
127.0.0.1:6379> lpush list 'hello'
(integer) 1
127.0.0.1:6379> lpush list 'hello1'
(integer) 2
127.0.0.1:6379> lpush list 'hello3'
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello1"
3) "hello"
127.0.0.1:6379> rpush list 'this is new element'
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello1"
3) "hello"
4) "this is new element"
127.0.0.1:6379> lpush list 'this is left new element'
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "this is left new element"
2) "hello3"
3) "hello1"
4) "hello"
5) "this is new element"
#########################################################################
使用lpop 和 rpop 进行相应的删除操作
127.0.0.1:6379> lpop list 1
1) "this is left new element"
127.0.0.1:6379> rpop list 1
1) "this is new element"
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello1"
3) "hello"
#########################################################################
127.0.0.1:6379> lindex list 2 # 使用lindex 查询指定下标的值
"hello"
127.0.0.1:6379> llen list # 使用llen 获取列表的长度
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello1"
3) "hello"
4) "hello"
5) "hello"
127.0.0.1:6379> lrem list 2 hello # 使用lrem 删除特定的值
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello1"
3) "hello"
127.0.0.1:6379> ltrim list 0 1 # 使用ltrim 加个列表按需截取
OK
127.0.0.1:6379> lrange list 0 1
1) "hello3"
2) "hello1"
127.0.0.1:6379> rpoplpush list newlist # 使用rpoplpush 将最后一个元素删除并加到新的列表中
"hello1"
127.0.0.1:6379> lrange newlist 0 1
1) "hello1"
127.0.0.1:6379> lpush list 'xihuanghua'
(integer) 2
127.0.0.1:6379> lpush list 'xihuanghua'
(integer) 3
127.0.0.1:6379> lpush list 'xihuanghua'
(integer) 4
127.0.0.1:6379> lpush list 'xihuanghua'
(integer) 5
127.0.0.1:6379> lpush list 'xihuanghua'
(integer) 6
127.0.0.1:6379> lset list 2 'qiantian'
OK
127.0.0.1:6379> lrange list 0 -1
1) "xihuanghua"
2) "xihuanghua"
3) "qiantian"
4) "xihuanghua"
5) "xihuanghua"
6) "hello3"
127.0.0.1:6379> lpush list '1' '2'
(integer) 8
127.0.0.1:6379> lpush list '3' '4'
(integer) 10
127.0.0.1:6379> linsert list before '3' 'new3' # 使用Linsert在指定的位置插入元素
(integer) 11
127.0.0.1:6379> linsert list after '4' 'new4'
(integer) 12
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "new4"
3) "new3"
4) "3"
5) "2"
6) "1"
7) "xihuanghua"
8) "xihuanghua"
9) "qiantian"
10) "xihuanghua"
11) "xihuanghua"
12) "hello3"
set
127.0.0.1:6379> sadd mset1 'xiaogang' # 使用sadd 创建集合 并添加元素
(integer) 1
127.0.0.1:6379> sadd mset1 'xiaohuanghua'
(integer) 1
127.0.0.1:6379> sadd mset1 'zhangyu'
(integer) 1
127.0.0.1:6379> SMEMBERS mset1 # 使用SMEMBERS 查看集合中的元素
1) "zhangyu"
2) "xiaogang"
3) "xiaohuanghua"
127.0.0.1:6379> SISMEMBER mset 'hello' # 使用SISMEMBER 查看集合中是否存在查询元素
(integer) 0
127.0.0.1:6379> scard mset1 # 使用scard 查看集合中元素个数
(integer) 3
127.0.0.1:6379> SMEMBERS mset1
1) "zhangyu"
2) "xiaogang"
3) "xiaohuanghua"
127.0.0.1:6379> srem mset1 'xiaogang' # 使用srem 删除特定元素
(integer) 1
127.0.0.1:6379> SMEMBERS mset1
1) "zhangyu"
2) "xiaohuanghua"
127.0.0.1:6379> SRANDMEMBER mset1 # 使用SRANDMEMBER 随机筛选元素
"zhangyu"
127.0.0.1:6379> SRANDMEMBER mset1
"xiaohuanghua"
127.0.0.1:6379> sadd mset1 'hello' 'nihao' 'mustyou'
(integer) 3
127.0.0.1:6379> SMEMBERS mset1
1) "hello"
2) "nihao"
3) "zhangyu"
4) "xiaohuanghua"
5) "mustyou"
127.0.0.1:6379> spop mset1 # 使用spop 随机删除元素
"nihao"
127.0.0.1:6379> SMEMBERS mset1
1) "hello"
2) "mustyou"
3) "zhangyu"
4) "xiaohuanghua"
127.0.0.1:6379> sadd mset2 'thisissecond'
(integer) 1
127.0.0.1:6379> smove mset1 mset2 'hello' #使用smove一个集合中的元素 并添加这个元素到新集合中
(integer) 1
127.0.0.1:6379> SMEMBERS mset2
1) "hello"
2) "thisissecond"
127.0.0.1:6379> sdiff mset1 mset2 #使用sdiff 求两个集合的差集,相对于第一个集合来说
1) "zhangyu"
2) "mustyou"
3) "xiaohuanghua"
127.0.0.1:6379> sunion mset1 mset2 #使用sunion 求两个集合的并集
1) "hello"
2) "zhangyu"
3) "mustyou"
4) "xiaohuanghua"
5) "thisissecond"
127.0.0.1:6379> sadd mset2 'xiaohuanghua'
(integer) 1
127.0.0.1:6379> sinter mset1 mset2 #使用 sinter 求两个集合的交集
1) "xiaohuanghua"
hash
map集合,key-value -->key-< fild:value >.值可以看做一个map集合,和string没有太大区别
127.0.0.1:6379> hset myhash field1 hello field1 world # 创建一个hash集合
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取hash集合中指定的值
"world"
127.0.0.1:6379> hset myhash field2 world
(integer) 1
127.0.0.1:6379> hgetall myhash # 使用hgetall 查看hash中 所有的内容
1) "field1"
2) "world"
3) "field2"
4) "world"
127.0.0.1:6379> hmset myhash fiedl3 xiaogang fiedl4 xiangzhang # 使用hmset批量操作
OK
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "world"
3) "field2"
4) "world"
5) "fiedl3"
6) "xiaogang"
7) "fiedl4"
8) "xiangzhang"
127.0.0.1:6379> hdel myhash field1 # 使用hdel删除指定的元素
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "fiedl3"
4) "xiaogang"
5) "fiedl4"
6) "xiangzhang"
127.0.0.1:6379> hlen myhash # 使用hlen 获取hash集合的长度
(integer) 3
127.0.0.1:6379> HEXISTS myhash field2 # 使用HEXISTS 判断hash集合中是否有查询的值
(integer) 1
127.0.0.1:6379> HKEYS myhash # 使用HKEYS 获取键名
1) "field2"
2) "fiedl3"
3) "fiedl4"
127.0.0.1:6379> HVALS myhash #使用hvals 获取键值
1) "world"
2) "xiaogang"
3) "xiangzhang"
127.0.0.1:6379> hset myhash filed5 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash filed5 2 # 使用HINCRBY 实现增加
(integer) 7
zset(有序集合)
在普通的set里面 加上权重
127.0.0.1:6379> zadd myset 1 hello 2 world 3 xiaohuanghua # 添加元素
(integer) 3
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf # 使用ZRANGEBYSCORE从小到大
1) "hello"
2) "world"
3) "xiaohuanghua"
127.0.0.1:6379> ZREvrange myset 0 -1 # 使用ZREvrange从大到小进行排序
1) "xiaohuanghua"
2) "world"
3) "hello"
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf withscores #排序时候带上scores
1) "hello"
2) "1"
3) "world"
4) "2"
5) "xiaohuanghua"
6) "3"
具体的命令 http://redis.cn/commands.html#sorted_set
三种特殊数据类型
geospatial(地理位置)
应用在定位,附近的人,打车距离等
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
当坐标位置超出上述指定范围时,该命令将会返回一个错误

geoadd 添加元素
geoadd key member
127.0.0.1:6379> geoadd china 116.40 39.90 beijin
(integer) 1
127.0.0.1:6379> geoadd china 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china 120.15 30.28 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china 120.60 31.29 suzhou
(integer) 1
127.0.0.1:6379> geoadd china 106.50 29.53 chongqin
(integer) 1
geopos 获取值得经度纬度
geopos key member
127.0.0.1:6379> geopos china beijin chongqin guizhou
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
3) 1) "106.70999854803085327"
2) "26.57000036883258787"
georadius 在半径内找目标
127.0.0.1:6379> GEORADIUS china 110 20 1000 km withdist
1) 1) "guizhou"
2) "804.2331"
2) 1) "guizhuo"
2) "804.2331"
127.0.0.1:6379> GEORADIUS china 110 20 1000 km withcoord
1) 1) "guizhou"
2) 1) "106.70999854803085327"
2) "26.57000036883258787"
2) 1) "guizhuo"
2) 1) "106.70999854803085327"
2) "26.57000036883258787"
127.0.0.1:6379> GEORADIUS china 110 20 1000 km withhash
1) 1) "guizhou"
2) (integer) 4022012816062167
2) 1) "guizhuo"
2) (integer) 4022012816062167
127.0.0.1:6379> GEORADIUS china 110 20 1000 km withcoord count 1
1) 1) "guizhou"
2) 1) "106.70999854803085327"
2) "26.57000036883258787"
geodist 获取两个位置的距离
127.0.0.1:6379> geodist china beijin hangzhou km
"1122.7998"
geohash 将目标位置的二维坐标转换为一维的11个字符的Geohash字符串
127.0.0.1:6379> geohash china guizhou chongqin
1) "wkezhhzv7p0"
2) "wm5xzrybty0"
georadiusbymember 这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是
GEORADIUSBYMEMBER的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点
127.0.0.1:6379> GEORADIUSBYMEMBER china guizhou 1000 km
1) "guizhou"
2) "guizhuo"
3) "chongqin"
hyperloglog
基数: A{1,2,3,4,5,6}
B{5,6,7,8,3,2}
基数:{1,2,3,4,5,6,7,8}
使用基数进行统计,使用场景比如在网站的访问量上面,传统的做法是使用set保存用户id ,然后进行求值,但是对于内存的需求很高,而使用hyperloglog,对于2^64的不同元素的技术,只需要消耗12kb的固定内存,对于允许容错率的地方,首选hyperloglog。81%的正确率
127.0.0.1:6379> pfadd china a s d f g h j # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount china # 统计个数
(integer) 14
127.0.0.1:6379> pfadd china2 z v b n m a s d
(integer) 1
127.0.0.1:6379> pfcount china2
(integer) 8
127.0.0.1:6379> pfmerge china3 china china2 # 合并元素
OK
127.0.0.1:6379> pfcount china3
(integer) 19
bitmaps(位存储)
使用二进制来进行相应的操作,只有0和1两种状态
应用场景:上班是否打卡,登陆状态是否显示,使用很小的内存来进行一些存储,比如一年的打卡次数,只需要几kb 的内存就能实现。

[root@keleshushu ~]# redis-cli -p 6379
127.0.0.1:6379> setbit dakastatus 0 1
(integer) 0
127.0.0.1:6379> setbit dakastatus 1 0
(integer) 0
127.0.0.1:6379> setbit dakastatus 2 0
(integer) 0
127.0.0.1:6379> setbit dakastatus 3 1
(integer) 0
127.0.0.1:6379> setbit dakastatus 4 0
(integer) 0
127.0.0.1:6379> setbit dakastatus 5 0
(integer) 0
127.0.0.1:6379> setbit dakastatus 6 1
(integer) 0
127.0.0.1:6379> getbit dakastatus 6 # 获取某一天是否打卡
(integer) 1
127.0.0.1:6379> getbit dakastatus 3
(integer) 1
127.0.0.1:6379> bitcount dakastatus #统计打卡的天数
(integer) 3
事务
redis事务本质,一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行的过程中,按照顺序执行
一次性,顺序性,排他性,执行一系列的命令
----- 队列 set set set 执行-------
redis 事务没有隔离级别的概念
所有的命令在事务中,并没有直接执行,只有发起执行命令的时候才会执行,Exec.
redis单条命令保存原子性,事务不保证原子性
redis的事务:
- 开启事务(multi)
- 命令入队(.........)
- 执行事务( exec )
正常入队
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
127.0.0.1:6379> get k1
"v1" # 事务里面的命令正常执行
discard 取消事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> discard # 取消事务
OK
127.0.0.1:6379> get k2
(nil) # 事务中的命令没有被执行
编译型异常 代码有问题,事务中所有的问题都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> setget k3 v3
(error) ERR unknown command `setget`, with args beginning with: `k3`, `v3`,
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2
(nil) # 当事务中有代码问题时候,就不会执行事务中的任何命令
运行时异常(1/0),如果事务队列中存在语法错误,其他命令正常执行,错误的命令抛出异常
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 'v1'
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range # 当有错误语法时候,其他命令正常执行,错误语句抛出异常
3) OK
事务的乐观锁和悲观锁
watch 实现监控
悲观锁:
就是很悲观,认为什么时候都会出错,都会加锁
乐观锁:
就是很乐观,认为什么时候都不会出现问题,所以不上锁,更改数据的时候去判断数据是否在此期间被修改过
获取version
更新的时候比较version
正常的执行
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 990
2) (integer) 10
测试多线程(多个客户端对同一数据进行修改)乐观锁的作用
在第一个线程中进行操作
127.0.0.1:6379> set money 100 # 2
OK
在第二个线程中进行操作
127.0.0.1:6379> watch money # 1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby money 10
QUEUED
127.0.0.1:6379(TX)> decrby out 10
QUEUED
127.0.0.1:6379(TX)> exec # 3
(nil)
在上面两个片段中,有三个步骤,首先在一个线程中对money属性进行监控,这是步骤1 ,然后进行一系列的事务操作,当进行到步骤3的时候 ,先在另外一个线程中执行步骤2 ,对money属性进行修改,然后在执行步骤3发现执行错误。
Python操作redis
安装
https://blog.csdn.net/linwow/article/details/95227392
pip install redis
普通连接
redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py
import redis
# from redis_pool import Pool
redis_server = redis.Redis(host='81.68.109.139',port=6379)
redis_server.set('name','helloredis')
print(redis_server.get('name'))
连接池链接
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池
import redis
from redis_pool import Pool
conn = redis.Redis(connection_pool=Pool)
conn.flushdb()
conn.hmset('test',{'name':'hello','age':'18'})
print(conn.hvals('test'))
Python -->String

Python --> List

Python --> Set

Python --> Zset

Python -- > Hash

Python -- > Redis >> 事务
import redis
POOL = redis.ConnectionPool(host='localhost',port=6379)
conn = redis.Redis(connection_pool=POOL)
pipline = conn.pipeline(transaction=True)
pipline.multi()
pipline.set('name','hello')
pipline.set('age',18)
pipline.execute()
print(conn.mget('age','name')) # 返回value
在连接过程可以设定密码,两种方式
1、直接在redis.conf中设定
2、通过config set requirepass '' 在命令行设置
redis持久化
RDB(Redis DataBase)
redis是内存数据库,如果不见内存中的数据保存到磁盘,那么一旦服务器进程退出,那么服务器中的内存状态也会消失,所以redis提供了持久化功能。

!(.\20200818081754919.png)在指定的时间间隔内将内存中的数据集体快照写入磁盘,恢复时将快照文件中的内容直接读到内存中


redis会单独创建一个子进程来进行持久化,会先将数据写入一个临时文件中,等到持久化过程结束以后,在用这个零临时文件替换上次的持久化好的文件,主进程是不进行任何IO操作的,确保了极高的性能,在进行大规模的数据恢复,且对于数据恢复的完整性不是那么敏感的时候,RDB比AOF更加高效,缺点是最后一次的持久化可能丢失
默认保存的文件名是dump.rdb,也可以自己修改,但是不建议
触发规则
1、save的规则满足的情况下,会触发rdb规则
2、执行flushdb,也会触发
3、退出redis,也会产生rdb文件
恢复rdb文件
1、将rdb文件放到redis的启动目录中(使用config get dir查看启动目录)
优点:
1、适合大规模的数据恢复,
2、对数据的完整性不高,
缺点:
1、需要时间间隔进程操作,意外宕机,最后一次的修改数据就没有了
2、fork子进程的时候,会占用一定的内存
AOF(Append Only File)
相当于Linux中的history,将在操作的命令保存在.aof文件中(只保存写操作的命令,读操作的不保存)
默认是不开启的,在redis.conf中修改


修改以后,使用save保存

再进行修改以后,查看appendonly.aof文件,看见写操作的命令保存,没有读操作的命令

在对appendonly.aof,进行自定义的修改,并保存,检测是否能连接redis

发现不能连接redis,因为aof文件有错误

使用 redis-check-aof --fix appendonly.aof 进行修复,检测是否能链接

连接成功

优点:
数据的完整性会更好,只会丢失一秒钟的数据内容,每秒同步一次
aof默认文件无限追加,文件会越来越大。
缺点:
因为aof是一个文件,大小大于rdb文件,所以速度比rdb慢运行效率也比rdb慢,所以默认是rdb 。
两种方式的对比
1、RDB持久化能够在指定的时间间隔内对你的数据进行快照存储
2、AOF持久方式记录每次对服务器的重写操作,当服务器重启的时候会重新执行这些操作,来恢复原来的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾。还能对AOF文件进行后台重写,是的AOF文件的体积不会过大
3、如果只是做缓存,只是希望数据在服务器运行的时候存在,那么就不需要进行持久化
4、同时开启两种方式:
- redis重启的时候会优先载入AOF文件来恢复原来的数据,因为AOF文件保存的数据比RDB文件保存的数据的完整性要好
- RDB的数据不实时,同时使用两种方式也会找AOF文件,相对来说,RDB文件更适合用来在主从配置过程中在从服务器上进行备份,
5、性能建议
- RDB文件用作备份,在Slave(从服务器)上持久化RDB文件,只要15分钟备份一次就可以,只保留save 900 1 这条规则
- 如果允许AOF ,那么尽量减少rewrite的频率,另外,默认保存的设置中的AOF重写的基础大小64M 太小了,条件允许可以设到5G以上,默认超过100%大小重写可以改到适当的数值
- 如果不允许AOF,虽然省掉IO操作,也减少rewrite带来的系统波动,但是如果 主 - 从 服务器同时 宕机,会丢失数据,启动时候也要比较两个服务中最新的那个RDB文件,加载最新的那个,微博就是这种结构。
redis发布订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。



具体测试
开启两个客户端,在一个客户端输入subscribe 频道名称

在另外一个客户端输入publish 频道名称 message

原理
redis是通过C实现的,使用PUBLISH,SUBSCRIBE,PSUBCRIBE实现发布和订阅功能
通过subscribe订阅某个频道后,redis-server 里维护了一个字典,频道就是字典里面的键key,字典的值就是一个链表,这个链表里面保存了所有的订阅者的客户端,SUBSCRIBE就是将客户端添加到给定的频道的链表中。
使用PUBLSIH向订阅者发布消息,redis-server就会在给定的频道作为键,向这个键的键值链表中发送消息
在redis中,对某一个key值进行消息发布和消息订阅,当一个key值上进行了消息发布后,所有订阅的客户端都会收到消息,可以用在普通的即时聊天和群聊等功能。
Redis主从复制
概念
主从复制是将一台服务器上redis数据复制到其他服务器上,前者称为主节点,后者称为从节点,数据的复制是单向的,只能从主节点到从节点,主节点以写为主,从节点以读为主。
默认情况下,每台redis服务器都是主节点,且一个主节点可以有多个从节点,一个从节点只能有一个主节点
主从复制的作用主要在:
- 数据冗余,实现数据的热备份
- 故障恢复,避免单点故障带来的服务不可用
- 读写分离,负载均衡。主节点负载读写,从节点负责读,提高服务器并发量
- 高可用基础,是哨兵机制和集群实现的基础
查看redis服务信息
[root@keleshushu ~]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master # 默认是主服务器
connected_slaves:0 # 从服务器为0
master_failover_state:no-failover
master_replid:169559905d27c7c83530525b72ece61b45a28dc2
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
配置redis服务
[root@keleshushu /]# cd /usr/local/bin
[root@keleshushu bin]# ls
busybox-x86_64 jemalloc-config jeprof luajit mcrypt pcre2-config pcre2test redis-benchmark redis-check-rdb redis-sentinel
dump.rdb jemalloc.sh libmcrypt-config luajit-2.0.4 mdecrypt pcre2grep rain redis-check-aof redis-cli redis-server
[root@keleshushu bin]# cd rain
[root@keleshushu rain]# ls
appendonly.aof dump.rdb redis.conf
[root@keleshushu rain]# rm -rf appendonly.aof
[root@keleshushu rain]# rm -rf dump.rdb
[root@keleshushu rain]# cp redis.conf redis79.conf # 分别创建三个配置文件,用端口的方式进行区分
[root@keleshushu rain]# cp redis.conf redis80.conf
[root@keleshushu rain]# cp redis.conf redis81.conf
#############################################################################
# 对三个配置文件中的端口号,进程id,日志文件名、dump数据库文件名进行相应的修改
[root@keleshushu rain]# vim redis79.conf
[root@keleshushu rain]# vim redis80.conf
[root@keleshushu rain]# vim redis81.conf
#############################################################################
# 分别启动三个服务
[root@keleshushu rain]# redis-server /usr/local/bin/rain/redis79.conf
[root@keleshushu rain]# redis-server /usr/local/bin/rain/redis80.conf
[root@keleshushu rain]# redis-server /usr/local/bin/rain/redis81.conf
[root@keleshushu rain]# ps -ef| grep redis
#############################################################################
# 查看是否启动 ps -ef| grep redis
root 16322 1 0 14:14 ? 00:00:00 redis-server 127.0.0.1:6380
root 16358 1 0 14:14 ? 00:00:00 redis-server 127.0.0.1:6381
root 16419 14691 0 14:14 pts/0 00:00:00 grep --color=auto redis
root 25851 1 0 10:14 ? 00:00:15 redis-server 127.0.0.1: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=84,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=84,lag=1
master_failover_state:no-failover
master_replid:3313ded50b414fccd4a7dc1c54d7ab7d28cba743
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
两个从机的信息
[root@keleshushu ~]# redis-cli -p 6380
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # 使用SLAVEOF配置主机的信息
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:3
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:3313ded50b414fccd4a7dc1c54d7ab7d28cba743
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
#################################################################
[root@keleshushu ~]# redis-cli -p 6381
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379 # 使用SLAVEOF配置主机的信息
OK
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:10
master_sync_in_progress:0
slave_repl_offset:56
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:3313ded50b414fccd4a7dc1c54d7ab7d28cba743
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:56
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:43
repl_backlog_histlen:14
主机和从机操作
在主机上
127.0.0.1:6379> set k1 v1
OK
在从机上
127.0.0.1:6380> get k1
"v1"
################################
127.0.0.1:6381> get k1
"v1"
通过命令行设置的主从复制只是暂时的,在真正的工作中,是在配置文件中进行配置的

主机断开链接,从机依然可以执行读操作,主机重新连接,从机依然可以从主机上获取主机写的信息
使用命令行配置的主从复制,如果从机断开连接,就会变成新的主机,只要变为从机,就会重新在主机中获取值
复制原理
从机器启动成功并连接到主机器后会发送一个sync同步命令
主机接到命令,启动后台的存盘进程,同时收集所有接受到的用于修改数据集命令,在后台的进程执行完毕之后,主机将整个数据文件传送到从机上,并完成一次完全同步
全量复制:slave服务在接受到数据库文件后,将其存盘并加载到内存中
增量复制:master继续将新的所有收集到修改命令依次传给slave,完成同步
多层嵌套

在上图中,虽然80是81 的master,但是它的身份还是slave,只能进行读操作
手动设置master
如果redis79宕机以后,那么在两个redis服务器80和81中,选择一个当做master
在redis80命令行之中输入 slave no one 解除slave模式
在81中输入 slave 127.0.0.1 6380 将redis80当做自己的master
哨兵模式
概述
一种自动选取‘老大’方式,在哨兵模式之前,如果master宕机之后,需要手打设置将一台slave设置为master,但是会造成一段时间里面的服务器不可用,所以使用哨兵模式
发生故障之后,根据投票数,自动将slave设置为master
哨兵模式是一种特殊的模式,是一个独立的进程,会独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例

这里的哨兵有两个作用:
- 通过发送命令,让redis服务器返回监控其运行状态,master和slave
- 当检测到master宕机之后,会自动将slave切换为master,然后通过发布订阅的方式通知其他的slave,修改配置文件,让他们切换主机。
但是一个哨兵也会出现故障 ,所以会设置多个哨兵,让他们之间相互监控
当一个哨兵发现主服务器不可用,不会进行failover过程,只是一个哨兵认为不可用,这个现象叫做主观下线,当所有的哨兵也发现主服务器不可用,那么这些哨兵之间会进行一次投票,投票的过程有一个哨兵发起,进行failover(故障转移)操作,切换成功,就会通过发布订阅的方式,让各个哨兵将自己监控的从服务器切换,这个过程称为客观下线
新建一个sentinel.conf文件,写入
sentinel monitor hostname ip port number
比如 sentinel monit myredsi 127.0.0.1 6379 1
将端口号为6379 的redis服务设置为master,后面的数字挂了
[root@keleshushu rain]# redis-sentinel /usr/local/bin/rain/sentinel.conf
24707:X 16 Aug 2021 09:50:37.591 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
24707:X 16 Aug 2021 09:50:37.591 # Redis version=6.2.5, bits=64, commit=00000000, modified=0, pid=24707, just started
24707:X 16 Aug 2021 09:50:37.591 # Configuration loaded
24707:X 16 Aug 2021 09:50:37.592 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.5 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 24707
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
24707:X 16 Aug 2021 09:50:37.593 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
24707:X 16 Aug 2021 09:50:37.600 # Sentinel ID is 167caf644fb8c5e350d93ce3de71a015a809e091
24707:X 16 Aug 2021 09:50:37.600 # +monitor master myredis 127.0.0.1 6379 quorum 1
24707:X 16 Aug 2021 09:50:37.601 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:50:37.606 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
当master宕机之后,哨兵会自动在监控的redis服务中选取新的作为master

24707:X 16 Aug 2021 09:53:14.141 # +sdown master myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:14.141 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
24707:X 16 Aug 2021 09:53:14.141 # +new-epoch 1
24707:X 16 Aug 2021 09:53:14.141 # +try-failover master myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:14.147 # +vote-for-leader 167caf644fb8c5e350d93ce3de71a015a809e091 1
24707:X 16 Aug 2021 09:53:14.147 # +elected-leader master myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:14.147 # +failover-state-select-slave master myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:14.218 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:14.218 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:14.294 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:15.091 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:15.091 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:15.184 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:16.144 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:16.144 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:16.210 # +failover-end master myredis 127.0.0.1 6379
24707:X 16 Aug 2021 09:53:16.210 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381
24707:X 16 Aug 2021 09:53:16.210 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381
24707:X 16 Aug 2021 09:53:16.210 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
由上面可知,79redis服务关闭之后,哨兵自动将master从79切换到81
127.0.0.1:6381> info replication
# Replication
role:master # 从slave自动切换到master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=228308,lag=0
master_failover_state:no-failover
master_replid:4b58b7a0ec6e4ba7f3d9dc697b15746ddfc41d27
master_replid2:3313ded50b414fccd4a7dc1c54d7ab7d28cba743
master_repl_offset:228308
second_repl_offset:224916
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:43
repl_backlog_histlen:228266
当旧的master重新上线之后,哨兵会将其变为新的master的slave
[root@keleshushu ~]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:slave # 重新上线之后,角色从master 变为 slave
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:233723
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:4b58b7a0ec6e4ba7f3d9dc697b15746ddfc41d27
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:233723
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:233569
repl_backlog_histlen:155
哨兵模式
在redis 的配置文件中,可以设置failover时间和一些相关的配置
优点:
1、哨兵集群,基于主从复制,所有的主从配置的优点,全有
2、主从可以切换,故障可以转移,系统的可用性会更好
3、主从模式的升级,手动到自动
缺点:
1、redis不好在线扩容,集群容量一旦到达上线,扩容十分麻烦
Redis击穿、穿透和雪崩
网上看见一个段子,可以加深对这三个的理解:
缓存穿透类似偷袭,绕过radis,袭击数据库。缓存击穿类似正面硬刚,一直进攻一个地方,直到失效时一起涌入攻击数据库。缓存雪崩类似鬼子进村
redis雪崩
大面积的缓存失效
在双十一或者其他一些电商热点节日的时候,热点的数据或者首页都会做缓存,缓存都是定时任务去刷新,或者查不到之后去刷新,比如所有的key的有效时间都是12个小时,在零点刷新,那么在零点时候,有大量的用户访问,如果缓存中的key是有效的,那么是可以承受这些访问量的,但是在那一瞬间,所有的key都在刷新,相当于在哪个时候内所有的key都是失效的,然后所有的访问量都会在那一瞬间同时访问数据库,那么数据库就会挂掉

处理方法
setRedis(key,value,time + Math.random() * 10000) 设置随机的有效时间
缓存穿透
在redis缓存和数据库中都没有的数据,但是还是在一直请求
比如在数据库的id从1自增,那么访问id=-1,缓存没有,数据库也没有。
如果高并发的一直访问,数据库就容易崩掉
处理方式
可以将对应Key的Value对写为null、位置错误、稍后重试这样的值
布隆过滤器(Bloom Filter)
缓存击穿
缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
处理方式
设置热点数据永远不过期,或者加上互斥锁