Overview

  • 内存中的数据结构存储系统,可以用作数据库、缓存和消息中间件
  • redis底层数据结构:跳跃表 [为什么选skiplist而不是red-black tree]
  • 支持多种数据结构:String, hash, list, set, sorted set...
  • 内置: Replication, lua scripting, LRU eviction, transaction, persistence, Sentinel, Cluster. [具体往下看,分别有具体介绍~]

单线程效率

  • 完全基于内存

  • 数据结构简单,对数据操作也简单

  • 多路IO复用模型 (参考link.)

  • 不要考虑各种锁

  • 不存在多线程上下文切换而消耗CPU

事务

  • 事务是一个单独的隔离操作,事务中所有命令都会序列化、按顺序地执行,执行过程中不被其它客户端发来的命令请求打断。
  • 不支持rollback (redis认为失败的命令是由编程错误造成,不应出现在生产环境中;使redis内部保持简单且快速。)
  • 若事务中某些命令在执行时产生了错误,事务中的其他命令仍然会继续执行。
  • redis中关于事务的命令:MULTI、EXEC、DISCARD、WATCH

 

CAS乐观锁

  • watch命令可以为redis事务提供CAS(check-and-set)行为
  • 典型的例子如下:
    val = GET mykey
    val = val + 1
    SET mykey $val
    

    上述代码在多个client争用时会产生不正确的结果。可以通过watch来解决:

    WATCH mykey
    val = GET mykey
    val = val + 1
    MULTI
    SET mykey $val
    EXEC
    

    上述代码会在exec执行之前监视mykey,如果有其他client修改了mykey的值,那么当前client的事务就会失败。程序要做的就是不断重试该操作,直到没有发生碰撞为止。
    watch命令从其被调用开始生效,一直到调用EXEC为止。

  • 实际上这就是乐观锁。(通常情况下不会产生大量的重试。)

ACID

以上,redis事务

  • 不满足原子性:redis事务在执行过程遇到错误,不会回滚,而是继续执行后续命令。

  • ?持久性:redis没有在事务层面有额外的持久性保证,所以仍然由redis提供的持久化模式决定。 [所以redis事务是不能保证持久性的,除非底层是aof always的持久化模式。]

  • 隔离性:redis事务执行过程中,不会处理其他命令。

  • 一致性:能保证。

Persistence

  • bgsave做全量持久化:耗时长,不够实时,宕机时存在大量数据丢失。

    • bgsave的原理是什么?你给出两个词汇就可以了,fork和cow

    • fork是指redis通过创建子进程来进行bgsave操作。

    • cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。 [使用单独子进程来进行持久化,主进程不进行任何IO操作,保证了redis的高性能。 ]

    • 由于os的写时复制(copy on write)机制,父子进程会共享当前的物理页面,当父进程处理写请求时,os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照。
    • [注意:每次快照持久化都是将内存数据完整地写入到磁盘一次,并不是增量的。因而,在数据量比较大的时候必然会引起大量的磁盘IO。]
  • aof(Append-only file)做增量持久化: 将“操作+数据”以格式化指令的方式追加到操作日志文件的尾部。在append操作返回后才进行实际的数据变更。

  • —> 二者结合,在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

  • 突然断电的情况? —> 取决于aof的配置:如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。 [linux文件操作的“延迟写入”:并非每次write都会出发实际磁盘操作,而是进入buffer] aof记录同步选项如下

    • always:每一条aof记录都立即同步到文件,最安全的方式,但带来更多的磁盘操作和更大的阻塞延迟。

    • erverysec:每秒同步一次。如果遇到物理server故障,可能导致最近1s内aof记录丢失。

    • no:redis并不直接调用文件同步(并不是说不采用aof),而是交给os来处理 --> os可以根据buffer填充/通道空闲时间等择机触发同步。性能比较好,数据丢失量会与OS配置有关。

以上,可以看出来redis并不十分适合“所有数据都需要机器可靠”的场景(may be relational db for ur choice)

  • RDB: 在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
  • 默认的持久化方式
  • 优点:
    • 使用单独子进程来进行持久化,主进程不进行任何IO操作,保证了redis的高性能。
    • 恢复速度快。
  • 缺点:
    • 间隔一段时间进行持久化,容易发生数据丢失。
    • 每次保存RDB时,redis都要fork出一个子进程,在数据集比较大时,fork可能会十分耗时,造成server在一段时间内停止处理client请求。

Partition

  • 按key分区
  • 作用:
    • 增大内存
    • 通过增加计算机来提高redis计算能力、网络带宽
  • 分区方式:
    • range:需要一张表存储数据到redis实例的映射关系。
    • hash:key必须是object_name:<id>的形式。
  • 分区实现方式:
    • 客户端分区:在client端就已经决定数据会被存储到哪个redis节点或从哪个redis节点读取。
    • 代理分区:client将请求发给代理,然后代理决定。redis和memcached的一种代理实现就是Twemproxy
    • 查询路由(Querying routing):client随即地请求任意一个redis实例,然后由redis将请求转发给正确的redis节点。
      Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求转发给正确的redis节点,而是在client端的帮助下直接redirected到正确的redis节点。
  • 缺点:
    • 涉及多个key的操作不被支持
    • 同时操作多个key,不能使用redis事务
    • 分区粒度是key,so it is not possible to shard a dataset with a single huge key like a very big sorted set
    • 分区时的动态扩容或缩容可能非常复杂。

Redis集群

Sentinel

  • redis sentinel着眼于高可用,在master宕机时会自动提升slave

  • Sentinel本身也是集群,避免SPOF。redis的客户端可以随意地连接任意一个sentinel来获得关于redis集群中的信息。 (sentinel集群自身也需要多数机制,也就是2个sentinel进程时,挂掉一个另一个就不可用了。)

  • sentinel的failover过程对client 是透明的。

    Set sentinels = new HashSet();
            sentinels.add(new HostAndPort("172.30.37.73", 26379).toString());
            sentinels.add(new HostAndPort("172.30.37.73", 26380).toString());
            sentinels.add(new HostAndPort("172.30.37.73", 26381).toString());
            JedisSentinelPool sentinelPool = new JedisSentinelPool("myredis", sentinels);
            System.out.println("Current master: " + sentinelPool.getCurrentHostMaster().toString());
            
            Jedis master = sentinelPool.getResource();
            master.set("username","tom");
            sentinelPool.returnResource(master);

Redis Cluster

  • redis cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。 [Sharding]

  • Redis采取了P2P而非Proxy方式、异步复制、客户端重定向等设计,而牺牲了部分的一致性。

  • 可用性:在Cluster推出之前,可用性要靠Sentinel保证。有了集群之后也自动具有了Sentinel的监控和自动Failover能力。

  • 有了Cluster功能后,Redis从一个单纯的NoSQL内存数据库变成了分布式NoSQL数据库,CAP模型也从CP变成了AP

  • redis cluster VS codis

    • redis cluster基于smart client和无中心的设计,client必须按key的哈希将请求直接发送到对应的节点。 [转发] —> client不能直接像单机一样使用pipeline来提高效率,想同时执行多个请求来提速必须在client端自行实现异步逻辑。

    • 而codis因其有中心节点、基于proxy的设计,对client来说可以像对单机redis一样去操作proxy(除了一些命令不支持),还可以继续使用pipeline并且如果后台redis有多个的话速度会显著快于单redis的pipeline。

    • codis是一整套解决方案,还提供了auto-rebalance(迁移对上层业务透明)等。

redis pipelining

  • Redis is a TCP server using the client-server model and what is called a Request/Response protocol.

  • The client sends a query to the server, and reads from the socket, usually in a blocking way, for the server response.

Operations

  • keys VS scan

    • keys阻塞 [redis单线程]

    • scan非阻塞,但有一定重复概率

FYI