Redis场景应用实例

Redis的相关知识点和需要注意的问题我们都已经梳理过了,关键还是需要运用到实际的工作中才能达到学以致用,下面我们将整理一个实际应用的场景,并且集合Redis来实现其需求。

目前公司有十万员工,分成500个部门,公司为员工制定了每日9点前和18点后网上签到的制度,签到之后可以及时查看自身签到状态,主管可以及时收到下属员工的签到状态,一整天未签到的员工自动补充旷工

以上为场景;请用java+redis+队列+mysql实现此功能

分析该需求,首先10w的员工签到,这个涉及到高并发的场景,并且主管还需要及时收到下属员工的签到状态,这个需要异步通知的功能,并且一整天未签到的员工自动补充旷工,这个需要一个定时任务来实现

综上所述我们为了实现其功能最少需要高并发签到,异步通知以及定时任务这三个技能,明白了其所需要使用到的技术,下面我们来一个个的剖析,并且实现这样的功能。

高并发来实现签到功能

为了实现高并发,题目中已经给出了我们需要使用redis来实现,这个没得选,但是我们还是需要分析下是单机版还是Redis集群来实现这样的功能。

单机还是集群?

10w员工,按照二八定律分析来说大概同时有2w的签到,也就是写请求(二八定律真乃神定律,在现实生活中随处可见),单机版本的Redis大概能支持10w左右的QPS(这个数值有波动,主要跟服务器的性能有关系,内存读写速度快的,这个数值还会更大点),所以我们这个单机版的Redis就能实现这样的功能,如果是员工是100w的话,我们就需要搭建Redis服务集群,最好还是能做到读写分离,这样还能做到横向扩容。

数据结构很重要!!

一个适用的数据结构能优化代码逻辑,并且减少操作时间复杂度,优化服务响应速率,提升客户使用度,所以数据结构十分的重要。

  1. 首先确定使用redis的数据类型

学过Java的朋友应该都知道Java中的Map的理想操作时间复杂度是O(1),也就是说对于内存来说,访问任何地址的时间是一样的,即时间极短,相当于可以同时访问到所有地址,那么这里redis的hash数据类型和Java的Map是类似的,所以我们选用hash类型来实现所有员工在Redis中的数据存储

  1. 继续优化hash的数据结构

看到这里有的朋友可能会考虑,将10w的员工的信息都存放在Redis中,就需要10w个key(map),No,No,不是这样的!为什么不这样,我们来分析下。

还是跟Map的类型有关,如果10w个map存储在redis中,需要消耗一定的内存,并且数据越大,内存查询的速度也就越慢,所以我们的着眼点应该是按照部门来分别存储,这样查询的时候也可以根据部门来减少查询到次数,所以我们第一层的数据结构应该是以部门ID+常量+签到日期为key的map,这样做的好处有两个:

  • 每个人都是归属部门的,所以先根据部门来缩小查找的范围,减少查询遍历的次数:500+2000=2500,也就是说我们定位一个人最大遍历2500次就够了,而如果是直接查询个人的话,最大可能遍历10w次。
  • Redis中存放500个map比存放10w个map来的小的多,减少了redis的数据存储内存的消耗

接着,我们再来分析下value的存储什么?

我们的需求是员工进行签到,所以我们的value值中必须要包含是否签到的相关信息,这里我们还可以扩展下,需求要求是上班前和下班后都进行签到,需要区分签到的类型,所以我们还可以添加一个常量来进行签到类型区分,其次是谁签到的,说到这里,我想这个数据结构已经很清晰了。

是的还是使用hash结构来存放人员的信息,其中key值最好是人员的id,而value值可以是签到的状态

  1. 数据结构的确定
    至此,经过我们的分析,我们确定了Redis中签到的数据结构,使用Java的数据结构来表达:
    // 外围的Key是String类型: 主要存放部门的id+常量+签到日期
    // 外围的Value是Map类型:主要存放该部门的人员的信息
    // 里层的Key是String类型: 主要存放人员的ID
    // 里层的key也是String类型(这里也可以修改为List类型): 主要存放签到的状态
    Map<String,Map<String,String>>

业务流程的实现

  1. 初始化数据

既然签到的map数据结构已经确定了,那么我们该怎么来使用这个数据结构来实现我们的业务呢?

这里提供一个方案,我们可以提前将部门的map的数据给初始化,有两个原因:

  • 部门和人员的数据一般变动不大,所以可以先初始化,节省时间,到时直接从Redis中取就可以了
  • 避免签到的时候才来生成部门签到map,主要是为了避免签到高并发时,造成数据覆盖现象,导致签到数据不正确

这里的部门签到map(暂时这么称呼),其有效期应该设置为永远,避免数据失效!

  1. 签到

用户登录系统后,我们会在redis中生成用户的基本信息,这里主要是用户的人员ID和所在部门的ID。

  • 用户签到的时候,将个人信息写入到redis,主要是为了用户查看其基本信息,包括签到的状态信息
  • 根据员工ID和部门ID到部门签到map中获取人员的相关信息,并且更新签到状态。这里最好添加锁,防止被其他员工取到产生竞态,等部门签到map中的个人签到状态更新成功之后,释放锁,并且更新redis中个人信息中的签到状态
  • 将签到相关信息放入队列中

上述的签到流程操作,可以及时的让用户查看到自己的签到情况,即使高并发的情况下也不影响系统的响应流畅度

  1. 通知主管

用户签到之后,就将签到的信息发送到队列,通过队列来异步通知主管,这里可以使用RabbitMQ来实现消息的发送通知,主要是考虑RabbitMQ的消息的安全和消息消费失败后,可以使用死信队列来实现重发,确保消息的准确和安全性。

  1. 定时数据持久化

开启一个定时任务,将部门签到map提取出来,然后循环遍历,将人员签到的信息持久化到数据库,对于持久化失败的数据,可以写入到日志中,展示给相关人员查看。将一整天都未签到的人员置为旷工。

数据持久化后,可以删除当天的部门签到map,同时初始化明天需要用到的部门签到map

可以是用流程图展示如下:

至此,我们的整个业务流程就很清晰了,对应的代码实现也就很容易了

总结

在对人员签到的分析设计已经结束了,不过我们就其中的几个问题需要讨论下

为了实现上述的功能,redis中最小的key值是多少?

我们上面设计中 redis中的key是500个? 那能不能再次缩小呢? 其实是可以的,就是在外面再加一层map,这样保存到redis中就一个key键值了,如果这样设计的话,那其实没有什么意义,还是需要查找到对应的部门,再查找到人员,这样反而增加了一步查询,还不如不加,而且如果只有一个key值的话,那么用户签到的时候需要加锁,就将这个key添加了锁,这样话并发量就不是很大了,所有的用户都要等该用户签到完成后再进行其拿到,从业务角度来看就是将10w员工的签到串行了,我们上面设计的500个key,对不不同部门的人员签到是不影响的,从业务角度来说,不同部门是可以同时签到的,而且不影响数据,提高了效率。

为什么需要吧签到的信息发到员工的信息里,还要保存到部门签到map中?

我们上面的设计中 redis的部门签到map中已经存放了人员的签到信息,为什么还要在个人信息中存放签到信息呢? 主要是为了需求中用户可以随时查看自己的签到状态。

这里有的朋友可能就疑问了,部门签到map中不是已经存在了么,这里主要是为了人员查看的效率考虑的,如果存放在部门签到map中,那么每次查看一次都需要从部门签到map中提取出来个人信息才能查看,这样会影响查看效率。

如果基于redis实现加锁和释放锁功能,如何考虑其中的锁释放问题?

加锁的功能其实也可以由redis来实现,我们可以在签到时,对这个部门ID+加锁常量+签到日期,在redis中新增一个key,如果同部门的其他用户也来签到时,先查看是否存在部门ID+加锁常量+签到日期的key,如果存在则循环等待一段时间再请求,相当于给当前签到用户添加了锁,等这个用户签到成功后,再删除这个key。

上面就是简单的加锁的功能,这样需要考虑释放锁的问题,如果加锁成功了,程序在处理业务时程序异常,没有正常的释放锁,这样就会造成其他的人员获取不到锁,无法进行业务操作,解决这个问题,我们首先需要考虑不管业务是否成功,我们都需要释放锁,这样至少不会对其他的人员签到造成影响。

那么该如何实现这样能,其实redis能很简单的实现,就是给这个key添加失效时间,这样只要到失效时间,这个key就会被删除,也就相当于释放了锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值