缓存

一.Redis

    1.redis和memcached的区别

    (1)memcached只能存储string,而Redis可以存储string、list(对应Java中的Queue,

             有些公司直接用该类型做消息队列)、set、zset、hash(对应Java中的Map)。

    (2)redis可以持久化,而memcached不可以

    (3)memcached可以多线程的运行,而redis只能单线程(线程指的是网络请求模块

             使用了一个线程,其他模块用了多个线程).

    2.Redis是单线程的

     (1)Redis单线程的含义

              即Redis是用"单线程-多路复用io模型"来实现高性能的内存数据

              服务的,同一时刻只有一个操作(命令)在进行,所以,耗时

              的命令会导致并发的下降(读并发和写并发),例如:sunion。

              注意:

                     Redis的单线程是指在处理我们的网络请求的时候只有一个

              线程来处理,一个正式的Redis Server运行的时候肯定是不止

              一个线程的,例如Redis进行持久化的时候会以子进程或者子线

              程的方式执行(具体是子线程还是子进程待读者深入研究);

              在例如在测试服务器上查看Redis进程,然后找到该进程下的线

              程:

     (2)Redis是单线程的,那么Redis为什么这么快?

               主要有以下3个原因:

             (a)完全基于内存;

             (b)数据结构简单,对数据操作也简单;

             (c)使用多路 I/O 复用模型;

                             多路 I/O 复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的

                      能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从

                      阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件

                      的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

                             这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O

                      复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),

                      且 Redis 在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),

                      主要以上两点造就了Redis 具有很高的吞吐量。

                             顺便说一句和Memcached 不同,Redis 并没有直接使用Libevent,而是自己完成

                      了一个非常轻量级的对 select、epoll、evport、kqueue 这些通用的接口的实现。在不

                      同的系统调用选用适合的接口,linux 下默认是epoll。因为Libevent 比较重,更通用,

                      代码量也就很庞大,拥有很多Redis 用不上的功能,Redis 为了追求“轻巧”并且去除依

                      赖,就选择自己去封装了一套。

    2.redis的持久化方式

    (1)rdb:在指定的时间间隔内生成数据集的时间点快照。Rdb保存默认文件名是

                      dump.rdb文件。该策略是redis默认的持久划方式。不同的版本的Redis,

                      Rdb文件是不同的。

             执行过程:实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成

                               功后,再替换之前的文件,用二进制压缩存储。

                               save:执行时阻塞,服务不可用

                               bgsave:fork时阻塞,大部分时间服务时可用的。

            触发时机:

                   --  从节点进行全量复制操作

                   --  degug reload命令

                   --  shut down命令,如果没有开启aof则自动执行bgsave(其实它是一个可选

                       项)

            实现原理

                   通过定时器,该定时器每100毫秒就会执行一次

                   --  定时器每次执行时判断“当前时间-上次执行rdb备份的时间”是否>“我们指定的时

                       间”,如果大于,则执行rdb备份。

                   --  判断更新次数是否大于指定修改次数

                   --  如果同时满足上面两个条件,则生成rdb文件。

                   --  可以使用命令info persistence查看持久化信息。

                  

    (2)aof: 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新

                       执行这些命令来还原数据集。Aof持久化时文件的格式:.aof文件。

             执行过程:AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,

             查询操作不会记录, 以文本的方式记录,可以打开文件看到详细的操作记录。

             开启 aof :appendonly yes  (默认是 no)。

             注:redis写的时候不直接写磁盘而是写到缓冲区,在由缓冲区刷如磁盘

                    有三种刷盘策略:

                     --  always:每次有新命令时,就将缓冲区数据写。性能会比较差,互联网公司一般不用

                     --  everysec:默认,每秒将缓冲区的数据写入并同步到AOF文件入并同步到AOF文件。

                                           服务器突然宕机会丢失一秒的数据,擎天说即使数据丢失了,redis还提供

                                           数据恢复的命令,这是一般使用的。

                     --  no:将缓冲区数据写入AOF文件,但是同步操作到交给操作系统来处理。性能比较好,但是

                                 实时性是最差的。

             Aof的重写:aof 文件持续增长而大时,会 fork 出一条新进程来将文件重写(也就

                                 是先写临时文件最后再 rename),遍历新进程的内存中的数据,每

                                 条记录有一条set语句,重写 aof 文件的操作,并没有读取旧的的

                                 aof 文件,而是将整个内存的数据库内容用命令的方式重写了一个新

                                 的 aof 文件。fork进程时一样阻塞。Aof文件可以用于不同版本的Redis,

                                 这点不同于RDB。

          重写能够压缩Aof体积的原因:

          

             Aof文件重写的触发机制:

                   (a) redis 会记录上次重写的 aof 的大小,默认的配置当 aof 文件大小上次

                             重写后大小的一倍且文件大于 64M触发(3G)。

                   (b) 调用BGREWRITEAOF手动触发

    (4)redis可以同时开启Aof和Rdb

              RDB与AOF同时开启  默认先加载AOF的配置文件。

              两者都开启的建议:RDB数据不实时,同时使用两者时服务器只会找AOF文件,

                                               可不可以只使用AOF?建议不要,因为RDB更适合备份数据

                                               库(AOF在不断变化,不好备份),快速重启,而且不会又AOF

                                               可能潜在的BUG,留作万一的手段。

    (5)Aof和Rdb两者优缺点:

             rdb:性能比较好,尤其大数据集的时候效率高一些;

             aof:效率比较低但是一致性做的比较好

                   (a) redis 会记录上次重写的 aof 的大小,默认的配置当 aof 文件大小上次

                             rewrite后大小的一倍且文件大于 64M触发(3G)。

                   (b) 调用BGREWRITEAOF手动触发

    (4)redis可以同时开启Aof和Rdb

              RDB与AOF同时开启  默认先加载AOF的配置文件。

              两者都开启的建议:RDB数据不实时,同时使用两者时服务器只会找AOF文件,

                                               可不可以只使用AOF?建议不要,因为RDB更适合备份数据

                                               库(AOF在不断变化,不好备份),快速重启,而且不会又AOF

                                               可能潜在的BUG,留作万一的手段。

    (5)Aof和Rdb两者优缺点:

             rdb:性能比较好,尤其大数据集的时候效率高一些;

             aof:效率比较低但是一致性做的比较好

    3.Redis缓存穿透、缓存击穿、缓存雪崩

     (1)缓存穿透(大量访问不存在的key)

            (a)什么是缓存穿透

                     是指查询一个一定不存在的key时,由于缓存不命中所以去数据库查询,但

                     是数据库依然查询不到这个结果,所以该数据不会写入缓存,这就导致这个

                     不存在的数据每次都要到数据库去查询。在高并发大流量时,可能DB就挂

                     掉了。如果有人频繁的利用不存在的key进行攻击,这就是系统漏洞。

            (b)解决方案

                   (i)布隆过滤器(常用)

                           将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在

                           的数据会被这个bitmap拦截掉(一个key如果一定不存在布隆过滤器中,

                           那么它一定不存在redis中,如果它存在布隆过滤器中,那么它有可能存

                           在也有可能不存在Redis中。),从而避免了对底层存储系统的查询压

                           力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果

                           一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们

                           仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分

                           钟。

                           关于布隆过滤器的介绍:

                           布隆过滤器简介:

                           布隆过滤器是一种多哈希函数映射的快速查找算法。它可以判断出某个

                           元素肯定不在集合里或者可能在集合里,即它不会漏报,但可能会误报

                         (一个key如果一定不存在布隆过滤器中,

                           那么它一定不存在redis中,如果它存在布隆过滤器中,那么它有可能存

                           在也有可能不存在Redis中)。通常应用在一些需要快速判断某个元素是

                           否属于集合,但不严格要求100%正确的场合。

                           布隆过滤器介绍

                                         布隆过滤器是一种多哈希函数映射的快速查找算法。它可

                                  以判断出某个元素肯定不在集合里或者可能在集合里,即它不

                                  会漏报,但可能会误报。通常应用在一些需要快速判断某个元

                                  素是否属于集合,但不严格要求100%正确的场合。

                                         一个空的布隆过滤器是一个m位的位数组,所有位的值都

                                  为0。定义了k个不同的符合均匀随机分布的哈希函数,每个函

                                  数把集合元素映射到位数组的m位中的某一位。

                                  * 布隆过滤器原理

                                    布隆过滤器有点像HashMap,一个空的布隆过滤器是一个m

                                    位的位数组,所有位的值都为0。定义了k个不同的符合均匀

                                    随机分布的哈希函数,每个函数把集合元素映射到位数组的

                                    m位中的某一位。布隆过滤器是一个 bit 向量或者说bit 数组,

                                    长这样:

​                             

                                    增加元素和查询元素

                                           何为增加一个元素?

                                                   先把这个元素作为k个哈希函数的输入,拿到k个数组

                                                   位置,然后把所有的这些位置置为1。

                                           何为查询一个元素?

                                                   把这个元素作为k个哈希函数的输入,得到k个数组位

                                                   置。这些位置中只要有任意一个是0,元素肯定不在

                                                   这个集合里。如果元素在集合里,那么这些位置在插

                                                   入这个元素时都被置为1了。如果这些位置都是1,那

                                                   么要么元素在集合里,要么所有这些位置是在其他元

                                                   素插入过程中被偶然置为1了,导致了一次“误报”。

                                           增加一个元素和查询一个元素详细说明

                                                  如果我们要映射一个值到布隆过滤器中,我们需要使

                                                  用多个不同的哈希函数生成多个哈希值,并对每个生

                                                  成的哈希值指向的 bit 位置 1,例如针对值 “baidu” 和

                                                  三个不同的哈希函数分别生成了哈希值 1、4、7,则

                                                  上图转变为:

​                                                

                                                  Ok,我们现在再存一个值 “tencent”,如果哈希函数

                                                  返回3、4、8 的话,图继续变为:

​                                               

                                                  值得注意的是,4 这个 bit 位由于两个值的哈希函数

                                                  都返回了这个 bit 位,因此它被覆盖了。现在我们如

                                                  果想查询“dianping” 这个值是否存在,哈希函数返回

                                                  了 1、5、8三个值,结果我们发现 5 这个 bit 位上的

                                                  值为 0,说明没有任何一个值映射到这个 bit 位上,

                                                  因此我们可以很确定地说 “dianping” 这个值不存在。

                                                  而当我们需要查询 “baidu”这个值是否存在的话,那

                                                  么哈希函数必然会返回 1、4、7,然后我们检查发现

                                                  这三个 bit 位上的值均为 1,那么我们可以说 “baidu”

                                                  存在了么?答案是不可以,只能是 “baidu”这个值可

                                                  能存在。

                                                          这是为什么呢?答案很简单,因为随着增加的

                                                  值越来越多,被置为 1 的 bit 位也会越来越多,这样

                                                  某个值“taobao” 即使没有被存储过,但是万一哈希

                                                  函数返回的三个 bit 位都被其他值置位了 1 ,那么程

                                                  序还是会判断“taobao” 这个值存在。

                                           误判率

                                                  布隆过滤器只会漏判不会误判

                                                  误判率就是在插入n个元素后,某元素被判断为“可能

                                                  在集合里”,但实际不在集合里的概率,此时这个元素

                                                  哈希之后的k个比特位置都被置为1。

                                                         这里对误判率只做了简单的概述,如果工作中用

                                                  到了误判率,请看博客:

                                                  https://www.cnblogs.com/Jack47/p/bloom_filter_intro.html

                                                  中的误判率一节。

                                    删除元素

                                           布隆过滤器可以支持 add 和 isExist 操作,但是不支持

                                           delete 操作可以么。例如上图中的bit 位 4 被两个值共同

                                           覆盖的话,一旦你删除其中一个值例如 “tencent” 而将其

                                           置位 0,那么下次判断另一个值例如 “baidu” 是否存在的

                                           话,会直接返回 false,而实际上你并没有删除它。

                                                  那么如何解决这个问题,答案是计数删除。但是计

                                           数删除需要存储一个数值,而不是原先的 bit 位,会增大

                                           占用的内存大小。这样的话,增加一个值就是将对应索

                                           引槽上存储的值加一,删除则是减一,判断是否存在则

                                           是看值是否大于0。

                                  * 如何选择哈希函数个数和布隆过滤器长度

                                    很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查

                                    询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过

                                    滤器的长度会直接影响误报率,布隆过滤器越长其误报率越

                                    小。另外,哈希函数的个数也需要权衡,个数越多则布隆过

                                    滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;

                                    但是如果太少的话,那我们的误报率会变高。

                               

                                    k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个

                                    数,p 为误报率。至于如何推导这个公式,我在知乎发布的

                                    文章有涉及,感兴趣可以看看,不感兴趣的话记住上面这个

                                    公式就行了。

                                  * 实践

                                    常见的适用常见有,利用布隆过滤器减少磁盘 IO 或者网络

                                    请求,因为一旦一个值必定不存在的话,我们可以不用进

                                    行后续昂贵的查询请求。另外,既然你使用布隆过滤器来

                                    加速查找和判断是否存在,那么性能很低的哈希函数不是

                                    个好选择,推荐 MurmurHash、Fnv 这些。

                                  * 大Value拆分

                                    Redis 因其支持 setbit 和 getbit 操作,且纯内存性能高等特

                                    点,因此天然就可以作为布隆过滤器来使用。但是布隆过滤

                                    器的不当使用极易产生大 Value,增加 Redis 阻塞风险,因

                                    此生成环境中建议对体积庞大的布隆过滤器进行拆分。拆分

                                    的形式方法多种多样,但是本质是不要将 Hash(Key) 之后

                                    的请求分散在多个节点的多个小 bitmap 上,而是应该拆分

                                    成多个小 bitmap 之后,对一个 Key 的所有哈希函数都落在

                                    这一个小 bitmap 上。

                                    布隆过滤器参考文章:

                                    https://www.jianshu.com/p/2104d11ee0a2

                                    https://www.cnblogs.com/Jack47/p/bloom_filter_intro.html

                  

    (2)缓存击穿(热点key过期)

            (a)什么是缓存击穿

                     对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并

                     发地访问,是一种非常“热点”的数据。而恰好这些热点key缓存过期,这个时

                     候大量的请求会访问数据库,这就是缓存击穿。     

            (b)解决方案

                   (i)使用互斥锁

                           在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,

                           释放锁。若其他线程发现获取锁失败(此处的逻辑是,该线程先去查缓

                           存,发现为空,那么再去获取锁,但获取不到锁),则睡眠50ms后重

                           试。至于锁的类型,单机环境用并发包的Lock类型就行,集群环境则使

                           用分布式锁。

                   (ii)“提前”使用过期锁(这个方案不对,如果这些key一直没被访问,过期了

                            还是会造成缓存击穿)

                           在value内部设置1个超时值(timeout1), 该超时值比实际的超时值

                         (timeout2)小。当从缓存读取到timeout1发现它已经过期时候,马上延

                           长timeout1并重新设置到缓存。然后再从数据库加载数据并设置到缓存

                           中。

                   (iii)设置永不过期

                             设置Redis的key永不过期,这就保证了热点key不会过期。把过期时间

                             存在key对应的value里,如果发现要过期了,通过一个后台的异步线

                             程进行缓存的构建(例如,该方案redis自己维护一个timeout,

                             当timeout小于System.currentTimeMillis())。 从实战看,这种方法对于

                             性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的

                             线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可

                             以忍受。

                   (iv)采用netflix的hystrix,可以做资源的隔离保护主线程池,如果把这个应

                            用到缓存的构建也未尝不可,但是这只适合于java应用。

     (3)缓存雪崩(缓存同时失效)

            (a)什么是缓存雪崩

                     缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时

                     刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

            (b)解决方案

                     多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,

                     从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单

                     方案就是讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增

                     加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就

                     会降低,就很难引发集体失效的事件。

    4.  redis的集群

         本节内容来自官方文档:

         http://www.redis.cn/topics/cluster-tutorial.html

       (1)Redis集群的优点

                --  自动分割数据到不同的节点上。

                --  整个集群的部分节点失败或者不可达的情况下能够继续处理命令

       (2)Redis集群是如何分片的

                Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。

                集群的每个节点负责一部分hash槽。举个例子,比如当前集群有3个节点,那么:

                --  节点 A 包含 0 到 5500号哈希槽。

                --  节点 B 包含5501 到 11000 号哈希槽。

                --  节点 C 包含11001 到 16384号哈希槽。

                这种结构很容易添加或者删除节点,比如如果我想新添加个节点D, 我需要从节点 A, B, C中

                得部分槽到D上; 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何

                槽的A节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,

                所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

       (3)Redis集群数据一致性保证以及它在的两个缺陷(会丢失少量数据)

                Redis 并不能保证数据的强一致性。这意味这在实际中集群在特定的条件下可能会丢失写操作

                第一个原因是因为集群是用了异步复制. 写操作过程:

                --  客户端向主节点B写入一条命令.

                --  主节点B向客户端回复命令状态.

                --  主节点将写操作复制给他得从节点 B1, B2 和 B3.

                      主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等

               待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和

               一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一

               种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内

               的少数实例被孤立

                      举个例子,假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主

               节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,

                那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则

                包含节点 B 和客户端 Z1 Z1仍然能够向主节点B中写入,如果网络分区发生时间较短,那么集群将

                会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中

                得数据便丢失。

                :redis是线程安全的,但是Redis集群不是线程安全的

    5.  Redis分区算法

         :redis是线程安全的,但是Redis集群不是线程安全的

                Redis分区算法:

                     (1)范围分区:就是将一个范围内的key都映射到同一个Redis实例中,具体做

                                                法如下:我们可以将用户ID从0到10000的用户数据映射到R0实

                                                例,而将用户ID从10001到20000的对象映射到R1实例,依次类

                                                推。

                     (2)hash分区:常见的hash分区有 “节点取余分区”、“一致性哈希分区”、“虚拟槽

                                                分区”。

                       还有redis集群配合文章:

                        https://blog.csdn.net/sanpo/article/details/52839044

                    (3)redis cluster

​                       

    5.  Redis的主从复制

       (1)概念

                       是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主服务器

             (master)后者称为从服务器);数据的复制是单向的,只能由主服务器到从服务器。

               注:默认况下,每台Redis服务器都是主服务器;一个主服务器可以有多个从服务器(

                      或没有从服务器),但一个从服务器只能有一个主服务器。

       (2)主从复制的作用

                --  数据冗余

                           主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

                --  故障恢复

                           当主服务器出现问题时,可以由从服务器提供服务,实现快速的故障恢复;实

                           际上是一种服务的冗余。

                --  负载均衡

                           在主从复制的基础上,配合读写分离,可以由主服务器提供写服务,由从服务

                           器提供读服务(即写Redis數据时应用连接主服务器,读Redis數据时应用连接

                           从服务器),分担服务器负载;尤其是在读多写少的场景下,通过多个从服务

                           器分担读负载,可以大大提高Redis服务器的并发量。

                --  高可用基石

                            除了上术作用以外,主从复制还是哨兵桕集群能够实施的基础,因仳说主从复

                            制是Redis高可用的基

       (3)配置主从的方式

                Redis配置主从,有两种方式,一种是通过命令,一种是通过redis.config

                配置完之后,可以使用命令 info replication去查看主从复制状态

       (4)三种拓扑结构

              (a)一主一从

                       从节点只是做备份

              (b)星形结构

                       

 

                       一般用来分担主节点上的读写的压力。

                              对于用于读多写少的场景,可以把读命令发送到从节点来分担主节点压力。

                       如:keys命令,可以在其中一台从节点上执行,防止慢查询对主节点造成阻塞从

                       而影响线上服务的稳定性。

                              对于写多读少的场景,多个从节点会导致主节点写命令的多次发送从而过度

                       消耗网络带宽,同时也加重了主节点的负载影响服务稳定性。

              (c)树状结构

                       

                      

                              从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向

                       下层复制。通过这种结构可以有效降低主节点负载和需要传送给从节点的数据量

        

         其原理如下:

                当一个从数据库启动后,会向主数据库发送SYNC命令。同时主数据库接收到SYNC

                命令后会开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到

                的命令缓存起来。当快照完成后Redis会将快照文件和所有缓存的命令发送给从数据

                库,从数据库收到后,会载入快照文件并执行收到的缓存的命令。以上过程称为复

                制初始化。复制初始化结束后,主数据库每当收到写命令时就会将命令同步给从数

                据库,从而保证主从数据库数据一致。

                       当主从数据库之间的连接断开重连后,Redis2.6以及之前的版本会重新进行复制

                初始化(即主数据库重新保存快照并传送给从数据库),即使从数据库可以仅有几条

                命令没有收到,主数据库也必须要将数据库里的所有数据重新传送给从数据库。这使

                得主从数据库断线重连后的数据恢复过程效率很低下在网络环境不好的时候这一问题

                尤其明显。Redis2.8版的一个重要改进就是断线重连能够支持有条件的增量数据传输,

                当从数据库重新连接上主数据库后,主数据库只需要将断线期间执行的命令传送给从

                数据库,从而大大提高Redis复制的实用性。8.17节会详细介绍增量复制的实现原

                理以及应用条件。

                主从复制和哨兵的区别:

                       主从是集群方式而哨兵只是提供了监控和自动选主的机制,并不是集群方式

   6.  redis的高可用

              一个哨兵可以监控多个主从系统,多个哨兵可以监控一个主从系统

     (1)哨兵系统主要有三个任务

            (a)监控

                            哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常

            (b)重新选主(或叫自动故障迁移)

                            master数据库出现故障时,可以自动通过投票机制,从slave节点中选举新的

                     master,实现将从数据库转换为主数据库的自动切换。

            (b)提醒

                            当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者

                     其他应用程序发送通知。

       (2)哨兵节点要部署在不同的物理机上,至少三个哨兵节点(一定是奇数)

       (3)哨兵的是实现原理

                       哨兵启动后,会与要监控的主数据库建立两条连接(这两个连接的建立方式与普

                通的Redis客户端无异)。其中一条连接用来订阅该主数据的_sentine_:helllo频道,

                以获取其他同样监控该数据库的哨兵节点的信息;另外一条连接时哨兵也需要定期向

                主数据库发送INFO等命令来获取主数据库本身的信息(因为4.4.4节介绍过当客户

                端的连接进入订阅模式时就不能再执行其他命令了,所以这时哨兵会使用另外一条连

                接来发送这些命令)。   

                       链接建立完成之后,哨兵会定时执行如下几个操作:

                       --  每10秒哨兵会向主数据库和从数据库发送INFO命令。

                                  哨兵通过发送INFO命令来获得当前数据库的相关信息(包括运行ID、复制信

                           息等)从而实现新节点的自动发现(前面说配置哨兵监控Redis主从系统时只需要

                           指定主数据库的信息即可,因为哨兵正是借助工INFO命令来获取所有复制该主数

                           据库的从数据库信息的)。启动后,哨兵向主数据库发送工INFO命令,通过解析

                           返回结果来得知从数据库列表,而后对每个从数据库同样建立两个连接,这两个

                           连接的作用和前文介绍的与主数据库建立的两个连接完全一致。在此之后,哨兵

                           会每10秒定时向已知的所有主从数据库发送INFO命令来获取信息更新并进行相应

                           操作,比如对新增的从数据库建立连接并加入监控列表,对主从数据库的角色变

                           化(由故障恢复操作引起)进行信息更新等。

                       --  每2秒哨兵会向主数据库和从数据库的sentinel:hello频道发送自己的信息。

                                   哨兵向主从数据库的sentinel:hello频道发送信息来与同样监控该数据库的哨

                            兵分享自己的信息。主要包括的哨兵的基本信息,以及其监控的主数据库的信息。

                            其具体发送的消息内容为:

                            

                                   哨兵会订阅每个其监控的数据库的_sentinel_:hello频道,当其他哨兵收到消

                            息后,会判断发消息的哨兵是不是新发现的哨兵。如果是则将其加入己发现的哨

                            兵列表中并创建一个到其的连接(与数据库不同,哨兵与哨兵之间只会创建一条

                            连接用来发送ping命令,而不需要创建另外一条连接来订阅频道,因为哨兵只需

                            要订阅数据库的频道即可实现自动发现其他哨兵)。同时哨兵会判断信息中主数

                            据库的配置版本,如果该版本比当前记录的主数据库的版本高,则更新主数据库

                            的数据。配置版本的作用会在后面详细介绍。

                       --  每1秒哨兵会向主数据库、从数据库和其他哨兵节点发送PING命令。

                                  实现了自动发现从数据库和其他哨兵节点后,哨兵要做的就是定时监控这些

                            数据库和节点有没有停止服务。这是通过每隔一定时间向这些节点发送PING命

                            令实现的(时间间隔与down—after—milliseconds选项指定时间有关,当

                            down—after—milliseconds的值小于1秒时,哨兵会每隔

                            down—after—milliseconds指定的时间发送一次PING命令,当

                            down-after-milliseconds的值大于1秒时,哨兵会每隔1秒发送一次PING命令。

                                   当超过down-after-milliseconds选项指定时间后如果被PING的数据库或节

                            点仍然未进行回复,则哨兵认为其主观下线。主观下线表示当前的哨兵进程

                            看来,该节点已经下线。如果该节点是主数据库,则哨兵会进一步判断是否需

                            要对其进行故障恢复(哨兵发送SENTINEL  is-master-down-by-addr命令询问

                            其他哨兵节点以了解他们是否也认为该主数据库主观下线,如果达到指定数量

                            时,哨兵会认为其客观下线,并选举领头的哨兵节点对主从系统发起故障恢

                            复。这个指定数量即为前文介绍的quorum参数)。

                            关于领头哨兵选举:

                                   故障恢复需要由领头的哨兵来完成,这样可以保证同一时间只有一个哨兵节

                                   点来执行故障恢复。选举领头哨兵的过程使用了Raft算法,具体过程如下:

                                   *  发现主数据库客观下线的哨兵节点(下面称作A)向每个哨兵节点发送命

                                        令,要求对方选自己成为领头哨兵。

                                   *  如果目标哨兵节点没有选过其他人,则会同意将A设置成领头哨兵。

                                   *  如果A发现有超过半数且超过quorum参数值的哨兵节点同意选自己成为

                                      领头哨兵,则A成功成为领头哨兵。

                                   *  当有多个哨兵节点同时参选领头哨兵,则会出现没有任何节点当选的可

                                      能。此时每个参选节点将等待一个随机时间重新发起参选请求,进行下一

                                      轮选举,直到选举成功。

                            选举出领头哨兵后,哨兵开始对主数据库进行故障恢复,故障恢复的过程如下:

                                   *  所有在线的从数据库中,选择优先级最高的从数据库。优先级可以通过

                                      slave-priority选项来设置。

                                   *  如果有多个最高优先级的从数据库,则复制的命令偏移量(见8.1.7节)

                                      越大(即复制越完整)越优先。

                                   *  如果以上条件都一样,则选择运行ID较小的从数据库。

                       以上三个操作贯穿哨兵进程的整个生命周期中,非常重要,可以说了解了这3个操

                       作的意义就能够了解哨兵工作原理的一半内容了。下面分别详细介绍。           

    7. redis与数据库的数据一致性

      (1)数据库和缓存数据一致性的策略

              先说两种套路:

            (a)先更新数据库在更新缓存

            (b)先更新缓存

              但是无论那种方式,都会有更新一个成功而另外一个失败的情况。

              所以采取的策略是:所以一般的策略是当更新数据时,先删除缓存数据,然后

              更新数据库,而不是更新缓存,等要查询的时候才把最新的数据更新到缓存。

     (2)数据库与缓存双写情况下导致数据不一致问题

              首先说明,该问题是选用(1)中的策略产生的

              场景一

                     当更新数据时,如更新某商品的库存,当前商品的库存是100,现在要更

                     新为99,先更新数据库更改成99,然后删除缓存,发现删除缓存失败了,

                     这意味着数据库存的是99,而缓存是100,这导致数据库和缓存不一致。

              场景一解决方案

                     这种情况应该是先删除缓存,然后在更新数据库,如果删除缓存失败,那

                     就不要更新数据库,如果说删除缓存成功,而更新数据库失败,那查询的

                     时候只是从数据库里查了旧的数据而已,这样就能保持数据库与缓存的一

                     致性。 

              场景二

                     在高并发的情况下,如果当删除完缓存的时候,这时去更新数据库,但还

                     没有更新完,另外一个请求来查询数据,发现缓存里没有,就去数据库里

                     查,还是以上面商品库存为例,如果数据库中产品的库存是100,那么查

                     询到的库存是100,然后插入缓存,插入完缓存后,原来那个更新数据库

                     的线程把数据库更新为了99,导致数据库与缓存不一致的情况。

             场景二解决方案

                    原文写的并不完善,根据我多次读这段话的意思觉得下文中商品的id就是

                    redis中的key。

                    遇到这种情况,可以用队列的去解决这个问,创建几个队列,如20个,根

                    据商品的ID去做hash值,然后对队列个数取摸,当有数据更新请求时,先

                    把它丢到队列里去,当更新完后在从队列里去除,如果在更新的过程中,

                    遇到以上场景,先去缓存里看下有没有数据,如果没有,可以先去队列里

                    看是否有相同商品ID在做更新,如果有也把查询的请求发送到队列里去,

                    然后同步等待缓存更新完成。这里有一个优化点,如果发现队列里有一个

                    查询请求了,那么就不要放新的查询操作进去了,用一个while(true)循

                    环去查询缓存,循环个200MS左右,如果缓存里还没有则直接取数据库的

                    旧数据,一般情况下是可以取到的。  

                            个人认为如果不考虑请求处理的顺序,可以直接查redis里有没有,

                    如果没有,则sleep(200ms),然后在去查redis。  

    8.Redis的过期策略和内存淘汰机制

     (1)过期策略

                     我们set key的时候,都可以给一个expire time(值为-1时,表示永不过期

              ),就是过期时间,指定这个key比如说只能存活1个小时,我们自己可以指定

              缓存到期就失效。如果假设你设置一个一批key只能存活1个小时,那么接下来

              1小时后,redis是怎么对这批key进行删除的?

              答案是:定期删除+惰性删除

                     所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了

              过期时间的key,检查其是否过期,如果过期就删除。

                     注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那

              样就是一场性能上的灾难。

               实际上redis是每隔100ms随机抽取一些key来检查和删除的。但是,

              定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠

              惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这

              个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,

              不会给你返回任何东西。并不是key到时间就被删除掉,而是你查询这个

              key的时候,redis再懒惰的检查一下通过上述两种手段结合起来,保证过

              期的key一定会被干掉。

                     但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然

              后你也没及时去查,也就没走惰性删除,此时会怎么样?

                    如果大量过期key堆积在内存里,导致redis内存块耗尽了,怎么办?

               答案是:走内存淘汰机制。

     (2)内存淘汰机制

                             redis内存淘汰指的是用户存储的一些键被可以被Redis主动地从

                    实例中删除,从而产生读miss的情况,redis为了更好地使用内存,用

                    一定的缓存miss来换取内存的使用效率。 我们可以通过配置

                    redis.conf中的maxmemory这个值来开启内存淘汰功能。maxmemory

                    为0的时候表示我们对Redis的内存使用没有限制。

                    redis内存淘汰过程

                            * 客户端发起了需要申请更多内存的命令(如set)。

                            * Redis检查内存使用情况,如果已使用的内存大于maxmemory

                              则开始根据用户配置的不同淘汰策略来淘汰内存(key),从

                              而换取一定的内存。

                            * 如果上面都没问题,则这个命令执行成功。

                    redis中有如下一些内存淘汰策略

                            noeviction:当内存不足以容纳新写入数据时,新写入操作会报

                                                错,这个一般没人用吧。

                            allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除

                                                最近最少使用的key(这个是最常用的)

                            allkeys-random:当内存不足以容纳新写入数据时,在键空间中,

                                                        随机移除某个key,这个一般没人用吧

                            volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间

                                                的键空间中,移除最近最少使用的key(这个一般不

                                                太合适)

                            volatile-random:当内存不足以容纳新写入数据时,在设置了过

                                                        期时间的键空间中,随机移除某个key

                            volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间

                                               的键空间中,优先移除更早过期时间的key。    

                            键空间:Redis 是一个键值对(key-value pair)数据库服务器,

                                          服务器中的每个数据库都由一个 redis.h/redisDb 结构表

                                          示,其中,redisDb 结构的dict字典保存了数据库中的所

                                          有键值对, 我们将这个字典称为键空间(key space)

                                          说白了上文中的键空间就翻译为key-value对。

                           上面的过期策略看不懂的话,下面有一个白话版:

                                  noeviction : 永不过期,返回错误

                                  allkeys-lru : 删除lru算法的key

                                  allkeys-random:随机删除

                                  volatile-lru:只对设置了过期时间的key进行LRU(默认值)

                                  volatile-random:随机删除即将过期key
                                 
                                  volatile-ttl : 删除即将过期的

                           注意:内存淘汰删除的和key本身的过期时间没有关系,而取决

                                      于内存淘汰测率是哪一种。

                    过期的key对rdb和aof的影响

                    * 过期的key对rdb没有任何影响

                      当从内存数据库持久化数据到RDB文件使,持久化key之前,会检查

                      是否过期,过期的key不进入RDB文件;当从RDB文件恢复数据到内

                      存数据库数据载入数据库之前,会对key先进行过期检查,如果过期,

                      不导入数据库(主库情况)

                    * 过期key对aof没有任何影响

                      当从内存数据库持久化数据到AOF文件时:

                      当key过期后,还没有被删除,此时进行执行持久化操作(该key是

                      不会进入aof文件的,因为没有发生修改命令);当key过期后,在

                      发生删除操作时,程序会向aof文件追加 一条del命令(在将来的以

                      aof文件恢复数据的时候该过期的键就会被删掉)。

                      当aof重写时:

                      重写时,会先判断key是否过期,已过期的key不会重写到aof文件。

     (3)redis和jredis的线程安全

              redis本身是单线程的是线程安全的,Jredis是非线程安全的,可以通过

              JedisPool获取线程安全的Redis实例,如 下代码:

Jedis jedis = null;
try {
    jedis = pool.getResource();
    /// ... 执行相关的Redis操作
    jedis.set("foo", "bar");
    String foobar = jedis.get("foo");
    jedis.zadd("sose", 0, "car"); jedis.zadd("sose", 0, "bike"); 
    Set<String> sose = jedis.zrange("sose", 0, -1);
} finally {
    if (jedis != null) {
        jedis.close();
    }
}
/// ... 当关闭应用程序时:
pool.destroy();

     9.redis事务

        redis的事务时不可嵌套的。

        详细参考文章:

        http://blog.csdn.net/hechurui/article/details/49508749

        http://www.runoob.com/redis/redis-transactions.html

        http://blog.csdn.net/cuipeng0916/article/details/53698774

    10.redis实现分布式锁

       https://www.cnblogs.com/liuyang0/p/6744076.html

    11.redis数据备份

    (1)创建一个定期任务(cron job), 每小时将一个 RDB 文件备份到一个文件

             夹,并且每天将一个 RDB 文件备份到另一个文件夹。确保快照的备份都带

             有相应的日期和时间信息, 每次执行定期任务脚本时, 使用 find 命令来删

             除过期的快照:比如说, 你可以保留最近 48 小时内的每小时快照, 还可以

             保留最近一两个月的每日快照。至少每天一次, 将 RDB 备份到你的数据中

             心之外, 或者至少是备份到你运行 Redis 服务器的物理机器之外。

    (2)Redis 的容灾备份基本上就是对数据进行备份, 并将这些备份传送到多个不

             同的外部数据中心。容灾备份可以在 Redis 运行并产生快照的主数据中心发

             生严重的问题时, 仍然让数据处于安全状态。有的Redis 用户是创业者,他

             们没有大把大把的钱可以浪费, 所以下面介绍的都是一些实用又便宜的容灾

             备份方法:

             Amazon S3 ,以及其他类似 S3 的服务,是一个构建灾难备份系统的好地方。

            最简单的方法就是将你的每小时或者每日 RDB 备份加密并传送到 S3 。 对数

            据的加密可以通过 gpg -c 命令来完成(对称加密模式)。 记得把你的密码放

            到几个不同的、安全的地方去(比如你可以把密码复制给你组织里最重要的人

            物)。 同时使用多个储存服务来保存数据文件,可以提升数据的安全性。传

            送快照可以使用 SCP 来完成(SSH 的组件)。 以下是简单并且安全的传送

            方法:

           买一个离你的数据中心非常远的 VPS(虚拟专用服务器) , 装上 SSH , 创建

           一个无口令的 SSH客户端 key ,并将这个 key 添加到 VPS 的 authorized_keys

           文件中, 这样就可以向这个 VPS传送快照备份文件了。 为了达到最好的数据安

           全性,至少要从两个不同的提供商那里各购买一个 VPS 来进行数据容灾备份。

           需要注意的是, 这类容灾系统如果没有小心地进行处理的话,是很容易失效的。

           最低限度下, 你应该在文件传送完毕之后, 检查所传送备份文件的体积和原始

           快照文件的体积是否相同。如果你使用的是 VPS , 那么还可以通过比对文件的

           SHA1 校验和来确认文件是否传送完整。另外,你还需要一个独立的警报系统,

           让它在负责传送备份文件的传送器(transfer)失灵时通知你。

    12.Redis的数据结构

         学习本节,如果看不明白可以先看以下博客

         https://www.cnblogs.com/wujinsen/p/8949293.html

         

                Redis内部,使用字典来存储不同类型的数据,如图中的dictht,字典由一组dicEntry组

         成,其中包含了指向key和value的指针以及指向下一个dicEntry的指针。

       (1)RedisObject

                       在Redis中所有的对象都被封装成了RedisObject(Redis 内部使用一个 redisObject

                对象来表示所有的 key 和 value),如上图中浅绿色的模块,RedisObject包括了对象的

                类型(Redis支持的5种数据类型),另外还包括了具体对象的存储方式,比如最右边虚

                线模块内的几种类型。

                

 

                上图说明:

                type:代表我们的5中数据类型(string、List、Hash、Set、ZSet)

                encoding:代表具体的实现方式,其内容如下表:

                 

                                  扩展:为什么要有这么多编码方式?

                                             为的是更合理的使用内存。编码方式一旦升级是不能降级的

                lru:LRU_BITS:保存的是最近一次访问的时间戳,用来做内存清理。

                refcount:引用计数器,redis中0-9999是共享对象。使用命令 object refcount key查看

                                key被引用了几次。

                我们来结合类型来介绍具体的数据存储方式:

       (2)数据的存储方式

              (a)String

                    

                       

 

 

 

                    String类型的内部是通过SDS来实现,SDS类似于Java中的ArrayList,可以通过预

                    分配冗余空间的方式来减少内存的频繁分配。

                    有三种编码方式:

                    --  INT

                        整数类型

                    --  EMBSTR

                        字符长度>44字节

                    --  RAW

                        字符长度<44字节

                    Embstr和raw区别

                    embstr和raw的区别是,embstr在创建字符串对象的时候,会分配一次空间,这个空间

                    包含redisObject对象结构和sdshdr结构(保存字符串),而raw则会进行两次分配,分

                    表分配空间给redisObject和sdshdr。所以在字符串较短时,使用embstr会有一些优势:

                    分配次数降低,释放次数降低(因为只需要释放一个空间,而raw需要释放两个空间),

                    字符串连续。

              (b)List

                       3.2之前,List类型有ZipList压缩链表和LinkedList双链表实现;3.2开始,只有quicklist

                       ZipList是存存储在一段连续的

                       内存空间上,存储效率高,但是它不利于修改操作,适用于数据较少的情况;

                       LinkedList在插入节点上复杂度很低,但它的内存开销很大,每个节点的地址不连

                       续,容易产生内存碎片;此外在3.2版本后增加了quicklist,quicklist结合了两者的

                       优点,quicilist本身是一个双向无环链表,每一个节点都是一个ziplist。

                       扩展:什么是ziplist

                               ziplist是Redis为了节约内存而开发的,具体结构如下:

                      

 

         

                    

                           ziplist节点构成:

                         

                          --  previous_entry_length

                              previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度。

                          --  encoding

                              encoding属性记录了节点的content属性所保存数据的类型以及长度

                          --  content

                              content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的

              (c)Hash类型

                              Hash类型有ziplist和字典(或叫hashtable)两种实现,当hash表中所有的key和

                       value字符串长度都小于64字节且键值对的数量小于512个时,使用ziplist来节省空间,

                       超过时,使用字典。查找和增加同Java中的相同。

                       扩展:什么是字典?

                    

 

                            

                       --  dictht ht[2]:表示两个hash表,一个是正在使用的,一个是用来做扩容的。

                                                hash表有一个负载因子,当超过负载因子时,效率就会变差,所以就

                                                要使用两个hash表,一个是正在使用的,一个是用来做扩容的。 一旦

                                                超过这个因子做hash扩容的时候,它会把正在使用的数据慢慢的迁移

                                                到空的hash表里面去。迁移完成之后用新的hash替换原来的hash(指针

                                                互换)。

                                                       *  为什么使用两个hash做扩容,而不像Java中直接做扩容?

                                                          执行扩容时等待时间变长了,而Redis是单线程的,所以它接受

                                                          不了这样的等待。

                                                       *  访问的时候发现key被迁移了,正在使用的hash表里不存在这个

                                                           key,怎么办?

                                                           它可以做路由的功能,去扩容的hash获取key。         

                       --  hash表

                           同Java中的一样,也是一个数组的链表

              (d)set

                       set类型的内部实现可以是intset或者是字典(或称HashTablle),当集合中元素小于512

                       且所有的数据都是数值类型时,才会使用Intset,否则使用hashtable

                       https://www.cnblogs.com/thrillerz/p/4505550.html

              (e)sorted set

                       实现可以是ziplist或者是skiplist跳表,当有序集合中的元素数量小于128个时,并 

                       且所有元素长度都小于64字节时会使用ziplist,否则会转化成skiplist跳表

                       什么是跳表:

                       https://www.cnblogs.com/thrillerz/p/4505550.html

    12. Redis PipeLine(管道)

                 Redis的pipeline(管道)功能在命令行中没有,但redis是支持pipeline的,而且在各个语言版的

          client中都有相应的实现。 由于网络开销延迟,就算redis server端有很强的处理能力,也会由于 

          收到的client消息少,而造成吞吐量小。

        (1)为什么要使用PipeLine

                                Redis客户端与Redis服务器之间使用TCP协议进行连接,一个客户端可以通过一个

                         socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务器处理,

                         redis处理完请求命令后会将结果通过响应报文返回给client,因此当执行多条命令的时候都需

                         要等待上一条命令执行完毕才能执行。比如:

                         

                         其执行过程如下图所示:

                    

 

                   由于通信会有网络延迟,假如client和server之间的包传输时间需要0.125秒。那么上面的三个命令

                   6个报文至少需要0.75秒才能完成。这样即使redis每秒能处理100个命令,而我们的client也只能一

                   秒钟发出四个命令。这显然没有充分利用 redis的处理能力。

                          而管道(pipeline)可以一次性发送多条命令并在执行完后一次性将结果返回,pipeline通过减

                   少客户端与redis的通信次数来实现降低往返延时时间,而且Pipeline 实现的原理是队列,而队列的

                   原理是时先进先出,这样就保证数据的顺序性。 Pipeline 的默认的同步的个数为53个,也就是说

                   arges中累加到53条数据时会把数据提交。其过程如下图所示:client可以将三个命令放到一个tcp报

                   文一起发送,server则可以将三条命令的处理结果放到一个tcp报文返回。

                 

        (2)使用pipeline应该注意的点

                        需要注意到是用 pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的

                 处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。具体多少合适

                 需要根据具体情况测试。

                        另外,编码时还应该注意,pipeline期间将“独占”链接,此期间将不能进行非“管道”类型的其他操

                 作,直到pipeline关闭;如果你的pipeline的指令集很庞大,为了不干扰链接中的其他操作,你可以为

                 pipeline操作新建Client链接,让pipeline和其他正常操作分离在2个client中。

        (3)pipeline的适用场景

                        有些系统可能对可靠性要求很高,每次操作都需要立马知道这次操作是否成功,是否数据已经写

                 进redis了,那这种场景就不适合。

                   还有的系统,可能是批量的将数据写入redis,允许一定比例的写入失败,那么这种场景就可以使

                 用了,比如10000条一下进入redis,可能失败了2条无所谓,后期有补偿机制就行了,比如短信群发

                 这种场景,如果一下群发10000条,按照第一种模式去实现,那这个请求过来,要很久才能给客户端

                 响应,这个延迟就太长了,如果客户端请求设置了超时时间5秒,那肯定就抛出异常了,而且本身群发

                 短信要求实时性也没那么高,这时候用pipeline最好了。

        (3)pipeline和普通模式示例                 

/*
     * 测试普通模式与PipeLine模式的效率: 
     * 测试方法:向redis中插入10000组数据
     */
    public static void testPipeLineAndNormal(Jedis jedis)
            throws InterruptedException {
        Logger logger = Logger.getLogger("javasoft");
//普通模式开始
long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { jedis.set(String.valueOf(i), String.valueOf(i)); } long end = System.currentTimeMillis(); logger.info("the jedis total time is:" + (end - start)); //普通模式结束
//Pipeline模式开始 Pipeline pipe
= jedis.pipelined(); // 先创建一个pipeline的链接对象 long start_pipe = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { pipe.set(String.valueOf(i), String.valueOf(i)); } pipe.sync(); // 获取所有的response long end_pipe = System.currentTimeMillis(); logger.info("the pipe total time is:" + (end_pipe - start_pipe));
        //Pipeline模式结束
BlockingQueue<String> logQueue = new LinkedBlockingQueue<String>(); long begin = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { logQueue.put("i=" + i); } long stop = System.currentTimeMillis(); logger.info("the BlockingQueue total time is:" + (stop - begin)); }

    13. Redis 事务

        (1)说一下Redis对两种类型错误及对其的处理

                  --  在调用EXEC命令之前就失败。

                      一个命令可能会在被放入队列时失败。因此,事务有可能在调用EXEC命令之前就发生错误。例

                      如,这个命令可能会有语法错误(参数的数量错误、命令名称错误,等等),或者可能会有某些

                      临界条件(例如:如果使用maxmemory指令,为Redis服务器配置内存限制,那么就可能会有内

                      存溢出条件)。 

                             Redis 2.6.5版本之前,可以使用Redis客户端检测第一种类型的错误,在调用EXEC命令之

                      前,这些客户端可以检查被放入队列的命令的返回值:如果命令的返回值是QUEUE字符串,那

                      么就表示已经正确地将这个命令放入队列;否则,Redis将返回一个错误。如果将某个命令放入

                      队列时发生错误,那么大多数客户端将会中止事务,并且丢弃这个事务。

                             然而,从Redis 2.6.5版本开始,服务器会记住事务积累命令期间发生的错误。然后,

                      Redis会拒绝执行这个事务,在运行EXEC命令之后,便会返回一个错误消息。最后,Redis会

                      自动丢弃这个事务。

                  --   在调用EXEC命令之后失败。

                       事务中的某个命令可能会执行失败。例如,我们对某个键执行了错误类型的操作(例如,对一

                       个字符串(String)类型的键执行列表(List)类型的操作)。

                            此时,Redis不会进行任何特殊处理:在事务运行期间,即使某个命令运行失败,所有其

                       他的命令也将会继续执行(这也是Redis事务的缺陷,它不支持回滚)

        (2)相关命令介绍

                (a)MULTI

                         用于标记事务块的开始。Redis会将后续的命令逐个放入队列中,然后才能使用EXEC命令原

                         子化地执行这个命令序列。

                         这个命令的运行格式如下所示:

                         MULTI

                         这个命令的返回值是一个简单的字符串,总是OK

                (b)EXEC

                         在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。

                         当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令,

                         这种方式利用了检查再设置(CAS)的机制。这个命令的运行格式如下所示:

                         EXEC

                         这个命令的返回值是一个数组,其中的每个元素分别是原子化事务中的每个命令的返回值。

                         当使用WATCH命令时,如果事务执行中止,那么EXEC命令就会返回一个Null值。

                (c)DISCARD

                         清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。

                         如果使用了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控。

                         这个命令的运行格式如下所示:

                         DISCARD

                         这个命令的返回值是一个简单的字符串,总是OK。

                (c)WATCH

                         当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。这个命令的

                         运行格式如下所示:

                         WATCH key [key ...]

                         这个命令的返回值是一个简单的字符串,总是OK。

                         对于每个键来说,时间复杂度总是O(1)

                         注:用了WATCH命令也不能保证事务的真正意义上的回滚,执行EXEC命令期间出现错误,

                                是不能回滚的。

                (c)UNWATCH

                         清除所有先前为一个事务监控的键。

                         如果你调用了EXEC或DISCARD命令,那么就不需要手动调用UNWATCH命令。

                         这个命令的运行格式如下所示:

                         UNWATCH

                         这个命令的返回值是一个简单的字符串,总是OK。

                         时间复杂度总是O(1)。

    12.redis的五种数据类型及其常用命令常用命令

       (1)String(重点)

                * set key value:设置key的值,若存在则覆盖

                * setnx key value:SET if Not exists,若key存在则不操作,所以不会覆

                                            盖原值。

                * setex:e表示expired。该命令同set命令,同时可以指定key的过期时间。

                            注意这是一个原子命令,例子:

                            setex color 10 red

                            指定key(color)的值为red,过期时间10秒,10秒后返回nil(

                            redis里nil表示空)。

                * setrange:替换字符串。

                                 例子:set email 174754613@163.com

                                 setrange email  10 ww

                                 表示重第10位开始替换,替换后的字符串为ww 

                * getset:返回旧值并设置新值

                * incr和decr:对一个值进行递增和递减,这是一个原子命令

                * incrby和decrby:对某个值进行指定长度的递增和递减

                                           语法:

                                           incrby key [步长]

                                           decrby key [步长]

                * mset key1 value1 key2 value2 ... keyN valueN:设置这些key的值,若

                                                                                                存在则覆盖

                * mget key1 key2...keyN:获取这些key的value,例子:

                                                        redis> SET key1 "Hello"

                                                        "OK"

                                                        redis> SET key2 "World"

                                                        "OK"

                                                        redis> MGET key1 key2 nonexisting

                                                        1) "Hello"

                                                        2) "World"

                                                        3) (nil)

                                                        redis> 

                * append key value:向key的字符串追加拼接

                * strlen [key]:获取key对应value的长度

                * del key :删除指定的key

                * mset key1 value1 key2 value2 ... keyN valueN:设置这些key的值,若

                                                                                                存在则覆盖。

               * rename key:重命名key

    (2)hash(重点)

           (a)hash介绍

                    类似于Java中的HashMap

                    Hash类型是String类型的field和value的映射表,或者说一个String集

                    合。它特别适合存储对象,相比较而言将一个对象存储在Hash类型

                    中要比存储在String类型中更节省空间。

          (b)hash的有两种使用方式:

                 (i)一条数据对应一个hash。如下表:

                    

                        需要两个hash表,如下

                        hash1(id:1,name:zhangsan,age:20)

                        hash2(id:2,name:lisi,age:25)

                        对应redis命令:

                        HSET hash1 id 1 name zhangsan age 20

                        HSET hash2 id 2 name lisi age 25

                 (ii)一个hash存多个对象

                         表还是如(i)中的,filed可以是uuid,value是对象的JSON字符

                         串hash1(1:Object1.toJSON,2:Object2.toJSON)

                         对应redis命令:

                         HSET hash1 id Object1.toJSON hash2 id Object2.toJSON(总决定这个不对应该是下面的)

                         HSET hash1 1 Object1.toJSON 2 Object2.toJSON

                         HSET key field value:key是对象名,field是属性,value是值。

                         HMSET key field value [field value ...]:同时设置多个属性。

                         HGET key field:获取该对象的该属性。 

                 (iii)Hash命令介绍

                           * HMGET key field value [field value ...]:获取多个属性值。

                           * HGETALL key:获取对象的所有信息。

                           * HKEYS key:获取对象的所有属性。

                           * HVALS key:获取对象的所有属性值。

                           * HDEL key field:删除对象的该属性。

                           * HEXISTS key field:查看对象是否存在该属性。

                           * HINCRBY key field value:原子自增操作,只能是integer的

                                                                         属性值可以使用。

                           * HLEN key:Return the number of entries (fields) contained

                                                 in the hash storedat key.获取属性的个数。

                ???redis除了String类型存数据的时候,需要自己拼数据吗?

    (3)List(简单的说它就是java中的Queue)

           (i)简介

                   List类型是一个链表结构的集合,其主要功能有push、pop、获取元

                   素等。更详细的说,List类型是一个双端链表的结构,我们可以操作

                   集合的头部或者尾部进行添加删除元素,List的设计非常简单精巧,

                   既可以作为栈,也可以作为队列,满足绝大多数需求。有些公司直

                   接使用该类型做消息队列。

           (ii)常用命令

                    * lpush keyList value:向keyList左边(头部,先进后出,该类型相

                                                      当于栈)添加元素。

                                                      取值时使用命令:

                                                      lrange keyList 0 -1;

                    * rpush keyList value:向keyList右边(尾部)添加元素(先进先

                                                      出,该类型相当于队列),向后加,r表示右

                                                      边。   

                                                      取值时使用命令:

                                                      lrange keyList 0 -1;               

                    * lrange keyList beginIndex endIndex:获取keyList的元素,用两

                                                                                  端的索引取出子集,

                                                                                  endIndex=-1则表示全部

                                                                                  取出。

                    * llen keyList:获取keyList的长度大小。

                    * lpop keyList:取出并移除keyList第一个元素,左边的元素。

                    * rpop keyList:取出并移除keyList最后一个元素,右边的元素。

                    * rpoplpush:该命令是一个原子命令。该命令包含如下两个步骤:

                                          先从尾部删除元素,然后子从头部加入元素。

                    * lindex key index:获取该索引下的元素。

                    * lrem key count value:删除count个value。(count为正数,从

                                                           头开始,删除count个value元素;count

                                                           为负,则从尾部向头删除|count|个value

                                                           元素;count为0,则所有的元素为value

                                                           的都删除)

                    * LSET key index value:设置索引为index下的元素为value.超出

                                                             索引范围报错。

                    * Linsert:在指定集合元素前插入元素(该方法基本不用)。

                                  例子:

                                  集合元素{one,two}

                                  执行命令:

                                  Linsert list3 before “one” "three"

                                  结合元素变为:{three,one,two}

                    * LTRIM key start end:清空索引在start 和end之外的元素,索引

                                                          从0开始,两端保留,两端之外的清空。

                    * RPOPLPUSH srckey dstkey:源队列srckey,目标队列dstkey,

                                                                    将srckey的最后一个移除,并放到

                                                                    dstkey的第一个。

           (iii)redis实现消息队列思路

                     使用命令rpush和lpop、rpop

                     * 使用rpush放入数据到list;

                     * 使用命令lpop或rpop方法从list的头部或尾部删除数据并返回删

                       除的数据

    (4)set(像Java List的升级版)

             set是string类型的无序集合(zset是string类型有序集合),set是通

             过hashtable实现的,对集合我们可以取交集、并集、差集。set中的

             元素不允许重复。

             sadd key value : 向set添加元素。

             srem key value :从set中移除元素。

             smembers key : 取出所有set元素。

             SISMEMBER key value: 查看value是否存在set中。

             SUNION key1 key2 ... keyN:将所有key合并后取出来,相同的值只取一次。

             key1 = {a,b,c,d}

             key2 = {c}

             key3 = {a,c,e}

             SUNION key1 key2 key3 = {a,b,c,d,e}

             scard key : 获取set中元素的个数。

             SRANDMEMBER key: Return a random element from a Set, without removing the

                                                  element.随机取出一个。

             SDIFF key1 key2 ... keyN:获取第一set中不存在后面几个set里的元素。

             SDIFFSTORE dstkey key1 key2 ... keyN:和sdiff相同,获取key1中不存在其他key

                                                                                 里的元素,但要存储到dstkey中。

             SINTER key1 key2 ... keyN:取出这些set的交集。

             SINTERSTORE dstkey key1 key2 ... keyN:取出这些key的交集并存储到dstkey。

             SMOVE srckey dstkey member:将元素member从srckey中转移到dstkey中,这个

                                                                   操作是原子的。

    (5)zset(相当于java中有序的set)

             zset是string类型有序集合

             ZADD key score member:向有序set中添加元素member,其中score为分数,默认

                                                         升序。

             ZRANGE key start end [WITHSCORES]:获取按score从低到高索引范围内的元素,

                                                                                 索引可以是负数,-1表示最后 一个,-2表

                                                                                 示倒数第二个,即从后往前。withscores

                                                                                 可选,表示获取包括分数。

            ZREVRANGE key start end [WITHSCORES]:同上,但score从高到低排序。

            ZCOUNT key min max:获取score在min和max范围内的元素的个数。

            ZCARD key:获取集合中元素的个数。

            ZINCRBY key increment member:根据元素,score原子增加increment.。

            ZREMRANGEBYSCORE key min max:清空集合内的score位于min和max之间的元

                                                                             素。

            ZRANK key member:获取元素的索引(照score从低到高排列)。

            ZREM key member:移除集合中的该元素。

            ZSCORE key member:获取该元素的score。

            更多详细参考:

            http://www.cnblogs.com/woshimrf/p/5198361.html

    13.redis的五种数据类型及使用场景

         https://www.cnblogs.com/qunshu/p/3196972.html

 

posted @ 2019-05-03 08:32  jialanshun  阅读(757)  评论(0编辑  收藏  举报