1. 数据类型
1.1.key
Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值。
关于key的几条规则:
太长的键值不是个好主意
太短的键值通常也不是好主意
最好坚持一种模式
”object-type:id:field”就是个不错的注意,像这样”user:1000:password”。
1.2. string
值可以是任何种类的字符串(包括二进制数据),例如你可以在一个键下保存一副jpeg图片。值的长度不能
超过512 MB。
INCR 命令将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值,类似的命令有INCRBY,
DECR 和 DECRBY。
INCR是原子操作意味着什么呢?就是说即使多个客户端对同一个key发出INCR命令,也决不会导致竞争的情
况。
1.3. list
Redis lists基于Linked Lists实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。
Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。
如果快速访问集合元素很重要,建议使用可排序集合(sorted sets).
list可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY 操作,将会非常快,也会很容易扩展到百万级别元素的规模。
1.4. set
Redis Set 是 String 无序,唯一的排列。
1.5. sorted set
参见: sorted set是index的 (根据score索引,score相同根据value的字典顺序索引)
有序集合,它在 set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,会自动重新按新的值调整顺序。
score相同的元素,根据value的字典排序作为排序顺序
1.6.hash
Hash 便于表示 objects,实际上,你可以放入一个 hash 的域数量实际上没有限制(除了可用内存以外)。
1.7.bitmap
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR以及其它位操作。
bitmaps事实上并不是一种新的数据类型,而是基于字符串位操作的集合,由于字符串是二进制安全的,并且最长可支持512M
bitmaps最大的优势是在存储数据时可以极大的节省空间,比如在一个项目中采用自增长的id来标识用户,就可以仅用512M的内存来记录4亿用户的信息
1.8. HyperLogLogs
HyperLogLog是Probabilistic data
Structures的一种,这类数据结构的基本大的思路就是使用统计概率上的算法,牺牲数据的精准性来节省内存的占用空间及提升相关操作的性能。
redis的HyperLogLog特别是适合用来对海量数据进行unique统计,对内存占用有要求,而且还能够接受一定的错误率的场景.最典型的使用场景就是统计网站的每日UV。
1.9.GEO
使用 geohash 保存地理位置的坐标(把精度和维度重新编码后成一个字符串)
使用有序集合(sorted set)保存地理位置的集合,其score是GeoHash的52位整数值
2. HA
2.1. replica-主从模式
1,读写分离
2,容灾备份
3,配置slave node,不配置master node。 从库配置:slaveof 主库IP 主库端口
4, master node down掉, slave 不会自动跳转成master node
5,哨兵(sentinel )模式实现master slave 自动跳转
6,由于所有的写操作都是先在master上操作,然后同步更新到slave上,所以从master同步到slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,slave机器数量的增加也会使这个问题更加严重
7,主从模式, 数据不是强一致性,master节点存储数据后, 立即返回,然后在通知slave node保存数据,保证性能优先,牺牲一点点数据一致性。 鱼合熊掌不能兼得。
2.1.1. Master node
1, 可读可写
2,复制数据到slave node
2.1.2. Slave node
1,只读不写
2,数据从master复制过来
2.1.3. Sentinel哨兵
1, 一个或多个sentinel 独立进程组成集群
2,监控 Master 和Slave node
3, 如果Master 异常,则会进行Master-slave 转换,将其中一个Slave作为Master,将之前的Master作为Slave Sentinel的工作方式:
1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的
Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线
5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令
6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO命令的频率会从 10 秒一次改为每秒一次
7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
2.2. cluster- 分slot,采用一致性算法,写入数据
1,所有的redis节点彼此互联, 去中心化
2,节点的fail是通过集群中超过半数的节点检测失效时才生效
3,cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value
4,Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod16384的值,决定将一个key放到哪个桶中。
5,新增一个主节点:,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到新的node上,并把数据拷贝到新的node
6,删除一个节点也是类似,把将要删除的节点的slots,分摊到剩余的节点,移动完成后就可以删除这个节点了。
7,cluster中一个node down掉,数据就不完整,需要结合主从+哨兵模式,提高HA可用性。
3. 功能
3.1.分布式锁
具备3个特性就可以实现一个最低保障的分布式锁。
安全属性(Safety property): 独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。
活性A(Liveness property A): 无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。
活性B(Liveness property B): 容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁.
实现Redis分布式锁的最简单的方法就是在Redis中创建一个key,这个key有一个失效时间(TTL),以保证锁最终会被自动释放掉(这个对应特性2)。当客户端释放资源(解锁)的时候,会删除掉这个key(这个对应特性1)。
在cluster环境下,获取锁和释放锁必须得到 n/2+1个node确认后,方可认为成功(这个对应特性3)
3.2.发布,订阅-消息系统
3.3.缓存
3.4.数据库
参见: 数据持久化 (持久化)
4. 索引
4.1.key是有索引的
4.2. sorted set是index的
参见: sorted set (根据score索引,score相同根据value的字典顺序索引)
5. 数据持久化
5.1.RDB
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
rdb 记录是key某一时刻的值,但不记录对这个key的操作, 适合用于快照,
5.1.1. save - 阻塞,手工
阻塞Redis的服务器进程,直到RDB文件被创建完毕
5.1.2. bgsave - 非阻塞, 手工
Fork出一个子进程来创建RDB文件,不阻塞服务器进程 lastsave 指令可以查看最近的备份时间.
Redis 是单线程工作,如果 重写rdb需要比较长的时间,那么在重写rdb期间,Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了克服这个问题,解决办法是将 rdb重写程序放到子程序中进行。通过fork出子程序,利用COW技术主程序和子程序公用数据内存,避免了额外的内存数据拷贝,也可以避免多线程通过锁机制保证数据的一致性。
5.1.3. 自动保存 - 非阻塞,自动
根据redis.conf配置里的save m n定时触发(用的是BGSAVE)
主从复制时,主节点自动触发
执行Debug Relaod时触发
执行Shutdown且没有开启AOF持久化时触发
5.2.AOF(Append Only File)
每当redis执行一个改变数据集的命令时,这个命令就会追加到aof文件的末尾。这样的话,当redis重新启动时,程序就会通过执行aof文件中的命令来达到重建数据集的目的。
aof文件里可能有太多“琐碎”指令,所以aof会定期根据内存的最新数据重新生成aof文件。例如有很多命令都是在不停的往一个set里面一个一个的添加元素,aof里面会有很sadd记录,aof重写后会合并这些记录到一条一次性加入多个元素,这样可以压缩aof文件。
Redis 是单线程工作,如果 重写 AOF 需要比较长的时间,那么在重写 AOF期间,Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了克服这个问题,解决办法是将AOF 重写程序放到子程序中进行通过fork出子程序,利用COW技术主程序和子程序公用数据内存,避免了额外的内存数据拷贝,也可以避免多线程通过锁机制保证数据的一致性。
Redis 服务器设置了一个 AOF重写缓冲区,这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF 重写缓冲区。当子进程完成 AOF重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF重写缓冲区的内容都写到新的 AOF 文件中。
5.2.1. always
每次有新命令追加到aof文件时就执行一个持久化,非常慢但是安全
5.2.2. every second
每秒执行一次持久化,足够快(和使用rdb持久化差不多)并且在故障时只会丢失1秒钟的数据
5.2.3. no
从不持久化,将数据交给操作系统来处理。redis处理命令速度加快但是不安全。
5.3.RDB+AOF混合模式
redis4.0,混合持久化就是同时结合RDB持久化以及AOF持久化混合写入AOF文件混合持久化同样也是通过bgrewriteaof完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后在将重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据。
当我们开启了混合持久化时,启动redis依然优先加载aof文件,aof文件加载可能有两种情况如下:
aof文件开头是rdb的格式, 先加载 rdb内容再加载剩余的 aof。
aof文件开头不是rdb的格式,直接以aof格式加载整个文件
6. 实现技术
6.1. IO多路复用技术
通过多路复用技术监听与redis sever创建连接的socket。监听的消息大致分为三类
1,有客户端与redis连接的消息
2,可以从客户端socket读取数据的消息
3,可以往客户端socket写入数据的消息
所有消息放入事件消息队列
6.2.事件处理队列
事件消息队列将消息与实践处理器做关联。
客户端请求连接的消息,交给连接应答处理器
数据可读消息,交给命令请求处理器
数据可写消息,交给命令回复处理器
因为消息处理是单线程的,每一时刻只有一个消息被处理,所以redis是一个单线程模型。一种事件会等待另一种事件执行完后,才开始执行,事件之间不会出现抢占.事件处理器先处理文件事件,再执行时间事件文件事件的等待时间,由距离到达时间最短的时间事件决定
6.2.1. 文件事件
用于处理 Redis 服务器和客户端之间的网络IO
当一个新的client连接到服务器时, server会给该client绑定读事件, 直到client断开连接后,该读事件才会被移除和client自始至终都关联着读事件不同, server只会在有命令结果要传回给client时,才会为client关联写事件, 并且在命令结果传送完毕之后, client和写事件的关联就会被移除.因为在同一次文件事件处理器的调用中,单个客户端只能执行其中一种事件(要么读,要么写,不能又读又写),当出现读事件和写事件同时就绪时,事件处理器优先处理读事件
6.2.2. 时间事件
Redis 服务器中的一些操作需要在给定的时间点执行,而时间事件就是处理这类定时操作的。事件为单次执行事件,该事件会在指定时间被处理一次,之后该事件就会被删除。
循环事件,该事件会在指定时间被处理,之后它会按照timeProc的返回值,更新事件的 when属性,让这个事件在之后某时间点再运行,以这种方式一直更新运行。
6.3.事件处理器
文件事件相关的一些具体的事件处理器.
连接请求处理器acceptTcpHandler:程序会为redisServer.eventLoop关联一个客户连接的事件处理器。
命令请求处理器readQueryFromClinet :当新连接来的时候,需要创建客户端,在其中为客户端套接字注册读事件,关联处理器readQueryFromClinet处理函数。
命令回复处理器sendReplyToClient :当Redis根据客户端的输入得到输出候后,注册客户端套接字写事件,当套接字可写时,触发sendReplyToClient发送命令回复。
7. 回收策略
7.1.过期策略
Redis采用惰性删除 + 定时任务删除机制实现过期键的内存回收。
7.1.1. 惰性删除
惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。
7.1.2. 定时任务删除
Redis内部维护一个定时任务,默认每秒运行10次。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例,使用快慢两种速率模式回收键。
比如:
定时任务在每个数据库空间随机检查20个键,当发现过期时删除对应的键。如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止。
7.2.内存淘汰策略
当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受maxmemorypolicy参数控制,Redis支持6种策略。当用户set 一个key时,redis 检查所使用的内存是否超过maxmemory的设置,如果超过,通过执行设定的淘汰策略删除相应的的key,确保set成功执行。淘汰策略并不是针对所有数据, 只是采样其中部分数据,可以通过配置文件设置,这样做是保证redis可以高效运行,在准确性上做出了部分牺牲。
7.2.1. noeviction
默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
7.2.2. allkeys-lru
当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的Key。推荐使用,目前项目在用这种。
7.2.3. allkeys-random
当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。应该也没人用吧,你不删最少使用Key,去随机删。
7.2.4. volatile-lru
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。
7.2.5. volatile-random
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。
7.2.6. volatile-ttl
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。