springboot项目创建笔记21 之《redis应用场景》--不断更新

一、incr命令统计访问量
1)用于统计文章、用户的访问量和点击率
2)incr命令
redis incr命令将key中储存的数字值增一。
如果key不存在,那么key的值会先被初始化为0,然后再执行INCR操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
本操作的值限制在64位(bit)有符号数字表示之内。
3)redis内部采用整数形式(Integer representation)来存储对应的整数值
4)缺点是用户每操作一次,就向redis服务器发送一个incr命令,如果并发量很大,会对服务器造成压力,耗费CPU
5)如何把统计的访问量同步到mysql数据库?

二、使用hash结构存储商品的库存、价格、关注数、评价数

三、使用hash结构实现短链接转换器
1)长链接转换为短链接:长链接转换为短链接加密串key,存储在redis的hash结构中
2)重定向到原始url:通过加密串key到redis里找到原始url,然后重定向出去

四、使用hash结构实现购物车场景
1)登录状态下添加商品到购物车
在高并发情况下,登录用户添加购物车,购物车服务先把数据存储在redis,然后发消息给rabbitmq写入数据库。这种高并发情况,就能保证所有的查询操作都能落在redis上,从而减少db的操作。
2)未登录状态下添加商品到购物车
未登录用户添加购物车,购物车服务为该用户生成唯一id:cartid,并把该cartid作为key,把购物车的数据保存进redis(有效期3个月)。
购物车服务把该cartid会写进用户的cookies。
这个环节不保存数据库,因为没有对应的用户。
以后用户操作购物车,都带上这个cartid就行。
3)登录后发起合并购物车请求
用户登录时,如何把已登录的购物车历史数据和未登录的购物车历史数据合并?
用户登录成功后,客户端把cookies里面的cartid和userid发给购物车系统,购物车系统通过cartid把redis未登录的数据合并插入到该userid的数据库里面。
重新刷新添加到登录userid的redis数据。
删除未登录cartid的redis数据。
4)采用hash数据结构
往购物车加入商品,key=cart:user:用户id
hset cart:user:1000 101 1
hset cart:user:1000 102 1
hgetall cart:user:1000
修改购物车数据,为某件商品添加数量
hincrby cart:user:1000 101 1
hincrby cart:user:1000 102 2
hgetall cart:user:1000
统计购物车有多少件商品
hlen cart:user:1000
删除购物车某件商品
hdel cart:user:1000 102
hgetall cart:user:1000

五、解决分布式系统session一致性问题
session是客户端与服务端通讯会话跟踪技术,服务器与客户端保持整个通讯会话基本信息。
客户端在第一次访问服务端的时候,服务端会响应一个sessionId并且将它存入到本地cookie中。
在之后的访问会将cookie中的sessionId放入到请求头中去访问服务器。
分布式session会有什么问题?
用户第一次访问nginx,请求落到服务器A,服务器A生成了一个sessionId,并保存在用户的cookie中。
用户第二次再来访问nginx,他这次把cookie里面的sessionId加入到http请求头中,这时请求落到了服务器B,服务器B发现没有找到sessionId,于是创建了一个新的sessionId并保存在用户cookie中。
解决办法:
分布式系统统一将sessionId保存在redis中

六、热门商品抢购查询
1、需求和特点:
1)数据量少
2)高并发,请求量大
2、技术方案:
像这种高并发的功能,绝对不可能用数据库
一般的做法是先把数据库中的数据抽取到redis里面。采用定时器,来定时缓存
还有要支持分页查询
redis list数据结构天然支持这种高并发的分页查询功能
具体技术方案采用list的lpush和lrange来实现
3、什么是缓存击穿?
在高并发系统中,大量的请求同时查询一个key时,如果这个key正好失效或删除,就会导致大量的查询都打到数据库上。
如当QPS=1000的时候,这时定时器更新redis,先删除再添加就会出现缓存击穿的问题。就必定导致大量的请求都打到数据库上面,从而把数据库打垮。
4、如何解决缓存击穿的问题?
针对这种定时更新的特定场景,解决方案:采用主从轮询的原理来实现
步骤1:定时更新原理
开辟2块缓存,A和B,定时器在更新缓存的时候,先更新B缓存,然后再更新A缓存,记得要按照这个顺序来。
步骤2:查询原理
用户先查询缓存A,如果缓存A查询不到(例如,更新缓存的时候删除了),再查询缓存B。
以上两个问题,由原来的一块缓存,开辟出2块缓存,最终解决了缓存击穿的问题。

七、高并发的微信抢红包
1、微信抢红包的并发场景分析
微信抢红包的高峰期一般是在年底公司开年会和春节2个时间段,高峰的并发量是在几千万以上
高峰的枪红包有3大特点:
1)包红包的人多:也就是创建红包的任务比较多,即红包系统是以单个红包的任务来区分,特点就是在高峰期红包任务多
2)抢红包的人更多:当你发红包出去后,是几十甚至几百人来抢你的红包,即单红包的请求并发量大
3)抢红包体验:当你发现红包时,要越快抢到越开心,所以要求抢红包的响应速度要快,一般1秒响应
2、微信抢红包的技术实现原理
2-1、包红包
1)先把金额拆解为小金额的红包,例如总金额100元,发20个,用户在点保存的时候,就自动拆解为20个随机小红包
2)这里的存储就是个难题,多个金额(例如20个小金额的红包)如何存储?采用set?list?hash?
2-2、抢红包
高并发的抢红包时核心的关键技术,就是控制各个小红包的原子性
例如:20个红包在500人的群里被抢,20个红包被抢走一个的同时要把红包的库存减1,即剩下19个
在整个过程中抢走一个和红包库存减1个是一个原子操作
那数据类型符合“抢走一个 和 红包库存减1个 是一个原子操作”采用set?list?hash?
2-3、list比较合适
list的pop操作弹出一个元素的同时会自动从队列里面剔除该元素,它是一个原子性操作

八、高并发文章的阅读量PV业务场景分析
1、一级缓存的高并发文章的阅读量PV方案

假设微信每天有10万篇文章,每篇文章的访问量10万
如果采用redis的incr命令来实现计数器的话,每天redis=10万*10万=10亿次的写操作,1天高峰12小时算的话,redis大约QPS=57万
如此大的并发量,CPU单核必定100%(redis是支持单核),此种技术方案是行不通的
2、二级缓存的高并发文章的阅读量PV技术方案

 如此大的并发量,唯一出路就是减少redis的访问量;那如何减少redis访问量?
上图的设计了二级缓存和2个定时器
1)文章服务采用了集群部署,在线上可以部署N台
2)每个文章服务,增加了一级JVM缓存,即MAP存储在JVM中
Map<Long,Map<Integer, Integer>> = Map<时间块,Map<文章Id,访问量>>
3)一级缓存定时器消费
定时器,定时(5分钟)从jvm的map把时间块的阅读pv取出来,然后push到redis的list数据结构中,list的存储的数为Map<文章id, 访问量pv>即每个时间块的pv数据
4)二级缓存定时器消费
定时器,定时(6分钟)从redis的list数据结构pop弹出Map<文章id,访问量pv>,弹出来做了2件事:
第一件事:先把Map<文章id,访问量pv>,保存到数据库
第二件事:再把Map<文章id,访问量pv>,同步到redis缓存的计数器incr
Map<Long,Map<Integer,Integer>> = Map<时间块,Map<文章id,访问量>>
小结:以上4个步骤,用了一级缓存,所有的高并发流量都收集到了本地JVM;然后5分钟同步给二级缓存,从而给redis降压
3、什么是时间块
就是把时间切割为一块块,例如:一般文章在1小时,30分钟,5分钟的时间内产生了多少阅读量
那如何切割时间块呢?如何把当前的时间切入时间块中?
例如,我们要计算“小时块”,先把当前的时间转换为毫秒的时间戳,然后除以一个小时,即当前时间T / 1000 * 60 * 60 = 小时key,然后用这个小时序号作为key
例如:
2020-01-12 15:30:00=1578814200000毫秒,转换为小时key=1578814200000 / ( 1000 * 60 * 60 ) = 438560
2020-01-12 15:59:00=1578815940000毫秒,转换为小时key=1578815940000 / ( 1000 * 60 * 60 ) = 438560
2020-01-12 16:30:30=1578817800000毫秒,转换为小时key=1578817800000 / ( 1000 * 60 * 60 ) = 438561
剩下的以此类推
每一次PV操作时,先计算当前时间是哪个时间块,然后存储Map中:
Map<时间块, Map<文章id,访问量>>
=Map<2020-01-12 15:00:00到15:59:59, Map<文章id,访问量>>
=Map<438560, Map<文章id,访问量>>
4、一级缓存定时器消费
定时器,定时(5分钟)从jvm的map把时间块的阅读pv取出来,然后push到redis的list数据结构中,list的存储数据为Map<文章id,访问量PV>,即每个时间块的pv数据
5、二级缓存定时器消费
定时器,定时(6分钟)从redis的list数据结果pop弹出Map<文章id,访问量PV>,弹出来做了两件事
第一件事:先把Map<文章id,访问量PV>,保存到数据库
第二件事:再把Map<文章id,访问量PV>,同步到redis缓存的计数器incr

以上几个步骤:用了一级缓存,所有的高并发流量都收集到了本地JVM,然后5分钟后同步给二级缓存,从而给redis降压

九、黑名单过滤器
1、业务场景分析
电商的商品评价功能,不是任何人就能评价的,有一种职业就是差评师,差评师就是勒索敲诈商家,这种差评师在电商里面就被设置了黑名单,即使购买了商品,也评价不了。
2、技术方案
黑名单过滤器,除了针对上文说的购物评价,针对用户黑名单外,其实还有ip黑名单、设备黑名单等。
在高并发的情况下,通过数据库过滤明显不符合要求,一般的做法都是通过redis来实现的。
那redis哪种数据结构适合做这种黑名单的呢?
答案是:set
步骤1:先把数据库的数据同步到redis的set集合中。
步骤2:评价的时候验证是否为黑名单,通过ismember命令来实现。

十、大转盘抽奖
1、业务场景分析
获取金币,大转盘抽奖
2、技术方案
抽奖一般是采用redis的set集合来操作的,那为什么是set集合适用于抽奖呢?
2个原因:
1)set集合的特点是元素不重复
存放1个、5个、10个金币、谢谢参与
2)set集合支持随机读取
具体的技术方案是采用set集合的srandmember命令来实现,随机返回set的一个元素
3、编码实现
步骤1:奖品的初始化
由于set集合是不重复,故在奖品初始化的时候,要为每个奖品设置一个序列号
步骤2:抽奖

十一、天天抽奖
1、业务场景分析
0元参与抽奖,比如参与华为手机抽奖,用户报名
2、技术方案
思考一个问题:天天抽奖和大转盘抽奖有什么区别?
1)大转盘抽奖:奖品是可以重复,例如抽5金币,可以再抽到5金币,几金币是无限量抽
2)天天抽奖:奖品不能重复抽,例如1万人抽1台华为手机;再给大家举一个熟悉的例子:例如公司年会,抽中奖品的人,下一轮就不能重复抽取,不然就
技术方案和大转盘的金币类似,但是不同的是
大转盘的金币用了srandmember命令,即随机返回set中的一个元素
天天抽奖要用spop命令,即随机返回并删除set中的一个元素
为什么呢?
因为天天抽奖的奖品有限,不能重复抽,故抽奖完后,必须从集合中剔除中奖的人
再举个每个人都参与过的例子,年会抽奖,你公司1000人,年会抽奖3等奖500名100元,2等奖50名1000元,1等奖10名10000元,在抽奖的设计中就必须把已中奖的人剔除,不然就会出现重复中奖的概率
3、编码实现
步骤1:初始化抽奖数据
步骤2:抽奖逻辑

十二、好友、榜单、QQ群随机展示功能
1、业务场景分析
有一批数据要随机展示给用户看,比如:微博好友推荐(换一换)
思考题:为什么要随机展示?
因为展示的区域有限,在那么小的地方展示全部数据是不可能的,通常的做法就是随机展示一批数据,然后用户点击“换一换”按钮,再随机展示另一批
2、技术方案
随机展示的原因就是区域有限,而区域有限的地方通常就是首页或频道页,这些位置通常都是访问量并发非常高的,一般是不可能采用数据库来实现,通常都是redis来实现
redis的技术方案:
步骤1:先把数据准备好,把所有需要展示的内容存入redis的set数据结构中
步骤2:通过srandmember命令随机拿一批数据出来
3、编码实现
步骤1:提前把数据刷新到redis缓存中
步骤2:编写随机查询接口
4、榜单和QQ群的区别
榜单是整块数据的,所以随机的数据要按块来推荐
所以我们要定义一个java bean来包装整块数据

十三、点赞功能
1、业务场景分析
点赞业务场景,它有2个接口:
第一个接口:点赞或取消点赞,用户点击功能
第二个接口:查看帖子信息,通过用户id和帖子id,查看该帖子的点赞数、该用户是否点赞状态
2、技术方案
点赞的关键技术就是要判断该用户是否点赞,已重复点赞的不允许再点赞,即过滤重复。虽然业务部复杂,可以采用数据库直接实现,但是对于微博这种高并发的场景,不可能查数据库的,一般是缓存,即redis
我们来对上文梳理的2个接口进行技术分析:
第一个:点赞或取消点赞,用户点击功能
采用redis的set数据结构,key=like:postid,value={userid}
采用sadd命令,添加点赞
采用srem命令,取消点赞
第二个:查看帖子信息,通过用户id和帖子id,查看该帖子的点赞数、该用户是否点赞状态
采用scard命令,获取点赞总数
采用sismember命令,判断是否点赞
3、编码实现
步骤1:点赞逻辑
步骤2:查询逻辑

十四、热度排行榜
1、业务场景分析
1)微博热度排行榜:http://d.weibo.com/102803
24小时
一周
一月
从这个排行榜我们可以看出,里面按小时、按24小时(天)、周、月来排行的
而排序的依据是热度,简单的热度算法为:热度 = 转发数 + 赞成数 + 评论数
如果现在就要实现这么一个功能,按照热度实时统计每小时、天、周、月的排行情况。思考如何做?
2)再来看另外一个业务场景:百度热搜排行榜
http://top.baidu.com
也是按实时(估计也是小时)、今日、七日等来排行的
搜索排行是按关键字来排行的,也就是说统计每个搜索关键字来实现排行
2、技术方案
像微博、百度这种高并发实时计算的排行榜,根本就不可能用db实现,db实现小时、天、周、月的排行榜,难度及其大,而且表结构的设计也非常难;
再者db也扛不住这么大的并发量。所以高并发实时的排行榜天然适合用redis来实现。
整体的技术实现是采用redis的zset来实现,每条微博是一个member,每条微博的热度值为一个score
1)思考:那如何把小时、天、周、月的数据,实时计算呢?
我们以小时为单位,即每个小时为一个zset
近24小时,就合并24个zset(求并集)
近7天,就合并24*7个zset
近30天(月),就合并24*30个zset
2)思考:如何实现以每个小时为一个zset?如何把时间切割为小时?
使用时间块
先把当前的时间转换为毫秒的时间戳,然后除以一个小时,即当前时间T/1000*60*60=小时key,然后用这个小时序号作为zset的key
有了这个思路,剩下的工作就是每次某个热度有变化,先计算当前的小时key,然后把当前的微博作为member,热度值作为score,加入zset中。
redis zset命令如下:
ZADD 小时key 热度值 微博内容
3、编码实现
1)技术模拟思路:
采用26个英文字母来实现排行,随机为每个字母生成一个随机数作为score(一个字母当做一个微博内容)
为了更好的体验,先做几件事:
1-1)先初始化1个月的历史数据
1-2)定时5秒钟,模拟微博的热度刷新(例如模拟点赞、收藏、评论的热度值更新)
1-3)定时1小时合并统计,天、周、月的排行榜
2)步骤1:先初始化1个月的历史数据
3)步骤2:定时刷新数据
4)步骤3:排行榜查询接口

十五、地图附近酒店搜索
1、业务场景分析
在日常生活中,附近的酒店和附近的人
2、技术方案
自redis 3.2开始,redis基于geohash和zset提供了地理位置相关功能
什么是Geohash
Geohash是一种地址编码,它能把二维的经纬度编码成一维的字符串。比如,世界之窗的编码是ws101xy1rp0
Redis Geo模块包含了以下6个命令:

GEOADD命令
将给定的位置对象(纬度、经度、名字)添加到指定的key
注:
1)这里我们采用的是中文存储,如果出现了乱码,redis命令的登录命令加上  --raw
例如:./redis-cli --raw
2)查看某个地址的经纬度,建议用http://www.gpsspg.com/maps.htm

GEOPOS命令
从key里面返回所有给定位置对象的位置(经度和纬度)

GEOHASH命令
返回一个或多个位置对象的geohash

GEODIST命令
geodist key member1 member2 [unit]
返回两个给定位置之间的距离:
指定单位的参数unit必须是以下单位的其中一个:
m:表示单位为米
km:表示单位为千米
mi:表示单位为英里
ft:表示单位为英尺

GEORADIUS命令
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
给定一个经纬度,然后以半径为中心,计算出半径内的数据
WITHDIST:在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致
WITHCOORD:将位置元素的经度和纬度也一并返回
WITHHASH:以52位有符号整数的形式,返回位置元素经过原始geohash编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中的作用并不大
ASC、DESC:排序方式,按照距离的升序、降序排列
STORE key1:把结果存入key1,zset格式,以坐标hash为score
STOREDIST key2:把结果存入key2,zset格式,以距离为score

GEORADIUSBYMEMBER命令
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
GEORADIUSBYMEMBER和GEORADIUS一样的功能,区别在于,GEORADIUS是以经纬度去查询,而GEORADIUSBYMEMBER是以当前集合中的某个member元素来查询

3、编码实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值