二、redis的实践应用

本文介绍了IT技术中短信验证码的使用、商品缓存策略与一致性解决方案,包括缓存击穿、穿透和雪崩现象,以及如何通过全局唯一ID、用户签到机制和HyperLogLog计算UV和PV。
摘要由CSDN通过智能技术生成

目录

一、短信验证码

1.1、说明

1.2、发送验证码

1.3、登录

1.4、登录状态

 二、商品缓存

2.1、说明

2.2、要点分析

2.3、系统设计

三、缓存击穿、穿透、雪崩

四、全局唯一id(业务时间+incrby)

五、用户签到(bitmap)

六、页面UV、PV(HyperLogLog)



一、短信验证码

1.1、说明

我们常见的web网站或者app中,用户使用的第一步就是手机验证登录。

1、输入手机号

2、点击获取验证码

3、点击登录

那么这里在后端涉及两个操作,1:获取验证码,2:登录

在获取验证码之后,那么手机号和验证码将一一绑定,然后返回给前端,当前端登录的时候,拿手机号和验证码进行验证。

1.2、发送验证码

此功能主要以下几步

1、验证手机号是否合规。正则表达式:^1[3-9]\d{9}$

2、生成验证码&发送。可以使用hutool工具获取一个随机6位数字

3、将验证码保存。直接利用redis的 set key value。其中key为手机号,value为验证码。

1.3、登录

登录时,前端将会把手机号和验证码发送给后端。后端的主要功能是校验手机号,校验

验证码。

1、手机号验证直接查询数据库判断当前是否存在。

2、从redis中通过get key获取对应的验证码,进行匹配。

1.4、登录状态

当用户登录成功之后,可以将用户的登录状态放入redis中,在用户每次请求的时候,利用拦截器去redis中获取登录状态。

为了保证请求后续的逻辑都能拿到用户的信息,可以将获取到的用户登录状态放入ThreadLocal中。
 

 二、商品缓存

2.1、说明

一般在电商网站,日均PV可能达到亿级流量,这就意味着会有大量的流量过来请求商品数据。但是商品的数据一般都放在结构化数据库中,例如mysql。如果这些请求都直接请求mysql的话,那么将导致mysql压力非常大。

考虑到请求的类型一般都是读请求,因此可以将商品数据放入到redis中,来应对大流量的请求。

2.2、要点分析

1、redis需要多大内存呢?是否要把成千万的商品信息都放入redis?

2、redis和mysql如何保证数据一致性呢?

3、如果redis里面没有数据,怎么保证mysql不被打挂?

2.3、系统设计

首先我们先分析一下查询商品的一般过程:

1、通过id查询redis。

2、如果redis存在,则直接返回

3、如果redis不存在,则查询数据库

4、如果数据库不存在,返回错误。

5、如果数据库存在,则写入redis,然后返回。

上述过程是一个简单的流程。

那么上述流程是否有问题,是否考虑了2.2的问题呢? 显然没有。

问题一:redis需要内存多大,是否需要把所有商品都放入redis呢?

回答一:显然不需要,在流程中,我们从数据库中查询出来一个商品,就将其放入到redis,这样随着系统运行,redis里面的商品会越来越多。但是我们知道redis是基于内存,内存的成本很昂贵,因此对于内存的使用需要格外的慎重。解决办法就是在每次放入redis时,需要增加一个超时时间,这样当到达了超时时间,redis的内存就会被清理。这样就防止redis存储僵尸数据。

问题二:redis和mysql如何一致呢?(执行异常&线程并发)

回答二:

        方法一:如果系统压力不是很大的情况下,当针对商品进行修改的时候,我们可以将mysql的数据数据之后,删除redis数据,这样在下次请求的时候,redis数据不存在,直接查询数据库,然后将最新的数据更新进去。但是此时还是会发生小概率的数据不一致情况。

这里还有一个问题,就是如果mysql数据更新成功,但是删除redis失败了,这也会导致数据不一致,这个时候就需要将redis和mysql的执行放入同一个事物中。

        方法二:如果要求数据强一致,我们就需要针对mysql的写和redis的度和写进行 互斥锁。即,当更新数据库的时候,禁止redis操作。当redis在读和写的时候,禁止mysql写。

        方法三:不直接操作mysql,而是增删改都在redis,然后通过一个异步线程,将redis数据同步到mysql。但是一致性和redis内存使用都不是最优的。

问题三:如果redis没有数据,如何保证数据库不被打挂?

回答三:由于我们在向redis里面放数据的时候增加一个超时,这就导致redis数据会失效,如果数据大量失效,就会导致所有的请求都直接请求redis。这就会导致redis压力很大。针对这种情况,可以采用redis随机超时,即超时时间是被打散的,避免所有redis数据同时失效。

        另外一种极端情况,流量频繁请求一个根本就不存在的数据(恶意攻击),这就导致每次redis都未命中,流量打到mysql,mysql又查不到数据。这就导致缓存穿透。这种情况一般采用缓存空对象来解决。但是如果恶意攻击不断更换不存在的数据,将导致redis缓存大量的空对象,浪费空间。因此可以采用布隆过滤器来解决。

三、缓存击穿、穿透、雪崩

上述三个概念是面试经常提到的,而且也是开发过程中一定要考虑的,但是这个几个概念非常难记,老搞混,今天就详细介绍一下这个三个概念:

1、缓存击穿:

概念解释:

重点在一个【穿】字。指的是,查询的数据redis没有,但是mysql有。 按理来说这个是正常的,因为redis的key终会有过期的哪天,而此时请求来的时候一定会击穿。但是如果这个key是一个热点key并且构建缓存耗时很长,那么就会出现问题。

设想一下,如果是一个秒杀品key,一瞬间很多人来抢,如果redis不存在,那么一定会打到mysql。而由于构建缓存耗时很长,这就导致在构建缓存这段时间,大量的请求都会来mysql。那mysql肯定受不了。

解决办法

首先需要预热,这对这种热点key,一定要预热。

方法一:就是可以尝试互斥锁,当第一个线程发现没有数据时,先redission加锁,然后去查询mysql。此时第二个线程来的时候,发现加锁失败,那就一直等。直到redis数据被线程一构建成功。

方法二:可以尝试逻辑过期。意思就是rediskey不设置过期,而是在value里面增加一个过期时间戳。当线程来请求的时候,都能拿到value,但是检查发现value是一个过期的。那么就去加锁,重新构建。但此时只有一个线程能加锁成功,其他线程无需等待,直接返回旧数据。

2、缓存穿透:

概念解释

重点在一个【透】字。指的是,查询的数据redis没有,去mysql查也没有,直接透了。

按理说透了直接返回给前端失败就行,为什么会有问题呢? 问题就在于这种穿透的数据,下次前端可能还会请求,每次都打到mysql,那如果有恶意攻击,那mysql肯定受不了。

解决办法

方法一:缓存空对象。当mysql查询不存在的时候,向redis插入一个空数据,这样下次再请求,redis就会命中了。这种办法很常用,但是有一个缺陷就是增加redis占用空间。如果有恶意攻击,导致redis有大量垃圾空对象

方法二:布隆过滤器。当请求来的时候,先不检查redis,先检查布隆过滤器,如果过滤器返回不存在,那么就直接返回前端。如果过滤器提示存在,这时候再去查redis。 但是缺点就是布隆过滤器有概率误差问题。而且比较复杂,还需要考虑hash函数等问题

除此之外,我们可以先主动校验id规则,id长度,等等。

3、缓存雪崩:

概念解释重点在一个【崩】字。指的是,大量的请求redis中没有或者redis宕机,导致大量的请求达到了mysql。这mysql肯定受不了
解决办法

第一种情况是rediskey过期。这种解决办法就是不要让大量的rediskey同时过期。在设置过期时间的时候,增加一个随机偏移量,把过期时间打撒一点。

第二种情况就是redis挂了,这个比较难解决,首先redis集群必须有主从,高可用,第二我们可以增加一些多级缓存,例如前端缓存,jvm本地loadingcache缓存等。

   

四、全局唯一id(业务时间+incrby)

全局id在互联网业务中非常常见,我们这里不去讨论各种其他方案了,直接一步到位。

1、雪花算法

2、redis自增key

第一种雪花算法在大部分场景都能满足,其实很多大厂也在用雪花算法。基本上就够用。如果非说雪花算法的缺点那也就是需要获取机器编号或者服务时钟。

第二种就是利用redis的increby命令。但是这里面我们需要加一点花样,而不是直接从0一直加下去。

通常来将全局id一般都是一个long类型。因此可以将long类型前面32bit和后面31bit作为拆分。

32bit:表示当前【业务开始的时间】到【当前时间】的秒数。正常来讲32bit能表示4294967295秒,也就是136年。完全够用了。

31bit:表示当前redis的自增key的value。当然redis的key不要所有业务都公用同一个,应该按照不同业务进行区分。例如 “user:id”, "order:id"。由于redis的incrby对value的数值有限制,因为这个value不能一直自增。那么我们可以分而治之。在key上拼接date。也就是 "order:id" + date。这样每一天的订单数就在这key下存储。

这样一个全局唯一id就生成了。一般来讲redis的写操作的qps极限在10wqps。基本上所有业务都能满足了。如果你在一个非常大型的互联网且做的是一个平台,那就搞多个redis节点就好了。

这里需要注意的是,这个全局唯一中的【全局】两个字指的是业务范围,比如用户id范围,订单范围,但是订单id和用户id完全是可以重复的。

五、用户签到(bitmap)

在一些app中,经常会看到用户签到领奖品的功能,记录用户每天登录或者签到的情况。

思路:

一般来讲,我们可以简单的用mysql来实现,每次用户记录都写入数据库表中。但是用户的签到功能实际上不怎么需要落地,只需要简单计算就行,所以利用mysql占用数据空间太大,没有必要。

用户的签到与否其实可以用0.1来表示:

例如针对张三在1月份的签到情况,可以用32为bit来记录,这样一个用户一个月的存储只需要32bit,即4byte。那么一亿个用户,一年占用空间约为45G。

在redis中,利用string类型来存储bitmap,其最大空间为512m,约为2的32次幂bit,这远远足够了。

六、页面UV、PV(HyperLogLog)

在互联网web应用中,UV和PV是衡量一个网站的用户访问量。一般来讲,用户的访问基本上由前端的页面进行数据上报埋点,我们业务系统一般不会直接记录用户访问情况。但是在特殊情况下,我们也可能会用到UV PV的记录。

那么如何记录页面的UV呢?

第一种方法:每次用户访问的时候,将uid存入到set中。这样set将会保存所有访问的用户。但是这样的代价就是空间成本高,如果web有千万级用户,那么set就有千万个元素,这是很费性能的。

第二种方法:利用hyperloglog。基于概率统计原理,hyperloglog的占用空间非常少,但是代价就是精确率的问题,误差控制到0.8百分之。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值