最代码官方的gravatar头像
最代码官方 2014-08-31 18:06:42

最代码网站中在线提醒的机制优化经验及其相关源代码片断分享

提醒功能前台截图:

最代码网站中在线提醒的机制优化经验及其相关源代码片断分享

提醒功能在线用户列表管理后台截图:

最代码网站中在线提醒的机制优化经验及其相关源代码片断分享

最代码网站中的提醒功能经历了几次的优化:

  1. js每隔10s请求下mysql中reminds的在线用户数据
  2. js每隔10s请求从java cache中获取,有其他用户私信该在线用户,或评论该在线用户的分享等操作时,同时更新mysql reminds记录和在线用户的cache,如果用户不在线则只更新mysql
  3. 除了js每隔10s请求外,如果用户刷新页面同时从session中获取对应的reminds数据,有则直接提醒,无须等到10s后再提醒

这个机制还不完善,后续版本可以修改为基于html5长连接的方法。

相关源代码片断:

UserController.java中reminds方法,也就是js请求的接口方法

@RequestMapping(value = { "reminds" }, method = { RequestMethod.GET })
	public @ResponseBody
	JSONObject reminds(HttpServletRequest request, HttpSession session) {
		JSONObject json = new JSONObject();
		User user = (User) session
				.getAttribute(GlobalConstants.SESSION_LOGIN_USER_NAME);
		if (user == null) {
			json.put("error", "尚未登录");
			return json;
		}

		List<ModuleDesc> remindDescs = (List<ModuleDesc>) session
				.getAttribute("remindDescs");
		json.put("error", "");
		json.put("remindDescs", remindDescs);
		user.setLoginTime(new Date());
		return json;
	}

OnlineUserRemind.java,这个类是核心实现,负责维护在线用户及其提醒,注意多线程并发问题,中间有个版本出现把未登陆的用户算到在线的用户列表中的问题。多线程的锁机制也得注意,得用当前登陆用户的实例锁。而不是所有在线用户去争这个对象实例的锁。

@Service
public class OnlineUserRemind {

	private static final Logger logger = Logger
			.getLogger(OnlineUserRemind.class);
	@Autowired
	public RemindService remindService;
	@Autowired
	public UserService userService;

	private static final long CHECK_OFFLINE_USER_TIME = 10 * 60 * 1000;

	// private static final long CHECK_OFFLINE_USER_TIME = 10 * 1000;// test
	// time

	private static final long OFFLINE_USER_KICKTIME = 30 * 60 * 1000;

	// private static final long OFFLINE_USER_KICKTIME = 30 * 1000;// test time

	private ConcurrentHashMap<Long, OnlineUser> onlineUsers;

	public OnlineUserRemind() {
		onlineUsers = new ConcurrentHashMap<Long, OnlineUser>();
		new ClearThread().start();
	}

	public void update(User user, int type, int count) {
		synchronized (user) {
			OnlineUser onlineUser = onlineUsers.get(user.getId());
			// 如果user offline不管
			if (onlineUser == null) {
				return;
			}
			onlineUser.updateRemind(type, count);
		}
	}

	public List<ModuleDesc> getRemindDescs(User user) {
		synchronized (user) {
			Long userId = user.getId();
			List<ModuleDesc> remindDescs = new ArrayList<ModuleDesc>();
			OnlineUser onlineUser = onlineUsers.get(user.getId());
			Map<Integer, Remind> reminds = null;
			if (onlineUser == null) {// 只有第一次初始化从数据库读取
				Page<Remind> page = remindService.findAllByUserId(userId, 1,
						Integer.MAX_VALUE);
				List<Remind> _reminds = page.getContent();
				onlineUser = new OnlineUser(user);
				onlineUser.addReminds(_reminds);
				onlineUsers.put(userId, onlineUser);
			}
			reminds = onlineUser.getReminds();
			for (Remind remind : reminds.values()) {
				if (remind.getCount() == 0) {
					continue;
				}
				ModuleDesc remindDesc = ModuleConstants.REMIND_TYPE_DESC_MAP
						.get(remind.getType());
				String desc = remindDesc.getDesc().replace("n",
						remind.getCount() + "");
				String url = remindDesc.getUrl();
				ModuleDesc _remindDesc = new ModuleDesc(desc, url);
				remindDescs.add(_remindDesc);
			}
			return remindDescs;
		}
	}

	private void removeAll(List<Long> userIds) {
		if (userIds.isEmpty()) {
			return;
		}
		for (Long userId : userIds) {
			onlineUsers.remove(userId);
		}
	}

	public Map<Long, OnlineUser> getOnlineUsers() {
		return onlineUsers;
	}

	private class ClearThread extends Thread {
		public void run() {
			while (true) {
				List<Long> offlineUserIds = new ArrayList<Long>();
				for (OnlineUser onlineUser : onlineUsers.values()) {
					User user = onlineUser.getUser();
					Long userId = user.getId();
					Date loginTime = user.getLoginTime();
					if (loginTime == null) {
						continue;
					}
					logger.info("Check user#" + user.getId() + " "
							+ loginTime.toLocaleString() + " is onlining.");
					long now = System.currentTimeMillis();
					long t = (now - loginTime.getTime());
					if (t >= OFFLINE_USER_KICKTIME) {// 移除超时的数据
						offlineUserIds.add(userId);
						logger.info("User#" + user.getId() + " is offline.");
					}
				}
				removeAll(offlineUserIds);
				try {
					Thread.sleep(CHECK_OFFLINE_USER_TIME);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}

UserInterceptor.java的postHandle方法,此处注意是用拦截器的postHandle,而不是preHandle或afterCompletion,用户刷新页面时从cache中获取reminds放到session中

public void postHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		HttpSession session = request.getSession();
		User user = (User) session
				.getAttribute(GlobalConstants.SESSION_LOGIN_USER_NAME);
		if (user == null) {
			return;
		}
		user.setLoginTime(new Date());
		List<ModuleDesc> remindDescs = onlineUserRemind.getRemindDescs(user);
		session.setAttribute("remindDescs", remindDescs);
	}

RemindServiceImpl.java,所有涉及到用户提醒的都调用该方法,该方法会调用OnlineUserRemind来更新mysql和cache,比如评论:remindService.save(sourceUser, ModuleConstants.REMIND_TYPE_COMMENT);

@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public void save(User user, int type) {
		long userId = user.getId();
		Remind remind = null;
		Page<Remind> reminds = findAllByUserIdAndType(userId, type, 1, 1);
		int count = 0;
		if (reminds.getContent().size() > 0) {
			remind = reminds.getContent().get(0);
			count = remind.getCount();
		} else {
			remind = new Remind();
			remind.setType(type);
			remind.setUserId(userId);
		}
		count++;
		remind.setCount(count);
		save(remind);
		// 更新cache
		onlineUserRemind.update(user, type, count);
	}

都是最代码的代码分享,希望对大家有帮助,有问题请留言。

另外不管每个人牛币多少,能力大小,都希望参与到总结分享中来,这个过程也是和其他牛牛学习交流的过程,对所有参与其中的人都是有帮助的,而不是需要牛币的时候才来分享,那样对平台建设无益。


打赏

顶部 客服 微信二维码 底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友