Redis

1.介绍

Redis是NoSQL的一种,NoSQL也称“非关系型数据库”,不同于SQL(如mySQL,Oracle),NoSQL主要是为了提高效率的(如io效率),其主要特征是数据不是依靠业务逻辑以行列的形式存放的,而是key-value的形式,而且主要存储位置是内存(这也是为什么效率高,当然redis也支持持久化操作->周期性的硬盘存储)。

2.安装,启动与关闭

2.1.安装

这里安装linux版本的redis6.2.4,压缩包放在/usr/loclal/src中后解压,进入redis6.2.4,执行make命令(./configure不需要),然后make install ,会将redis的相关文件放在==/usr/local/bin==下。

  • redis-benchmark:性能测试工具
  • redis-check-rdb:修复有问题的RDB文件
  • redis-check-aof:修复有问题的AOF文件
  • redis-sentinel:集群时使用
  • redis-server:Redis的服务器启动命令
  • redis-cli:客户端操作

2.2.启动

2.2.1.前台启动[不推荐]

前台启动就是直接在/usr/local/bin中执行redis-server即可,但是此时就不能再这个窗口进行其他操作了,一但关闭redis也就关闭了.

2.2.2.后台启动[推荐]

后台启动就没有关掉窗口就关闭的问题,实际上就是将redis在后台启动,需要在启动时指明配置文件。原配置文件在/usr/local/src/redis6.2.4/redis.conf,

  • 推荐将这个文件备份出来,这里放在了/xiao/redis.conf。
  • 修改这个文件的daemonize属性为yes【意思就是启动方式为后台启动】
  • 在/usr/local/bin中启动【redis-server /xiao/redis.conf】(后面的是需要的配置文件位置)

执行server-cli开启客户端连接[ping结果为PONG表示ping通了]。

2.3.关闭

2.3.1.单实例关闭

  • 方式一:redis-cli shutdown
  • 方式二:先redis-cli进入连接模式,再shutdown

2.3.2.多实例关闭

ps -ef | grep redis找到redis的进程号使用kill命令关闭。

3.Redis五大数据类型

3.1.键操作[key]

在redis中默认支持16个库,从0到15,默认使用0号库,可以使用select [dbid] 切换。

  • keys * [查询当前库中所有的key]
  • exists key [判断某个key是否存在]
  • type key [查看指定key的类型]
  • del key [删除指定的key数据]
  • unlink key [根据value选择非阻塞删除] (执行后显示已删除,但是可能不会立刻从库数据中删除,可能会在后续删除)
  • expire key 20 [未知的的key设置过期时间为20秒]
  • ttl key 查看还有多少秒过期,-1为永不过期,-2为已过期 [没有设置过期时间的key默认就是永不过期]。
  • dbsize 查看当前数据库的key的数量
  • flushdb 清空当前库
  • flushall 通杀所有库

3.2.字符窜类型[String]

  • String是Redis中最基本的类型,一个key对应一个value
  • String是二进制安全的,意味着Redis的String可以包含任何数据,比如jpg图片或者序列化的图像。
  • 一个字符串value最多可以是512MB。

3.2.1.常用命令

  • set key value:给数据库中名称为key的string赋予值value
  • get key :返回数据库中名称为key的string的value
  • mget key1, key2,…, key N :返回库中多个string的value
  • setnx key, value :添加string,名称为key,值为value【只有当key不存在时才设置】
  • setex key, time, value :向库中添加string,设定过期时间time
  • mset key N, value N :批量设置多个string的值【具有原子性】
  • msetnx key N, value N :如果所有名称为key i的string都不存在就设置【具有原子性】
  • incr key :名称为key的string增1操作
  • incrby key integer:名称为key的string增加integer
  • decr key :名称为key的string减1操作
  • decrby key integer :名称为key的string减少integer
  • append key value:名称为key的string的值附加value
  • substr key start end:返回名称为key的string的value的子串

Redis中的String实际上是可变字符串,类似于java的ArrayList

3.3.列表类型[List]

List是单键多值,按照插入顺序排序,可以添加一个元素到列表的头部或尾部。

其底层是一个双向链表,对两端的操作性能很高,而通过下标索引的方式性能比较差。

3.3.1.常用命令

  • rpush(key, value):在名称为key的list尾添加一个值为value的元素
  • lpush(key, value):在名称为key的list头添加一个值为value的 元素
  • llen(key):返回名称为key的list的长度
  • lrange(key, start, end):返回名称为key的list中start至end之间的元素【lrange 0 -1 表示所有的元素】
  • ltrim(key, start, end):截取名称为key的list
  • lindex(key, index):返回名称为key的list中index位置的元素
  • lset(key, index, value):给名称为key的list中index位置的元素赋值
  • lrem(key, count, value):删除count个key的list中值为value的元素
  • lpop(key):返回并删除名称为key的list中的首元素
  • rpop(key):返回并删除名称为key的list中的尾元素
  • blpop(key1, key2,… key N, timeout):lpop命令的block版本。
  • brpop(key1, key2,… key N, timeout):rpop的block版本。
  • rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部

3.4.集合类型[set]

set与list类似,不过set由于使用hashcode实现的,所以元素的增删改查复杂度都是O(1)且不会有重复。

3.4.1.常用命令

  • sadd(key, member):向名称为key的set中添加元素member
  • srem(key, member) :删除名称为key的set中的元素member
  • spop(key) :随机返回并删除名称为key的set中一个元素
  • smove(srckey, dstkey, member) :移到集合元素
  • scard(key) :返回名称为key的set的基数
  • sismember(key, member) :member是否是名称为key的set的元素
  • sinter(key1, key2,…key N) :求交集
  • sunion(key1, (keys)) :求并集
  • sdiff(key1, (keys)) :求差集
  • smembers(key) :返回名称为key的set的所有元素
  • srandmember(key,n) :随机返回名称为key的set的n个元素

3.5.哈希类型[Hash]

  • Redis hash是一个键值对集合
  • hash是一个string类型的filed与value的映射表,适合存储对象
  • 类似于java的Map

3.5.1.常用命令

  • hset(key, field, value):向名称为key的hash中添加元素field
  • hget(key, field):返回名称为key的hash中field对应的value
  • hmget(key, filed1,filed2 ……):返回名称为key的hash中field i对应的value
  • hmset(key, field1, value1 ,filed2,value2 ……):向名称为key的hash中添加元素field
  • hincrby(key, field, integer):将名称为key的hash中field的value增加integer
  • hexists(key, field):名称为key的hash中是否存在键为field的域
  • hdel(key, field):删除名称为key的hash中键为field的域
  • hlen(key):返回名称为key的hash中元素个数
  • hkeys(key):返回名称为key的hash中所有键
  • hvals(key):返回名称为key的hash中所有键对应的value
  • hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value

3.6.有序集合类型[Zset]

  • zset与普通的set十分类似,是一个没有重复元素的字符串集合。
  • 但是zset是有序的,每个成员都关联了一个评分(score),这个评分被用来按照从最低分到最高分的方式排序集合中的成员,集合中的成员是惟一的,但是评分可以重复。
  • 因为元素是有序的,所以也可以根据评分(score)或次序(position)获取元素

3.6.1.常用命令

  • zadd key score member[{score member}…]

    创建或设置指定key对应的有序集合,根据每个值对应的score来排名,升序。例如有命令 zadd key1 10 A 20 B 30 D 40 C;那么真实排名是 A B D C

  • zrem key member

    删除指定key对应的集合中的member元素

  • zcard key

    返回指定key对应的有序集合的元素数量

  • zincrby key increment member

    将指定key对应的集合中的member元素对应的分值递增加 increment

  • zcount key min max

    返回指定key对应的有序集合中,分值在min~max之间的元素个数

  • zrank key member

    返回指定key对应的有序集合中,指定元素member在集合中排名,从0开始切分值是从小到大升序

  • zscore key member

    返回指定key中的集合中指定member元素对应的分值

  • zrange key min max [withscores]

    返回指定key对应的有序集合中,索引在min~max之间的元素信息,如果带上 withscores 属性的话,可以将分值也带出来

  • zrevrank key member

    返回指定key对应的集合中,指定member在其中的排名,注意排名从0开始且按照分值从大到小降序

  • zrevrange key start end [withscores]

    指定key对应的集合中,分值在 start~end之间的降序,加上 withscores 的话可以将分值以及value都显示出来

  • zrangebyscore key start end [withscores]

    同 zrange命令不同的是,zrange命令是索引在startend范围的查询,而zrangebyscore命令是根据分值在startend之间的查询且升序展示

  • zrevrangebyscore key max min [withscores]

    同zrangebyscore命令不同的是,zrangebyscores是根据分值从小到大升序展示,而zrevrangebyscore命令是从max到min降序展示

  • zremrangebyrank key start end

    移除指定key对应集合中索引在start~end之间(包括start和end本身)的元素

  • zremrangebyscore by min max

    同zremrangebyrank命令类似,不同的该命令是删除分值在min~max之间的元素

  • zinterstore desk-key key-count key…

    获取指定数量的key的交集。例如有 key1:{10:A,20:B,30:C},key2{40:B,50:C,60:D},那么命令 zinterstore key3 2 key1 key2 意思就是 将key1 key2这两个集合的交集 赋给key3,如何获取key1与key2的交集呢。 key1中存在 A B C,key2中存在 B C D,那么交集就是 B 和 C,且 B与C对应的score也会叠加,即 key3{B:20+40=60,C:30+50=80}

  • zunionstore desk-key key-count key…

    获取指定数量key的并集,例如有 key1{10:A,20:B,30:C},key2{40:B,50:C,60:D},可以看出 A和D不是key1与key2共有的,但是并集中只要存在就会记录进去,然后B与C是共有的,即 并集的结果就是 key3{10:A,B:60,D:60,C:80}

4.Redis特殊数据类型

4.1.GEOSPATAL[地理位置]

这个数据类型就是用来对地理位置的操作,其底层实际上就是zset,只有六个命令。

1.geoadd[添加地理位置]

格式:

  • geoadd key longitude latitude member [longitude latitude member….]
  • longitude:纬度
  • latidude:经度
  • member:成员/元素

规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!

有效的经度从-180度到180度。

有效的纬度从-85.05112878度到85.05112878度

2.geopos[获取当前定位]

  • 格式:geopos key member [member…]:查询一个或多个城市的经纬度。

3.geodist[计算两地之间距离]

  • 格式:geodist key member1 member2 [unit]:计算两个城市/人之间的举例
  • unit表示单位

    • m 表示单位为米。
    • km 表示单位为千米。
    • mi 表示单位为英里。
    • ft 表示单位为英尺。

4.georadius[以指定经纬为中心查找指定半径内的元素]

  • 格式:georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count n]
  • withcoord:显示坐标
  • witthdist:显示距离
  • count n:指定查找的数量

5.georadiusbymember[以元素为中心查找半径以内的元素]

和georadius语法一样,只是把经纬换成了元素。

6.geohash[以字符串形式表示返回的经纬度]

geohash key [key…]

4.2.Hyperloglog[基数计算]

基数:一系列元素中不重复元素的个数。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

1.常用方法

序号 命令及描述
1 PFADD key element [element …] 添加指定元素到 HyperLogLog 中。
2 PFCOUNT key [key …] 返回给定 HyperLogLog 的基数估算值。
3 PFMERGE destkey sourcekey [sourcekey …] 将多个 HyperLogLog 合并为一个 HyperLogLog

2.注意

显然HyperLogLog就是一个计算基数的作用,那为什么不用set呢?

  • 优点:当处理大数据量时,用set{实际上只要是将所有的数据表示出来}都会占用很大的空间且效率较低,这时如果可以接受较小的误差的话HyperLogLog可以较好的实现占用仅仅16KB空间统计出不论多大量的数据的基数。
  • 缺点:底层的算法实际上是近似计算,所以有误差的存在,如果不能有一点误差,就不能用了。

至于HyperLogLog的实现原理,实际上是伯努利试验的近似的使用。

3.实现原理

伯努利试验

在认识为什么HyperLogLog能够使用极少的内存来统计巨量的数据之前,要先认识下伯努利试验

伯努利试验是数学概率论中的一部分内容,它的典故来源于抛硬币

硬币拥有正反两面,一次的上抛至落下,最终出现正反面的概率都是50%。假设一直抛硬币,直到它出现正面为止,我们记录为一次完整的试验,间中可能抛了一次就出现了正面,也可能抛了4次才出现正面。无论抛了多少次,只要出现了正面,就记录为一次试验。这个试验就是伯努利试验

那么对于多次的伯努利试验,假设这个多次为n次。就意味着出现了n次的正面。假设每次伯努利试验所经历了的抛掷次数为k。第一次伯努利试验,次数设为k1,以此类推,第n次对应的是kn

其中,对于这n伯努利试验中,必然会有一个最大的抛掷次数k,例如抛了12次才出现正面,那么称这个为k_max,代表抛了最多的次数。

伯努利试验容易得出有以下结论:

  1. n 次伯努利过程的投掷次数都不大于 k_max。
  2. n 次伯努利过程,至少有一次投掷次数等于 k_max

最终结合极大似然估算的方法,发现在nk_max中存在估算关联:n = 2^(k_max) 。这种通过局部信息预估整体数据流特性的方法似乎有些超出我们的基本认知,需要用概率和统计的方法才能推导和验证这种关联关系。

例如下面的样子:

  1. 第一次试验: 抛了3次才出现正面,此时 k=3n=1
  2. 第二次试验: 抛了2次才出现正面,此时 k=2n=2
  3. 第三次试验: 抛了6次才出现正面,此时 k=6n=3
  4. n 次试验:抛了12次才出现正面,此时我们估算, n = 2^12

假设上面例子中实验组数共3组,那么 k_max = 6,最终 n=3,我们放进估算公式中去,明显: 3 ≠ 2^6 。也即是说,当试验次数很小的时候,这种估算方法的误差是很大的。

估算的优化

在上面的3组例子中,我们称为一轮的估算。如果只是进行一轮的话,当 n 足够大的时候,估算的误差率会相对减少,但仍然不够小。

那么是否可以进行多轮呢?例如进行 100 轮或者更多轮次的试验,然后再取每轮的 k_max,再取平均数,即: k_mx/100。最终再估算出 n。下面是LogLog的估算公式:

上面公式的DVLL对应的就是nconstant是修正因子,它的具体值是不定的,可以根据实际情况而分支设置。m代表的是试验的轮数。头上有一横的R就是平均数:(k_max_1 + ... + k_max_m)/m

这种通过增加试验轮次,再取k_max平均数的算法优化就是LogLog的做法。而 HyperLogLogLogLog的区别就是,它采用的不是平均数,而是调和平均数调和平均数平均数的好处就是不容易受到大的数值的影响。下面举个例子:

  1. > 求平均工资:
  2. > A的是1000/月,B30000/月。采用平均数的方式就是: (1000 + 30000) / 2 = 15500
  3. > 采用调和平均数的方式就是: 2/(1/1000 + 1/30000) 1935.484

明显地,调和平均数平均数的效果是要更好的。下面是调和平均数的计算方式, 是累加符号。

Hyperloglog实现

上面的内容我们已经知道,在抛硬币的例子中,可以通过一次伯努利试验中出现的k_max来估算n

那么这种估算方法如何和下面问题有所关联呢?

统计 APP或网页 的一个页面,每天有多少用户点击进入的次数。同一个用户的反复点击进入记为 1 次

HyperLogLog是这样做的。对于输入的数据,进行下面几个步骤:

1.比特串

通过hash函数,将数据转为比特串,例如输入5,便转为:101。为什么要这样转化呢?

是因为要和抛硬币对应上,比特串中,0 代表了反面,1 代表了正面,如果一个数据最终被转化了 10010000,那么从右往左,从低位往高位看,我们可以认为,首次出现 1 的时候,就是正面。

那么基于上面的估算结论,我们可以通过多次抛硬币实验的最大抛到正面的次数来预估总共进行了多少次实验,同样也就可以根据存入数据中,转化后的出现了 1 的最大的位置 k_max 来估算存入了多少数据。

2.分桶

分桶就是分多少轮。抽象到计算机存储中去,就是存储的是一个以单位是比特(bit),长度为 L 的大数组 S ,将 S 平均分为 m 组,注意这个 m 组,就是对应多少轮,然后每组所占有的比特个数是平均的,设为 P。容易得出下面的关系:

  • L = S.length
  • L = m * p
  • 以 K 为单位,S 占用的内存 = L / 8 / 1024

Redis 中,HyperLogLog设置为:m=16834,p=6,L=16834 6。占用内存为=16834 6 / 8 / 1024 = 12K

形象化为:

  1. 0 1 .... 16833
  2. [000 000] [000 000] [000 000] [000 000] .... [000 000]

3. 对应

现在回到我们的原始APP页面统计用户的问题中去。

  • 设 APP 主页的 key 为: main
  • 用户 id 为:idn , n->0,1,2,3….

在这个统计问题中,不同的用户 id 标识了一个用户,那么我们可以把用户的 id 作为被hash的输入。即:

hash(id) = 比特串

不同的用户 id,必然拥有不同的比特串。每一个比特串,也必然会至少出现一次 1 的位置。我们类比每一个比特串为一次伯努利试验

现在要分轮,也就是分桶。所以我们可以设定,每个比特串的前多少位转为10进制后,其值就对应于所在桶的标号。假设比特串的低两位用来计算桶下标志,此时有一个用户的id的比特串是:1001011000011。它的所在桶下标为:11(2) = 1*2^1 + 1*2^0 = 3,处于第3个桶,即第3轮中。

上面例子中,计算出桶号后,剩下的比特串是:10010110000,从低位到高位看,第一次出现 1 的位置是 5 。也就是说,此时第3个桶,第3轮的试验中,k_max = 5。5 对应的二进制是:101,又因为每个桶有 p 个比特位。当 p>=3 时,便可以将 101 存进去。

模仿上面的流程,多个不同的用户 id,就被分散到不同的桶中去了,且每个桶有其 k_max。然后当要统计出 mian 页面有多少用户点击量的时候,就是一次估算。最终结合所有桶中的 k_max,代入估算公式,便能得出估算值。

下面是 HyperLogLog 的结合了调和平均数的估算公式,变量释意和LogLog的一样:

以上原理参考:https://www.cnblogs.com/linguanh/p/10460421.html

4.3.bitmap[位图]

这个实际上就是多种状态的数据化表示。比如员工的出勤率就可以用0和1表示。

1.setbit[添加]

  • setbit key offest value

    • offset表示偏移量,位存储,第0位,第1位…依次存储

2.getbit[查询]

  • getbit key offset

3.bitcount[统计]

  • bitcount key

5.Redis事务

5.1.说明

Redis的事务与之前sql的事务是不一样的

  • sql的事务可以保证原子性,即可以失败回滚,保证一组命令要不全部完成要不全部失败,遵循ACID原则。
  • Redis中事务只是一组命令的集合,按照顺序执行,即便其中有一条失败了,下面的也能继续执行,不能保证原子性【但是Redis单条指令执行是原子性的,因为Redis是单线程IO多路复用】。

有以下三个保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。(multi)
  • 命令入队。(任意命令)
  • 执行事务。(exec)

5.2.相关命令

序号 命令及描述
1 DISCARD 取消事务,放弃执行事务块内的所有命令。
2 EXEC 执行所有事务块内的命令。
3 MULTI 标记一个事务块的开始。

5.3.Redis乐观锁

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。

5.3.1.Watch乐观锁

序号 命令及描述
4 UNWATCH 取消 WATCH 命令对所有 key 的监视。
5 WATCH key [key …] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

Watch 指定一个或多个 key ,会将key当前的值“记住”,当要执行事务时要监视指定的key是否发生过改变,如果已经改变事务不会执行。

如果要改变监控的key的值,就要先unwatch解锁,在watch监视。

6.Jedis

Jedis是官方推荐的java与redis连接工具,类似于JDBC,是通过java操作redis的中间件。

需要的jar包:

  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
  3. <dependency>
  4. <groupId>redis.clients</groupId>
  5. <artifactId>jedis</artifactId>
  6. <version>3.3.0</version>
  7. </dependency>
  8. <dependency>
  9. <groupId>com.fasterxml.jackson.core</groupId>
  10. <artifactId>jackson-databind</artifactId>
  11. <version>2.12.1</version>
  12. </dependency>
  13. </dependencies>

6.1.Redis配置

由于我们的redis服务器在linux上,所以需要改一些配置信息

  1. 在/xiao/redis.conf中找到protected-mod字段,是保护模式,默认为yes,改为no
  2. 在/xiao/redis.conf中找到bind字段,改为127.0.0.1 192.168.125.129【前面是本机ip,后面是虚拟机ip】
  3. 配置防火墙,放行6379端口
  4. 为了安全,最好在/xiao/redis.conf中配置一个远程连接密码是requirepass字段(第一次需要新加)。

6.2.java测试

  1. package com.xiao;
  2. import redis.clients.jedis.Jedis;
  3. public class Test {
  4. public static void main(String[] args) {
  5. //新建连接
  6. Jedis jedis = new Jedis("192.168.125.129",6379);
  7. //密码连接
  8. jedis.auth("xyf12345xyf");
  9. //测试连接状态
  10. System.out.println(jedis.ping());
  11. }
  12. }
  13. //返回值
  14. //PONG表示连通

jedis使用的所有API都是上面Redis的原生命令,就是把命令变为方法。

7.springboot整合

7.1.导入与配置

springboot整合redis需要的starter是:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

对redis的配置前缀是spring.redis

  1. spring.redis.host=192.168.125.129
  2. spring.redis.port=6379
  3. spring.redis.password=xyf12345xyf

在spring2.x之后java与redis连接中间件不再是jedis了,而是lettuce

  • jedis使用的是直连,多个线程操作的话有安全问题,当然可以使用jedis pool连接池【底层使用BIO模型】
  • lettuce底层采用netty,不存在线程不安全的问题。

7.2.spring自动装配

lettuce在IOC容器中装配了两个Tamlpate: RedisTemplate【大的Redis操作实例】与StringRedisTemplate【针对String类型的Redis操作实例】(Redis中String使用最多)。

  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnClass(RedisOperations.class)
  3. @EnableConfigurationProperties(RedisProperties.class)
  4. @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
  5. public class RedisAutoConfiguration {
  6. @Bean
  7. @ConditionalOnMissingBean(name = "redisTemplate")
  8. @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  9. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  10. RedisTemplate<Object, Object> template = new RedisTemplate<>();
  11. template.setConnectionFactory(redisConnectionFactory);
  12. return template;
  13. }
  14. @Bean
  15. @ConditionalOnMissingBean
  16. @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
  17. public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
  18. StringRedisTemplate template = new StringRedisTemplate();
  19. template.setConnectionFactory(redisConnectionFactory);
  20. return template;
  21. }
  22. }

7.3.测试

  1. package com.xiao.redisspringboot;
  2. import org.junit.jupiter.api.Test;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.boot.test.context.SpringBootTest;
  5. import org.springframework.data.redis.connection.RedisConnection;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. import org.springframework.data.redis.core.ValueOperations;
  8. @SpringBootTest
  9. class RedisSpringbootApplicationTests {
  10. //自动装配redisTemplate
  11. @Autowired
  12. private RedisTemplate redisTemplate;
  13. @Test
  14. void contextLoads() {
  15. //获取连接
  16. RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
  17. //测试连接是否成功
  18. System.out.println(connection.ping());
  19. /**
  20. * redisTemplate通过opeForxxx获取指定类型的操作对象[如opsForHash,opsForList等]
  21. * redisTemplate.opsForValue()获取一个操作字符串的实例,这个实例的方法全是原生redis的命令
  22. **/
  23. ValueOperations valueOperations = redisTemplate.opsForValue();
  24. //添加一个key-value
  25. valueOperations.set("name","xiaoyunfei");
  26. //获取value
  27. System.out.println(valueOperations.get("name"));
  28. }
  29. }

7.4.注意

完成上面的测试后回到linux发现在远程设置的key-value有问题,如下。

这是由于RedisTamlpate默认使用的是JDK的序列化反序列化,但我们可能需要其他的方式,就要自定义RedisTamlpate【因为RedisTamplate有注解@ConditionalOnMissingBean(name = “redisTemplate”),所以如果自己配了就会优先与默认的】

7.5.自定义redisTamplate

  1. package com.xiao.conf;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.PropertyAccessor;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.data.redis.connection.RedisConnectionFactory;
  9. import org.springframework.data.redis.core.RedisTemplate;
  10. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  11. import org.springframework.data.redis.serializer.StringRedisSerializer;
  12. @Configuration
  13. public class RedisTamplateConf {
  14. @Bean
  15. public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
  16. //一般我们序列化会采用String,object格式
  17. RedisTemplate<String, Object> Template = new RedisTemplate<>();
  18. Template.setConnectionFactory(factory);
  19. //json序列化格式
  20. Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
  21. ObjectMapper objectMapper = new ObjectMapper();
  22. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  23. objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
  24. /**Jackson ObjectMapper 中的 enableDefaultTyping 方法由于安全原因,从 2.10.0 开始标记为过期
  25. 建议用 activateDefaultTyping 方法代替**/
  26. //objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  27. serializer.setObjectMapper(objectMapper);
  28. //String的序列化
  29. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  30. //key采用String格式序列化
  31. Template.setKeySerializer(stringRedisSerializer);
  32. //hash采用String格式序列化
  33. Template.setHashKeySerializer(stringRedisSerializer);
  34. //value采用jackson序列化
  35. Template.setValueSerializer(serializer);
  36. //hash的value采用jackson序列化
  37. Template.setHashValueSerializer(serializer);
  38. Template.afterPropertiesSet();
  39. return Template;
  40. }
  41. }

注意序列化是针对对象的操作,其作用是可以让value是一个对象。

  1. ValueOperations valueOperations = redisTemplate.opsForValue();
  2. //添加一个key-value(对象)
  3. Person person = new Person(1, "肖云飞", 19);
  4. valueOperations.set("person",person);
  5. //获取value
  6. System.out.println(valueOperations.get("person"));
  7. //结果:
  8. //Person(id=1, name=肖云飞, age=19)

8.Redis持久化

为什么要持久化:

  • Redis 中的数据类型都支持 Push/Pop、Add/Remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
  • 在此基础上,Redis 支持各种不同方式的排序。与 Memcached 一样,为了保证效率,数据都是缓存在内存中。
  • 因为数据都是缓存在内存中的,当你重启系统或者关闭系统后,缓存在内存中的数据都会消失殆尽,再也找不回来了。
  • 所以,为了让数据能够长期保存,就要将 Redis 放在缓存中的数据做持久化存储。

8.1.RDB与AOF介绍

  • RDB 持久化方式能够在指定的时间间隔对你的数据进行快照存储。
  • AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以 Redis 协议追加保存每次写的操作到文件末尾。

Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大。

  • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。
  • 你也可以同时开启两种持久化方式,在这种情况下,当 Redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。

8.2.RDB

8.3.1.触发条件

1.手动触发
  • save命令和bgsave命令都可以生成RDB文件。
  • save命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在Redis服务器阻塞期间,服务器不能处理任何命令请求。
  1. save1

而bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。

  1. bgsave

bgsave命令执行过程中,只有fork子进程时会阻塞服务器,而对于save命令,整个过程都会阻塞服务器,因此save已基本被废弃,线上环境要杜绝save的使用。此外,在自动触发RDB持久化时,Redis也会选择bgsave而不是save来进行持久化。

  1. shutdown

没错,shutdown命令也会激活RDB保存,但是一定要是有至少一个save m n这个配置才可以。

2.自动触发

自动触发的开启需要在配置文件中配置

其中save 3600 1的含义是:当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave;save 300 100和save 60 10000同理。当三个save条件满足任意一个时,都会引起bgsave的调用。

3.save m n的实现原理

Redis的save m n,是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。

  • serverCron是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是检查 save m n 配置的条件是否满足,如果满足就执行bgsave。
  • dirty计数器是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改);而当save/bgsave执行完成后,会将dirty重新置为0。例如,如果Redis执行了set mykey helloworld,则dirty值会+1;如果执行了sadd myset v1 v2 v3,则dirty值会+3;注意dirty记录的是服务器进行了多少次修改,而不是客户端执行了多少修改数据的命令。
  • astsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。
    save m n的原理如下:每隔100ms,执行serverCron函数;在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足,就进行bgsave。对于每一个save m n条件,只有下面两条同时满足时才算满足:

    • (1)当前时间-lastsave > m
    • (2)dirty >= n

8.3.2. 执行流程

图片中的5个步骤所进行的操作如下:

  1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
  3. 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令
  4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息

8.4.3. 启动时加载

RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。
Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。

8.4.5. RDB常用配置总结

下面是RDB常用的配置项,以及默认值;前面介绍过的这里不再详细介绍。

  • save m n:bgsave自动触发的条件;如果没有save m n配置,相当于自动的RDB持久化关闭,不过此时仍可以通过其他方式触发
  • stop-writes-on-bgsave-error yes:当bgsave出现错误时,Redis是否停止执行写命令;设置为yes,则当硬盘出现问题时,可以及时发现,避免数据的大量丢失;设置为no,则Redis无视bgsave的错误继续执行写命令,当对Redis服务器的系统(尤其是硬盘)使用了监控时,该选项考虑设置为no
  • rdbcompression yes:是否开启RDB文件压缩
  • rdbchecksum yes:是否开启RDB文件的校验,在写入文件和读取文件时都起作用;关闭checksum在写入文件和启动文件时大约能带来10%的性能提升,但是数据损坏时无法发现
  • dbfilename dump.rdb:RDB文件名
  • dir ./:RDB文件和AOF文件所在目录

8.4.6.优点

RDB 方式的优点:

  • RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份。
  • 比如你可以在每个小时保存一下过去 24 小时内的数据,同时每天保存过去 30 天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
  • RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心,非常适用于灾难恢复。
  • RDB 在保存 RDB 文件时父进程唯一需要做的就是 Fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能。
  • 与 AOF 相比,在恢复大的数据集的时候,RDB 方式会更快一些。

8.4.7.缺点

RDB 方式的缺点:

  • 如果你希望在 Redis 意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么 RDB 不适合。

虽然你可以配置不同的 Save 时间点(例如每隔 5 分钟并且对数据集有 100 个写的操作),但是 Redis 要完整的保存整个数据集是一个比较繁重的工作。

你通常会每隔 5 分钟或者更久做一次完整的保存,万一 Redis 意外宕机,你可能会丢失几分钟的数据。

  • RDB 需要经常 Fork 子进程来保存数据集到硬盘上,当数据集比较大的时,Fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。

如果数据集巨大并且 CPU 性能不是很好的情况下,这种情况会持续 1 秒,AOF 也需要 Fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。

8.4.8.总结

8.3.AOF

8.3.1.开启AOF

Redis默认是关闭AOF的,需要将配置文件中的appendonly字段的值改为yes

appendonly yes

8.3.2.执行流程

  1. 命令追加(append):将Redis的写命令追加到缓冲区aof_buf;
  2. 文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘;
  3. 文件重写(rewrite):定期重写AOF文件,达到压缩的目的。

1.命令追加append
  • Redis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。
  • 命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点;具体格式略。在AOF文件中,除了用于指定数据库的select命令(如select 0 为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。

2.文件写入write与文件同步syns

Redis提供了多种AOF缓存区的同步文件策略,策略涉及到操作系统的write函数和fsync函数,说明如下:
为了提高文件写入效率,在现代操作系统中,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。

  • AOF缓存区的同步文件策略由参数appendfsync控制,各个值的含义如下:
  • always:命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
  • no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
  • everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是推荐的配置。

3.文件重写rewrite

随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。

文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作!

关于文件重写需要注意的另一点是:对于AOF持久化来说,文件重写虽然是强烈推荐的,但并不是必须的;即使没有文件重写,数据也可以被持久化并在Redis启动的时候导入;因此在一些实现中,会关闭自动的文件重写,然后通过定时任务在每天的某一时刻定时执行。

文件重写的触发

文件重写的触发,分为手动触发和自动触发:

  • 手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。

  • 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。

  • auto-aof-rewrite-min-size:执行AOF重写时,文件的最小体积,默认值为64MB。

  • auto-aof-rewrite-percentage:执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值。

  1. Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。前面曾介绍过,这个主要是基于性能方面的考虑。
  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的。[创建过程中断]
  3. 父进程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父进程,并可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。
  4. 由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。
  5. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。
  6. 子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。
  7. 父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。
  8. 使用新的AOF文件替换老文件,完成AOF重写。

8.3.3.启动时加载

当AOF开启时,Redis启动时会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会载入RDB文件恢复数据。

8.3.4.AOF常用配置

  • appendonly no:是否开启AOF
  • appendfilename “appendonly.aof”:AOF文件名
  • dir ./:RDB文件和AOF文件所在目录
  • appendfsync everysec:fsync持久化策略
  • no-appendfsync-on-rewrite no:AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
  • auto-aof-rewrite-percentage 100:文件重写触发条件之一
  • auto-aof-rewrite-min-size 64mb:文件重写触发提交之一
  • aof-load-truncated yes:如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件

8.3.5.优点

  • 使用 AOF 会让你的 Redis 更加耐久。
  • 你可以使用不同的 Fsync 策略:无 Fsync、每秒 Fsync 、每次写的时候 Fsync 使用默认的每秒 Fsync 策略。

Redis 的性能依然很好( Fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失 1 秒的数据。

  • AOF文件是一个只进行追加的日志文件,所以不需要写入 Seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也可使用 redis-check-aof 工具修复这些问题。
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。

整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。

而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

  • AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存。

因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。导出(export) AOF 文件也非常简单。

举个例子,如果你不小心执行了 FLUSHALL 命令,但只要 AOF 文件未被重写,那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis ,就可以将数据集恢复到 FLUSHALL 执行之前的状态。

8.3.6.缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 Fsync 策略,AOF 的速度可能会慢于 RDB。在一般情况下,每秒 Fsync 的性能依然非常高,而关闭 Fsync 可以让 AOF 的速度和 RDB 一样快,即使在高负荷之下也是如此。

8.4.总结

  • 优点对比:
  • RDB 方式可以保存过去一段时间内的数据,并且保存结果是一个单一的文件,可以将文件备份到其他服务器,并且在回复大量数据的时候,RDB 方式的速度会比 AOF 方式的回复速度要快。
  • AOF 方式默认每秒钟备份 1 次,频率很高,它的操作方式是以追加的方式记录日志而不是数据,并且它的重写过程是按顺序进行追加,所以它的文件内容非常容易读懂。
  • 缺点对比:
  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 Fsync 策略,AOF 的速度可能会慢于 RDB。在一般情况下,每秒 Fsync 的性能依然非常高,而关闭 Fsync 可以让 AOF 的速度和 RDB 一样快,即使在高负荷之下也是如此。

8.5.选择

  • 对于企业级的中大型应用,如果不想牺牲数据完整性但是又希望保持高效率,那么你应该同时使用 RDB 和 AOF 两种方式。
  • 如果你不打算耗费精力在这个地方,只需要保证数据完整性,那么优先考虑使用 AOF 方式。
  • RDB 方式非常适合大规模的数据恢复,如果业务对数据完整性和一致性要求不高,RDB 是很好的选择。

9.Redis配置文件

  1. # vim redis.conf
  2. daemonize yes #是否以后台进程运行
  3. pidfile /var/run/redis/redis-server.pid #pid文件位置
  4. port 6379#监听端口
  5. bind 127.0.0.1 #绑定地址,如外网需要连接,设置0.0.0.0
  6. timeout 300 #连接超时时间,单位秒
  7. loglevel notice #日志级别,分别有:
  8. # debug :适用于开发和测试
  9. # verbose :更详细信息
  10. # notice :适用于生产环境
  11. # warning :只记录警告或错误信息
  12. logfile /var/log/redis/redis-server.log #日志文件位置
  13. syslog-enabled no #是否将日志输出到系统日志
  14. databases 16#设置数据库数量,默认数据库为0
  15. ############### 快照方式(RDB持久化)###############
  16. save 900 1 #在900s(15m)之后,至少有1个key发生变化,则快照
  17. save 300 10 #在300s(5m)之后,至少有10个key发生变化,则快照
  18. save 60 10000 #在60s(1m)之后,至少有1000个key发生变化,则快照
  19. rdbcompression yes #dump时是否压缩数据
  20. dir /var/lib/redis #数据库(dump.rdb)文件存放目录
  21. ############### 主从复制 ###############
  22. slaveof <masterip> <masterport> #主从复制使用,用于本机redis作为slave去连接主redis
  23. masterauth <master-password> #当master设置密码认证,slave用此选项指定master认证密码
  24. slave-serve-stale-data yes #当slave与master之间的连接断开或slave正在与master进行数据同步时,如果有slave请求,当设置为yes时,slave仍然响应请求,此时可能有问题,如果设置no时,slave会返回"SYNC with master in progress"错误信息。但INFO和SLAVEOF命令除外。
  25. ############### 安全 ###############
  26. requirepass foobared #配置redis连接认证密码
  27. ############### 限制 ###############
  28. maxclients 128#设置最大连接数,0为不限制
  29. maxmemory <bytes>#内存清理策略,如果达到此值,将采取以下动作:
  30. # volatile-lru :默认策略,只对设置过期时间的key进行LRU算法删除
  31. # allkeys-lru :删除不经常使用的key
  32. # volatile-random :随机删除即将过期的key
  33. # allkeys-random :随机删除一个key
  34. # volatile-ttl :删除即将过期的key
  35. # noeviction :不过期,写操作返回报错
  36. maxmemory-policy volatile-lru#如果达到maxmemory值,采用此策略
  37. maxmemory-samples 3 #默认随机选择3个key,从中淘汰最不经常用的s
  38. ############### 附加模式 ###############
  39. appendonly no #AOF持久化,是否记录更新操作日志,默认redis是异步(快照)把数据写入本地磁盘
  40. appendfilename appendonly.aof #指定更新日志文件名
  41. # AOF持久化三种同步策略:
  42. # appendfsync always #每次有数据发生变化时都会写入appendonly.aof
  43. # appendfsync everysec #默认方式,每秒同步一次到appendonly.aof
  44. # appendfsync no #不同步,数据不会持久化
  45. no-appendfsync-on-rewrite no #当AOF日志文件即将增长到指定百分比时,redis通过调用BGREWRITEAOF是否自动重写AOF日志文件。
  46. ############### 虚拟内存 ###############
  47. vm-enabled no #是否启用虚拟内存机制,虚拟内存机将数据分页存放,把很少访问的页放到swap上,内存占用多,最好关闭虚拟内存
  48. vm-swap-file /var/lib/redis/redis.swap #虚拟内存文件位置
  49. vm-max-memory 0 #redis使用的最大内存上限,保护redis不会因过多使用物理内存影响性能
  50. vm-page-size 32 #每个页面的大小为32字节
  51. vm-pages 134217728 #设置swap文件中页面数量
  52. vm-max-threads 4 #访问swap文件的线程数
  53. ############### 高级配置 ###############
  54. hash-max-zipmap-entries 512 #哈希表中元素(条目)总个数不超过设定数量时,采用线性紧凑格式存储来节省空间
  55. hash-max-zipmap-value 64 #哈希表中每个value的长度不超过多少字节时,采用线性紧凑格式存储来节省空间
  56. list-max-ziplist-entries 512 #list数据类型多少节点以下会采用去指针的紧凑存储格式
  57. list-max-ziplist-value 64 #list数据类型节点值大小小于多少字节会采用紧凑存储格式
  58. set-max-intset-entries 512 #set数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储
  59. activerehashing yes #是否激活重置哈希

10.Redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

10.1.作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

10.2.状态查询

  • info replication命令可以查询当前Redis主机的状态
  1. 127.0.0.1:6379> info replication
  2. # Replication
  3. role:master
  4. connected_slaves:0
  5. master_failover_state:no-failover
  6. master_replid:f06574dfb48833e713853f3785c66202584a3098
  7. master_replid2:0000000000000000000000000000000000000000
  8. master_repl_offset:0
  9. second_repl_offset:-1
  10. repl_backlog_active:0
  11. repl_backlog_size:1048576
  12. repl_backlog_first_byte_offset:0
  13. repl_backlog_histlen:0

10.3.主从服务器配置

我只有一台电脑,就只能伪集群了,在虚拟机中/xiao目录下复制出三个配置文件:redis6379.conf,redis6380.conf,redis6381.conf,修改配置后打开三个putty启动三个服务模拟三台主机。

主机配置:

```shell port 6379 pidfile /var/run/redis_6379.pid logfile “6379.log” dumpfilename dump6379.rdb

requirepass xyf12345xyf

  1. > 从机6380配置
  2. > ```shell
  3. port 6380
  4. pidfile /var/run/redis_6380.pid
  5. logfile "6380.log"
  6. dumpfilename dump6380.rdb
  7. #从机需要配置主机的ip与port
  8. slaveof 192.168.125.129 6379
  9. #如果主机有requirepass需要指明密码
  10. masterauth xyf12345xyf
  11. requirepass xyf12345xyf

从机6381配置

```shell port 6381 pidfile /var/run/redis_6381.pid logfile “6381.log” dumpfilename dump6381.rdb

从机需要配置主机的ip与port

slaveof 192.168.125.129 6379

如果主机有requirepass需要指明密码

masterauth xyf12345xyf

requirepass xyf12345xyf

  1. <a name="d27e3764"></a>
  2. ### 10.4.启动测试
  3. > 开启三个putty连接虚拟机,进入/usr/local/bin,启动Redis。
  4. ```bash
  5. #主机
  6. [root@localhost bin]# redis-server /xiao/redis6379.conf
  7. #从机6380
  8. [root@localhost bin]# redis-server /xiao/redis6379.conf
  9. #从机6381
  10. [root@localhost bin]# redis-server /xiao/redis6379.conf

现已全部启动

连接主机,redis-cli -p 6379可以指定连接的redis端口,使用info replication显然当前是主机,有两台从机,分别是6380与6381。

  1. [root@localhost bin]# redis-cli -p 6379
  2. 127.0.0.1:6379> auth xyf12345xyf
  3. OK
  4. 127.0.0.1:6379> info replication
  5. # Replication
  6. role:master
  7. connected_slaves:2
  8. slave0:ip=127.0.0.1,port=6380,state=online,offset=2044,lag=1
  9. slave1:ip=127.0.0.1,port=6381,state=online,offset=2044,lag=1
  10. master_failover_state:no-failover
  11. master_replid:b37ff3dcdd295b4455652957ed0adef543b70a8b
  12. master_replid2:0000000000000000000000000000000000000000
  13. master_repl_offset:2044
  14. second_repl_offset:-1
  15. repl_backlog_active:1
  16. repl_backlog_size:1048576
  17. repl_backlog_first_byte_offset:1
  18. repl_backlog_histlen:2044

连接从机,显示角色是slave,master的ip是192.168.125.129,端口是6379

  1. [root@localhost bin]# redis-cli -p 6380
  2. 127.0.0.1:6380> auth xyf12345xyf
  3. OK
  4. 127.0.0.1:6380> info replication
  5. # Replication
  6. role:slave
  7. master_host:192.168.125.129
  8. master_port:6379
  9. master_link_status:up
  10. master_last_io_seconds_ago:1
  11. master_sync_in_progress:0
  12. slave_repl_offset:2282
  13. slave_priority:100
  14. slave_read_only:1
  15. replica_announced:1
  16. connected_slaves:0
  17. master_failover_state:no-failover
  18. master_replid:b37ff3dcdd295b4455652957ed0adef543b70a8b
  19. master_replid2:0000000000000000000000000000000000000000
  20. master_repl_offset:2282
  21. second_repl_offset:-1
  22. repl_backlog_active:1
  23. repl_backlog_size:1048576
  24. repl_backlog_first_byte_offset:1
  25. repl_backlog_histlen:2282

10.5.复制原理

1.建立连接阶段

  1. 保存主节点信息,从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。

  2. 建立socket连接,从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建socket连接。如果连接成功,则:

    • 从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。

    • 主节点:接收到从节点的socket连接后(即accept之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行。

  3. 发送ping命令,从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。
    从节点发送ping命令后,可能出现3种情况:

    • 返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。
    • 超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。
    • 返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。
  4. 身份验证,如果从节点中设置了masterauth选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的,auth命令的参数即为配置文件中的masterauth的值。

2.数据同步阶段

主从节点之间的连接建立以后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。具体执行的方式是:从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。

数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制s。

需要注意的是,在数据同步阶段之前,从节点是主节点的客户端,主节点不是从节点的客户端;而到了这一阶段及以后,主从节点互为客户端。原因在于:在此之前,主节点只需要响应从节点的请求即可,不需要主动发请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。

3.命令传播阶段

数据同步阶段完成后,主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。

注意:

命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复;因此实际上主从节点之间很难保持实时的一致性,延迟在所难免。数据不一致的程度,与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。

repl-disable-tcp-nodelay no:该配置作用于命令传播阶段,控制主节点是否禁止与从节点的TCP_NODELAY;默认no,即不禁止TCP_NODELAY。当设置为yes时,TCP会对包进行合并从而减少带宽,但是发送的频率会降低,从节点数据延迟增加,一致性变差;具体发送频率与Linux内核的配置有关,默认配置为40ms。当设置为no时,TCP会立马将主节点的数据发送给从节点,带宽增加但延迟变小。

11.Redis哨兵模式

  • 实际环境中主机有可能宕机,这是就需要主从切换(就是“谋权篡位”),哨兵模式就是实现自动检测主机状态,并实现主从切换。
  • 哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。

11.1.简介

这里的哨兵有两个作用:

  • 通过发送命令,让 Redis 服务器返回监测其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知到其他的从服务器,修改配置文件,让它们切换主机。

只是现实中一个哨兵进程对 Redis 服务器进行监控,也可能出现问题,为了处理这个问题,还可以使用多个哨兵的监控,而各个哨兵之间还会相互监控,这样就变为了多个哨兵模式。多个哨兵不仅监控各个 Redis 服务器,而且哨兵之间互相监控,看看哨兵们是否还“活”着,其关系如下图所示。

11.2.故障切换failover

故障切换(failover)的过程。假设主服务器宕机,哨兵 1 先监测到这个结果,当时系统并不会马上进行 failover 操作,而仅仅是哨兵 1 主观地认为主机已经不可用,这个现象被称为主观下线。

当后面的哨兵监测也监测到了主服务器不可用,并且有了一定数量的哨兵认为主服务器不可用,那么哨兵之间就会形成一次投票,投票的结果由一个哨兵发起,进行 failover 操作,在 failover 操作的过程中切换成功后,就会通过发布订阅方式,让各个哨兵把自己监控的服务器实现切换主机,这个过程被称为客观下线。这样对于客户端而言,一切都是透明的。

11.3.哨兵模式配置

/usr/local/bin下有一个专门的命令用来开启哨兵模式,类似于开启Redis服务,我们需要配置一个哨兵的配置文件。可以复制bin下的自带配置修改,当然也可以直接写一个【需要的配置内容不多】。

  1. sentinel monitor mymaster 192.168.125.129 6379 1
  2. /**
  3. 指示 Sentinel 去监视一个被命名为 mymaster 的master,可指定为任何名字
  4. Master IP为192.168.125.129 , 端口号为 6379 ,
  5. 这个master判断为失效至少需要 1 个 Sentinel 同意 (只要同意 Sentinel 的数量不达标,自动故障迁移就不会执行)
  6. **/
  7. port 26379
  8. //哨兵启动端口[默认26379]
  9. sentinel auth-pass mymaster xyf12345xyf
  10. /**如果主机配置了密码,需要指定密码**/
  11. daemonize yes
  12. //以守护进程方式运行,与Redis服务一样,即后台服务
  13. sentinel down-after-milliseconds mymaster 30000
  14. //down-after-milliseconds 指定了 Sentinel 认为master已经断线所需的毫秒数[默认为30秒]
  15. sentinel parallel-syncs mymaster 1
  16. /**parallel-syncs 指定了在执行故障转移时, 最多可以有多少个slave同时对新的master进行同步, 这个数字越小, 完成故障转移所需的时间就越长**/
  17. sentinel failover-timeout mymaster 180000
  18. /**failover-timeout 指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟 **/

核心:

  • sentinel monitor mymaster 192.168.125.129 6379 1
  • port 26379
  • sentinel auth-pass mymaster xyf12345xyf
  • daemonize yes
  1. 将配置文件放在/xiao/sentinel.conf中
  2. 进入/usr/local/bin启动哨兵
  1. redis-sentinel /xiao/sentinel.conf

效果[配置文件中没有配置守护进程方式,所以会在前台启动]:

加上daemonize yes 配置后就可以后台运行了。

开启后可以通过客户端连接到哨兵查看一些信息

  1. [root@localhost bin]# redis-cli -p 26379
  2. 127.0.0.1:26379> info sentinel
  3. # Sentinel
  4. sentinel_masters:1
  5. sentinel_tilt:0
  6. sentinel_running_scripts:0
  7. sentinel_scripts_queue_length:0
  8. sentinel_simulate_failure_flags:0
  9. master0:name=mymaster,status=ok,address=192.168.125.129:6379,slaves=2,sentinels=1

12.缓存穿透,击穿与雪崩