Java高并发实战:利用线程池和Redis实现高效数据入库

简介: Java高并发实战:利用线程池和Redis实现高效数据入库

Java高并发实战:利用线程池和Redis实现高效数据入库

在高并发环境下进行数据入库是一项具有挑战性的任务。为了保证系统的性能和稳定性,可以利用线程池和Redis来实现数据的实时缓存和批量入库处理。本文将介绍一个具体实现,该实现能够根据设定的超时时间和最大批次处理数据入库。

主要思路

  • 实时数据缓存:接收到的数据首先存入Redis,保证数据的实时性。
  • 批量数据入库:当达到设定的超时时间或最大批次数量时,批量将数据从Redis中取出并入库。


主要组件

  • BatchDataStorageService:核心服务类,负责数据的缓存和批量入库。
  • CacheService:缓存服务类,使用Java的ConcurrentHashMap实现简易缓存。
  • RedisUtils:Redis工具类,用于数据的缓存。
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;

  /**
  * 获取当前机器的核数
  */
  public static final int cpuNum = Runtime.getRuntime().availableProcessors();

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

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

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

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

  @Override
  public void afterPropertiesSet() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    // 核心线程大小
        taskExecutor.setCorePoolSize(cpuNum);
        // 最大线程大小
        taskExecutor.setMaxPoolSize(cpuNum * 2);
        // 队列最大容量
        taskExecutor.setQueueCapacity(500);
        // 当提交的任务个数大于QueueCapacity,就需要设置该参数,但spring提供的都不太满足业务场景,可以自定义一个,也可以注意不要超过QueueCapacity即可
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        taskExecutor.setThreadFactory(r -> {
            Thread thread = new Thread(r);
            if (r instanceof BatchWorker) {
                thread.setName("batch-worker-" + ((BatchWorker) r).batchKey);
            });
        taskExecutor.initialize();
        executorService = taskExecutor.getThreadPoolExecutor();
  }

  /**
   * 需要做高并发处理的类只需要调用该方法 (我用的是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() {

    }
}

目录
相关文章
|
7月前
|
Java 索引
多线程向设备发送数据
多线程向设备发送数据
123 0
|
5月前
|
存储 NoSQL 前端开发
Redis专题-实战篇一-基于Session和Redis实现登录业务
本项目基于SpringBoot实现黑马点评系统,涵盖Session与Redis两种登录方案。通过验证码登录、用户信息存储、拦截器校验等流程,解决集群环境下Session不共享问题,采用Redis替代Session实现数据共享与自动续期,提升系统可扩展性与安全性。
334 3
Redis专题-实战篇一-基于Session和Redis实现登录业务
|
5月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
268 1
Redis专题-实战篇二-商户查询缓存
|
5月前
|
存储 SQL NoSQL
Redis-常用语法以及java互联实践案例
本文详细介绍了Redis的数据结构、常用命令及其Java客户端的使用,涵盖String、Hash、List、Set、SortedSet等数据类型及操作,同时提供了Jedis和Spring Boot Data Redis的实战示例,帮助开发者快速掌握Redis在实际项目中的应用。
379 1
Redis-常用语法以及java互联实践案例
|
8月前
|
缓存 监控 NoSQL
Redis 实操要点:Java 最新技术栈的实战解析
本文介绍了基于Spring Boot 3、Redis 7和Lettuce客户端的Redis高级应用实践。内容包括:1)现代Java项目集成Redis的配置方法;2)使用Redisson实现分布式可重入锁与公平锁;3)缓存模式解决方案,包括布隆过滤器防穿透和随机过期时间防雪崩;4)Redis数据结构的高级应用,如HyperLogLog统计UV和GeoHash处理地理位置。文章提供了详细的代码示例,涵盖Redis在分布式系统中的核心应用场景,特别适合需要处理高并发、分布式锁等问题的开发场景。
523 41
|
8月前
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
437 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
|
8月前
|
机器学习/深度学习 存储 NoSQL
基于 Flink + Redis 的实时特征工程实战:电商场景动态分桶计数实现
本文介绍了基于 Flink 与 Redis 构建的电商场景下实时特征工程解决方案,重点实现动态分桶计数等复杂特征计算。通过流处理引擎 Flink 实时加工用户行为数据,结合 Redis 高性能存储,满足推荐系统毫秒级特征更新需求。技术架构涵盖状态管理、窗口计算、Redis 数据模型设计及特征服务集成,有效提升模型预测效果与系统吞吐能力。
887 10
|
8月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
2265 7
|
7月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
8月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
443 6