数据库分表实际项目使用

115 阅读3分钟

一、业务场景

统计不同应用,在不同渠道上架后用户的访问量和浏览量。因为数据量很大,所以考虑分表。

数据是我们对接的系统提供,因为我们系统本身没有埋点,不统计用户量。对接的系统,分为两个维度,一个是应用维度,一个是用户维度统计,我们这边只涉及应用维度的数据统计。他们把数据存储到es表内,es也涉及分表,逻辑同数据库分表,过去我们是直接从es取数据,但这样的话两个系统就是强关联关系,需要es相同,现在改成需要es中取的数据由他们系统提供接口,我们再通过网关调用他们的接口获取。

我们自身是把对方提供的数据存到数据库中,下方代码也是展示这些数据存取,因为数据是从其他系统获取,所以由其他系统提供一个数据的接口,我们把这个系统的地址配置到nacos上,通过读取nacos获取接口的地址。

然后我们通过一个定时任务,获取数据,定时任务轮训的时间为30分钟。还预留了一个接口可以自己输入时间获取数据,避免因各种原因造成的获取数据问题

二、实际代码

protected Result doWork(String s) {
    try {
        String today = DateUtil.currDay();
        EsDataByAppService esDataByAppService = SpringUtil.getBean(EsDataByAppService.class);
        esDataByAppService.calEsDataByAppDay(today);

        int hour = LocalDateTime.now().getHour();
        int min = LocalDateTime.now().getMinute();
        //重新统计一遍昨日数据保证数据准确性
        if (SysNameConstant.NUM0 == hour && SysNameConstant.NUM30 >= min) {
            today = DateUtil.befoDay(1);
            esDataByAppService.calEsDataByAppDay(today);
        }
    } catch (Exception e) {
        LOG.error("system error", e);
    }
    return null;
}
/**
 * 计算某天的数据并入库
 *
 * @param time
 */
public void calEsDataByAppDay(String time) {
    try {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM");
        Calendar c = Calendar.getInstance();
        String startTime = time + " 00:00:00";
        String endTime = time + " 23:59:59";
        Date sdate = sdf.parse(startTime);
        Date edate = sdf.parse(endTime);
        int hour = LocalDateTime.now().getHour();
        int min = LocalDateTime.now().getMinute();
        Boolean lasttimes = false;
        LOG.debug(time + " es统计数据入库开始 时间: " + LocalDateTime.now());
        //每天23点30分以后最后一次统计
        if (hour == SysNameConstant.NUM0 && min == SysNameConstant.NUM0) {
            lasttimes = true;
            if ((time.equals(DateUtil.currDay()))) {
                c.setTime(sdate);
                c.add(Calendar.DAY_OF_MONTH, -1);
                sdate = c.getTime();
                c.setTime(edate);
                c.add(Calendar.DAY_OF_MONTH, -1);
                edate = c.getTime();
            }
        } else if (hour == StatisticsConstant.NUM23 && min > StatisticsConstant.NUM30) {
            lasttimes = true;
        } else {
            LOG.info("********");
        }

        int year = DateUtil.getYear(sdate);
        int month = DateUtil.getMonth(sdate);
        String tableName;
        if (month <= SysNameConstant.NUM6) {
            tableName = year + "_1";
        } else {
            tableName = year + "_2";
        }

        addDateByApp(sdate, edate, startTime, tableName);
        //最后一次更新当天数据的同时间更新当月和当年数据
        if (lasttimes) {
            this.calibrationStatisticsMonth(sdf2.format(sdate));
            this.calibrationStatisticsYear(String.valueOf(year));
        }
        LOG.debug(time + " es统计数据入库结束");
    } catch (Exception e) {
        LOG.error("服务器错误!", e);
    }
}
public void addDateByApp(Date start, Date end, String startTime, String tableName) {
    String interfaceAddress = properties.getInterfaceAddress();
    String url = interfaceAddress + "/interface/findapptodate";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    HttpSender httpSender = null;
    List<RegionAndChannelAndTerminal> findAppAccessByConditions = new ArrayList<>(Constants.MAP_INIT_SIZE);
    try{
        httpSender = HttpSender.getInstancePost(url);
        httpSender.addParam("starttime", sdf.format(start));
        httpSender.addParam("endtime", sdf.format(end));
        httpSender.execute();
        String res = httpSender.getResult();
        JSONObject jsonObject = JSON.parseObject(res);
        JSONObject jsonObjectData = jsonObject.getJSONObject(SystemConstant.DATA);
        if(jsonObjectData != null){
            JSONArray jsonArray = jsonObjectData.getJSONArray("result");
            if(jsonArray != null){
                for (int i = 0; i < jsonArray.size(); i++) {
                    JSONObject jsonObject2 = jsonArray.getJSONObject(i);
                    RegionAndChannelAndTerminal customObject = JSON.toJavaObject(jsonObject2, RegionAndChannelAndTerminal.class);
                    findAppAccessByConditions.add(customObject);
                }
            }
        }
    }catch (Exception e){
        LOG.error("调用获取数据接口失败" + e.getMessage(), e);
    }finally {
        if (httpSender != null) {
            httpSender.close();
        }
    }
    if (CollectionUtils.isNotEmpty(findAppAccessByConditions)) {
        //删除旧数据
        esDateByAppDao.removeDayData(startTime, tableName);
        for (RegionAndChannelAndTerminal appModuleEs : findAppAccessByConditions) {
            EsDateByApp esDateByApp = new EsDateByApp();
            esDateByApp.setAppid(appModuleEs.getAppid());
            esDateByApp.setAppname(appModuleEs.getAppName());
            esDateByApp.setPv(appModuleEs.getPv());
            esDateByApp.setUv(appModuleEs.getUv());
            esDateByApp.setKeyvalue(appModuleEs.getKeyValue());
            esDateByApp.setShowtime(startTime);
            esDateByApp.setChannel(appModuleEs.getChannel());
            esDateByAppDao.saveDateData(esDateByApp, tableName);
        }
    }
}

三、查询时分表思路

天表按年份月份分表,如2023年前6个月,table2023_1,后六个月table2023_2。

删除旧数据前先判断表是否存在,如果表存在则根据时间和表名删除数据。

存储数据前同样先判断表是否存在,不存在则先创建表,再插入数据。

年表,月表没有分表,在每天最后的一次数据统计的时候,先分组查询数据库天表中的按当年或者当月统计的数据,再删除年表,月表中当年,当月的数据,最后插入。

至于取数据,我们设计是查询时间能任意选,但结束时间-开始时间只能是一年内,因为一年就是三张表联查了,同样我会根据传入时间进行区分,得到年份月份,拼成表,然后判断表是否存在,存在的表则拼接成sql去查询,整体逻辑就是这样。