Redis在线用户设计(解决分页问题)

一、用户模型

这里写图片描述

  • TokenDelegate 理解为token的委派类,何为委派?他组合了User对象,有关token的信息封装在里面,如token、ip、createTime(登陆时间) 等等.
  • TokenDelegate可以抽象理解为User对象,在UI层展示的时候是用TokenDelegate而非User,因为TokenDelegate组合User对象,还有很多其他属性。他可以抽象理解为User对象,所以下面提到的User用户对象其实就是TokenDelegate。展示用户列表有IP、登陆时间、登陆子系统等。so,TokenDelegate是User对象的领域模型

二、List结构

首先最简单的方法就是存list,根据list的上标和下标就可以分页。这样设计有几个问题。
- 不适合海量存储,list数据量过大有性能问题。
- 无法对登录时间进行排序
- 当有一个用户session失效,要删除list对应数据会很麻烦

三、有序集合设计

以上暴露的几个问题可以这样解决
这里写图片描述

  • 解决性能问题
    首先看图中set集合下的元素,其实就是redis的set命令。key为用户id,value是用户对象(序列化后的字符,取的时候反序列化为用户对象)。性能问题就解决了,简单的用id就能获取对象。

  • 分页和排序问题
    有序集合结构{ score=2010-1-1 11:11:02,memerKey=userid},代码解释

//logtinTime为分数值score,memberKey为用户id
redis.zadd("pageNum",loginTime,userId);

有序集合的score分数值可以用来排序,我存的是用户创建时间(转为毫秒),memberKey为用户id,这样就可以根据创建时间进行排序,然后用memberKey(userid)作为key,在set集合中关联出用户对象

  • session失效后,删除有序集合对应的userId
    原有的设计是在获取用户列表的时候判断一下全局token是否失效,失效则删除。删除后会出现当前页展示不满一页的情况(比如一页10条,你删除后变成9条,但是总的条数是20,显示9条肯定不符合逻辑[下面有图])。采取往后一页的数据填充到当前页。缺点不够实时性(不能及时删除失效用户)、性能有问题、编码逻辑复杂

四、实时删除无效key,解决分页问题。

上面提到往后一页填充数据的场景

这里写图片描述

图中有4页数据,假如一页显示6条。第1页是正常显示,第2页有三条失效(不正常),第3页有4条失效(不正常),第4页正常显示.
假如翻到第2页,那么需要进行补页,在第3页取三条数据填充到第2页,发现第三页不够3条数据又向后一页填充。GG思密达,好比数组删除一个元素后要整体移动的道理一样,假如数据量过大移动的时间复杂度也很大。
因此,我采用一个定时检测线程不断去轮询一个有序集合(为了不影响主系统,独立出一个模块执行线程)

String key= "存sessionID和创建时间的key,自己定义";
String sessionID_startTime = sessionID+startTime;

//sessionTime ,session创建时间(转为毫秒的分数值)
zadd(key,sessionTime,sessionId_startTime);

zadd操作在什么情况下执行?当用户第一次建立会话的时候、当用户进行会话操作的时候更新对应的sessionTime。

while(true){
     try {
         String key = “自己定义key";
         Set<String> sets = redis.zrange(key, 0, -1);//降序获取集合
         for (String sessionWithTime :sets) {
             //从sessionWithTime中截取sessionID
             String sessionId = sessionWithTime.substring(xx);
             //从sessionWithTime和截取session最后访问时间
             double lastTime  = redis.zscore(key, sessionWithTime);

             //当前系统时间减去用户最后访问时间得出session活了多长时间
             long elapseTime = System.currentTimeMillis()-(long)lastTime;

             //因为是降序查询,因此其中有session存活期不超过30分钟,后面都不会超过。直接结束循环
             if(elapseTime<1800000L){
                 break;
             }else{
                 //redis各种del操作。
                 redis.del("userid");
             }
        }
    }
}

细心的读者可以发现上面其实只是解决实时失效问题,补页逻辑不用判断用户是否失效。对于图中第2、3页出现空白的情况无无法避免。

五、解决分页填充问题

这里写图片描述

如上图所示实际上的第一页包含了UI展示的许多页,先下个定义,UI展示页定义为虚拟页,实际上的第一页定义为真实页,真实页包含于虚拟页,真实页是一万条,虚拟页为10条。
这能很多很大程度避免补页逻辑的出现,当然无法完全避免。目前先这这样,当运用于真实环境的时候可以再调节。不过还有个问题,假如用户点击第X页,系统如何计算出真实页。代码解释

/**
   * 计算实际页数,virPageSize为页面显示的虚拟页(如10条或者20条),pageNum是前台传过来的页数,
   *pageSize是实际数目。我设置为1万。通过取余数算法可算出
   * @param pageNum
   * @return
   */
private int calculatePageNum(int pageNum) {
      int targetRow = pageNum*virPageSize;
      if(targetRow%pageSize==0){
          return targetRow/pageSize;
      }
      if(targetRow%pageSize==targetRow){
          return 1;
      }
      return (targetRow/pageSize)+1;
  }

如 虚拟页10,真实页数10000。假如用户想查看第1000页
targetRow = 10000 = 1000*10
targetRow%pageSize = 0,则 实际页数(targetRow/10000) =1

如 虚拟页10,真实页数100000。假如用户想查看第999页
targetRow = 9990 = 999*10
targetRow%pageSize==targetRow 余数等于targetRow,说明targetRow不超过1W,不超1万肯定是第一页了.因此targetRow/pageSize = 0,再加上1,结果正确。

大家看完后有什么见解都可以互相讨论下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值