一、初始Redis

1、Redis特性与优点

  • 速度快。redis所有数据都存放于内存;是用C语言实现,更加贴近硬件;使用了单线程架构,避免了多线程竞争问题
  • 基于键值对的数据结构,支持的数据结构丰富。它主要提供了5种数据结构: 字符串、 哈希、 列表、 集合、 有序集合, 同时在字符串的基础之上演变出了位图(Bitmaps) 和HyperLogLog两种神奇的“数据结构”, 并且随着LBS(Location Based Service, 基于位置服务) 的不断发展, Redis3.2版本中加入有关GEO(地理信息定位) 的功能
  • 功能丰富。键过期功能,用来实现缓存;提供了发布订阅功能,实现消息系统;支持Lua脚本;提供简单的事务功能;提供了Pipeline,一次性将一批命令传给redis服务器,减小了网络开销
  • 简单,稳定。源码少,3.0版本源代码只有5w行左右
  • 客户端语言多。
  • 持久化。两种持久化方式: RDB和AOF
  • 主从复制。复制功能是分布式Redis的基础
  • 高可用和分布式。2.8版本提供了高可用实现Redis Sentinel, 它能够保证Redis节点的故障发现和故障自动转移。 Redis从3.0版本正式提供了分布式实现Redis Cluster, 它是Redis真正的分布式实现, 提供了高用、 读写和容量的扩展性。

2、应用场景

  • 缓存。
  • 排行榜系统。redis提供了列表和有序集合支持。
  • 计数器应用。
  • 社交网络。
  • 消息队列系统。redis提供了订阅发布和阻塞队列功能,虽然和专业的消息队列软件比不够强大,但是可以满足一般的消息队列需求

3、redis常用操作

  • 服务端启动。配置文件启动,redis-server  /opt/redis/redis.conf
  • 客户端启动。redis-cli -h 127.0.0.1  -p 6379
  • 停止redis服务。redis-cli shutdown nosave|save  关闭过程:断开与客户端的连接;持久化文件生成。不建议kill -9 暴力杀进程,极端下会造成丢失数据的情况

二、API的理解和使用

1、全局命令

  • 查看所有键。【keys *】会遍历所有键,禁用
  • 键总数。【dbsize】不会遍历所有键,直接获取redis内置的键总数
  • 检查键是否存在。【exists key】存在返回1,不存在返回0
  • 删除键。【del key】返回结果为成功删除的键的个数
  • 设置键过期。【expire key seconds】秒级别;【expireat key timestamp 】秒级时间戳;【pexpire key milliseconds】毫秒级过期 ;【pexpireat key milliseconds-timestamp 】毫秒级时间戳
  • 检查键过期。【ttl key】返回键的剩余过期时间,秒级;【pttl key】毫秒级;-1,键没设置过期时间;-2 键不存在;
  • 清除过期时间。【persist】;对于字符串类型键,执行set命令也会去掉过期时间;setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间
  • 查看键的类型。【type key】键不存在,返回none
  • 键重命名。rename key newkey。如果在rename newkey之前,newkey已经存在,newkey的值会被覆盖。防止被覆盖,提供了renamenx命令。重命名期间会删除原来的key,如果key对应的值过大,存在阻塞redis的可能。
  • 随机返回一个键。randomkey。

2、数据结构和内部编码

每种数据结构都有底层的内部编码实现,而且是多种实现,redis会在合适的场景下选择合适的内部编码。

3、单线程架构

  • Redis使用了单线程架构和IO多路复用模型(epoll作为多路复用技术的实现,非阻塞IO)来实现。
  • 每次客户端的请求都会经过发送命令、执行命令、返回结果三个阶段。
  • 所有客户端命令都会放入到同一个队列中,然后逐个被执行。
  • 单线程避免了线程切换和竞态产生的消耗

4、字符串,最大不能超过512M

(1)命令

  • 设置值。set key value [ex seconds] [px milliseconds] [nx|xx]。其中setnx可以作为分布式锁的实现
  • 获取值。 get key。不存在返回 nil(空)
  • 批量设置值,mset key value [key value ...] ;批量获取值,mget key [key ...] 。一次请求的网络时间大于命令处理时间。学会使用批量操作,能减少大量的网络消耗。提高业务处理效率。但是一次批量操作过多,有可能导致Redis阻塞或者网络拥堵
  • 计数。incr、decr、incrby、decrby、incrbyfloat。很多其他的语言和存储系统通过cas实现计数,会有一定的cpu开销。redis单线程模型,完全不存在这个问题。
  • 追加值。append key value。
  • 字符串长度。strlen key。每个中文占用三个字节,也就是三个长度。
  • 设置并返回原值。getset key value
  • 设置指定位置的字符。setrange key offset value
  • 获取部分字符串。getrange key start end。时间复杂度O(n)

(2)内部编码,有三种

  • int。8个字节的长整型
  • embstr。小于等于39个字节的字符串
  • raw。大于39个字节的字符串

(3)应用场景

  • 缓存
  • 计数
  • 共享session
  • 限速。每个用户的请求频率,每个ip的请求频率

5、哈希

(1)命令

  • 设置field值。hset key field value。hsetnx,和setnx作用相同,只不过作用域由键变为field
  • 获取field值。hget key field。如果field不存在,返回nil
  • 删除field。hdel key field [field ...]
  • 计算field个数。hlen key
  • 批量设置和获取field-value。hmget key field [field ...] ;hmset key field value [field value ...] 。
  • 判断field是否存在。hexists key field。存在返回1,不存在返回0
  • 获取所有field。hkeys key
  • 获取所有value。hvals key
  • 获取所有field-value。hgetall key。如果获取的元素比较多,可能会阻塞Redis。
  • hincrby key field;hincrbyfloat key field
  • 计算value的字符串长度。hstrlen key field

(2)内部编码

  • ziplist(压缩列表)。当哈希类型元素个数小于hash-max-ziplist-entries
    配置(默认512个) 、 同时所有值都小于hash-max-ziplist-value配置(默认64
    字节) 时, Redis会使用ziplist作为哈希的内部实现, ziplist使用更加紧凑的
    结构实现多个元素的连续存储, 所以在节省内存方面比hashtable更加优秀
  • hashtable(哈希表)。当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现

6、列表。列表中的每个字符串称为元素(element),一个列表最多存储2^32-1个元素。可以充当栈和队列,比较灵活。列表中的元素是有序的且可重复的,可以通过下标获取某个元素

(1)命令

  • 添加操作。从右边插入数据 rpush key value [value ...];从左边插入数据  lpush key value [value ...];向某个元素前或后插入数据 linsert key before|after pivot value
  • 查找。获取指定范围的元素列表 lrange key start end(包含end);获取指定下标的元素 lindex key index(-1为最后一个元素);获取列表长度 llen key
  • 删除。从左侧弹出元素 lpop key;从右侧弹出元素 rpop key;删除指定元素 lrem key count value(count>0 从左到右,删除最多count个元素;count<0 从右到左;count=0,删除所有);按照索引范围修剪列表 ltrim key start end
  • 修改。修改执行索引下标的元素 lset key index newValue;
  • 阻塞操作。blpop key [key ...] timeout ;brpop key [key ...] timeout 。需要注意两点:第一点,如果blpop多个key,一旦有一个键能弹出元素,立刻给客户端返回;第二点,如果多个客户端对同一个key执行blpop,那么最先执行blpop的客户端可以获取到弹出的值。

(2)内部编码

  • ziplist(压缩列表)。当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用
  • linkedlist(链表):当列表类型无法满足ziplist的条件时, Redis会使用linkedlist作为列表的内部实现。
  • Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现

(3)应用场景

  • 消息队列。Redis的lpush+brpop命令组合即可实现阻塞队列, 生产者客户端使用lrpush从列表左侧插入元素, 多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素, 多个客户端保证了消费的负载均衡和高可用性。
  • 文章列表。哈希存储每篇文章的详细信息。列表存储每个人的文章列表。支持分页获取,如果列表较大,获取列表中间元素的性能会变差,可以使用redis3.2版本的quicklist内部编码实现。
  • lpush+lpop=Stack(栈) ;lpush+rpop=Queue(队列) ;lpush+brpop=Message Queue(消息队列,阻塞队列); lpush+ltrim=Capped Collection(有限集合)

7、集合(set)。不允许有重复元素,无序,不能通过索引下标获取元素。一个集合最多可以存储2^32-1个元素。支持集合内的增删改查,集合的交集、并集、差集。

(1)命令。集合内操作。

  • 添加元素。sadd key element [element ...]
  • 删除元素。srem key element [element ...]
  • 计算元素个数。scard key 。时间复杂度O(1),不会遍历集合。
  • 判断元素是否在集合中。sismember key element 。在集合内返回1,不在返回0
  • 随机从集合中返回指定个数元素。srandmember key [count]
  • 从集合随机弹出指定个数的元素。spop key  [count]
  • 获取所有元素。smembers key 。如果元素过多,存在阻塞redis的可能。可以用sscan来完成。

(2)命令,集合间操作。

  • 求多个集合的交集。sinter key [key ...]
  • 求多个集合的并集。suinon key [key ...]
  • 求多个集合的差集。sdiff key [key ...]
  • 将集合操作的结果保存。sinterstore/suionstore/sdiffstore  destination key [key ...]

(3)内部编码

  • intset(整数集合) : 当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时, Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
  • hashtable(哈希表) : 当集合类型无法满足intset的条件时, Redis会使用hashtable作为集合的内部实现。

(4)使用场景

  • 标签(tag)。使用sinter命令计算用户的共同兴趣。
  • 生成随机数,抽奖

8、有序集合。元素可以排序,每个元素设置一个分数(score)作为排序的依据。集合成员不能重复,但是每个元素的score可以重复

(1)命令。集合内的命令。

  • 添加成员。zadd key score member [score member ...]

    ·Redis3.2为zadd命令添加了nx、 xx、 ch、 incr四个选项:
    ·nx: member必须不存在, 才可以设置成功, 用于添加。
    ·xx: member必须存在, 才可以设置成功, 用于更新。
    ·ch: 返回此次操作后, 有序集合元素和分数发生变化的个数
    ·incr: 对score做增加, 相当于后面介绍的zincrby。

  • 计算成员个数。zcard key
  • 计算某个成员的分数。zscore key member
  • 计算成员排名。zrank key member (分数由低到高);zrevrank key member (分数由高到低)
  • 删除成员。zrem key member [member ...]
  • 增加成员分数。zincrby key increment member
  • 返回指定排名范围的成员。zrange key start end [withscores] (排名由低到高);zrevrange key start end [withscores] (排名由高到低)
  • 返回指定分数范围的成员。zrangebyscore key min max [withscores] [limit offset count] 。其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 例如
    下面操作从低到高返回200到221分的成员, withscores选项会同时返回每个
    成员的分数。 [limit offset count]选项可以限制输出的起始位置和个数 。同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和
    +inf分别代表无限小和无限大。
  • 返回指定分数范围成员个数。zcount key min max
  • 删除指定排名内的升序元素。zremrangebyrank key start end
  • 删除指定分数范围的成员。zremrangebyscore key min max

(2)命令,集合间的操作

  • 交集。zinterstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max]
  • destination: 交集计算结果保存到这个键。
    ·numkeys: 需要做交集计算键的个数。
    ·key[key...]: 需要做交集计算的键。
    weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中
    的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
    ·aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、
    min(最小值) 、 max(最大值) 做汇总, 默认值是sum
  • 并集。zunionstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max] 。

(3)内部编码

  • ziplist(压缩列表) : 当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个) , 同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节) 时, Redis会用ziplist来作为有序集合的内部实现, ziplist
    可以有效减少内存的使用
  • skiplist(跳跃表) : 当ziplist条件不满足时, 有序集合会使用skiplist作为内部实现, 因为此时ziplist的读写效率会下降。

(4)使用场景

  • 排行榜

9、键管理

(1)键迁移

  • dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能, 整个迁移的过程分为两步:
    1) 在源Redis上, dump命令会将键值序列化, 格式采用的是RDB格式。
    2) 在目标Redis上, restore命令将上面序列化的值进行复原, 其中ttl参数代表过期时间, 如果ttl=0代表没有过期时间。
    第一, 整个迁移过程并非原子性的,而是通过客户端分步完成的。 第二, 迁移过程是开启了两个客户端连接, 所以dump的结果不是在源Redis和目标Redis之间进行传输

  • migrate命令也是用于在Redis实例间进行数据迁移的, 实际上migrate命令就是将dump、 restore、 del三个命令进行组合, 从而简化了操作流程。
    migrate命令具有原子性, 而且从Redis3.0.6版本以后已经支持迁移多个键的功能, 有效地提高了迁移效率, migrate在10.4节水平扩容中起到重要作用。
    第一, 整个过程是原子执行的, 不需要在多个Redis实例上开启客户端的, 只需要在源Redis上执行migrate命令即可。
    第二, migrate命令的数据传输直接在源Redis和目标Redis上完成的。
    第三, 目标Redis完成restore后会发送OK给源Redis, 源Redis接收后会根据migrate对应的选项来决定是否在源Redis上删除对应的键。
    migrate host port key|"" destination-db timeout [copy] [replace] keys key1 key2 key3
    key|""。要迁移的键,多个键此处为""。
    keys key1 key2 key3。迁移多个键

(2)键遍历

  • 全量遍历键。keys pattern。支持pattern匹配。如果非要使用,在一个不对外提供服务的Redis从节点上执行, 这样不会阻塞到客户端的请求, 但是会影响到主从复制
  • 渐进式遍历。每次执行scan, 可以想象成只扫描一个字典中的一部分键, 直到将字典中的所有键遍历完毕。
    scan cursor [match pattern] [count number]
    ·cursor是必需参数, 实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
    ·match pattern是可选参数,它的作用的是做模式的匹配,这点和keys的模式匹配很像。
    ·count number是可选参数,它的作用是表明每次要遍历的键个数,默认值是10,此参数可以适当增大。
    除了scan以外,Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、 smembers、 zrange可能产生的阻塞问题,对应的命令分别是hscan、 sscan、 zscan,它们的用法和scan基本类似
    渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无瑕,如果在scan的过程中如果有键的变化(增加、 删除、 修改),scan并不能保证完整的遍历出来所有的键

(3)数据库管理

  • 切换数据库。select db。各个数据库之间没有联系,可以存在相同的键。redis是单线程的,多个数据库使用同一个cpu,彼此至今还是会受影响。
  • 清除数据库。flushdb/flushall。如果当前数据库键值数量比较多,flushdb/flushall存在阻塞Redis的可能性

三、小功能,大用处

1、慢查询分析

(1)慢查询只是统计的命令执行的时间,不包括客户端发送命令、命令排队、返回结果的时间。

  • slowlog-log-slower-than预设阀值,它的单位是微秒(1秒=1000毫秒=1000000微秒),默认值是10000。slowlog-log-slower-than=0会记录所有的命令,slowlog-log-slowerthan<0对于任何命令都不会进行记录。
  • 实际上Redis使用了一个列表来存储慢查询日志,slowlog-max-len就是列表的最大长度
  • 在Redis中有两种修改配置的方法, 一种是修改配置文件, 另一种是使用config set命令动态修改。如果要Redis将配置持久化到本地配置文件,需要执行config rewrite命令
  • 获取慢查询日志。slowlog get [n] ,n可以指定条数。每个慢查询日志有4个属性组成, 分别是慢查询日志的标识id、 发生时间戳、 命令耗时、 执行命令和参数
  • 获取慢查询日志当前的列表长度,slowlog len。
  • 慢查询日志列表清空。slowlog reset。

(2)最佳实践

  • slowlog-max-len配置建议: 线上建议调大慢查询列表, 记录慢查询时Redis会对长命令做截断操作, 并不会占用大量内存。 增大慢查询列表可以
    减缓慢查询被剔除的可能, 例如线上可设置为1000以上。
  • slowlog-log-slower-than配置建议: 默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。 由于Redis采用单线程响应命令, 对于高流量的场景, 如果命令执行时间在1毫秒以上, 那么Redis最多可支撑OPS不到1000。 因此对于高OPS场景的Redis建议设置为1毫秒。
  • 慢查询只记录命令执行时间, 并不包括命令排队和网络传输时间。 因此客户端执行命令的时间会大于命令实际执行时间。
  • 定期执行slow get命令将慢查询日志持久化到其他存储中(例如MySQL) , 然后可以制作可视化界面进行查询

2、Redis  Shell

(1)Redis-cli

  • -h 服务端ip
  • -p 端口
  • -r (repeat)将命令执行多次。redis-cli -r 3 ping
  • -i (interval)每个几秒执行几次。redis-cli -r 5 -i 1 ping
  • -a (auth)密码
  • --slave。将当前客户端模拟成服务端的从节点。
  • --rdb。生成RDB持久化文件,保存到本地。可用来做持久化文件的定期备份
  • --eval。执行lua脚本
  • --latency。测试客户端到目标redis服务的网络延迟;--latency-history,每隔多久输出一次网络延迟;--latency-dist,使用统计图表的形式从控制台输出延迟统计信息
  • --stat。实时获取redis的重要统计信息。key的数量、内存占用量、客户端数量、请求数量、连接数量

(2)redis-server

  • redis-server --test-memory 1024。检测当前操作系统能否稳定地分配指定容量的内存给Redis。整个内存检测的时间比较长。该功能更偏向于调试和测试

(3)redis-benchmark。可以为Redis进行基准性能测试

  • -c。代表客户端的并发数量(默认是50)
  • -n(num) 。代表客户端请求总量(默认是100000) 。redis-benchmark-c100-n20000代表100各个客户端同时请求Redis, 一共执行20000次
  • -r(random) 选项, 可以向Redis插入更多随机的键
  • --csv选项会将结果按照csv格式输出, 便于后续处理, 如导出到Excel等

3、Pipeline。

(1)概述。Redis客户端执行一次命令,需要经历发送命令、命令排队、命令执行、返回结果四个过程。4个过程统称为一次Round Trip Time ,往返时间。

Pipeline(流水线)机制能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端

(2)性能测试。执行速度一般比逐条执行要快,客户端和服务端的网络延迟越大,Pipeline的效果越明显

(3)原生批量命令和Pipeline对比。

  • 原生批量命令是原子的,Pipeline是非原子的。
  • 原生批量命令是一个命令对应多个key,Pipeline支持多个命令。
  • 原生批量命令是Redis服务端支持实现的,而Pipeline需要服务端和客户端的共同实现。

(4)最佳实践

  • 一次组装Pipeline数据量过大, 一方面会增加客户端的等待时间, 另一方面会造成一定的网络阻塞, 可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成
  • Pipeline只能操作一个Redis实例

4、事务与lua

(1)事务

  • 简单事务功能,multi和exec命令之间的命令,能够原子顺序执行。如果事务之中存在命令拼写错误,整个事务不会执行;如果存在运行时错误,事务并不会回滚
  • watch命令,是事务执行之前,确保事务中的key没有被其他客户端修改过,修改过的话就不执行事务。类似于乐观锁
  • Redis的事务比较简单,无法保证事务回滚,无法实现事务内命令之间的逻辑运算

(2)lua用法简述。由c语言实现,许多应用选用它作为脚本语言。尤其在游戏领域

(3)Redis中使用lua

  • eval 执行lua脚本。eval 脚本内容 key个数 key列表 参数列表。对应 jedis.eval
  • evalsha 执行lua脚本。

    script load命令可以将脚本内容加载到Redis内存中,返回SHA1。对应jedis.scriptLoad
    evalsha 脚本SHA1值 key个数 key列表 参数列表

  • lua 的Redis API。

    使用redis.call函数实现对Redis的访问。例如:redis.call("set", "hello", "world");redis.call("get", "hello")
    还可以使用redis.pcall函数实现对Redis的调用,如果redis.call执行失败,那么脚本执行结束会直接返回错误,而redis.pcall会忽略错误继续执行脚本

  • 开发提示

    Lua可以使用redis.log函数将Lua脚本的日志输出到Redis的日志文件中,但是一定要控制日志级别。
    Redis3.2提供了Lua Script Debugger功能用来调试复杂的Lua脚本

  • Redis管理Lua脚本

    script load 将Lua脚本加载到Redis内存中
    scripts exists sha1 [sha1 …] 判断sha1是否已经加载到Redis内存中
    script flush 清除Redis内存已经加载的所有Lua脚本
    script kill 用于杀掉正在执行的Lua脚本。如果此时lua脚本阻塞redis了,可以使用此命令快速将脚本杀掉。如果正在执行的是写操作,script kill 将不会生效,只能通过shutdown save停掉redis服务。

5、Bitmaps

(1)概述。Bitmaps本身的数据结构就是字符串,支持对字符串的位进行操作。

(2)每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id

  • 设置值。setbit key offset value 。初始化的时候,如果偏移量非常大,整个初始化过程会非常慢,可能会造成redis阻塞。
  • 获取值。gitbit key offset 。返回0说明没有访问过
  • 获取Bitmaps指定范围值为1的个数 。bitcount [start][end] 。
  • Bitmaps间的运算 。bitop op destkey key[key....] 。做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中
  • 计算Bitmaps中第一个值为targetBit的偏移量。bitpos key targetBit [start] [end]

(3)Bitmaps分析

假设网站有1亿用户, 每天独立访问的用户有5千万, 如果每天用集合类型和Bitmaps分别存储活跃用户。
集合类型内存量:64位*50000000=400M。
Bitmaps内存量:1位*50000000=12.5M。

6、HyperLogLog

  • Redis中hyperloglog是用来做基数统计的,其优点是:在输入元素的数量或者体积非常非常大的时候,计算基数所需的空间总是固定的,并且是很小的。在Redis里面,每个Hyperloglog键只需要12Kb的大小就能计算接近2^64个不同元素的基数,但是hyperloglog只会根据输入元素来计算基数,而不会存储元素本身,所以不能像集合那样返回各个元素本身
  • pfadd key element [element …]。添加操作。如果添加成功返回1
  • pfcount key [key …]。求一个或多个key的基数,计算独立用户数
  • pfmerge destkey sourcekey [sourcekey ...]。合并。pfmerge可以求出多个HyperLogLog的并集并赋值给destkey
  • HyperLogLog内存占用量小得惊人, 但是用如此小空间来估算如此巨大的数据, 必然不是100%的正确, 其中一定存在误差率。 Redis官方给出的数字是0.81%的失误率

7、发布订阅

  • 发布消息。publish channel message 。
  • 订阅消息。subscribe channel [channel ...]。多个客户端订阅同一个频道,都会受到消息推送,类似于广播。
  • 取消订阅。unsubscribe [channel [channel ...]]
  • 按照模式订阅和取消订阅。psubscribe pattern [pattern...];punsubscribe [pattern [pattern ...]]
  • 订阅查询。pubsub channels [pattern],查看活跃的频道;pubsub numsub [channel ...],查看频道订阅数;pubsub numpat,查看模式订阅数

8、GEO

  • 增加地理位置信息。geoadd key longitude latitude member [longitude latitude member ...]。longitude、 latitude、 member分别是该地理位置的经度、 纬度、 成员。添加和更新都是用此命令,可以同时添加多个地理位置信息。
  • 获取地理位置信息。geopos key member [member ...]
  • 获取两个地址位置的距离。geodist key member1 member2 [unit]。unit代表返回结果的单位,m(meters) 代表米、km(kilometers) 代表公里、mi(miles) 代表英里、ft(feet) 代表尺
  • 获取指定位置范围内的地理信息位置集合。georadius key longitude latitude/georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist][withhash] [COUNT count] [asc|desc] [store key] [storedist key]
    都是以一个地理位置为中心算出指定半径内的其他地理信息位置

  • 获取geohash。geohash key member [member ...]。Redis使用geohash将二维经纬度转换为一维字符串。
    GEO的数据类型为zset, Redis将所有地理位置信息的geohash存放在zset中
    字符串越长, 表示的位置更精确, 例如geohash长度为9时, 精度在2米左右
    两个字符串越相似, 它们之间的距离越近, Redis利用字符串前缀匹配算法实现相关的命令
    geohash编码和经纬度是可以相互转换的

  • 删除地理位置信息。zrem key member。GEO没有提供删除成员的命令, 但是因为GEO的底层实现是zset, 所以可以借用zrem命令实现对地理位置信息的删除。

四、客户端

1、客户端通讯协议

通讯协议是建立在TCP协议之上的。Redis制定了RESP(REdis Serialization Protocol, Redis序列化协议) 实现客户端与服务端的正常交互

2、Java客户端Jedis

基本使用、Jedis连接池、Pipeline用法、执行Lua脚本等功能

3、客户端管理

(1)客户端API。client .. 命令

  • client list。列出与Redis服务端相连的所有客户端连接信息。输出结果的每一行代表一个客户端的信息, 可以看到每行包含了十几个属性
    id: 客户端连接的唯一标识, 这个id是随着Redis的连接自增的, 重启Redis后会重置为0
    addr: 客户端连接的ip和端口。
    fd: socket的文件描述符, 与lsof命令结果中的fd是同一个, 如果fd=-1代表当前客户端不是外部客户端, 而是Redis内部的伪装客户端。
    name: 客户端的名字
    age和idle分别代表当前客户端已经连接的时间和最近一次的空闲时间。当age等于idle时,说明连接一直处于空闲状态
    flag是用于标识当前客户端的类型, 例如flag=S代表当前客户端是slave客户端、 flag=N代表当前是普通客户端, flag=O代表当前客户端正在执行monitor命令

  • 输入缓冲区: qbuf(总容量)、 qbuf-free(剩余容量)。
    redis为每个客户端分配了输入缓冲区, 它的作用是将客户端发送的命令临时保存, 同时Redis从会输入缓冲区拉取命令并执行, 输入缓冲区为客户端发送命令到Redis执行命令提供了缓冲功能
    输入缓冲区会根据输入内容大小的不同动态调整, 只是要求每个客户端缓冲区的大小不能超过1G, 超过后客户端将被关闭。输入缓冲区过大主要是因为Redis的处理速度跟不上输入缓冲区的输入速度

  • 输入缓冲使用不当会产生两个问题:
    ·一旦某个客户端的输入缓冲区超过1G, 客户端将会被关闭。
    ·输入缓冲区不受maxmemory控制,一旦超过maxmemory限制, 可能会产生数据丢失、 键值淘汰、 OOM等情况

  • 监控输入缓冲区异常的方法有两种:
    通过定期执行client list命令,收集qbuf和qbuf-free找到异常的连接记录并分析,最终找到可能出问题的客户端。能精准定位客户端,但是执行速度较慢,频繁指定可能阻塞redis
    通过info命令的info clients模块,找到最大的输入缓冲区,client_biggest_input_buf代表最大的输入缓冲区,可以设置超过10M(阈值)就进行报警。定位不精准,执行速度快。

  • 输出缓冲区: obl、 oll、 omem。
    Redis为每个客户端分配了输出缓冲区, 它的作用是保存命令执行的结果返回给客户端, 为Redis和客户端交互返回结果提供缓冲。
    输出缓冲区由两部分组成: 固定缓冲区(16KB) 和动态缓冲区, 其中固定缓冲区返回比较小的执行结果, 而动态缓冲区返回比较大的结果
    client list中的obl代表固定缓冲区的长度, oll代表动态缓冲区列表的长度, omem代表使用的字节数
    监控输出缓冲区的方法同输入缓冲区监控方法

  • 输出缓冲区的容量可以通过参数client-outputbuffer-limit来进行设置,输出缓冲区也不会受到maxmemory的限制
    配置规则:client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
    <class>: 客户端类型, 分为三种。 a) normal: 普通客户端; b)slave: slave客户端, 用于复制; c) pubsub: 发布订阅客户端。
    <hard limit>: 如果客户端使用的输出缓冲区大于<hard limit>, 客户端会被立即关闭。
    <soft limit>和<soft seconds>: 如果客户端使用的输出缓冲区超过了<softlimit>并且持续了<soft limit>秒, 客户端会被立即关闭

  • 输出缓冲区出现异常的概率相对会比较大,如何预防;
    进行上述监控, 设置阀值, 超过阀值及时处理
    限制普通客户端输出缓冲区的, 把错误扼杀在摇篮中
    适当增大slave的输出缓冲区的,master节点写入较大, slave客户端的输出缓冲区可能会比较大, 一旦slave客户端连接因为输出缓冲区溢出被kill, 会造成复制重连
    限制容易让输出缓冲区增大的命令, 例如, 高并发下的monitor命令就是一个危险的命令。
    及时监控内存, 一旦发现内存抖动频繁, 可能就是输出缓冲区过大。

  • client setName和client getName。client setName用于给客户端设置名字, 这样比较容易标识出客户端的来源。
  • client kill ip:port。此命令用于杀掉指定IP地址和端口的客户端。
  • client pause timeout(毫秒)。client pause命令用于阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞。生产环境中,暂停客户端成本非常高
    该命令可以在如下场景起到作用:
    client pause只对普通和发布订阅客户端有效,对于主从复制(从节点内部伪装了一个客户端)是无效的,也就是此期间主从复制是正常进行的,此命令可以用来让主从复制保持一致
    client pause可以用一种可控的方式将客户端连接从一个Redis节点切换到另一个Redis节点。

  • monitor命令用于监控Redis正在执行的命令
    每个客户端都有自己的输出缓冲区, 既然monitor能监听到所有的命令, 一旦Redis的并发量过大,monitor客户端的输出缓冲会暴涨, 可能瞬间会占用大量内存

(2)客户端相关配置

  • Redis提供了maxclients参数来限制最大客户端连接数, 一旦连接数超过maxclients, 新的连接将被拒绝。
    可以通过info clients来查询当前Redis的连接数
    axclients默认值是10000,可以通过config set maxclients对最大客户端连接数进行动态设置

  • Redis提供了timeout(单位为秒)参数来限制连接的最大空闲时间,一旦客户端连接的idle时间超过了timeout,连接将会被关闭。默认的timeout是0,动态设置config set timeout 30
  • tcp-keepalive:检测TCP连接活性的周期,默认值为0,也就是不进行检测,如果需要设置,建议为60,那么Redis会每隔60秒对它创建的TCP连接进行活性检测,防止大量死连接占用系统资源
  • tcp-backlog:TCP三次握手后,会将接受的连接放入队列中,tcpbacklog就是队列的大小,它在Redis中的默认值是511。通常来讲这个参数不需要调整,但是这个参数会受到操作系统的影响

(3)客户端统计片段

info clients命令

  • connected_clients:代表当前Redis节点的客户端连接数,需要重点监控,一旦超过maxclients,新的客户端连接将被拒绝。
  • client_longest_output_list: 当前所有输出缓冲区中队列对象个数的最大值。
  • client_biggest_input_buf: 当前所有输入缓冲区中占用的最大容量。
  • blocked_clients:正在执行阻塞命令(例如blpop、 brpop、brpoplpush)的客户端个数。

info stats  命令

  • total_connections_received: Redis自启动以来处理的客户端连接数总数
  • rejected_connections: Redis自启动以来拒绝的客户端连接数, 需要重点监控

4、客户端常见异常

(1)无法从连接池获取到连接

  • JedisPool中的Jedis对象个数是有限的, 默认是8个。如果连接池中没有空闲Jedis对象,新的请求就需要进行等待(例如设置了maxWaitMillis>0)
    在maxWaitMillis时间内仍然无法获取到Jedis对象就会抛出异常:JedisConnectionException: Could not get a resource from the pool
    如果设置了blockWhenExhausted=false, 那么调用者发现池子中没有资源时, 会立即抛出异常不进行等待

  • 造成没有资源的原因非常多:
    客户端: 高并发下连接池设置过小, 出现供不应求
    客户端: 没有正确使用连接池, 比如没有进行释放
    客户端: 存在慢查询操作, 这些慢查询持有的Jedis对象归还速度会比较慢,造成池子满了
    服务端: 客户端是正常的, 但是Redis服务端由于一些原因造成了客户端命令执行过程的阻塞

(2)客户端读写超时,SocketTimeoutException: Read timed out

造成该异常的原因也有以下几种:
·读写超时间设置得过短。
·命令本身就比较慢。
·客户端与服务端网络不正常。
·Redis自身发生阻塞。

(3)客户端连接超时,SocketTimeoutException: connect timed out

造成该异常的原因也有以下几种:
连接超时设置得过短, 可以通过下面代码进行设置:jedis.getClient().setConnectionTimeout(time);
Redis发生阻塞, 造成tcp-backlog已满, 造成新的连接失败。
客户端与服务端网络不正常。

(4)客户端缓冲区异常

造成这个异常的原因可能有如下几种:
输出缓冲区满。
长时间闲置连接被服务端主动断开
不正常并发读写: Jedis对象同时被多个线程并发操作, 可能会出现上述异常

(5)Lua脚本正在执行

如果Redis当前正在执行Lua脚本, 并且超过了lua-time-limit, 此时Jedis调用Redis时, 会收到下面的异常。

JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

(6)Redis正在加载持久化文件

Jedis调用Redis时, 如果Redis正在加载持久化文件, 那么会收到下面的异常:
JedisDataException: LOADING Redis is loading the dataset in memory

(7)Redis使用的内存超过maxmemory配置

Jedis执行写操作时, 如果Redis的使用内存大于maxmemory的设置, 会收到下面的异常, 此时应该调整maxmemory并找到造成内存增长的原因
JedisDataException: OOM command not allowed when used memory > 'maxmemory'.

(8)客户端连接数过大

  • 如果客户端连接数超过了maxclients, 新申请的连接就会出现如下异常:
    JedisDataException: ERR max number of clients reached
  • 一般来说可以从两个方面进行着手解决:
    如果应用方是分布式结构的话,可以通过下线部分应用节点(例如占用连接较多的节点),使得Redis的连接数先降下来。从而让绝大部分节点可以正常运行,此时再通过查找程序bug或者调整maxclients进行问题的修复。
    果此时客户端无法处理, 而当前Redis为高可用模式(例如Redis Sentinel和Redis Cluster) , 可以考虑将当前Redis做故障转移。
    但是无论从哪个方面进行处理, 故障的快速恢复极为重要, 当然更为重要的是找到问题的所在, 否则一段时间后客户端连接数依然会超过maxclients。

5、客户端案例分析

(1)Redis主节点内存陡增

  • 服务端现象: Redis主节点内存陡增, 几乎用满maxmemory, 而从节点内存并没有变化
  • 客户端现象: 客户端产生了OOM异常, 也就是Redis主节点使用的内存已经超过了maxmemory的设置, 无法写入新的数据
  • 分析原因:
    确实有大量写入, 但是主从复制出现问题: 查询了Redis复制的相关信息, 复制是正常的, 主从数据基本一致。主从的键个数基本一致,使用dbsize命令
    其他原因造成主节点内存使用过大: 排查是否由客户端缓冲区造成主节点内存陡增, 使用info clients命令发现客户端输出缓冲区不正常,client_longest_output_list:225698
    通过client list命令找到omem不正常的连接, 一般来说大部分客户端的omem为0,redis-cli client list | grep -v "omem=0"
    最后发现是因为有客户端在执行monitor命令造成的

(2)客户端周期性超时

  • 客户端现象: 客户端出现大量超时, 经过分析发现超时是周期性出现的
  • 服务端现象: 服务端并没有明显的异常, 只是有一些慢查询操作
  • 原因分析:
    网络原因: 服务端和客户端之间的网络出现周期性问题, 经过观察网络是正常的
    客户端: 由于是周期性出现问题, 就和慢查询日志的历史记录对应了一下时间, 发现只要慢查询出现, 客户端就会产生大量连接超时, 两个时间点基本一致
    最终找到问题是慢查询操作造成的, 通过执行hlen发现有200万个元素, 这种操作必然会造成Redis阻塞, 有定时任务代码每5分钟执行一次hgetall操作

五、持久化

1、RDB。RDB持久化是把当前进程数据生成快照保存到硬盘的过程, 分为手动触发和自动触发

(1)触发机制

  • 手动触发:save命令。阻塞当前Redis服务器,直到RDB过程完成。线上环境不建议使用
  • 手动触发:bgsave命令。Redis进程执行fork操作创建子进程,RDB持久化由子进程负责。阻塞只发生在fork阶段,时间很短。
  • 自动触发:使用save相关配置:“save m n”,表示m秒内发生了n次数据修改,自动触发bgsave。
  • 自动触发:从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件,发给从节点
  • 自动触发:执行debuge reload命令重新加载Redis时,会自动触发save操作
  • 自动触发:执行shutdown命令时,如果没有开启AOF功能,会自动触发bgsave

(2)流程说明

  • 监测是否存在正在执行的子进程,如RDB/AOF进程,如果存在直接返回
  • 父进程创建子进程,fork过程中父进程会阻塞,通过info stats 命令查看latest_fork_usec选项查看最近一次fork的耗时,单位为微秒
  • 父进程fork完成后,返回“Backgroung saving started”,不在阻塞父进程,开始响应其他命令
  • 子进程创建RDB文件,根据父进程内存生成快照,完成后对原有文件进行原子替换
  • 子进程发送信号给父进程表示完成,父进程更新统计信息

(3)RDB文件的处理

  • RDB文件保存在dir配置的路径下,名字为dbfilename配置。可以通过命令动态修改 config set dir;config set dbfilename
  • Redis采用默认的LZF算法对生成的RDB文件进行压缩,默认开启,虽然压缩过程会消耗cpu,但是可以大幅降低压缩后的文件体积,方便保存到磁盘和通过网络传输到从节点,线上建议开启
  • 如果Redis加载损坏的RDB文件时拒绝启动,可以使用Redis提供的redis-check-dump工具检测RDB文件生成对应的错误报告

 (4)RDB的优缺点

  • 优点:RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的快照。适用于备份、全量复制等场景。
  • 优点:Redis加载RDB恢复数据的速度远远快于AOF的方式
  • 缺点:RDB没有办法做到实时持久化
  • 缺点:REB文件使用特定的二进制格式保存,存在老版本的服务器无法兼容新版RDB格式的问题。

2、AOF。以独立日志的形式记录每次写命令。重启时再重新执行AOF文件中的命令,达到恢复数据的目的

(1)配置

  • 开启AOF功能需要设置配置: appendonly yes, 默认不开启。 AOF文件名通过appendfilename配置设置, 默认文件名是appendonly.aof。 保存路径同RDB持久化方式一致, 通过dir配置指定。
  • 工作流程操作: 命令写入(append) 、 文件同步(sync) 、 文件重写(rewrite) 、 重启加载(load)

(2)命令写入缓存

  • 写入的命令内容直接是文本协议格式,会把内容追加到缓冲区。文本协议具有很好的兼容性
  • 先写入缓冲区,避免了每次的命令直接追加到硬盘,提高性能。

(3)文件同步到磁盘,由参数appendfsync控制。设置不同值含义不同

  • always。命令写入缓冲区之后,调用系统fsync命令同步到磁盘,fsync完成后线程返回
  • everysec。命令写入缓冲区之后,调用系统的write操作,write操作完成后线程返回。fsync同步操作由专门的线程每秒调用一次
  • no。命令写入缓冲区之后,调用系统的write操作,不进行fsync操作。同步到硬盘由操作系统负责,通常同步周期最长30秒
  • write操作会触发延迟写(delayed write)机制,Linux内核提供页缓冲区提高硬盘IO性能。write写入系统缓冲区之后直接返回。同步硬盘操作依赖于操作系统调度机制
  • fsync操作,针对单个文件操作,强制磁盘同步。将阻塞直到写入硬盘完成后返回。
  • always配置太耗费性能,只能支持大约几百tps并发写入;no配置,每次系统不同周期不可控;everysec,建议采用的同步策略,也是默认配置

(4)AOF文件重写。把Redis进程内的数据转化成写命令同步更新到新的AOF文件,AOF重新降低了占用空间,更小的AOF文件可以更快的被Redis加载

  • 重写之后的AOF文件变小的原因 :
    进程内已经超时的数据不在写入文件
    旧的AOF文件中含有无效命令,新的AOF文件只保留最终数据的写入命令
    多条写命令可以合并成一个

、集群

1、数据分布

《Redis开发与运维》读书笔记的更多相关文章

  1. csapp读书笔记-并发编程

    这是基础,理解不能有偏差 如果线程/进程的逻辑控制流在时间上重叠,那么就是并发的.我们可以将并发看成是一种os内核用来运行多个应用程序的实例,但是并发不仅在内核,在应用程序中的角色也很重要. 在应用级 ...

  2. CSAPP 读书笔记 - 2.31练习题

    根据等式(2-14) 假如w = 4 数值范围在-8 ~ 7之间 2^w = 16 x = 5, y = 4的情况下面 x + y = 9 >=2 ^(w-1)  属于第一种情况 sum = x ...

  3. CSAPP读书笔记--第八章 异常控制流

    第八章 异常控制流 2017-11-14 概述 控制转移序列叫做控制流.目前为止,我们学过两种改变控制流的方式: 1)跳转和分支: 2)调用和返回. 但是上面的方法只能控制程序本身,发生以下系统状态的 ...

  4. CSAPP 并发编程读书笔记

    CSAPP 并发编程笔记 并发和并行 并发:Concurrency,只要时间上重叠就算并发,可以是单处理器交替处理 并行:Parallel,属于并发的一种特殊情况(真子集),多核/多 CPU 同时处理 ...

  5. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

  6. 读书笔记--SQL必知必会18--视图

    读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...

  7. 《C#本质论》读书笔记(18)多线程处理

    .NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...

  8. C#温故知新:《C#图解教程》读书笔记系列

    一.此书到底何方神圣? 本书是广受赞誉C#图解教程的最新版本.作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式.朴实简洁的文字,并辅之以大量表格和代码示例,全面.直观地阐述了C#语言的各种 ...

  9. C#刨根究底:《你必须知道的.NET》读书笔记系列

    一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...

  10. Web高级征程:《大型网站技术架构》读书笔记系列

    一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...

随机推荐

  1. Python 基础API

    针对python的os库一些API记录,觉得python的命名并不好,很多API看名字,并不知道具体功能是什么 1. os.path.basename() 得到文件名称,不包括路径,例子:/var/t ...

  2. 【阿里聚安全·安全周刊】苹果证实 iOS 源代码泄露|英国黑客赢下官司

    本周的七个关键词:iOS 源代码泄露  丨 阿里软件供应链安全大赛  丨  个人数据安全  丨  Android P  丨  黑客赢下官司  丨  备忘录泄露美国安全局机密  丨  机器学习系统 -1 ...

  3. Java基础巩固——异常

    基础回顾 什么是异常? 在Java程序运行时,常常会出现一些非正常的现象,这种情况称为运行错误.根据其性质可以分为错误和异常. Java程序中所有抛出的异常都必须从Throwable派生而来.类Thr ...

  4. LeetCode题解33.Search in Rotated Sorted Array

    33. Search in Rotated Sorted Array Suppose an array sorted in ascending order is rotated at some piv ...

  5. Android开发技术周报182学习记录

    Android开发技术周报182学习记录 教程 App安全二三事 记录 为什么要安全 App的移动安全主要包括下面几种: 密钥破解,导致本地加密数据被盗取. 通信密钥破解,导致接口数据被盗取. 伪造接 ...

  6. Linux 总是提示You have new mail in /var/spool/mail/root

    解决办法: echo “unset MAILCHECK” >> /etc/profile source /etc/profile 这样就可以了!!!!!!!!!!

  7. js控制全屏及退出全屏

    js控制全屏及退出全屏,网上很多代码例子,我这里需求和标准的有点出入: 1.当用户点击某按钮,触发iframe下的页面全屏. 2.不允许用户退出全屏. 解决第一点,触发全屏可以按照网上的例子,代码如下 ...

  8. Ubuntu 18.04基础软件安装

    1.fcitx 这个应该是最基础的了,虽然系统自带的也有,不过说实话可能是我还不会配置,我觉得是不好用,坚持用了一周后还是换回了小企鹅,最初是装小企鹅时失败了被迫坚持用了一周,当时失败情况是这样的,使 ...

  9. spring boot -整合Ehcahe

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring- ...

  10. SVN切换账号

    问题背景 SVN账号在登录的时候,默认是保存在个人电脑的 C:\Users\Administrator\AppData\Roaming\Subversion\auth\svn.simple\ 目录下的 ...