实现方案
用户登录的时候往redis里面存储一个值(对应的在线人数增1),这个值的key值是和这个用户的唯一识别码(例如userId或者username)有联系,在存储的同时,设置其对应的生命周期,比如30分钟,当用户在网站上点击相关操作时(可以结合AOP去实现),重新刷新这个值为30分钟,如果用户什么都不干,30分钟后,redis里面存储的值消失了(对应的在线人数减1),另外用户注销时,直接删除这个用户对应的数据。而我们在获取在线人数的时候不需要对其进行加减操作,我们只需统计redis里面当前存储的数据的个数即可。
主要基于redis里面的EXPIRE指令
example:
redis> SET mykey "Hello"
OK
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10
redis> SET mykey "Hello World"
OK
redis> TTL mykey
(integer) -1
redis>
设置了生命周期后,可以用TTL去查看生命周期剩余时间(单位是秒),最后如果时间到了查询值是-2,这时候用GET test去查询值返回的结果就是 空了(nil)
如果没有设置EXPIRE,TTL查看结果返回是-1,表示生命周期无限大。
核心代码实现
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Created by Kevin on 2015/1/16.
*/
public class OnlineRecordUtils {
private final static Logger log = LoggerFactory.getLogger(OnlineRecordUtils.class);
private static final String UID_PREFIX = "online:uid:";
private static final int EXPIRE_TIME = 200; //设置200秒做测试,按照上述思路是设置成30分钟也就是1800秒
/**
* 用户登录时,记录当前用户ID对应的数据到redis里面
* 此时 在线人数增一
* @param userId 用户ID
* @param username 用户名
*/
public static void login(Integer userId, String username) {
Jedis jedis = JedisPoolUtil.getJedis();
jedis.setex(UID_PREFIX + userId, EXPIRE_TIME, username);
JedisPoolUtil.release(jedis);
}
/**
* 当用户退出时,删除其在redis里面存储的数据
* 此时 在线人数-1
* @param userId
*/
public static void logout(Integer userId) {
Jedis jedis = JedisPoolUtil.getJedis();
jedis.del(UID_PREFIX + userId);
JedisPoolUtil.release(jedis);
}
/**
* 当用户进行相关操作时,重置用户的在线时长
*/
public static void refreshOnlineRecord(Integer userId) {
Jedis jedis = JedisPoolUtil.getJedis();
jedis.expire(UID_PREFIX + userId, EXPIRE_TIME);//重置用户的在线时长
JedisPoolUtil.release(jedis);
}
/**
* 获取在线人数
* @return
*/
public static Integer countOnline() {
Jedis jedis = JedisPoolUtil.getJedis();
Set online = jedis.keys(UID_PREFIX+"*");//keys如果查询不出来返回空的set,不会返回null
return online.size();
}
/**
* 获取在线用户的username结合
* @return
*/
public static List<String> getOnlineUsernames() {
Jedis jedis = JedisPoolUtil.getJedis();
Set online = jedis.keys(UID_PREFIX+"*");//keys如果查询不出来返回空的set,不会返回null
return new ArrayList<String>(online);
}
}
测试
模拟有11个人登陆
import utils.OnlineRecordUtils;
/**
* Created by Kevin on 2015/1/16.
* 记录在线用户数测试
*/
public class TestRecordOnline {
public static void main(String[] args) {
for (int i = 0; i <= 10; i++) {
OnlineRecordUtils.login(i,"user"+i);
}
}
}
人数统计
import utils.OnlineRecordUtils;
/**
* Created by Kevin on 2015/1/16.
*/
public class LogoutUser {
public static void main(String[] args) {
// OnlineRecordUtils.logout(3); //这里模拟用户注销
// OnlineRecordUtils.logout(7);
// OnlineRecordUtils.logout(2);
System.out.println(OnlineRecordUtils.countOnline()); //统计在线人数
System.out.println(OnlineRecordUtils.getOnlineUsernames()); //获取在线人数的username
// OnlineRecordUtils.refreshOnlineRecord(1); //模拟用户进行了操作,重置生命周期,在AOP里面补获到用户进行了某些页面的跳转
// OnlineRecordUtils.refreshOnlineRecord(8);
// OnlineRecordUtils.refreshOnlineRecord(4);
}
}
当然此方案存在一些问题和不足,keys*的执行效率在数据量很大的情况下应该并不是很理想.