您的位置:首页 > 数据库 > Redis

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

2017-05-27 15:19 951 查看

一、用户模型



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,结果正确。

大家看完后有什么见解都可以互相讨论下
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐