使用 Redis 如何统计一亿个 keys ?

news2024/11/18 11:36:59

目录

1、聚合统计

2、排序统计

3、二值状态统计

4、基数统计

总结


// 淡泊明志,宁静致远

        在 Web 和移动应用的业务场景中,我们经常需要保存这样一种信息:一个 key 对应了一个数据集合。举几个例子:

  • 手机 App 中的每天的用户登录信息:一天对应一系列用户 ID 或移动设备 ID;
  • 电商网站上商品的用户评论列表:一个商品对应了一系列的评论;
  • 用户在手机 App 上的签到打卡信息:一天对应一系列用户的签到记录;
  • 应用网站上的网页访问信息:一个网页对应一系列的访问点击。

        我们知道,Redis 集合类型的特点就是一个键对应一系列的数据,所以非常适合用来存取这些数据。但是,在这些场景中,除了记录信息,我们往往还需要对集合中的数据进行统计,例如:

  • 在移动应用中,需要统计每天的新增用户数和第二天的留存用户数;
  • 在电商网站的商品评论中,需要统计评论列表中的最新评论;
  • 在签到打卡中,需要统计一个月内连续打卡的用户数;
  • 在网页访问记录中,需要统计独立访客(Unique Visitor,UV)量。

        通常情况下,我们面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。// 获取缓存,然后通过程序对亿级数据进行聚合的想法,不切实际?

        要想选择合适的集合,我们就得了解常用的集合统计模式。集合类型常见的四种统计模式,包括聚合统计、排序统计、二值状态统计基数统计

1、聚合统计

        所谓的聚合统计,就是指统计多个集合元素的聚合结果,包括:统计多个集合的共有元素(交集统计);把两个集合相比,统计其中一个集合独有的元素(差集统计);统计多个集合的所有元素(并集统计)。// 交、差、并、补

        在刚才提到的场景中,统计手机 App 每天的新增用户数和第二天的留存用户数,正好对应了聚合统计。// 使用set 集合,避免数据重复

        要完成这个统计任务,我们可以用一个集合记录所有登录过 App 的用户 ID,同时,用另一个集合记录每一天登录过 App 的用户 ID。然后,再对这两个集合做聚合统计。我们来看下具体的操作。

        记录所有登录过 App 的用户 ID 还是比较简单的,我们可以直接使用 Set 类型,把 key 设置为 user:id,表示记录的是用户 ID,value 就是一个 Set 集合,里面是所有登录过 App 的用户 ID,我们可以把这个 Set 叫作累计用户 Set,如下图所示:

        需要注意的是,累计用户 Set 中没有日期信息,我们是不能直接统计每天的新增用户的。所以,我们还需要把每一天登录的用户 ID,记录到一个新集合中,我们把这个集合叫作每日用户 Set,它有两个特点:

  1. key 是 user:id 以及当天日期,例如 user:id:20200803;
  2. value 是 Set 集合,记录当天登录的用户 ID。

        在统计每天的新增用户时,我们只用计算每日用户 Set 和累计用户 Set 的差集就行。 

        借助一个具体的例子来解释一下。

        假设我们的手机 App 在 2020 年 8 月 3 日上线,那么,8 月 3 日前是没有用户的。此时,累计用户 Set 是空集,当天登录的用户 ID 会被记录到 key 为 user:id:20200803 的 Set 中。所以,user:id:20200803 这个 Set 中的用户就是当天的新增用户。// 新增集合和累计集合分开

        然后,我们计算累计用户 Set 和 user:id:20200803 Set 的并集结果,结果保存在 user:id 这个累计用户 Set 中,如下所示 // user:id 和 user:id:20200803 并集

// Sunionstore 命令将给定集合的并集存储在指定的集合 destination 中。
// SUNIONSTORE destination key [key ...]
SUNIONSTORE  user:id  user:id  user:id:20200803 

        此时,user:id 这个累计用户 Set 中就有了 8 月 3 日的用户 ID。等到 8 月 4 日再统计时,我们把 8 月 4 日登录的用户 ID 记录到 user:id:20200804 的 Set 中。接下来,我们执行 SDIFFSTORE 命令计算累计用户 Set 和 user:id:20200804 Set 的差集,结果保存在 key 为 user:new 的 Set 中,如下所示:// 累计用户 Set 和 user:id:20200804 Set 的差集 ->统计新增用户

SDIFFSTORE  user:new  user:id:20200804 user:id  

        可以看到,这个差集中的用户 ID 在 user:id:20200804 的 Set 中存在,但是不在累计用户 Set 中。所以,user:new 这个 Set 中记录的就是 8 月 4 日的新增用户。

        当要计算 8 月 4 日的留存用户时,我们只需要再计算 user:id:20200803 和 user:id:20200804 两个 Set 的交集,就可以得到同时在这两个集合中的用户 ID 了,这些就是在 8 月 3 日登录,并且在 8 月 4 日留存的用户。执行的命令如下:// 使用交集统计留存用户

SINTERSTORE user:id:rem user:id:20200803 user:id:20200804

        当你需要对多个集合进行聚合计算时,Set 类型会是一个非常不错的选择。不过,需要注意的是,这里有一个潜在的风险。

        Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。所以,建议从主从集群中选择一个从库,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避阻塞主库实例和其他从库实例的风险了。

2、排序统计

        接下来,分析下应对集合元素排序需求的方法。以在电商网站上提供最新评论列表的场景为例。

        最新评论列表包含了所有评论中的最新留言,这就要求集合类型能对元素保序,也就是说,集合中的元素可以按序排列,这种对元素保序的集合类型叫作有序集合。

        在 Redis 常用的 4 个集合类型中(List、Hash、Set、Sorted Set),List 和 Sorted Set 就属于有序集合。

        List 是按照元素进入 List 的顺序进行排序的,而 Sorted Set 可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。比如说,我们可以根据元素插入 Sorted Set 的时间确定权重值,先插入的元素权重小,后插入的元素权重大。// 也就是说 Sorted Set 排序需要人为干涉

        看起来好像都可以满足需求,我们该怎么选择呢?

        我先说说用 List 的情况。每个商品对应一个 List,这个 List 包含了对这个商品的所有评论,而且会按照评论时间保存这些评论,每来一个新评论,就用 LPUSH 命令把它插入 List 的队头。

        在只有一页评论的时候,我们可以很清晰地看到最新的评论,但是,在实际应用中,网站一般会分页显示最新的评论列表,一旦涉及到分页操作,List 就可能会出现问题了。

        假设当前的评论 List 是{A, B, C, D, E, F}(其中,A 是最新的评论,以此类推,F 是最早的评论),在展示第一页的 3 个评论时,我们可以用下面的命令,得到最新的三条评论 A、B、C:

// Redis Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
LRANGE product1 0 2
1) "A"
2) "B"
3) "C"

        然后,再用下面的命令获取第二页的 3 个评论,也就是 D、E、F。

LRANGE product1 3 5
1) "D"
2) "E"
3) "F"

        但是,如果在展示第二页前,又产生了一个新评论 G,评论 G 就会被 LPUSH 命令插入到评论 List 的队头,评论 List 就变成了{G, A, B, C, D, E, F}。此时,再用刚才的命令获取第二页评论时,就会发现,评论 C 又被展示出来了,也就是 C、D、E。// 新的数据出现会打乱之前的排序

LRANGE product1 3 5
1) "C"
2) "D"
3) "E"

        之所以会这样,关键原因就在于,List 是通过元素在 List 中的位置来排序的,当有一个新元素插入时,原先的元素在 List 中的位置都后移了一位,比如说原来在第 1 位的元素现在排在了第 2 位。所以,对比新元素插入前后,List 相同位置上的元素就会发生变化,用 LRANGE 读取时,就会读到旧元素。

        和 List 相比,Sorted Set 就不存在这个问题,因为它是根据元素的实际权重来排序和获取数据的

        我们可以按评论时间的先后给每条评论设置一个权重值,然后再把评论保存到 Sorted Set 中。Sorted Set 的 ZRANGEBYSCORE 命令就可以按权重排序后返回元素。这样的话,即使集合中的元素频繁更新,Sorted Set 也能通过 ZRANGEBYSCORE 命令准确地获取到按序排列的数据。// 假设当前的评论 List 是{A, B, C, D, E, F}(其中,A 是最新的评论,以此类推,F 是最早的评论,权重分别为 10,9,8,7,6,5)。 在展示第一页的 3 个评论时,按照权重排序,查出 ABC。 展示第二页的 3 个评论时,按照权重排序,查出 DEF。 如果在展示第二页前,又产生了一个新评论 G,权重为 11,排序为 {G, A, B, C, D, E, F}。 再次查询第二页数据时,权重还是会以 10 为准,逻辑上,第一页的权重还是 10,9,8。 查询第二页数据时,可以查询出权重等于 7,6,5 的数据,返回评论 DEF。 当想查询出最新评论时,需要以权重 11 为准,第一页数据的权重就是 11,10,9,返回评论 GAB。 再次查询第二页数据时,以权重 11 为准,查询出评论 CDE。

        假设越新的评论权重越大,目前最新评论的权重是 N,我们执行下面的命令时,就可以获得最新的 10 条评论:

ZRANGEBYSCORE comments N-9 N

        所以,在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议你优先考虑使用 Sorted Set。

3、二值状态统计

        现在,我们再来分析下第三个场景:二值状态统计。这里的二值状态就是指集合元素的取值就只有 0 和 1 两种。在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。

        在签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。这个时候,我们就可以选择 Bitmap。这是 Redis 提供的扩展数据类型。下边分析一下它的实现原理。

        Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态。可以把 Bitmap 看作是一个 bit 数组。

        Bitmap 提供了 GETBIT/SETBIT 操作,使用一个偏移值 offset 对 bit 数组的某一个 bit 位进行读和写。不过,需要注意的是,Bitmap 的偏移量是从 0 开始算的,也就是说 offset 的最小值是 0。当使用 SETBIT 对一个 bit 位进行写操作时,这个 bit 位会被设置为 1。Bitmap 还提供了 BITCOUNT 操作,用来统计这个 bit 数组中所有“1”的个数。

        那么,具体该怎么用 Bitmap 进行签到统计呢?举一个具体的例子来说明。

        假设我们要统计 ID 3000 的用户在 2020 年 8 月份的签到情况,就可以按照下面的步骤进行操作。

        第一步,执行下面的命令,记录该用户 8 月 3 号已签到。

// Setbit KEY_NAME OFFSET, 把 OFFSET 为 2 处的值设置为 1
SETBIT uid:sign:3000:202008 2 1 

        第二步,检查该用户 8 月 3 日是否签到。

GETBIT uid:sign:3000:202008 2 

        第三步,统计该用户在 8 月份的签到次数。

BITCOUNT uid:sign:3000:202008

        这样,我们就知道该用户在 8 月份的签到情况了,是不是很简单呢?接下来,你可以再思考一个问题:如果记录了 1 亿个用户 10 天的签到情况,你有办法统计出这 10 天连续签到的用户总数吗?

        在介绍具体的方法之前,我们要先知道,Bitmap 支持用 BITOP 命令对多个 Bitmap 按位做“与”“或”“异或”的操作,操作的结果会保存到一个新的 Bitmap 中。// 二值运算:“与”“或”“异或”

        我以按位“与”操作为例来具体解释一下。从下图中,可以看到,三个 Bitmap bm1、bm2 和 bm3,对应 bit 位做“与”操作,结果保存到了一个新的 Bitmap 中(示例中,这个结果 Bitmap 的 key 被设为“resmap”)。

        回到刚刚的问题,在统计 1 亿个用户连续 10 天的签到情况时,你可以把每天的日期作为 key,每个 key 对应一个 1 亿位的 Bitmap,每一个 bit 对应一个用户当天的签到情况。

        接下来,我们对 10 个 Bitmap 做“与”操作,得到的结果也是一个 Bitmap。在这个 Bitmap 中,只有 10 天都签到的用户对应的 bit 位上的值才会是 1。最后,我们可以用 BITCOUNT 统计下 Bitmap 中的 1 的个数,这就是连续签到 10 天的用户总数了。// 有点像并查集

        现在,我们可以计算一下记录了 10 天签到情况后的内存开销。每天使用 1 个 1 亿位的 Bitmap,大约占 12MB 的内存(10^8/8/1024/1024),10 天的 Bitmap 的内存开销约为 120MB,内存压力不算太大。不过,在实际应用时,最好对 Bitmap 设置过期时间,让 Redis 自动删除不再需要的签到记录,以节省内存开销。

        所以,如果只需要统计数据的二值状态,例如商品有没有、用户在不在等,就可以使用 Bitmap,因为它只用一个 bit 位就能表示 0 或 1。在记录海量数据时,Bitmap 能够有效地节省内存空间

4、基数统计

        基数统计就是指统计一个集合中不重复的元素个数。对应到我们刚才介绍的场景中,就是统计网页的 UV。

        网页 UV 的统计有个独特的地方,就是需要去重,一个用户一天内的多次访问只能算作一次。在 Redis 的集合类型中,Set 类型默认支持去重,所以看到有去重需求时,我们可能第一时间就会想到用 Set 类型。

        我们来结合一个例子看一看用 Set 的情况。

        有一个用户 user1 访问 page1 时,你把这个信息加到 Set 中:

SADD page1:uv user1

        用户 1 再来访问时,Set 的去重功能就保证了不会重复记录用户 1 的访问次数,这样,用户 1 就算是一个独立访客。当你需要统计 UV 时,可以直接用 SCARD 命令,这个命令会返回一个集合中的元素个数。

        但是,如果 page1 非常火爆,UV 达到了千万,这个时候,一个 Set 就要记录千万个用户 ID。对于一个搞大促的电商网站而言,这样的页面可能有成千上万个,如果每个页面都用这样的一个 Set,就会消耗很大的内存空间

        当然,你也可以用 Hash 类型记录 UV。// Hash 一样占内存

        例如,你可以把用户 ID 作为 Hash 集合的 key,当用户访问页面时,就用 HSET 命令(用于设置 Hash 集合元素的值),对这个用户 ID 记录一个值“1”,表示一个独立访客,用户 1 访问 page1 后,我们就记录为 1 个独立访客,如下所示:

HSET page1:uv user1 1

        即使用户 1 多次访问页面,重复执行这个 HSET 命令,也只会把 user1 的值设置为 1,仍然只记为 1 个独立访客。当要统计 UV 时,我们可以用 HLEN 命令统计 Hash 集合中的所有元素个数。

        但是,和 Set 类型相似,当页面很多时,Hash 类型也会消耗很大的内存空间。那么,有什么办法既能完成统计,还能节省内存吗?

        这时候,就要用到 Redis 提供的 HyperLogLog 了。// 专门用来做基数统计

        HyperLogLog 是一种用于统计基数的数据集合类型,它的最大优势就在于,当集合元素数量非常多时,它计算基数所需的空间总是固定的,而且还很小

        在 Redis 中,每个 HyperLogLog 只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数。你看,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。

        在统计 UV 时,你可以用 PFADD 命令(用于向 HyperLogLog 中添加新元素)把访问页面的每个用户都添加到 HyperLogLog 中。

PFADD page1:uv user1 user2 user3 user4 user5

        接下来,就可以用 PFCOUNT 命令直接获得 page1 的 UV 值了,这个命令的作用就是返回 HyperLogLog 的统计结果。

PFCOUNT page1:uv

        不过,需要注意的是,HyperLogLog 的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是 0.81%。这也就意味着,你使用 HyperLogLog 统计的 UV 是 100 万,但实际的 UV 可能是 101 万。虽然误差率不算大,但是,如果你需要精确统计结果的话,最好还是继续用 Set 或 Hash 类型

总结

        redis 中 Set、Sorted Set、Hash、List、Bitmap、HyperLogLog 的支持情况和优缺点汇总

        可以看到,Set 和 Sorted Set 都支持多种聚合统计,不过,对于差集计算来说,只有 Set 支持。Bitmap 也能做多个 Bitmap 间的聚合计算,包括与、或和异或操作。

        当需要进行排序统计时,List 中的元素虽然有序,但是一旦有新元素插入,原来的元素在 List 中的位置就会移动,那么,按位置读取的排序结果可能就不准确了。而 Sorted Set 本身是按照集合元素的权重排序,可以准确地按序获取结果,所以建议优先使用它。

        如果我们记录的数据只有 0 和 1 两个值的状态,Bitmap 会是一个很好的选择,这主要归功于 Bitmap 对于一个数据只用 1 个 bit 记录,可以节省内存。

        对于基数统计来说,如果集合元素量达到亿级别而且不需要精确统计时,建议使用 HyperLogLog。

        当然,Redis 的应用场景非常多,这张表中的总结不能覆盖到所有场景。所以也试着自己去画一张表,把遇到的其他场景添加进去。长久积累下来,一定能够更加灵活地把集合类型应用到合适的实践项目中。

        // 多总结多思考,经验也是重要的知识

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1129442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

任务调度器详解(FreeRTOS)

目录 什么是任务调度器 FreeRTOS的任务调度器 抢占式调度 协作式调度 时间片调度 什么是任务调度器 任务调度器是实时操作系统(RTOS)的一个关键组件,它负责决定在多个可运行任务中哪一个将获得CPU时间以执行。它基于任务的优先级和状态来…

软考高级系统架构 上午真题错题总结

目录 前言一、2022年真题(√)二、2021年真题三、2020年真题(√)四、2019年真题(√)五、2018年真题(√)六、2017年真题(√)七、2016年真题(√&…

Remmina Linux 远程桌面(堡垒机)解决方案,含文件互传

简介 Remmina 是一款在 Linux 和其他类 Unix 系统下的自由开源、功能丰富、强大的远程桌面客户端。 对于一个Linux作为主力开发机而言,Remmina 解决痛点主要是公司堡垒机远程客户现场的计算机,公司只给开发了win系统下的远程连接程序,而没有…

SQLi靶场

SQLi靶场 less1- less2 (详细讲解) less 1 Error Based-String (字符类型注入) 思路分析 判断是否存在SQL注入 已知参数名为id,输入数值和‘ 单引号‘’ 双引号来判断,它是数值类型还是字符类型 首先输入 1 , 发现…

IDEA在GitHub / Gitee中拉取特定一个分支代码方法

IDEA通过HTTP / SSH拉取项目的时候,默认都是拉取master分支的节点代码,对于我们通过分支来逐一消化项目的需求是相违背的,那么下面就是如何对一个项目特定分支读取方法 首先正常通过HTTP / SSH拉一个项目下来 打开分支列表,选择指…

一文了解GC垃圾回收

一文了解GC垃圾回收 1 判断一个对象为垃圾对象的方法 引用计数法(弃用) 可达性分析算法 是否有指向GC root 的引用链,如果有,不是垃圾对象 ---->GC roo:即rt.jar包中内容 2 内存泄漏与内存溢出区别 泄漏:原本需要被回收的对象&#…

Python 深度学习入门之CNN

CNN 前言一、CNN简介1、简介2、结构 二、CNN简介1、输出层2、卷积层3、池化层4、全连接层5、输出层 前言 1024快乐!1024快乐!今天开新坑,学点深度学习相关的,说下比较火的CNN。 一、CNN简介 1、简介 CNN的全称是Convolutiona…

英语——语法——从句——名词性从句——笔记

文章目录 名词性从句一、定义二、分类(一)宾语从句(二)主语从句(三)C同位语从句(四)D表语从句 名词性从句 一、句子成分 简而言之,构成一个句子的成分(或要素…

关于 provide、inject 在Vue3中的用法

Vue3关于 provide、inject 的用法 一、传递变量/数据二、传递函数 前言: 在父子组件传递数据时,通常使用的是 props 和 emit。 父传子时,使用的是 props,如果是父组件传孙组件时,就需要先传给子组件,子组件…

STM32:TTL串口调试

一.TTL串口概要 TTL只需要两个线就可以完成两个设备之间的双向通信,一个发送电平的I/O称之为TX,与另一个设备的接收I/O口RX相互连接。两设备之间还需要连接地线(GND),这样两设备就有相同的0V参考电势。 二.TTL串口调试 实现电脑通过STM32发送…

高等数学啃书汇总重难点(六)定积分的应用

无论是考研还是学校期末考试,这一部分的内容都不会太难,因此今天的内容一页笔记就足够放的下了。 重点在于理解所谓的元素法——也就是微元法,即定积分的几何意义。只不过,定积分几何意义的引例为平面问题,实际上&…

Spring初步了解到深入理解

文章目录 1.Spring1.1简介1.2优点1.3组成1.4拓展 2.IOC理论推导3.Hello Spring下面牵扯到的地址: 4.IOC创建对象的方式1.使用无参构造创建对象,默认2.假设我们要使用有参构造创建对象1.下标赋值2.类型3.参数名 5.Spring配置5.1别名5.2bean的配置5.3import 6.依赖注入…

Monocular arbitrary moving object discovery and segmentation 代码复现

环境 https://github.com/michalneoral/Raptor 1.创建environment.yaml name: raptor channels:- pytorch- conda-forge dependencies:- python3.8- pytorch1.9.0- torchvision0.10.0- cudatoolkit11.1- pipconda env create -f environment.yaml conda activate raptor2.安…

C++基类和派生类的内存分配,多态的实现

目录 基类和派生类的内存分配基类和派生类的成员归属多态的实现 基类和派生类的内存分配 类包括成员变量(data member)和成员函数(member function)。 成员变量分为静态数据(static data)和非静态数据&…

技术分享 | 针对蜜罐反制Goby背后的故事

0x01 概述 近期我们联动FORadar做了一个插件,实现了从企业名称->企业漏洞的全自动检测流程,在做具体实践的时候碰到了两个很有意思的蜜罐,其中一个蜜罐内置了Weblogic漏洞,同时配置有专门针对旧版本Goby反制Payload&#xff0…

点亮现代编程语言的男人——C语言/UNIX之父Dennis Ritchie

祝各位程序员们1024程序员节快乐🎉🎉🎉 图片来自网络,侵删 前言 在程序员中,有一位人物的不被人熟知,他的贡献甚至比他自身更要出名 C语言之父,UNIX之父——Dennis MacAlistair Ritchie 一…

0基础学习PyFlink——使用Table API实现SQL功能

在《0基础学习PyFlink——使用PyFlink的Sink将结果输出到Mysql》一文中,我们讲到如何通过定义Souce、Sink和Execute三个SQL,来实现数据读取、清洗、计算和入库。 如下图所示SQL是最高层级的抽象,在它之下是Table API。本文我们会将例子中的SQ…

【机器学习合集】深度学习模型优化方法最优化问题合集 ->(个人学习记录笔记)

文章目录 最优化1. 最优化目标1.1 凸函数&凹函数1.2 鞍点1.3 学习率 2. 常见的深度学习模型优化方法2.1 随机梯度下降法2.2 动量法(Momentum)2.3 Nesterov accelerated gradient法(NAG)2.4 Adagrad法2.5 Adadelta与Rmsprop法2.6 Adam法2.7 Adam算法的改进 3. SGD的改进算法…

LVS+keepalived高可用集群

1、定义 keepalived为lvs应运而生的高可用服务。lvs的调度器无法做高可用,keepalived实现的是调度器的高可用,但keepalived不只为lvs集群服务的,也可以做其他代理服务器的高可用,比如nginxkeepalived也可实现高可用(重…