Redis 5.x——多种数据类型使用场景

📅 2026/7/9 0:28:01
Redis 5.x——多种数据类型使用场景
多种数据类型使用场景1、String数据类型的API和使用场景1.1、String数据类型的常规操作1.1.1、设置与获取键的内容1.1.2、获取子字符串1.1.3、 返回旧值1.2、结合应用场景演练1.2.1、单个值缓存1.2.2、对象缓存1.2.3、计数器1.2.4、唯一自增的ID或者流水号1.2.5、文章阅读数或者网页浏览数统计1.2.6、分布式锁2、String也能做为什么还要用Hash2.1、Hash数据类型的常规操作2.2、结合应用场景演练2.2.1、作为实时数据库2.2.2、使用Hash数据类型实现购物车2.2.3、作为计数器3、一种类型多种用法原来List还可以这样做3.1、List模拟数据结构3.1.1、先进后出操作3.1.2、先进先出操作3.1.3、阻塞队列3.1.4、模拟消息推送3.2、List常规操作3.3、利用列表迅速提升网站首页的并发量4、开启Set数据类型的正确操作姿势4.1、明星绯闻和微博瘫痪的那些事4.2、抽奖逻辑4.3、文章点赞或者投票4.4、共同好友统计5、微服务的限流操作和Zset的那些瓜葛5.1、利用Zset实现限流5.1.1、滑动窗口限流方式5.1.2、令牌桶5.2、新闻排行榜场景实战5.3、直播打赏排名场景实战1、String数据类型的API和使用场景1.1、String数据类型的常规操作1.1.1、设置与获取键的内容#为键设置指定的内容setname zhangsan#获取键的内容get name运行效果如图所示。1.1.2、获取子字符串#设置长字符串setnamemy name is zhangsan#获取长字符串的子字符串下标从0开始getrange name37运行效果如图所示。1.1.3、 返回旧值#为键设置新的内容并返回键的旧内容getset name zhangsan运行效果如图所示。1.2、结合应用场景演练1.2.1、单个值缓存顾名思义单个值的缓存就是单个字符串的存储。在分布式项目部署的业务中需要一个统一的鉴权和授权中心将相对应的算法生成token而token本身就是单个字符串这样完全可以利用Redis中的String数据类型来实现。图所示的是一个比较简单的分布式架构中的部分环节网关服务提供的鉴权中心需要判断当前访问的客户端所提供的token是否合法所以当客户端在第一次请求服务时会带着一些信息然后由服务根据信息生成一个token这其中存储的就是经过压缩后的客户端信息和这个信息的过期时间。因为网关服务本身就是分布式部署所以需要一个分布式的缓存服务器而Redis刚好可以满足这个要求。第一次请求的服务节点生成一个用户token并存储到Redis服务器然后可以根据需要设置对应的过期时间从而保证token过期之后的信息从Redis服务器移除。如果有需要也可以不设置token在内存中存储的过期时间。之后请求的节点只需要通过get命令从缓存中获取token验证合法性如图所示。1.2.2、对象缓存用户信息既可能是一个简单的字符串也可能是一个对象或者高频率访问但是很少修改的数据这些数据可以作为一个整体对象如果想要减轻数据库访问的压力那么可以将其对象进行json序列化后缓存起来。这样的数据可以采用String数据类型进行存储。针对这种需求有两种不同命令的操作方式。下图所示是将对象一次性转换成json序列化进行存储。下图所示的是利用mset、mget同时设置多个键、获取多个键的值。如果我们存储的对象会用到的只是获取某个属性的值则可以这样去操作。如果我们存储的对象将作为整个对象操作则不建议这样使用因为需要把多个键的值序列化这是需要自己去实现的没有现成的序列化工具。1.2.3、计数器在高并发的业务场景中可能会遇到各种各样的业务场景比如接口限流。在双十一或者其他大型节日中业务服务器的资源有限但是在高并发的场景下过多的请求可能会导致服务器宕机等问题所以会对接口请求做一些限制比如限制每秒请求总数为200次超过200次就等待等下一秒再次请求这里用Redis作为计数器的模式来实现。实现流程如下1)首先在接到请求之后设置一个键然后自增1如果不存在则值会被初始化为0。incr mykey#第一次执行返回的结果是1这里的mykey可能需要特殊处理因为是根据秒来的所以当接口得到一个请求之后应该获取当前时间生成动态的键如20200101235959:mykey。2)当接口得到请求后执行incryyyyMMddHHmmss:mykey如果返回结果小于200则进行处理超过200将等待下一秒处理。3)因为每秒会生成一个键如下图​为了节省内存空间可能需要一个定时任务定时删除这些已经使用过的键。1.2.4、唯一自增的ID或者流水号在有些业务中为了提高数据库的读写能力可能会将一个库根据业务拆分成多个小库、将一张大表拆分成几个小表比如user01表、user02表但是又要保证两张表中的ID是全局统一自增的所以数据库表自带的自增属性是无法使用的。这时也可以利用Redis的String数据类型实现自增在新增数据行时通过调用Redis服务自增功能生成一个自增的数值来实现多张表的唯一自增。如果数据库表过多会不会造成Redis的压力过大这个不必担心Redis单机的性能还是很不错的如果读者真的想扩展Redis的服务器也是可以的不过这里的实现方式可能需要改变了。假设目前有5台单独的Redis服务器那么生成的方式就是这样的第1台的生成方式如图所示。第1台服务器从初始值1开始生成的序列号第2台的生成方式如图所示。第2台服务器从2开始生成的序列号第3台的生成方式如图所示。第3台服务器从3开始生成的序列号第4台的生成方式如图所示。第4台服务器从4开始生成的序列号第5台的生成方式如图所示。第5台服务器从5开始生成的序列号每一台服务器从初始值15开始然后每次累加数值5则多个节点生成自增且不重复的序列号。以上只是利用Redis的自增API处理多个表的唯一自增ID而额外提供的一种方案。如果了解雪花算法或其他更简易的方式则完全没有必要使用Redis的这种操作。1.2.5、文章阅读数或者网页浏览数统计当我们打开浏览器时会面临各种各样的网页信息比如技术博客或者新闻博客等这些网页里面无不充斥着各种统计数量比如用户的总点赞数、关注数、粉丝数、帖子的评论数、热度、文章的阅读数和收藏数等。实现这些需求并想要减轻数据库压力而且对数据的实效性和写文章的频率要求高时则完全可以使用Redis。实现文章阅读数量统计需要执行如下命令#文章第一次被阅读Incr mypageurl#查看文章的阅读数量get mypageurl#访问时每次都自增1Incr mypageurl1.2.6、分布式锁随着互联网业务发展和需求的不断变化应用程序从单体架构一路进化到当下的分布式和微服务架构。在使用单体架构时多线程操作是通过线程锁来实现资源抢占控制的但是在分布式系统中需要用其他手段来实现可以借助于Redis中的setnx命令来完成。具体操作流程如下如果键没有值就能新增成功就表示抢到锁了否则失败。等到操作成功时释放锁也就是删除键。下面是一段秒杀商品的伪代码# 给键seckill001赋值如果之前不存在则新增成功返回1表示抢到了锁setnx seckill001true#当其他客户端进来之后执行如下命令返回0则表示锁被占用只能后期继续尝试再次执行setnx seckill001true#当第一个抢到锁的线程执行完业务之后便可以删除键让其他线程能抢到锁del seckill001#其他线程执行如下命令时返回的结果是1表示拿到了锁setnx seckill001true其中还存在一个问题如果第一个线程拿到锁之后还没有执行删除键这个线程就宕机了就会导致这个锁永久被占用其他线程的业务将无法执行。对于这个问题可以通过给键设置过期时间来解决到达过期时间后让Redis服务自动把这个键删除——释放。#抢到锁setnx seckill001true#设置过期时间为20毫秒到期之后则键会被删除掉expire seckill00120#查询键还有多久过期如果超时则返回负数如果键被释放掉则返回nilttl seckill001执行结果如图所示。赋值和设置过期时间是分为两步的。如果要保证原子性则可以在设置值的同时再次设置过期时间效果如图所示。setseckill001trueex20nx2、String也能做为什么还要用Hash哈希(Hash)在很多编程语言中都有着广泛的应用在Redis中也是如此。在Redis中哈希数据类型是指Redis键值对中的值本身又是一个键值对(Key-Value Pair)结构如图所示。既然使用String可以实现相同的功能为何还要使用Hash呢在Redis官网中会看到优先使用Hash的字眼主要是基于以下三个因素内存占用率、时间复杂度和使用的简便性。下面就来了解一下其中的缘由。假如我们想要存储的一些用户信息如表所示。使用Redis的String和Hash来存储与处理如上信息的优缺点对比如下1)利用String存储用户信息的命令如下setuserid:1:name zhangsansetuserid:1:age18优点简单直观每个属性都支持更新的操作。缺点占用更多的键内存占用量较大同时用户信息分散一般不会在实际的生产环境中使用。2)利用String来序列化字符串后的命令如下setuser:1 serialize(userInfo)优点简化编程合理使用序列化可以提高内存的使用效率。缺点序列化和反序列化有一定的开销同时每次更新属性都需要把全部数据取出进行反序列化更新后再序列化到Redis中操作比较麻烦。3)利用Hash存储用户信息的命令如下hmset user:1 name clay age18优点简单直观使用合理可以减少内存空间的使用。缺点要控制Hash在ZipList和HashTable两种内部编码之间的转换HashTable会消耗更多内存在后续的数据类型原理章节会对此做更进一步的分析。2.1、Hash数据类型的常规操作如下是Hash数据类型基本的操作命令// 给myhash 键设置nameclay的键值对192.168.3.202:1HSET myhash nameclay(integer)1// 获取myhash键中键值对name的值192.168.3.202:1HGET myhash nameclay2.2、结合应用场景演练2.2.1、作为实时数据库根据Hash特性可以把Redis作为一个实时数据库来处理。比如在硬件相关的系统中通常关于硬件的相关参数配置或者一些设备的实时状态都会放在内存中但是参数过多时维护非常麻烦而且稍微改动一个参数就会重新发布程序。在有些场景下也采用SQLite来存储这些参数但是对于高效访问和修改比如多个主机中的每个主机具有不同的属性每个属性又有对应的值Redis是相对比较好的选择。如下是记录设备的实时状态的命令// 设置设备001的当前异常码为1hset device:001code1// 设置设备001的温度为10hset device:001temperature10// 设置设备的两种状态hmset device:001s1starts2 stop// 获取设备001的所有属性hgetall device:001执行结果如图所示。下图所示的是设备状态界面化管理工具的展示效果。2.2.2、使用Hash数据类型实现购物车很多电商网站一般都会使用Cookie来实现购物车也就是将整个购物车信息都存入Cookie中。这种做法的一大好处就是无须对数据库进行写入就可以实现购物车功能而且大大提高了购物车的性能缺点是程序需要重新解析和验证Cookie还要保证Cookie的格式正确并且包含的商品都是可购买的商品。用Cookie实现购物车的缺点是因为浏览器每次发送请求都会连同Cookie一起发送所以如果购物车Cookie的体积比较大那么请求发送和处理的速度可能会有所降低对性能有所影响。购物车的定义非常简单以每个用户的userid作为Redis的键(Key)每个用户的购物车都是一个哈希表它存储了商品ID与商品订购数量之间的映射关系。在商品的订购数量出现变化时操作Redis哈希对购物车进行更新。如果用户订购某件商品的数量大于0那么程序会将这件商品的ID以及用户订购该商品的数量添加到Hash中。购物车操作命令如下执行结果如图所示。// 用户1 商品1 数量1 hset userid:1 pid:11// 返回值0代表该field在哈希表中不存在为新增的键如果用户购买的商品已经存在于哈希表中那么新的订购数量会覆盖已有的订购数量。如下是操作命令执行结果如图所示。// 用户1 商品1 hset userid:1 pid:15// 如果用户订购某件商品的数量不大于0那么程序将从哈希表中删除该项 // 用户1 商品1 hdel userid:1 pid:12.2.3、作为计数器在学习Redis中的String数据类型时知道了String可以用作计数器实际上Redis的Hash作为计数器的使用也非常广泛它常被用于记录网站一天、一月、一年的访问数量。每次访问在对应的field上自增1即可。如下是记录博客文章每月访问量的命令执行结果如图所示。// 记录博客文章每月的访问量 hincrby myBlog2021021hincrby myBlog2021021hincrby myBlog2021021hincrby myBlog2021021如下是记录商品的好评数量、差评数量的命令执行结果如图所示。// 记录商品的好评数量、差评数量 hincrby pid:1 Good1hincrby pid:1 Good1hincrby pid:1 bad1如下是记录网站实时在线人数的命令执行结果如图所示。// 记录实时在线人数 // 登录 hincrby mySite202102011hincrby mySite202102011// 退出 hincrby mySite20210201-13、一种类型多种用法原来List还可以这样做从常规角度来看列表(List)只是一系列有序元素比如(1, 2, 3, 4, 5)。不过使用Array实现的List属性与使用链表(Linked List)实现的List属性非常不同。Redis的List数据类型是通过链表实现的。这意味着即使用户在列表中有数百万个元素在列表的开头或结尾添加新元素的操作也会在固定时间内执行。使用LPUSH命令将新元素添加到具有100个元素的列表开头的速度与将元素添加到具有1000万个元素的列表开头的速度相同而且一个列表最多可以包含232-14294967295每个列表超过40亿个元素个元素。有优点必然也会存在缺点那它有什么缺点呢在使用Array实现的列表中按索引访问元素的速度非常快而在通过链表实现的列表中访问速度却不是那么快其中操作需要的工作量与所访问元素的索引大小成比例​具体可以看数据结构原理章节来了解。如前所述Redis的List数据类型是通过链表实现的因为对于数据库系统而言至关重要的是能够以非常快的方式将元素添加到很长的列表中。我们可以用两种不同的方式来操作列表第一种是队列按照先进先出的顺序操作第二种是栈按照先进后出的顺序操作。接下来我们来了解一下这两种不同的操作方式。3.1、List模拟数据结构List数据类型有以下特点List列表中的元素是有序的可以通过下标或称为索引来获取某个元素或者某个范围内的元素列表。List中的元素可以是重复的。可以实现顺序排队和插队的操作。下图为利用List数据类型的数据结构来存储数据的结构图。3.1.1、先进后出操作用Redis的List数据类型模拟栈的数据结构实现先进后出的操作执行命令如下// 向栈的左侧插入数据a lpush mystack a // 向栈的左侧插入数据b lpush mystack b // 向栈的左侧同时插入多个数据dd ee ff lpush mystackddee ff // 获取栈中从下标0开始到2结束的数据 lrange mystack02// 获取栈中的所有元素 lrange mystack0-1// 从栈中取出一个值就是从栈中弹出按照后进先取出的顺序 lpop mystack执行结果如图所示。3.1.2、先进先出操作用Redis的List数据类型模拟队列的数据结构来实现先进先出的操作执行命令如下// 模拟队列先进先出 // 向队列中插入第一个元素 lpush myqueue a // 向队列中插入第二个元素 lpush myqueue b // 向队列中插入多个元素 lpush myqueue ccddee // 获取队列中从下标0开始到2结束的数据 lrange mystack02// 查看队列中的所有元素 lrange myqueue0-1// 从队列右边取出第一个元素 rpop myqueue // 查看队列中的所有元素 lrange myqueue0-13.1.3、阻塞队列Redis中的列表还具有一项特殊功能使其适用于实现队列通常用作进程间通信系统的构建模块阻止操作。比如用户想通过一个流程将数据项推入或压入列表然后使用不同的流程来对这些数据项进行某种加工或处理。这是通常的生产者/消费者模式可以通过以下的简单方式来实现将数据项推入列表生产者调用lpush命令。从列表中提取数据项消费者调用rpop。有时列表可能为空没有任何要处理的数据项此时rpop就返回null。在这种情况下消费者被迫等待一段时间然后使用rpop重试这称为轮询。不过这样会有一个缺点强制Redis和客户端处理无用的命令列表为空时的所有请求将不执行任何实际的操作只会返回null​。因为消费处理端在收到null之后会等待一段时间所以会增加数据项处理的延迟。为了减小延迟我们可以在两次调用rpop之间等待更少的时间即对Redis的调用变得越来越无用。按照先进先出的顺序如果列表没有元素就等待执行命令如下// 客户端1 // 先向队列中推入两个元素 lpush blockmq a b c // 客户端2 // 通过阻塞的方式获取元素 brpop blockmq10// 如果没有元素则需要等待直到有元素为止20代表的是没有元素时需要等待的时间 brpop blockmq20使用brpop要注意以下几点客户端以有序的方式服务第一个被阻塞而等待列表的客户端在某个元素被其他客户端推送到列表中时首先被服务以此类推。返回值与rpop相比有所不同它是一个包含两个元素的数组还包含键的名称​brpop和blpop通过阻塞方式等待来自多个列表的元素。如果超时了则返回null。3.1.4、模拟消息推送将上面的阻塞队列稍微扩展就可以实现消息的推送和消费功能其架构图如图所示。3.2、List常规操作如下是Redis的List常规操作// 向keyList集合中写入三个元素192.168.3.202:1lpush keyList mysql mongodb redis(integer)3// 获取集合keyList中的所有元素192.168.3.202:1lrange keyList01001)mysql2)mongodb3)redis3.3、利用列表迅速提升网站首页的并发量以博客站点为例当用户和文章都越来越多时为了加快程序的响应速度我们可以把用户自己的文章存入列表(List)中因为列表是有序的结构所以这样又可以实现分页功能从而加快程序的响应速度。每篇文章使用哈希结构存储例如每篇文章有3个属性title、timestamp、content。使用列表存储文章内容的执行命令如下hmset bbs:1 title title1 timestamp123456789content 这是内容 hmset bbs:2 title title2 timestamp123456789content 这是内容 hmset bbs:3 title title3 timestamp123456789content 这是内容上面的命令仅存储了文章的详细信息如果要实现分页就必须把每篇文章存储在哈希表中的键(Key)存储在列表中执行命令如下lpush user:1:bbs bbs:1 lpush user:1:bbs bbs:2 lpush user:1:bbs bbs:3分页获取用户文章列表的命令如下lrange user:1:bbs09如果每次分页获取的文章数较多就需要执行多次hgetall操作此时可以考虑使用Pipeline批量获取或者考虑将文章数据序列化为字符串类型然后使用mget批量获取。在我们访问任何一个网站时并发最高的可能就是网站的首页如果首页是列表类信息那么完全可以使用List来实现。把首页中的所有信息都存储在List中即放在内存中提高用户访问首页的响应效率。如果数据量特别大那么肯定不是一个好方案。可以把标题和当前文章的ID主键存储在Redis的List中当用户点击当前文章并需要查看详情的时候结合主键ID再去数据库中查询这算是一个很不错的方案。4、开启Set数据类型的正确操作姿势集合(Set)数据类型是其元素无序且唯一的一种键值对(Key-Value Pair)集合。因为是无序的所以不会按照元素插入的先后顺序进行存储。集合类型和列表类型的区别如下列表可以存储重复元素集合默认去重。列表顺序存储而集合是无序存储。列表和集合都支持增、删、改、查同时集合还支持取多个集合的交集、并集、差集。4.1、明星绯闻和微博瘫痪的那些事当出现xxx明星和xxx事件的八卦时可能会造成服务器压力过大导致服务器宕机。宕机的大多数原因是很多人同时去关注和查看一条热点新闻或者八卦而对于热点的过多点赞或者评论会导致并发问题。如果将这些点赞和评论利用缓存来解决然后不定时地把缓存数据刷到数据库中那么将能提高性能。4.2、抽奖逻辑在我们的生活中不管是公司年会抽奖还是公众号抽奖​大部分都是通过程序把手机号码或者其他信息放在一起然后随机抽取。在这个场景中很适合使用Redis的Set数据类型。重复再抽奖品可以执行如下命令结果如图所示。// 首先存入所有参与抽奖的用户信息 sadd luckuser user1 // 当存入重复的用户信息时存入操作会失败并返回结果0 sadd luckuser user1 // 一次性存储多个用户信息 sadd luckuser user1 user2 user3 user3 user4 user5 // 查看所有参与抽奖人的信息不会有重复的 smembers luckuser // 随机抽取一个人抽完不删除信息可以再次抽奖 srandmember luckuser // 随机抽取三个人抽完不删除信息可以再次抽奖 srandmember luckuser3// 验证抽完之后人员信息是否被删除 smembers luckuser对于一个用户只可以抽一次奖品的抽奖活动则可以执行如下命令执行结果如图所示。// 清理所有数据flushdb// 一次性存储多个用户信息sadd luckuser user1 user2 user3 user3 user4 user5// 随机抽取一个人抽完从集合中删除获奖用户的信息不能重抽spop luckuser1// 随机抽取两个人抽完之后不能重抽spop luckuser2// 验证抽完之后获奖用户的信息是否被删除smembers luckuser4.3、文章点赞或者投票在朋友圈经常收到亲戚朋友发来的链接请求帮忙点赞和投票。就投票而言如果根据用户ID或者IP地址来进行限制那么一个IP地址或者一个用户ID只能针对一个信息投票一次。集合数据类型能够体现出它的特色即自动去重。对于点赞应用也是一样的。点赞或者投票操作可执行如下命令结果如图所示。// 用户根据ip点赞或者投票sadd like:id1 ip1// 用户根据ip点赞或者投票sadd like:id1 ip2// 用户根据ip点赞或者投票sadd like:id1 ip3// 重复投票和点赞会失败并返回0sadd like:id1 ip3// 取消投票或者点赞srem like:id1 ip3// 查看点赞或者投票人的信息smembers like:id1// 查看所有的投票数或者点赞数量4.4、共同好友统计利用Redis中的Set类型可以模拟此功能执行如下命令的结果如图所示。// 给张三同学加好友 friendasadd zhangsan frienda// 给张三同学加好友 friendbsadd zhangsan friendb// 给张三同学加好友 friendcsadd zhangsan friendc// 给张三同学加好友 frienddsadd zhangsan friendd// 给李四同学加好友 friendasadd lisi frienda// 给李四同学加好友 friendbsadd lisi friendb// 给李四同学加好友 friendesadd lisi friende// 给李四同学加好友 friendfsadd lisi friendf可以利用如下命令模拟张三和李四的共同好友操作结果如图所示。// 张三和李四共同的好友两个集合的交集sinter zhangsan lisi// 张三可能认识李四的好友sdiff lisi zhangsan// 李四可能认识张三的好友sdiff zhangsan lisi// 张三和李四两个人的总好友两个集合的并集sunion zhangsan lisi5、微服务的限流操作和Zset的那些瓜葛有序集(Zset)类型是Redis中一个非常重要的数据类型类似于集合类型和哈希类型之间的混合类型。像Set集合一样Zset有序集由唯一、非重复的一组元素所组成从某种意义上说也是一个集合。Zset类型的数据结构如图所示。虽然Zset内的元素没有排序但是排序后集合中的所有元素都与一个称为得分的浮点值相关联这就是Zset类型类似于哈希类型的原因因为每个元素都映射到一个值​。微服务架构图如图所示日益流行缓存、降级和限流是保护微服务系统运行稳定性的三大利器。缓存的目的是提升系统访问速度和增大系统处理的容量而降级是当服务出现问题或者影响到核心流程的性能时需要暂时被屏蔽掉待高峰或者问题解决后再打开。有些场景并不能用缓存和降级来解决比如稀缺资源、数据库的写操作、频繁的复杂查询因此需有一种手段来限制这些场景的请求量这就是限流。Redis的Zset类型的结构可以实现限流功能。5.1、利用Zset实现限流限流是对系统的出入流量进行控制防止大流量出入从而导致资源不足、系统不稳定。限流的目的应当是通过对并发访问和请求进行限速或者对一段时间内的请求进行限速来保护系统。一旦达到限制速率就可以拒绝服务或者让请求的服务等待。假设我们上线一个服务而这个服务提供的最大处理能力是1秒服务2000个QPS遇到高于2000的QPS该如何解决呢如果不对其限流后果可想而知。下面了解一下两种基本的限流算法。5.1.1、滑动窗口限流方式固定窗口限流的效果如图所示。前两个窗口和后两个窗口分别表示的是第一秒和第二秒它们的长度相同表示每一秒接收的请求数量相同达到了限流的效果。这种固定窗口限流会出现一些问题。假设限流设置为1秒2000个请求而在第一秒的最后100毫秒以及第二秒开始的100毫秒都收到2000次请求就等于在200毫秒的周期中收到4000次请求并且限流通过。这样就出现了两倍的配置速率问题。滑动窗口为固定窗口的改进版如图所示。在每个时间片段接收到的请求数量都是相等的或者不会超过限制数量。接下来我们看一下使用Zset应该如何处理。首先利用Zset类型在value处存放随机字符而在score部分存储时间戳。每一次请求进来的时候获取限流key值数量根据当前时间戳和当前时间减去前一秒的时间戳。这样每次获取的都是一秒内的总请求数量。如果没有超过限制继续处理如果超过则等待或者直接返回。使用Zset实现限流的数据结构图如图所示。实现代码如下以C#代码为例​// / summary // / 滑动窗口限流利用Redis的Zset类型 // / /summary // / returns/returns public bool LimitFlow() { DateTime currentTime DateTime.Now; using (IRedisClient client redisService.GetRedisClient()) { // 判断是否存在响应的键 if (client.ContainsKey服务名称) { // 获取前一秒到当前秒时间内的集合总数量 // 此数量表示的就是请求数量 int count client.GetRangeFromSortedSetByLowestScore(服务名称 currentTime.AddSeconds(-1).ToFileTimeUtc() currentTime.ToFileTimeUtc()).Count; // 8表示限流数量 // 如果请求数量大于限流数量则返回false可以拒绝服务请求 if (count ! nullcount 8) { return false; } } // 如果是第一次获取请求数量或者满足限流限制 // 则返回true然后给Zset中添加一次请求数量score部分存储的就是当前的时间戳 client.AddItemToSortedSet(服务名称, Guid.NewGuid().ToString() currentTime.ToFileTimeUtc()); return true; } }当请求数量超过限流数量时则拒绝业务处理限流数量为8​结果如图所示。5.1.2、令牌桶令牌桶算法是和漏桶算法效果一样但方向相反的算法且更加容易理解。随着时间的流逝系统会按恒定1/QPS时间间隔如果QPS100则间隔是10毫秒往桶里加入令牌如果桶已经满了就不再加了。新请求来临时会各自拿走一块令牌如果没有令牌可拿就阻塞或者拒绝服务。令牌桶操作流程示意如图所示。实现代码如下// / summary // / 利用Redis中的List类型定时生成令牌 // / /summary public void SetIntervalTimeTask() { // 定时调用此方法往keylimit_list的List类型中插入随机值 using (IRedisClient client redisService.GetRedisClient()) { // 往阻塞列表压入数据 client.PushItemToList(limit_list Guid.NewGuid().ToString()); } } // / summary // / 获取令牌 // / /summary // / returns/returns public bool GetToken() { using (IRedisClient client redisService.GetRedisClient()) { // 从阻塞列表弹出键 var token client.PopItemFromList(limit_list); // 返回结果是空表示令牌桶中的令牌已经用完否则表示拿到了令牌 if (!string.IsNullOrWhiteSpace(token)) { return true; } return false; } }5.2、新闻排行榜场景实战新闻排行榜效果如图所示。百度热榜可能是我们经常看到的针对这样数据量大且实时性高的排名用传统的数据库来实现性能太差。下面我们使用Redis中的Zset数据类型来模拟一下。// 清空当前数据库flushdb// 以关注度分值将新闻id是001的数据存储到集合中zadd news601newid001// 以关注度分值将新闻id是002的数据存储到集合中zadd news605newid002// 以关注度分值将新闻id是003的数据存储到集合中zadd news505newid003// 根据关注度的递增获取热榜新闻zrange news010withscores// 对新闻001点赞量加3zincrby news3newid0015.3、直播打赏排名场景实战当下直播平台都会给粉丝主播提供打赏功能并且打赏金额越多粉丝排位越靠前。这样的功能也是在数据实时性高、大数据和高并发下的场景中实现的存在一个时间段内大量粉丝打赏和打赏排名更新的情况。为了提高性能和实时性用Redis实现是一个很不错的选择下面我们来模拟一下该场景。模拟直播打赏排名场景需要执行如下命令执行结果如图所示。// 清空当前数据库flushdb// 以粉丝打赏的金额作为元素分值存入集合中zadd anchor10fans1 zadd anchor20fans2 zadd anchor200fans3// 按照粉丝的打赏金额排名顺序输出粉丝信息zrange anchor0-1// 按照粉丝的打赏金额排名倒序输出粉丝信息zrevrange anchor0-1// 粉丝再次打赏金额叠加zincrby anchor100fans1// 粉丝信息和金额全部输出zrange anchor0-1withscores