• [技术干货] windows下Redis的安装和配置
    安装和配置Redis前言:windows没有32位的Redis,所以32位机器的朋友考虑换一下机器。1、windows环境下的redis需要去github上获取:https://github.com/MicrosoftArchive/redis/releases2、直接下载最新版本,选择.msi格式的安装版本(另外一种.zip通过命令安装)3、直接运行.msi的安装包,一直next,直到下面界面,勾选上再next4、这一步选择端口,然后next(后面可以通过配置文件修改的)5、选择最大缓存容量,点击next(后面可以通过配置文件修改的)6、接下来可以点击install进行安装了,安装完毕后,进入redis安装目录找到配置文件,注意是redis.windows-service.conf,不是redis.windows.conf,后者是以非系统服务方式启动程序使用的配置文件.7、在配置文件中,找到requirepass foobared字样,在其后面追加一行,输入requirepass 123456。这是访问Redis时所需的密码,一般测试情况下可以不用设定密码。不过,即使是作为本地访问,也建议设定一个密码。此处以简单的123456来演示,然后保存退出8、进入计算机服务中(右键计算机-->管理-->服务和应用程序-->服务),再在右侧找到Redis名称的服务,查看启动情况。如未启动,则手动启动之。正常情况下,服务应该正常启动并运行了,但是因为前面修改过配置文件,需要重启服务,切记9、测试一下redis能否正常工作。用命令进入redis安装路径,输入redis-cli并回车(redis-cli是客户端程序)如图正常提示进入,并显示正确端口号,则表示服务已经启动。10、使用服务前需要先通过密码验证。输入“auth 123456”并回车(123456是之前设定的密码)。返回提示OK表示验证通过。然后再验证set和get。一切正常,说明可以开始随便盘它了。可视化工具redis常用的可视化工具 Redis Desktop Manager。0.9.4以上要给钱的。下载地址:https://github.com/uglide/RedisDesktopManager/releases/download/0.9.3/redis-desktop-manager-0.9.3.817.exe这位朋友的百度云盘里也有(感谢):https://blog.csdn.net/u012688704/article/details/82251338下载完成之后直接点击安装包安装即可,无需任何配置。然后直接连接Redis就可以盘了————————————————原文链接:https://blog.csdn.net/weixin_41381863/article/details/88231397
  • [知识分享] 用GaussDB(for Redis)存画像,推荐业务轻松降本60%
    【摘要】 用户画像存储是推荐业务核心,但开源Redis无法胜任。华为云高斯Redis是最佳存储选型,轻松降本60%,同时获得企业级高稳定性。本文分享自华为云社区《华为云GaussDB(for Redis)揭秘第23期:用GaussDB(for Redis)存画像,推荐业务轻松降本60%》,作者: 高斯Redis官方博客 。一、什么是推荐系统       不知道大家有没有过这样的经历,当你前脚刚在某电商网站买了一个手机,过两天之后你再打开该电商网站,显示在首页的上必定有耳机、手机壳和蓝牙音箱等手机配件。如果你买的不是手机,而是一件衣服的话,下次打开显示的必定是和该衣服非常搭配的裤子和鞋子等。聪明的你不禁要问,为何电商网站如此强大,竟然能提前预知你的偏好,并且给你推荐你可能喜欢的商品?其实在这背后,都离不开那强大的推荐系统。       什么是推荐系统呢?首先我们来看看维基百科对推荐系统的定义:推荐系统是一种信息过滤系统,可以根据用户历史行为用于预测用户对物品的“评分”或“偏好”。简单来说,如果你是一个电子发烧友,那么系统肯定会给你推荐各种新鲜出炉的3C产品,如果你是一个coder,那么你的页面肯定充斥各种编程大全的书籍。推荐系统近年来非常流行,应用于各行各业,它推荐的对象包括:电影、音乐、新闻、书籍、学术论文、搜索查询、分众分类、电商购物和游戏业务等。二、推荐系统的架构       了解了什么是推荐系统之后,接下来我们继续介绍下推荐系统的架构,以游戏行业为例,一个典型的游戏业务中的推荐系统架构设计如下:         推荐系统主要由3部分组成,分别是:行为日志收集、特征生产和特征消费。(1)行为日志收集         大数据业务收集用户的行为日志,分析获得用户画像,并且将这些用户画像保存在分布式文件系统HDFS中。(2)特征生产         工程业务负责为大数据业务提供一套接口调用,主要是“灌库”,即定时或按一定逻辑把HDFS中的用户画像加工成特征,灌入工程业务负责的“KV存储”。(3)特征消费         工程业务团队还负责将算法团队的推荐模型进行工程落地,他们开发线上推理组件,从KV存储中提取特征数据,分析计算,最终得出推荐结论,展示给用户。三、推荐系统的存储痛点       上一节中我们介绍了游戏行业中推荐系统的架构,这也是推荐系统的一个典型架构。从架构图可以看出,KV存储在整套链路中,承载着重要的承上启下作用。然而,推荐系统中的KV存储却存在着两个大的痛点,第一个是成本高,第二个是扩容慢。(1)成本高         首先第一个是成本高的问题。通常我们搭建KV存储的话首选都是选择自建一个开源Redis集群作为KV存储系统。一方面,开源Redis的数据全部放在内存中,众所周知内存的存储成本是非常昂贵的,内存只适合用于存储少量数据,如果数据量大的话,数据的存储成本将成为企业的负担;另一方面由于开源Redis在进行AOF文件重写的过程中,存在fork机制,导致开源Redis在AOF文件重写时,其内存利用率只有50%,这就进一步使增加了开源Redis的内存使用成本。(2)扩容慢       除了成本比较高,开源Redis还存在扩容慢的问题。在自建的开源Redis集群中,随着业务增长,KV存储的容量不得不进行扩容。但是由于原生Redis本身的架构特点,在扩容过程中难免要发生key的跨slot迁移,如下图所示:       如果发生slot迁移,需要耗时很久并且业务受影响时间长。四、为什么推荐高斯Redis       上一节中我们介绍了推荐系统的存储痛点,如何解决呢?要解决这两个痛点,无非是需要进行降本增效,而高斯Redis似乎就是为了解决这些问题而生。(1)降本       就降本而言,高斯Redis从两个方面降低KV数据的存储成本:       第一个方面,高斯Redis的所有数据全部落在磁盘,相比开源Redis存在内存中而言,其成本降低了75%~90%,拥有极大的价格优势。举个例子来说,一个512GB的开源Redis集群,其成本几乎要5w/月,而相同规格的实例,如果替换成高斯Redis,其成本节约40%以上,几乎节省了一个人力成本在里面。下面这张表格是不同层级的存储器之间的成本对比图。       另一方面,在推荐系统中,KV数据库主要存储的是用户画像的信息,而这些信息基本上都是经过Protobuf序列化后的信息,而高斯Redis自带的数据压缩功能,对序列化后的信息可以进行高压缩比的压缩,相对于开源Redis而言,存储空间仅仅为开源Redis的70%到85%,这又进一步降低了KV数据的存储成本。(2)增效       除了降本,另一方面就是增效了。众所周知,开源Redis的架构中,如下图所示,其存储和计算是不分离的,这就导致节点进行扩容的时候,会发生分片的迁移,从而导致业务会受到影响。        高斯Redis为了解决这个问题,采用了存算分离的架构,如下图所示:       在采用存算分离的架构下,底层数据可以被任意上层计算节点访问,扩容过程不发生数据拷贝搬迁,速度极快。节点扩容时,分钟级完成,业务只有秒级感知。容量扩容时,业务0感知。无论是扩节点还是扩磁盘容量,对业务造成的影响几乎为0。五、总结       本文简单介绍了推荐系统是什么,并且以游戏行业中的推荐系统为例,介绍了推荐系统的架构和其存在的存储痛点,并且介绍了高斯Redis如何解决该存储痛点。在这个大数据时代,推荐系统的应用会越来越多,作为推荐系统的数据存储,高斯Redis解决了开源Redis所存在的缺点,为推荐系统提供了有力的技术支撑。六、附录本文作者:华为云数据库GaussDB(for Redis)团队杭州/西安/深圳简历投递:yuwenlong4@huawei.com更多产品信息,欢迎访问官方博客:bbs.huaweicloud.com/blogs/248875
  • [新手课堂] 资讯|每条你收藏的资讯背后,都离不开TA
    随着互联网信息技术的发展个性化推荐早已融入我们的生活手机里收藏的各类资讯内容背后都有TA作为国内领先的内容生态服务平台,上海阅客信息科技有限公司(简称“阅客”)通过数据分析驱动运营,规模化提供内容生态服务,并基于内容场景提供广告技术服务,以技术精准匹配内容和用户,实现内容收益的最大化。阅客拥有强大的内容服务和广告能力,每日过万的内容更新以及上亿的曝光,庞大的数据体量和海量高并发,对支撑阅客业务应用的数据库发起了挑战。数据量激增下的存储问题数据库作为承载海量数据的基石,承担着守护企业数据资产的重任,也在企业数字化转型中发挥着关键作用。数据量激增下,阅客使用的基于ECS自建的Redis数据库在高并发和稳定性方面面临巨大压力,成本也随之攀升:性能问题 经常出现慢查询问题,每天有大量告警。专业分析:在配置缓存场景中,阅客使用了Redis存储配置策略信息。这里通常会存在一些大key,大key在开源Redis中经常有阻塞请求的性能问题。海量数据高并发访问业务经常出现访问超时,甚至需要重启自建Redis。同样,每天也会受到大量告警。专业分析:由于业务采用分布式部署,对Redis的并发请求量很大,自建sentinel哨兵Redis上连接数日常维持在3万,开源Redis无法承受。数据存储成本高昂 数据量激增,给业务运营带来压力,成本随之增加。专业分析:布隆过滤场景中的protobuf序列化数据也越来越多,增长到了TB级。而开源Redis内存成本痛点、稳定性痛点开始出现。搬迁兼容顾虑 如果选择上云,阅客必须修改自己的业务代码,然后重新发版、上线,业务改造负担大。专业分析:阅客一开始自建了两类不同架构的Redis集群,分别是Cluster集群和Sentinel集群。每个集群对应相应的客户端代码,且不互相支持。云原生时代的个性化推荐云原生时代,基于统一云基础设施的云原生数据库,成为企业上云首选。阅客紧随时代发展潮流,选择了华为云云原生数据库GaussDB(for Redis)作为企业数字化转型的数据底座,全数替换了原先自建的Redis数据库,业务发展迈上新台阶。性能卓越,内容推荐更快速针对阅客配置缓存业务中的性能问题,GaussDB(for Redis)采用分布式架构和多线程结合的方式,提供了卓越的性能,保障业务持续高效运行。相比开源Redis的单线程架构, GaussDB(for Redis)的多线程架构更具优势,即使存在大key,也不会导致全局性能受损。成功搬迁后,阅客自己的响应超时告警大幅减少,配置缓存业务响应及时高效,内容推荐更快速到达用户端。海量存储,内容推荐更平稳GaussDB(for Redis)提供独享的连接数资源,客户将自建哨兵Redis搬迁到4节点GaussDB(for Redis)实例后,业务实实在在独享4万连接数资源,且都在合适阈值内,运行非常稳定,彻底解决了阅客业务的连接数问题,亿级流量洪峰场景下也能从容面对,内容推荐更平稳。布隆过滤器业务成本节省80%GaussDB(for Redis)采用存算分离架构,可以独立购买计算、存储资源,避免开源Redis经常出现的算力成本浪费;拥有强大的数据压缩能力,尤其对布隆过滤场景中的protobuf序列化数据有奇效,实现了TB级数据到GB级的有效压缩,释放了80%的存储成本,完全超乎客户想象,也为客户今后的业务增长铺好了路。应用无须改造,一键式搬迁GaussDB(for Redis) 提供“Proxy通用型”实例类型,同时兼容StandAlone客户端、Cluster客户端以及Sentinel客户端,无需修改客户端业务代码,真正做到了“一种架构全兼容”、“业务搬迁0改造”,彻底打消了阅客的搬迁兼容顾虑。在研发团队支撑下,一周就搞定了全部数十套自建Redis,实现了高效平滑无感迁移。云原生数据库GaussDB(for Redis)不仅提升了阅客的服务效率,让个性化推荐更快更稳,还降低了存储和改造成本,为企业未来发展奠定了云化基础,助力阅客实现更高质量的资讯触达。转自华为云公众号
  • [技术干货] 用GaussDB(for Redis)存画像,推荐业务轻松降本60%
    一、什么是推荐系统不知道大家有没有过这样的经历,当你前脚刚在某电商网站买了一个手机,过两天再打开该电商网站,首页推荐显示的必定有耳机、手机壳、蓝牙音箱等手机配件。如果你买的不是手机,而是一件衣服,那么下次打开电商网站显示的,必定是和衣服搭配的裤子和鞋子等。聪明的你不禁要问,为何电商网站如此强大,竟能提前预知你的偏好,并且给你推荐你可能喜欢的商品?其实在这背后,都离不开那强大的推荐系统。什么是推荐系统?首先我们来看看维基百科上的定义:推荐系统是一种信息过滤系统,可以根据用户历史行为预测用户对物品的“评分”或“偏好”。简单来说,如果你是一个电子发烧友,那么系统肯定会给你推荐各种新鲜出炉的3C产品,如果你是一个coder,那么你的页面肯定充满各种编程大全的书籍。推荐系统近年来非常流行,应用于各行各业,推荐的对象包括:电影、音乐、新闻、书籍、学术论文、搜索查询、分众分类、电商购物和游戏业务等。二、推荐系统的架构了解了什么是推荐系统之后,接下来我们继续介绍下推荐系统的架构,以游戏行业为例,一个典型的游戏业务的推荐系统架构设计如下: 推荐系统主要由3部分组成,分别是:行为日志收集、特征生产和特征消费。 01.行为日志大数据业务通过收集用户的行为日志,分析获得用户画像,并且将这些用户画像保存在分布式文件系统HDFS中。02.特征生产工程业务负责为大数据业务提供一套接口调用,主要是“灌库”,即定时或按一定逻辑把HDFS中的用户画像加工成特征,灌入工程业务负责的“KV存储”。 03.特征消费工程业务团队还负责将算法团队的推荐模型进行工程落地,他们开发线上推理组件,从KV存储中提取特征数据、分析计算,最终得出推荐结论,展示给用户。 三、推荐系统的存储痛点上一节中我们介绍了游戏行业中推荐系统的架构,这也是推荐系统的一个典型架构。从架构图可以看出,KV存储在整套链路中,承载着重要的承上启下作用。然而,推荐系统中的KV存储却存在着两个大的痛点,第一个是成本高,第二个是扩容慢。 01.成本高首先第一个是成本高的问题。通常我们搭建KV存储的首选是选择自建一个开源Redis集群作为KV存储系统。一方面,开源Redis的数据全部放在内存中,众所周知内存的存储成本非常昂贵,只适用于存储少量数据,如果数据量大,存储成本将成为企业的负担;另一方面,开源Redis在进行AOF文件重写的过程中存在fork机制,导致开源Redis在AOF文件重写时,其内存利用率只有50%,这就进一步使增加了开源Redis的内存使用成本。02.扩容慢除了成本比较高,开源Redis还存在扩容慢的问题,在自建的开源Redis集群中,随着业务增长,KV存储的容量不得不进行扩容。但由于原生Redis本身的架构特点,在扩容过程中难免要发生key的跨slot迁移,如下图所示,跨slot迁移需要耗时很久并且业务受影响时间长。 四、为什么推荐GaussDB(for Redis)知道了推荐系统的痛点所在,该如何解决呢?究其根本是降本增效,而GaussDB(for Redis)可以说是为解决这些问题而生。01.降本GaussDB(for Redis)从两个方面降低KV数据的存储成本: 第一个方面,GaussDB(for Redis)的所有数据全部落在存储,相比开源Redis数据存放在内存中,其成本降低了75%~90%,形成极大的价格优势。举个例子,一个512GB的开源Redis集群,其成本几乎要5w/月,而相同规格的实例,如果替换成GaussDB(for Redis),其成本节约40%以上,几乎节省了一个人力成本在里面。下面这张表格是不同层级存储器之间的成本对比图。 另一方面,在推荐系统中,KV数据主要存储的是用户画像的信息,这些信息基本上都是经过Protobuf序列化后的信息,而GaussDB(for Redis)自带的数据压缩功能,可以对序列化后的信息进行高压缩比的压缩,实际占用空间仅为开源Redis的50%左右,这又进一步降低了KV数据的存储成本。 02.增效除了降本,另一方面就是增效了。众所周知,开源Redis的架构中,如下图所示,其存储和计算是不分离的,这就导致节点进行扩容的时候,会发生分片的迁移,从而导致业务会受到影响。GaussDB(for Redis)为了解决这个问题,采用了存算分离的架构,如下图所示:在存算分离的架构下,底层数据可以被任意上层计算节点访问,扩容过程不发生数据拷贝搬迁,速度极快;同时还做到分钟级节点扩容,业务秒级感知;存储扩容业务0感知。无论是扩节点还是扩存储容量,对业务的影响几乎为0。五、总结本文简单介绍了推荐系统是什么,并以游戏业务为例,阐明了推荐系统的架构和存在的存储痛点,同时GaussDB(for Redis)是如何解决这些存储痛点的。在大数据时代,推荐系统的应用场景将会越来越多,作为推荐系统的数据存储,GaussDB(for Redis)完美弥补了开源Redis的短板,能够为推荐系统提供强有力的技术支撑。
  • [干货汇总] 基于Redis + Lua脚本的设计红包雨
    【摘要】 2018年,王思聪的冲顶大会,西瓜视频的百万英雄,再到映客的芝士超人,直播答题火爆全网。我服务的一家电商公司也加入了这次热潮,技术团队研发了直播答题功能。答题结束之后,红包会以红包雨的形式落下,用户点击屏幕上落下的红包,若抢到红包,红包会以现金的形式进入用户账户。 红包雨是一个典型的高并发场景,短时间内有海量请求访问服务端,技术团队为了让系统运行顺畅,抢红包采用了基于 Redis + Lua...本文分享自华为云社区《红包雨中:Redis 和 Lua 的邂逅》,作者:勇哥java实战分享。2018年,王思聪的冲顶大会,西瓜视频的百万英雄,再到映客的芝士超人,直播答题火爆全网。我服务的一家电商公司也加入了这次热潮,技术团队研发了直播答题功能。答题结束之后,红包会以红包雨的形式落下,用户点击屏幕上落下的红包,若抢到红包,红包会以现金的形式进入用户账户。红包雨是一个典型的高并发场景,短时间内有海量请求访问服务端,技术团队为了让系统运行顺畅,抢红包采用了基于 Redis + Lua 脚本的设计方案。​​1 整体流程我们分析下抢红包的整体流程 :运营系统配置红包雨活动总金额以及红包个数,提前计算出各个红包的金额并存储到 Redis 中;抢红包雨界面,用户点击屏幕上落下的红包,发起抢红包请求;TCP 网关接收抢红包请求后,调用答题系统抢红包 dubbo 服务,抢红包服务本质上就是执行 Lua 脚本,将结果通过 TCP 网关返回给前端;用户若抢到红包,异步任务会从 Redis 中 获取抢得的红包信息,调用余额系统,将金额返回到用户账户。2 红包 Redis 设计抢红包有如下规则:同一活动,用户只能抢红包一次 ;红包数量有限,一个红包只能被一个用户抢到。如下图,我们设计三种数据类型:运营预分配红包列表 ;队列元素 json 数据格式 :1{2 //红包编号3 redPacketId : '365628617880842241' 4 //红包金额5 amount : '12.21' 6}用户红包领取记录列表;队列元素 json 数据格式:1{2 //红包编号3 redPacketId : '365628617880842241'4 //红包金额5 amount : '12.21',6 //用户编号7 userId : '265628617882842248'8}用户红包防重 Hash 表;抢红包 Redis 操作流程 :通过 hexist 命令判断红包领取记录防重 Hash 表中用户是否领取过红包 ,若用户未领取过红包,流程继续;从运营预分配红包列表 rpop 出一条红包数据 ;操作红包领取记录防重 Hash 表 ,调用 HSET 命令存储用户领取记录;将红包领取信息 lpush 进入用户红包领取记录列表。抢红包的过程 ,需要重点关注如下几点 :执行多个命令,是否可以保证原子性 , 若一个命令执行失败,是否可以回滚;在执行过程中,高并发场景下,是否可以保持隔离性;后面的步骤依赖前面步骤的结果。Redis 支持两种模式 : 事务模式 和 Lua 脚本,接下来,我们一一展开。3 事务原理Redis 的事务包含如下命令:序号命令及描述1MULTI 标记一个事务块的开始。2EXEC 执行所有事务块内的命令。3DISCARD 取消事务,放弃执行事务块内的所有命令。4WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。5UNWATCH 取消 WATCH 命令对所有 key 的监视。事务包含三个阶段:事务开启,使用 MULTI , 该命令标志着执行该命令的客户端从非事务状态切换至事务状态 ;命令入队,MULTI 开启事务之后,客户端的命令并不会被立即执行,而是放入一个事务队列 ;执行事务或者丢弃。如果收到 EXEC 的命令,事务队列里的命令将会被执行 ,如果是 DISCARD 则事务被丢弃。下面展示一个事务的例子。1redis> MULTI 2OK3redis> SET msg "hello world"4QUEUED5redis> GET msg6QUEUED7redis> EXEC81) OK91) hello world这里有一个疑问?在开启事务的时候,Redis key 可以被修改吗?在事务执行 EXEC 命令之前 ,Redis key 依然可以被修改。在事务开启之前,我们可以 watch 命令监听 Redis key 。在事务执行之前,我们修改 key 值 ,事务执行失败,返回 nil 。通过上面的例子,watch 命令可以实现类似乐观锁的效果 。4 事务的ACID4.1 原子性原子性是指:一个事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。第一个例子:在执行 EXEC 命令前,客户端发送的操作命令错误,比如:语法错误或者使用了不存在的命令。1redis> MULTI2OK3redis> SET msg "other msg"4QUEUED5redis> wrongcommand ### 故意写错误的命令6(error) ERR unknown command 'wrongcommand' 7redis> EXEC8(error) EXECABORT Transaction discarded because of previous errors.9redis> GET msg10"hello world"在这个例子中,我们使用了不存在的命令,导致入队失败,整个事务都将无法执行 。第二个例子:事务操作入队时,命令和操作的数据类型不匹配 ,入队列正常,但执行 EXEC 命令异常 。1redis> MULTI 2OK3redis> SET msg "other msg"4QUEUED5redis> SET mystring "I am a string"6QUEUED7redis> HMSET mystring name "test"8QUEUED9redis> SET msg "after"10QUEUED11redis> EXEC121) OK132) OK143) (error) WRONGTYPE Operation against a key holding the wrong kind of value154) OK16redis> GET msg17"after"这个例子里,Redis 在执行 EXEC 命令时,如果出现了错误,Redis 不会终止其它命令的执行,事务也不会因为某个命令执行失败而回滚 。综上,我对 Redis 事务原子性的理解如下:命令入队时报错, 会放弃事务执行,保证原子性;命令入队时正常,执行 EXEC 命令后报错,不保证原子性;也就是:Redis 事务在特定条件下,才具备一定的原子性 。4.2 隔离性数据库的隔离性是指:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别 ,分别是:未提交读(read uncommitted)提交读(read committed)可重复读(repeatable read)串行化(serializable)首先,需要明确一点:Redis 并没有事务隔离级别的概念。这里我们讨论 Redis 的隔离性是指:并发场景下,事务之间是否可以做到互不干扰。我们可以将事务执行可以分为 EXEC 命令执行前和 EXEC 命令执行后两个阶段,分开讨论。EXEC 命令执行前在事务原理这一小节,我们发现在事务执行之前 ,Redis key 依然可以被修改。此时,可以使用 WATCH 机制来实现乐观锁的效果。EXEC 命令执行后因为 Redis 是单线程执行操作命令, EXEC 命令执行后,Redis 会保证命令队列中的所有命令执行完 。 这样就可以保证事务的隔离性。4.3 持久性数据库的持久性是指 :事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。Redis 的数据是否持久化取决于 Redis 的持久化配置模式 。没有配置 RDB 或者 AOF ,事务的持久性无法保证;使用了 RDB模式,在一个事务执行后,下一次的 RDB 快照还未执行前,如果发生了实例宕机,事务的持久性同样无法保证;使用了 AOF 模式;AOF 模式的三种配置选项 no 、everysec 都会存在数据丢失的情况 。always 可以保证事务的持久性,但因为性能太差,在生产环境一般不推荐使用。综上,redis 事务的持久性是无法保证的 。4.4 一致性一致性的概念一直很让人困惑,在我搜寻的资料里,有两类不同的定义。维基百科我们先看下维基百科上一致性的定义:Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct. Referential integrity guarantees the primary key – foreign key relationship.在这段文字里,一致性的核心是“约束”,“any data written to the database must be valid according to all defined rules ”。如何理解约束?这里引用知乎问题 如何理解数据库的内部一致性和外部一致性,蚂蚁金服 OceanBase 研发专家韩富晟回答的一段话:“约束”由数据库的使用者告诉数据库,使用者要求数据一定符合这样或者那样的约束。当数据发生修改时,数据库会检查数据是否还符合约束条件,如果约束条件不再被满足,那么修改操作不会发生。关系数据库最常见的两类约束是“唯一性约束”和“完整性约束”,表格中定义的主键和唯一键都保证了指定的数据项绝不会出现重复,表格之间定义的参照完整性也保证了同一个属性在不同表格中的一致性。“ Consistency in ACID ”是如此的好用,以至于已经融化在大部分使用者的血液里了,使用者会在表格设计的时候自觉的加上需要的约束条件,数据库也会严格的执行这个约束条件。所以事务的一致性和预先定义的约束有关,保证了约束即保证了一致性。我们细细品一品这句话: This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct。写到这里可能大家还是有点模糊,我们举经典转账的案例。我们开启一个事务,张三和李四账号上的初始余额都是1000元,并且余额字段没有任何约束。张三给李四转账1200元。张三的余额更新为 -200 , 李四的余额更新为2200。从应用层面来看,这个事务明显不合法,因为现实场景中,用户余额不可能小于 0 , 但是它完全遵循数据库的约束,所以从数据库层面来看,这个事务依然保证了一致性。Redis 的事务一致性是指:Redis 事务在执行过程中符合数据库的约束,没有包含非法或者无效的错误数据。我们分三种异常场景分别讨论:执行 EXEC 命令前,客户端发送的操作命令错误,事务终止,数据保持一致性;执行 EXEC 命令后,命令和操作的数据类型不匹配,错误的命令会报错,但事务不会因为错误的命令而终止,而是会继续执行。正确的命令正常执行,错误的命令报错,从这个角度来看,数据也可以保持一致性;执行事务的过程中,Redis 服务宕机。这里需要考虑服务配置的持久化模式。无持久化的内存模式:服务重启之后,数据库没有保持数据,因此数据都是保持一致性的;RDB / AOF 模式: 服务重启后,Redis 通过 RDB / AOF 文件恢复数据,数据库会还原到一致的状态。综上所述,在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。《设计数据密集型应用》这本书是分布式系统入门的神书。在事务这一章节有一段关于 ACID 的解释:Atomicity, isolation, and durability are properties of the database,whereas consistency (in the ACID sense) is a property of the application. The application may rely on the database’s atomicity and isolation properties in order to achieve consistency, but it’s not up to the database alone. Thus, the letter C doesn’t really belong in ACID.原子性,隔离性和持久性是数据库的属性,而一致性(在 ACID 意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。因此,字母 C 不属于 ACID 。很多时候,我们一直在纠结的一致性,其实就是指符合现实世界的一致性,现实世界的一致性才是事务追求的最终目标。为了实现现实世界的一致性,需要满足如下几点:保证原子性,持久性和隔离性,如果这些特征都无法保证,那么事务的一致性也无法保证;数据库本身的约束,比如字符串长度不能超过列的限制或者唯一性约束;业务层面同样需要进行保障 。4.5 总结我们通常称 Redis 为内存数据库 , 不同于传统的关系数据库,为了提供了更高的性能,更快的写入速度,在设计和实现层面做了一些平衡,并不能完全支持事务的 ACID。Redis 的事务具备如下特点:保证隔离性;无法保证持久性;具备了一定的原子性,但不支持回滚;一致性的概念有分歧,假设在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。另外,在抢红包的场景下, 因为每个步骤需要依赖上一个步骤返回的结果,需要通过 watch 来实现乐观锁 ,从工程角度来看, Redis 事务并不适合该业务场景。5 Lua 脚本5.1 简介“ Lua ” 在葡萄牙语中是“月亮”的意思,1993年由巴西的 Pontifical Catholic University 开发。该语言的设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua 脚本可以很容易的被 C/C ++ 代码调用,也可以反过来调用 C/C++ 的函数,这使得 Lua 在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替 XML, Ini 等文件格式,并且更容易理解和维护。Lua 由标准 C 编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。一个完整的 Lua 解释器不过 200 k,在目前所有脚本引擎中,Lua 的速度是最快的。这一切都决定了 Lua 是作为嵌入式脚本的最佳选择。Lua 脚本在游戏领域大放异彩,大家耳熟能详的《大话西游II》,《魔兽世界》都大量使用 Lua 脚本。Java 后端工程师接触过的 api 网关,比如 Openresty ,Kong 都可以看到 Lua 脚本的身影。从 Redis 2.6.0 版本开始, Redis内置的 Lua 解释器,可以实现在 Redis 中运行 Lua 脚本。使用 Lua 脚本的好处 :减少网络开销。将多个请求通过脚本的形式一次发送,减少网络时延。原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。复用。客户端发送的脚本会永久存在 Redis 中,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。Redis Lua 脚本常用命令:序号命令及描述1EVAL script numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。2EVALSHA sha1 numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。3SCRIPT EXISTS script [script ...] 查看指定的脚本是否已经被保存在缓存当中。4SCRIPT FLUSH 从脚本缓存中移除所有脚本。5SCRIPT KILL 杀死当前正在运行的 Lua 脚本。6SCRIPT LOAD script 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。5.2 EVAL 命令命令格式:1EVAL script numkeys key [key ...] arg [arg ...]说明:script是第一个参数,为 Lua 5.1脚本;第二个参数numkeys指定后续参数有几个 key;key [key ...],是要操作的键,可以指定多个,在 Lua 脚本中通过KEYS[1], KEYS[2]获取;arg [arg ...],参数,在 Lua 脚本中通过ARGV[1], ARGV[2]获取。简单实例:1redis> eval "return ARGV[1]" 0 100 2"100"3redis> eval "return {ARGV[1],ARGV[2]}" 0 100 10141) "100"52) "101"6redis> eval "return {KEYS[1],KEYS[2],ARGV[1]}" 2 key1 key2 first second71) "key1"82) "key2"93) "first"104) "second"下面演示下 Lua 如何调用 Redis 命令 ,通过redis.call()来执行了 Redis 命令 。1redis> set mystring 'hello world'2OK3redis> get mystring4"hello world"5redis> EVAL "return redis.call('GET',KEYS[1])" 1 mystring6"hello world"7redis> EVAL "return redis.call('GET','mystring')" 08"hello world"5.3 EVALSHA 命令使用 EVAL 命令每次请求都需要传输 Lua 脚本 ,若 Lua 脚本过长,不仅会消耗网络带宽,而且也会对 Redis 的性能造成一定的影响。思路是先将 Lua 脚本先缓存起来 , 返回给客户端 Lua 脚本的 sha1 摘要。 客户端存储脚本的 sha1 摘要 ,每次请求执行 EVALSHA 命令即可。EVALSHA 命令基本语法如下:redis> EVALSHA sha1 numkeys key [key ...] arg [arg ...] 实例如下:redis> SCRIPT LOAD "return 'hello world'" "5332031c6b470dc5a0dd9b4bf2030dea6d65de91" redis> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0 "hello world"5.4 事务 VS Lua 脚本从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。因为脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。-- https://redis.io/Lua 脚本是另一种形式的事务,他具备一定的原子性,但脚本报错的情况下,事务并不会回滚。Lua 脚本可以保证隔离性,而且可以完美的支持后面的步骤依赖前面步骤的结果。综上,Lua 脚本是抢红包场景最优的解决方案。但在编写 Lua 脚本时,要注意如下两点:为了避免 Redis 阻塞,Lua 脚本业务逻辑不能过于复杂和耗时;仔细检查和测试 Lua 脚本 ,因为执行 Lua 脚本具备一定的原子性,不支持回滚。6 实战准备我选择 Redisson 3.12.0 版本作为 Redis 的客户端,在 Redisson 源码基础上做一层薄薄的封装。创建一个 PlatformScriptCommand 类, 用来执行 Lua 脚本。// 加载 Lua 脚本 String scriptLoad(String luaScript); // 执行 Lua 脚本 Object eval(String shardingkey, String luaScript, ReturnType returnType, List<Object> keys, Object... values); // 通过 sha1 摘要执行Lua脚本 Object evalSha(String shardingkey, String shaDigest, List<Object> keys, Object... values);这里为什么我们需要添加一个 shardingkey 参数呢 ?因为 Redis 集群模式下,我们需要定位哪一个节点执行 Lua 脚本。public int calcSlot(String key) { if (key == null) { return 0; } int start = key.indexOf('{'); if (start != -1) { int end = key.indexOf('}'); key = key.substring(start+1, end); } int result = CRC16.crc16(key.getBytes()) % MAX_SLOT; log.debug("slot {} for {}", result, key); return result; }7 抢红包脚本客户端执行 Lua 脚本后返回 json 字符串。用户抢红包成功{ "code":"0", //红包金额 "amount":"7.1", //红包编号 "redPacketId":"162339217730846210" }用户已领取过{ "code":"1" }用户抢红包失败{ "code":"-1" }Redis Lua 中内置了 cjson 函数,用于 json 的编解码。-- KEY[1]: 用户防重领取记录 local userHashKey = KEYS[1]; -- KEY[2]: 运营预分配红包列表 local redPacketOperatingKey = KEYS[2]; -- KEY[3]: 用户红包领取记录 local userAmountKey = KEYS[3]; -- KEY[4]: 用户编号 local userId = KEYS[4]; local result = {}; -- 判断用户是否领取过 if redis.call('hexists', userHashKey, userId) == 1 then result['code'] = '1'; return cjson.encode(result); else -- 从预分配红包中获取红包数据 local redPacket = redis.call('rpop', redPacketOperatingKey); if redPacket then local data = cjson.decode(redPacket); -- 加入用户ID信息 data['userId'] = userId; -- 把用户编号放到去重的哈希,value设置为红包编号 redis.call('hset', userHashKey, userId, data['redPacketId']); -- 用户和红包放到已消费队列里 redis.call('lpush', userAmountKey, cjson.encode(data)); -- 组装成功返回值 result['redPacketId'] = data['redPacketId']; result['code'] = '0'; result['amount'] = data['amount']; return cjson.encode(result); else -- 抢红包失败 result['code'] = '-1'; return cjson.encode(result); end end脚本编写过程中,难免会有疏漏,如何进行调试?个人建议两种方式结合进行。编写 junit 测试用例 ;从 Redis 3.2 开始,内置了 Lua debugger(简称LDB), 可以使用 Lua debugger 对 Lua 脚本进行调试。8 异步任务在 Redisson 基础上封装了两个类 ,简化开发者的使用成本。RedisMessageConsumer : 消费者类,配置监听队列名,以及对应的消费监听器String groupName = "userGroup"; String queueName = "userAmountQueue"; RedisMessageQueueBuilder buidler = redisClient.getRedisMessageQueueBuilder(); RedisMessageConsumer consumer = new RedisMessageConsumer(groupName, buidler); consumer.subscribe(queueName, userAmountMessageListener); consumer.start();RedisMessageListener : 消费监听器,编写业务消费代码public class UserAmountMessageListener implements RedisMessageListener { @Override public RedisConsumeAction onMessage(RedisMessage redisMessage) { try { String message = (String) redisMessage.getData(); // TODO 调用用户余额系统 // 返回消费成功 return RedisConsumeAction.CommitMessage; }catch (Exception e) { logger.error("userAmountService invoke error:", e); // 消费失败,执行重试操作 return RedisConsumeAction.ReconsumeLater; } } }9 写到最后"纸上得来终觉浅, 绝知此事要躬行" 。学习 Redis Lua 过程中,查询了很多资料,一个例子一个例子的实践,收获良多。非常坦诚的讲 , 写这篇文章之前,我对 Redis Lua 有很多想当然的理解,比如 Redis 的事务不能回滚就让我惊讶不已。所以当面对自己不熟悉的知识点时,不要轻易下结论,以谦卑的心态去学习,才是一个工程师需要的心态。同时,没有任何一项技术是完美的,在设计和编码之间,有这样或者那样的平衡,这才是真实的世界。
  • [知识分享] 拔掉电源会怎样?GaussDB(for Redis)双活让你有备无患
    【摘要】 GaussDB(for Redis)推出双活方案,助力全球化业务部署,为您的数据资产保驾护航!本文分享自华为云社区《华为云GaussDB(for Redis)揭秘第22期:拔掉电源会怎样?GaussDB(for Redis)双活让你有备无患》,作者: 高斯Redis官方博客。一、GaussDB(for Redis)双活方案介绍       数据库系统是业务稳定运行的基石,其重要性不言而喻。然而,现实世界存在着的如断电、火灾,甚至是更小概率的地震等突发灾害,这些不稳定因素都会威胁到公司核心业务的连续性。       华为云GaussDB(for Redis)是采用存算分离架构的企业级KV数据库,使用方式上完全兼容开源Redis,同时稳定性全面超越开源Redis,此外还提供数据高可靠存储、秒级在线扩容等企业级能力。       在上半年的故障演练中,作为需求方的某内部重要业务部门对华为云KV数据库GaussDB(for Redis)进行了一系列严苛的可靠性测试。其中就包括模拟实例级断电,评测GaussDB(for Redis)的双活容灾能力。在故障演练中,GaussDB(for Redis)顺利通过测试,满足了业务部门对RTO和RPO等指标的严格要求。GaussDB(for Redis)支持双活容灾,即在两个实例之间建立数据同步链路,其中主实例支持读写,备实例只读。适用于以下两类业务场景:       1.    灾备系统:核心业务生产环境可靠性要求高,例如金融服务等。       2.    实例级读写分离:有超高并发访问诉求,例如广告竞价、推荐系统等。       在后续规划中,GaussDB(for Redis)还将进一步支持双主实例模式,实现双主双写,敬请期待。下面让我们来深入了解GaussDB(for Redis)的双活能力。二、GaussDB(for Redis)双活原理及评估指标1) 架构解析       常见的容灾方案有以下2种:同城容灾:由于距离近,通信质量好,比较容易实现数据零丢失。一般用于防范火灾、供电故障等人为破坏引起的灾难。异地容灾:由于主备数据中心之间距离远,通常会有少量的数据丢失,而异地灾备可以防范火灾、水灾、建筑物破坏、地震、战争等可能遇到的风险隐患,保障业务的连续性。       GaussDB(for Redis)双活方案支持同城容灾和异地容灾,下图展示了其技术原理:       可以看出,与开源Redis那种简单的命令转发不同,GaussDB(for Redis)的双活方案是基于WAL日志的数据同步,原理上更类似于MySQL数据库。在双活架构中,RsyncServer进程负责数据的全量和增量同步。数据同步链路采用华为云内部高速网络,同Region内仅毫秒级延迟。       GaussDB(for Redis)的双活功能有以下企业级特性:全量同步:支持秒级快照,且不影响原有集群的正常读写,发送速度快增量同步:监听日志写入变更,实时同步最新数据,毫秒级延迟key保序:主备实例采用多线程异步并发模式发送数据,按Key保序可靠传输:利用wal日志序号的单调递增特性,实现滑窗机制确保可靠传输高效传输:日志迭代、网络发送、DB增删监听等任务采用Reactor事件管理,全流程pipeline异常重传:解决网络抖动导致的丢包或延迟现象断点续传:每个DB持久化保存其应答过的日志序号,如发生宕机、网络隔离、进程重启等情况,可从该序号位置进行续传防止回流:同步到对端流量做特殊标记,防止回流到源端2) 技术指标       从技术上看,业界衡量容灾系统有两个主要指标:RPO:最多可能丢失数据的时长。RTO:从灾难发生到整个系统恢复正常所需要的最大时长。       一般而言,容灾系统能够提供较好的RTO和RPO指标。国际通用的容灾系统的评审标准SHARE 78(7个层次、8个原则),可以作为广大用户衡量和选择容灾解决方案的指标。      目前,在大部分场景下,GaussDB(for Redis)灾备方案位于6级,即只有在极端场景下才会丢失少量数据。3) 优势总结       根据前期市场调研,有些客户的业务场景基于开源Redis搭建集群级的灾备系统,从技术原理上评估可靠性很低,违背了灾备的初衷。与之相比,GaussDB(for Redis)提供企业级可靠的双活方案,优势如下:三、GaussDB(for Redis)双活搭建步骤       登录GaussDB NoSQL控制台,您可以很方便地搭建GaussDB(for Redis)双活系统:       1.    点击“购买数据库实例”,创建2个GaussDB(for Redis)实例(主、备),建议规格保持相同。       2.    进行网络配置,详细指导:https://support.huaweicloud.com/redisug-nosql/nosql_10_0502.html       3.    选中期望的主实例,如图操作,开始搭建双活关系:       4.    在“搭建双活关系”页面,选中期望的备实例,点击确定,即开始双活系统搭建。              注意:一旦双活系统开始搭建,备实例数据将被清空,请谨慎操作。        双活关系搭建完成后,除了用于容灾之外,其备实例可提供读服务,可以有效分担主实例的读压力。四、GaussDB(for Redis)双活技术演进        当前双活方案中,备实例在运行期间仅提供读服务,如果两个数据中心能同时提供写服务,那么将极大地提高资源利用率。当灾难发生时,双活系统提供极致的RPO和RTO,从而最大程度保障业务连续性。它具有资源利用率高的特点,支持数据冲突解决等功能。目前,GaussDB(for Redis)的双活多写功能已经在开发阶段,不久之后即将上线,请大家拭目以待。五、附录本文作者:华为云数据库GaussDB(for Redis)团队杭州/西安/深圳简历投递:yuwenlong4@huawei.com更多产品信息,欢迎访问官方博客:bbs.huaweicloud.com/blogs/248875
  • [知识分享] 使用高斯Redis实现二级索引
    【摘要】 高斯Redis 搭建业务二级索引,低成本,高性能,实现性能与成本的双赢本文分享自华为云社区《华为云GaussDB(for Redis)揭秘第21期:使用高斯Redis实现二级索引》,作者:高斯Redis官方博客。一、背景提起索引,第一印象就是数据库的名词,但是,高斯Redis也可以实现二级索引!!!高斯Redis中的二级索引一般利用zset来实现。高斯Redis相比开源Redis有着更高的稳定性、以及成本优势,使用高斯Redis zset实现业务二级索引,可以获得性能与成本的双赢。索引的本质就是利用有序结构来加速查询,因而通过Zset结构高斯Redis可以轻松实现数值类型以及字符类型索引。• 数值类型索引(zset按分数排序):• 字符类型索引(分数相同时zset按字典序排序):            下面让我们切入两类经典业务场景,看看如何使用高斯Redis来构建稳定可靠的二级索引系统。 二、场景一:词典补全当在浏览器中键入查询时,浏览器通常会按照可能性推荐相同前缀的搜索,这种场景可以用高斯Redis二级索引功能实现。2.1 基本方案最简单的方法是将用户的每个查询添加到索引中。当需要进行用户输入补全推荐时,使用ZRANGEBYLEX执行范围查询即可。如果不希望返回太多条目,高斯Redis还支持使用LIMIT选项来减少结果数量。• 将用户搜索banana添加进索引:ZADD myindex 0 banana:1• 假设用户在搜索表单中输入“bit”,并且我们想提供可能以“bit”开头的搜索关键字。ZRANGEBYLEX myindex "[bit" "[bit\xff"即使用ZRANGEBYLEX进行范围查询,查询的区间为用户现在输入的字符串,以及相同的字符串加上一个尾随字节255(\xff)。通过这种方式,我们可以获得以用户键入字符串为前缀的所有字符串。2.2 与频率相关的词典补全实际应用中通常希望按照出现频率自动排序补全词条,同时可以清除不再流行的词条,并自动适应未来的输入。我们依然可以使用高斯Redis的ZSet结构实现这一目标,只是在索引结构中,不仅需要存储搜索词,还需要存储与之关联的频率。• 将用户搜索banana添加进索引      • 判断banana是否存在ZRANGEBYLEX myindex "[banana:" + LIMIT 0 1      • 假设banana不存在,添加banana:1,其中1是频率ZADD myindex 0 banana:1      • 假设banana存在,需要递增频率      若ZRANGEBYLEX myindex "[banana:" + LIMIT 0 1 中返回的频率为1      1)删除旧条目:ZREM myindex 0 banana:1      2)频率加一重新加入:ZADD myindex 0 banana:2      请注意,由于可能存在并发更新,因此应通过Lua脚本发送上述三个命令,用Lua script自动获得旧计数并增加分数后重新添加条目。• 假设用户在搜索表单中输入“banana”,并且我们想提供相似的搜索关键字。通过ZRANGEBYLEX获得结果后按频率排序。ZRANGEBYLEX myindex "[banana:" + LIMIT 0 10 1) "banana:123" 2) "banaooo:1" 3) "banned user:49" 4) "banning:89"• 使用流算法清除不常用输入。从返回的条目中随机选择一个条目,将其分数减1,然后将其与新分数重新添加。但是,如果新分数为0,我们需从列表中删除该条目。      • 若随机挑选的条目频率是1,如banaooo:1ZREM myindex 0 banaooo:1      • 若随机挑选的条目频率大于1,如banana:123ZREM myindex 0 banana:123 ZADD myindex 0 banana:122      从长远来看,该索引会包含热门搜索,如果热门搜索随时间变化,它还会自动适应。三、场景二:多维索引除了单一维度上的查询,高斯Redis同样支持在多维数据中的检索。例如,检索所有年龄在50至55岁之间,同时薪水在70000至85000之间的人。实现多维二级索引的关键是通过编码将二维的数据转化为一维数据,再基于高斯Redis zset存储。从可视化视角表示二维索引。下图空间中有一些点,它们代表我们的数据样本,其中x和y是两个变量,其最大值均为400。图片中的蓝色框代表我们的查询。我们希望查询x介于50和100之间,y介于100和300之间的所有点。3.1 数据编码    若插入数据点为x = 75和y = 2001) 填充0(数据最大为400,故填充3位)       x = 075       y = 2002) 交织数字,以x表示最左边的数字,以y表示最左边的数字,依此类推,以便创建一个编码       027050若使用00和99替换最后两位,即027000 to 027099,map回x和y,即:       x = 70-79       y = 200-209因此,针对x=70-79和y = 200-209的二维查询,可以通过编码map成027000 to 027099的一维查询,这可以通过高斯Redis的Zset结构轻松实现。同理,我们可以针对后四/六/etc位数字进行相同操作,从而获得更大范围。3) 使用二进制为获得更细的粒度,可以将数据用二进制表示,这样在替换数字时,每次会得到比原来大二倍的搜索范围。假设我们每个变量仅需要9位(以表示最多400个值的数字),我们采用二进制形式的数字将是:       x = 75 -> 001001011       y = 200 -> 011001000交织后,000111000011001010让我们看看在交错表示中用0s ad 1s替换最后的2、4、6、8,...位时我们的范围是什么:3.2 添加新元素    若插入数据点为x = 75和y = 200    x = 75和y = 200二进制交织编码后为000111000011001010,ZADD myindex 0 0001110000110010103.3 查询    查询:x介于50和100之间,y介于100和300之间的所有点从索引中替换N位会给我们边长为2^(N/2)的搜索框。因此,我们要做的是检查搜索框较小的尺寸,并检查与该数字最接近的2的幂,并不断切分剩余空间,随后用ZRANGEBYLEX进行搜索。下面是示例代码:def spacequery(x0,y0,x1,y1,exp) bits=exp*2 x_start = x0/(2**exp) x_end = x1/(2**exp) y_start = y0/(2**exp) y_end = y1/(2**exp) (x_start..x_end).each{|x| (y_start..y_end).each{|y| x_range_start = x*(2**exp) x_range_end = x_range_start | ((2**exp)-1) y_range_start = y*(2**exp) y_range_end = y_range_start | ((2**exp)-1) puts "#{x},#{y} x from #{x_range_start} to #{x_range_end}, y from #{y_range_start} to #{y_range_end}" # Turn it into interleaved form for ZRANGEBYLEX query. # We assume we need 9 bits for each integer, so the final # interleaved representation will be 18 bits. xbin = x_range_start.to_s(2).rjust(9,'0') ybin = y_range_start.to_s(2).rjust(9,'0') s = xbin.split("").zip(ybin.split("")).flatten.compact.join("") # Now that we have the start of the range, calculate the end # by replacing the specified number of bits from 0 to 1. e = s[0..-(bits+1)]+("1"*bits) puts "ZRANGEBYLEX myindex [#{s} [#{e}" } } end spacequery(50,100,100,300,6) 四、总结本文介绍了如何通过高斯Redis搭建二级索引,二级索引在电商、图(hexastore)、游戏等领域具有广泛的应用场景,高斯redis现网亦有很多类似应用。高斯Redis基于存算分离架构,依托分布式存储池确保数据强一致,可方便的支持二级索引功能,为企业客户提供稳定可靠、超高并发,且能够极速弹性扩容的核心数据存储服务。附录本文作者:华为云数据库GaussDB(for Redis)团队杭州/西安/深圳简历投递:yuwenlong4@huawei.com更多产品信息,欢迎访问官方博客:bbs.huaweicloud.com/blogs/248875
  • [技术干货] 【全方位解读】GaussDB(for Redis)技术与应用解读
    文末附件可下载《GaussDB(for Redis)技术与应用解读》电子书你有没有想过这样一个问题:当下的互联网时代,网络覆盖了10.32亿人的生活,从衣食住行到日常社交,究竟是什么支撑着我们多样化的需求,它背后的底层系统到底是什么?是数据库。第一次听到这个答案可能会有点迟疑,细思一下,不无道理!随着短视频的兴起和支付系统的完善,越来越多人的物质消费、精神需求、情感导向等被算法一一俘获,“TA经济”在年轻人中迅速蔓延,人们也与数据库无数次不期而遇,比如:周末宅在家里,不想出门也不想做饭,饿了点外卖,查询附近的餐厅、附近的外卖商家,这些所有地理位置的查找,都需要数据库的存储;吃完饭躺在沙发上玩手机,微信朋友圈、微博、抖音以及头条等社交软件,系统及时且准确地推送着关注的好友或感兴趣的内容,这些Feed流信息的推送,也是数据库的功劳;到了晚上,各个直播间里“宝子们福利来喽,今天xx返场,全年最低价……”,这高亢的呼唤、迷人的价格让穿梭在直播间的看客们果断砍手,于是,账单来袭,不经意间又一次与数据库撞个满怀。数据库是存放数据的地方,它神秘但又无处不在,而类似于以上发生的场景,都有Redis忙碌的身影。提起Redis,互联网从业者里无人不知无人不晓。开源Redis作为一款经典的“缓存”产品,有着丰富的数据类型,不仅好用还支撑着众多业务的架构搭建,在游戏、电商、社交媒体及其他互联网领域有着重要地位。然而,在数据量和访问量指数级增长的今天,“容量有限,高并发写入容易OOM”、“内存昂贵、成本降不下来”、“可靠性有限,容易丢关键数据”的问题,也成了开源Redis绕不过的痛。不过,GaussDB(for Redis)完美地解决了开源Redis的难题。GaussDB(for Redis) 是华为云推出的一款,基于计算存储分离架构的云原生分布式数据库,兼容Redis生态的云原生NoSQL数据库,并提供强一致、三副本存储,高度保证数据的安全可靠。GaussDB(for Redis)突破了开源Redis的内存限制,通过将数据进行冷热分离,在保证热数据驻留计算节点内存满足业务低时延要求的同时,将冷数据置换入分布式存储池进行持久化存储,最大程度的降低使用成本,具有高兼容、高性价比、高可靠、弹性伸缩、高可用、冷热分离等特点。为了让开发者更加系统地了解GaussDB(for Redis),华为云数据库专家集经验与心血之所成,联合推出《GaussDB(for Redis)企业与技术解读》经典案例锦集,从入门篇、架构篇、测试篇、应用篇四个章节,全面解析GaussDB(for Redis)技术架构与实践,帮助开发者充分了解GaussDB(for Redis)的能力。附件可下载《GaussDB(for Redis)技术与应用解读》电子书
  • [知识分享] 盘点华为云GaussDB(for Redis)六大秒级能力
    >摘要:盘点高斯Redis的秒级能力,包括扩容、备份、删除、启动等。 本文分享自华为云社区《华为云GaussDB(for Redis)揭秘第20期:六大秒级能力盘点》,作者: 高斯Redis官方博客。 # 引言 大家经常用时间类指标来评估数据库能力,诸如RTO/RPO、扩容耗时、变更操作对业务影响的时长等等,在高可用需求日益迫切的当下,这些都是评价数据库能力的重要参考。在KV数据库领域,华为云GaussDB(for Redis)基于存算分离的基础架构,在很多关键评估维度都具有“秒级”能力,今天便带大家了解一下。 # GaussDB(for Redis)秒级能力盘点 ## 秒启动 社区版Redis在宕机恢复的场景下,需要依赖AOF或RDB文件将数据全量加载到内存中,随着数据量增加,这个耗时经常需要数分钟甚至更久。 GaussDB(for Redis)节点拉起时无需加载全量数据,即可快速提供服务,启动时间受数据量影响极小,**大数据规模下亦可以达到秒级启动**,体验丝滑。 ## 秒删除 社区版Redis在删除/过期数据,尤其是大key的情况下会导致访问严重阻塞。 而GaussDB(for Redis)从根本上解决了大key删除/过期的操作隐患,在底层事先采用了“标记删除,异步回收”的逻辑,对任何数据执行删除/过期,都是立刻执行成功且0阻塞,因此完全不影响业务访问。在实测删除/过期一个大hash key(包含1000w个元素)时,GaussDB(for Redis)仅耗时毫秒。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/1/1656639071668891773.png) 图:GaussDB(for Redis)大key秒删 ## 秒备份 社区版Redis打快照时需要将全量数据dump到磁盘上,效率受到磁盘IO性能影响,耗时久。而且有着“fork问题”,造成性能抖动,导致容量利用率只有50%。 GaussDB(for Redis)备份基于底层文件系统的快照技术,记录某一时间点的数据状态,无需进行耗时的数据拷贝操作,真正做到秒级打快照。客户的数据快照文件自动上传到华为云OBS桶内,后续可用来一键恢复到新实例,相当便捷。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/1/1656639088751433379.png) 图:GaussDB(for Redis)时间点快照一键恢复 ## 秒扩容 社区版Redis计算、存储强耦合。比如64G规格装满了,当需要升128G规格时,升级过程不仅耗时久,严重影响业务;而且价格翻倍,存在大量算力成本浪费。 GaussDB(for Redis)提供以下3种灵活变更的“扩容”方式: - **1)数据快装满了?一键扩容量,秒级完成,不影响业务** GaussDB(for Redis)底层存储资源按配额形式提供使用,数据快写满场景,只需一键点击扩容,秒级获得更多容量。比如64G扩容到128G,秒级完成,不影响业务。同时无需为不必要的算力买单,扩容价格涨幅小,成本有极大优势。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/1/1656639130986819876.png) 图:GaussDB(for Redis)存储扩容 - **2)流量突发高峰?一键加节点,业务只受到秒级影响** GaussDB(for Redis)扩算力的最便捷方式即增加节点,秒级即可完成负载重新均衡,业务短暂受影响。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/1/1656639152163701279.png) 图:GaussDB(for Redis)算力扩容-添加节点 - **3)周期性做活动?节点规格按需伸缩,避免算力浪费** 在一些周期性的活动抢券场景,客户往往需要按周期进行规格升降,承载计划内的流量高峰,同时避免平时的浪费。GaussDB(for Redis)支持提升节点规格,比如16U升32U,客户可灵活按需扩容。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/1/1656639171808506421.png) 图:GaussDB(for Redis)算力扩容-节点规格变更 ## 故障秒恢复 GaussDB(for Redis)内部有全局的HA管控能力,计算节点故障秒级探测,发现故障后同样采用秒级接管的方式处理,秒级恢复业务侧正常访问。 ## 双活秒级RPO GaussDB(for Redis)支持region内/跨region容灾,即搭建稳定主从实例,提供企业级的高可用。通过高性能数据同步模块实现高性能主从实例同步,在高压力写入的业务压力下测试,有着RPO=10s的超高可靠性表现。 # 总结 经过上面的简单盘点可以看出,GaussDB(for Redis)在启动、大key删除、扩容变更等多种应用场景下均具有秒级能力,最小化业务影响,给用户提供企业级安全、稳定、可靠的使用体验。
  • [新手课堂] [转载]redis内存优化
    ## Redis优化及配置 Redis所有的数据都在内存中,而内存又是非常宝贵的资源。常用的内存优化方案有如下几部分: 一. 配置优化 二. 缩减键值对象 三. 命令处理 四. 缓存淘汰方案 ## 一、配置优化 **Linux 配置优化** ** ** 目前大部分公司都会将 Web 服务器、数据库服务器等部署在 Linux 操作系统上,Redis优化也需要考虑操作系统,所以接下来介绍 Linux 操作系统如何优化Redis。 ①、内存分配 vm.overcommit_memory Redis是内存操作,需要优先使用内存。设置overcommit 为1。是为了让 fork 操作能够在低内存下也执行成功。Linux 操作系统对大部分申请内存的请求都回复 yes,以便能运行更多的程序。因为申请内存后,并不会马上使用内存,这种技术叫做 overcommit。 vm.overcommit_memory 用来设置内存分配策略,有三个可选值: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/19/1655652174158283205.png) ②、THP Redis 在启动时可能会看到如下日志: ``` WARNING you have Transparent Huge Pages (THP) support enabled ``in` `your kernel. This``will create latency and memory usage issues with Redis. To fix this issue run``the ``command` `'echo never > /sys/kernel/mm/transparent_hugepage/enabled'` `as root,``and add it to your ``/etc/rc``.``local` `in` `order to retain the setting after a reboot.``Redis must be restarted after THP is disabled. ``` Redis建议修改 Transparent Huge Pages(THP)的相关配置,Linux kernel 在2.6.38内核增加了 THP 特性,支持大内存页(2MB)分配,默认开启。当开启时可以降低 fork 子进程的速度,但 fork 操作之后,每个内存页从原来 4KB 变为 2MB,会大幅增加重写期间父进程内存消耗。同时每次写命令引起的复制内存页单位放大了512倍,会拖慢写操作的执行时间,导致大量写操作慢查询,例如简单的 incr 命令也会出现在慢查询中。因此 Redis 日志中建议将此特性进行禁用,禁用方法如下: ``` echo` `never > ``/sys/kernel/mm/transparent_hugepage/enabled ``` 为使机器重启后THP配置依然生效,可以在/etc/rc.local 中追加 echo never>/sys/kernel/mm/transparent_hugepage/enabled ③、swappiness swap对于操作系统来比较重要,当物理内存不足时,可以将一部分内存页进行 swap 操作,已解燃眉之急。swap 空间由硬盘提供,对于需要高并发、高吞吐的应用来说,磁盘 IO 通常会成为系统瓶颈。在 Linux 中,并不是要等到所有物理内存都使用完才会使用到 swap,系统参数 swppiness 会决定操作系统使用 swap 的倾向程度。swappiness 的取值范围是0~100,swappiness 的值越大,说明操作系统可能使用swap的概率越高,swappiness 值越低,表示操作系统更加倾向于使用物理内存。swap 的默认值是60,了解这个值的含义后,有利于 Redis 的性能优化。下图对 swappiness 的重要值进行了说明: 、![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/19/1655652186537720655.png) OOM(Out Of Memory)killer 机制是指 Linux 操作系统发现可用内存不足时,强制杀死一些用户进程(非内核进程),来保证系统有足够的可用内存进行分配。 为使配置在重启 Linux 操作系统后立即生效,只需要在/etc/sysctl.conf 追加 vm.swappiness={bestvalue}即可。 echo vm.swappiness={bestvalue} >> /etc/sysctl.conf 查看 swap 的总体情况可使用 free-m 命令,如下服务器开启了8189M swap,其中使用了 5241MB ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/19/1655652200458282425.png) ④、ulimit设置 可以通过 ulimit 查看和设置系统当前用户进程的资源数。其中 ulimit-a 命令包含的 open files 参数,是单个用户同时打开的最大文件个数: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/19/1655652208794907632.png) Redis允许同时有多个客户端通过网络进行连接,可以通过配置 maxclients 来限制最大客户端连接数。对 Linux 操作系统来说,这些网络连接都是文件句柄。假设当前 open files 是4096,那么启动 Redis 时会看到如下日志: \#You requested maxclients of 10000 requiring at least 10032 max file descriptors. \#Redis can’t set maximum open files to 10032 because of OS error: Operation not permitted. \#Current maximum open files is 4096. Maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit –n'. **上面日志的内容解释如下:** 第一行:Redis 建议把 open files 至少设置成10032,那么这个10032是如何来的呢?因为 maxclients 默认是10000,这些是用来处理客户端连接的,除此之外,Redis 内部会使用最多32个文件描述符,所以这里的10032=10000+32。** ** 第二行:Redis 不能将 open files 设置成10032,因为它没有权限设置。 第三行:当前系统的 open files 是4096,所以将 maxclients 设置成4096-32=4064个,如果你想设置更高的 maxclients,请使用 ulimit-n 来设置。 从上面的三行日志分析可以看出 open files 的限制优先级比 maxclients 大。 Open files 的设置方法如下: ``` ulimit` `–Sn {max-``open``-files} ``` **Redis配置优化** ①、设置maxmemory。设置Redis使用的最大物理内存,即Redis在占用maxmemory大小的内存之后就开始拒绝后续的写入请求,该参数可以确保Redis因为使用了大量内存严重影响速度或者发生OOM(out-of-memory,发现内存不足时,它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存)。此外,可以使用info命令查看Redis占用的内存及其它信息。 ②、让键名保持简短。键的长度越长,Redis需要存储的数据也就越多 ③、客户端timeout 设置一个超时时间,防止无用的连接占用资源。设置如下命令: timeout 150 tcp-keepalive 150 (定时向client发送tcp_ack包来探测client是否存活的。默认不探测) ④、检查数据持久化策略 数据落磁盘尽可能减少性能损坏,以空间换时间。设置如下命令: rdbcompression no : 默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。 rdbchecksum no : 默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。 ⑤、优化AOF和RDB,减少占用CPU时间 主库可以不进行dump操作或者降低dump频率。取消AOF持久化。 命令如下: appendonly no ⑥、监控客户端的连接 因为Redis是单线程模型(只能使用单核),来处理所有客户端的请求, 但由于客户端连接数的增长,处理请求的线程资源开始降低分配给单个客户端连接的处理时间 ⑦、限制客户端连接数 。在Redis-cli工具中输入info clients可以查看到当前实例的所有客户端连接信息 maxclients属性上修改客户端连接的最大数,可以通过在Redis-cli工具上输入 config set maxclients 去设置最大连接数。根据连接数负载的情况。 ## 二、缩减键值对象 降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。 - key长度:如在设计键时,在完整描述业务情况下,键值越短越好。 - value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。首先应该在业务上精简业务对象,在存到Redis之前先把你的数据压缩下。 常用压缩方法对比: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/19/1655652239456342501.png) ## 三、命令处理 Redis基于C/S架构模式,基于Redis操作命令是解决响应延迟问题最关键的部分,因为Redis是个单线程模型,客户端过来的命令是按照顺序执行的。比较常见的延迟是带宽,通过千兆网卡的延迟大约有200μs。倘若明显看到命令的响应时间变慢,延迟高于200μs,那可能是Redis命令队列里等待处理的命令数量比较多。 要分析解决这个性能问题,需要跟踪命令处理数的数量和延迟时间。 比如可以写个脚本,定期记录total_commands_processed的值。当客户端明显发现响应时间过慢时,可以通过记录的total_commands_processed历史数据值来判断命理处理总数是上升趋势还是下降趋势,以便排查问题。 在info信息里的 total_commands_processed 字段显示了Redis服务处理命令的总数 ![1655652398457.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/19/1655652428680504250.png) 解决方案: ①、使用多参数命令:若是客户端在很短的时间内发送大量的命令过来,会发现响应时间明显变慢,这由于后面命令一直在等待队列中前面大量命令执行完毕。有个方法可以改善延迟问题,就是通过单命令多参数的形式取代多命令单参数的形式。 举例来说:循环使用LSET命令去添加1000个元素到list结构中,是性能比较差的一种方式,更好的做法是在客户端创建一个1000元素的列表,用单个命令LPUSH或RPUSH,通过多参数构造形式一次性把1000个元素发送的Redis服务上。下面是Redis的一些操作命令,有单个参数命令和支持多个参数的命令,通过这些命令可尽量减少使用多命令的次数。 ②、管道命令:另一个减少多命令的方法是使用管道(pipeline),把几个命令合并一起执行,从而减少因网络开销引起的延迟问题。因为10个命令单独发送到服务端会引起10次网络延迟开销,使用管道会一次性把执行结果返回,仅需要一次网络延迟开销。Redis本身支持管道命令,大多数客户端也支持,倘若当前实例延迟很明显,那么使用管道去降低延迟是非常有效的。 ## 四、缓存淘汰优化 redis内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略。如果不淘汰经常不用的缓存数据,那么正常的数据将不会存储到缓存当中。 可通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能。 maxmemory 值得注意的是,maxmemory为0的时候表示我们对Redis的内存使用没有限制。 根据应用场景,选择淘汰策略 maxmemory-policy noeviction 内存淘汰的过程如下: ①、首先,客户端发起了需要申请更多内存的命令(如set)。 ②、然后,Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存。 ③、最后,如果上面都没问题,则这个命令执行成功。 ### 动态改配置命令 此外,redis支持动态改配置,无需重启。 **设置最大内存** config set maxmemory 100000 **设置淘汰策略** config set maxmemory-policy noeviction ### 内存淘汰策略 volatile-lru 从已设置过期时间的数据集(server.db.expires)中挑选最近最少使用的数据淘汰。 allkeys-lru 从数据集(server.db.dict)中挑选最近最少使用的数据淘汰 volatile-lfu 从设置了过期时间的数据集(server.db.expires)中选择某段时间之内使用频次最小的键值对清除掉 allkeys-lfu 从所有的数据集(server.db.dict)中选择某段时间之内使用频次最少的键值对清除 volatile-ttl 从已设置过期时间的数据集(server.db.expires)中挑选将要过期的数据淘汰 volatile-random 从已设置过期时间的数据集(server.db.expires)中任意选择数据淘汰 allkeys-random 从数据集(server.db.dict)中任意选择数据淘汰 no-enviction 当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错。 算法文章:(https://blog.csdn.net/ZYZMZM_/article/details/90546812) ### 如何选择淘汰策略 下面看看几种策略的适用场景 allkeys-lru:如果我们的应用对缓存的访问符合幂律分布,也就是存在相对热点数据,或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择allkeys-lru策略。 allkeys-random:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略。 volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被eviction。 另外,volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此建议使用allkeys-lru策略从而更有效率的使用内存。 原文链接:[https://blog.csdn.net/qq_39399966/article/details/101211939](https://blog.csdn.net/qq_39399966/article/details/101211939)
  • [交流分享] 关于幂等性的学习笔记
    最基础的概念,什么是幂等性?幂等性:提交多次的情况下,结果都一样。比如数据库查询,可称为天然幂等性,即查询多次结果都一样,无需人为去做幂等性操作。但是update table set value=value+1 where id=1,每次执行的结构都会发生变化,不是幂等。inter into table(id,name)values(1,‘name’),如id不是主键或者没有唯一索引,重复操作上面的业务,会插入多条数据,不具备幂等性;所以我们在什么情景下需要确保幂等性呢?用户多次点击保存按钮用户保存成功后,返回上一页再次保存微服务相互调用,由于网络原因,导致请求失败解决方案一、token机制:1、根据业务场景,判断哪些业务存在幂等性问题,在执行业务之前先获取token,将token缓存止redis中2、调用业务接口时,将token携带过去,一般放在请求头,作为Auth认证3、服务器判断token是否存在于redis中,存在表示第一次请求,然后删除token,继续执行业务4、如果不存在,则表示反复操作,不执行业务逻辑,直接返回重复标志!结束风险性:业务执行前删除还是后删除token?如果是执行后删除,在业务执行中,未删除token,用户又点了请求进来,那么则无法保障幂等性。如果是执行前删除,在分布式下,用户快速请求2次,这时2个请求同时到redis去获取token,对比成功,同时删除,同时执行业务,那么也无法保障幂等性。so:使用执行前删除,在分布式情况下,获取,对比,删除必须确保原子性,所以要加分布式锁。二、加锁1、数据库锁select * from table where … for update2、业务层面加分布式锁将获取、对比、删除作为一个原子性的操作加锁,处理完成后释放锁,确保串行操作。三、约束数据库唯一约束:通过主键、唯一索引,确保无法重复新增同一笔数据,这就能确保幂等性
  • [知识分享] 谁说Redis不能存大key
    >摘要:推荐使用GaussDB(for Redis)搞定"大key"存储,从根本上解决社区版Redis使用风险。 本文分享自华为云社区《[华为云GaussDB(for Redis)揭秘第18期:谁说Redis不能存大key](https://bbs.huaweicloud.com/blogs/358513?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=database&utm_content=content)》,作者: 高斯Redis官方博客 。 # 一、社区版Redis的大key痛点 GaussDB(for Redis)专家小强最近有点忙,因为很多客户经理都来找他咨询社区版Redis的大key问题,且一个个都求知欲爆表: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654831750890201410.png) 小强一拍大腿:你们还真问对人了! 根据现网经验,生产环境因为大key导致的Redis事故屡见不鲜,其中典型的有扩容失败、请求阻塞、OOM宕机等等。早期业务规划不充分、消息队列消费不及时、未及时清理无效数据等原因都可能引入大key隐患。 **社区版Redis大key隐患常见于以下三大场景:** 1. 内存消耗不均衡,大key所在分片有OOM风险 2. 扩容时需要搬迁部分数据,大key耗时久,会导致访问阻塞甚至数据丢失 3. 删除或过期大key时,业务访问被长时间阻塞,甚至导致主从同步中断 社区版Redis架构并不适合可靠存储大key,业界也只能建议预防、拆分或及时清理大key。从客户视角出发,其实有些场景是需要大key的,例如企业ERP系统,海量货币汇率存储等。这时即使适当拆分,也避免不了较大HASH key存在。 # 二、华为云GaussDB(for Redis)的大Key解决方案 小强始终认为,好的产品应当尽量把复杂留给自己,把简单留给用户。因此,小强给每一位来咨询大key问题的客户经理都安利了better解决方案——**使用华为云企业级KV数据库GaussDB(for Redis)**。 大key场景下,GaussDB(for Redis)究竟比社区版Redis优秀在哪?**下面从3个角度深度分析**: ## 1. 高斯Redis支持大key存储,不用担心分片OOM - 社区版Redis存储大key会导致分片内存消耗不均,随着集群整体数据量水位提升,大key所在分片随时有OOM风险。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654831808025261121.png) - 高斯Redis支持大key可靠存储,且不会导致分片OOM。需要注意的是,虽然高斯Redis适合用来可靠存储大key,但从网络链路角度考虑,业务应避免对大key执行诸如hgetall等风险命令。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654831820002773073.png) ## 2. 高斯Redis在大key场景中也支持秒级无损扩容 - 社区版Redis在扩容时,由于要搬迁数据,此时画风是这样的: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654831839254171355.png) 图:社区版Redis在大key场景扩容,风险高 可以总结为:**有大key,开源Redis请谨慎扩容!** - 高斯Redis支持秒级扩容,并支持升规格、加节点、加存储容量3种手段灵活扩容,运维体验极佳。‍ ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654831894877234967.png) 图:高斯Redis在大key场景可安全扩容 从上图可以看出,高斯Redis采用计算、存储分离架构,扩容时不必搬迁任何数据,因此速度、稳定性都远超社区版Redis。**有大key,高斯Redis可以放心扩容!** ## 3. 高斯Redis删除/过期大key时,业务0阻塞 - 社区版Redis大key的删除/过期都会导致访问严重阻塞。实测删除/过期一个大hash key(包含1000w个元素),社区版Redis访问阻塞长达整整14秒。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654831962213535379.png) 图:社区版Redis在大key删除场景阻塞业务访问 虽然社区版Redis提供了“异步”的unlink命令能够一定程度上缓解大key阻塞问题,但unlink并非严格异步,例如对于zset类型(skiplist编码)以及全部string key都只能阻塞删除,风险不可控。 - 高斯Redis从根本上解决了大key删除/过期操作隐患。在高斯Redis中,对任何数据执行删除/过期,都是立刻执行成功且0阻塞。这是由于底层采用了真正的“标记删除”,因此完全不影响业务访问。实测删除/过期一个大hash key(包含1000w个元素),高斯Redis仅毫秒级。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654831978776157898.png) # 三、总结 根据上述对比评测,可看出相比社区版Redis的实际表现,高斯Redis更适用于大key的可靠存储场景。除了能解决业务大key痛点外,高斯Redis在稳定性、可靠性、安全性等方面也有全面的提升。
  • [交流吐槽] Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?
    原始数据存储在 DB 中(如 MySQL、Hbase 等),但 DB 的读写性能低、延迟高。比如 MySQL 在 4 核 8G 上的 TPS = 5000,QPS = 10000 左右,读写平均耗时 10~100 ms。用 Redis 作为缓存系统正好可以弥补 DB 的不足,「码哥」在自己的 MacBook Pro 2019 上执行 Redis 性能测试如下:$ redis-benchmark -t set,get -n 100000 -qSET: 107758.62 requests per second, p50=0.239 msecGET: 108813.92 requests per second, p50=0.239 msecTPS 和 QPS 达到 10 万,于是乎我们就引入缓存架构,在数据库中存储原始数据,同时在缓存总存储一份。当请求进来的时候,先从缓存中取数据,如果有则直接返回缓存中的数据。如果缓存中没数据,就去数据库中读取数据并写到缓存中,再返回结果。这样就天衣无缝了么?缓存的设计不当,将会导致严重后果,本文将介绍缓存使用中常见的三个问题和解决方案:缓存击穿(失效);缓存穿透;缓存雪崩。缓存击穿(失效)高并发流量,访问的这个数据是热点数据,请求的数据在 DB 中存在,但是 Redis 存的那一份已经过期,后端需要从 DB 从加载数据并写到 Redis。关键字:单一热点数据、高并发、数据失效但是由于高并发,可能会把 DB 压垮,导致服务不可用。如下图所示:解决方案过期时间 + 随机值对于热点数据,我们不设置过期时间,这样就可以把请求都放在缓存中处理,充分把 Redis 高吞吐量性能利用起来。或者过期时间再加一个随机值。设计缓存的过期时间时,使用公式:过期时间=baes 时间+随机时间。即相同业务数据写缓存时,在基础过期时间之上,再加一个随机的过期时间,让数据在未来一段时间内慢慢过期,避免瞬时全部过期,对 DB 造成过大压力预热预先把热门数据提前存入 Redis 中,并设热门数据的过期时间超大值。使用锁当发现缓存失效的时候,不是立即从数据库加载数据。而是先获取分布式锁,获取锁成功才执行数据库查询和写数据到缓存的操作,获取锁失败,则说明当前有线程在执行数据库查询操作,当前线程睡眠一段时间在重试。这样只让一个请求去数据库读取数据。伪代码如下:public Object getData(String id) {    String desc = redis.get(id);        // 缓存为空,过期了        if (desc == null) {            // 互斥锁,只有一个请求可以成功            if (redis(lockName)) {                try                    // 从数据库取出数据                    desc = getFromDB(id);                    // 写到 Redis                    redis.set(id, desc, 60 * 60 * 24);                } catch (Exception ex) {                    LogHelper.error(ex);                } finally {                    // 确保最后删除,释放锁                    redis.del(lockName);                    return desc;                }            } else {                // 否则睡眠200ms,接着获取锁                Thread.sleep(200);                return getData(id);            }        }}缓存穿透缓存穿透:意味着有特殊请求在查询一个不存在的数据,即数据不存在 Redis 也不存在于数据库。导致每次请求都会穿透到数据库,缓存成了摆设,对数据库产生很大压力从而影响正常服务。如图所示:解决方案缓存空值:当请求的数据不存在 Redis 也不存在数据库的时候,设置一个缺省值(比如:None)。当后续再次进行查询则直接返回空值或者缺省值。布隆过滤器:在数据写入数据库的同时将这个 ID 同步到到布隆过滤器中,当请求的 id 不存在布隆过滤器中则说明该请求查询的数据一定没有在数据库中保存,就不要去数据库查询了。BloomFilter 要缓存全量的 key,这就要求全量的 key 数量不大,100 亿 条数据以内最佳,因为 100 亿条数据大概要占用 3.5GB 的内存。说下布隆过滤器的原理吧BloomFilter 的算法是,首先分配一块内存空间做 bit 数组,数组的 bit 位初始值全部设为 0。加入元素时,采用 k 个相互独立的 Hash 函数计算,然后将元素 Hash 映射的 K 个位置全部设置为 1。检测 key 是否存在,仍然用这 k 个 Hash 函数计算出 k 个位置,如果位置全部为 1,则表明 key 存在,否则不存在。如下图所示:哈希函数会出现碰撞,所以布隆过滤器会存在误判。这里的误判率是指,BloomFilter 判断某个 key 存在,但它实际不存在的概率,因为它存的是 key 的 Hash 值,而非 key 的值。所以有概率存在这样的 key,它们内容不同,但多次 Hash 后的 Hash 值都相同。对于 BloomFilter 判断不存在的 key ,则是 100% 不存在的,反证法,如果这个 key 存在,那它每次 Hash 后对应的 Hash 值位置肯定是 1,而不会是 0。布隆过滤器判断存在不一定真的存在。缓存雪崩缓存雪崩指的是大量的请求无法在 Redis 缓存系统中处理,请求全部打到数据库,导致数据库压力激增,甚至宕机。出现该原因主要有两种:大量热点数据同时过期,导致大量请求需要查询数据库并写到缓存;Redis 故障宕机,缓存系统异常。缓存大量数据同时过期数据保存在缓存系统并设置了过期时间,但是由于在同时一刻,大量数据同时过期。系统就把请求全部打到数据库获取数据,并发量大的话就会导致数据库压力激增。缓存雪崩是发生在大量数据同时失效的场景,而缓存击穿(失效)是在某个热点数据失效的场景,这是他们最大的区别。如下图:缓存雪崩-大量缓存同时失效解决方案过期时间添加随机值要避免给大量的数据设置一样的过期时间,过期时间 = baes 时间+ 随机时间(较小的随机数,比如随机增加 1~5 分钟)。这样一来,就不会导致同一时刻热点数据全部失效,同时过期时间差别也不会太大,既保证了相近时间失效,又能满足业务需求。接口限流当访问的不是核心数据的时候,在查询的方法上加上接口限流保护。比如设置 10000 req/s。如果访问的是核心数据接口,缓存不存在允许从数据库中查询并设置到缓存中。这样的话,只有部分请求会发送到数据库,减少了压力。限流,就是指,我们在业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库。如下图所示:缓存雪崩-限流Redis 故障宕机一个 Redis 实例能支撑 10 万的 QPS,而一个数据库实例只有 1000 QPS。一旦 Redis 宕机,会导致大量请求打到数据库,从而发生缓存雪崩。解决方案对于缓存系统故障导致的缓存雪崩的解决方案有两种:服务熔断和接口限流;构建高可用缓存集群系统。服务熔断和限流在业务系统中,针对高并发的使用服务熔断来有损提供服务从而保证系统的可用性。服务熔断就是当从缓存获取数据发现异常,则直接返回错误数据给前端,防止所有流量打到数据库导致宕机。服务熔断和限流属于在发生了缓存雪崩,如何降低雪崩对数据库造成的影响的方案。构建高可用的缓存集群所以,缓存系统一定要构建一套 Redis 高可用集群,比如 《Redis 哨兵集群》或者 《Redis Cluster 集群》,如果 Redis 的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。总结缓存穿透指的是数据库本就没有这个数据,请求直奔数据库,缓存系统形同虚设。缓存击穿(失效)指的是数据库有数据,缓存本应该也有数据,但是缓存过期了,Redis 这层流量防护屏障被击穿了,请求直奔数据库。缓存雪崩指的是大量的热点数据无法在 Redis 缓存中处理(大面积热点数据缓存失效、Redis 宕机),流量全部打到数据库,导致数据库极大压力。
  • [交流吐槽] Redis消息队列发展历程
    作者 | 丕天Redis是目前最受欢迎的kv类数据库,当然它的功能越来越多,早已不限定在kv场景,消息队列就是Redis中一个重要的功能。Redis从2010年发布1.0版本就具备一个消息队列的雏形,随着10多年的迭代,其消息队列的功能也越来越完善,作为一个全内存的消息队列,适合应用与要求高吞吐、低延时的场景。我们来盘一下Redis消息队列功能的发展历程,历史版本有哪些不足,后续版本是如何来解决这些问题的。一、Redis 1.0 list从广义上来讲消息队列就是一个队列的数据结构,生产者从队列一端放入消息,消费者从另一端读取消息,消息保证先入先出的顺序,一个本地的list数据结构就是一个进程维度的消息队列,它可以让模块A写入消息,模块B消费消息,做到模块A/B的解耦与异步化。但想要做到应用级别的解耦和异步还需要一个消息队列的服务。1.list的特性Redis 1.0发布时就具备了list数据结构,应用A可以通过lpush写入消息,应用B通过rpop从队列中读取消息,每个消息只会被读取一次,而且是按照lpush写入的顺序读到。同时Redis的接口是并发安全的,可以同时有多个生产者向一个list中生产消息,多个消费者从list中读取消息。这里还有个问题,消费者要如何知道list中有消息了,需要不断轮询去查询吗。轮询无法保证消息被及时的处理,会增加延时,而且当list为空时,大部分轮询的请求都是无效请求,这种方式大量浪费了系统资源。好在Redis有brpop接口,该接口有一个参数是超时时间,如果list为空,那么Redis服务端不会立刻返回结果,它会等待list中有新数据后在返回或是等待最多一个超时时间后返回空。通过brpop接口实现了长轮询,该效果等同于服务端推送,消费者能立刻感知到新的消息,而且通过设置合理的超时时间,使系统资源的消耗降到很低。#基于list完成消息的生产和消费 #生产者生产消息msg1 lpush listA msg1 (integer) 1 #消费者读取到消息msg1 rpop listA "msg1" #消费者阻塞式读取listA,如果有数据立刻返回,否则最多等待10秒 brpop listA 10 1) "listA" 2) "msg1"使用rpop或brpop这样接口消费消息会先从队列中删除消息,然后再由应用消费,如果应用应用在处理消息前异常宕机了,消息就丢失了。但如果使用lindex这样的只读命令先读取消息处理完毕后在删除,又需要额外的机制来保证一条消息不会被其他消费者重复读到。好在list有rpoplpush或brpoplpush这样的接口,可以原子性的从一个list中移除一个消息并加入另一个list。应用程序可以通过2个list组和来完成消息的消费和确认功能,使用rpoplpush从list A中消费消息并移入list B,等消息处理完毕后在从list B中删除消息,如果在处理消息过程中应用异常宕机,恢复后应用可以重新从list B中读取未处理的消息并处理。这种方式为消息的消费增加了ack机制。#基于2个list完成消息消费和确认 #从listA中读取消息并写入listB rpoplpush listA listB "msg1" #业务逻辑处理msg1完毕后,从listB中删除msg1,完成消息的确认 lrem listB 1 msg1 (integer) 12.list的不足之处通过Redis 1.0就引入的list结构我们就能实现一个分布式的消息队列,满足一些简单的业务需求。但list结构作为消息队列服务有一个很致命的问题,它没有广播功能,一个消息只能被消费一次。而在大型系统中,通常一个消息会被下游多个应用同时订阅和消费,例如当用户完成一个订单的支付操作时,需要通知商家发货,要更新物流状态,可能还会提高用户的积分和等级,这些都是不同的下游子系统,他们全部会订阅支付完成的操作,而list一个消息只能被消费一次在这样复杂的大型系统面前就捉襟见肘了。可能你会说那弄多个list,生产者向每个list中都投递消息,每个消费者处理自己的list不就行了吗。这样第一是性能不会太好,因为同一个消息需要被重复的投递,第二是这样的设计违反了生产者和消费者解耦的原则,这个设计下生产者需要知道下游有哪些消费者,如果业务发生变化,需要额外增加一个消费者,生产者的代码也需要修改。3.总结优势模型简单,和使用本地list基本相同,适配容易通过brpop做到消息处理的实时性通过rpoplpush来联动2个list,可以做到消息先消费后确认,避免消费者应用异常情况下消息丢失不足消息只能被消费一次,缺乏广播机制二、Redis 2.0 pubsublist作为消息队列应用场景受到限制很重要的原因在于没有广播,所以Redis 2.0中引入了一个新的数据结构pubsub。pubsub虽然不能算作是list的替代品,但它确实能解决一些list不能解决的问题。1.pubsub特性pubsub引入一个概念叫channel,生产者通过publish接口投递消息时会指定channel,消费者通过subscribe接口订阅它关心的channel,调用subscribe后这条连接会进入一个特殊的状态,通常不能在发送其他请求,当有消息投递到这个channel时Redis服务端会立刻通过该连接将消息推送到消费者。这里一个channel可以被多个应用订阅,消息会同时投递到每个订阅者,做到了消息的广播。另一方面,消费者可以会订阅一批channel,例如一个用户订阅了浙江的新闻的推送,但浙江新闻还会进行细分,例如“浙江杭州xx”、“浙江温州xx”,这里订阅者不需要获取浙江的所有子类在挨个订阅,只需要调用psubscribe“浙江*”就能订阅所有以浙江开头的新闻推送了,这里psubscribe传入一个通配符表达的channel,Redis服务端按照规则推送所有匹配channel的消息给对应的客户端。#基于pubsub完成channel的匹配和消息的广播 #消费者1订阅channel1 subscribe channel1 1) "subscribe" 2) "channel1" 3) (integer) 1 #收到消息推送 1) "message" 2) "channel1" 3) "msg1" #消费者2订阅channel* psubscribe channel* 1) "psubscribe" 2) "channel*" 3) (integer) 1 #收到消息推送 1) "pmessage" 2) "channel*" 3) "channel1" 4) "msg1" 1) "pmessage" 2) "channel*" 3) "channel2" 4) "msg2" #生产者发布消息msg1和msg2 publish channel1 msg1 (integer) 2 publish channel2 msg2 (integer) 1在Redfis 2.8时加入了keyspace notifications功能,此时pubsub除了通知用户自定义消息,也可以通知系统内部消息。keyspace notifications引入了2个特殊的channel分别是__keyevent@__:和__keyspace@__:,通过订阅__keyevent客户端可以收到某个具体命令调用的回调通知,通过订阅__keyspace客户端可以收到目标key的增删改操作以及过期事件。使用这个功能还需要开启配置notify-keyspace-events。#通过keyspace notifications功能获取系统事件 #写入请求 set testkey v EX 1 #订阅key级别的事件 psubscribe __keyspace@0__:testkey 1) "psubscribe" 2) "__keyspace@0__:testkey" 3) (integer) 1 #收到通知 1) "pmessage" 2) "__keyspace@0__:testkey" 3) "__keyspace@0__:testkey" 4) "set" 1) "pmessage" 2) "__keyspace@0__:testkey" 3) "__keyspace@0__:testkey" 4) "expire" 1) "pmessage" 2) "__keyspace@0__:testkey" 3) "__keyspace@0__:testkey" 4) "expired" #订阅所有的命令事件 psubscribe __keyevent@0__:* 1) "psubscribe" 2) "__keyevent@0__:*" 3) (integer) 1 #收到通知 1) "pmessage" 2) "__keyevent@0__:*" 3) "__keyevent@0__:set" 4) "testkey" 1) "pmessage" 2) "__keyevent@0__:*" 3) "__keyevent@0__:expire" 4) "testkey" 1) "pmessage" 2) "__keyevent@0__:*" 3) "__keyevent@0__:expired" 4) "testkey"2.pubsub的不足之处pubsub既能单播又能广播,还支持channel的简单正则匹配,功能上已经能满足大部分业务的需求,而且这个接口发布的时间很早,在2011年Redis 2.0发布时就已经具备,用户基础很广泛,所以现在很多业务都有用到这个功能。但你要深入了解pubsub的原理后,是肯定不敢把它作为一个一致性要求较高,数据量较大系统的消息服务的。首先,pubsub的消息数据是瞬时的,它在Redis服务端不做保存,publish发送到Redis的消息会立刻推送到所有当时subscribe连接的客户端,如果当时客户端因为网络问题断连,那么就会错过这条消息,当客户端重连后,它没法重新获取之前那条消息,甚至无法判断是否有消息丢失。其次,pubsub中消费者获取消息是一个推送模型,这意味着Redis会按消息生产的速度给所有的消费者推送消息,不管消费者处理能力如何,如果消费者应用处理能力不足,消息就会在Redis的client buf中堆积,当堆积数据超过一个阈值后会断开这条连接,这意味着这些消息全部丢失了,在也找不回来了。如果同时有多个消费者的client buf堆积数据但又还没达到断开连接的阈值,那么Redis服务端的内存会膨胀,进程可能因为oom而被杀掉,这导致了整个服务中断。3.总结优势消息具备广播能力psubscribe能按字符串通配符匹配,给予了业务逻辑的灵活性能订阅特定key或特定命令的系统消息不足Redis异常、客户端断连都会导致消息丢失消息缺乏堆积能力,不能削峰填谷。推送的方式缺乏背压机制,没有考虑消费者处理能力,推送的消息超过消费者处理能力后可能导致消息丢失或服务异常三、Redis 5.0 stream消息丢失、消息服务不稳定的问题严重限制了pubsub的应用场景,所以Redis需要重新设计一套机制,来解决这些问题,这就有了后来的stream结构。1.stream特性一个稳定的消息服务需要具备几个要点,要保证消息不会丢失,至少被消费一次,要具备削峰填谷的能力,来匹配生产者和消费者吞吐的差异。在2018年Redis 5.0加入了stream结构,这次考虑了list、pubsub在应用场景下的缺陷,对标kafka的模型重新设计全内存消息队列结构,从这时开始Redis消息队列功能算是能和主流消息队列产品pk一把了。stream的改进分为多个方面成本:存储message数据使用了listpack结构,这是一个紧凑型的数据结构,不同于list的双向链表每个节点都要额外占用2个指针的存储空间,这使得小msg情况下stream的空间利用率更高。功能:stream引入了消费者组的概念,一个消费者组内可以有多个消费者,同一个组内的消费者共享一个消息位点(last_delivered_id),这使得消费者能够水平的扩容,可以在一个组内加入多个消费者来线性的提升吞吐,对于一个消费者组,每条msg只会被其中一个消费者获取和处理,这是pubsub的广播模型不具备的。不同消费者组之前是相互隔离的,他们各自维护自己的位点,这使得一条msg能被多个不同的消费者组重复消费,做到了消息广播的能力。stream中消费者采用拉取的方式,并能设置timeout在没有消息时阻塞,通过这种长轮询机制保证了消息的实时性,而且消费速率是和消费者自身吞吐相匹配。消息不丢失:stream的数据会存储在aof和rdb文件中,这使Redis重启后能够恢复stream的数据。而pubsub的数据是瞬时的,Redis重启意味着消息全部丢失。stream中每个消费者组会存储一个last_delivered_id来标识已经读取到的位点,客户端连接断开后重连还是能从该位点继续读取,消息不会丢失。stream引入了ack机制保证消息至少被处理一次。考虑一种场景,如果消费者应用已经读取了消息,但还没来得及处理应用就宕机了,对于这种已经读取但没有ack的消息,stream会标示这条消息的状态为pending,等客户端重连后通过xpending命令可以重新读取到pengind状态的消息,继续处理。如果这个应用永久宕机了,那么该消费者组内的其他消费者应用也能读取到这条消息,并通过xclaim命令将它归属到自己下面继续处理。#基于stream完成消息的生产和消费,并确保异常状态下消息至少被消费一次 #创建mystream,并且创建一个consumergroup为mygroup XGROUP CREATE mystream mygroup $ MKSTREAM OK #写入一条消息,由redis自动生成消息id,消息的内容是一个kv数组,这里包含field1 value1 field2 value2 XADD mystream * field1 value1 field2 value2 "1645517760385-0" #消费者组mygroup中的消费者consumer1从mystream读取一条消息,>表示读取一条该消费者组从未读取过的消息 XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream > 1) 1) "mystream" 2) 1) 1) "1645517760385-0" 2) 1) "field1" 2) "value1" 3) "field2" 4) "value2" #消费完成后ack确认消息 xack mystream mygroup 1645517760385-0 (integer) 1 #如果消费者应用在ack前异常宕机,恢复后重新获取未处理的消息id。 XPENDING mystream mygroup - + 10 1) 1) "1645517760385-0" 2) "consumer1" 3) (integer) 305356 4) (integer) 1 #如果consumer1永远宕机,其他消费者可以把pending状态的消息移动到自己名下后继续消费 #将消息id 1645517760385-0移动到consumer2下 XCLAIM mystream mygroup consumer2 0 1645517760385-0 1) 1) "1645517760385-0" 2) 1) "field1" 2) "value1" 3) "field2" 4) "value2"Redis stream保证了消息至少被处理一次,但如果想做到每条消息仅被处理一次还需要应用逻辑的介入。消息被重复处理要么是生产者重复投递,要么是消费者重复消费。对于生产者重复投递问题,Redis stream为每个消息都设置了一个唯一递增的id,通过参数可以让Redis自动生成id或者应用自己指定id,应用可以根据业务逻辑为每个msg生成id,当xadd超时后应用并不能确定消息是否投递成功,可以通过xread查询该id的消息是否存在,存在就说明已经投递成功,不存在则重新投递,而且stream限制了id必须递增,这意味了已经存在的消息重复投递会被拒绝。这套机制保证了每个消息可以仅被投递一次。对于消费者重复消费的问题,考虑一个场景,消费者读取消息后业务处理完毕,但还没来得及ack就发生了异常,应用恢复后对于这条没有ack的消息进行了重复消费。这个问题因为ack和消费消息的业务逻辑发生在2个系统,没法做到事务性,需要业务来改造,保证消息处理的幂等性。2.stream的不足stream的模型做到了消息的高效分发,而且保证了消息至少被处理一次,通过应用逻辑的改造能做到消息仅被处理一次,它的能力对标kafka,但吞吐高于kafka,在高吞吐场景下成本比kafka低,那它又有哪些不足了。首先消息队列很重要的一个功能就是削峰填谷,来匹配生产者和消费者吞吐的差异,生产者和消费者吞吐差异越大,持续时间越长,就意味着steam中需要堆积更多的消息,而Redis作为一个全内存的产品,数据堆积的成本比磁盘高。其次stream通过ack机制保证了消息至少被消费一次,但这有个前提就是存储在Redis中的消息本身不会丢失。Redis数据的持久化依赖aof和rdb文件,aof落盘方式有几种,通过配置appendfsync决定,通常我们不会配置为always来让每条命令执行完后都做一次fsync,线上配置一般为everysec,每秒做一次fsync,而rdb是全量备份时生成,这意味了宕机恢复可能会丢掉最近一秒的数据。另一方面线上生产环境的Redis都是高可用架构,当主节点宕机后通常不会走恢复逻辑,而是直接切换到备节点继续提供服务,而Redis的同步方式是异步同步,这意味着主节点上新写入的数据可能还没同步到备节点,在切换后这部分数据就丢失了。所以在故障恢复中Redis中的数据可能会丢失一部分,在这样的背景下无论stream的接口设计的多么完善,都不能保证消息至少被消费一次。3.总结优势在成本、功能上做了很多改进,支持了紧凑的存储小消息、具备广播能力、消费者能水平扩容、具备背压机制通过ack机制保证了Redis服务端正常情况下消息至少被处理一次的能力不足内存型消息队列,数据堆积成本高Redis本身rpo>0,故障恢复可能会丢数据,所以stream在Redis发生故障恢复后也不能保证消息至少被消费一次。四、Tair持久内存版 streamRedis stream的不足也是内存型数据库特性带来的,它拥有高吞吐、低延时,但大容量下成本会比较高,而应用的场景也不完全是绝对的大容量低吞吐或小容量高吞吐,有时应用的场景会介于二者之间,需要平衡容量和吞吐的关系,所以需要一个产品它的存储成本低于Redis stream,但它的性能又高于磁盘型消息队列。另一方面Redis stream在Redis故障场景下不能保证消息的不丢失,这导致业务需要自己实现一些复杂的机制来回补这段数据,同时也限制了它应用在一些对一致性要求较高的场景。为了让业务逻辑更简单,stream应用范围更广,需要保证故障场景下的消息持久化。兼顾成本、性能、持久化,这就有了Tair持久内存版。1.Tair持久内存版特性更大空间,更低成本Tair持久内存版引入了Intel傲腾持久内存(下面称作AEP),它的性能略低于内存,但相同容量下成本低于内存。Tair持久内存版将主要数据存储在AEP上,使得相同容量下,成本更低,这使同样单价下stream能堆积更多的消息。兼容社区版Tair持久内存版兼容原生Redis绝大部分的数据结构和接口,对于stream相关接口做到了100%兼容,如果你之前使用了社区版stream,那么不需要修改任何代码,只需要换一个连接地址就能切换到持久内存版。并且通过工具完成社区版和持久内存版数据的双向迁移。数据的实时持久化Tair持久内存版并不是简单将Redis中的数据换了一个介质存储,因为这样仅能通过AEP降低成本,但没用到AEP断电数据不丢失的特性,对持久化能力没有任何提升。开源Redis通过在磁盘上记录AppendOnlyLog来持久化数据,AppendOnlyLog记录了所有的写操作,相当于redolog,在宕机恢复时通过回放这些log恢复数据。但受限于磁盘介质的高延时和Redis内存数据库使用场景下对低延时的要求,并不能在每次写操作后fsync持久化log,最新写入的数据可能并没有持久化到磁盘,这也是数据可能丢失的根因。Tair持久内存版的数据恢复没有使用AppendOnlyLog来完成, 而是将将redis数据结构存储在AEP上,这样宕机后这些数据结构并不会丢失,并且对这些数据结构增加了一些额外的描述信息,宕机后在recovery时能够读到这些额外的描述信息,让这些redis数据结构重新被识别和索引,将状态恢复到宕机前的样子。Tair通过将redis数据结构和描述信息实时写入AEP,保证了写入数据的实时持久化。HA数据不丢失Tair持久内存版保证了数据的持久化,但生产环境中都是高可用架构,多数情况下当主节点异常宕机后并不会等主节点重启恢复,而是切换到备节点继续提供服务,然后给新的主节点添加一个新的备节点。所以在故障发生时如果有数据还没从主节点同步到备节点,这部分数据就会丢失。Redis采用的异步同步,当客户端写入数据并返回成功时对Redis的修改可能还没同步到备节点,如果此时主节点宕机数据就会丢失。为了避免在HA过程中数据丢失,Tair持久内存版引入了半同步机制,确保写入请求返回成功前相关的修改已经同步到备节点。可以发现开启半同步功能后写入请求的RT会变高,多出主备同步的耗时,这部分耗时大概在几十微秒。但通过一些异步化的技术,虽然写请求的RT会变高,但对实例的最大写吞吐影响很小。当开启半同步后生成者通过xadd投递消息,如果返回成功,消息一定同步到备节点,此时发生HA,消费者也能在备节点上读到这条消息。如果xadd请求超时,此时消息可能同步到备节点也可能没有,生产者没法确定,此时通过再次投递消息,可以保证该消息至少被消费一次。如果要严格保证消息仅被消费一次,那么生产者可以通过xread接口查询消息是否存在,对于不存在的场景重新投递。2.总结优势引入了AEP作为存储介质,目前Tair持久内存版价格是社区版的70%。保证了数据的实时持久化,并且通过半同步技术保证了HA不丢数据,大多数情况下做到消息不丢失(备库故障或主备网络异常时会降级为异步同步,优先保障可用性),消息至少被消费一次或仅被消费一次。五、未来消息队列主要是为了解决3类问题,应用模块的解耦、消息的异步化、削峰填谷。目前主流的消息队列都能满足这些需求,所以在实际选型时还会考虑一些特殊的功能是否满足,产品的性能如何,具体业务场景下的成本怎么样,开发的复杂度等。Redis的消息队列功能并不是最全面的,它不希望做成一个大而全的产品,而是做一个小而美的产品,服务好一部分用户在某些场景下的需求。目前用户选型Redis作为消息队列服务的原因,主要有Redis在相同成本下吞吐更高、Redis的延时更低、应用需要一个消息服务但又不想额外引入一堆依赖等。未来Tair持久内存版会针对这些述求,把这些优势继续放大。吞吐通过优化持久内存版的持久化流程,让吞吐接近内存版甚至超过内存版吞吐。延时通过rdma在多副本间同步数据,降低半同步下写入数据的延时。
  • [交流吐槽] MySQL 与 Redis 的区别
    MySQL 是持久化存储,存放在磁盘里面,检索的话,会涉及到一定的 IO,为了解决这个瓶颈,于是出现了缓存,比如现在用的最多的 memcached(简称mc)。首先,用户访问mc,如果未命中,就去访问 MySQL,之后像内存和硬盘一样,把数据复制到mc一部分。  Redis 和mc都是缓存,并且都是驻留在内存中运行的,这大大提升了高数据量web访问的访问速度。然而mc只是提供了简单的数据结构,比如 string存储;Redis却提供了大量的数据结构,比如string、list、set、hashset、sorted set这些,这使得用户方便了好多,毕竟封装了一层实用的功能,同时实现了同样的效果,当然用Redis而慢慢舍弃mc。  内存和硬盘的关系,硬盘放置主体数据用于持久化存储,而内存则是当前运行的那部分数据,CPU访问内存而不是磁盘,这大大提升了运行的速度,当然这是基于程序的局部化访问原理。  推理到 Redis + MySQL,它是内存+磁盘关系的一个映射,MySQL 放在磁盘,Redis放在内存,这样的话,web应用每次只访问Redis,如果没有找到的数据,才去访问 MySQL。  然而 Redis + MySQL 和内存+磁盘的用法最好是不同的。前者是内存数据库,数据保存在内存中,当然速度快。后者是关系型数据库,功能强大,数据访问也就慢。像memcache,MongoDB,Redis,都属于No SQL系列。不是一个类型的东西,应用场景也不太一样,还是要看你的需求来决定。