centos7下学习Redis(三)

一、HyperLogLog

一般我们评估一个网站的访问量,有几个主要的参数:
pv,Page View,网页的浏览量
uv,User View,访问的用户
一般来说,pv 或者 uv 的统计,可以自己来做,也可以借助一些第三方的工具,比如 cnzz,友盟 等。
如果自己实现,pv 比较简单,可以直接通过 Redis 计数器就能实现。但是 uv 就不一样,uv 涉及到另外一个问题,去重。
我们首先需要在前端给每一个用户生成一个唯一 id,无论是登录用户还是未登录用户,都要有一个唯一id,这个 id 伴随着请求一起到达后端,在后端我们通过 set 集合中的 sadd 命令来存储这个 id,最后通过 scard 统计集合大小,进而得出 uv 数据。
如果是千万级别的 UV,需要的存储空间就非常惊人。而且,像 UV 统计这种,一般也不需要特别精确,800w 的 uv 和 803w 的 uv,其实差别不大。所以,我们要介绍今天的主角—HyperLogLog。
Redis 中提供的 HyperLogLog 就是专门用来解决这个问题的,HyperLogLog 提供了一套不怎么精确但是够用的去重方案,会有误差,官方给出的误差数据是 0.81%,这个精确度,统计 UV 够用了。
HyperLogLog 主要提供了两个命令:pfadd 和 pfcount。
pfadd 用来添加记录,类似于 sadd ,添加过程中,重复的记录会自动去重。
pfcount 则用来统计数据。
数据量少的时候看不出来误差。
在 Java 中,我们多添加几个元素:

public class HyperLogLog {
    public static void main(String[] args) {
        Redis redis = new Redis();
        redis.execute(jedis -> {
            for (int i = 0; i < 1000; i++) {
                jedis.pfadd("uv", "u" + i, "u" + (i + 1));
           }
            long uv = jedis.pfcount("uv");
            System.out.println(uv);//理论值是 1001
       });
   }
}

理论值是 1001,实际打印出来 994,有误差,但是在可以接受的范围内。
除了 pfadd 和 pfcount 之外,还有一个命令 pfmerge ,合并多个统计结果,在合并的过程中,会自动
去重多个集合中重复的元素。

二、布隆过滤器

1、场景重现

例如刷今日头条,推送的内容有相似的,但是没有重复的。这就涉及到如何在推送的时候去重。

2、Bloom Filter 介绍

Bloom Filter专门用来解决我们上面所说的去重问题的,使用 Bloom Filter 不会像使用缓存那么浪费空间。当然,他也存在一个小小问题,就是不太精确。
Bloom Filter 相当于是一个不太精确的 set 集合,我们可以利用它里边的 contains 方法去判断某一个对象是否存在,但是需要注意,这个判断不是特别精确。一般来说,通过 contains 判断某个值不存在,那就一定不存在,但是判断某个值存在的话,则他可能不存在。
以今日头条为例,假设我们将用户的浏览记录用 B 表示,A 表示用户没有浏览的新闻,现在要给用户推送消息,先去 B 里边判断这条消息是否已经推送过,如果判断结果说没推送过(B 里边没有这条记录),那就一定没有推送过。如果判断结果说有推送过(B 里边也有可能没有这条消息),这个时候该条消息就不会推送给用户,导致用户错过该条消息,当然这是概率极低的。

3、Bloom Filter 原理

每一个布隆过滤器,在 Redis 中都对应了一个大型的位数组以及几个不同的 hash 函数。
所谓的 add 操作是这样的:
首先根据几个不同的 hash 函数给元素进行 hash 运算一个整数索引值,拿到这个索引值之后,对位数组的长度进行取模运算,得到一个位置,每一个 hash 函数都会得到一个位置,将位数组中对应的位置设置位 1 ,这样就完成了添加操作。
在这里插入图片描述
当判断元素是否粗存在时,依然先对元素进行 hash 运算,将运算的结果和位数组取模,然后去对应的位置查看是否有相应的数据,如果有,表示元素可能存在(因为这个有数据的地方也可能是其他元素存进来的),如果没有表示元素一定不存在。
Bloom Filter 中,误判的概率和位数组的大小有很大关系,位数组越大,误判概率越小,当然占用的存储空间越大;位数组越小,误判概率越大,当然占用的存储空间就小。

4、基本用法

主要是两类命令,添加和判断是否存在。
bf.add\bf.madd 添加和批量添加
bf.exists\bf.mexists 判断是否存在和批量判断
默认情况下,我们使用的布隆过滤器它的错误率是 0.01 ,默认的元素大小是 100。但是这两个参数也
是可以配置的。我们可以调用 bf.reserve 方法进行配置。

BF.RESERVE k1 0.0001 1000000

第一个参数是 key,第二个参数是错误率,错误率越低,占用的空间越大,第三个参数预计存储的数
量,当实际数量超出预计数量时,错误率会上升。

5、典型场景

前面所说的新闻推送过滤算是一个应用场景。
解决 Redis 穿透或者又叫缓存击穿问题。
假设我有 1亿 条用户数据,现在查询用户要去数据库中查,效率低而且数据库压力大,所以我们会把请求首先在 Redis 中处理(活跃用户存在 Redis 中),Redis 中没有的用户,再去数据库中查询。
现在可能会存在一种恶意请求,这个请求携带上了很多不存在的用户,这个时候 Redis 无法拦截下来请求,所以请求会直接跑到数据库里去。这个时候,这些恶意请求会击穿我们的缓存,甚至数据库,进而引起“雪崩效应”。为了解决这个问题,我们就可以使用布隆过滤器。将 1亿条用户数据存在 Redis 中不现实,但是可以存在布隆过滤器中,请求来了,首先去判断数据是否存在,如果存在,再去数据库中查询,否则就不去数据库中查询。

三、Redis限流

1、预备知识

Pipeline(管道)本质上是由客户端提供的一种操作。Pipeline 通过调整指令列表的读写顺序,可以大
幅度的节省 IO 时间,提高效率。

2、深入限流操作

Redis4.0 开始提供了一个 Redis-Cell 模块,这个模块使用漏斗算法,提供了一个非常好用的限流指令。漏斗算法就像名字一样,是一个漏斗,请求从漏斗的大口进,然后从小口出进入到系统中,这样,无论是多大的访问量,最终进入到系统中的请求,都是固定的。
使用漏斗算法,需要我们首先安装 Redis-Cell 模块:
安装步骤省略。
安装后修改 redis.conf 文件,加载额外的模块:

loadmodule /root/redis-5.0.7/redis-cell/libredis_cell.so

CL.THROTTLE 命令一共有五个参数
1、 第一个参数是 key
2. 第二个参数是漏斗的容量
3. 时间窗内可以操作的次数
4. 时间窗
5. 每次漏出数量
执行完成后,返回值也有五个:

  1. 第一个 0 表示允许,1表示拒绝
  2. 第二个参数是漏斗的容量
  3. 第三个参数是漏斗的剩余空间
  4. 如果拒绝了,多长时间后,可以再试
  5. 多长时间后,漏斗会完全空出来

四、Redis 单线程处理高并发

1、阻塞IO与非阻塞IO

Java 在 JDK1.4 中引入 NIO,但是也有很多人在使用阻塞 IO,这两种 IO 有什么区别?
在阻塞模式下,如果你从数据流中读取不到指定大小的数据,IO 就会阻塞。比如已知会有 10 个字节发送过来,但是我目前只收到 4 个,还剩六个,此时就会发生阻塞。如果是非阻塞模式,虽然此时只收到 4 个字节,但是读到 4 个字节就会立即返回,不会傻傻等着,等另外 6 个字节来的时候,再去继续读取。所以阻塞 IO 性能低于 非阻塞 IO。
如果有一个 Web 服务器,使用阻塞 IO 来处理请求,那么每一个请求都需要开启一个新的线程;但是如果使用了非阻塞 IO,基本上一个小小线程池就够用了,因为不会发生阻塞,每一个线程都能够高效利用。

2、Redis 的线程模型

首先一点,Redis 是单线程。单线程如何解决高并发问题的?
实际上,能够处理高并发的单线程应用不仅仅是 Redis,除了 Redis 之外,还有 NodeJS、Nginx 等等
也是单线程。
Redis 虽然是单线程,但是运行很快,主要有如下几方面原因:
1、 Redis 中的所有数据都是基于内存的,所有的计算也都是内存级别的计算,所以快。
2、Redis 是单线程的,所以有一些时间复杂度高的指令,可能会导致 Redis 卡顿,例如 keys。
3、Redis 在处理并发的客户端连接时,使用了非阻塞 IO。
在使用非阻塞 IO 时,有一个问题,就是线程如何知道剩下的数据来了?这里就涉及到一个新的概念叫做多路复用,本质上就是一个事件轮询 API。
4、Redis 会给每一个客户端指令通过队列来排队进行顺序处理。
5、Redis 做出响应时,也会有一个响应的队列。

五、Redis 通信协议

Redis 通信使用了文本协议,文本协议比较费流量,但是 Redis 作者认为数据库的瓶颈不在于网络流
量,而在于内部逻辑,所以采用了这样一个费流量的文本协议。这个文本协议叫做 Redis Serialization Protocol,简称 RESP。
Redis 协议将传输的数据结构分为 5 种最小单元,单元结束时,加上回车换行符 \r\n。

  1. 单行字符串以 + 开始,例如 +javaboy.org\r\n
  2. 多行字符串以 $ 开始,后面加上字符串长度,例如 $11\r\njavaboy.org\r\n
  3. 整数值以: 开始,例如 :1024\r\n
  4. 错误消息以 - 开始
  5. 数组以 * 开始,后面加上数组长度。
    需要注意的是,如果是客户端连接服务端,只能使用第 5 种。

六、Redis持久化

Redis 是一个缓存工具,也叫做 NoSQL 数据库,既然是数据库,必然支持数据的持久化操作。在 Redis中,数据库持久化一共有两种方案:
1、快照方式
2、 AOF 日志

1、快照

1.1 原理

Redis 使用操作系统的多进程机制来实现快照持久化:Redis 在持久化时,会调用 glibc 函数 fork 一个
子进程,然后将快照持久化操作完全交给子进程去处理,而父进程则继续处理客户端请求。在这个过程中,子进程能够看到的内存中的数据在子进程产生的一瞬间就固定下来了,再也不会改变,也就是为什么 Redis 持久化叫做 快照。

1.2具体配置

在 Redis 中,默认情况下,快照持久化的方式就是开启的。
默认情况下会产生一个 dump.rdb 文件,这个文件就是备份下来的文件。当 Redis 启动时,会自动的去加载这个 rdb 文件,从该文件中恢复数据。
具体的配置,在 redis.conf 中:

# 表示快照的频率,第一个表示 900 秒内如果有一个键被修改,则进行快照
save 900 1
save 300 10
save 60 10000
# 快照执行出错后,是否继续处理客户端的写命令
stop-writes-on-bgsave-error yes
# 是否对快照文件进行压缩
rdbcompression yes
# 表示生成的快照文件名
dbfilename dump.rdb
# 表示生成的快照文件位置
dir ./

1.3 备份流程

  1. 在 Redis 运行过程中,我们可以向 Redis 发送一条 save 命令来创建一个快照。但是需要注意,
    save 是一个阻塞命令,Redis 在收到 save 命令开始处理备份操作之后,在处理完成之前,将不再
    处理其他的请求。其他命令会被挂起,所以 save 使用的并不多。
  2. 我们一般可以使用 bgsave,bgsave 会 fork 一个子进程去处理备份的事情,不影响父进程处理客
    户端请求。
  3. 我们定义的备份规则,如果有规则满足,也会自动触发 bgsave。
  4. 另外,当我们执行 shutdown 命令时,也会触发 save 命令,备份工作完成后,Redis 才会关闭。
  5. 用 Redis 搭建主从复制时,在 从机连上主机之后,会自动发送一条 sync 同步命令,主机收到命令
    之后,首先执行 bgsave 对数据进行快照,然后才会给从机发送快照数据进行同步。

2、AOF

与快照持久化不同,AOF 持久化是将被执行的命令追加到 aof 文件末尾,在恢复时,只需要把记录下来的命令从头到尾执行一遍即可。
默认情况下,AOF 是没有开启的。我们需要手动开启:

# 开启 aof 配置
appendonly yes
# AOF 文件名
appendfilename "appendonly.aof"
# 备份的时机,下面的配置表示每秒钟备份一次
appendfsync everysec
# 表示 aof 文件在压缩时,是否还继续进行同步操作
no-appendfsync-on-rewrite no
# 表示当目前 aof 文件大小超过上一次重写时的 aof 文件大小的百分之多少的时候,再次进行重写
auto-aof-rewrite-percentage 100
# 如果之前没有重写过,则以启动时的 aof 大小为依据,同时要求 aof 文件至少要大于 64M
auto-aof-rewrite-min-size 64mb

七、Redis主从同步

1、CAP

在分布式环境下,CAP 原理是一个非常基础的东西,所有的分布式存储系统,都只能在 CAP 中选择两项实现。
c:consistent 一致性
a:availability 可用性
p:partition tolerance 分布式容忍性
在一个分布式系统中,这三个只能满足两个:在一个分布式系统中,P 肯定是要实现的,c 和 a 只能选择其中一个。大部分情况下,大多数网站架构选择了 ap。
在 Redis 中,实际上就是保证最终一致。
Redis 中,当搭建了主从服务之后,如果主从之间的连接断开了,Redis 依然是可以操作的,相当于它
满足可用性,但是此时主从两个节点中的数据会有差异,相当于牺牲了一致性。但是 Redis 保证最终一致,就是说当网络恢复的时候,从机会追赶主机,尽量保持数据一致。

2、主从复制

主从复制可以在一定程度上扩展 redis 性能,redis 的主从复制和关系型数据库的主从复制类似,从机
能够精确的复制主机上的内容。实现了主从复制之后,一方面能够实现数据的读写分离,降低master的压力,另一方面也能实现数据的备份。

2.1 配置方式

假设我有三个redis实例,地址分别如下:

192.168.91.128:6379  
192.168.91.128:6380  
192.168.91.128:6381  

即同一台服务器上三个实例,配置方式如下:

  1. 将 redis.conf 文件更名为 redis6379.conf,方便我们区分,然后把 redis6379.conf 再复制两份,
    分别为 redis6380.conf 和 redis6381.conf.
  2. 打开 redis6379.conf,将如下配置均加上 6379,(默认是6379的不用修改),如下:
port 6379
pidfile /var/run/redis_6379.pid
logfile "6379.log"
dbfilename dump6379.rdb
appendfilename "appendonly6379.aof"
  1. 同理,分别打开 redis6380.conf 和 redis6381.conf 两个配置文件,将第二步涉及到 6379 的分别
    改为 6380 和 6381。
  2. 输入如下命令,启动三个redis实例:
[root@localhost redis-4.0.8]# redis-server redis6379.conf
[root@localhost redis-4.0.8]# redis-server redis6380.conf
[root@localhost redis-4.0.8]# redis-server redis6381.conf
  1. 输入如下命令,分别进入三个实例的控制台:
[root@localhost redis-4.0.8]# redis-cli -p 6379
[root@localhost redis-4.0.8]# redis-cli -p 6380
[root@localhost redis-4.0.8]# redis-cli -p 6381
  1. 假设在这三个实例中,6379 是主机,即 master,6380 和 6381 是从机,即 slave,那么如何配
    置这种实例关系呢,很简单,分别在 6380 和 6381 上执行如下命令:
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK

这一步也可以通过在两个从机的 redis.conf 中添加如下配置来解决:

slaveof 127.0.0.1 6379

主从关系搭建好之后,可以通过如下命令查看实例状态

127.0.0.1:6379> INFO replication
  1. 此时,我们在主机中存储一条数据,在从机中就可以 get 到这条数据了。

2.2 主从复制注意点

  1. 如果主机已经运行了一段时间了,并且了已经存储了一些数据了,此时从机连上来,那么从机会将
    主机上所有的数据进行备份,而不是从连接的那个时间点开始备份。
  2. 配置了主从复制之后,主机上可读可写,但是从机只能读取不能写入(可以通过修改redis.conf
    中 slave-read-only 的值让从机也可以执行写操作)。
  3. 在整个主从结构运行过程中,如果主机不幸挂掉,重启之后,他依然是主机,主从复制操作也能够
    继续进行。

2.3 复制原理

每一个 master 都有一个 replication ID,这是一个较大的伪随机字符串,标记了一个给定的数据集。每个 master 也持有一个偏移量,master 将自己产生的复制流发送给 slave 时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新 slave 的状态。复制偏移量即使在没有一个 slave 连接到 master 时,也会自增,所以基本上每一对给定的
Replication ID, offset 都会标识一个 master 数据集的确切版本。当 slave 连接到 master 时,它们使用
PSYNC 命令来发送它们记录的旧的 master replication ID 和它们至今为止处理的偏移量。通过这种方
式,master 能够仅发送 slave 所需的增量部分。但是如果 master 的缓冲区中没有足够的命令积压缓冲记录,或者如果 slave 引用了不再知道的历史记录(replication ID),则会转而进行一个全量重同步:在这种情况下,slave 会得到一个完整的数据集副本,从头开始(参考redis官网)。
简单来说,就是以下几个步骤:

  1. slave 启动成功连接到 master 后会发送一个 sync 命令。
  2. Master 接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令。
  3. 在后台进程执行完毕之后,master 将传送整个数据文件到 slave,以完成一次完全同步。
  4. 全量复制:而 slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  5. 增量复制:Master 继续将新的所有收集到的修改命令依次传给 slave,完成同步。
  6. 但是只要是重新连接 master,一次完全同步(全量复制)将被自动执行。

2.4 不同主从模式

在这里插入图片描述
在这里插入图片描述

2.5 哨兵模式

结合上篇文章,我们一共介绍了两种主从模式了,但是这两种,不管是哪一种,都会存在这样一个问
题,那就是当主机宕机时,就会发生群龙无首的情况,如果在主机宕机时,能够从从机中选出一个来充当主机,那么就不用我们每次去手动重启主机了,这就涉及到一个新的话题,那就是哨兵模式。
所谓的哨兵模式,其实并不复杂,我们还是在我们前面的基础上来搭建哨兵模式。假设现在我的
master 是 6379,两个从机分别是 6380 和 6381,两个从机都是从 6379 上复制数据。先按照上文的步骤,我们配置好一主二仆,然后在 redis 目录下打开 sentinel.conf 文件,做如下配置:

sentinel monitor mymaster 127.0.0.1 6379 1

其中 mymaster 是给要监控的主机取的名字,随意取,后面是主机地址,最后面的 2 表示有多少个
sentinel 认为主机挂掉了,就进行切换。好了,配置完成后,输入
如下命令启动哨兵:

redis-sentinel sentinel.conf

然后启动我们的一主二仆架构,启动成功后,关闭 master,观察哨兵窗口输出的日志。通过日志可以看到,6379 挂掉之后,redis 内部重新举行了选举,6380 重新上位。此时,如果 6379重启,也不再是扛把子了,只能屈身做一个 slave 了。

2.6 注意问题

由于所有的写操作都是先在 Master 上操作,然后同步更新到 Slave 上,所以从 Master 同步到 Slave
机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave 机器数量的增加也会使这个问题更加严重。因此我们还需要集群来进一步提升 redis 性能,这个问题我们将在后面说到。

八、Redis集群

集群原理
Redis集群架构图如下:
在这里插入图片描述
Redis 集群运行原理如下:

  1. 所有的 Redis 节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
  2. 节点的 fail 是通过集群中超过半数的节点检测失效时才生效
  3. 客户端与 Redis 节点直连,不需要中间 proxy 层,客户端不需要连接集群所有节点,连接集群中任
    何一个可用节点即可
  4. Redis-cluster 把所有的物理节点映射到 [0-16383]slot 上,cluster (簇)负责维护 node<->slot<->value。Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个key-value 时,Redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
    如何投票
    投票过程是集群中所有 master 参与,如果半数以上 master 节点与 master 节点通信超过 clusternode-timeout 设置的时间,认为当前 master 节点挂掉。
    如何判断节点不可用
  5. 如果集群任意 master 挂掉,且当前 master 没有 slave.集群进入 fail 状态,也可以理解成集群的 slot
    映射 [0-16383] 不完整时进入 fail 状态。
  6. 如果集群超过半数以上 master 挂掉,无论是否有 slave,集群进入 fail 状态,当集群不可用时,所有
    对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误。

文章仅供学习参考,具体内容关注大神公众号:江南一点雨

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值