redis总结

redis单点吞吐量

单点TPS达到8万/面,QPS达到10万/秒

redis的5中存储类型:

redis总结_第1张图片

String字符串类型

Redis中的String类型就是一个由字节组成的序列,他和其他编程语言或者其他键值对存储提供的字符串操作非常相似。

一个String类型的实例,其中键为hello,值为world:

redis总结_第2张图片

(1)常用命令如下:

redis总结_第3张图片

(2)Redis中的自增命令和自减命令:

redis总结_第4张图片

(3)除了get、set、del、自增、自减等操作外,Redis还提供了下面一些操作:

1、获取字符串长度 
2、往字符串append内容 
3、设置和获取字符串的某一段内容 
4、设置及获取字符串的某一位(bit) 
5、批量设置一系列字符串的内容

如下图所示:

redis总结_第5张图片

(4)应用场景:

String是最常用的一种数据类型,普通的key/value存储都可以归为此类,value其实不仅是String, 也可以是数字:比如想知道什么时候封锁一个IP地址(访问超过几次)。INCRBY命令让这些变得很容易,通过原子递增保持计数

List列表类型

Redis中的List其实就是链表(redis 使用双端链表实现的 List),相信学过数据结构知识的人都应该能理解其结构。

使用 List 结构,我们可以轻松地实现最新消息排行等功能(比如新浪微博的 TimeLine )。List 的另一个应用就是消息队列,可以利用 List 的 PUSH 操作,将任务存在 List 中,然后工作线程再用 POP 操作将任务取出进行执行。

一个List类型的实例,列表包含的元素,相同元素可以重复出现:
redis总结_第6张图片

需要注意的是:一个List结构可以有序的存储多个字符串,并且是允许元素重复的。

(1)常用命令如下:

redis总结_第7张图片

LPUSH和RPUSH命令分别用于将元素推入列表的左端和右端;

LPOP和RPOP命令分别用于从列表的左端和右端弹出元素,也就是删除元素;

其中对于LRANGE命令来说,使用0位范围的起始索引,-1为范围的结束索引,可以取出列表包含元素的所有元素。

(2)除了上边比较常用的命令之外,Redis列表还可以从列表里边移除元素、将元素插入列表中间、将列表修剪至指定长度的命令,以及一些其他命令。

(3)使用场景:

     微博 TimeLine
     消息队列

Set集合类型

Redis的集合和列表都可以存储多个字符串,他们的不同支持在于,列表可以存储多个相同的字符串,而集合通过使用散列表来保证自己存储的每个字符串都是各不相同的。

Redis的集合使用的是无序的方式存储元素,所以不可以像List列表那样,将元素推入集合的某一端,或者从集合的某一端弹出元素。

一个Set集合类型的实例,各不相同的元素,无序排列:

redis总结_第8张图片

(1)常用命令如下:

redis总结_第9张图片

(2)除了常见的命令之外,还有交集、并集、差集的计算,如下:

redis总结_第10张图片

(3)使用场景:

共同好友、二度好友
利用唯一性,可以统计访问网站的所有独立 IP
好友推荐的时候,根据 tag 求交集,大于某个 threshold 就可以推荐


Hash散列类型
        Redis的散列可以存储多个键值对之间的映射。和字符串一样,散列存储的值既可以是字符串又可以是数字值,并且用户同样可以对散列存储的数字执行自增操作或者是自减操作。

一个List散列类型的实例,是一个包含两个键值对的散列键:

redis总结_第11张图片

(1)常用命令如下:

redis总结_第12张图片

(2)其他命令包含添加和删除键值对的命令、获取所有键值对的命令、以及对键值对的值进行自增和自减操作的命令,如下所示:

redis总结_第13张图片

Redis的有序集合ZSet数据类型

有序集合和散列一样,用于存储键值对;有序集合的键被称为成员member,每一个成员都是独一无二的;而有序集合的值被称为分值score,分值必须是浮点数。

有序集合是Redis里面唯一一个既可以根据成员访问元素,又可以根据分值以及分值的排序来访问元素的结构。

一个有序集合类型的实例,zset-key是一个包含两个元素的有序集合键:

redis总结_第14张图片

(1)常用命令如下:

redis总结_第15张图片

 

redis客户端与服务器的交互模式

Redis实例运行在单独的进程中,应用系统(Redis客户端)通过Redis协议和Redis Server 进行交互。在Redis 协议之上,客户端和服务端可以实现多种类型的交互模式:

  1. 串行请求/响应模式:
                每一次请求的发送都依赖于上一次请求的相应结果完全接收,同一个连接的每秒吞吐量低,redis对单个请求的处理时间通常比局域网的延迟小一个数量级,所以串行模式下,单链接的大部分时间都处于网络等待
  2.  双工的请求/响应模式(pipeline):
                适用于批量的独立写入操作。即可将请求数据批量发送到服务器,再批量地从服务器连接的字节流中一次读取每个响应数据,减少了网络延迟,所以单连接吞吐量较串行会提高一个数量级
  3. 原子化的批量请求/响应模式(事务):
                客户端通过和redis服务器两阶段的交互做到批量命令原子执行的事务效果:入队操作(即服务器端先将客户端发送过来的连接对象暂存在请求队列中)和执行阶段(依次执行请求队列中的所有请求)一个连接的请求在执行批量请求的过程中,不会执行其他客户端的请求.
                redis的事务不是一致的,没有回滚机制。如果中途失败,则返回错误信息,但已经成功执行的命令不会回滚事务.
                里面有可能会带有读操作作为条件,由于批量请求只会先入队列,再批量一起执行,所以一般读操作不会跟批量写请求一起执行,这时候就有可能会导致批量写之前和之后读到的数据不一致,这种可以通过乐观锁的可串行化来解决,redis通过watch机制实现乐观锁。一旦两次读到的操作不一样,watch机制触发,拒绝了后续的EXEC执行
  4. 发布/订阅模式:
                发布端和订阅者通过channel关联,channel的订阅关系,维护在reids实例级别,独立于redisDB的key-value体系。所有的channel都由一个map维护,键是channel的名字,value是它所有订阅者client的指针链表
  5.   脚本化的批量执行(Lua脚本):
                redis确保正一条Lua脚本执行期间,其它任何脚本或者命令都无法执行。正是由于这种原子性,script才可以替代MULTI/EXEC作为事务使用。

redis的网络协议

 redis协议位于TCP层之上,即客户端和redis实例保持双工的连接,交互的都是序列化后的协议数据


redis的持久化机制

 redis主要提供了两种持久化机制:RDB和AOF;

  1. RDB 
            默认开启,会按照配置的指定时间将内存中的数据快照到磁盘中,创建一个dump.rdb文件,redis启动时再恢复到内存中。
            redis会单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
            需要注意的是,每次快照持久化都会将主进程的数据库数据复制一遍,导致内存开销加倍,若此时内存不足,则会阻塞服务器运行,直到复制结束释放内存;都会将内存数据完整写入磁盘一次,所以如果数据量大的话,而且写操作频繁,必然会引起大量的磁盘I/O操作,严重影响性能,并且最后一次持久化后的数据可能会丢失;
  2.  AOF 
            以日志的形式记录每个写操作(读操作不记录),只需追加文件但不可以改写文件,redis启动时会根据日志从头到尾全部执行一遍以完成数据的恢复工作。包括flushDB也会执行。
            主要有两种方式触发:有写操作就写、每秒定时写(也会丢数据)。
            因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对应一条set语句,写到临时文件中,然后再替换到旧的日志文件(类似rdb的操作方式)。默认触发是当aof文件大小是上次重写后大小的一倍且文件大于64M时触发;
            当两种方式同时开启时,数据恢复redis会优先选择AOF恢复。一般情况下,只要使用默认开启的RDB即可,因为相对于AOF,RDB便于进行数据库备份,并且恢复数据集的速度也要快很多。
            开启持久化缓存机制,对性能会有一定的影响,特别是当设置的内存满了的时候,更是下降到几百reqs/s。所以如果只是用来做缓存的话,可以关掉持久化。

redis数据恢复的设计思路

主要有3种方式可以实现 
    keys命令:获取到所有的key,再根据key获取所有的内容。缺点是如果key数量特别多,则会导致redis卡住影响业务
    aof:通过aof文件获取到所有数据。缺点是有一些redis实例写入频繁,不适合开启aof,并且文件可能特别大,传输、解析效率差
    rdb:使用bgsave获取rdb文件,然后解析。缺点是bgsave在fork子进程时有可能会卡住主进程。当对于其他两种,在低峰期在从节点做bgsave获取rdb文件,相对安全可靠。

redis集群(redis cluster)

redis3以后,节点之间提供了完整的sharding(分片)、replication(主备感知能力)、failover(故障转移)的特性

      配置一致性:

        每个节点(Node)内部都保存了集群的配置信息,存储在clusterState中,通过引入自增的epoch变量来使得集群配置在各个节点间保持一致sharding数据分片 

将所有数据划分为16384个分片(slot),每个节点会对应一部分slot,每个key都会根据分布算法映射到16384个slot中的一个,分布算法为slotId=crc16(key)%16384

当一个client访问的key不在对应节点的slots中,redis会返回给client一个moved命令,告知其正确的路由信息从而重新发起请求。client会根据每次请求来缓存本地的路由缓存信息,以便下次请求直接能够路由到正确的节点

   分片迁移:

        分片迁移的触发和过程控制由外部系统完成,redis只提供迁移过程中需要的原语支持。主要包含两种:一种是节点迁移状态设置,即迁移钱标记源、目标节点;另一种是key迁移的原子化命令

   failover故障转移 :

        故障发现:节点间两两通过TCP保持连接,周期性进行PING、PONG交互,若对方的PONG相应超时未收到,则将其置为PFAIL状态,并传播给其他节点

        故障确认:当集群中有一半以上的节点对某一个PFAIL状态进行了确认,则将起改为FAIL状态,确认其故障

   slave选举:

        当有一个master挂掉了,则其slave重新竞选出一个新的master。主要根据各个slave最后一次同步master信息的时间,越新表示slave的数据越新,竞选的优先级越高,就更有可能选中。竞选成功之后将消息传播给其他节点。

集群不可用的情况: 

    1.集群中任意master挂掉,且当前master没有slave。
    2.集群中超过半数以上master挂掉。

普通哈希算法和一致性哈希算法对比

    普通哈希:
    也称硬哈希,采用简单取模的方式,将机器进行散列,这在cache环境不变的情况下能取得让人满意的结果,但是当cache环境动态变化时,这种静态取模的方式显然就不满足单调性的要求(当增加或减少一台机子时,几乎所有的存储内容都要被重新散列到别的缓冲区中)。


    一致性哈希:
    将机器节点和key值都按照一样的hash算法映射到一个0~2^32的圆环上。当有一个写入缓存的请求到来时,计算Key值k对应的哈希值Hash(k),如果该值正好对应之前某个机器节点的Hash值,则直接写入该机器节点,如果没有对应的机器节点,则顺时针查找下一个节点,进行写入,如果超过2^32还没找到对应节点,则从0开始查找(因为是环状结构)。为了更可能的满足平衡性,可以引入虚拟节点,即一个实体节点映射到多个虚拟节点。

redis总结_第16张图片


缓存雪崩:

可能是因为数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。
    解决思路: 加锁计数(即限制并发的数量,可以用semphore)或者起一定数量的队列来避免缓存失效时大量请求并发到数据库。但这种方式会降低吞吐量。
    分析用户行为,然后失效时间均匀分布。或者在失效时间的基础上再加1~5分钟的随机数。
    如果是某台缓存服务器宕机,则考虑做主备。

缓存穿透:

指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库中查询。
           解决思路:  如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。
    可以给key设置一些格式规则,然后查询之前先过滤掉不符合规则的Key。

缓存并发:

  如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。
             解决思路:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。

缓存预热:

目的就是在系统上线前,将数据加载到缓存中。
           解决思路: 数据量不大的话,在系统启动的时候直接加载。自己写个简单的缓存预热程序。


缓存算法:

FIFO算法:First in First out,先进先出。原则:一个数据最先进入缓存中,则应该最早淘汰掉。也就是说,当缓存满的时候,应当把最先进入缓存的数据给淘汰掉。

LFU算法:Least Frequently Used,最不经常使用算法。

LRU算法:Least Recently Used,近期最少使用算法。

LRU和LFU的区别。LFU算法是根据在一段时间里数据项被使用的次数选择出最少使用的数据项,即根据使用次数的差异来决定。而LRU是根据使用时间的差异来决定的。

用redis实现分布式锁

主要使用的命令: 
     1.setnx key val。当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
     2.expire key timeout。为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
   3. delete key。删除锁
实现思想: 
    使用setnx加锁,如果返回1,则说明加锁成功,并设置超时时间,避免系统挂了,锁没法释放。在finally中delete删除锁释放。
    如果需要设置超时等待时间,则可以加个while循环,在获取不到锁的情况下,进行循环获取锁,超时了则退出。

 

你可能感兴趣的:(redis)