Redis笔记
一、NoSQL概述
早些年的MyISAM:表锁,十分影响效率,高并发下会出现严重的锁问题
转战InnoDB,行锁
Not Only SQL(不仅是sql),泛指非关系型数据库,随着web2.0,传统的关系型数据库很难对付web2.0时代,尤其是超大规模的高并发社区。比如个人信息,社交网络,地理位置,这类数据的存储不需要一个固定的格式,不需要多月的操作就可以横向扩展
NoSQL特点:
-
解耦
-
方便扩展(数据之间没有关系,很好扩展)
-
大数据量高性能(Redis一秒写8万次,读取11万,NoSQL的缓存记录级),是一种细粒度的缓存,性能会比较高
-
数据类型多样性(不需要事先设计数据库,数据量十分大的表,很多人就无法设计了)
-
传递RDBMS和NoSQL
传统的RDBMS:
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row column
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
Nosql:
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能
大数据时代,对程序的要求
- 高并发
- 高扩展
- 高性能
NoSQL的四大分类:
- KV键值对Redis
- 文档型数据库(bson格式和json一样)
- MongoDB
- 是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据中间的产品,是非关系型数据库中功能最丰富,最像关系型数据库
- ConthDB
- MongoDB
- 列存储数据库
- HBase
- 分布式文件系统
- 图关系数据库
二、Redis概述
(Remote Dictionary Server)远程字典服务
开源使用ANSI C语言编写,支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言API
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现master-slave(主从)同步
Redis用处:
- 内存存储、持久化、内存中是断电即失,所以说持久化很重要(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
- ...
特性:
- 多样的数据类型
- 持久化
- 集群
- 事务
- ...
windows安装:
- 去github上下载下来安装包
- 解压到本地环境
- 双击redis-server.exe即可启动
- 使用redis客户端来连接redis,打开redis-cli.exe,ping来测试是否连接成功
windows下使用很简单,但是推荐使用Linux去使用
Linux安装:
-
下载安装包
-
通过ftp传到linux服务器上可以放到/opt目录下 mv 文件名 目录
-
解压Redis安装包 tar -zxvf redis-7.0.8.tar.gz
-
进入解压后的文件,可以看到redis的配置文件
-
基本的环境安装
yum install gcc-c++ -
安装完成之后,使用make命令把所有需要的文件给配上 备注:(在redis的文件目录中)安装完可有再make一下确认,然后make install安装一下
-
redis的默认安装路径 /user/local/bin
-
将redis配置文件,复制到我们当前目录下
#切换到bin目录下,创建一个文件夹 mkdir Redisconfig cp /opt/redis-7.0.8/redis.conf redisconfig #随后就用这个副本配置文件启动 -
redis默认不是后台启动的,修改配置文件
vim redis.conf #把daemonize 改为yes -
启动Redis服务
#切换到bin目录 redis-server redisconfig/redis.conf #如果没反应,可以直接一下命令 redis-server #使用redis客户端连接 redis-cli -p 6379 #操作 set name hello get name #查看所有的键 keys * -
查看redis的进程是否开启
ps -ef|grep redis -
如何关闭Redis服务
shutdown exit -
然后再次查看进程是否存在
Redis-benchmark性能测试:
#测试:100个并发连接 100000请求 redis-benchmark -h localhost -p 6379 -c 100 -n 100000
redis-server redis-cli -p 6379 #新建一个链接 cd /user/local/bin
参数 | 描述 | 默认值 |
---|---|---|
-h | 指定服务器主机名 | 127.0.0.1 |
-p | 指定服务器端口 | 6379 |
-s | 指定服务器 socket | |
-c | 指定并发连接数 | 50 |
-n | 指定请求数 | 10000 |
-d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
-k | 1=keep alive 0=reconnect | 1 |
-r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
-P | 通过管道传输 请求 | 1 |
-q | 强制退出 redis。仅显示 query/sec 值 | |
–csv | 以 CSV 格式输出 | |
-l | 生成循环,永久执行测试 | |
-t | 仅运行以逗号分隔的测试命令列表。 | |
-I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
三、Redis基础知识
redis默认有16个数据库
默认使用的是第0个
Redis是单线程的,很快,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了
Redis是全部数据放在内存中,使用单线程去操作效率最高(CPU上下文会切换:耗时的操作)
五大数据类型:
Redis可以用作数据库、缓存和消息中间件MQ
Redis-Key:
select 3 #切换数据库 DBSIZE #查看大小 keys * #查看所有的key flushdb #清除当前数据库 flushall #清除全部数据库的内容 EXISTS 名称 #查看是否存在这个字段 EXPIRE 名称 时间 #设置过期时间 ttl 名称 #查看剩余时间 move 名称 #移除字段 type 名称 #查看key类型
String字符串:
set key value get key APPEND KEY VALUE #追加,返回的是长度,如果key不存在,则相当于set key STRLEN KEY # 获取字符串的长度 incr key # 自增1 decr key # 自减1 INCRBY key 步长 # 设置步长增减 GETRANGE KEY 0 3 # 截取字符串[0,3] GETRANGE KEY 0 -1 # 获取全部的字符串 SETRANGE KEY 1 xx # 替换指定位置开始的字符串 setex key 时间 字符串 # 设置过期时间,如果已经存在了,则不会设置 ttl key #查看剩余时间 mset k1 v1 k2 v2 k3 v3 #批量设置 mget k1 k2 k3 #批量获取 msetnx k1 v1 k4 v4 #原子性操作,要么一起成功要么一起失败 # 对象操作 set user:1 {name:hello,age:3} # 设置一个user:1 对象 值为json字符来保存一个对象 # 这里的key是一个巧妙的设计 user:{id}:{filed},如此设计在Redis里可以 set article:1000:views 0 # 初始浏览量为0 getset db redis # 如果不存在值,则返回nil getset db mongodb # 如果存在则先获取原来的值再设置
String类型的使用场景:value除了是字符串还可以是数字
- 计数器
- 统计多单位的数量 uid
List列表:
基本的数据类型,列表
在redis里面,我们可以把list玩成,栈、队列、阻塞队列
所有的list命令都是l开头的
LPUSH list one LPUSH list two LPUSH list three # 添加 LRANGE list 0 -1 # 获取全部的值 LRANGE list 0 1 # 获得某个区间的值 RPUSH list righr # 向右边添加LPUSH是往左加 LPOP list RPOP list # 从左边和右边移出 lindex list 1 lindex list 0 #通过下标获取list中的某一个值 # 移除指定的值 比如取关uid lrem list 1 one # 移除精确的一个值 lrem list 2 three # 移除两个three,因为list里面可能有重复的值 # 修剪trim ltrim mylist 1 2 # 通过下标截取指定的长度,这个list已经被改变了,截断了只剩下截取的元素 rpoplpush oldlist newlist # 移除列表的最后一个元素,将他移动到新的列表中 lset list 0 item # 将列表中指定下标的值替换成另外一个值,如果不存在则会报错 LINSERT list before "world" "other" # 在某个值前面插入值 LINSERT list after world new # 在某个值后面插入值
小结:
- list实际上是一个链表 before Node after。left right 都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!中间元素,想对来说效率会低
消息排队:消息队列(Lpush Rpop),栈(Lpush Lpop)
Set集合:
set中的值是不能重读的
sadd myset "hello" sadd myset "world" # 向set添加 SMEMBERS myset # 显示set所有内容 SISMEMBER myset hello # 判断hello是否在set里面 scard myset #获取set集合中的内容元素个数 srem myset hello # 移除set集合中的指定元素 # set 无需不重复集合,抽随机 SRANDMEMBER myset # 随机抽选出一个元素 SRANDMEMBER myset 2 # 随机抽选出指定个数的元素 spop myset # 随机删除一些set集合中的元素 # 将一个指定的值移动到另外一个 smove oldset newset "hello" # 两个集合的差集、交集、并集 SDIFF set1 set2 # 差集 SINTER set1 set2 # 交集 比如微博的共同关注功能 SUNION set1 set2 # 并集
Hash哈希:
Map集合,key-map,这个时候值是一个map集合。本质和string类型没有太大区别,还是一个简单的key-value
hset myhash field1 hello # set一个具体的key-value hget myhash field1 # 获取一个字段值 hmset myhash field1 hello field2 world # set多个key-value hmget myhash field1 field2 #获取多个字段值 hdel myhash field1 # 删除hash指定的字段,对应的value值也就没有了 hgetall myhash #获取所有的 hlen myhash #获取hash的字段数量 HEXISTS myhash field1 #判断某个字段是否存在 hkeys myhash #获取所有的字段 hvals myhash #获取所有的值 # value增加或者减少 hset myhash filed3 5 HINCRBY myhash field3 1 # 自增,可以指定增量 HINCRBY myhash field3 -1 # 自减,可以指定增量 hsetnx myhash field4 hello #不存在则可以设置,如果存在则不能设置
hash的应用,可以存一些变更的数据,比如user里面有name,age。尤其是一些用户信息的保存,经常变动的信息,hash更适合于对象的存储,String更适合字符串的存储
hset user:1 name hello hget user:1 name
Zset有序集合:
在set的基础上,增加了一个值 set k1 v1 --> zset k1 score1 v1
zadd myset 1 one # 添加一个值 zadd myset 2 two 3 three # 添加多个值 ZRANGE myset 0 -1 # 展示所有值 # 排序如何实现 zadd salary 2500 xiaohong #添加三个用户 zadd salary 5000 zhangsan zadd salary 500 xiaowang ZRANGEBYSCORE salary -inf +inf # 显示全部的用户,从小到大 ZRANGEBYSCORE salary -inf +inf withscores #显示全部的用户并且附带成绩 ZRANGEBYSCORE salary -inf 2500 withscores #限定查询范围 ZREVRANGE salary 0 -1 # 从大到小进行排序 # 移除rem中的元素 zrange salary 0 -1 # 显示所有元素 zrem salary xiaohong # 移除有序集合中的指定元素 zrange salary 0 -1 zcard salary # 获取有序集合中的个数 zcount myset 1 3 # 获取指定区间的成员数量
其余的API可以查看官方文档
案例思路:set排序 存储班级成绩表、工资表排序
普通消息 1、重要消息 2、带权重进行判断
比如排行榜应用
三种特殊数据类型:
geospatial地理位置:
这个功能可以推算地里位置的信息,两地之间的距离
# getadd 添加地理位置 # 规则:两级无法直接添加,我们一般会下载城市数据,通过程序一次性导入 # 参数 key 值(纬度、经度、名称) geoadd china:city 经度 纬度 beijing geoadd china:city 经度 纬度 beijing 经度 纬度 shanghai GEOPOS china:city beijing # 获取制定城市的经度和纬度,获得当前定位一定是一个坐标值 # geodist可以查看两地之间的距离单位 m米、km千米、mi英里、ft英尺 GEODIST china:city beijing shanghai km # 查看北京到上海的直线距离 # georadius 以给定的经纬度为中心,找出某一半径内的元素 # 比如寻找我附近的人,通过半径来查询 GEORADIUS china:city 经度 维度 半径距离 单位 # 后面添加withdist可以带上距离,withcoord带上坐标 # 比如 GEORADIUS china:city 110 30 1000 km withdist # 以110 30这个经纬度为中心,寻找房源1000km内的城市 GEORADIUS china:city 110 30 1000 km withdist withcoord count 2 # 筛选出指定的结果 # 找出位于指定元素周围的其他元素 GEORADIUSBYMEMBER china:city beijing 1000 km # 返回一个或多个位置元素的Geohash表示 # 该命令将返回11个字符的Geohash字符串,如果两个字符串越接近,那么距离越近 geohash china:city beijing chongqing
geo底层的实现原理其实就是Zset!我们可以使用Zset命令来操作geo
ZRANGE china:city 0 -1 # 查看地图中的全部的元素 zrem china:city beijing # 移除指定元素
Hyperloglog基数统计:
什么是基数?
A{1,3,5,7,8,7} B{1,3,5,7,8} 基数为5
基数(不重复的元素)
Redis Hyperloglog 基数统计的算法
网页的UV(一个人访问一个网站多次,但是还算作一个人)
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断
这个方式如果保存大量的用户id就会比较麻烦,我们的目的是计数,而不是保存用户id
优点:占用的内存是固定的,2^64 不同的元素,只需要12KB内存,如果要从内存的角度来比较的话Hyperloglog首选
但是有0.81%的错误率,统计UV任务可以忽略不计
PFadd mykey a b c d e f g h i j # 创建第一组元素mykey PFCOUNT mykey # 统计mykey元素的基数数量 PFadd mykey2 i j z x c v b n m #创建第二组元素mykey2 PFCount mykey2 PFMERGE mykey3 mykey mykey2 # 合并两组mykey mykey2 => mykey3 并集 PFCount mykey3 # 看并集的数量
如果允许容错,那么一定可以使用Hyperloglog
如果不允许容错,就使用set或自己的数据类型即可
Bitmaps:
位存储 0 1 表示两种状态,只有两个状态的都可以使用Bitmaps
比如统计用户信息,活跃,不活跃
使用bitmap来记录周一到周日的打卡
setbit sign 0 1 setbit sign 1 0 setbit sign 2 0 setbit sign 3 1 setbit sign 4 1 setbit sign 5 0 setbit sign 6 0 # 查看某天打卡情况 getbit sign 3 gitbit sign 6 # 统计打卡的天数 bitcount sign #统计这周的打卡记录,就可以确定是否全勤
事务:
Redis单条命令是保存原子性的,但是事务不保证原子性
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
-------队列 set set set 执行 ------
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行
一次性、顺序性、排他性!执行一系列的命令!
redis的事务:
- 开启事务(muti)
- 命令入队(....)
- 执行事务(exec)
正常执行事务
# 命令入队 multi set k1 v1 set k2 v2 get k2 set k3 v3 # DISCARD 取消事务,上面的命令就不会执行 exec # 执行事务
编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行
multi set k1 v1 set k2 v2 getset k3 # 报错 set k4 v4 exec # 会报错 get k4 # 会报nil
运行时异常(1/0),如果事务
set k1 "v1" multi incr k1 # 对字符串加1报错 set k2 v2 set k3 v3 get k3 exec # 报错 get k2 # v2 get k3 # v3
悲观锁:
很悲观,认为什么时候都会出问题,无论做什么都会加锁
乐观锁:
- 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
- 获取version
- 更新的时候比较version
Redis监视测试
set money 100 set out 0 watch money # 监视money 对象 multi # DECRBY money 20 INCRBY out 20 exec
测试多线程修改值,监视失败,使用watch可以当做redis的乐观锁操作
watch money # 监视 money multi DECRBY money 10 INCRBY out 10 exec # 执行之前,另一个线程,修改了我们的值,这个时候会导致事务执行失败
unwatch # 如果发现事务执行失败,就先解锁 watch money # 获取最新的值,再次监视,select version multi DECRBY money 1 INCRBY money 1 exec # 对比监视的值是否发生了变化,如果没有变化,那么可以执行成果,如果变了就执行失败
Redis配置文件详解:
-
配置文件unit单位 对大小写不敏感,还有引用文件
-
网络
bind 127.0.0.1 # 绑定的ip protected-mode yes #保护模式 port 6379 # 端口设置 -
通用general
daemonize yes # 以守护进程的方式运行,默认为no,我们需要自己开启为yes pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个pid文件 # 日志相关 # Specify the server verbosity level. # This can be one of: # debug (a lot of information, useful for development/testing) # verbose (many rarely useful info, but not a mess like the debug level) # notice (moderately verbose, what you want in production probably) 生产环境 # warning (only very important / critical messages are logged) loglevel notice logfile "" # 日志的文件位置名 databases 16 # 默认数据库的数量,默认是16个 always-show-logo yes # 是否总是显示logo -
快照
持久化,在规定时间内,执行了多少次操作,则会持久化到文件 .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文件保存的目录 -
设置密码
默认是没有密码的
ping config get requirepass # 获取redis的密码 config set requirepass "123456" # 设置redis的密码 config get requirepass # 发现所有的命令都没有权限了 auth 123456 # 使用密码进行登录 config get requirepass -
限制客户端
maxclients 10000 # 设置能连接上redis的最大客户端数量 maxmemory <bytes> # redis配置最大的内存容量 maxmemory-police noeviction # 内存达到上限之后的处理策略 # 移除一些过期的key # 报错 # volatile-lru -> 只对设置了过期时间的key进行LRU(默认值) # allkeys-lru -> 删除lru算法的key # volatile-random -> 随机删除即将过期的key # allkeys-random -> 随机删除 # volatile-ttl -> 删除即将过期的 # noeviction -> 用不过期,返回错误 -
append only模式 aof配置
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb情况够用 appendfilename "appendonly.aof" # 持久化的文件的名字 appendfsync always # 每次修改都会 sync 消耗性能 appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据 appendfsync no # 不执行sync,这个时候操作系统会自己同步数据,速度最快
持久化之RDB操作:
面试和工作持久化都是重点
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会小时,所以Redis提供了持久化功能
在指定的时间间隔将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果进行大规模数据的恢复,且对于数据恢复的完整性不是很敏感,哪RDB方式要比AOF方式更加高效。RDB的缺点是最后一次持久化的数据可能丢失,我们默认的就是RDB,一般情况下不需要修改这个配置
有时候在生产环境我们会将这个文件进行备份
rdb保存的文件是dump.rdb,都是在我们的配置文件中快照中进行配置的
触发机制:
- save的规则满足的情况下,会自动触发rdb规则
- 执行flushall命令,也会触发我们的rdb规则
- 退出redis,也会产生rdb文件
备份就会自动生成一个dump.rdb文件
如何恢复rdb文件:
-
只需要将rdb文件放在我们的redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据
-
查看需要存在的位置
config get dir "dir" "/user/local/bin" # 如果这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据
优点:
- 适合大规模的数据恢复dump.rdb
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进程操作,如果redis意外宕机了,这个最后一次修改的数据就没有了
- fork进程的时候,会占用一定的内容空间
持久化操作之AOF:
Append Only File
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部执行一遍
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换而言之,redis重启的话就会根据日志文件的内容将写指令从前到后执行一次以完成数据恢复工作
Aof保存的是appendonly.aof文件
默认是不开启的,我们需要手动进行配置,我们只需要将appendonly改为yes就开启了aof
重启redis即可生效
如果这个aof文件有错位,这时候redis是启动不起来的吗,我们需要修复这个aof文件
redis给我们提供了一个工具 redis-check-aof --fix
一种是全丢,一种是之丢弃错误的数据
appendonly no # 默认是不开启aof模式的,默认是rdb方式持久化,在大部分所有的情况下,rdb完全够用 appendfilename "appendonly.aof" #持久化的文件的名字 appendfsync always # 每次修改都会 sync 消耗性能 appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据 appendfsync no # 不执行sync,这个时候操作系统会自己同步数据,速度最快 # 如果aof文件大于64m,太大了,fork一个新的进程来将我们的文件进行重写,aof默认是无限追加,文件会越来越大 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
优点:
- 每一次修改都同步,文件的完整会更好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点:
- 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
- aof运行效率也比rdb慢,所以我们redis默认的配资就是rdb持久化
扩展:
- RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
- AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
- 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
- 同时开启两种持久化方式
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
- RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢 ?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份 ),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。
- 性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
- 如果Enable AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价0是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite 的频率,AOF重写的基础大小默认值64M大小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
- 如果不Enable AOF,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔10,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。
Redis订阅发布:
通信 队列 发送者 ==== 订阅者
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
Redis客户端可以订阅任意数量的频道
订阅/发布消息图:
第一个:消息发送者 第二个:频道 第三个:消息订阅者
订阅端:
SUBSCRIBE world # 订阅一个频道 world # 等待推送的信息
发送端:
PUBLISH world "hello,world" # 发布者发布消息到频道
原理:
Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍此加深对 Redis 的理解.
Redis 通过 PUBLISH 、SUBSCRBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者
Pub/Sub 从字面上理解就是发布 (Publish ) 与订阅( Subscribe ),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
使用场景:
- 实时消息系统
- 实时聊天(频道当做聊天室,将信息回显给所有人)
- 订阅,关注系统
稍微复杂的场景就会使用消息队列MQ
Redis集群环境搭建:
概念:
主从复制,是将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点,Master以写为主,Slave以读为主
默认情况下,每台Redis服务器都是主节点,且一个主节点可以有很多个从节点(或者没有从节点),但一个从节点只能有一个主节点
主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
- 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
- 从容量上,单个Redis服务器内存容量优先,就算一台Redis服务器内存容量为256B,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用内存不超过20G
电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是“多读少写”
主从复制,读写分离!80%的情况下都是在进行读操作,减缓服务器的压力,架构中经常使用,一主二从
只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis
环境配置:
只配置从库,不用配置主库
info replication # 查看当前库的信息 role:master # 角色 master connected_slaves:0 # 没有从机 master_failover_state:no-failover master_replid:373b217939c0842da254b761b1809788dcf2d4ba 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
cp redis.conf redis79.conf # 复制几个配置文件 cp redis.conf redis80.conf cp redis.conf redis81.conf # 然后改每个配置文件 port #修改成对应的 pidfile /var/run/redis_xxxx.pid #也改成对应的 logfile "xxxx log" # 日志也改成对应的 dbfilename dumpxxx.rbd # 改成对应的
复制3个配置文件,然后修改对应的信息
- 端口
- pid名字
- log文件名字
- dump.rdb
修改完毕之后,启动3个redis服务器
主从复制原理:
默认情况下,每台Redis服务器都是主节点;我们一般情况下只用配置从机就好了
一主(79)二从(80,81)
配置从机:
SLAVEOF 127.0.0.1 6379 # 配置谁当主机 info replication # 展开的信息里面可以看到当前身份和谁是主机
真实的主从配置应该在配置文件中配置,这样的话是永久的,我们这里使用的是命令,是暂时的
细节:
主机可以写,从机不能写只能读!主机中的所有信息和数据,会自动的被从机保存,从机只能读取内容
测试:
主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候主机如果回来了,从机依旧可以直接获取到主机写的信息
如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变回从机,立马就会从主机中获取值
复制原理:
Slave 启动成功连接到master后会发送以个sync同步命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到Slave,并完成一次完全同步
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行!我们的数据一定可以在从机中看到
宕机后配置主机:
如果主机断开了连接,我们可以使用SLAVEOF NO ONE
让自己变成主机,其他的节点就可以手动连接到最新的主节点(手动),如果这个时候原来的主修复了,那就重新连接
哨兵模式:
(自动选取主机)
概述:
-
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题
-
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
-
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵有两个作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个唱兵发起,进行failover[故障转移] 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
测试:
我们目前的状态是一主二从
-
配置哨兵配置文件sentinel.conf
# sentinel monitor 被监控的名称 host port 1 sentinel monitor myredis 127.0.0.1 6379 1 后面的数字1,代表主机挂了,slave投票
-
启动哨兵
redis-sentinel redisconfig/sentinel.conf # 启动 如何Master节点断开了,这个时候就会从从机中随机选择一个服务器(这里面有一个投票算法)
哨兵模式:
如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则
优点:
- 哨兵集群,基于主从复制模式,所有的主从配置有点,它全有
- 主从可以切换,故障可以转移,系统的可用性就会更好
- 哨兵模式就是主从模式的升级 ,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多选择
哨兵模式的配置:
# sentinel.conf #哨兵sentinel实例运行的端口 默认26379 port 26379 # 哨兵sentinel的工作目录 dir /tmp # 哨兵sentinel监控redis主节点 ip port # master-name 可以自己命名的主节点名字,只能由字母A-Z、数字0-9、这三个字符".-_"组成 # guorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了 # sentinel monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127.0.0.1 6379 2 # 在redis实例中开启了requirepass foobared 授权密码 这样所有连接redis实例的客户端都需要提供密码 # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的密码 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster MySUPER--secret-0123passwOrd # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒 sentinel down-after-milliseconds mymaster 30000 # 这个配置指定了发生failover主备切换时最多可以有多少个slave同时对新的master进行同步 # 这个数字越小,完成failover所需的时间就越长 # 但是这个数字越大,就意味着越多slave因为relicaiton而不可用 # 可以通过将这个值设为 1 来保证每次只有一个slave处于不能处理命令请求的状态 sentinel parallel-syncs mymaster 1 #....等等
Redis缓存穿透和雪崩:
缓存穿透(查不到):
概念:
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案:
-
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力 ;
-
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
缓存击穿(量太大,缓存过期):
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
解决方案:
-
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题
-
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩:
是指在某一个时间段,缓存集中过期失效,Redis宕机
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存诸层的调用量会暴增,造成存储层也会挂掉的情况。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
比如双十一的时候,会停掉一些服务
解决方案:
-
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。( 异地多活!)
-
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
本文作者:oaoa
本文链接:https://www.cnblogs.com/oaoa/p/17161695.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步