一、五大数据类型
相关命令参考https://www.redis.net.cn/tutorial/3508.html
1、string
相关命令
重要的几个关于key的命令
keys [表达式] #匹配指定key
exists key # 判断当前的key是否存在
move key [num]# 移除指定num数据库的key
del key key #移除当前所在数据库的key(和上面的move类似)
expire key [num] 设置key过期时间
ttl key # 查看当前key的剩余时间
type key 查看key的类型
ㅤ
string相关指令
get key
set key
exists key
incr key 前提数值类型
dece key 前提数值类型
incrby key num 自增num
setnx (set if not exist) key value# 不存在再设置 (在分布式锁中会常常使用!)
setex(set with expire) key time value #设置key 的值为 value,time秒后过期
mset key value key value 设置多个值
getset key value # 如果不存在值,则返回 nil,如果存在值,获取原来的值,并设置新的值
ㅤ
一个巧妙的设计 user:{id}:{filed}
如set user:1 {name:zhangsan,age:3} # 设置一个user:1 对象 值为 json字符来保存一个对象!
ㅤ
应用场景
1、计数器 (incr)
2、对象存储(比如储存用户信息)
3、分布式锁(setnx,返回1表示获取到锁,0表示没有获取到)
分布式锁https://blog.youkuaiyun.com/kongmin_123/article/details/82080962
https://www.cnblogs.com/wukongbubai/p/12393370.html
ㅤ
2、list(列表)
ㅤ
应用场景
消息队列 (Lpush Rpop)
栈( Lpush Lpop)
排行榜
list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等
最新列表
ㅤ
数据结构
使用了quiclklist
quiclklist=ziplist(压缩列表)+linklist(双向链表)
quliclist数据结构底层实现参考https://blog.youkuaiyun.com/gongsenlin341/article/details/110527676
ㅤ
3、set
相关命令
sadd key value
SMEMBERS set# 查看指定set的所有值
SISMEMBER myset hello # 判断某一个值是不是在set集合中!
scard myset # 获取set集合中的内容元素个数!
srem myset hello # 移除set集合中的指定元素
SRANDMEMBER myset 2 # 随机抽选出指定个数的元素
SINTER key1 key2 # 交集 共同好友就可以这样实现
…
ㅤ
应用场景
评论点赞
好友/关注/粉丝/感兴趣的人集合(set提供了相关集合操作)
随机展示
黑名单/白名单
ㅤ
数据结构
intset+hashtable
intset内部其实是一个数组(int8_t coentents[]数组),而且存储数据的时候是有序的,因为在查找数据的时候是通过二分查找来实现的。
使用intset的条件
- 如果能够转成int的对象(isObjectRepresentableAsLongLong),那么就用intset保存。
- 如果用intset保存的时候,如果长度超过512(REDIS_SET_MAX_INTSET_ENTRIES)就转为hashtable编码。
- 其他情况统一用hashtable进行存储。
ㅤ
4、hash
Map集合,key-map! 时候这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的key-vlaue!
相关命令
hset myhash field1 kuangshen # set一个具体 key-vlaue
hget myhash field1 # 获取一个字段值
hdel myhash field1 # 删除hash指定key字段!对应的value值也就消失了!
hlen myhash # 获取hash表的字段数量!
HEXISTS myhash field1 # 判断hash中指定字段是否存在!
hsetnx myhash field4 hello # 如果不存在则可以设置
ㅤ
应用场景
hash变更的数据 user name age,尤其是是用户信息之类的,经常变动的信息! hash 更适合于对象的存储,String更加适合字符串存储!
ㅤ
4、zset
在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1
相关命令
zadd myset 1 one # 添加一个值
zadd myset 2 two 3 three # 添加多个值
ZRANGE myset 0 -1
ZREVRANGE salary 0 -1 # 从大到小进行排序!
zrem salary xiaohong
zcard salary # 获取有序集合中的个数
zcount myset 1 3 # 获取指定区间的成员数量!
ㅤ
应用场景
常用于排行榜 成绩
带权重的消息队列
ㅤ
数据结构
dict(map)+zskiplist(跳表)
跳表数据结构https://zhuanlan.zhihu.com/p/101143158/
使用map的原因:查询指定元素可以通过map达到O(1)的查询效率
使用跳表:为了保证集合有序,提高插入删除效率O(logn)
二、三种特殊类型
ㅤ
1、Geospatial 地理位置
相关命令
参考文档
使用场景
可以推算地理位置的信息,两地之间的距离,方圆几里的人!
ㅤ
2、Hyperloglog
基数统计的算法!
优点:占用的内存是固定,2^64 不同的元素的技术,只需要废 12KB内存!如果要从内存角度来比较的话 Hyperloglog 首选!
相关命令
参考文档
使用场景
网页的 UV (一个人访问一个网站多次,但是还是算作一个人!)
传统的方式, set 保存用户的id,然后就可以统计 set 中的元素数量作为标准判断 !
这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id;
0.81% 错误率! 统计UV任务,可以忽略不计的!
如果允许容错,那么一定可以使用 Hyperloglog !
如果不允许容错,就使用 set 或者自己的数据类型即可!
ㅤ
3、Bitmap
位存储
统计用户信息,活跃,不活跃! 登录 、 未登录! 打卡,365打卡! 两个状态的,都可以使用Bitmaps!
Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态!
365 天 = 365 bit 1字节 = 8bit 46 个字节左右!
ㅤ
三、事务
Redis事务没有没有隔离级别的概念!
Redis单条命令式保存原子性的,但是事务不保证原子性!
编译型异常(代码有问题! 命令有错!) ,事务中所有的命令都不会被执行!
运行时异常(1/0), 如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
redis事务不支持回滚
可以参考http://www.redis.cn/topics/transactions.html
watch监视(一种乐观锁)
WATCH 命令可以被调用多次。 对键的监视从 WATCH 执行之后开始生效, 直到调用 EXEC 为止。
事务的相关命令
watch key 监视
multi 开启事务
discard 取消事务
exec 执行事务
ㅤ
四、redis.conf
相关配置参数
bind 127.0.0.1 # 绑定的ip,可以绑定多个 (不写的话默认所有ip可以连接)
protected-mode yes # 保护模式
关闭protected-mode模式,此时外部网络可以直接访问
开启protected-mode保护模式,需配置bind ip或者设置访问密码
port 6379 # 端口设置
daemonize yes # 以守护进程的方式运行,默认是 no,我们需要自己开启为yes!
loglevel notice 日志级别
logfile “” # 日志的文件位置名
databases 16 # 数据库的数量,默认是 16 个数据库
快照
持久化, 在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb. aof
redis 是内存数据库,如果没有持久化,那么数据断电及失!
# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试!
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作!
rdbcompression yes # 是否压缩 rdb 文件,需要消耗一些cpu资源!
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验!
dir ./ # rdb 文件保存的目录!
SECURITY 安全
config get requirepass # 获取redis的密码
config set requirepass “123456” # 设置redis的密码
maxclients 10000 # 设置能连接上redis的最大客户端的数量
maxmemory # redis 配置最大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,
rdb完全够用!
appendfilename “appendonly.aof” # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
ㅤ
五、redis持久化
Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能!
RDB(Redis DataBase)
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bucAjKZU-1633959848433)(img\QQ截图20210618180458.png)]
触发:
1、save的规则满足的情况下,会自动触发rdb规则
2、执行 flushall 命令,也会触发我们的rdb规则!
3、退出redis,也会产生 rdb 文件!
备份就自动生成一个 dump.rdb
优点:
1、适合大规模的数据恢复!
缺点:
1、需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的
2、fork进程的时候,会占用一定的内存空间
ㅤ
AOF(Append Only File)
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍!
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
aof 默认就是文件的无限追加,文件会越来越大!
如果 aof 文件大于 64m,太大了! fork一个新的进程来将我们的文件进行重写!(根据内存中的数据进行重写)
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,
rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
# rewrite 重写
优点:
1、每一次修改都同步,文件的完整会更加好!
2、每秒同步一次,可能会丢失一秒的数据
3、从不同步,效率最高的!
缺点:
1、相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!
2、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 这条规则。
如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自
己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产
生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite
的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重
写可以改到适当的数值。
如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也
减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,
启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。
ㅤ
六、消息订阅模型
一种一对多的消息模型
案例
订阅端:
127.0.0.1:6379> SUBSCRIBE kuangshenshuo # 订阅一个频道 kuangshenshuo
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "kuangshenshuo"
3) (integer) 1
# 等待读取推送的信息
1) "message" # 消息
2) "kuangshenshuo" # 那个频道的消息
3) "hello,kuangshen" # 消息的具体内容
1) "message"
2) "kuangshenshuo"
3) "hello,redis"
发送端:
127.0.0.1:6379> PUBLISH kuangshenshuo "hello,kuangshen" # 发布者发布消息到频道!
(integer) 1
127.0.0.1:6379> PUBLISH kuangshenshuo "hello,redis" # 发布者发布消息到频道!
(integer) 1
127.0.0.1:6379>
原理
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 频道!,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
使用场景:
1、实时消息系统!
2、事实聊天!(频道当做聊天室,将信息回显给所有人即可!)
3、订阅,关注系统都是可以的!
稍微复杂的场景我们就会使用 消息中间件 MQ
ㅤ
七、redis主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。
默认情况下,每台Redis服务器都是主节点;
主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存!
复制原理
Slave 启动成功连接到 master 后会发送一个sync同步命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行
完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!
主从复制原理参考https://www.cnblogs.com/daofaziran/p/10978628.html
ㅤ
八、哨兵模式
(自动选举老大的模式)
主机挂了,从新选举主机
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独
立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis**实例。
这里的哨兵有两个作用
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
多哨兵监控
假设主服务器宕机,哨兵检查到主服务器不可用,并且数量达到一定值时,哨兵之间就会进行一次投票,重新选出新的主服务器。
优点:
1、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
2、 主从可以切换,故障可以转移,系统的可用性就会更好
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点:
1、Redis 不好啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
2、实现哨兵模式的配置其实是很麻烦的,里面有很多选择!
ㅤ
九、Redis缓存问题
缓存穿透
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
redis布隆过滤器解决缓存穿透问题 https://www.cnblogs.com/ysocean/p/12594982.html
底层使用bitmap,一个很长的hash表。布隆过滤器可以判断一个元素一定不存在,但无法判断一定存在。
https://blog.youkuaiyun.com/dreamispossible/article/details/89972545
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多
的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于
需要保持一致性的业务会有影响。
ㅤ
缓存击穿(量太大,缓存过期!)
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
ㅤ
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
ㅤ
缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
ㅤ
解决方案
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
不会出现热点 key 过期后产生的问题。