为了避免学过就忘,写篇文章记录一下Redis的知识点。
官方文档 定义Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)
set key value # set值keys * #查看当前db的所有keyget key # 得到key的valueexists key # 是否存在key(键)move key db # 移动键到另外一个数据库,共有16个数据库,默认为0expire key 秒数 # 设置过期时间,单位是秒ttl key # 查看还有多少时间过期type key # 查看key的具体类型
append key "字符串" # 向key后追加一个字符串,如果当前key不存在,就相当于setstrlen key # 获取字符串长度incr key # 加一decr key # 自减1incrby value #增加value,可以设置步长,指定增量decrby value # 减少value############################################################字符串范围 range getrange key start end # 下标从0开始,start-endgetrange key 0 -1 # 查看整个字符串,获取全部字符串,和get key是一样的# 替换# 替换指定位置开始的字符串setrange key offset string # offset:偏移量,string:要替换的字符串############################################################ setex(set with expire) # 设置过期时间# setnx(set if not exist) # 不存在设置,在分布式锁中会经常使用,保证当前这个值存在127.0.0.1:6379> setex key3 30 hello # 设置key3的值30s后过期OK127.0.0.1:6379> ttl key3(integer) 27127.0.0.1:6379> get key3"hello"127.0.0.1:6379> setnx mykey "redis" # 如果mykey不存在,创建mykey(integer) 1127.0.0.1:6379> keys *1) "mykey"2) "key2"3) "key1"127.0.0.1:6379> ttl key3(integer) -2127.0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,则创建失败(integer) 0127.0.0.1:6379> get mykey"redis"######################################################### 批量set,getmset k1 v1 k2 v2 ... # 同时设置多个值mget k1 k2 ... # 同时获取多个值msetnx k1 v1 k2 v2 ... # msetnx是一个原子性的操作,要么一起成功,要么一起失败# 对象set user:1 {name:zhangsan,age:3} # 设置一个user:1对象,值为json字符串来保存一个对象mset user:1:name zhangsan user:1:age 20 # 这里的key是一个巧妙的设计,user:{id}:{field},如此设计在redis中是完全可以的getset # 先get后segetset key value # 如果不存在值则返回nil,如果存在值则获取原来的值,然后set
String类似的使用场景:value除了是我们的字符串还可以是数字
计数器
统计多单位的数量 uid
粉丝数
对象缓存存储
基本的数据类型,列表
list命令基本都是用l
来开头的,不区分大小写命令
127.0.0.1:6379> lpush list one # 将一个值或多个值插入到列表的头部(integer) 1127.0.0.1:6379> lpush list two(integer) 2127.0.0.1:6379> lpush list three(integer) 3127.0.0.1:6379> lrange list 0 -1 # 获取所有元素1) "three"2) "two"3) "one"127.0.0.1:6379> lrange list 0 1 # 获取0-1,通过区间获取具体的值1) "three"2) "two"127.0.0.1:6379> rpush list right # 将一个值放在链表的尾部(integer) 4127.0.0.1:6379> lrange list 0 -11) "three"2) "two"3) "one"4) "right"##############################################################lpop key # 移除列表的第一个元素rpop key # 移除列表的最后一个元素lindex key index # 获取key的index下标处的值,index从0开始llen key # 获取list的长度# 移除指定的值lrme key count value # 删除count个value,从前往后删除,精确匹配#############################################################trim 修剪操作:list截断ltrim key start stop # 截取list的start到stop之间的所有元素,左右都闭合,这个list就已经被改变了,只剩下截取的元素rpoplpush source destination # 移除列表的最后一个元素并添加到目的列表的第一个exists key # 判断里面有没有值lset key index element # 设置key的index下标位置的值为element ,只有存在这个index下标才可以使用linsert key before|after pivot element # 在pivot之前或之后插入值
小结:
list实际上一个链表,before Node after,左右都可以插入
如果key不存在,创建新的链表
如果存在,新增内容
如果移除了所有值,空链表,不存在
在两边改动效率最高,中间元素效率会低一点
set中的值是不能重复的
sadd key value # 向set集合中添加smembers key # 查看key里面的所有值scard key # 获取set的元素个数sismember key value # 判断value值是否在key中srem key value # 移除value元素set # 无序不重复集合,抽随机srandmember myset count # 随机抽出指定个数元素spop key count # 随机删除一些set集合中的元素smove source destination # 将一个集合中的元素移动到另一个集合中数字集合类: - 差集 sdiff key1 key2 - 交集 sinter key1 key2 # 共同好友就可以这样实现 - 并集 sunion key1 key2
共同关注,共同爱好,二度好友
Map集合,key-,key-map,这时候这个值是一个map集合,本质和string没有太大区别
hset key filed value [field value] # 存放hash As of Redis 4.0.0, HSET is variadic and allows for multiple field/value pairs.# As per Redis 4.0.0, HMSET is considered deprecated. Please use HSET in new code.hget key field # 获取key中filed键的值hgetall key # 获取所有的键值对hdel key filed # 删除hash指定的字段,对应的value也就没有了hlen key # 获取hash的内容长度hexists key field # 判断hash中的某个filed是否存在hkeys key # 获取hash的所有键(key)hvals key # 获取hash的所有值hincrby key filed count # 自增counthdecrby key field count # 自减
在set的基础上,增加了一个值, zset k1 score1 v1
zadd key score value # 添加值,score代表优先级,可以一次添加多个zrange key start end # 获取start-end的值,0 -1代表获取所有值# 排序如何实现zrangebyscore key startscore endscore # 对集合通过score排序, 默认升序zrangebyscore key -inf inf withscores # 显示scorezrevrange salary 0 -1 [withscores] # 降序排列所有值zrem key member [member] # 移除元素zcard key # 获取有序集合中的个数zcount key start end # 获取start-end之间的个数
案例思路:set 排序 存储班级成绩表,工资表排序
普通消息:1.重要消息 2.带权重进行判断
排行榜应用实现,取top n测试
朋友的定位,附近的人,打车距离计算。
两地之间的距离,方圆几里的人
可以查询一些测试数据
geoadd key 纬度 经度 名称 # 添加地理位置信息127.0.0.1:6379> geoadd China:city 116.23128 40.22077 beijing(integer) 1127.0.0.1:6379> geoadd China:city 121.48941 31.40527 shanghai(integer) 1127.0.0.1:6379> geoadd China:city 106.54041 29.40268 chongqing
Redis Hyperloglog 基数统计的算法
网页的 UA(一个访问一个网站多次,但是还是算做一个人)
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断,存在误差
这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是计数,而不是保存用户id
优点:占用的内存是固定的,2^64不同元素,只需要废12kb的内存,如果要从内存角度来比较的话,Hyperloglog是首选
0.81%的错误率!统计UA的任务,是可以忽略不计的
pfadd key member [member] # 添加元素pfcount key # 查看个数pfmerge destkey sourcekey [sourcekey] # 合并多个sourcekey# 注意:不会有重复数据
统计疫情感染人数:0 1 0 1,统计用户信息,活跃,不活跃,打开,365打卡,两个状态的,都可以使用bitmaps来存储
bitmap 位图,也是一种数据结构!都是操作二进制位来进行记录,都只有 0 1两种状态
365天 = 365 bit, 1字节 = 8bit, 46个字节左右!
setbit key offset value # 设置getbit key offset # 得到结果# 统计操作,打卡的天数bitcount key [start end] # 统计打卡天数
MYSQL:ACID!
要么同时成功,要么同时失败:原子性,redis没有原子性
Redis单条命令保证原子性,但是事务不保证原子性
Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
一次性,顺序性,排他性!执行一系列的命令
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有被直接被执行,只有发起执行命令的时候才会被执行!Exec
redis的事务
开启事务(multi)
命令入队(……)
执行事务(exec)
锁:redis可以实现乐观锁,watch
127.0.0.1:6379> multiOK127.0.0.1:6379> set k1 v2 QUEUED127.0.0.1:6379> set k2 v2QUEUED127.0.0.1:6379> set k3 v3QUEUED127.0.0.1:6379> getset k3 # 错误的命令(error) ERR wrong number of arguments for 'getset' command127.0.0.1:6379> set k4 v4QUEUED127.0.0.1:6379> set k5 v5QUEUED127.0.0.1:6379> exec # 执行事务报错(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379> get k5 # 所有的命令都不会被执行(nil)
127.0.0.1:6379> multiOK127.0.0.1:6379> set k1 "v1"QUEUED127.0.0.1:6379> incr k1QUEUED127.0.0.1:6379> set k2 v2QUEUED127.0.0.1:6379> set k3 v3QUEUED127.0.0.1:6379> get k3QUEUED127.0.0.1:6379> exec1) OK2) (error) ERR value is not an integer or out of range # 虽然这条命令报错,但是依旧执行成功3) OK4) OK5) "v3"
监控!Watch :
悲观锁:
很悲观,认为什么时候都会出问题,无论做什么都会加锁
乐观锁:
很乐观,认为什么时候都不会出现问题,所以不会加锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
获取version
更新的时候比较version
watch key # 监视值unwatch # 放弃监视exec # 执行事务multi # 开启事务127.0.0.1:6379> set money 100OK127.0.0.1:6379> set out 0OK127.0.0.1:6379> watch money # 监视money对象OK127.0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功OK127.0.0.1:6379> decrby money 20QUEUED127.0.0.1:6379> incrby out 20QUEUED127.0.0.1:6379> exec1) (integer) 802) (integer) 20
执行失败,使用watch可以当redis的乐观锁操作
# 主线程127.0.0.1:6379> watch moneyOK127.0.0.1:6379> multiOK127.0.0.1:6379> decrby money 10QUEUED127.0.0.1:6379> incrby out 10QUEUED127.0.0.1:6379> exec # 执行之前,另外一个线程修改了值,就会导致执行失败(nil)# 线程2127.0.0.1:6379> get money"80"127.0.0.1:6379> set money 1000OK
启动的时候就是通过配置文件启动的
bind 127.0.0.1 # 绑定的ipprotected-mode yes # 保护模式port 6379 # 端口设置daemonize yes # 以守护进程的方式运行,默认是no,需要自己开启为yespidfile /var/run/redis_6379.pid # 如果以后台方式运行,我们需要指定一个pid文件# 日志loglevel noticelogfile "" # 日志的文件位置名databases 16 #数据库的数量,默认为16个always-show-logo yes # 是否显示logo
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aofredis是内存数据库,如果没有持久化,那么数据断电即失去
# 如果900秒内至少有一个key进行了修改,我们就进行持久化操作save 900 1# 如果300秒内至少有10key进行了修改,我们就进行持久化操作save 300 10# 如果60秒内至少有10000key进行了修改,我们就进行持久化操作save 60 10000# 我们之后学习持久化,会自己定义这个配置stop-writes-on-bgsave-error yes # 持久化出错后是否继续工作rdbcompression yes # 是否压缩rdb文件,需要消耗一些cpu的资源rdbchecksum yes # 报错rdb文件时候进行错误的检查校验dir ./ # rdb文件保存的目录,默认为当前目录
可以在这里设置redis的密码,默认是没有密码的
127.0.0.1:6379> config get requirepass # 获取redis的密码1) "requirepass"2) ""127.0.0.1:6379> config set requirepass "123456" # 设置redis密码OKauth 123456 # 输入密码后才可以登录
maxclients 10000 # 设置能连接上的最大客户端的数量maxmemory # redis最大的内存容量maxmemory-policy noeviction # 内存达到上限的处理策略 # 移除一些过期的key # 报错 # 。。。noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。(默认值)allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。allkeys-random: 所有key通用; 随机删除一部分 key。volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。redis中并不会准确的删除所有键中最近最少使用的键,而是随机抽取maxmeory-samples个键,删除这三个键中最近最少使用的键。
appendonly no # 默认不开启aof模式,默认使用rdb方式持久化,几乎在所有情况下rdb够用appendfilename "appendonly.aof" # 持久化文件的名字# appendfsync always# 每次修改都会同步,消耗性能appendfsync everysec # 每秒都同步一次 sync,可能会丢失这1s数据# appendfsync no # 不执行同步,这时候操作系统自己同步数据,速度最快
在主从复制中,rdb就是备用的!在从机上面
面试和工作,持久化必须是重点
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以redis提供了持久化功能
在指定时间间隔内将内存中的数据集写入磁盘,也就是Snapshot快照,它恢复时是将文件直接读到内存里
Redis会单独创建(fork)一个子进程来进行持久化,会先将书局写入到一个临时文件中,待持久化过程都结束了。再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF更加高效,RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置。
RDB保存的文件是dump.rdb都是在我们配置文件中的快照中进行配置的
自己测试一次,60s内修改5次,就会触发rdb操作
flushall默认产生一个dump.rdb文件
save规则满足的情况下,会自动触发rdb规则
执行flushall命令也会触发rdb规则
退出redis,也会产生rdb
备份就自动生成dump.rdb
只需要将rdb文件放在redis的启动目录下就可以了,redis启动的时候会自动检查dump.rdb文件恢复
查看需要存在的位置
127.0.0.1:6379> config get dir1) "dir"2) "/usr/local/redis-6.0.6/bin" #如果在这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据
几乎他自己的默认配置就够用了,但是我们还是学习
有时候在生产环境,我们会将这个文件进行备份
优点:
适合大规模的数据恢复!dump.rdb
如果对数据完整性不高!
缺点:
需要一定的时间间隔进行操作,如果redis意外宕机了,那么最后一次修改的数据就没有了
fork进程的时候会占用一定的内存空间
将我们的所有命令都记录下来,history,恢复的时候把这个文件全部再执行一遍
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件,但是不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从签到后执行一次以完成数据的恢复工作
AOF保存的是appendonly.aof文件
默认是不开启的,我们需要手动进行配置,我们只需要将appendonly改为yes即可
重启redis就可以生效了
如果这个aof文件有错位,这时候redis是启动不起来的,我们需要修复这个aof文件
redis给我们提供了redis-check-aof --fix appendonly.aof
来进行appendonly.aof的修复
如果文件正常,重启就可以直接恢复了
优点:
每一次修改都同步,文件完整性更加好
每同步一次,可能会丢失一秒的数据
从不同步
缺点:
相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢
AOF运行效率也要比rdb慢,redis默认的配置就是rdb持久化
如果aof文件大于64MB,太大了,!fork一个新的进程来将我们文件进行重写
aof默认就是文件的无限追加,文件会越来越大
Redis 提供了不同级别的持久化方式:
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.
如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.
使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。导出(export) AOF 文件也非常简单:举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
消息队列
通信,队列 发送者 订阅者
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信,微博
Redis客户端可以订阅任意数量的频道
订阅/发布消息图:
第一个:消息发送者,第二个:频道 第三个:消息订阅者!
psubcribe pattern [pattern]
订阅一个或多个符合给定模式的频道
pubsub subcommand [argument [argument]]
查看订阅与发布系统状态
publish channel message
将信息发送到指定的频道
punsubscribe [pattern [pattern]]
退订所有给定模式的频道
subscribe channel [channel
订阅一个或多个频道的信息
unsubscribe [channel [channel]]
退订给定的频道
127.0.0.1:6379> SUBSCRIBE yangjianChannel # 订阅一个频道Reading messages... (press Ctrl-C to quit)1) "subscribe"2) "yangjianChannel"3) (integer) 1# 等待读取推送的信息1) "message" #消息2) "yangjianChannel" # 哪个频道的消息3) "hello,yangjianChannel" # 具体内容1) "message"2) "yangjianChannel"3) "hello,Redis"
127.0.0.1:6379> PUBLISH yangjianChannel "hello,yangjianChannel" # 发布者发布消息到指定的频道(integer) 1127.0.0.1:6379> PUBLISH yangjianChannel "hello,Redis"(integer) 1
Redis是使用C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,籍此加深对Redis 的理解。Redis通过PUBLISH 、SUBSCRIBE和PSUBSCRIBE等命令实现发布和订阅功能。微信:通过SUBSCRIBE命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个频道!,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE命令的关键,就是将客户端添加到给定channel的订阅链表中。
通过PUBLSH命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub从字面上理解就是发布(Publish)与订阅( Subscribe ),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
实时消息系统
实时聊天(频道当做聊天室)
订阅关注系统
稍微复杂的场景,消息中间件MQ()
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(masterleader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。
主从复制的作用主要包括:1、数据冗余∶主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。2、故障恢复∶当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。3、负载均衡︰在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载﹔尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。4、高可用(集群)基石︰除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机,最少3个),原因如下:1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。对于这种场景,我们可以使如下这种架构︰
只配置从库,不用配置主库
info replication # 查看当前库的信息127.0.0.1:6379> info replication# Replicationrole:master #角色:masterconnected_slaves:0 # 没有从机master_replid:85e0dc3072e2b2d876b7ff4a0cb53f961d7fd5e6master_replid2:0000000000000000000000000000000000000000master_repl_offset:0second_repl_offset:-1repl_backlog_active:0repl_backlog_size:1048576repl_backlog_first_byte_offset:0repl_backlog_histlen:0
复制三个配置文件,修改对应的信息
端口号
pid
日志名字
备份文件名字 dump.rdb
修改完毕之后启动三个redis服务
默认情况下,每台Redis服务器都是主节点
slaveof host port # 设置这个主机的主人127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # 认定这个主机下的6379端口为主OK127.0.0.1:6380> info replication# Replicationrole:slavemaster_host:127.0.0.1 # 可以看到主机的信息master_port:6379master_link_status:upmaster_last_io_seconds_ago:8master_sync_in_progress:0slave_repl_offset:14slave_priority:100slave_read_only:1connected_slaves:0master_replid:acec4d2172706ac4d75dc58669cda8a2d7a8bfbdmaster_replid2:0000000000000000000000000000000000000000master_repl_offset:14second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:1repl_backlog_histlen:14# 在主机中查看[root@iZbp17e1mp4a0cgbw1358xZ bin]# redis-cli -p 6379127.0.0.1:6379> info replication# Replicationrole:masterconnected_slaves:1slave0:ip=127.0.0.1,port=6380,state=online,offset=126,lag=1master_replid:acec4d2172706ac4d75dc58669cda8a2d7a8bfbdmaster_replid2:0000000000000000000000000000000000000000master_repl_offset:126second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:1repl_backlog_histlen:126
真实的主从配置应该在配置文件中配置,这样的话是永久的,使用命令是暂时的
主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存
测试:主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,如果主机回来,从机依旧可以直接获取到主机写的信息
如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机中获取值
Slave启动成功连接到master后会发送一个sync命令Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制︰而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步。
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。我们的数据一定可以在从机中看到
谋朝篡位
salveof no one
,如果主机断开了连接,可以使用这个命令来让自己成为主节点,其他的结点就可以手动连接到最新的这个主节点
如果这个时候老大修复了,那就只能重新连接
主从切换技术的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务器不可用。这不是一种推荐方式,更多时候,我们优先考虑哨兵模式,Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例
哨兵有两个作用
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
我们目前的状态是一主二从
配置哨兵配置文件sentinel.conf
# sentinel monitor 被监控的名称 host port 1sentinel monitor myredis 127.0.0.1 6379 1# 1是有多少哨兵认为挂了,master才是真的挂了
启动哨兵
redis-sentinel ../etc/sentinal.conf[root@iZbp17e1mp4a0cgbw1358xZ bin]# redis-sentinel ../etc/sentinal.conf 20615:X 05 Nov 2020 13:05:15.181 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo20615:X 05 Nov 2020 13:05:15.181 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=20615, just started20615:X 05 Nov 2020 13:05:15.181 # Configuration loaded _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 6.0.6 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 20615 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 20615:X 05 Nov 2020 13:05:15.182 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.20615:X 05 Nov 2020 13:05:15.185 # Sentinel ID is ee900e749cf585634b3bc2860471d28dcd1ec10920615:X 05 Nov 2020 13:05:15.185 # +monitor master myredis 127.0.0.1 6379 quorum 120615:X 05 Nov 2020 13:05:15.186 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 637920615:X 05 Nov 2020 13:05:15.188 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
如果master结点断开了,这个时候就会从从机中随机选择一个服务器,这里面有一个投票算法
哨兵日志
如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则!
优点:
哨兵模式,基于主从复制模式,所有主从配置的优点它都有
主从可以切换,故障可以转移,系统的可用性就会更好
哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
实现哨兵模式的配置是非常麻烦的,里面有很多选择
# Example sentinel.conf # 哨兵sentinel实例运行的端口 默认26379port 26379 # 哨兵sentinel的工作目录dir /tmp # 哨兵sentinel监控的redis主节点的 ip port # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了# sentinel monitor sentinel monitor mymaster 127.0.0.1 6379 1 # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码# sentinel auth-pass sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒# sentinel down-after-milliseconds sentinel down-after-milliseconds mymaster 30000 # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。# sentinel parallel-syncs sentinel parallel-syncs mymaster 1 # 故障转移的超时时间 failover-timeout 可以用在以下这些方面:#1. 同一个sentinel对同一个master两次failover之间的间隔时间。#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。#3.当想要取消一个正在进行的failover所需要的时间。 #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了# 默认三分钟# sentinel failover-timeout sentinel failover-timeout mymaster 180000 # SCRIPTS EXECUTION #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。#对于脚本的运行结果有以下规则:#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。 #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,#一个是事件的类型,#一个是事件的描述。#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。#通知脚本# sentinel notification-script sentinel notification-script mymaster /var/redis/notify.sh# 客户端重新配置主节点参数脚本# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。# 以下参数将会在调用脚本时传给脚本:# # 目前总是“failover”,# 是“leader”或者“observer”中的一个。# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的# 这个脚本应该是通用的,能被多次调用,不是针对性的。# sentinel client-reconfig-script sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
服务的高可用问题
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力﹔
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
微博服务器宕机(60 60.1 0.1)
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁考验很大
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis宕机产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活)
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。