Redis
Pipeline机制
Pipeline指的是管道技术,指的是客户端允许将多个请求依次发给服务器,过程中而不需要等待请求的回复,在最后再一并读取结果即可。
pipeline机制可以优化吞吐量,但无法提供原子性/事务保障,而这个可以通过Redis-Multi等命令实现。
Redis和数据库双写一致性问题
首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
Cache Aside pattern
- 写:更新 DB,然后直接删除 cache 。
- 读:从 cache 中读取数据,读取到就直接返回,读取不到的话,就从 DB 中取数据返回,然后再把数据放到 cache 中。
Read/Write Through Pattern
Write Behind Pattern
Redis事务
三阶段:
- 开启:以MULTI开始一个事务
- 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
- 执行:由EXEC命令触发事务
WATCH监控
通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
Redis的三种模式:主从、哨兵、集群
作用: 读写分离、容灾恢复。
- 主从
- 主从复制:
-
Slave启动成功连接到Master后会发送一个sync命令
-
Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,Master将传送整个数据文件到Slave,以完成一次完全同步
-
SYNC命令是一个非常耗费资源的一个操作 1)主服务器需要执行BGSAVE命令来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘和I/O资源。 2)主服务器需要将自己生成的RDB文件发送给从服务器,这个发送操作会主从服务器的大量的网络资源(带宽和流量),并对主服务器响应命令请求的时间产生影响。 3)从服务器接收要载入RDB文件,在载入期间,从服务器因为阻塞而没办法处理命令请求。
-
PSYNC命令具有完整重同步和部分重同步两种模式: 完整重同步用户处理初次复制,部分重同步用于处理断线后重复制
-
- 主从复制:
- 哨兵
- Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
- 新建sentinel.conf文件, 一组sentinel能同时监控多个Master
- 复制延时:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
- Redis Cluster
- 可以在线水平扩展、客户端直连,系统瓶颈更少、无中心架构、支持数据分片
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 自动地将key分配到16384个槽(slot),而集群中的每个redis节点存储一部分槽。计算key的CRC16然后取模16384,得到的值即为槽点编号。
HASH_SLOT=CRC16(key) mod 16384
- 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。
为什么Redis集群有16384个槽
在握手成功后,节点之间会定期发送ping/pong消息,交换数据信息。
-
交换什么数据信息
-
typedef struct{ char sig[4]; ... uint16_t type; // 消息类型,用于区分meet,ping,pong消息 unsigned char mysolts[CLUSTER_SLOTS/8]; // 发送节点负责的槽信息 }
消息头里面有个myslots的char数组,长度为16384/8,这其实是一个bitmap,每一个位代表一个槽,如果该位为1,表示这个槽是属于这个节点的。
-
-
数据信息究竟多大
- 在消息头中,最占空间的是
myslots[CLUSTER_SLOTS/8]
。这块的大小是:16384÷8÷1024=2kb
- 在消息头中,最占空间的是
-
定期的频率什么样
-
redis集群内节点,每秒都在发ping消息。规律如下
- 每秒会随机选取5个节点,找出最久没有通信的节点发送ping消息
- 每100毫秒(1秒10次)都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster-node-timeout/2 则立刻发送ping消息
- 因此,每秒单节点发出ping消息数量为
数量=1+10*num(node.pong_received>cluster_node_timeout/2)
回答
- 如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
- redis的集群主节点数量基本不可能超过1000个。
- 槽位越小,节点少的情况下,压缩比高
配置文件redis.conf
GENERAL通用
-
Daemonize : Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程。
-
Pidfile : 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
-
Port
-
Tcp-backlog
-
分离为两个backlog来分别限制半连接(SYN_RCVD状态)队列大小和全连接(ESTABLISHED状态)队列大小。
SYN queue 队列长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,默认为2048。
Accept queue 队列长度由 /proc/sys/net/core/somaxconn 和使用listen函数时传入的参数,二者取最小值。
-
-
Timeout :客户端闲置多长时间后关闭连接
-
Bind : 绑定的主机地址
-
Tcp-keepalive
-
Loglevel : 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
-
Logfile
-
Syslog-enabled
-
Syslog-ident
-
Syslog-facility
-
Databases : 设置数据库的数量
SNAPSHOTTING快照
-
save : save <seconds> <changes> 。Redis默认配置文件中提供了三个条件:save 900 1、 save 300 10、 save 60 10000 rdbcompression no rdbchecksum yes dbfilename dump.rdb dir /data/redis_data/data
REPLICATION复制
SECURITY安全
LIMITS限制
APPEND ONLY MODE追加
- appendonly : 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no。
集群
cluster-enabled yes //开启集群 把注释#去掉
cluster-config-file nodes_6379.conf //集群的配置 配置文件首次启动自动生成 6379,6380,6381 把注释#去掉
cluster-node-timeout 15000 //请求超时 默认15秒,可自行设置 把注释#去掉
Redis的发布订阅
先订阅后发布后才能收到消息,
- 可以一次性订阅多个,SUBSCRIBE c1 c2 c3
- 消息发布,PUBLISH c2 hello-redis
- 订阅多个,通配符*, PSUBSCRIBE new*
- 收取消息, PUBLISH new1 redis2015
在分布式数据库中CAP、BASE
- C:Consistency(强一致性)、A:Availability(可用性)、P:Partition tolerance(分区容错性)
- CAP的3进2
- CA 传统Oracle数据库
- AP 大多数网站架构的选择
- CP Redis、Mongodb
- BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
- BASE其实是下面三个术语的缩写:
- 基本可用(Basically Available):在分布式系统环境中,允许牺牲掉部分不影响主流程的功能的不可用,将其降级以确保核心服务的正常可用。
- 软状态(Soft state):就是指在事务中,允许系统存在中间状态,且并不影响系统。就拿数据的主从复制来说,是完全允许复制的时候有延迟的发送的。
- 最终一致(Eventually consistent):如数据库的主从复制,有小延迟,但是最终保持一致了。
- BASE其实是下面三个术语的缩写:
存在的问题
-
缓存穿透
- 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。
- 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。
-
缓存雪崩
- 给缓存的失效时间,加上一个随机值,避免集体失效
- 保证redis高可用
- 本地缓存
- redis持久化,然后恢复
-
脑裂问题
-
问题描述:假设现在有三台机器,分别安装了redis服务,如果此时master服务器所在区域网络通信出现异常,导致和两台slave机器无法正常通信,但是和客户端的连接是正常的。那么sentinel就会从两台slave机器中选举其中一个作为新的master来处理客户端请求。这个时候,已经存在两台master服务器,client发送的数据会持续保存在旧的master服务器中,而新的master和slave中没有新的数据。如果一分钟以后,网络恢复正常,服务之间能够正常通信。此时,sentinel会把旧的master会变成新的master的slave节点。
-
解决方案:在配置文件中添加如下配置
min-slaves-to-write 1 // 连接到master的最少slave数量 min-slaves-max-lag 10 // slave连接到master的最大延迟时间 // 新版本的redis.conf min-replicas-to-write 3 min-replicas-max-lag 10
按照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失。
-
为什么要用 redis 而不用 map/guava 做缓存?
缓存分为本地缓存和分布式缓存。以Java为例,使用自带的map或者guava实现的是本地缓存,最重要的特点是轻量以及快速,生命周期随着jvm的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性[1]。
- Redis 可以用几十 G 内存来做缓存,Map 不行,一般 JVM 也就分几个 G 数据就够大了
- Redis 的缓存可以持久化,Map 是内存对象,程序一重启数据就没了
- Redis 可以实现分布式的缓存,Map 只能存在创建它的程序里
- Redis 可以处理每秒百万级的并发,是专业的缓存服务,Map 只是一个普通的对象
- Redis 缓存有过期机制,Map 本身无此功能
- Redis 有丰富的 API,Map 就简单太多了
分布式锁
简易加锁思路
// 获取锁
// NX是指如果key不存在就成功,key存在返回false,PX可以指定过期时间
SET anyLock unique_value NX PX 30000
// 释放锁:通过执行一段lua脚本
// 释放锁涉及到两条指令,这两条指令不是原子性的
// 需要用到redis的lua脚本支持特性,redis执行lua脚本是原子性的
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
存在的问题:设置了30s的超时时间,加入30s内没有完成业务逻辑的情况下,key会过期,其他线程又可以能会获取到锁。
RedLock算法
这个场景是假设有一个redis cluster,有5个redis master实例。然后执行如下步骤获取一把锁:
-
获取当前时间戳,单位是毫秒
-
跟上面类似,轮流尝试在每个master节点上创建锁,过期时间较短,一般就几十毫秒
-
尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2 +1)
-
客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了
-
要是锁建立失败了,那么就依次删除这个锁
-
只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁
存在的问题:颇具争议的,可能还会存在不少的问题,无法保证加锁的过程一定正确。
Redisson
-
Redisson 所有指令都通过 Lua 脚本执行,Redis 支持 Lua 脚本原子性执行。
-
Redisson 中有一个 Watchdog 的概念,设置一个 Key 的默认过期时间为 30s,它会在你获取锁之后,每隔 10s 帮你把 Key 的超时时间设为 30s(使用定时任务TimerTask, 通过递归调用本方法,无限循环延长过期时间 )。一直持有锁,也不会出现过期。宕机了,watchdog也会消失。
参考:
[1] https://segmentfault.com/q/1010000009106416