Redis学习笔记

  • Redis是一个远程内存数据库,复制特性和提供了不同类型的数据结构,非关系数据库可以存储键和五种不同类型的值之间的映射,可以将存储在内存的键值对数据持久化到硬盘,可以使用复制特性来扩展读性能,使用客户端分片来扩展写性能,用户可以将Redis扩展成一个能够包含数百GB数据,上百万TPS的系统。
  • Redis不使用表,也不会预定义或强制去要求用户对Redis存储的数据进行关联。
  • Memcached和Redis的区别:两种都可用于键值映射;但Redis能自动以两种不同方式将数据写入硬盘,并且除了能存储普通的字符串键之外,还能存储其它4中数据结构,而Memcached只能存储普通的字符创键。使用Redis使得代码更简洁易懂和维护,并且使得代码的运行速度更快(不需要通过读取数据库来更新数据)。
  • 如果程序对性能要求不高又或因为内存原因而无法将大量数据存储在内存中,那么用户就可能会选择关系数据库,并考虑将Redis作为主存储还是辅助存储。
  • Redis两种持久化方法:时间点转储和将所有修改了数据库的命令都写入到一个只追加文件中,用户可以根据数据的重要程度,将只追加设置为不同步,每秒同步一次或每写入一个命令就同步一次。
  • 为了扩展Redis的读性能,并为Redis提供故障转移支持,Redis实现了主从复制特性:执行复制的从服务器会连接上主服务器,接收主服务器发送的整个数据库的初始副本,并且主服务器执行的写命令都会被发送给所有连接着的从服务器去执行,从而实时更新从服务器的数据集。
  • 通常而言,数据库的插入行的方式来存储数据速度非常快(因为插入行只会在硬盘文件末尾进行写入),不过对表中行进行更新的速度很慢(因为这种更新除了会引起一个随机读之外还可能会引起一次随机写)。因为Redis将数据存储在内存中,并且发送给Redis的命令请求并不需要经过典型的查询分析器或查询优化器进行处理,所以对Redis存储的数据执行随机写的速度很快。
  • Redis提供的五种数据结构类型分别是:STRING/LIST/SET/HASH/ZSET。
  • 在Redis服务上执行命令需要一个Redis客户端;Redis键命令用于管理redis键,包括DEL、EXISTS、DUMP(序列化给定key)、EXPIRE(为给定key设置过期时间)、KEY pattern(查找所有符合模式的key)、PERSIST key(移除key的过期时间,key将永久保持)、TTL(返回key的剩余存活时间)、RANDOMKEY(从当前数据库中随机返回一个key)、RENAME(修改key的名称)、TYPE(返回key所存储值的类型)。
  • Redis字符串命令:SET、GET、GETRANGE、GETSET、GETBIT/SETBIT(对key所存储的字符串值,返回指定偏移量上的位)、MGET/MSET(获取一个或多个给定key的值)、STRLEN(获取key的字符串长度)、INCR/INCRBY/DECR(增加key中存储数字)、APPEND。
  • Hash命令:HDEL key fied1 [field](删除一个或多个哈希表字段)、HEXISTS、HGET、HGETALL(获取哈希表中指定key的所有字段和值)、HINCRBY、HKEYS(获取哈希表中所有字段)、HLEN(获取哈希表中字段的数量)、HMGET、HSET、HVALS、HSCAN key cursor [pattern] [count](迭代哈希表中的键值对)。
  • 列表命令:BLPOP/BRPOP/BRPOPLPUSH/LINDEX(通过索引获取列表中的元素)/LLEN/LINSERT key BEFORE|AFTER pivot value/LPOP/LPUSH/LRANGE/LREM(移除列表元素)/LTRIM。
  • Redis的SET集合是通过哈希表来实现的:SADD/SCARD(获取集合的成员数)/SDIFF(差集)/SDIFFSTORE(将差集存储)/SINTER/SISMEMBER key member(判断member元素是否是集合key的成员)/SMEMBERS/SMOVE/SREM/SUNION。
  • Redis有序集合不同在于每个元素都会关联一个double类型的分数,从小到大:ZADD/ZCARD/ZCOUNT/ZSCORE。
  • Redis发布订阅命令:PUBLISH channel msg(将信息发送到指定频道)、SUBSCRIBE/UNSUBSCRIBE、PSUBSCRIBE pattern [pattern]。
  • 事务命令:MULTI(标记一个事务块的开始)、EXEC(执行事务块中的命令)、DISCARD、WATCH/UNWATCH。
  • Redis管道技术可以在服务端未响应时,客户端可以继续想服务端发送请求,并最终一次性读取所有服务端响应——显著的提高了redis服务的性能。
  • Redis分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集;Redis有两种类型的分区:范围分区和哈希分区;分区的不足:涉及到多个key的操作通常不被支持,涉及到多个key的redis事务不能使用,增减容量复杂。


  • Redis相对于其他key-value缓存产品的特点:
    • Redis运行在内存中,支持数据的持久化,可以将内存中的数据保存在磁盘中;
    • Redis除了支持简单的key-value类型的数据,同时还提供list、set、zset、hash等数据结构;
    • Redis支持数据的备份,即master-slave模式的数据备份;
    • Redis所有操作都是原子的;
    • 性能极高,丰富的特性(发布/订阅等)。
  • Redis服务器将所有数据库都保存在redisServer结构的db数组中,db数组中的每一项都是redisDB结构;每个Redis客户端都有自己的目标数据库,默认情况下Redis客户端的目标数据库为0号数据库,可通过SELECT命令来切换目标数据库。Redis是一个键值对数据库服务器,服务器中的每个数据库都由一个reids.h/redisDb结构表示,其中redisDb结构中的dict字典保存了数据库中的所有键值对(键空间)。
  • Redis服务器是一个事件驱动程序,需要处理两类事件——文件事件(服务器通过监听并处理相关事件来完成一系列网络I/O操作)和时间事件(定时操作/周期操作)。Redis是基于Reactor模式,它使用了I/O多路复用来同时监听多个套接字。
  • key-value store最大的特点就是可扩展性——海量数据存储和高并发查询;
  • MySQL+Memcached架构的问题:通过将热点数据加载到cache加速访问,但随着业务数据量/访问量的不断增加,MySQL需要不断进行拆库拆表,Memcached也需要不断进行扩容,Memcached和MySQL存在数据库一致性问题,此外如果Memcached数据命中率低或down机,大量访问直接穿透DB.。
  • NoSQL解决的问题:少量数据存储高速读写访问(通过把数据全部in-momery);海量数据存储分布式系统支持;
  • memecached和redis的比较:数据持久化和同步(Redis支持数据的备份即master-slave);数据结构,并且reids拥有更多的数据结构并支持更丰富的数据操作(通常在memcached中需要将数据拿到客户端进行类似的更新再set回去,这大大增加了I/O次数和数据体积);网络IO模型(Memcached是多线程,分为监听线程、worker线程、引入锁,Redis使用单线程的IO复用模型);内存管理(Memcached使用预分配的内存池方法,带来了一定程度的空间的浪费并且内存仍然有很大空间时新数据也可能会被剔除,而Redis 使用现场申请内存方式存储数据,不会剔除任何非临时数据,Redis更适合作为存储而不是cache);数据一致性(Redis提供了事务,该数据并不提供严格的ACID,比如exec执行过程中服务器宕机,那么会有一部分命令执行了,剩下的没执行)。
  • 当我存储结构化数据的时候。。。增加了序列化/反序列化的开销,并且需要修改其中一项信息的时候,需要把整个对象取回,并且修改操作并发/CAS问题。而Redis提供的hash实际上是内部存储的value是一个HashMap,并提供了直接存取这个Map成员的接口(通过内部的key,也就是field,即通过key+field)。
  • 另外Redis提供了接口(hgetall)返回所有的值,但如果内部Map中成员很多的话,因为Redis单线程模型,所以遍历整个Map会很耗时。
  • Redis还提供了一个Watch功能,可以对一个key进行watch,然后再执行transactions,此时如果这个watch的值被修改了,那么该事务会拒绝执行。

  • Redis使用场景:redis相对于其他数据库的解决方案,它使用内存提供主存储支持,而仅仅使用硬盘做持久性存储,并且其是单线程:
    • select * from foo where ... order by time desc limit n——列出最新回复之类的查询非常频繁,并且会带来可扩展性问题(排序);而是用redis每次回复时就将id添加到redis列表LPUSH,然后再通过LTRIM只保留若干回复,这样每次获取最新回复就直接可以用LRANGE。可以使用LREM删除回复。
    • 排行榜相关 ZADD ZREVRANGE/ZRANK。
    • 按用户投票和时间排序(首先通过LPUSH+LTRIM确保只取出最新的若干条数据,在后台任务列表获取这个列表并在后台进行计算最终得分排序,计算结果由ZADD生成列表)。
    • 另外Redis的list可以用来实现消息队列(这样将系统各组件解耦,异步)。
    • 计数。
  • 使用redis需要进行备份!

  • redis提供了四种持久化方式:定时快照/基于语句追加方式(aof)/虚拟内存/Diskstore,前两种数据全部放在内存中,即小数据量的磁盘化,后两种是指当存储数据超过物理内存时的大数据量的Disk,后两种最好不要用。
  • 不要让redis使用的物理内存超过总内存的3/5(这不是因为基于快照方式持久化的fork系统调用造成的内存占用加倍而导致的,因为fork调用的copy-on-write机制是基于OS页,且只有脏页才会被复制,而一般短时内不可能所有的页都发送了写入),这是因为Redis持久化是使用了Buffer IO而造成内存占用加倍的(并非使用直接I/O)。

  • 对于主库和从库同步不及时而产生的数据不一致称为延迟,对于一致性要求不高的场景,只需要保证最终一致性即可,可通过在应用层增加策略来解决该问题:比如新注册用户必须先查询主库,注册成功之后需要等待3s之后才跳转(此时后台就是在做同步)。

  • redis作为存储并提供查询,后台不需要使用mysql。
  • redis的问题:replication中断后重发导致网络流量突增(重写replication,rdb+aof滚动);容量问题等。
  • 相对于硬件/IO/CPU资源消耗,Redis消耗最多的是CPU(复杂的数据类型必然带来CPU消耗)。
  • 暴露服务的常见过程:IP—》负载均衡—》域名—》命名服务。
  • redis不用做读写分离(每个请求都是单线程的)。

  • redis内存操作快、磁盘操作慢、偶尔高延迟、可能费内存。适用场景:大量写入、复杂的数据结构(简单数据结构持久化)、容量小于内存。容量规划(容量增长预估、读写量预估、数据结构、内存碎片),高延迟(尤其是做持久化),CPU瓶颈(hgetAll/单线程,可以增加mc)。
  • Redis事务只保证了CI,不满足AD,redis的事务实际上就是用队列包装的一组redis命令。并且在事务中如果后续的更新操作依赖于前面的查询指令,那么redis事务就无法有效完成。事务中的每条命令都与redis进行一次网络交互,应将多个命令打包批量发送给redis服务器执行,比如使用mget/mset或对于各种不同类型的更新操作,使用lua脚本将命令打包。

  • Redis自己构建了名为简单动态字符串(SDS)的类型,相对于直接使用C字符串:
    • C字符串本身不记录自身的长度信息,所以为获取字符串长度,必须要遍历整个字符串,直到遇到空字符;而sds本身的len域就记录了长度。
    • sds不会发生缓冲区溢出(它会首先检查空间是否足够,否则会先扩展sds的空间)。
    • C字符串只能保存文本数据,而不能保存二进制数据。
  • 在Redis中,哈希表自动执行扩展:
    • 哈希表的负载因子>=1。
    • 服务器正在执行BGSAVE或BGREWRITEAOF命令,并且哈希表的负载因子>=5(这样设置是因为在执行BGSAVE或BGREWRITEAOF命令时Redis需要创建当前进程的子进程,而通常操作系统都是采用COW,所以此时服务器会提高执行扩展操作所需的负载因子,从而尽可能地避免在子进程存在期间进行哈希表扩展,这样就避免不必要的内存写入操作)。
  • 当哈希表负载因子<0.1时将自动执行收缩操作。
  • 另外,Redis的扩展和收缩的rehash是渐进式的,即rehash是分多次完成的——这样是为了避免一次性rehash对服务器性能的影响。它具体是先设置一个变量表示rehash进行中,此后每次向哈希表进行操作时除了完成指定的操作外,还会顺带进行rehash。
  •  压缩列表是由一系列特殊编码的连续内存块组成的顺序数据结构,一个压缩列表可以包含多个entry,每个entry可以保存一个字节数组或一个整数值。
  • Redis每个对象都是由redisObject结构体表示:
    • type域记录对象的类型(使用TYPE命令返回的值就是数据库键对应的值对象的类型)。
    • ptr域指向对象包含的元素,这些元素类型由对象的encoding域决定。
    • encoding记录了对象的存储方式,比如type=string表示value存储的是一个普通字符串,那么对应的encoding可以是raw或int,表示redis内部是按数值类型存储和表示字符串(使用OBJECT ENCODING命令返回的是值对象的编码)。
    • vm字段等。
  • 当列表对象包含的元素较少时,Redis使用压缩列表ziplist作为列表对象的底层实现(相对于双端链表更节约内存,并且在元素较少时,在内存中以连续块方式保存的压缩列表更快被载入缓存);但当元素越来越多时使用功能更强、可保存大量元素的双端链表linkedlist更好。可使用list-max-ziplist-entries和list-max-ziplisr-value设置当列表对象保存的元素数量小于多少或所有元素的长度都小于多少时会采用压缩列表方式存储(默认512个/64字节)。
  • 如果字符串对象保存的是一个字符串值,并且值的长度大于32字节,那么字符串对象将使用sds进行保存,编码为raw,小于或等于32字节的话则使用embstr编码保存(相对于raw编码,embstr只需要调用一次内存分配函数来分配一块连续的空间,而raw则会调用两次来分别创建redisObject结构体和sdshdr结构体)。
  • 哈希对象的编码有ziplist和hashtable:

    • Redis学习笔记_第1张图片

    • 可使用hash-max-zipmap-entries和hash-max-zipmap-value来设置使用哪种编码。
  • 集合对象的编码可以是intset或hashtable,前者使用整数集合作为底层实现,后者使用键来保存,而值全部被设为NULL。set-max-intset-entries。
  • 有序集合的编码可以是ziplist或skiplist;如果使用前者作为底层实现的话,每个集合元素使用两个紧挨着的压缩列表节点来保存——第一个保存元素的成员member,第二个保存元素的分值score,且按分值大小升序排列。
  • redis实际的内存管理成本较高,为此redis提供了一系列参数来控制内存:
    • vm字段关闭虚拟内存。
    • 其次设置maxmemory指定当使用了多少物理内存就开始拒绝后续的写入请求(不会使用过多内存而swap)。
    • hash-max-zipmap-entries/hash-max-zipmap-value等——时间成本和空间成本的权衡。
  • RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。有两个Redis命令可以用于生成RDB文件——SAVE和BGSAVE,前者会阻塞Redis服务器进程直到RDB文件创建完毕,而后者会fork一个子进程,然后由子进程来负责创建RDB文件,服务器进程(父进程)继续处理命令请求。
  • RDB文件的载入是在服务器启动时自动执行的(阻塞,所以没有专门用于载入RDB文件的命令)。但如果开启了AOF持久化功能,那么会载入AOF文件。
  • 在BGSAVE命令执行期间:
    • 客户端发送的SAVE命令会被服务器拒绝——这样为了避免服务器进程和子进程同时执行两个rdbSave调用而产生的竞态条件。
    • 客户端发送的BGSAVE命令会被拒绝——竞态条件。
    • 最后,BGREWRITEAOF和BGSAVE命令也不能同时执行——因为两者的实际工作都是由子进程来执行的,虽然没有竞态条件,但是同时让两个子进程进行大量的磁盘I/O,性能。
  •  当Redis服务器启动时,通过指定配置文件或传入启动参数的方式设置save选项,从而就可以自动执行BGSAVE命令,进行间隔性数据保存(servercron函数)。
  • RDB文件结构:
    •  
    •   
    •   
    • 服务器读入RDB文件中的键值对数据时,会根据TYPE值来决定如何读取和解释value。
  • Redis提供了RDB和AOF(append only file)持久化功能;RDB是通过保存数据库中键值对来记录数据库状态,而AOF是通过保存Redis服务器所执行的写命令来记录数据库状态(写入到AOF文件的所有命令都是以Redis命令请求协议格式保存的,纯文本格式)。
  • AOF持久化功能可以分为命令追加、文件写入和文件同步三个步骤:
    • 当AOF持久化功能处于打开状态时,服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到redisServer的aof_buf缓冲区末尾;
    • Redis服务器进程实际上就是一个事件循环,这个循环中的I/O事件负责接收客户端的命令请求并进行响应,而时间事件则负责执行像serverCron这样定时运行的函数,因为服务器在处理I/O事件可能会执行写命令,使得一些内容会被追加到aof_buf缓冲区当中,所以服务器每次结束一个事件循环以前都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf中的内容写入保存到AOF文件中;flushAppendOnlyFile的行为是由appendfsync选项的值决定的:
      • always:所有内容都写入并同步到AOF文件中;
      • everysec:默认,将缓冲区中所有内容都写入到AOF文件中,如果上一次同步AOF文件的时间距离现在超过1s,那么再次对AOF文件进行同步(由一个专门的线程完成);
      • no:写入但不同步,何时同步由操作系统来决定;
      • 在现在操作系统中,为了提供文件的写入效率,当用户进程调用write将数据写入文件时,它会首先保存到内存缓冲区中,之后再将缓冲区中的数据写入到磁盘中。
  • 因为AOF文件中包含了重建数据库状态的所有写命令,所以服务器只需读入并重新执行一遍AOF文件中所保存的写命令即可:创建一个不带网络连接的伪客户端来执行AOF文件保存的写命令。
  • Redis提供了AOF文件重写来解决AOF文件体积膨胀的问题——通过创建一个新的AOF文件来替换现有的AOF文件,新的AOF文件并不会操作现有的AOF文件,而是通过读取服务器当前数据库状态,然后用一条或多条命令去记录键值对。
  • 因为Redis服务器使用单线程来处理命令请求,所以为防止重写函数被长时间阻塞,Redis将AOF重写函数放到子进程去执行。但是在子进程进行AOF重写期间,因为服务器进程还需要继续处理新的命令请求,而这些新的命令可能会对现有的数据库状态进行修改,所以Reids服务器设置了一个AOF重写缓冲区,该缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令的同时,会将这个写命令发送给AOF缓冲区和AOF重写缓冲区:
    • Redis学习笔记_第2张图片
    • 这样一来,AOF缓冲区的内容会定时被写入和同步到(父进程的)AOF文件中,另外从创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区中,当子进程完成AOF重写工作之后会向父进程发送一个信号,父进程在接收到该信号之后会调用一个信号处理函数,接着将AOF重写缓冲区中的所有内容都写入到新的AOF文件中(此时新的AOF文件所保存的数据库状态将和服务器当前数据库状态一致),然后对新的AOF文件重命名,原子地覆盖现有的AOF文件(在整个AOF后台重写过程中,只有信号处理函数执行时会阻塞服务器进程)。
  • 通常Redis只会将那些对数据库进行了修改的命令写到AOF文件,并复制到各个从服务器中,否则不会;但是PUBSUB和SCRIPT LOAD命令是例外,虽然PUBSUB命令没有修改,但所有订阅者都会因为该命令而改变,因此服务器需要使用REDIS_FORCE_AOF标志强制将该命令写到AOF文件中;SCRIPT LOAD命令类似,并且为了让主从服务器都可以正确载入指定的脚步,服务器需要使用REDIS_FORCE_REPL标志——强制将该命令复制给所有从服务器。
  • redis持久化是通过调用fork创建一个子进程,父进程继续处理client请求,子进程负责将内存内容写入到临时文件,因为OS的copy on write,父子进程共享相同的物理页面,当父进程处理写请求时OS会为父进程要写入的页面创建副本而不是共享,所以子进程地址空间内的数据fork时整个数据库的快照。

你可能感兴趣的:(开源项目)