Redis注意事项

1、Redis3.0没有虚拟内存概念,已从2.4就移除;

2、redis挂掉并重启时,如果有主从备份的,主机挂掉重启时先关掉主从备份,不然从机的数据会被冲洗掉

数据恢复时如果有 AOF(原理是将Reids的操作日志以追加的方式写入文件)和 RDB(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化)的,则从 AOF 恢复,若只有 RDB 的,只从 RDB 恢复。

RDB存在哪些优势呢

  1)一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

  2)对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。

  3)性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

  4)相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB又存在哪些劣势呢

  1)如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

  2)由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF的优势有哪些呢?

  1)该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了三种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。

  2)由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。

  3)如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。

  4)AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

AOF的劣势有哪些呢?

  1)对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

  2)根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

3、保存快照需要注意频率,好像没有指明什么方式save,是save还是bgsave;

4、redis指明使用多少内存比较好?

5、删除策略:

  (1)过期策略

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

  Redis中同时使用了惰性过期和定期过期两种过期策略。

  (2)内存淘汰机制

  在redis.conf中有一行配置,用于配内存淘汰策略的

maxmemory-policy volatile-lru

  1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
  2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。
  3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
  4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
  5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐
  6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
    ps:如果没有设置 expire 的 key,不满足先决条件(prerequisites);那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为,和 noeviction(不删除) 基本上一致。

6、事务,没有回滚:只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。

>>>multi,exec,watch,discard,unwatch

批量操作在发送 EXEC 命令前被放入队列缓存,收到 EXEC 命令后进入事务执行;事务中任意命令执行失败,其余的命令依然被执行

watch 提供 check-and-set (CAS)行为,即乐观锁,监控某个或某些key,在watch监控mykey的过程中,mykey被修改了,所以随后的事务便会被取消;如果watch监视了一个带过期时间的键, 那么即使这个键过期了, 事务仍然可以正常执行, http://code.google.com/p/redis/issues/detail?id=270

exec后因其他原因,redis服务器挂掉,剩下事务的命令没被执行,那么后续的命令将不会被执行;当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中;使用 redis-check-aof 程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动

7、惊群效应:在大并发情况下,我们通常会用缓存来给数据库分压,但是会有这么一种情况发生,那就是在一定时间内生成大量的缓 存,然后当缓存到期之后又有大量的缓存失效,导致后端数据库的压力突然增大,这种现象就可以称为“缓存过期产生的惊群现象”。

处理逻辑:

  缓存内真实失效时间time1

  缓存value中存放人为失效时间戳 :time2 ( time2 永远小于time1)

  缓存value对应的lock锁(就是一个与value 对应的 另一个key),主要用于判断是第几个线程来读取redis的value

  当把数据库的数据写入缓存后,这时有客户端第一次来读取缓存,取当前系统时间:system_time 如果system_time >= time2  则认为默认缓存已过期(如果system_time< time1 则还没真实失效 ),这时再获取value的lock锁,调用redis的incr函数(单线程自增函数)判断是第几个获取锁的线程,当且仅当是第一个线程时返回1,以后都逐渐递增。第一个访问的线程到数据库中获取最新值重新放入缓存并删除lock锁的key,并重新设置时间戳;在删除lock之前所有访问value客户端线程获取lock的value都大于1,这些线程仍然读取redis中的旧值,而不会集中访问数据库。

 1 public function getByLock($key)
 2   {
 3     $sth = $this->redis->get($key);
 4     if ($sth === false) {
 5       return $sth;
 6     } else {
 7       $sth = json_decode($sth, TRUE);
 8       if (intval($sth['expire']) <= time()) {
 9         $lock = $this->redis->incr($key . ".lock");
10         if ($lock === 1) {
11           return false;
12         } else {
13           return $sth['data'];
14         }
15       } else {
16         return $sth['data'];
17       }
18     }
19   }

8、redis 集群:

Redis仅支持单实例,内存一般最多10~20GB。对于内存动辄100~200GB的系统,就需要通过集群来支持了。

Redis集群有三种方式:客户端分片、代理分片、RedisCluster

9、缓存与数据库不一致:

1、写数据前后都del key,且设置缓存时间短些

2、监控mysql的binlog,异步删除key

3、缓存操作失败时需要重试,放入消息队列,实在不行报警

10、5种数据类型

Redis注意事项

11、缓存穿透:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

  解决:1、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截。

     2、从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。

12、缓存雪崩:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

  解决:1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

     2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

13、缓存击穿:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

  解决:1、设置热点数据永远不过期。

     2、加互斥锁,第一个线程进去获取到锁并去数据库取值,其他的线程等锁;能针对每个key设置颗粒级的锁会更好,这样key1不会影响到key2。

14、redis总说单线程,其实不是整个server就一个线程在处理所有的事情,它是指接收网络IO请求是一个线程(IO多路复用),文件事件分派又是一个线程,然后请求放在队列里,有序执行

 Redis注意事项

15、哨兵机制

16、消息模式

(1)任务队列模式

  就是“传递消息的队列”。与任务队列进行交互的实体有两类,一类是生产者(producer),另一类则是消费者(consumer)。生产者将需要处理的任务放入任务队列中,而消费者则不断地从任务独立中读入任务信息并执行。

  任务队列的好处:

  松耦合。生产者和消费者只需按照约定的任务描述格式,进行编写代码。

  易于扩展。多消费者模式下,消费者可以分布在多个不同的服务器中,由此降低单台服务器的负载。

  使用LPUSH、RPOP,这样实现先进先出队列

  可以使用BRPOP,进行阻塞连接。BRPOP命令接收两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过了此时间仍然没有获得新元素的话就返回nil。为0时表示不限制等待时间,即如果没有新元素加入列表就永远阻塞下去。当获得一个元素后会返回两个值,分别是键名和元素值。BRPOP命令可以同时接收多个键,其完整的命令格式为 BRPOP key [key key ......] timeout ,如果多个键都有元素,则按照从左到右的顺序取第一个键中的元素。借此特性我们可以实现区分优先级的队列。

(2)发布订阅模式

   “发布/订阅”模式中包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到此消息。

  PUBLISH:

    将信息 message 发送到指定的频道 channel。返回收到消息的客户端数量。

  SUBSCRIBE

    订阅给指定频道的信息。

    一旦客户端进入订阅状态,客户端就只可接受订阅相关的命令SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE和PUNSUBSCRIBE除了这些命令,其他命令一律失效。

  UNSUBSCRIBE 

    取消订阅指定的频道,如果不指定,则取消订阅所有的频道。

 1 127.0.0.1:6379> PUBLISH channel1.1 test
 2 (integer) 0 //有0个客户端收到消息
 3  
 4 127.0.0.1:6379> SUBSCRIBE channel1.1
 5 Reading messages... (press Ctrl-C to quit)
 6 1) "subscribe"  //"subscribe"表示订阅成功的信息
 7 2) "channel1.1" //表示订阅成功的频道
 8 3) (integer) 1  //表示当前订阅客户端的数量
 9 //当发布者发布消息时,订阅者会收到如下消息
10 1) "message"    //表示接收到消息
11 2) "channel1.1" //表示产生消息的频道
12 3) "test"       //表示消息的内容
13 //当订阅者取消订阅时会显示如下:
14 127.0.0.1:6379> UNSUBSCRIBE channel1.1
15 1) "unsubscribe"    //表示成功取消订阅
16 2) "channel1.1" //表示取消订阅的频道
17 3) (integer) 0  //表示当前订阅客户端的数量
18  
19 //注:在redis-cli中无法测试UNSUBSCRIBE命令

  PSUBSCRIBE 

    订阅给定的模式(patterns)。

  PUNSUBSCRIBE 

    可以退订指定的规则,如果没有参数会退订所有的规则。

 1 127.0.0.1:6379> PSUBSCRIBE channel1.*
 2 Reading messages... (press Ctrl-C to quit)
 3 1) "psubscribe"
 4 2) "channel1.*"
 5 3) (integer) 1
 6 //等待发布者发布消息
 7 127.0.0.1:6379> PUBLISH channel1.1 test1.1
 8 (integer) 1 //发布者在channel1.1发布消息
 9  
10 1) "pmessage"   //表示通过PSUBSCRIBE命令订阅而收到的
11 2) "channel1.*" //表示订阅时使用的通配符
12 3) "channel1.1" //表示收到消息的频道
13 4) "test1.1"        //表示消息内容
14  
15 127.0.0.1:6379> PUBLISH channel1.2 test1.2
16 (integer) 1 //发布者在channel1.2发布消息
17  
18 1) "pmessage"
19 2) "channel1.*"
20 3) "channel1.2"
21 4) "test1.2"
22  
23 127.0.0.1:6379> PUBLISH channel1.3 test1.3
24 (integer) 1 //发布者在channel1.3发布消息
25  
26 1) "pmessage"
27 2) "channel1.*"
28 3) "channel1.3"
29 4) "test1.3"
30  
31 127.0.0.1:6379> PUNSUBSCRIBE channal1.*
32 1) "punsubscribe"   //退订成功
33 2) "channal1.*" //退订规则的通配符
34 3) (integer) 0  //表示当前订阅客户端的数量

  

  PUNSUBSCRIBE应该注意一下两点:

    使用PUNSUBSCRIBE命令只能退订通过PSUBSCRIBE命令订阅的规则,不会影响SUBSCRIBE订阅的频道。

    使用PUNSUBSCRIBE命令退订某个规则时不会将其中通配符展开,而是严格的进行字符串匹配,所以PUNSUBSCRIBE *无法退订PUNSUBSCRIBE channal1.*规则,而必须使用PUNSUBSCRIBE channal1.*才能退订

17、主从同步

18、分布式锁