Java基于队列和数据库批量维护用户在线离线状态和位置信息

目录

前言介绍

用户数据迁移

用户状态数据批量处理

队列数据批量消费


前言介绍

系统用户实时位置都保存在redis中,我们采用redis过期方式来监听用户位置离线,离线的用户key会放入对应的队列进行消费(这里的队列可以是Java队列也可以是RabbitMQ之类的消息中间件,我们采用的是ConcurrentLinkedQueue)。项目中对用户实时位置有两个要求(特殊行业对人员位置安全规范的要求):

  • a.用户最后一次经纬度实时位置更新到数据库
  • b.用户离线后更新用户在线状态

为了解决每次用户位置心跳更新导致数据库连接不足问题,我采用了批量更新的方式来解决大批量的用户上线和离线更新。解决了如下问题:

  • a.解决用户状态表统计数据不一致问题
  • b.解决数据库更新占用数据库连接过多问题(可实现批量更新上线状态位置和离线状态,最后一次位置保存更新降低数据库压力)
  • c.解决redis和database在线状态延迟过大问题

用户数据迁移

针对需要维护最后用户在线位置数据的这类用户,我们有单独用户角色加以限定。这种用户只需要简单将数据做个分表保存起来就可以了。首次初始化这类数据是根据已有用户数据直接采用SQL 支持的SELECT [A,B,....] INTO TABLE_SUB FROM TABLE_MAIN 

示例脚本如下:

-- 注意:======依赖触发器更新用户信息=====
--  修改目的:
--  a.解决用户状态表格统计数据不一致问题
--  b.解决数据库更新占用数据库连接过多问题(可实现批量更新上线状态位置和离线状态,最后一次位置保存更新降低数据库压力)
--  c.解决redis和database在线状态延迟过大问题
-- 可反复执行如下表数据记录
DROP TABLE if exists xh_yw.xh_user_online_tb ;

SELECT
  -- 组织机构ID
  i_orgid,
  -- 组织机构编号
	c_orgbh,
	-- 组织机构名称
	c_orgname,
	-- 用户ID
	i_userid,
	-- 用户姓名
	c_userealname,
	-- 手机号码
	c_usertel,
	-- 时间戳
  make_timestamp(2020,1,1,0,0,0) AS lasttime,
  -- 最后位置:经度
  0.0 AS longitude,
  -- 最后位置:纬度
	0.0 AS latitude,
	-- 是否在线: 0 离线  1 在线
	0 AS is_online ,
	-- 日期make_date(2020,1,1) AS last_date
	'2020-01-01' AS last_date

INTO xh_yw.xh_user_online_tb

FROM xh_ht.fs_yw_base_user where i_userid
-- 角色过滤
IN (select  distinct i_userid from xh_ht.fs_yw_user_role where i_roleid = 5 )

用户状态数据批量处理

批量处理都依赖数据库支持的方式。

MySQL参考:https://www.cnblogs.com/mslagee/p/6509682.html

Postgresql参考:https://www.itranslater.com/qa/details/2583251656280376320

Java代码示例:

package com.patrol.position.service;

import com.alibaba.fastjson.JSONArray;
import com.forestar.platform.dao.DatabaseRepository;
import com.patrol.beans.Constants;
import com.patrol.beans.user.UserPosition;
import com.patrol.beans.util.LogicUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @Copyright: 2019-2021
 * @FileName: UserOnlineService.java
 * @Author: PJL
 * @Date: 2020/7/15 19:37
 * @Description: 用户在线中间数据表服务【通过队列方式批量更新】
 */
@Slf4j
@Service
public class UserOnlineService {

    /**
     * Redis查询工具模板类
     */
    @Qualifier("redisTemplateByLettuce")
    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    DatabaseRepository databaseRepository;

    SimpleDateFormat sdfTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * 用户在线位置中间表
     */
    final String XH_USER_ONLINE_TB = "XH_USER_ONLINE_TB";

    /**
     * 解析临时表数据多列值
     *
     * @param list
     * @return
     */
    private List parseMultiParams(List<UserPosition> list) {
        List result = new ArrayList();
        int count = list.size();
        Integer[] userArray = new Integer[count];
        String[] lastTimeArray = new String[count];
        String[] lastDateArray = new String[count];
        Double[] longitudeArray = new Double[count];
        Double[] latitudeArray = new Double[count];
        UserPosition userPosition;
        Date date;
        for (int i = 0; i < count; i++) {
            userPosition = list.get(i);
            if (null != userPosition) {
                date = new Date(userPosition.getTimestamp());
                userArray[i] = Integer.valueOf(userPosition.getUserId());
                lastTimeArray[i] = new StringBuffer("date_trunc('second', TIMESTAMP '").append(sdfTime.format(date)).append("')").toString();
                String[] dateValues = sdfDate.format(date).split("-");
                lastDateArray[i] = new StringBuffer("make_date(").append(dateValues[0]).append(",").append(dateValues[1]).append(",").append(dateValues[2]).append(")").toString();
                longitudeArray[i] = userPosition.getPosition()[0];
                latitudeArray[i] = userPosition.getPosition()[1];
            }
        }
        result.add(userArray);
        result.add(lastTimeArray);
        result.add(lastDateArray);
        result.add(longitudeArray);
        result.add(latitudeArray);
        return result;
    }


    /**
     * 批量更新用户上线状态表
     *
     * @param list
     */
    public void batchOnline(List<UserPosition> list) {
        if (ObjectUtils.isNotEmpty(list)) {
            List paramList = this.parseMultiParams(list);
            String userIds = JSONArray.toJSONString(paramList.get(0));
            StringBuffer lastTimes = new StringBuffer();
            String[] timeList = (String[]) paramList.get(1);
            for (String s : timeList) {
                if (lastTimes.length() == 0) {
                    lastTimes.append(s);
                } else {
                    lastTimes.append(",").append(s);
                }
            }
            String[] dateList = (String[]) paramList.get(2);
            StringBuffer lastDates = new StringBuffer();
            for (String s : dateList) {
                if (lastDates.length() == 0) {
                    lastDates.append(s);
                } else {
                    lastDates.append(",").append(s);
                }
            }
            String longitudes = JSONArray.toJSONString(paramList.get(3));
            String latitudes = JSONArray.toJSONString(paramList.get(4));
            StringBuffer sb = new StringBuffer(" UPDATE ")
                    .append(Constants.DB_YW_TABLE_SPACE).append(XH_USER_ONLINE_TB).append(" a ")
                    .append(" SET ")
                    .append(" LASTTIME = u.LASTTIME,")
                    .append(" LAST_DATE = u.LAST_DATE,")
                    .append(" LONGITUDE = u.LONGITUDE,")
                    .append(" LATITUDE = u.LATITUDE,")
                    .append(" IS_ONLINE = 1 ")
                    .append(" FROM ( SELECT ")
                    .append(" unnest(array").append(userIds).append(") ").append(" as I_USERID,")
                    .append(" unnest(array[").append(lastTimes).append("]) ").append(" as LASTTIME,")
                    .append(" unnest(array[").append(lastDates).append("]) ").append(" as LAST_DATE,")
                    .append(" unnest(array").append(longitudes).append(") ").append(" as LONGITUDE,")
                    .append(" unnest(array").append(latitudes).append(") ").append(" as LATITUDE")
                    .append("  ) as u ")
                    .append(" WHERE a.I_USERID = u.I_USERID ");
            jdbcTemplate.execute(sb.toString());
        }

    }

    /**
     * 用户离线状态修改(0:离线  1:在线)
     *
     * @param userIdList
     */
    public void updateUserOffline(List<String> userIdList) {
        String[] userIds = new String[userIdList.size()];
        userIds = userIdList.toArray(userIds);
        String filter = LogicUtil.getOrgFilterString(userIds);
        String sql = new StringBuffer(" UPDATE ").append(XH_USER_ONLINE_TB).append(" SET IS_ONLINE = 0 WHERE I_USERID IN (").append(filter).append(")").toString();
        databaseRepository.execute(XH_USER_ONLINE_TB, sql);
    }
}

注意:Postgresql函数日期和时间戳需要特殊处理。

队列数据批量消费

用户在线离线只需要两个队列就可以区分开处理了。

package com.patrol.position.queue;

import com.patrol.beans.user.UserPosition;
import com.patrol.config.condition.ServerCondition;
import com.patrol.position.service.UserOnlineService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @Copyright: 2019-2021
 * @FileName: UpdateStatusOnlineQueue.java
 * @Author: PJL
 * @Date: 2020/8/20 11:03
 * @Description: 在线离线状态列表数据更新队列
 */
@Slf4j
@Component
public class UpdateStatusQueue {

    @Autowired
    UserOnlineService userOnlineService;

    /**
     * 并发链表队列--在线位置队列
     */
    private static final ConcurrentLinkedQueue<UserPosition> onlineQueue = new ConcurrentLinkedQueue<>();

    /**
     * 并发链表队列--离线位置队列
     */
    private static final ConcurrentLinkedQueue<String> offlineQueue = new ConcurrentLinkedQueue<>();

    /**
     * 消费用户上线、离线下线任务
     */
    @PostConstruct
    private void consumeUserOnlineStatusQueue() {
        if (ServerCondition.isServer) {
            log.info(">>>>>>>>启动服务端消费线程....");
            /***********用户上线批量消费***********/
            this.userOnline();

            /***********用户离线状态批量消费***********/
            this.userOffline();

            log.info(">>>>>>>>启动服务端消费线程....完毕!");
        }
    }


    /**
     * 添加在线用户
     *
     * @param userPosition
     */
    public static void addToOnlineQueue(UserPosition userPosition) {
        if (ServerCondition.isServer) {
            onlineQueue.add(userPosition);
        }
    }

    /**
     * 添加离线用户
     *
     * @param userId
     */
    public static void addToOfflineQueue(String userId) {
        if (ServerCondition.isServer) {
            offlineQueue.add(userId);
        }
    }

    /**
     * 用户上线更新最后一次上线位置批量处理
     */
    private void userOnline() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    int size = onlineQueue.size();
                    if (size > 0) {
                        List<UserPosition> list = new ArrayList<>();
                        UserPosition userPosition;
                        for (int i = 0; i < size; i++) {
                            userPosition = onlineQueue.poll();
                            if (null != userPosition) {
                                list.add(userPosition);
                            }
                        }
                        if (ObjectUtils.isNotEmpty(list)) {
                            // 批量上线
                            userOnlineService.batchOnline(list);
                        }
                    }
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    /**
     * 更新用户在线状态表为离线
     */
    private void userOffline() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    int size = offlineQueue.size();
                    if (size > 0) {
                        List<String> list = new ArrayList<>();
                        String userId;
                        for (int i = 0; i < size; i++) {
                            userId = offlineQueue.poll();
                            if (null != userId && StringUtils.isNotEmpty(userId)) {
                                list.add(userId);
                            }
                        }
                        if (ObjectUtils.isNotEmpty(list)) {
                            // 批量离线
                            userOnlineService.updateUserOffline(list);
                        }
                    }
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

这里开了两个线程处理,根据用户规模可以改为多线程并发消费(但是请注意控制数据库连接)。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 要下载Java 8的离线安装包,可以按照以下步骤进行操作: 1. 打开Java官方网站,进入Java下载页面。 2. 在Java下载页面上,找到Java 8的下载链接,并点击进入。 3. 在Java 8的下载页面上,找到适合您操作系统的版本。通常会有Windows、Mac、Linux等版本。 4. 在所选操作系统的版本下方,可以看到两个选项,一个是在线安装程序,另一个是离线安装包。选择离线安装包选项。 5. 点击离线安装包选项后,会弹出一个文件下载对话框。选择保存文件的位置,并点击“保存”按钮。 6. 下载完成后,找到保存的文件,并双击运行安装包。 7. 按照安装向导的提示,逐步进行安装。通常会有许可协议等需要同意。 8. 安装完成后,Java 8就已成功安装在您的计算机上了。 这样,您就可以在离线状态下安装Java 8了。记得在安装前检查您的操作系统和Java版本的兼容性,以确保安装顺利进行并达到预期的效果。 ### 回答2: 要获取Java 8的离线安装包,可以按照以下步骤操作: 1. 打开Java官方网站。你可以通过搜索引擎搜索“Java官方网站”来找到它。 2. 在官方网站上,定位到Java 8的下载页面。你可以尝试搜索“Java 8下载”或浏览网站上的菜单和链接以找到下载页面。 3. 在Java 8下载页面上,找到适合你操作系统的安装包。Java 8支持多个操作系统(例如Windows,Mac和Linux),所以请确保选择适合你的操作系统的安装包。 4. 在安装包附近查找“离线安装”或“离线版本”的链接。这个链接可能位于安装包的旁边,或者在页面的顶部或底部。点击该链接以下载离线安装包。 5. 选择一个下载链接并点击它开始下载。这个过程可能需要一些时间,具体取决于你的互联网连接速度。 6. 下载完成后,你可以将这个离线安装包保存在本地文件夹中,以备将来使用。 注意:Java的官方网站可能会根据时期和地区的不同而有所改动,所以保持连接的最新性是很重要的,以确保你能找到准确的下载页面和离线安装包。 ### 回答3: 要下载Java 8的离线安装包,你可以按照以下步骤进行操作。 1. 打开Oracle官方网站:www.oracle.com。 2. 在网站的顶部导航栏中找到"Downloads"(下载)选项,并点击进入。 3. 在"Java Platform (JDK) - Java SE"部分,点击 "Java SE"。 4. 滚动页面直到你找到Java SE 8的版本,点击它。 5. 在页面中,你会看到不同的下载选项。找到适合你操作系统的版本,并点击对应的下载链接。 6. 接下来,你可能需要点击"Accept License Agreement"(接受许可协议)方框以继续。 7. 选择下载适合你操作系统的文件。如果你使用的是Windows平台,通常会有一个exe文件可供下载。 8. 一旦下载完成,你可以将下载的文件保存到你的电脑上的任何地方。 9. 离线安装包可以运行而无需互联网连接,因此你可以使用它在没有网络的环境中安装Java 8。 记住,Java官方网站上提供的下载链接可能会有所变化,因此在下载前最好再次检查以确保链接的准确性。另外,你还可以在其他可靠的下载网站上寻找Java 8的离线安装包。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值