java高并发下数据入库

java高并发下数据入库java高并发下数据批量入库该服务利用线程池并结合缓存类来处理高并发下数据入库问题,做到实时数据存入redis和数据批量入库,使用的时候需要修改为自己的业务数据,该服务暂时适合下面两种情况:1、达到设置的超时时间。2、达到最大批次。packageio.renren.service.impl;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONArray;importlombok.extern.slf4j.Slf

大家好,又见面了,我是你们的朋友全栈君。

java高并发下数据入库

该服务利用线程池并结合缓存类来处理高并发下数据入库问题,做到实时数据存入redis和数据批量入库,使用的时候需要修改为自己的业务数据,该模块是根据下面的设置进行高并发处理。
1、达到设置的超时时间。
2、达到最大批次。

package io.jack.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/** * <pre> * 数据批量入库服务 * </pre> * Created by RuiXing Hou on 2021-08-05. * * @since 1.0 */
@Component
@Slf4j
public class BatchDataStorageService implements InitializingBean
{ 
   
	/** * 最大批次数量 */
	@Value("${app.db.maxBatchCount:800}")
    private int maxBatchCount;

	/** * 最大线程数 */
    @Value("${app.db.maxBatchThreads:100}")
    private int maxBatchThreads;

	/** * 超时时间 */
	@Value("${app.db.batchTimeout:3000}")
    private int batchTimeout;

	/** * 批次数量 */
    private int batchCount = 0;

	/** * 批次号 */
	private static long batchNo = 0;

	/** * 线程池定义接口 */
    private ExecutorService executorService = null;

	/** * 服务器缓存工具类,下面提供源码 */
	@Resource
	private CacheService cacheService;

	/** * 业务接口 */
	@Resource
	private DeviceRealTimeService deviceRealTimeService;

	/** * redis工具类 */
	@Resource
	private RedisUtils redisUtils;

	@Override
	public void afterPropertiesSet() { 
   
		this.executorService = Executors.newFixedThreadPool(this.maxBatchThreads, r -> { 
   
			Thread thread = new Thread(r);
			if (r instanceof BatchWorker) { 
   
				thread.setName("batch-worker-" + ((BatchWorker) r).batchKey);
			}
			return thread;
		});
	}

	/** * 需要做高并发处理的类只需要调用该方法 (我用的是rabbitMq) * * @param deviceRealTimeDTO */
	public void saveRealTimeData(DeviceRealTimeDTO deviceRealTimeDTO) { 
   
		final String failedCacheKey = "device:real_time:failed_records";

		try { 
   

			String durationKey = "device:real_time:batchDuration" + batchNo;
			String batchKey = "device:real_time:batch" + batchNo;

			if (!cacheService.exists(durationKey)) { 
   
				cacheService.put(durationKey, System.currentTimeMillis());
				new BatchTimeoutCommitThread(batchKey, durationKey, failedCacheKey).start();
			}

			cacheService.lPush(batchKey, deviceRealTimeDTO);
			if (++batchCount >= maxBatchCount) { 
   
				// 达到最大批次,执行入库逻辑
				dataStorage(durationKey, batchKey, failedCacheKey);
			}

		} catch (Exception ex) { 
   
			log.warn("[DB:FAILED] 设备上报记录入批处理集合异常: " + ex.getMessage() + ", DeviceRealTimeDTO: " + JSON.toJSONString(deviceRealTimeDTO), ex);
			cacheService.lPush(failedCacheKey, deviceRealTimeDTO);
		} finally { 
   
			updateRealTimeData(deviceRealTimeDTO);
		}
	}

	/** * 更新实时数据 * @param deviceRealTimeDTO 业务POJO */
	private void updateRealTimeData(DeviceRealTimeDTO deviceRealTimeDTO) { 
   
		redisUtils.set("real_time:"+deviceRealTimeDTO.getDeviceId(), JSONArray.toJSONString(deviceRealTimeDTO));
	}

	/** * * @param durationKey 持续时间标识 * @param batchKey 批次标识 * @param failedCacheKey 错误标识 */
	private void dataStorage(String durationKey, String batchKey, String failedCacheKey) { 
   
		batchNo++;
		batchCount = 0;
		cacheService.del(durationKey);
		if (batchNo >= Long.MAX_VALUE) { 
   
			batchNo = 0;
		}
		executorService.execute(new BatchWorker(batchKey, failedCacheKey));
	}

	private class BatchWorker implements Runnable
	{ 
   

		private final String failedCacheKey;
		private final String batchKey;

		public BatchWorker(String batchKey, String failedCacheKey) { 
   
			this.batchKey = batchKey;
			this.failedCacheKey = failedCacheKey;
		}
		
		@Override
		public void run() { 
   
			final List<DeviceRealTimeDTO> deviceRealTimeDTOList = new ArrayList<>();
			try { 
   
				DeviceRealTimeDTO deviceRealTimeDTO = cacheService.lPop(batchKey);
				while(deviceRealTimeDTO != null) { 
   
					deviceRealTimeDTOList.add(deviceRealTimeDTO);
					deviceRealTimeDTO = cacheService.lPop(batchKey);
				}

				long timeMillis = System.currentTimeMillis();

				try { 
   
					List<DeviceRealTimeEntity> deviceRealTimeEntityList = ConvertUtils.sourceToTarget(deviceRealTimeDTOList, DeviceRealTimeEntity.class);
					deviceRealTimeService.insertBatch(deviceRealTimeEntityList);
				} finally { 
   
					cacheService.del(batchKey);
					log.info("[DB:BATCH_WORKER] 批次:" + batchKey + ",保存设备上报记录数:" + deviceRealTimeDTOList.size() + ", 耗时:" + (System.currentTimeMillis() - timeMillis) + "ms");
				}
			} catch (Exception e) { 
   
				log.warn("[DB:FAILED] 设备上报记录批量入库失败:" + e.getMessage() + ", DeviceRealTimeDTO: " + deviceRealTimeDTOList.size(), e);
				for (DeviceRealTimeDTO deviceRealTimeDTO : deviceRealTimeDTOList) { 
   
					cacheService.lPush(failedCacheKey, deviceRealTimeDTO);
				}
			}
		}
    }

	class BatchTimeoutCommitThread extends Thread { 
   

		private final String batchKey;
		private final String durationKey;
		private final String failedCacheKey;

		public BatchTimeoutCommitThread(String batchKey, String durationKey, String failedCacheKey) { 
   
			this.batchKey = batchKey;
			this.durationKey = durationKey;
			this.failedCacheKey = failedCacheKey;
			this.setName("batch-thread-" + batchKey);
		}

		public void run() { 
   
			try { 
   
				Thread.sleep(batchTimeout);
			} catch (InterruptedException e) { 
   
				log.error("[DB] 内部错误,直接提交:" + e.getMessage());
			}

			if (cacheService.exists(durationKey)) { 
   
				// 达到最大批次的超时间,执行入库逻辑
				dataStorage(durationKey, batchKey, failedCacheKey);
			}
		}

	}

}

package io.jack.service;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Component
@Scope("singleton")
public class CacheService implements InitializingBean { 
   

    private Map<String, Object> objectCache = new ConcurrentHashMap<>();

    private Map<String, AtomicLong> statCache = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() { 
   
        statCache.put("terminals", new AtomicLong(0));
        statCache.put("connections", new AtomicLong(0));
    }

    public long incr(String statName) { 
   
        if (!statCache.containsKey(statName))
            statCache.put(statName, new AtomicLong(0));
        return statCache.get(statName).incrementAndGet();
    }

    public long decr(String statName) { 
   
        if (!statCache.containsKey(statName))
            statCache.put(statName, new AtomicLong(0));
        return statCache.get(statName).decrementAndGet();
    }

    public long stat(String statName) { 
   
        if (!statCache.containsKey(statName))
            statCache.put(statName, new AtomicLong(0));
        return statCache.get(statName).get();
    }

    public <T> void put(String key, T object) { 
   
        objectCache.put(key, object);
    }

    public <T> T get(String key) { 
   
        return (T) objectCache.get(key);
    }

    public void remove(String key) { 
   
        objectCache.remove(key);
    }

    public void hSet(String key, String subkey, Object value) { 
   
        synchronized (objectCache) { 
   
            HashMap<String, Object> submap = (HashMap<String, Object>) objectCache.get(key);
            if (submap == null) { 
   
                submap = new HashMap<>();
                objectCache.put(key, submap);
            }
            submap.put(subkey, value);
        }
    }

    public <T> T hGet(String key, String subkey) { 
   
        synchronized (objectCache) { 
   
            HashMap<String, Object> submap = (HashMap<String, Object>) objectCache.get(key);
            if (submap != null) { 
   
                return (T) submap.get(subkey);
            }
            return null;
        }
    }

    public boolean hExists(String key, String subkey) { 
   
        synchronized (objectCache) { 
   
            HashMap<String, Object> submap = (HashMap<String, Object>) objectCache.get(key);
            if (submap != null) { 
   
                return submap.containsKey(subkey);
            }
            return false;
        }
    }

    public void lPush(String key, Object value) { 
   
        synchronized (objectCache) { 
   
            LinkedList queue = (LinkedList) objectCache.get (key);
            if (queue == null) { 
   
                queue = new LinkedList();
                objectCache.put(key, queue);
            }
            queue.addLast(value);
        }
    }

    public <T> T lPop(String key) { 
   
        synchronized (objectCache) { 
   
            LinkedList queue = (LinkedList) objectCache.get (key);
            if (queue != null) { 
   
                if (!queue.isEmpty()) { 
   
                    return (T)queue.removeLast();
                }
                objectCache.remove(key);
            }
            return null;
        }
    }

    public void del(String key) { 
   
        objectCache.remove(key);
    }

    public boolean exists(String key) { 
   
        return objectCache.containsKey(key);
    }

    public void dump() { 
   

    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/144596.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • php 字符串 替换 最后,如何替换php字符串中最后一个字符

    php 字符串 替换 最后,如何替换php字符串中最后一个字符如何替换php字符串中最后一个字符发布时间:2020-08-1010:36:23来源:亿速云阅读:91作者:Leah这篇文章运用简单易懂的例子给大家介绍如何替换php字符串中最后一个字符,代码非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。php替换字符串最后一个字符的方法:首先使用PHP中的“substr”函数或者“mb_substr”截取字符串至倒数第一位;然后拼接自己想要的数…

    2022年5月23日
    35
  • latex 希腊字母加粗_mathtype公式取消加粗

    latex 希腊字母加粗_mathtype公式取消加粗在编辑公式时,当使用\mathbf{\sigma}时,\mathbf{}不起作用?【解决方案】方案一、用\usepackage{amsmath}\boldsymbol{\sigma}\mathbf只对公式中的普通字母ABC…abcdef等起作用。方案二、更好的方法是使用\usepackage{bm}\bm{}来加粗。…

    2022年10月13日
    2
  • android 联系人中,在超大字体下,加入至联系人界面(ConfirmAddDetailActivity)上有字体显示不全的问题…

    android 联系人中,在超大字体下,加入至联系人界面(ConfirmAddDetailActivity)上有字体显示不全的问题…

    2022年3月3日
    66
  • cnpm安装与vue安装[通俗易懂]

    cnpm安装与vue安装[通俗易懂]首先是cnpm安装,在安装之前首先确定安装好了nodejs并且配置好了环境终端cmd后输入npm查看版本确定结果正确后进行cnpm安装终端输入npminstall-gcnpm–registry=https://registry.npm.taobao.org这里安装的是淘宝的镜像,等待安装完成后输入cnpm-v查看版本如果出现不是命令检查你的path路径确定安装成功后,终端键入:cnpminstallvue等待安装完成键入vue-V查看安装是否成功默认安装

    2022年10月15日
    3
  • php与dreamweaver基础教程,Dreamweaver基础教程 基础技巧全面接触

    php与dreamweaver基础教程,Dreamweaver基础教程 基础技巧全面接触三、内容篇如何更快更方便得对内容进行组织,是每个朋友都想了解的,下面这几个技巧也许你知道,也许不知道,但重要的是通过不断的运用,你会发现它的优越性的。1、快速标签编辑。对于熟悉手写代码的朋友来说,经常需要切换到代码窗口手工添加一些代码。其实利用Dreamweaver的QuickTagEditor可以快速插入各种HTML标签,一个是点击属性面板的图标插入,另一种快捷方式是Ctrl+T,这两种方…

    2022年4月30日
    38
  • Java中数字的四舍五入和取整

    Java中数字的四舍五入和取整Java中对数字进行四舍五入或取整处理经常使用Math库中的三个方法:ceilfloorround1ceil向上取整ceil英文释义:天花板。天花板在上面,所以是向上取整,好记了。Math.ceil函数接收一个double类型的参数,用于对数字进行向上取整(遇小数进1),即返回一个大于或等于传入参数的最小整数(但还是以double类型返回)。2floor向下取整floor英文释义:地板。地板在下面,所以是向下取整,好记了。Math.floor函数接收一个double

    2022年7月7日
    22

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号