CAP理论

C:Consistency (强一致性)
A:Availability (可用性)
P:Partition tolerance (分区容错性)
CAP理论是指,在分布式存储系统中,最多只能实现CAP的两个特点,分区容错性是必须要实现的,要不然无法进行数据的统一,一般都是选择可用性,牺牲强一致性。

Redis简介

Redis:Remote Dictionary Server(远程字典服务器)

是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据 库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为数据结构服务器。

特点:

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候会再次进行加载使用
  • Redis不仅仅支持简单的key-value类型的数据,还支持list、set、hash、zset等数据结构的存储
  • Redis支持数据的备份,master-slave模式的数据备份

    用途:

  • redis支持异步将内存的数写到硬盘上,同时不影响继续服务

  • 取最新N个数据的操作(例如:可以将最新的10条评论ID放在Redis的List集合里面)
  • 高频查找、并发高的数据可以放在Redis中
  • 商品秒杀
  • 发布、订阅消息系统
  • 地图信息分析
  • 定时器、计数器

    安装:37、Redis精讲.pdf

    默认16个数据库,从0开始,默认使用0号库
    默认端口:6379

    常用命令:

    select index(数据库号) 切换数据库
    DBSIZE 查看当前数据库key的数量
    keys * 查看具体的key
    Flushdb 清空当前库
    Flushall 清空全部库

    单线程:

    Redis在处理客户端的请求时,包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。但如果严格来讲从Redis4.0之后并不是单线程,除了主线程外,它也有后台线程在处理一些较为缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等等

为什么Redis单线程还这么快:
首先最重要的一点就是Redis完全基于内存,纯粹的内存操作非常快速;
Redis对数据操作也简单,数据结构是专门进行设计的;

多线程是CPU空闲,为了提高CPU利用率而采用的办法,CPU不是Redis的瓶颈,Redis的瓶颈在于内存和网络;
使用多路复用I/O,多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
Redis采用Reactor的方式来处理文件事件处理器(每一个网络连接其实都对应一个文件描述符)
文件事件处理器使用 I/O 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器。
虽然整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以保证整个 Redis 服务实现的简单。image.png

多线程:

Redis 6.0 支持多线程
为什么要开始多线程?
从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:
• 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式
• 使用多线程充分利用多核,典型的实现比如 Memcached。
协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:
• 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
• 多线程任务可以分摊 Redis 同步 IO 读写负荷

基本数据类型

String(字符串类型)

String一个key对应一个value,value最大为512M,另外,String类是二进制安全的,也就是说可以存储图片或者序列化的对象,是Redis最基本的对象。
可以利用INCR命令,方便的统计访问量,比如存放登陆验证码生成的图片。
image.png

Hash (类似Java里的Map)

键值对集合,String类型的,就和hashmap差不多,适合于存储对象 比如商品(id,name,数量)

List(列表)

底层是一个String类型的链表,是按插入顺序来排序的,
比如 用户的关注列表,粉丝列表

Set(集合)

String类型的无序集合(无序指的是不按放进去的顺序排,并不是说完全没顺序),是通过HashTable来实现的,不允许重复
利用其唯一性求交集,并集,差集:(Set)

ZSet(有序集合)

按照score 进行排序,score允许重复,但value不允许重复,就像java里的Set
应用:排行榜 、取TopN操作
带权重的消息队列

特殊数据类型

GEO地理位置

HyperLogLog 不精确的快速去重技术

比如{1,3,3,4,5,5,6} 基数集{1,3,4,5,6} 不重复元素为5

BitMap

利用二进制存储0和1,比如7天签到 010101 0为未签到 1为签到 节省空间

持久化

Redis持久化的方式主要有两种:

RDB

什么是RDB

在指定的时间间隔内或指定的规则内,将内存中的数据集快照以二进制的方式写入到磁盘中,以此来实现持久化,也就是通常说的Snapshot,恢复数据的话是把磁盘里的RDB文件再读到内存里,也就是Redis的进程会基于此运行。

RDB过程

Redis主进程会fork出一个子进程来进行持久化,会先将数据写入到一个临时文件中,等待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的,这样就能保证不影响Redis的性能,如果需要进行大规模数据的恢复,RDB是要比AOF高效的,但是可能最后一次持久化的数据可能会保存不了丢失,所以对数据的完整性要求极高就不要使用RDB,在Redis启动时会自动把RDB文件加载到内存里,保证了Redis数据库的数据,Redis默认配置就是采用RDB来持久化的。

FORK

FORK的意思是分叉,就是复制一个和当前进程一模一样的子进程,新进程的(变量、环境变量、程序计数器等)都和当前进程保持一致,但是是一个全新的进程,并且作为原进程的子进程。

RDB文件

持久化的文件命一般默认为dump.rdb,存放在redis的运行目录下,可以在redis.conf中配置重命名,除此之外还可以自定义RDB的保存规则,在原来的redis.conf中是这样配置的:save 900 1 save 300 10 save 60 10000 ,意思是每 900秒更新一次数据就触发一次rdb操作,除此之外,Redis停止服务的时候也会触发一次持久化,也就是写入新的RDB文件到磁盘,如果是宕机或者突然断电,那么最后一次保存的数据可能就没有写入到磁盘里的RDB恢复文件中。
手动触发:
save:只管保存,其他请求不管,全部阻塞
bgsave:后台异步进行快照操作,同时还可以处理客户端请求
恢复:
只要放到redis安装的目录下,redis启动的时候就会自动恢复,dir配置文件里可以看,也可以通过命令**config get dir** 来拿到目录。

优缺点:

优点:
适合大量数据的恢复
恢复速度稍微快一点,也简单方便
缺点:
数据的完整性不高,有数据丢失的风险
FORK的时候,内存中的数据相当于被克隆了一份,需要考虑内存空间

AOF

什么是AOF

和RDB不同,AOF是以日志的形式记录下来执行过的每个操作(除了读操作),只许追加不可改写,Redis启动的时候会读取这个日志来构建数据,以此来实现数据的恢复,其实就是把之前执行过的命令执行了一遍,怎样数据就和原来是一样的。

配置

  1. appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这
  2. 种方式在许多应用中已经足够用了
  3. appendfilename "appendonly.aof" # appendfilename AOF 文件名称
  4. appendfsync everysec # appendfsync aof持久化策略的配置
  5. # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
  6. # always表示每次写入都执行fsync,以保证数据同步到磁盘。
  7. # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
  8. No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安
  9. 全性
  10. Auto-aof-rewrite-min-size # 设置重写的基准值
  11. Auto-aof-rewrite-percentage #设置重写的基准值

AOF过程

AOF通过redis.conf的配置开启,即 appendonly yes ,然后根据配置的不同情况,
具体情况有:
always 每次写入都执行 fsync 同步数据到磁盘 这种数据完整性是最高的
everysec 每秒执行一次 fsync 可能会丢失1s数据
no 不执行fsync 由系统保证数据同步到磁盘 速度最快 数据完整性较差
来执行同步,AOF只允许追加,文件会越来越大,一般在大于64M的时候,会采用重写机制,Redis就会将启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。

优缺点:

优点:

设置的同步等级高的话,数据完整性比较好

缺点:

aof文件要比RDB文件大,且恢复速度慢
运行效率比RDB慢

Reids事务

Redis事务的本质是一组命令的集合,事务支持一次执行多个命令,一个事务中所有命令都会被序列化,在事务执行过程中,严格按照顺序执行这一系列命令,其他客户端提交的命令不会插入到这一系列命令中。
总的来说:redis的事务就是一次性按顺序执行给定的命令集合,不会插入其他的命令。

正常执行:image.png

命令性错误

image.png
如果有命令性错误,事务里的所有命令都不会执行。

语法性错误

如果有语法性错误,会把语法错误的报错,其他的正常执行
image.png

主从复制 读写分离

主从复制,也就是将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master),后者称为从节点(leader),数据的复制是单向的,也就是只能从主节点到从节点,Master主要负责写操作,Slave负责读操作,一个主节点可以有多个从节点,一个从节点只能有一个主节点。

作用:

  • 数据冗余,主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复,当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复
  • 负载均衡,主从复制的基础上,实现了读写分离,分担了服务器的负载,正常情况下肯定是写少读多的场景,这种场景下读写分离是非常有利于分摊服务器的压力的,提高Redis服务器的并发量。
  • 高可用的基石(是哨兵和集群实施的基础)

image.png

配置

至少需要三台Redis服务器,第一种方式是在从机配置文件里就写好主机的地址,直接按配置文件启动redis-server /etc/redis.conf就可以实现主从复制。
另外一种就是在从机上 slaveof 主机ip+主机端口 也可以实现。
redis-cli -p 6379
auth 123123
可以在redis里面通过info replication来查看当前主从关系
需要注意的是,如果主机有密码,从机需要将密码写在配置文件里。

复制原理

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

哨兵模式

如果没有哨兵模式的情况下,当我们的主Redis服务器挂掉后,我们需要在从机的服务器中,手动的通过slave no one 来将自己改为主机。这样做麻烦又费时,所以诞生了哨兵模式。
哨兵是一个独立的进程,独立运行,他的基本原理就是:给redis服务器发送命令,看redis服务器回不回,如果回了就证明redis服务器好着呢,没回到一定条件就认为这个服务器挂掉了。
image.png
如上是一个哨兵监控多个redis服务器

哨兵作用:

  • 通过发送命令,让redis服务器返回其运行状态,包括主服务器和从服务器
  • 当哨兵检测到master宕机,会自动将slave切换到master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机

image.png
单个哨兵可能会出现问题,一般使用多个哨兵进行监控,比如上面的3个哨兵检测3台服务器,当哨兵1发现master宕机后,并不会直接通知slave1和slave2切换主机,而是会等待其他的两台哨兵也检测到master宕机后,然后进行投票,投票通过后由一个哨兵发起,进行【故障转移】操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机。

配置测试

  1. 调整结构,6379带着80、81
  2. 自定义的 /myredis 目录下新建 sentinel.conf 文件,名字千万不要错
  3. 配置哨兵,填写内容sentinel monitor 被监控主机名字 127.0.0.1 6379 1,上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机
  4. 启动哨兵Redis-sentinel /myredis/sentinel.conf上述目录依照各自的实际情况配置,可能目录不同
  5. 正常主从演示
  6. 原有的Master 挂了
  7. 投票新选
  8. 重新主从继续开工,info replication 查查看
  9. 问题:如果之前的master 重启回来,会不会双master 冲突?
  10. 之前的回来只能做小弟了

    优缺点:

    优点:

    • 哨兵模式具有主从模式的所有优点
    • 在主从模式上进一步实现了故障自动转移,可用性更好
    • 系统更健壮,可用性更高

缺点:

  • redis较难支持在线扩容
  • 配置也比较繁琐

    配置

    1. # Example sentinel.conf
    2. # 哨兵sentinel实例运行的端口 默认26379
    3. port 26379
    4. # 哨兵sentinel的工作目录
    5. dir /tmp
    6. # 哨兵sentinel监控的redis主节点的 ip port
    7. # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
    8. # quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
    9. # sentinel monitor <master-name> <ip> <redis-port> <quorum>
    10. sentinel monitor mymaster 127.0.0.1 6379 2
    11. # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都
    12. 要提供密码
    13. # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
    14. # sentinel auth-pass <master-name> <password>
    15. sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
    16. # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
    17. # sentinel down-after-milliseconds <master-name> <milliseconds>
    18. sentinel down-after-milliseconds mymaster 30000
    19. # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同
    20. 步,
    21. 这个数字越小,完成failover所需的时间就越长,
    22. 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
    23. 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
    24. # sentinel parallel-syncs <master-name> <numslaves>
    25. sentinel parallel-syncs mymaster 1
    26. # 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
    27. #1. 同一个sentinel对同一个master两次failover之间的间隔时间。
    28. #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的
    29. master那里同步数据时。
    30. #3.当想要取消一个正在进行的failover所需要的时间。
    31. #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超
    32. 时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
    33. # 默认三分钟
    34. # sentinel failover-timeout <master-name> <milliseconds>
    35. sentinel failover-timeout mymaster 180000
    36. # SCRIPTS EXECUTION
    37. #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮
    38. 件通知相关人员。
    39. #对于脚本的运行结果有以下规则:
    40. #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
    41. #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
    42. #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
    43. #一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执
    44. 行。
    45. #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等
    46. 等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常
    47. 运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果
    48. sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执
    49. 行的,否则sentinel无法正常启动成功。
    50. #通知脚本
    51. # sentinel notification-script <master-name> <script-path>
    52. sentinel notification-script mymaster /var/redis/notify.sh
    53. # 客户端重新配置主节点参数脚本
    54. # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master
    55. 地址已经发生改变的信息。
    56. # 以下参数将会在调用脚本时传给脚本:
    57. # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
    58. # 目前<state>总是“failover”,
    59. # <role>是“leader”或者“observer”中的一个。
    60. # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的
    61. slave)通信的
    62. # 这个脚本应该是通用的,能被多次调用,不是针对性的。
    63. # sentinel client-reconfig-script <master-name> <script-path>
    64. sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

    缓存穿透雪崩击穿

    缓存穿透(缓存和数据库都没有)

    缓存穿透是指,客户端发过来的请求查询一个数据,发现Redis中没有数据,去数据库MySQL中查找,造成MySQL压力过大,响应慢,这个时候就相当于出现了缓存穿透。
    解决方案:
    对于一些恶意查询请求,接口层添加相应的过滤(布隆过滤器)
    缓存空对象,当MySQL也没查到时,将返回的空对象也缓存到redis中,设置一个30s的过期时间(不要太长,但是可以极大降级性能影响),这样的话能起到保护MySQL的作用

    缓存击穿(缓存突然没有数据库还有单个)

    缓存击穿是指,一个key非常热点,不停的扛着高并发,高并发持续集中对着一个点进行访问,如果这个key设置了过期时间,那么一瞬间会涌向MySQL,会导致数据库瞬间压力过大。
    解决方案:
    设置热点key永不过期
    使用分布式锁,保证每个key同时只有一个线程去查询后端服务,压力转移给分布式锁

    缓存雪崩(缓存没有数据库还有批量)

    缓存雪崩是指,在某一个时间段,缓存集中过期失效,致命的是缓存服务器的某个节点宕机或者断网形成的缓存雪崩(比如只有一台redis),可能会瞬间把数据库服务器击垮。
    比如,秒杀抢购的时候,一批商品放进了缓存,刚好0点过期了,就会导致大批量进入数据库查询,数据库的访问暴增,造成数据库也挂掉。
    解决方案:
    集群布置,主从复制,多台Redis一起工作
    限流降级,加锁,排队查询数据库
    数据预热:热点key提前放进数据库,过期时间设置成不同的,均匀过期,不能说同时过期。

    面试题:

    redis为什么这么快?

  1. Redis是基于内存的,这一点就比基于硬盘的要快很多。
    对于Mysql这些基于磁盘的而言,会受到磁盘IO的限制和影响,内存速度一般肯定比磁盘快很多的,磁盘一般都是永久化存储,除了固态是电压,普通的机械盘其实就是碟片,还是机械式的获取数据,而
  2. Redis对数据类型的优化,对底层数据结构的优化。
    1. String :动态字符串
      1. 长度处理:C语言是遍历到”/0”,然后得到字符串的长度,而Redis中,有一个字段len就存储着字符串的长度,这样对比速度是O(n)到O(1)的提升
      2. 空间预分配:len长度小于1M,那么将分配同等大小的未使用空间,如果大于1M。那么就分配1M的使用空间。image.png
    2. List 也有一个len来记录链表长度,是一个双向无环链表

https://mp.weixin.qq.com/s/0R0Evh1QX5BPOQt9233vpQ

  1. Redis合适的线程模型
    多路复用IO,对内存而言,一直干活就是最快的,频繁的切换会拖延时间,多次读写都在一个CPU 上,对于内存来说就是最佳方案,Redis 中使用 I/O 多路复用程序同时监听多个套接字,并将这些事件推送到一个队列里,然后命令逐个被执行,最终将结果返回给客户端。

    Reids项目中的实际应用:

    是验证码的存储,项目登陆的验证码是用的base64加密,每一个验证码有一个UUID作为key,验证码的答案作为value存入到Redis中,到时候前端提交过来的答案就对比一下,按条件返回异常或者成功。

还有就是采购还有出库的一些商品记录,一般就是放5条最新的历史记录,这个是放在Set里面,可以去重,然后按时间排序,再就是满5个自动删除一个,就可以了。