深入理解Redis

Redis作为目前非常火的非关系型数据库我们有必要花精力和时间来研究,redis是一个开源的、使用C语言编写的单线程的、支持网络交互的、可基于内存也可持久化的Key-Value数据库。

Redis的优势

redis较其他数据库最明显的优势就是性能和并发上:

  • redis是纯内存操作,所以它能够做出快速响应;
  • 高并发的情况下,如果所有的请求都直接访问MySql会导致数据库连接异常,而redis支持高性能的主从复制的集群策略,这样就大大提高满足高并发访问及快速响应,请求会优先访问redis请求数据,从而避免高并发情况下都直接访问数据库;

Redis单线程

既然redis是单线程,那为什么它性能还这么高呢?

  • redis是纯内存操作;
  • 因为单线程,避免了频繁的上下文切换;
  • 采用I/O多路复用机制;

Redis支持多数据类型

string,hash,list,set,sorted set

1. string

字符串类型,其实它表示一个可变的字节数组,其key对应的value值可以使字符串也可以是数字。如果对应的value值是字符串时,常规的字符串操作基本上都是支持的;如果对应的value值是数字,可以通过incr/incrby/decr/decrby等操作来实现类似计数器的功能。

2. list

利用这个数据结构可以实现最新消息的排行、消息队列的存取等。

3. hash

可以类比python的字典,比如用于存放微博用户的信息(出生年月、性别、手机号等)

4. set

全局去重

5. sorted set

带有权重(score)的set,根据score自动排序

Redis的持久化

持久化就是把数据保存到磁盘上,而redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。

RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上。

AOF,Append Only File,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。

其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。

如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库,就像memcache一样,下面着重谈谈Redis的两种持久化方式。

1. RDB

RDB方式,是将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。

redis在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。

对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效,另外通过RDB对数据进行备份容灾是个不错的选择。

虽然RDB有不少优点,但它的缺点也是不容忽视的。如果你对数据的完整性非常敏感,那么RDB方式就不太适合你,因为即使你每5分钟都持久化一次,当redis故障时,仍然会有近5分钟的数据丢失。所以,redis还提供了另一种持久化方式,那就是AOF。

2. AOF

如果对数据的耐久性需求很高,那么RDB快照持久化的方式可能就显得不那么合适了,此时可以采用AOF,每次有更新数据的操作则会把对应的命令追加到文件的末尾,等下次redis重启时通过执行文件中的命令来实现数据的重建。

AOF通过fsync来进行新命令的追加,默认是间隔1秒。

另外,一般情况下,对于同等规模的数据集,一般AOF文件的体积要比RDB大,并且RDB会具有更好的载入数据性能,实际使用场景中,建议两种持久化方式同时使用。

Redis主从复制策略

redis主从复制就是把redis服务器的数据复制到其他redis节点,前者是主节点,后者是从节点,一般是一主多从的架构且一个从节点只能对应唯一的主节点,另外数据的复制是单向的只能从主节点到从节点,而数据同步的方法是异步的(从redis 2.8开始,采用psync来实现主从数据库的同步)。

1. 主从复制的功能

  • 数据备份;
  • 故障恢复,当主节点出现故障时,可以切换到从节点恢复;
  • 读负载均衡能力加强,一般主节点承担写而从节点负责读,这样多个从节点就分担了读压力;
  • 主从复制是redis sentinel和cluster实现的基础;

2. 缺陷

从上面所述不难看出主从没能解决至少下面三个问题:

  • 无法自动故障切换,只能手动干预或者写监控程序来实现,都比较麻烦;
  • 从节点故障没法恢复;
  • 受单机内存限制无法存储更多的数据,且没实现写的负载均衡能力;

3. 数据复制

分部分复制和全量复制,得看场景。

4. 超时问题

5. 相关配置

从节点配置文件redis.conf加上slaveof

Redis Sentinel

在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。

1. Sentinel模块构成

  • 监控模块:随时监察主从节点的在线状态;
  • 自动故障转移:当主节点出现故障时,哨兵会自动开始故障转移的操作,选举一个从节点作为新的主节点,并且其它从节点会开始复制新的主节点;
  • 通知:主要是跟客户端交互的,比如把故障转移的结果通知到客户端;
  • 配置提供者:客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址;

哨兵架构的主从节点跟一般的主从节点没有区别,故障发现和自动转移是由哨兵控制和完成的;哨兵本质上也是redis节点,不过不存储数据;每个哨兵节点只需配置对主节点的监控,并可自动发现其他哨兵节点及从节点;在哨兵系统启动和故障转移阶段,每个节点的配置文件都会被重写,另外,一个哨兵节点可以监控多个主节点,在sentinel.conf配置多条sentinel monitor语句即可。

2. 实现原理

  • 定时任务:通过向主从节点发送info命令获取最新的主从拓扑结构;通过发布订阅功能发现和获取其他哨兵的信息;通过ping来判断是不是下线;
  • 主观下线:单个哨兵通过心跳检测对服务器做出的下线判断;
  • 客观下线:多个哨兵通过心跳检测对同一个服务器做出的下线判断,并且客观下线只针对主节点;
  • 选举领导者哨兵节点:通过某种算法选择一个哨兵节点来来控制和操作故障转移;
  • 故障转移

3. 相关配置

配置文件:sentinel.conf
sentinel monitor {masterName} {masterIp} {masterPort} {quorum}哨兵系统最核心的配置。

Redis Cluster

redis集群的设计可以说是真正实现了redis高可用,集群中是由多个节点组成,而redis的数据是分布在这些节点上,节点分为主节点和从节点,只有主节点负责数据的读写和集群状态的维护,而从节点只进行主节点数据和状态信息的复制。

redis集群引入了一个新的概念:数据分片,它可以使数据分散到各个主节点,解决了单机内存大小的限制,存储量大大增加;另一方面每一个主节点都可以对外提供读写服务,提高了服务的响应能力。redis集群支持主从复制和自动故障转移的功能,实现高可用。

1. 集群搭建

一般集群的搭建分如下四个步骤:

  • 启动节点:仍然使用redis-server命令,需要使用集群模式启动,cluster-enabled和cluster-config-file这两个配置参数是跟集群相关的,前者是开启集群模式,默认是单机(standalone)模式,后者是集群节点配置文件,无须人为修改, 它由 Redis 集群在启动时创建, 并在有需要时自动进行更新;
  • 节点握手:各个节点之间建立网络让各个节点间可以通讯,相关的命令cluster meet
  • 分配主节点槽:集群总共有16383个槽,redis-cli -h -p cluster addslots {0..5461}
  • 设置主从关系:redis-cli cluster replicate

也可以使用官方推荐的指令redis-trib.rb来完成集群的自动搭建。

Redis分布式锁/并发锁

通过redis的SETNX命令来实现,具体就是有且只有当该key不存在时,才能获取锁设置value;为避免其他客户端来竞争锁造成死锁的问题,可以给key设置一个合理的过期时间,当检测到key过期时删除数据来释放锁以供其他客户端使用。

另外,存在setnx操作之后expire操作之前服务器发生宕机的可能,可以把这两个操作打包成一个原子性操作,命令格式:set nx ex

Redis和数据库双写及数据的一致性

如果只是读操作其实不用太关心redis跟mysql数据的一致性问题,但在进行增删改操作时,由于读写的并发及顺序问题,可能会造成缓存和数据库数据的不一致,现罗列网上的其中两种解决方案:

  • 延时双删策略:在写库前后都加上删除缓存的操作,且要在写库后设置一个合理的延时时间,保证这个时间内其他进程的读请求已经结束;
  • 订阅MySQL的bin log(异步更新缓存):一旦发现数据库有数据更新,就将bin log的消息推送给redis达到及时更新缓存的目的;

缓存雪崩和缓存击穿

这两种情形一般中小型企业不会遇到,但是高并发情形下需要考虑这两种问题。

1. 缓存雪崩

缓存数据某个时间段大面积失效,高并发情形下大量请求直接到数据库,导致数据库连接异常。
解决方案:

  • 加锁排队;
  • 给数据的失效时间加一个随机值,避免数据一时间集体过期失效;
  • 双缓存,一个缓存设置失效时间,另一个不设置;

2. 缓存击穿

特意大量请求缓存不存在的数据,导致所有请求都直接到了数据库,从而数据库连接异常。
解决方案:

  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,对一定不存在的数据进行拦截;
  • 如果返回的结果为空,不管数据是不存在还是因为其他故障,依然对这个空结果进行缓存,并设置过期时间(不要超过5mins)

Redis过期删除和内存淘汰机制

对于过期的数据,redis默认会采用定期删除+惰性删除的策略
定期删除:每隔比如100ms对过期的数据进行随机删除(显然,这样做会导致有些过期数据没被删除);
惰性删除:查询时先判断数据是否过期,如果过期则删除,但是如果短期数据没被查询访问,那么这些数据还是没被删除;

如此可以设置内存淘汰策略(设置maxmemeory-policy)来优化,目前内存淘汰策略有6种:

  • noeviction:当内存不足以容纳新数据时直接报错;
  • allkeys-lru:当内存不足以容纳新数据,在键空间中,移除最近最少使用的key --- 推荐使用
  • allkeys-random:当内存不足以容纳新数据,在键空间中,随机移除一个key;
  • volatile-lru:当内存不足以容纳新数据,在设置了过期时间的键空间中,移除最近最少使用的key,这种情况redis既做缓存又做了持久化才用;
  • volatile-random:当内存不足以容纳新数据,在设置了过期时间的键空间中,随机移除一个key;
  • volatile-ttl:当内存不足以容纳新数据,在设置了过期时间的键空间中,有更早过期时间的key优先移除;

具体采取哪种内存淘汰策略要结合实际的应用场景。

Redis的事务处理

虽然redis提供的事务功能不能算是严格意义上的事务,但在服务器不出问题的情况下,还是可以保证命令打包执行的。

  • MULTI:用来组装一个事务;
  • EXEC:用来执行一个事务;
  • DISCARD:用来取消一个事务;
  • WATCH:用来监视一些key,一旦这些key在事务执行之前被改变,则取消事务的执行。

python下redis缓存的应用场景

一般生产环境中对于使用频率很大的数据经常用Redis作缓存,比如在进行数据读的操作时会先从Redis获取数据,如果获取不到才会从mysql数据库读。

下面是一个简单的缓存实现

def cache(cmd_func, ex=86400):
    def wrapper(func):
        def decorater(*args, **kwargs):
            client = redisclient.get_client('cache')
            key = 'br_args:{}'.format(cmd_func)
            value = client.get(key)
            if data:
                data = pickle.loads(value)
            else:
                data = func(*args, **kwargs)
                value = pickle.dumps(data)
                client.set(key, value, ex)
            return data
        return decorater
    return wrapper

python下获取redis client连接接口可以用redis.StrictRedis()

你可能感兴趣的:(深入理解Redis)