Error[8]: Undefined offset: 142, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

redis总结 Redis

redis视频链接

概述

Redis是什么

Redis是一个使用ANSI C编写的开源、支持网络、基于内存、分布式、可选持久性的键值对存储数据库。

Redis能做什么

  1. 内存存储,持久化(rdb,aof)
  2. 效率高,可以用于高速缓存
  3. 订阅系统
  4. 地图信息分析
  5. 计时器,浏览量
  6. .
安装
1. 下载压缩包
   	wget 软件链接
2. 解压gz压缩包
     tar -zxvf redis-6.2.6.tar.gz 
3. 安装gcc-c++环境
    yum install gcc-c++
4. 进入解压后的文件夹,make命令
    make	//第一次会下载很多环境
    make install //测试是否安装好了
5. redis默认安装路径/usr/local/bin
    复制原生的redis.config到bin下进行配置
6. redis默认不是后台启动,需要设置配置文件
    daemonize=yes
7. 启动redis服务,进入/usr/local/bin,运行redis-server
    redis-server 配置文件(redis.conf)
    //redis-server redisconfig/redis.conf
8. 测试连接
    redis-cli -p 6379//启动服务之后才行
9. shutdown//关闭服务
性能测试

压测命令:redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000

压测需要一段时间,因为它需要依次压测多个命令的结果,如:get、set、incr、lpush等等,所以我们需要耐心等待,如果只需要压测某个命令,如:get,那么可以在以上的命令后加一个参数-t
//比如
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000

基础知识
  1. redis 有16个数据库,默认使用第0个

  2. select 数据库序号 切换使用的数据库

  3. DBSIZE 查询数据库大小

  4. set get

  5. keys * 查看数据库所有的key

  6. flushdb 清空当前数据库 flushall 清空所有数据库

  7. Redis 是单线程的

    Redis 是基于内存 *** 作的,Redis的瓶颈是带宽和内存

    1. 为什么单线程还这么快?

      1. 高性能服务器不一定是多线程的
      2. 多线程会有上下文切换不一定比单线程效率高

      Redis将数据全部放到内存中,使用单线程效率非常高

五大数据类型 Redis-key
set key value 	//设置key value
get key			//获取key对应的value
keys * 			//显示所有键值对
exists key 		//是否存在key
move key db		//移动key到db数据库
expire key time //设置key过期时间(秒)
ttk key			//查询key的剩余时间
type key		//查看key类型
String
  1. 动态字符串,可以进行修改.
  2. 当字符串长度小于1MB时,拓容是加倍内存,大于MB时每次只会加1MB,字符串长度最大为512MB.
  3. 如果value是一个整数,可以对其进行自增,但其范围为signed long的最大最小值之间
append key v  	 //给key追加v
strlen key		 //获取key对应value长度
incr key 		 //是key对应value自增
decr key		 //使key对应value自减
incrby key count //使key对应value+=count
decrby key count //减少count
getrange key st et 		//截取key对应字符串value st到et 闭区间
setrange key start str 	//从start起替换key对应value(可以变长)
SETEX key time value	//设置key-value 过期时间为time
SETNX key value 		//不存在key时创建key-value
mset k1 v1 k2 v2 		//一次性设置多值
mget k1 k2 k3			//一次性获取多值
msetnx k1 v1 k2 v2      //不存在才能创建,原子性必须全部成功才行
getset key newvalue		//获取key值并设置newvalue
实例:
	set user:1 {name:raja,age:18}
List (列表)

注:命令均以l开头

  1. list是链表(不严谨),一些 *** 作需要遍历链表,效率低,如:lindex
  2. index 可以为负数,表示倒数第index个数
  3. redis底层快速链表.
    1. 在元素较少的情况下,redis会使用一片连续的内存存储(ziplist 压缩列表),
    2. 在元素较多的情况下,redis会改用quicklist:多个ziplist通过双向指针连接
      1. 满足了快速插入删除.
      2. 不会有太大的空间冗余->普通链表有指针空间,还会加重内存碎片化.
lpush list v1 v2    //从list左边插入v1 v2
lrange list st et 	//从list左边起遍历st到et
rpush list v1 v2    //从list右边插入v1 v2
lpop list [cnt]		//d出list左边1或[cnt]个元素
rpop list [cnt]		//d出list右边1或[cnt]个元素
lindex list index   //获取下标从左起为index的元素
llen list 			//查看list大小
lrem list cnt value //移除list从左起cnt个值为value的元素
ltrim list st et	//保留list从左起下标st到et的元素
rpoplpush list1 list2  	//移动
lset list index value	//将list中下标为index的值改为value(不存在就报错)
linsert list before value1 value2 //在value1前插入value2
linsert list after value1 value2  //在value1后插入value2
Set (集合)

注:命令均以s开头,集合元素不重复

  1. 可以用来保存中奖的人,以确保不会二次中奖
sadd myset v1 v2 		//向集合中插入v1 v2
smembers myset			//查看myset中的所有元素
sismember myset value	//判断value是不是myset的元素(1,0)
scard myset				//获取set的中的元素个数
srem myset v1 v2		//删除myset中的元素v1 v2
srandmember myset [cnt] //随机抽选出1或[cnt]个元素
spop myset [cnt]		//随机删除1或[cnt]个元素
smove myset1 myset2 value //将myset1中value移动至myset2
sdiff set1 set2			//返回set1-set2的差集
sinter set1 set2		//交集
sunion set1 set2		//并集
Hash (map集合)

key-map

  1. 类似java的hashmap,使用一维数组+二维链表的形式存储
  2. 拓容使用渐进式rehash,使用新旧两个hash结构.

注:以h开头

hset myhash k1 v1  			//设置myhash中k1 v1元素
hget myhash key				//获取..key元素的值
hmset myhash k1 v1 k2 v2 	//设置多个值 可以分段进行保存,获取数据比较高效
hmget myhash k1 k2			//获取多个值
hgetall myhash				//获取myhash中所有键值对
hdel myhash k1 k2			//删除myhash中指定的k1 k2
hlen myhash					//获取myhash元素个数
hincrby myhash key num		//增加num
hsetnx myhash key value		//不存在才创建

和String差不多

Zset (有序集合)
  1. 使用跳表进行存储 O(logn)
    1. 先在顶层定位,然后一层一层潜入下去,最后插入即可
  2. inf 表示无穷大
zadd myset score1 v1 score2 v2  //插入数据
zrange myset st et				//遍历
zrangebyscore myset (minscore (maxscore [withscores] //返回递增minscore<= x <=maxscore [带score]的所有x 不加括号表示没等号
zrangebyscore myset (maxscore (minscore [withscores]//降序
zrem myset v1			//移除myset中的v1元素
zcard myset				//获取myset的元素个数
zcount myset min max    //获取myset score min到max中共多少元素
拓展
  1. 容器型数据结构的通用规则:

    list,set,hash,zset是容器型数据结构,共享以下两种规则

    1. 如果容器不存在就创建一个,在进行 *** 作
    2. 如果容器里没有元素就立即删除容器.
  2. 过期时间

    1. redis 的过期时间是以对象为单位进行设置的,如一个hash的过期是整个hash对象的过期,而不是其中一个子key的过期.
    2. 如果一个字符串设置了过期时间,然后使用set方法修改了它,则它的过期时间会消失.
  3. 分布式锁:

    其利用redis处理指令是单线程.在redis内占一个坑,告诉别人我已经上锁了

    使用setnx(不存在才设置),为了防止因为各种原因导致无法解锁导致死锁,给锁设置一个过期时间.

    使用set 锁名 锁值 ex 过期时间 nx进行上锁

    1. 锁冲突:
      1. 直接抛出异常,提示用户稍后重试
      2. sleep一会再试
      3. 将请求转移到延时队列
  4. 延时队列

    1. 异步消息队列:

      使用list,读取使用阻塞读:blpop/brpop->在队列没有数据时会立刻休眠等待指定时间.

      BRPOP 队列名1 队列名2 时间

      1. 注意:如果一直阻塞,redis客户端可能会主动断开连接!!!
    2. 延时消息队列

      1. 使用zset,将消息序列化为一个字符串作为value,到期处理时间作为score,然后多线程轮询到期任务进行处理,

        ZADD 队列名 过期时间 值

        ZRANGEBYSCORE 队列名 0 time.Now()

        zrem 队列名 value 通过这个的返回结果判断是否获取到数据,处理并发问题

  5. 布隆过滤器

    使用:

    bf.add k v # 添加
    bf.madd k v1 v2 # 添加多个
    bf.exists k v # 判断有没有...
    bf.reserve # 自定义过滤器
    	-key #key
    	-error_rate # 错误率越高,占用空间越高
    	-initial_size # 预计放入的元素个数
    

    原理:一个大型位数组和几个不一样的无偏hash(把key算的比较均匀)函数,每次add一个key,通过多个hash函数算出多个位,将其都置为1,在exists时查询那几个位是否都为1.

  6. 限流

    1. 简单限流:滑动窗口

      使用zset,每次请求使用zset记录下来,value保证唯一,score保存当前时间戳,每次查询时先删除过期时间,再查询记录数进行判断.

    2. 漏斗限流

      漏斗的剩余空间代表着当前行为可以持续进行的数量,流水速率代表系统允许该行为的最大频率.

      结构:

      type funnel struct {
      	cap       float64   //容量
      	rate      float64   //速率 额度/s
      	freeSpace float64   //剩余空间
      	lastTime  time.Time //上一次漏水时间->用于加剩余空间
      }
      

      使用:

      每次计算与上次漏水的时间间隔,加空间,然后判断所需额度(每次请求消耗额度)与剩余空间大小.

      redis-cell提供了实现,可以了解一下.

  7. scan

    常规的keys可能会阻塞线程,不能控制最大条数.

    scan特点

    1. 通过游标分布进行,不会阻塞线程
    2. 提供limit参数,可以控制每次遍历的最大槽数
    3. 服务器不需要为游标保存状态,需要客户端进行保存.

    注意:

    1. 返回的结果可能重复
    2. 遍历过程中如果有数据修改,可能会无法遍历到
    3. 遍历结束的标志是返回的游标为0

    使用:

    三个参数:cursor游标位置,match正则表达式,count单次遍历的最大槽数

    $ scan 0 match * count 100
    1) "0"
    2) 1) "codehole"
       2) "list"
       3) "list2"
       4) "delay-queue"
       5) "k1"
    

    scan遍历顺序:采用高位进位加法进行遍历避免因为字典的扩容缩容导致槽位遍历重复和遗漏

三大特殊数据类型 geospatial 地理位置
geoadd key 纬度 经度 名称 //将指定的地理空间位置(经度,纬度、名称)添加到指定的key中
getpos key 名称	//获取指定名称的经度和纬度
geodist key city1 city2 [unit]//获取city1和city2之间的距离[m米/km千米/mi英里/ft英尺]
GEORADIUS key 经度 纬度 radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] //查找指定经纬度的点周围的点
//WITHDIST:还要返回指定中心返回物品的距离。距离以与指定为命令的半径参数的单位相同的单位返回。
//WITHCOORD:还返回匹配项目的经度,纬度坐标。
//ASC:由近到远 DESC:相反
//COUNT:查询数量
GEORADIUSBYMEMBER key 城市 radius//通过存在的城市进行查询,和上面差不多
geohash key city1 city2 ... //返回一个或者多个值的经纬度哈希
zrange	//可以遍历
zrem myset v1 	//可以删除

底层使用zset实现,可以使用zset *** 作

Hyperloglog (基数统计)

基数:不重复的元素

  1. 提供不精确的去重统计
  2. 数据较少时,使用稀疏矩阵,较多时使用稠密矩阵(1个key固定12kb内存)
pfadd key v1 v2 v3 	   //添加元素
pfcount key		       //统计个数
pfmerge key3 key1 key2 //将key1和key2合并到key3,不是覆盖
Bitmaps (位存储)
  1. 位图:普通字符串byte数组

    位图的最小单位是bit.

  2. 位图的位数组是自动拓展的

setbit key offset value //设置key中第offset位值为value(0或1)
getbit key offset		//查看key中第offset位的值
bitpos key bit [start,end] //查看key中第一个bit出现的位置,指定区间可以看数量
bitcount key			//统计key的value总和
# 整存
set k1 v1
# 整取
get k1
# 一次性 *** 作多个位,有三个子命令,但每个子命令最多 *** 作64个连续位
BITFIELD
    -get
    	BITFIELD k1 get u4 0 //从第一个位开始获取4位,结果是uint型
    	BITFIELD k1 get i4 0 //从第一个位开始获取4位,第一位是符号,结果是int型
    -set
   		BITFIELD k1 set u8 8 97 //从第9位起8位用无符号数97替换
    -incrby
    	BITFIELD k1 incrby u4 2 1 //从第3位起对接下来的4位无符号数+1,溢出则折返(默认).
    		-overflow //溢出策略
    			-sat  //饱和截断
    			-fail //失败不执行
    	BITFIELD k1 overflow sat incrby u4 2 1 //饱和截断
应用场景:
统计用户信息,活跃不活跃,登录未登录,打卡,只要两个状态都可以使用
拓展

xstream-消息队列-自己看书…

事务
事务:	 一组命令的集合,所有的命令会按顺序执行
    一次性,顺序性,排他性
//Redis 单条命令是保证原子性的,但Redis的事务是不保证原子性的

//Redis的事务没有隔离级别的概念,不会出现幻读,脏读,重复读等概念->所有命令在事务中没有直接被执行,而是在发起执行命令时才会被执行
Redis 事务
    开启事务(multi)
    命令入队(...)
    执行事务(exec)
	取消事务(discard)
监控
    watch k1 k2 ... //开启监控
    unwatch			//取消监控

事务出错
    1. 编译型异常(命令的使用有误,如命令写错或参数不对)
       所有命令都不会执行
    2. 运行时异常(执行命令时发现问题,比如字符串增加)
       除了异常命令外,其余命令都会执行

监控: Watch 乐观锁

悲观锁:
	认为什么时候都会出问题,无论做什么都会加锁
乐观锁:
	不会加锁.在更新数据时判断一下,看此期间是否有人修改过这个数据
    1. 获取version
    2. 更新时比较version

正常情况下

监控开启之后进行事务,多线程修改监控的值导致事务提交失败

事务执行失败解决措施:

  1. 重新监控
  2. 再次执行
Redis.conf 详解

配置文件大小写不敏感

可以包含多配置文件

网络

ip``bind 127.0.0.1 -::1`

保护模式 protected-mode yes

端口号 7963

通用配置

是否以守护进程形式开启: daemonize yes

如果不以守护进程形式启动,需要指定对应pid文件pidfile /var/run/redis_6379.pid

日志

Specify the server verbosity level.

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

是否显示log always-show-logo no

快照 SNAPSHOTTING

持久化: 在规定的时间内,执行了多少次 *** 作,则会持久化到文件 .rdb .aof

save 3600 1 # 3600秒内,如果至少有1个key进行了修改,则持久化
save 300 100
save 60 10000

持久化出错后是否继续工作 : stop-writes-on-bgsave-error yes

是否压缩rdb文件 : rdbcompression yes

保存rdb文件时,进行错误的检查校验: rdbchecksum yes

rdb文件保存目录: dir ./

主从复制 REPLICATION

安全 SECURITY

密码: requirepass WW876001

设置(获取)密码: set(get) requirepass XXXX

客户端 CLIENTS

最大客户端连接数 maxclients 10000

redis配置最大内存数 maxmemory

内存满了采取什么策略 maxmemory-policy noeviction

1、volatile-1ru:只对设置了过期时间的key进行LRU (默认值)
2、al1keys-1ru :删除1ru算法的key 
3、volatile-random: 随机删除即将过期key
4、al1keys-random: 随机删除
5、volatile-tt1:删除即将过期的
6、noeviction :永不过期,返回错误

AOF 设置 APPEND ONLY

默认关闭 appendonly no 一般都是用rdb

默认文件名 appendfilename "appendonly.aof"

记录的频率
# appendfsync always
appendfsync everysec # 每秒执行一次,但可能会丢失1秒内的数据
# appendfsync no
Redis持久化

内存型数据库在退出时会丢失数据,所以需要持久化 *** 作

RDB

快照原理:

redis使用COW实现快照持久化

fork(多线程)

redis在持久化时会创建一个子进程来遍历内存进行持久化.当父进程修改数据时会复制一份页然后对复制的页进行修改,而子进程数据不会有改变,等到持久化快结束了替换上次的持久化文件.

在指定时间间隔内将数据快照写入磁盘,恢复是将快照直接读入内存

rdb默认保存文件为dbfilename dump.rdb

触发机制:

  1. save规则满足的条件下
  2. 执行flushall 后,也会触发rdb规则
  3. 退出redis关机,也会触发

如何恢复rdb文件?

只需要.rdb文件放到redis的启动目录下.

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高(玩意gg了最后一次修改的数据就无了)

缺点:

  1. 需要一定时间间隔进程 *** 作.
  2. fork进程会占用内存
AOF

以日志的形式记录每个写 *** 作,将redis执行的指令记录下来,只允许追加文件.

redis启动会根据此文件重新构建数据(很慢)

默认是不开启的,开启后重启即可

如果aof文件损坏,redis是无法开启的,需要修复

redis-check-aof --fix 但是会清空一部分异常数据

优点 :

  1. 每一次修改都会保存,数据完整性很好

缺点:

  1. aof文件非常大,修复速度慢,效率低

重写机制

如果aof文件太大,redis会fork一个新的进程对文件进行重写

拓展
  1. RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储
  2. AOF持久化方式记录每次对服务器写的 *** 作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协议追加保存每次写的 *** 作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
  3. 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
  4. 同时开启两种持久化方式
    1. 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
    2. RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库( AOF在不断变化不好备份) , 快速重启,而且不会有AOF可能潜在的Bug ,留着作为一个万一的手段。
  5. 性能建议
    1. 因为RDB文件只用作后备用途,建议只在从机上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
    2. 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的I0 ,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
    3. 如果不Enable AOF , 仅靠Master-Slave Repllcation实现高可用性也可以,能省掉一大笔I0 ,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构。
Redis 发布订阅

redis 发布订阅是一种消息通信模式 : 发送者(pub)发布消息,订阅者(sub)接收消息.

PSUBSCRIBE pattern [pattern ...] # 订阅一个或多个符合给定模式的频道。
PUBSUB subcommand [argument [argument ...]] # 查看订阅与发布系统状态。
PUBLISH channel message # 将信息发送到指定的频道。
PUNSUBSCRIBE [pattern [pattern ...]] # 退订所有给定模式的频道。
SUBSCRIBE channel [channel ...] # 订阅给定的一个或多个频道的信息。
UNSUBSCRIBE [channel [channel ...]] # 指退订给定的频道。
监听订阅:`SUBSCRIBE raja`
发布消息:`PUBLISH raja test`

使用场景:

  1. 实时消息系统
  2. 实时消息
  3. 订阅,关注
Redis主从复制

概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader) ,后者称为从节点(slave/follower) ;数据的复制是单向的,只能由主节点到从节点。Master以写为主 , Slave以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点) ,但一个从节点只能有一个主节点。

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点) , 分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
    一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
    1. 从结构上,单个Redis服务器会发生单点故障,并且- 台服务器需要处理所有的请求负载,压力较大;
    2. 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容 量为256G ,也不能将所有内存用作Redis存储内存,一般来说, 单台Redis最大使用内存不应该超过20G。电商网站.上的商品, 一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。对于这种场景,我们可以使如下这种架构:

增量同步:

redis同步的是指令流,主节点会将修改性指令保存到本地buffer(定长的环形数组),然后异步将buffer同步到从节点,从节点将偏移量返回,如果因为网络或者同步的指令被覆盖了,就要使用快照同步.

快照同步:

先进行bgsive,然后将rdb文件传输给从节点,然后通知从节点增量同步,

# 查看当前库的信息
127.0.0.1:7963> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数
master_failover_state:no-failover
master_replid:825140e4ce53c71b03609f34d4f2ac16c56f30ac
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. port 端口
  2. pidfile 后台运行pid
  3. logfile 日志文件
  4. dbfilename 备份文件名

对于Redis集群,如果设置了requirepass,则一定要设置masterauth,否则从节点无法正常工作!!!

主从配置(命令行配置在断开后失效)

默认下每一台redis都是主机.
需要配置从机来连接主机
`SLAVEOF host port`
# SLAVEOF localhost 7963
# 从机
127.0.0.1:7964> info replication
# Replication
role:slave
master_host:localhost
master_port:7963
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_read_repl_offset:1
slave_repl_offset:1
master_link_down_since_seconds:-1
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:08c57dc6f5e45b074aac4f23d4ff861a7dfd29e7
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
# 主机
127.0.0.1:7963> INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7964,state=online,offset=98,lag=0
master_failover_state:no-failover
master_replid:69f449a8cd8f14b80e7ed4eb6b1da2cf516b5629
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:98
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:98

注意:

  1. 主机可以写,从机只能读
  2. 主机中的数据会被从机保存
  3. 主机断掉,从机仍可读,主机重连,从机仍保持连接

复制原理

  1. Slave启动成功连接到master后会发送一个sync同步命令,Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master将传送整个数据文件到slave ,并完成一次完全同步。
  2. 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  3. 增量复制: Master继续将新的所有收集到的修改命令依次传给slave ,完成同步
  4. 但是只要是重新连接master , 一次完全同步(全量复制)将被自动执行
主从复制原理

原文链接

# 当启动一个一主两从的主从复制样例后 查看master
127.0.0.1:7963> info server
# Server
redis_version:6.2.6
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:65d970af095d0295
redis_mode:standalone
os:Linux 3.10.0-957.21.3.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:4.8.5
process_id:9794
process_supervised:no
run_id:3a7496f7372de18e4b9f31dc827e0c96f60268d2 # redis服务的唯一标志
tcp_port:7963
server_time_usec:1639148964386103
uptime_in_seconds:3347
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:11759012
executable:/usr/local/bin/redis-server
config_file:/root/redisconfigs/redis7963.conf
io_threads_active:0
# 这个run_id 就是redis服务的唯一标识,重启redis服务号,这个run_id 会改变,多个redis客户端连接到同一个服务端,其run_id 是一样的,也就是说run_id 指的是服务端的id

# 查看master
127.0.0.1:7963> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7965,state=online,offset=4718,lag=0
slave1:ip=127.0.0.1,port=7964,state=online,offset=4718,lag=0
master_failover_state:no-failover
master_replid:19396025e773f6a214de67159207cda5af5a3742
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4718
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576 # 复制缓存区大小
repl_backlog_first_byte_offset:1
repl_backlog_histlen:4718
# 其中repl_backlog_size 复制缓存区大小,默认大小为1M,如果mater_repl_offset在这个范围内,就看是部分复制,否则就开始全量复制。

全量复制:

  1. 首先slave向master发送一个psync命令,因为是第一次,所以不知道run_id和offset.

    所以传过来-1表示全量复制.

  2. master在接受到psync后,将run_id和offset发送给slave

    1. master进行bgsave生成rdb,并将rdb发送给slave
    2. 在bgsave时可能会有write数据,将数据存到repl_back_buffer 中 并将buffer发送给slave
  3. slave清空数据,然后加载rdb和buffer 将数据存储起来。

部分复制

既然是部分复制,那就是slave已经知道了master的run_id 和offset ,所以发送psync 命令带上这两个参数,master 就知道这是部分复制,然后通过偏移量将需要复制的数据发送给slave。

总结:

主从复制的过程中既用到了全量复制也用到了部分复制,二者是相互配合使用的。看下面的流程图:

层层链路

我想当老大

SLAVEOF no one # 自己成为主机
哨兵模式

介绍:

下面是Redis官方文档对于哨兵功能的描述:

  1. 监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。

  2. 自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移 *** 作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。

  3. 配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。

  4. 通知(Notification):哨兵可以将故障转移的结果发送给客户端。

哨兵模式的结构拓扑图

大意就是

  1. 每一个哨兵节点会监听其他的哨兵节点以及master 和所有的slave
  2. 所有哨兵节点会定期的ping 主节点,监控是否正常
  3. 如果认为主节点出现故障的哨兵数量达到阙值,就判定主节点死掉,主节点就会客观下线
  4. 主节点客观下线后,哨兵节点通过选举模式在 slave 中选择出一个升级为主节点
  5. 其他的salve 指向新的主节点
  6. 原来的master 变成 slave ,并且指向新的主节点

引用官方哨兵模式处理流程

每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。

●如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。

●如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。

●当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)。

●在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。

●当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。

●若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

自动选老大

部署哨兵模式

  1. 创建并修改哨兵配置文件sentinel.conf

    首先到我们redis安装目录下,发现有sentinel.conf ,我们把它移到我们自己定义的文件夹中,和redis.conf 放在一起。

    mv sentinel.conf /usr/local/redis/etc/
    
    port 26379
    dir /usr/local/redis/etc  
    # 这里默认的是“/tmp”,如果你没有这个目录的权限就需要换啦,换一个你有权限的目录
    sentinel monitor mymaster 192.168.252.53 6379 2
    sentinel auth-pass mymaster 123456
    # 设置监控的主节点,2是一个阈值,代表有两台或两台以上哨兵判断主节点redis不通的话就认定这个节点有问题,实行故障转移。
    
    daemonize yes #后台启动
    logfile "/usr/local/redis/logs/redis_sentinel-26379.log" #加上日志 ,不加也无所谓
    
  2. sentinel monitor myredis 127.0.0.1 7963 1

    sentinel monitor    
    
    1. masterName这个是对某个master+slave组合的一个区分标识(一套sentinel是可以监听多套master+slave这样的组合的)。
    2. ip和 port就是master节点的 ip 和 端口号
    3. quorum这个参数是进行客观下线的一个依据,意思是至少有 quorum个sentinel主观的认为这个master有故障,才会对这个master进行下线以及故障转移。因为有的时候,某个sentinel节点可能因为自身网络原因,导致无法连接master,而此时master并没有出现故障,所以这就需要多个sentinel都一致认为该master有问题,才可以进行下一步 *** 作,这就保证了公平性和高可用。
  3. sentinel down-after-milliseconds

    1. 这个配置其实就是进行主观下线的一个依据
    2. timeout是一个毫秒值,表示:如果这台sentinel超过timeout这个时间都无法连通master包括slave(slave不需要客观下线,因为不需要故障转移)的话,就会主观认为该master已经下线(实际下线需要客观下线的判断通过才会下线)
  4. 一些参数说明:

  5. 启动哨兵

    redis-sentinel sentinel.conf
    

    监听一主二从

    当主节点断开,通过选举出一个新节点座位主节点.之前的主节点也会称为子节点.

总结: 哨兵模式是主从复制的升级,但配置文件很难配

Redis 集群

原文链接

Redis Cluster 命令

Redis 集群介绍

Redis 集群是 Redis 提供的分布式数据库方案,集群通过分片( sharding )来实现数据共享,并提供复制和故障转移。

为什么需要Redis集群呢?

​ 主从复制和哨兵模式只会有一个主服务器( master )。主从复制,只会有一个 master ,可以有多个 slave。而哨兵模式是在主从复制的基础上,发现 master 挂掉,会自动的将其中一个 salve 升级成 master 。但是最终结果还是只有一个 master。所以如果系统很大,对Redis 写 *** 作的压力就会很大,所以才出现的集群的模式。集群模式可以有多个 master 。

集群模式图:

总结: 集群就是多个master之间数据同步,单个master存在主从复制的哨兵模式

使用集群的好处

在没有Redis 集群的时候,人们使用哨兵模式,所有的数据都存在 master 上面,master 的压力越来越大,垂直扩容再多的 salve 已经不能分担 master 的压力的,因为所有的写 *** 作集中都集中在 master 上。所以人们就想到了水平扩容,就是搭建多个 master 节点。客户端进行分片,手动的控制访问其中某个节点。但是这几个节点之间的数据是不共享的。并且如果增加一个节点,需要手动的将数据进行迁移,维护起来很麻烦。所以才产生了 Redis 集群。

Redis 集群有什么好处,就是进一步提升 Redis 性能,分布式部署实现高可用性,更加的稳定。当然还包含主从复制的数据热备份以及哨兵模式的故障转移等优点。

在哪使用集群呢?

一般较大的项目使用了 redis 的话,都会使用 redis 集群.

  1. 利于拓展
  2. Redis的轻量级的,不会占用过多的性能
集群的主从复制和故障转移

集群的主从复制和单机的差不多

  1. slave会同步所属的master的数据
  2. master之间的数据会同步

故障转移也差不多,但是可以不用哨兵了(使用master充当哨兵的作用了)

  1. 哨兵的作用,定期给监视的master发送ping,如果认为其主观下线,就和其他哨兵一起判断,当数量达到一定数量后就认为其客观下线,然后选举出一个新的master.
  2. 而集群的master以同样的方式进行,master之间会定时ping,如果没有ping通就认为其主观下线,再结合半数以上的master都认为如此,就认定其为客观下线,再选举出一个新的master接替.
搭建一个Redis集群
  1. 修改配置文件

    port 8000 # 端口
    daemonize yes # 后台
    cluster-enabled yes # 开启集群
    cluster-config-file nodes.conf # 用来存放当前节点信息。
    cluster-node-timeout 5000 # 超时
    
  2. 启动服务

    [root@raja clusters]# ps -elf|grep redis
    5 S root     10276     1  0  80   0 - 42805 ep_pol 20:08 ?        00:00:00 /usr/local/bin/redis-server *:7963 [cluster]
    5 S root     10306     1  0  80   0 - 41269 ep_pol 20:08 ?        00:00:00 /usr/local/bin/redis-server *:7964 [cluster]
    5 S root     10329     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7965 [cluster]
    5 S root     10341     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7966 [cluster]
    5 S root     10356     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7967 [cluster]
    5 S root     10369     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7968 [cluster]
    0 S root     10384  8781  0  80   0 - 28203 pipe_w 20:09 pts/0    00:00:00 grep --color=auto redis
    
    
  3. 节点互通

    cluster nodes # 查看当前连通的节点信息(包含自己)
    cluster meet  ip  port # 连接其他节点
    

    连接后的状态如下:

  4. 卡槽分配

    Redis 集群是通过分片的方式来保存数据库中的键值对的,集群整个数据库被分成16384个槽(slot)。也就是说所有数据的key都会映射到对应的 slot 中。只有当数据库中16384 个槽都在节点上有分派,集群才会上线,否则集群的状态就是 fail。

    cluster addslots  slots[slots]
    样例:
    cluster addslots 0 1 2
    
    start=
    end=
    port=
    for slot in `seq ${start} ${end}`
    do
               echo "slot:${slot}"
                  redis-cli -p ${port} cluster addslots ${slot}
          done
    

    注: cluster forget id用于节点下线时,将待下线节点从集群其他节点保存的节点列表中删除。由于redis cluster采用gossip协议交互节点信息及集群状态,所以只要集群中有一个节点知道待下线节点,随着gossip信息交换,集群中的其他节点最终也都知道该节点,正因为此,向集群添加一个节点时,只需要向集群任意一个节点执行cluster meet命令即可,也因此,为了将一个节点完全的从集群中删除,必须对集群中其他所有节点都发送cluster forget命令。

    # 分配完后发现ok了
    127.0.0.1:7963> cluster info
    cluster_state:ok
    cluster_slots_assigned:16384
    cluster_slots_ok:16384
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:3
    cluster_size:3
    cluster_current_epoch:5
    cluster_my_epoch:1
    cluster_stats_messages_ping_sent:5577
    cluster_stats_messages_pong_sent:5596
    cluster_stats_messages_meet_sent:7
    cluster_stats_messages_fail_sent:2
    cluster_stats_messages_sent:11182
    cluster_stats_messages_ping_received:5596
    cluster_stats_messages_pong_received:5578
    cluster_stats_messages_fail_received:6
    cluster_stats_messages_received:11180
    
    集群 *** 作

    可以看到,进行槽指派之后是可以进行正常的 *** 作的,这里的set k1 v1提示我移动到7965端口执行。因为k1对应的卡槽为12706.

    更方便的方法: redis-cli -c -p 端口

    这样启动后进行 *** 作会自动切换到对应端口后,再保存数据

    配置主从

    前面为止,集群模式已经搭建好了,但是呢前文说的还有点瑕疵,现在就来说说,我们现在搭建的集群只有三个主节点,任何一个主节点挂掉了,就会导致集群不可用,因为集群可用的标志是 16384 个卡槽全部都分配到可用的节点上。所以我们现在搭建的集群还是不稳定的。所以为了解决这个问题,我们需要为每一个主节点配置一个从节点。 从节点的作用是数据热备份和当主节点出现故障时可以替代主节点进行工作

    1. 先将子节点加入到集群中

      CLUSTER MEET 127.0.0.1 7966
      CLUSTER MEET 127.0.0.1 7967
      CLUSTER MEET 127.0.0.1 7968
      
    2. 查找主节点的 nodesId

    3. 将从节点和主节点关联起来。

      [root@raja 68]# redis-cli -p 7966 cluster replicate 1b560f8faa02976508ecd7fdfe408eb4ca28da8c
      OK
      [root@raja 68]# redis-cli -p 7967 cluster replicate dfcea0b4564a6ba484260dbe132793c0a9325a2e
      OK
      [root@raja 68]# redis-cli -p 7968 cluster replicate e4f037c9ca53f8da087d923e20c621b2a9f8e495
      OK
      

    再次查看节点信息

    测试(断开节点)

    # 断开连接
    127.0.0.1:7965> SHUTDOWN
    not connected>
    # 查看节点 信息
    e4f037c9ca53f8da087d923e20c621b2a9f8e495 127.0.0.1:7965@17965 master,fail - 1639230607395 1639230604885 0 disconnected
    # 其子节点编程master节点
    205be367c485aa2712ddb09cb2b86bdd3531f44e 127.0.0.1:7968@17968 master - 0 1639230788029 6 connected 10001-16383
    127.0.0.1:7963> set k1 v1
    -> Redirected to slot [12706] located at 127.0.0.1:7968
    OK
    127.0.0.1:7968> 
    # 重连节点,发现其称为slave节点
    e4f037c9ca53f8da087d923e20c621b2a9f8e495 127.0.0.1:7965@17965 slave 205be367c485aa2712ddb09cb2b86bdd3531f44e 0 1639230895000 6 connected
    

    方法二:

    第二种方法搭建集群就简单讲啦,准备工作和启动都是一样的,只是不用我们自己进行节点互通和分配卡槽啦

    1. 启动几个redis

    2. 安装软件

      yum install ruby
      yum install rubygems
      gem install redis
      
    3. 配置

      执行以下命令,就会自动的帮我们进行节点互通,分配卡槽以及设置从节点。

      ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
      

      特别提醒,这里的IP用主机的IP,如果使用127.0.0.1的话,在我们代码中访问会出错,我也是在项目中使用的时候碰到的

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xT02Qpie-1641304593373)(https://gitee.com/ORaja/picture/raw/master/img/image-20211211220339766.png)]

    上面就已经搭建好集群了

Redis缓存穿透和雪崩

原文链接

缓存穿透

当缓存中没有数据的话,会直接去查数据库,当查询过多就会导致数据库挂了.多key请求

解决方式:

  1. 接口校验

    在调用接口时,可以在最外层先做一层校验,比如用户鉴权、数据合法性校验等等,提前过滤掉一些非法的接口请求。例如商品查询中,商品的ID是正整数,则可以直接对非正整数ID的请求直接过滤掉。

  2. 缓存空值

    当访问缓存和数据库都没有的数据时,可以将一个空值写入缓存,并给这个空值设置较短的过期时间,防止这个无效值一直占有内存。

  3. 布隆过滤器

    可以使用布隆过滤器存储所有可能访问的key,当用户请求过来,先判断用户发来的请求的key是否存在于布隆过滤器中,不存在的key直接被过滤掉,存在的key则进一步查询缓存和数据库。布隆过滤器相当于在Redis缓存前面又进行了一次拦截校验。

    1. 原理:

      说一个值存在时,此值可能不存在,说一个值不存在时,此值一定不存在.

      实际上布隆过滤器在Redis中的数据结构就是一个大型的位数组+多个随机映射的哈希函数

      向布隆过滤器中添加key时,会使用多个hash函数计算这个key的hash值,并转换为数组中对应的索引值,然后对数组长度进行取模运算得到具体的存放位置,每一个hash函数都会计算出不同的位置,然后把数组中对应的位置置为1,就相当于完成添加 *** 作

      当一个请求向布隆过滤器查询一个key是否存在时,跟添加 *** 作一样,会把这个要查询的key通过hash函数计算出数组中对应的索引位置,看看数组中这个位置是否都为1,只要有一个位置为0,则说明布隆过滤器中不存在这个key。如果这几个位置都为1,并不能说明这个key一定存在,只是有非常大概率存在,因为这些位置为1很有可能是因为其它key存在所导致的。如果这个数组比较稀疏,那么布隆过滤器判断正确的概率还是很大的,如果说这个位数组比较密集,那么判断正确的概率就会降低。

      布隆过滤器空间占用的大小,可能会影响到判断元素是否存在的精准度。

      布隆过滤器有两个参数:

      • 第一个参数是预计元素的数量n(超过数量使错误率上升)
      • 第二个参数是错误率f。(错误率越低,空间越大)

      布隆过滤器的空间占用计算公式根据这两个参数输入得到两个输出:

      • 第一个输出是位数组的长度l,也就是需要的存储空间大小(bit)
      • 第二个输出是**hash函数的最佳数量k。**hash函数的数量也会直接影响到错误率,最佳的数量会有最低的错误率。

      k = 0.7 ∗ ( l / n ) k = 0.7 * (l/n) k=0.7∗(l/n)

      f = 0.618 5 ( l / n ) f = 0.6185 ^ (l/n) f=0.6185(l/n)

缓存击穿

某热点key在过期的一瞬间被大量请求访问,导致全部直接访问数据库.对同key请求

解决方法:

  1. 加互斥锁(同一时刻只有一个请求去访问数据库)

    在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询 *** 作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其它请求直接走缓存查询数据。

  2. 热点数据设置不过期

    直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。这种方式适用于比较极端的场景,例如流量特别特别大的场景。很有可能发生异常,缓存无法更新

缓存雪崩

大量的热点 key 设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。一组key击穿

解决方法:

  1. 分散过期时间

    可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

  2. 热点数据不过期

    该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。

  3. 加互斥锁

    该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可

原理 线程IO
  1. 指令队列

    redis为每个客户端关联一个指令队列,客户端指令通过队列按顺序处理.

  2. 定时任务

    redis的定时任务保存在一个小根堆中,最快要执行的任务在根顶,在每个循环周期内redis对已经到点的任务进行处理,记录下下一个要执行的任务的时间作为select中的timeout参数的值.

通信协议

redis使用RESP通信协议(redis序列化协议)

redis数据被分为5种最小单元类型.单元结束统一加上回车换行rn

  1. 单行字符串使用+开头
    +hello worldrn
    
  2. 多行字符串以$开头,后跟字符串长度
    rnhello worldrn
    
  3. 整数以:开头,后跟整数字符串形式
    :1024rn
    
  4. 错误信息以-开头
  5. 数组以*开头,后跟数组长度.
    # 数组[1,2,3]
    
    *3rn:1rn:2rn:3rn
    
    1. NULL使用多行字符串表示,不过长度为-1
      $-1rn
      
    2. 空串使用多行字符串表示,长度为0
      [+++]rnrn # 两个rn中间是空串
      
内存回收机制

redis并不是立刻将空闲内存归还给 *** 作系统,而是等 *** 作系统分配给它的页上的所有内存空闲时才归还,但是redis会利用那些空闲的内存.

过期策略

redis会将所有设置了过期的key存到一个字典里,定时进行遍历这个字典进行删除,当访问某个key时也会进行判断.

  1. 定期扫描策略

    redis每秒进行10次过期扫描,每次从过期字典中选出20个key,删除20个key中已经过期的key,如果过期的key比例超过1/4,就重复扫描.

)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 166, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
redis总结_随笔_内存溢出

redis总结

redis总结,第1张

redis总结 Redis

redis视频链接

概述

Redis是什么

Redis是一个使用ANSI C编写的开源、支持网络、基于内存、分布式、可选持久性的键值对存储数据库。

Redis能做什么

  1. 内存存储,持久化(rdb,aof)
  2. 效率高,可以用于高速缓存
  3. 订阅系统
  4. 地图信息分析
  5. 计时器,浏览量
  6. .
安装
1. 下载压缩包
   	wget 软件链接
2. 解压gz压缩包
     tar -zxvf redis-6.2.6.tar.gz 
3. 安装gcc-c++环境
    yum install gcc-c++
4. 进入解压后的文件夹,make命令
    make	//第一次会下载很多环境
    make install //测试是否安装好了
5. redis默认安装路径/usr/local/bin
    复制原生的redis.config到bin下进行配置
6. redis默认不是后台启动,需要设置配置文件
    daemonize=yes
7. 启动redis服务,进入/usr/local/bin,运行redis-server
    redis-server 配置文件(redis.conf)
    //redis-server redisconfig/redis.conf
8. 测试连接
    redis-cli -p 6379//启动服务之后才行
9. shutdown//关闭服务
性能测试

压测命令:redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000

压测需要一段时间,因为它需要依次压测多个命令的结果,如:get、set、incr、lpush等等,所以我们需要耐心等待,如果只需要压测某个命令,如:get,那么可以在以上的命令后加一个参数-t
//比如
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000

基础知识
  1. redis 有16个数据库,默认使用第0个

  2. select 数据库序号 切换使用的数据库

  3. DBSIZE 查询数据库大小

  4. set get

  5. keys * 查看数据库所有的key

  6. flushdb 清空当前数据库 flushall 清空所有数据库

  7. Redis 是单线程的

    Redis 是基于内存 *** 作的,Redis的瓶颈是带宽和内存

    1. 为什么单线程还这么快?

      1. 高性能服务器不一定是多线程的
      2. 多线程会有上下文切换不一定比单线程效率高

      Redis将数据全部放到内存中,使用单线程效率非常高

五大数据类型 Redis-key
set key value 	//设置key value
get key			//获取key对应的value
keys * 			//显示所有键值对
exists key 		//是否存在key
move key db		//移动key到db数据库
expire key time //设置key过期时间(秒)
ttk key			//查询key的剩余时间
type key		//查看key类型
String
  1. 动态字符串,可以进行修改.
  2. 当字符串长度小于1MB时,拓容是加倍内存,大于MB时每次只会加1MB,字符串长度最大为512MB.
  3. 如果value是一个整数,可以对其进行自增,但其范围为signed long的最大最小值之间
append key v  	 //给key追加v
strlen key		 //获取key对应value长度
incr key 		 //是key对应value自增
decr key		 //使key对应value自减
incrby key count //使key对应value+=count
decrby key count //减少count
getrange key st et 		//截取key对应字符串value st到et 闭区间
setrange key start str 	//从start起替换key对应value(可以变长)
SETEX key time value	//设置key-value 过期时间为time
SETNX key value 		//不存在key时创建key-value
mset k1 v1 k2 v2 		//一次性设置多值
mget k1 k2 k3			//一次性获取多值
msetnx k1 v1 k2 v2      //不存在才能创建,原子性必须全部成功才行
getset key newvalue		//获取key值并设置newvalue
实例:
	set user:1 {name:raja,age:18}
List (列表)

注:命令均以l开头

  1. list是链表(不严谨),一些 *** 作需要遍历链表,效率低,如:lindex
  2. index 可以为负数,表示倒数第index个数
  3. redis底层快速链表.
    1. 在元素较少的情况下,redis会使用一片连续的内存存储(ziplist 压缩列表),
    2. 在元素较多的情况下,redis会改用quicklist:多个ziplist通过双向指针连接
      1. 满足了快速插入删除.
      2. 不会有太大的空间冗余->普通链表有指针空间,还会加重内存碎片化.
lpush list v1 v2    //从list左边插入v1 v2
lrange list st et 	//从list左边起遍历st到et
rpush list v1 v2    //从list右边插入v1 v2
lpop list [cnt]		//d出list左边1或[cnt]个元素
rpop list [cnt]		//d出list右边1或[cnt]个元素
lindex list index   //获取下标从左起为index的元素
llen list 			//查看list大小
lrem list cnt value //移除list从左起cnt个值为value的元素
ltrim list st et	//保留list从左起下标st到et的元素
rpoplpush list1 list2  	//移动
lset list index value	//将list中下标为index的值改为value(不存在就报错)
linsert list before value1 value2 //在value1前插入value2
linsert list after value1 value2  //在value1后插入value2
Set (集合)

注:命令均以s开头,集合元素不重复

  1. 可以用来保存中奖的人,以确保不会二次中奖
sadd myset v1 v2 		//向集合中插入v1 v2
smembers myset			//查看myset中的所有元素
sismember myset value	//判断value是不是myset的元素(1,0)
scard myset				//获取set的中的元素个数
srem myset v1 v2		//删除myset中的元素v1 v2
srandmember myset [cnt] //随机抽选出1或[cnt]个元素
spop myset [cnt]		//随机删除1或[cnt]个元素
smove myset1 myset2 value //将myset1中value移动至myset2
sdiff set1 set2			//返回set1-set2的差集
sinter set1 set2		//交集
sunion set1 set2		//并集
Hash (map集合)

key-map

  1. 类似java的hashmap,使用一维数组+二维链表的形式存储
  2. 拓容使用渐进式rehash,使用新旧两个hash结构.

注:以h开头

hset myhash k1 v1  			//设置myhash中k1 v1元素
hget myhash key				//获取..key元素的值
hmset myhash k1 v1 k2 v2 	//设置多个值 可以分段进行保存,获取数据比较高效
hmget myhash k1 k2			//获取多个值
hgetall myhash				//获取myhash中所有键值对
hdel myhash k1 k2			//删除myhash中指定的k1 k2
hlen myhash					//获取myhash元素个数
hincrby myhash key num		//增加num
hsetnx myhash key value		//不存在才创建

和String差不多

Zset (有序集合)
  1. 使用跳表进行存储 O(logn)
    1. 先在顶层定位,然后一层一层潜入下去,最后插入即可
  2. inf 表示无穷大
zadd myset score1 v1 score2 v2  //插入数据
zrange myset st et				//遍历
zrangebyscore myset (minscore (maxscore [withscores] //返回递增minscore<= x <=maxscore [带score]的所有x 不加括号表示没等号
zrangebyscore myset (maxscore (minscore [withscores]//降序
zrem myset v1			//移除myset中的v1元素
zcard myset				//获取myset的元素个数
zcount myset min max    //获取myset score min到max中共多少元素
拓展
  1. 容器型数据结构的通用规则:

    list,set,hash,zset是容器型数据结构,共享以下两种规则

    1. 如果容器不存在就创建一个,在进行 *** 作
    2. 如果容器里没有元素就立即删除容器.
  2. 过期时间

    1. redis 的过期时间是以对象为单位进行设置的,如一个hash的过期是整个hash对象的过期,而不是其中一个子key的过期.
    2. 如果一个字符串设置了过期时间,然后使用set方法修改了它,则它的过期时间会消失.
  3. 分布式锁:

    其利用redis处理指令是单线程.在redis内占一个坑,告诉别人我已经上锁了

    使用setnx(不存在才设置),为了防止因为各种原因导致无法解锁导致死锁,给锁设置一个过期时间.

    使用set 锁名 锁值 ex 过期时间 nx进行上锁

    1. 锁冲突:
      1. 直接抛出异常,提示用户稍后重试
      2. sleep一会再试
      3. 将请求转移到延时队列
  4. 延时队列

    1. 异步消息队列:

      使用list,读取使用阻塞读:blpop/brpop->在队列没有数据时会立刻休眠等待指定时间.

      BRPOP 队列名1 队列名2 时间

      1. 注意:如果一直阻塞,redis客户端可能会主动断开连接!!!
    2. 延时消息队列

      1. 使用zset,将消息序列化为一个字符串作为value,到期处理时间作为score,然后多线程轮询到期任务进行处理,

        ZADD 队列名 过期时间 值

        ZRANGEBYSCORE 队列名 0 time.Now()

        zrem 队列名 value 通过这个的返回结果判断是否获取到数据,处理并发问题

  5. 布隆过滤器

    使用:

    bf.add k v # 添加
    bf.madd k v1 v2 # 添加多个
    bf.exists k v # 判断有没有...
    bf.reserve # 自定义过滤器
    	-key #key
    	-error_rate # 错误率越高,占用空间越高
    	-initial_size # 预计放入的元素个数
    

    原理:一个大型位数组和几个不一样的无偏hash(把key算的比较均匀)函数,每次add一个key,通过多个hash函数算出多个位,将其都置为1,在exists时查询那几个位是否都为1.

  6. 限流

    1. 简单限流:滑动窗口

      使用zset,每次请求使用zset记录下来,value保证唯一,score保存当前时间戳,每次查询时先删除过期时间,再查询记录数进行判断.

    2. 漏斗限流

      漏斗的剩余空间代表着当前行为可以持续进行的数量,流水速率代表系统允许该行为的最大频率.

      结构:

      type funnel struct {
      	cap       float64   //容量
      	rate      float64   //速率 额度/s
      	freeSpace float64   //剩余空间
      	lastTime  time.Time //上一次漏水时间->用于加剩余空间
      }
      

      使用:

      每次计算与上次漏水的时间间隔,加空间,然后判断所需额度(每次请求消耗额度)与剩余空间大小.

      redis-cell提供了实现,可以了解一下.

  7. scan

    常规的keys可能会阻塞线程,不能控制最大条数.

    scan特点

    1. 通过游标分布进行,不会阻塞线程
    2. 提供limit参数,可以控制每次遍历的最大槽数
    3. 服务器不需要为游标保存状态,需要客户端进行保存.

    注意:

    1. 返回的结果可能重复
    2. 遍历过程中如果有数据修改,可能会无法遍历到
    3. 遍历结束的标志是返回的游标为0

    使用:

    三个参数:cursor游标位置,match正则表达式,count单次遍历的最大槽数

    $ scan 0 match * count 100
    1) "0"
    2) 1) "codehole"
       2) "list"
       3) "list2"
       4) "delay-queue"
       5) "k1"
    

    scan遍历顺序:采用高位进位加法进行遍历避免因为字典的扩容缩容导致槽位遍历重复和遗漏

三大特殊数据类型 geospatial 地理位置
geoadd key 纬度 经度 名称 //将指定的地理空间位置(经度,纬度、名称)添加到指定的key中
getpos key 名称	//获取指定名称的经度和纬度
geodist key city1 city2 [unit]//获取city1和city2之间的距离[m米/km千米/mi英里/ft英尺]
GEORADIUS key 经度 纬度 radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] //查找指定经纬度的点周围的点
//WITHDIST:还要返回指定中心返回物品的距离。距离以与指定为命令的半径参数的单位相同的单位返回。
//WITHCOORD:还返回匹配项目的经度,纬度坐标。
//ASC:由近到远 DESC:相反
//COUNT:查询数量
GEORADIUSBYMEMBER key 城市 radius//通过存在的城市进行查询,和上面差不多
geohash key city1 city2 ... //返回一个或者多个值的经纬度哈希
zrange	//可以遍历
zrem myset v1 	//可以删除

底层使用zset实现,可以使用zset *** 作

Hyperloglog (基数统计)

基数:不重复的元素

  1. 提供不精确的去重统计
  2. 数据较少时,使用稀疏矩阵,较多时使用稠密矩阵(1个key固定12kb内存)
pfadd key v1 v2 v3 	   //添加元素
pfcount key		       //统计个数
pfmerge key3 key1 key2 //将key1和key2合并到key3,不是覆盖
Bitmaps (位存储)
  1. 位图:普通字符串byte数组

    位图的最小单位是bit.

  2. 位图的位数组是自动拓展的

setbit key offset value //设置key中第offset位值为value(0或1)
getbit key offset		//查看key中第offset位的值
bitpos key bit [start,end] //查看key中第一个bit出现的位置,指定区间可以看数量
bitcount key			//统计key的value总和
# 整存
set k1 v1
# 整取
get k1
# 一次性 *** 作多个位,有三个子命令,但每个子命令最多 *** 作64个连续位
BITFIELD
    -get
    	BITFIELD k1 get u4 0 //从第一个位开始获取4位,结果是uint型
    	BITFIELD k1 get i4 0 //从第一个位开始获取4位,第一位是符号,结果是int型
    -set
   		BITFIELD k1 set u8 8 97 //从第9位起8位用无符号数97替换
    -incrby
    	BITFIELD k1 incrby u4 2 1 //从第3位起对接下来的4位无符号数+1,溢出则折返(默认).
    		-overflow //溢出策略
    			-sat  //饱和截断
    			-fail //失败不执行
    	BITFIELD k1 overflow sat incrby u4 2 1 //饱和截断
应用场景:
统计用户信息,活跃不活跃,登录未登录,打卡,只要两个状态都可以使用
拓展

xstream-消息队列-自己看书…

事务
事务:	 一组命令的集合,所有的命令会按顺序执行
    一次性,顺序性,排他性
//Redis 单条命令是保证原子性的,但Redis的事务是不保证原子性的

//Redis的事务没有隔离级别的概念,不会出现幻读,脏读,重复读等概念->所有命令在事务中没有直接被执行,而是在发起执行命令时才会被执行
Redis 事务
    开启事务(multi)
    命令入队(...)
    执行事务(exec)
	取消事务(discard)
监控
    watch k1 k2 ... //开启监控
    unwatch			//取消监控

事务出错
    1. 编译型异常(命令的使用有误,如命令写错或参数不对)
       所有命令都不会执行
    2. 运行时异常(执行命令时发现问题,比如字符串增加)
       除了异常命令外,其余命令都会执行

监控: Watch 乐观锁

悲观锁:
	认为什么时候都会出问题,无论做什么都会加锁
乐观锁:
	不会加锁.在更新数据时判断一下,看此期间是否有人修改过这个数据
    1. 获取version
    2. 更新时比较version

正常情况下

监控开启之后进行事务,多线程修改监控的值导致事务提交失败

事务执行失败解决措施:

  1. 重新监控
  2. 再次执行
Redis.conf 详解

配置文件大小写不敏感

可以包含多配置文件

网络

ip``bind 127.0.0.1 -::1`

保护模式 protected-mode yes

端口号 7963

通用配置

是否以守护进程形式开启: daemonize yes

如果不以守护进程形式启动,需要指定对应pid文件pidfile /var/run/redis_6379.pid

日志

Specify the server verbosity level.

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

是否显示log always-show-logo no

快照 SNAPSHOTTING

持久化: 在规定的时间内,执行了多少次 *** 作,则会持久化到文件 .rdb .aof

save 3600 1 # 3600秒内,如果至少有1个key进行了修改,则持久化
save 300 100
save 60 10000

持久化出错后是否继续工作 : stop-writes-on-bgsave-error yes

是否压缩rdb文件 : rdbcompression yes

保存rdb文件时,进行错误的检查校验: rdbchecksum yes

rdb文件保存目录: dir ./

主从复制 REPLICATION

安全 SECURITY

密码: requirepass WW876001

设置(获取)密码: set(get) requirepass XXXX

客户端 CLIENTS

最大客户端连接数 maxclients 10000

redis配置最大内存数 maxmemory

内存满了采取什么策略 maxmemory-policy noeviction

1、volatile-1ru:只对设置了过期时间的key进行LRU (默认值)
2、al1keys-1ru :删除1ru算法的key 
3、volatile-random: 随机删除即将过期key
4、al1keys-random: 随机删除
5、volatile-tt1:删除即将过期的
6、noeviction :永不过期,返回错误

AOF 设置 APPEND ONLY

默认关闭 appendonly no 一般都是用rdb

默认文件名 appendfilename "appendonly.aof"

记录的频率
# appendfsync always
appendfsync everysec # 每秒执行一次,但可能会丢失1秒内的数据
# appendfsync no
Redis持久化

内存型数据库在退出时会丢失数据,所以需要持久化 *** 作

RDB

快照原理:

redis使用COW实现快照持久化

fork(多线程)

redis在持久化时会创建一个子进程来遍历内存进行持久化.当父进程修改数据时会复制一份页然后对复制的页进行修改,而子进程数据不会有改变,等到持久化快结束了替换上次的持久化文件.

在指定时间间隔内将数据快照写入磁盘,恢复是将快照直接读入内存

rdb默认保存文件为dbfilename dump.rdb

触发机制:

  1. save规则满足的条件下
  2. 执行flushall 后,也会触发rdb规则
  3. 退出redis关机,也会触发

如何恢复rdb文件?

只需要.rdb文件放到redis的启动目录下.

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高(玩意gg了最后一次修改的数据就无了)

缺点:

  1. 需要一定时间间隔进程 *** 作.
  2. fork进程会占用内存
AOF

以日志的形式记录每个写 *** 作,将redis执行的指令记录下来,只允许追加文件.

redis启动会根据此文件重新构建数据(很慢)

默认是不开启的,开启后重启即可

如果aof文件损坏,redis是无法开启的,需要修复

redis-check-aof --fix 但是会清空一部分异常数据

优点 :

  1. 每一次修改都会保存,数据完整性很好

缺点:

  1. aof文件非常大,修复速度慢,效率低

重写机制

如果aof文件太大,redis会fork一个新的进程对文件进行重写

拓展
  1. RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储
  2. AOF持久化方式记录每次对服务器写的 *** 作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协议追加保存每次写的 *** 作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
  3. 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
  4. 同时开启两种持久化方式
    1. 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
    2. RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库( AOF在不断变化不好备份) , 快速重启,而且不会有AOF可能潜在的Bug ,留着作为一个万一的手段。
  5. 性能建议
    1. 因为RDB文件只用作后备用途,建议只在从机上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
    2. 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的I0 ,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
    3. 如果不Enable AOF , 仅靠Master-Slave Repllcation实现高可用性也可以,能省掉一大笔I0 ,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构。
Redis 发布订阅

redis 发布订阅是一种消息通信模式 : 发送者(pub)发布消息,订阅者(sub)接收消息.

PSUBSCRIBE pattern [pattern ...] # 订阅一个或多个符合给定模式的频道。
PUBSUB subcommand [argument [argument ...]] # 查看订阅与发布系统状态。
PUBLISH channel message # 将信息发送到指定的频道。
PUNSUBSCRIBE [pattern [pattern ...]] # 退订所有给定模式的频道。
SUBSCRIBE channel [channel ...] # 订阅给定的一个或多个频道的信息。
UNSUBSCRIBE [channel [channel ...]] # 指退订给定的频道。
监听订阅:`SUBSCRIBE raja`
发布消息:`PUBLISH raja test`

使用场景:

  1. 实时消息系统
  2. 实时消息
  3. 订阅,关注
Redis主从复制

概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader) ,后者称为从节点(slave/follower) ;数据的复制是单向的,只能由主节点到从节点。Master以写为主 , Slave以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点) ,但一个从节点只能有一个主节点。

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点) , 分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
    一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
    1. 从结构上,单个Redis服务器会发生单点故障,并且- 台服务器需要处理所有的请求负载,压力较大;
    2. 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容 量为256G ,也不能将所有内存用作Redis存储内存,一般来说, 单台Redis最大使用内存不应该超过20G。电商网站.上的商品, 一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。对于这种场景,我们可以使如下这种架构:

增量同步:

redis同步的是指令流,主节点会将修改性指令保存到本地buffer(定长的环形数组),然后异步将buffer同步到从节点,从节点将偏移量返回,如果因为网络或者同步的指令被覆盖了,就要使用快照同步.

快照同步:

先进行bgsive,然后将rdb文件传输给从节点,然后通知从节点增量同步,

# 查看当前库的信息
127.0.0.1:7963> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数
master_failover_state:no-failover
master_replid:825140e4ce53c71b03609f34d4f2ac16c56f30ac
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. port 端口
  2. pidfile 后台运行pid
  3. logfile 日志文件
  4. dbfilename 备份文件名

对于Redis集群,如果设置了requirepass,则一定要设置masterauth,否则从节点无法正常工作!!!

主从配置(命令行配置在断开后失效)

默认下每一台redis都是主机.
需要配置从机来连接主机
`SLAVEOF host port`
# SLAVEOF localhost 7963
# 从机
127.0.0.1:7964> info replication
# Replication
role:slave
master_host:localhost
master_port:7963
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_read_repl_offset:1
slave_repl_offset:1
master_link_down_since_seconds:-1
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:08c57dc6f5e45b074aac4f23d4ff861a7dfd29e7
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
# 主机
127.0.0.1:7963> INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7964,state=online,offset=98,lag=0
master_failover_state:no-failover
master_replid:69f449a8cd8f14b80e7ed4eb6b1da2cf516b5629
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:98
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:98

注意:

  1. 主机可以写,从机只能读
  2. 主机中的数据会被从机保存
  3. 主机断掉,从机仍可读,主机重连,从机仍保持连接

复制原理

  1. Slave启动成功连接到master后会发送一个sync同步命令,Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master将传送整个数据文件到slave ,并完成一次完全同步。
  2. 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  3. 增量复制: Master继续将新的所有收集到的修改命令依次传给slave ,完成同步
  4. 但是只要是重新连接master , 一次完全同步(全量复制)将被自动执行
主从复制原理

原文链接

# 当启动一个一主两从的主从复制样例后 查看master
127.0.0.1:7963> info server
# Server
redis_version:6.2.6
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:65d970af095d0295
redis_mode:standalone
os:Linux 3.10.0-957.21.3.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:4.8.5
process_id:9794
process_supervised:no
run_id:3a7496f7372de18e4b9f31dc827e0c96f60268d2 # redis服务的唯一标志
tcp_port:7963
server_time_usec:1639148964386103
uptime_in_seconds:3347
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:11759012
executable:/usr/local/bin/redis-server
config_file:/root/redisconfigs/redis7963.conf
io_threads_active:0
# 这个run_id 就是redis服务的唯一标识,重启redis服务号,这个run_id 会改变,多个redis客户端连接到同一个服务端,其run_id 是一样的,也就是说run_id 指的是服务端的id

# 查看master
127.0.0.1:7963> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7965,state=online,offset=4718,lag=0
slave1:ip=127.0.0.1,port=7964,state=online,offset=4718,lag=0
master_failover_state:no-failover
master_replid:19396025e773f6a214de67159207cda5af5a3742
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4718
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576 # 复制缓存区大小
repl_backlog_first_byte_offset:1
repl_backlog_histlen:4718
# 其中repl_backlog_size 复制缓存区大小,默认大小为1M,如果mater_repl_offset在这个范围内,就看是部分复制,否则就开始全量复制。

全量复制:

  1. 首先slave向master发送一个psync命令,因为是第一次,所以不知道run_id和offset.

    所以传过来-1表示全量复制.

  2. master在接受到psync后,将run_id和offset发送给slave

    1. master进行bgsave生成rdb,并将rdb发送给slave
    2. 在bgsave时可能会有write数据,将数据存到repl_back_buffer 中 并将buffer发送给slave
  3. slave清空数据,然后加载rdb和buffer 将数据存储起来。

部分复制

既然是部分复制,那就是slave已经知道了master的run_id 和offset ,所以发送psync 命令带上这两个参数,master 就知道这是部分复制,然后通过偏移量将需要复制的数据发送给slave。

总结:

主从复制的过程中既用到了全量复制也用到了部分复制,二者是相互配合使用的。看下面的流程图:

层层链路

我想当老大

SLAVEOF no one # 自己成为主机
哨兵模式

介绍:

下面是Redis官方文档对于哨兵功能的描述:

  1. 监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。

  2. 自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移 *** 作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。

  3. 配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。

  4. 通知(Notification):哨兵可以将故障转移的结果发送给客户端。

哨兵模式的结构拓扑图

大意就是

  1. 每一个哨兵节点会监听其他的哨兵节点以及master 和所有的slave
  2. 所有哨兵节点会定期的ping 主节点,监控是否正常
  3. 如果认为主节点出现故障的哨兵数量达到阙值,就判定主节点死掉,主节点就会客观下线
  4. 主节点客观下线后,哨兵节点通过选举模式在 slave 中选择出一个升级为主节点
  5. 其他的salve 指向新的主节点
  6. 原来的master 变成 slave ,并且指向新的主节点

引用官方哨兵模式处理流程

每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。

●如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。

●如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。

●当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)。

●在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。

●当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。

●若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

自动选老大

部署哨兵模式

  1. 创建并修改哨兵配置文件sentinel.conf

    首先到我们redis安装目录下,发现有sentinel.conf ,我们把它移到我们自己定义的文件夹中,和redis.conf 放在一起。

    mv sentinel.conf /usr/local/redis/etc/
    
    port 26379
    dir /usr/local/redis/etc  
    # 这里默认的是“/tmp”,如果你没有这个目录的权限就需要换啦,换一个你有权限的目录
    sentinel monitor mymaster 192.168.252.53 6379 2
    sentinel auth-pass mymaster 123456
    # 设置监控的主节点,2是一个阈值,代表有两台或两台以上哨兵判断主节点redis不通的话就认定这个节点有问题,实行故障转移。
    
    daemonize yes #后台启动
    logfile "/usr/local/redis/logs/redis_sentinel-26379.log" #加上日志 ,不加也无所谓
    
  2. sentinel monitor myredis 127.0.0.1 7963 1

    sentinel monitor    
    
    1. masterName这个是对某个master+slave组合的一个区分标识(一套sentinel是可以监听多套master+slave这样的组合的)。
    2. ip和 port就是master节点的 ip 和 端口号
    3. quorum这个参数是进行客观下线的一个依据,意思是至少有 quorum个sentinel主观的认为这个master有故障,才会对这个master进行下线以及故障转移。因为有的时候,某个sentinel节点可能因为自身网络原因,导致无法连接master,而此时master并没有出现故障,所以这就需要多个sentinel都一致认为该master有问题,才可以进行下一步 *** 作,这就保证了公平性和高可用。
  3. sentinel down-after-milliseconds

    1. 这个配置其实就是进行主观下线的一个依据
    2. timeout是一个毫秒值,表示:如果这台sentinel超过timeout这个时间都无法连通master包括slave(slave不需要客观下线,因为不需要故障转移)的话,就会主观认为该master已经下线(实际下线需要客观下线的判断通过才会下线)
  4. 一些参数说明:

  5. 启动哨兵

    redis-sentinel sentinel.conf
    

    监听一主二从

    当主节点断开,通过选举出一个新节点座位主节点.之前的主节点也会称为子节点.

总结: 哨兵模式是主从复制的升级,但配置文件很难配

Redis 集群

原文链接

Redis Cluster 命令

Redis 集群介绍

Redis 集群是 Redis 提供的分布式数据库方案,集群通过分片( sharding )来实现数据共享,并提供复制和故障转移。

为什么需要Redis集群呢?

​ 主从复制和哨兵模式只会有一个主服务器( master )。主从复制,只会有一个 master ,可以有多个 slave。而哨兵模式是在主从复制的基础上,发现 master 挂掉,会自动的将其中一个 salve 升级成 master 。但是最终结果还是只有一个 master。所以如果系统很大,对Redis 写 *** 作的压力就会很大,所以才出现的集群的模式。集群模式可以有多个 master 。

集群模式图:

总结: 集群就是多个master之间数据同步,单个master存在主从复制的哨兵模式

使用集群的好处

在没有Redis 集群的时候,人们使用哨兵模式,所有的数据都存在 master 上面,master 的压力越来越大,垂直扩容再多的 salve 已经不能分担 master 的压力的,因为所有的写 *** 作集中都集中在 master 上。所以人们就想到了水平扩容,就是搭建多个 master 节点。客户端进行分片,手动的控制访问其中某个节点。但是这几个节点之间的数据是不共享的。并且如果增加一个节点,需要手动的将数据进行迁移,维护起来很麻烦。所以才产生了 Redis 集群。

Redis 集群有什么好处,就是进一步提升 Redis 性能,分布式部署实现高可用性,更加的稳定。当然还包含主从复制的数据热备份以及哨兵模式的故障转移等优点。

在哪使用集群呢?

一般较大的项目使用了 redis 的话,都会使用 redis 集群.

  1. 利于拓展
  2. Redis的轻量级的,不会占用过多的性能
集群的主从复制和故障转移

集群的主从复制和单机的差不多

  1. slave会同步所属的master的数据
  2. master之间的数据会同步

故障转移也差不多,但是可以不用哨兵了(使用master充当哨兵的作用了)

  1. 哨兵的作用,定期给监视的master发送ping,如果认为其主观下线,就和其他哨兵一起判断,当数量达到一定数量后就认为其客观下线,然后选举出一个新的master.
  2. 而集群的master以同样的方式进行,master之间会定时ping,如果没有ping通就认为其主观下线,再结合半数以上的master都认为如此,就认定其为客观下线,再选举出一个新的master接替.
搭建一个Redis集群
  1. 修改配置文件

    port 8000 # 端口
    daemonize yes # 后台
    cluster-enabled yes # 开启集群
    cluster-config-file nodes.conf # 用来存放当前节点信息。
    cluster-node-timeout 5000 # 超时
    
  2. 启动服务

    [root@raja clusters]# ps -elf|grep redis
    5 S root     10276     1  0  80   0 - 42805 ep_pol 20:08 ?        00:00:00 /usr/local/bin/redis-server *:7963 [cluster]
    5 S root     10306     1  0  80   0 - 41269 ep_pol 20:08 ?        00:00:00 /usr/local/bin/redis-server *:7964 [cluster]
    5 S root     10329     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7965 [cluster]
    5 S root     10341     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7966 [cluster]
    5 S root     10356     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7967 [cluster]
    5 S root     10369     1  0  80   0 - 41269 ep_pol 20:09 ?        00:00:00 /usr/local/bin/redis-server *:7968 [cluster]
    0 S root     10384  8781  0  80   0 - 28203 pipe_w 20:09 pts/0    00:00:00 grep --color=auto redis
    
    
  3. 节点互通

    cluster nodes # 查看当前连通的节点信息(包含自己)
    cluster meet  ip  port # 连接其他节点
    

    连接后的状态如下:

  4. 卡槽分配

    Redis 集群是通过分片的方式来保存数据库中的键值对的,集群整个数据库被分成16384个槽(slot)。也就是说所有数据的key都会映射到对应的 slot 中。只有当数据库中16384 个槽都在节点上有分派,集群才会上线,否则集群的状态就是 fail。

    cluster addslots  slots[slots]
    样例:
    cluster addslots 0 1 2
    
    start=
    end=
    port=
    for slot in `seq ${start} ${end}`
    do
               echo "slot:${slot}"
                  redis-cli -p ${port} cluster addslots ${slot}
          done
    

    注: cluster forget id用于节点下线时,将待下线节点从集群其他节点保存的节点列表中删除。由于redis cluster采用gossip协议交互节点信息及集群状态,所以只要集群中有一个节点知道待下线节点,随着gossip信息交换,集群中的其他节点最终也都知道该节点,正因为此,向集群添加一个节点时,只需要向集群任意一个节点执行cluster meet命令即可,也因此,为了将一个节点完全的从集群中删除,必须对集群中其他所有节点都发送cluster forget命令。

    # 分配完后发现ok了
    127.0.0.1:7963> cluster info
    cluster_state:ok
    cluster_slots_assigned:16384
    cluster_slots_ok:16384
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:3
    cluster_size:3
    cluster_current_epoch:5
    cluster_my_epoch:1
    cluster_stats_messages_ping_sent:5577
    cluster_stats_messages_pong_sent:5596
    cluster_stats_messages_meet_sent:7
    cluster_stats_messages_fail_sent:2
    cluster_stats_messages_sent:11182
    cluster_stats_messages_ping_received:5596
    cluster_stats_messages_pong_received:5578
    cluster_stats_messages_fail_received:6
    cluster_stats_messages_received:11180
    
    集群 *** 作

    可以看到,进行槽指派之后是可以进行正常的 *** 作的,这里的set k1 v1提示我移动到7965端口执行。因为k1对应的卡槽为12706.

    更方便的方法: redis-cli -c -p 端口

    这样启动后进行 *** 作会自动切换到对应端口后,再保存数据

    配置主从

    前面为止,集群模式已经搭建好了,但是呢前文说的还有点瑕疵,现在就来说说,我们现在搭建的集群只有三个主节点,任何一个主节点挂掉了,就会导致集群不可用,因为集群可用的标志是 16384 个卡槽全部都分配到可用的节点上。所以我们现在搭建的集群还是不稳定的。所以为了解决这个问题,我们需要为每一个主节点配置一个从节点。 从节点的作用是数据热备份和当主节点出现故障时可以替代主节点进行工作

    1. 先将子节点加入到集群中

      CLUSTER MEET 127.0.0.1 7966
      CLUSTER MEET 127.0.0.1 7967
      CLUSTER MEET 127.0.0.1 7968
      
    2. 查找主节点的 nodesId

    3. 将从节点和主节点关联起来。

      [root@raja 68]# redis-cli -p 7966 cluster replicate 1b560f8faa02976508ecd7fdfe408eb4ca28da8c
      OK
      [root@raja 68]# redis-cli -p 7967 cluster replicate dfcea0b4564a6ba484260dbe132793c0a9325a2e
      OK
      [root@raja 68]# redis-cli -p 7968 cluster replicate e4f037c9ca53f8da087d923e20c621b2a9f8e495
      OK
      

    再次查看节点信息

    测试(断开节点)

    # 断开连接
    127.0.0.1:7965> SHUTDOWN
    not connected>
    # 查看节点 信息
    e4f037c9ca53f8da087d923e20c621b2a9f8e495 127.0.0.1:7965@17965 master,fail - 1639230607395 1639230604885 0 disconnected
    # 其子节点编程master节点
    205be367c485aa2712ddb09cb2b86bdd3531f44e 127.0.0.1:7968@17968 master - 0 1639230788029 6 connected 10001-16383
    127.0.0.1:7963> set k1 v1
    -> Redirected to slot [12706] located at 127.0.0.1:7968
    OK
    127.0.0.1:7968> 
    # 重连节点,发现其称为slave节点
    e4f037c9ca53f8da087d923e20c621b2a9f8e495 127.0.0.1:7965@17965 slave 205be367c485aa2712ddb09cb2b86bdd3531f44e 0 1639230895000 6 connected
    

    方法二:

    第二种方法搭建集群就简单讲啦,准备工作和启动都是一样的,只是不用我们自己进行节点互通和分配卡槽啦

    1. 启动几个redis

    2. 安装软件

      yum install ruby
      yum install rubygems
      gem install redis
      
    3. 配置

      执行以下命令,就会自动的帮我们进行节点互通,分配卡槽以及设置从节点。

      ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
      

      特别提醒,这里的IP用主机的IP,如果使用127.0.0.1的话,在我们代码中访问会出错,我也是在项目中使用的时候碰到的

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xT02Qpie-1641304593373)(https://gitee.com/ORaja/picture/raw/master/img/image-20211211220339766.png)]

    上面就已经搭建好集群了

Redis缓存穿透和雪崩

原文链接

缓存穿透

当缓存中没有数据的话,会直接去查数据库,当查询过多就会导致数据库挂了.多key请求

解决方式:

  1. 接口校验

    在调用接口时,可以在最外层先做一层校验,比如用户鉴权、数据合法性校验等等,提前过滤掉一些非法的接口请求。例如商品查询中,商品的ID是正整数,则可以直接对非正整数ID的请求直接过滤掉。

  2. 缓存空值

    当访问缓存和数据库都没有的数据时,可以将一个空值写入缓存,并给这个空值设置较短的过期时间,防止这个无效值一直占有内存。

  3. 布隆过滤器

    可以使用布隆过滤器存储所有可能访问的key,当用户请求过来,先判断用户发来的请求的key是否存在于布隆过滤器中,不存在的key直接被过滤掉,存在的key则进一步查询缓存和数据库。布隆过滤器相当于在Redis缓存前面又进行了一次拦截校验。

    1. 原理:

      说一个值存在时,此值可能不存在,说一个值不存在时,此值一定不存在.

      实际上布隆过滤器在Redis中的数据结构就是一个大型的位数组+多个随机映射的哈希函数

      向布隆过滤器中添加key时,会使用多个hash函数计算这个key的hash值,并转换为数组中对应的索引值,然后对数组长度进行取模运算得到具体的存放位置,每一个hash函数都会计算出不同的位置,然后把数组中对应的位置置为1,就相当于完成添加 *** 作

      当一个请求向布隆过滤器查询一个key是否存在时,跟添加 *** 作一样,会把这个要查询的key通过hash函数计算出数组中对应的索引位置,看看数组中这个位置是否都为1,只要有一个位置为0,则说明布隆过滤器中不存在这个key。如果这几个位置都为1,并不能说明这个key一定存在,只是有非常大概率存在,因为这些位置为1很有可能是因为其它key存在所导致的。如果这个数组比较稀疏,那么布隆过滤器判断正确的概率还是很大的,如果说这个位数组比较密集,那么判断正确的概率就会降低。

      布隆过滤器空间占用的大小,可能会影响到判断元素是否存在的精准度。

      布隆过滤器有两个参数:

      • 第一个参数是预计元素的数量n(超过数量使错误率上升)
      • 第二个参数是错误率f。(错误率越低,空间越大)

      布隆过滤器的空间占用计算公式根据这两个参数输入得到两个输出:

      • 第一个输出是位数组的长度l,也就是需要的存储空间大小(bit)
      • 第二个输出是**hash函数的最佳数量k。**hash函数的数量也会直接影响到错误率,最佳的数量会有最低的错误率。

      k = 0.7 ∗ ( l / n ) k = 0.7 * (l/n) k=0.7∗(l/n)

      f = 0.618 5 ( l / n ) f = 0.6185 ^ (l/n) f=0.6185(l/n)

缓存击穿

某热点key在过期的一瞬间被大量请求访问,导致全部直接访问数据库.对同key请求

解决方法:

  1. 加互斥锁(同一时刻只有一个请求去访问数据库)

    在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询 *** 作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其它请求直接走缓存查询数据。

  2. 热点数据设置不过期

    直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。这种方式适用于比较极端的场景,例如流量特别特别大的场景。很有可能发生异常,缓存无法更新

缓存雪崩

大量的热点 key 设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。一组key击穿

解决方法:

  1. 分散过期时间

    可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

  2. 热点数据不过期

    该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。

  3. 加互斥锁

    该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可

原理 线程IO
  1. 指令队列

    redis为每个客户端关联一个指令队列,客户端指令通过队列按顺序处理.

  2. 定时任务

    redis的定时任务保存在一个小根堆中,最快要执行的任务在根顶,在每个循环周期内redis对已经到点的任务进行处理,记录下下一个要执行的任务的时间作为select中的timeout参数的值.

通信协议

redis使用RESP通信协议(redis序列化协议)

redis数据被分为5种最小单元类型.单元结束统一加上回车换行rn

  1. 单行字符串使用+开头
    +hello worldrn
    
  2. 多行字符串以$开头,后跟字符串长度
    rnhello worldrn
    
  3. 整数以:开头,后跟整数字符串形式
    :1024rn
    
  4. 错误信息以-开头
  5. 数组以*开头,后跟数组长度.
    # 数组[1,2,3]
    
    *3rn:1rn:2rn:3rn
    
    1. NULL使用多行字符串表示,不过长度为-1
      $-1rn
      
    2. 空串使用多行字符串表示,长度为0
      rnrn # 两个rn中间是空串
      
内存回收机制

redis并不是立刻将空闲内存归还给 *** 作系统,而是等 *** 作系统分配给它的页上的所有内存空闲时才归还,但是redis会利用那些空闲的内存.

过期策略

redis会将所有设置了过期的key存到一个字典里,定时进行遍历这个字典进行删除,当访问某个key时也会进行判断.

  1. 定期扫描策略

    redis每秒进行10次过期扫描,每次从过期字典中选出20个key,删除20个key中已经过期的key,如果过期的key比例超过1/4,就重复扫描.

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/zaji/5696736.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存