zoukankan      html  css  js  c++  java
  • 《Redis入门指南》笔记

    第1章 简介
    1.1 历史与发展
    2008年 意大利创业公司创始人因对mysql性能不满意,于是他决定开发redis。
    2009年 redis初版由他一个人开发完成。redis是“remote dictionary server”的缩写。
    redis最新版为4.0.6。
     
    1.2 特性
    redis是一个字典结构的存储服务器,支持5种数据类型:字符串、散列、列表、集合、有序集合。
    所有数据都存储在内存中。通过TCP协议与客户端连接。
    简单稳定:使用C语言编写,3万行代码量。
    TTL:time to live,可以为每个键设置生存时间。
     
     
    第2章 准备
    2.1 安装redis
    采用源码编译安装,没有任何依赖,安装简单。
     
    2.2 启动和停止
    启动:redis-server --port 6379
    停止:redis-cli SHUTDOWN 或者 kill SIGTERM 给PID。
    连接服务端:redis-cli -h 127.0.0.1 -p 6379
    发送命令:如ping等。
    命令返回值:分为状态回复(ping)、错误回复、整数回复(incr)、字符串回复(get)、多行字符串回复(keys)。
     
    2.3 配置
    redis.conf文件是redis的默认配置文件。
    动态修改配置命令:CONFIG SET name value
    获取配置命令:CONFIG GET
     
    2.4 多数据库
    redis实例默认有16个数据库,从0开始编号,每个数据库代表一本字典。可以用database配置参数修改数量。默认0号,用select 命令更换数据库,如:select 1。
    redis不支持自定义数据库名,也不支持为每个数据库设置不同权限。各数据库之间数据不是完全隔离(flushall命令),更像是命名空间。
    不同业务应用的数据不能用多个数据库隔离,而应该使用不同的redis实例。
     
     
    第3章 入门
    3.1 热身
    redis命令不区分大小写。所有redis命令都是原子操作。
    常用命令:keys、exists、set、del、type。
     
    3.2 字符串
    value最大512MB。
    命令:
    set key value;
    get key;
    incr key;
    incrby key increment;
    decr key;
    decrby key increment;
    incrbyfloat key increment;
    append key value;
    strlen key;
    mget key [key ..]
    getbit key offset;
    setbit key offset value;
    bitcount key [start] [end];
    bitop operation destkey key [key ...];
     
    3.2 散列
    hash由key、field字段、fieldValue字段值组成。不支持嵌套类型,fieldValue只能为字符串。一个hash散列只有一个key,而field数量最大支持2的32次方-1个。
    命令:
    hset key field value;
    hget key field;
    hmset key field value [field value ...];
    hmget key field [field ...];
    hgetall key;
    hexists key field;
    hsetnx key field value;
    hincrby key field increment;
    hdel key field [field ...]
    hkeys key;
    hvals key;
    hlen key;
     
    3.3 列表
    列表内部使用双向链表实现,在列表两端读写元素的时间复杂度为O(1),中间索引访问元素较慢。列表索引从0开始。数量最大支持2的32次方-1个。
    命令:
    lpush key value [value ...];
    rpush key value [value ...];
    lpop key;
    rpop key;
    llen key;
    lrange key start stop;
    lrem key count value;(count大于0从左删除;小于0从右删除;等于0删除所有)。
    lindex key index;(index大于0从左,小于0从右)。
    lset key index value;
    ltrim key start end;
    linsert key before|after pivot value;
    rpoplpush source destination;
     
    3.4 集合
    redis内部使用值为空的散列表(hash table)实现。时间复杂度O(1)。列表有序值不唯一,而集合无序值唯一。
    命令:
    sadd key member [member ...];
    srem key member [member ...];
    smembers key;
    sdiff key [key ...];
    sinter key [key ...];
    sunion key [key ...];
    scard key;
    sdiffstore destination key [key ...];
    sinterstore destination key [key ...];
    sunionstore destination key [key ...];
    srandmember key [count];
    spop key;
     
    3.5 有序集合
    在集合基础上,为每个元素都关联了一个分数,就成为有序集合。元素唯一,但分数可以相同。
    有序集合与列表的相同点:①都是有序的。②都可以获得某一范围的元素。
    两者区别为:
    ①访问速度:列表采用双向链表实现,两端快中间慢,适合“新鲜事”和”日志“等很少访问中间元素的应用。而有序集合采用散列表和跳跃表实现,访问中间元素也很快,时间复杂度O(log(N))。
    ②调整位置:列表不能简单的调整某个元素的位置,但有序集合可以(通过更改元素的分数)。
    ③内存耗费:有序集合比列表更耗费内存。
     
    命令:
    zadd key score member [score member ...];分数可以是整数和浮点数,+/-inf分别表示正负无穷。
    zscore key member;
    zrange key start stop [withscores] ;按升序。时间复杂度O(log n+m), n为集合基数,m为返回数。
    zrevrange key start stop [withscores] ;按降序。
    zrangebyscore key min max [withscores] [limit offset count];
    zrevrangebyscore key min max [withscores] [limit offset count];
    zincrby key increment member;
    zcard key;
    zcount key min max;
    zrem key member [member ...] ;
    zremrangebyrank key start stop;
    zremrangebyscore key min max;
    zrange key member;
    zrevrange key member;
    zinterstore destination numkeys key [key ...] [weights weight [weight ... ] ] [aggregate sum|min|max];
     
     
    第4章 进阶
    4.1 事务
    redis事务与命令都是原子的。命令为 multi ........ exec 。
    事务的原理:先将一个事务的命令发送给redis,然后再让redis依次执行这些命令。
    事务内命令得语法错误会拒绝执行,而运行错误会导致事务内正确的命令被执行。事务没有回滚功能,出现运行错误,得自己收拾烂摊子。
     
    WATCH命令:监控一个或多个键,一旦其中有一个键被修改或删除,之后得事务就不会执行。监控一直持续到EXEC命令。redis事务与RDBMS事务完全不同,就算事务MULTI后修改了监控键,事务也不会执行。
    UNWATCH命令:取消监控。
     
    4.2 过期时间
    expire命令:设置一个key的过期时间,到时间后redis会自动删除它。格式:expire key seconds。单位为秒。注意:因时间过期被自动删除,watch命令不认为键被改变。
    pexpire命令:与expire唯一区别是该命令单位为毫秒。
    ttl命令:查看一个key还有多久时间被删除。格式:ttl key。
    persist命令:取消键的过期时间设置。格式:persist key。
     
    4.3 排序
    sort命令:对列表类型、集合类型、有序集合类型的键(member)进行排序,默认升序。时间复杂度为O(n+m*log(m)),其中n表示要排序的元素个数,m表示要返回的元素个数。
    格式:sort key [DESC] [ALPHA] [LIMIT offset count ] 。
     
    by参数:格式:by 参考键。 参考键可以是字符串类型键、散列类型键的某个字段(表示为键名->字段名)。如果提供了by参数,sort命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序。
    如:sort key by key2:*->field1 desc 。参考键key2为散列类型。
    sort key by key2:* desc 。参考键key2为字符串类型。
     
    get参数:格式: get 参考键。规则与by一样。不影响排序,是sort命令返回结果不再是元素自身的值,而是get参数中指定的键值。可以有多个get参数,get # 返回元素本身的值。
    如:sort key by key2:*->field1 desc get key2:*->field2 get key2:*->field3。参考键key2为散列类型。
     
    store参数:格式:store key。会保存排序结果到key中。
     
    4.4 消息通知
    任务队列:使用列表类型的LPUSH和BRPOP命令来实现MQ。BRPOP比RPOP增加了当队列为空时阻塞功能,避免忙循环。
    优先级队列:使用BRPOP可以从左到右接收多个键的功能实现,格式:BRPOP key [key ...] timeout 。同时检测多个键,如果所有键都没元素则阻塞,否则从左到右取出第一个有值的键。
    发布/订阅模式:使用publish和subscribe命令。格式:publish channel message; subscribe channel 。
    publish命令这条接收到这条消息的订阅者数量,当没有订阅者时,消息不会持久化,直接丢失,后面的订阅者无法收到。
    subscribe可以订阅多个频道channel。客户端会进入订阅状态,只能执行四个命令:subscribe、unsubscribe、psubscribe、punsubscribe。订阅后会收到3种回复类型:subscribe、message、unsubscribe。
     
    按照规则订阅:psubscribe命令订阅指定的glob风格通配符格式规则。
     
    4.5 管道
    客户端与redis采用TCP协议连接,当不使用管道时,每个命令都必须等待上一条命令执行完并返回才能执行。时间序上是串行的。当使用管道pipelining后,通过管道可以一次性发送多条命令并在执行完后一次性将结果返回,但要求该组命令中各命令都不依赖于之前命令的执行结果。
    管道通过减少客户端与redis的通信次数来实现降低往返时延累计值的目的。
     
    4.6 节省空间
    采用两种方式:①精简键名和键值;②内部编码优化。
    内部编码方式的选择对开发者是透明的,redis会根据实际情况自动调整。
    object encoding命令:查看键的内部编码方式。格式:object encoding key。
    redisObject结构体:redis每一个键值都使用redisObject结构体保存,其中type字段表示键值的数据类型,分为5种:string、list、set、zset、hash。而encoding字段表示键值的内部编码方式,分为9种:raw、int、ht(hash table)、zipmap、linkedlist、ziplist、intset、skiplist、embstr。
     
    (1)字符串类型
    支持的三种内部编码方式:raw、int、embstr。
    raw内部编码:字符串采用 sdshdr 结构体存储。该结构体由redisObject的ptr字段指向。sdshdr结构体有三个字段:int len;int free;char buf [ ] 。占用内存计算公式:sizeof(redisObject)+ sizeof(sdshdr)+ strlen(value)。
    int内部编码:当value能用64位有符号整数表示时,prt字段为long型,不需要sdshdr结构体,从而节省空间。另外因为redis启动后会预先建立1万个分别存储0~9999这些数字的redisObject类型变量做为共享对象,当存储小数字时无需创建redisObject对象,因此非常节省空间。
    embstr内部编码:redis3.0引入,与raw的唯一区别是:sdshdr结构体空间分配紧跟在redisObject之后。因处于同一块连续的内存空间,分配和释放时内存操作次数从2次减少到1次,另外内存连续方便操作系统缓存。
     
    (2)散列类型
    支持的两种内部编码方式:ht、ziplist。
    ht内部编码:采用hash table,取值时间复杂度O(1)。字段和字段值都用redisObject存储。
    注意:redis的键值对也是采用散列表实现,其中键不使用redisObject存储,因此整形键和字符串键占用空间没太大差别,不对键名优化是因为绝大多数场景键名都不会是纯数字。
    ziplist内部编码:一种紧凑的编码格式,牺牲了部分读取性能来换取极高的空间利用率。适合在元素较少时使用。
     
    (3)列表类型
    支持的两种内部编码方式:linkedlist、ziplist、quicklist(最新版)。
    linkedlist内部编码:采用双向链表。
    quicklist内部编码:linkedlist和ziplist的结合,最新版本引入。原理是将一个长列表分成若干个以链表形式组织的ziplist。从而达到减少空间占用的同时提升ziplist编码的性能效果。
     
    (4)集合类型
    支持的两种内部编码方式:ht、intset。
    intset内部编码:以有序存储整数元素,二分查找效率很高,但元素较多时每次修改都会调整其他元素的内存位置,性能较差。当集合中所有元素都是整数且个数小于配置文件中指定数量(默认512)时采用此编码。每个元素占用大小默认是int16,可以升级为int32或int64。
     
    (5)有序集合类型
    支持的两种内部编码方式:skiplist、ziplist。
    skiplist内部编码:采用散列表和跳跃列表(skiplist)两种数据结构来存储。
    散列表用来存储元素值与元素分数的映射关系,以实现O(1)时间复杂度的ZSCORE等命令。
    跳跃表用来存储元素的分数及其到元素值的映射以实现排序的功能。
    元素值使用redisObject存储,元素分数使用double存储。
    ziplist内部编码:有序集合按照“元素1的值,元素1的分数,元素2的值,元素2的分数”的顺序排列,并且分数是有序的。
     
     
    第5章 持久化
    5.1 RDB方式
    rdb持久化是通过快照(snapshot)完成的,当符合一定条件时redis会自动将内存中的所有数据生成一份副本并存储在硬盘上。redis默认持久化方式为RDB。触发快照的几种情况如下:
    ①根据配置规则进行自动快照。
    在配置文件中定义格式:save M N 。每当时间窗口M秒内被更改的键的个数大于N时,触发快照。
    ②用户执行SAVE或BGSAVE命令。
    SAVE命令:同步操作,会阻塞所有客户端的请求,生产环境慎用。
    BGSAVE命令:异步操作,不会阻塞请求。可以用LASTSAVE命令查看最近一次成功快照时间。
    ③执行FLUSHALL命令。
    会清除数据库中的所有数据。当配置了自动快照时(不管条件是否满足)才会触发快照;没配置则不会快照。
    ④执行复制(replication)时。
     
    5.2 RDB快照原理
    快照文件默认存储到当前redis进程的工作目录的dump.rdb文件中。配置文件参数为dir和dffilename。
    快照过程如下:
    ①redis使用fork函数复制一份当前进程(父进程)的副本(子进程)。
    ②父进程继续接收客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件。
    ③当子进程写完所有数据后会用临时文件替换旧的RDB文件,至此一次快照操作完成。
     
    linux操作系统在执行fork函数时,会使用写时复制(copy on write)策略:即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时,操作系统会将该数据复制一份以保证子进程数据不受影响。
    写时复制策略实际占用的内存量并不会增加一倍。rdb文件是经过压缩的二进制数据。
    RDB持久化缺点:一旦redis异常退出,就会丢失最后一次快照以后更改的所有数据。绝大多数场景丢失最近几秒钟无所谓,如果数据相对重要且为非临时数据,推荐用AOF持久化。
     
    5.3 AOF方式
    aof持久化是将redis执行的每一条写命令追加到硬盘文件中,这一过程显然会降低redis的性能。
    通过配置文件 appendonly yes 参数启用AOF。文件路径和文件名配置与RDB相同,文件名默认为appendonly.aof 。
    aof文件以纯文本方式记录了redis执行的写命令。
    aof文件优化:需要重写来删除冗余命令。每当达到一定条件时(配置文件两参数),redis就会自动重写AOF文件。还可以用BGREWRITEAOF命令手动执行AOF重写。
    AOF持久化优点:不会丢失数据。
    AOF持久化缺点:文件存在冗余命令需要重写,启动时载入速度比RDB慢。
     
    同步硬盘数据:由于操作系统的缓存机制,数据并没有真正写入到硬盘,而是写到硬盘缓存。默认每30秒执行一次同步操作,当系统崩溃时会丢失数据。
    redis配置文件appendfsync everysec参数用来设置同步规则。取值:always,everysec(默认),no(交由操作系统每30秒一次)。
     
    redis支持RDB和AOF同时开启使用。
     
     
    第6章 集群
    6.1 复制
    复制采用master / slave 结构,一个master提供读写,一或多个slave提供只读。INFO replication命令查看服务器复制信息。
    配置方式有如下:
    ①从服务器的配置文件,格式:slaveof masterHost masterPort。
    ②启动命令行参数,如:$ redis-server --port 6390 --slaveof 127.0.0.1 6379 。
    ③运行时slaveof命令,如:slaveof 127.0.0.1 6379 。
    该命令会停止和原来数据库同步转而与新数据库同步。slaveof no one 命令将自己提升为master服务器。
     
    复制原理:
    (1)复制初始化阶段
    从服务器启动后,向主服务器发送SYNC命令,主服务器收到SYNC命令后开始后台保存快照,同时将保存期间接收到的命令缓存起来;当快照完成后,将快照文件和缓存命令发送给从服务器。从服务器载入快照文件并执行收到的缓存命令。当主从服务器连接断开重连后,采用增量复制。
    (2)复制同步阶段
    复制初始化阶段结束后,主数据库执行的任何能导致数据变化的命令都会异步传送给从数据库。直到主从关系终止为止。
    乐观复制:redis采用乐观复制策略,容忍在一定时间内主从数据库内容是不同的,但是两者的数据会最终同步。
     
    图结构:从服务器还可以做为主服务器,有自己的从服务器。
    读写分离:一主多从适合读多写少的场景。当一主不能满足需求时,需要采用redis3.0推出的集群功能。
    从数据库持久化:持久化相对耗时,为了提高性能,可以禁用主数据库持久化。当从数据库挂了后,只需重启从数据库即可;当主数据库挂了后,需严格按照以下两步进行:①slaveof no one 将从提升为主。②启动之前崩溃的主,用slaveof 命令将其设为新主的从。手工操作容易出错,应该用哨兵。
     
    增量复制概念:
    Run ID:运行ID。每个实例重启后会生成新的运行ID,从数据库会存储主数据库的运行ID。
    backlog:积压队列。复制同步阶段,主每将一个命令发送给从时,会同时将该命令存放在积压队列中,并记录当前偏移量范围。积压队列是固定长度的循环队列,默认1MB。
    偏移量:从收到主发送的命令时,会记录下该命令的偏移量。
    PSYNC命令:从给主发送。命令格式为:“psync 主数据库的运行ID 断开前最新的命令偏移量”。redis 2.8版后将复制初始化阶段的sync命令改成psync命令了。
     
    增量复制原理:
    ①主收到从发来的psync命令后,会判断收到的运行ID和自己的运行ID是否相等。
    ②当运行ID相等后,再判断收到的从在断开前最后一次同步成功的命令偏移量是否在当前主的积压队列里。
    只有当①和②都满足时,才能增量复制,否则会全量同步。
     
    6.2 哨兵
    哨兵目的是为了实现自动化的系统监控和故障恢复能力。哨兵功能如下:
    ①监控主数据库和从数据库是否正常运行。
    ②主数据库出现故障时自动将从数据库转换为主数据库。
     
    哨兵之间可以互相监控,一对主从系统可以拥有多个哨兵,一个哨兵也可以监控多对主从系统。
    配置文件为sentinel.conf,启动命令为redis-sentinel。
    配置参数格式:“sentinel monitor 主从系统自定义名称 masterHost|ip masertPort quorum ”。
    quorum:最低通过票数。表示执行故障恢复前至少需要几个哨兵节点同意。
     
    哨兵模式缺点:①当只有一个哨兵时,哨兵本身会存在单点故障;②集群总存储量存在木桶短板效应。即集群中每个节点都拥有全部数据,集群总数据存储量受限于可用存储内存最小的节点。旧版采用客户端分片来解决水平扩容问题。
     
    6.3 集群
    redis3.0后引入cluster集群功能。当不需要数据分片或已经在客户端进行分片的场景下,使用哨兵模式足够;而当需要水平扩容时,cluster是最好的选择。
    集群优点:网络分区后提供一定的可访问性;支持主数据库的故障恢复。
    集群缺点:多键命令mget时,如果每个键数据不在同一个节点,则会提示错误;只能使用0号字典数据库,不能select切换字典数据库。
     
    (1)配置集群
    至少需要3个主数据库才能运行,每个节点的配置参数为“cluster-enabled yes”。
    info cluster命令:查看集群状态。
    cluster nodes命令:查看集群所有节点信息。
    cluster meet命令:节点的增加,告诉当前节点指定ip和端口上运行的节点也是集群的一部分。格式:“cluster meet ip port”。
    cluster replicate命令:将当前节点转换为从数据库并复制指定的主数据库。格式:“cluster replicate 主数据库运行ID”。
    redis-trib.rb 是一个ruby写的辅助工具,本质是通过执行redis命令来实现集群管理的任务。
     
    (2)插槽的分配
    增加一个新节点时,可以有两种选择:使用cluster replicate命令变成从数据库;也可以向集群申请分配插槽slot来变成主数据库运行。
    一个集群所有的键会被分成16384个插槽,每个主数据库负责处理一部分插槽。
    键与插槽关系:将键使用CRC16得到校验和,然后取对16384的余数。
    cluster slots命令:查看插槽分配情况。
    cluster addslots命令:为节点分配插槽。格式:cluster addslots slot1 [slot2] ... [slotN]。
    cluster getkeysinslot命令:获取插槽号的键列表。格式:cluster getkeysinslot 插槽号。
    cluster setslot命令:插槽迁移。
    migrate命令:将键迁移到目标节点。
     
    插槽迁移命令和步骤,假设把0号插槽从A节点迁往B节点:
    ①在B执行cluster setslot 0 importing A节点运行ID。
    ②在A执行cluster setslot 0 migrating B节点运行ID。
    ③执行cluster getkeysinslot 0,获取0号插槽的键列表。
    ④对第三步获取的每个键执行migrate命令,将其从A迁移到B。
    ⑤执行cluster setslot 0 node B节点运行ID,完成迁移。
     
    命令请求重定向:当客户端向集群任意节点发送命令请求时,如果该键没有在该节点上,则返回MOVE重定向请求,告诉客户端键由哪个节点负责。实际使用时建议客户端缓存该MOVE信息,以避免二次请求。
    redis-cli脚本可以用 -c 参数来启用集群模式下的命令重定向支持。
     
    (3)故障恢复
    PFAIL:疑似下线。集群中的每个节点每隔1秒钟就会随机选择5个节点,然后选择其中最久没有响应的节点发送ping命令,如果在规定时间内没有收到回复,则认为目标节点疑似下线。
    疑似下线与哨兵的主观下线类似,都是表示某一节点从自身角度认为目标节点是下线状态。
    当一定数量的节点都认为疑似下线时,集群中所有节点才能都认为目标节点下线。
    当集群中的主数据库下线时,就会出现一部分插槽无法写入问题,集群会通过故障恢复操作把多个从数据库中的一个变成主数据库。具体选择哪个从数据库变成主数据库的过程与哨兵模式中选择领头哨兵过程一样,都是采用Raft算法。
     
    Raft算法过程如下:
    ①发现其主数据库下线的从数据库(A)向集群中所有节点发送请求,要求对方选自己为主数据库。
    ②如果收到请求的节点没有选过其他人,则会同意将A设置为主数据库。
    ③如果A发现有超过集群中节点总数一半的节点同意选自己为主数据库,则A成功变为主数据库。
    ④当没有过半选票时,则在等待一个随机时间后重新发起参选请求,进行下一轮选举,直到选举成功。
     
    当从被选举为主数据库以后,会通过slaveof no one将自己转换为主数据库,并将旧主数据库的插槽转换给自己负责。
    当一个至少负责一个插槽的主数据库下线且没有响应的从数据库进行故障恢复,则整个集群默认下线状态无法工作。可以修改配置参数cluster-require-full-coverage no(默认yes)来运行集群继续工作。
    因此尽量保证每个主数据库运行在不同IP上,并且每一个从数据库都不和其主数据库运行在同一个IP上。
     
     
    第7章 管理
    7.1 安全
    redis运行在可信环境。可以采用如下措施保证安全:
    ①配置文件参数“bind ip” ,绑定客户端ip,缺点是只能绑定一个ip。
    ②自由设置访问规则可以通过防火墙完成。
    ③配置文件参数“requirepass pass” ,设置数据库密码。但可以被穷举破解。
    ④配置文件参数“rename-command FLUSHALL alias|"" ” ,命令重命名为复杂字符串,为空白则禁用命令。
     
    7.2 通信协议
    分为两种:简单协议、统一请求协议(可包含二进制字符,指定后面字符串长度)。
     
    7.3 管理工具
    redis-cli可以执行绝大多数redis命令,如:
    slowlog get命令:查看耗时命令日志。耗时时间默认是微秒。
    monitor命令:实时监控redis执行的所有命令,性能会损耗一半,只能用来调试。
     
    phpRedisAdmin:PHP开发的网页端管理工具,使用“keys * ”命令获取键列表,然后树状展示。
    rdbtools:redis快照文件解析器,用来分析每个键的空间占用情况。
  • 相关阅读:
    递推 hdu 1396
    递推 hdu 3411
    Eclipse中git上如何把自己的分支保存到远端
    api-gateway-engine知识点(1)
    Java知识点ArrayList
    如何利用VMware安装XP系统
    IOP知识点(1)
    Eclipse如何导入DemoWeb.rar
    mysql忘记root密码
    实习培训——Java多线程(9)
  • 原文地址:https://www.cnblogs.com/fhwup/p/8587600.html
Copyright © 2011-2022 走看看