多线程上传图片(windows到服务器以及服务器到外部存储系统)_通过多线程发送10000张图片-程序员宅基地

技术标签: java  多线程  rabbitmq  redis  

场景:从磁盘某个地方获取文件然后上传到服务器,或者从服务器上传到外部系统

 考虑因素:网络因素,主要是网络带宽(实际环境遇到过,因为服务部署在不同省份)

单线程: 按一秒一张每天传递:24*60*60*N(N为机器数); 服务端带宽大的情况下配置为多线程

具体QPS设计,需要结合网络情况

另外考虑到windows电脑做成服务启动应用,具体可以参考github的项目:Releases · winsw/winsw · GitHubA wrapper executable that can run any executable as a Windows service, in a permissive license. - Releases · winsw/winswhttps://github.com/winsw/winsw/releases

win+R进入:services.msc 可查看最终部署的应用

 

具体操作见最后面

设计思路:队列+多线程

其他方式:redis队列或者mq方式实现,主体思路消费队列

需要注意的是:生产者生产的速度控制,在生产和消费之间找到一个对应的平衡点

适用于一个生产者多个消费者模式,主要是在消费瓶颈上

最终实现思路,文章代码待完善(阉割版),实际项目中已实现设计图内容

临时map的作用是把上传文件后将本地文件删除,避免删除不掉本地文件的情况(此处考虑过一段时间得到最终处理方案)

监控中心作用:定期将未从临时map中清除的文件清理掉,防止意外情况,正确情况下不会存在此情况发生

另外:实际项目中,本地到远程服务再到远程服务,中间搭建了一个中继服务

具体可参考模式:本地项目-----中继服务------远程服务器

maven依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

工程结构图

 

1.工具类获取bean

/**
 * description: 获取bean
 */
@Component
public class SpringBeanUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        applicationContext = context;
    }

    /**
     * 通过beanName获取bean
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

2.静态数据中心

/**
 * description:
 */
@Component
public class MonitorThread {


    @Value("${parent_file_path}")
    private String parent_file_path;

    @Value("${max_thread}")
    private int max_thread;

    @PostConstruct
    public void init() {
        MiddleUtils.setParent_file_path(parent_file_path);
        MiddleUtils.setMax_thread(max_thread);
        new ImageProducer().start();
        new ImageMonitorThread().start();
    }

}
@Component
public class MiddleUtils {

    private static volatile ConcurrentLinkedQueue<String> CONSUMER_FILE = new ConcurrentLinkedQueue<>(); //文件消费队列
    private static volatile ConcurrentHashMap<String, Long> FILE_MAP = new ConcurrentHashMap<>(); //计数器

    private static String parent_file_path = null;

    private static int max_thread = 1;

    public static int getMax_thread() {
        return max_thread;
    }

    public static void setMax_thread(int max_thread) {
        MiddleUtils.max_thread = max_thread;
    }

    public static ConcurrentLinkedQueue<String> getConsumerFile() {
        return CONSUMER_FILE;
    }

    public static void setConsumerFile(ConcurrentLinkedQueue<String> consumerFile) {
        CONSUMER_FILE = consumerFile;
    }

    public static ConcurrentHashMap<String, Long> getFileMap() {
        return FILE_MAP;
    }

    public static void setFileMap(ConcurrentHashMap<String, Long> fileMap) {
        FILE_MAP = fileMap;
    }

    public static String getParent_file_path() {
        return parent_file_path;
    }

    public static void setParent_file_path(String parent_file_path) {
        MiddleUtils.parent_file_path = parent_file_path;
    }
}

3.线程池

@Component
@Slf4j
public class ImageThreadPool {


    @Value("${max_thread}")
    private int max_thread;

    @Bean("imageThread")
    public ThreadPoolTaskExecutor initImagePool() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(max_thread);
        threadPoolTaskExecutor.setMaxPoolSize(i * 2);
        threadPoolTaskExecutor.setQueueCapacity(max_thread * 2);
        threadPoolTaskExecutor.setThreadNamePrefix("image_pool");
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

4.生产者

@Slf4j
public class ImageProducer extends Thread {


    private static ImageService imageService;

    @Override
    public void run() {
        while (true) {
            /**
             * https://www.ip138.com/ascii/  中文路径转为ASCII码
             * 做成windows应用
             * https://github.com/winsw/winsw/releases
             */
            getImage(MiddleUtils.getParent_file_path());
        }

    }


    public void getImage(String filePath) {
        if (Objects.isNull(filePath)) {
            log.info("根路径为空");
            return;
        }
        File file = new File(filePath);
        File[] files = file.listFiles();
        if (files == null || files.length < 1) {
            return;
        }

        for (File f : files) {
            if (f.isDirectory()) {
                getImage(f.getAbsolutePath());
            } else if (f.isFile()) {
                /**
                 * 自旋等待
                 */
                while (MiddleUtils.getFileMap().size() > MiddleUtils.getConsumerFile().size()) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                /**
                 * 将数据加入到队列中
                 */
                Long aLong = MiddleUtils.getFileMap().get(f.getAbsolutePath());
                if (aLong != null) {
                    return;
                }
                MiddleUtils.getFileMap().put(f.getAbsolutePath(), System.currentTimeMillis());
                if (Objects.isNull(imageService)) {
                    imageService = (ImageService) SpringBeanUtils.getBean("imageService");
                }
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                imageService.uploadImage(f.getAbsolutePath());
            }
        }
    }
}

5.消费者(可以将图片转成base64字符串,后端做解密操作完成上传服务端)

@Service
@Slf4j
public class ImageService {

    @Async("imageThread")
    public void uploadImage(String filePath) {
        log.info(filePath);
        if (Objects.isNull(filePath)) {
            return;
        }
        try {
            /**
             * 具体业务
             */
            log.info("具体业务逻辑");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            MiddleUtils.getFileMap().remove(filePath);
        }
    }
}

pom文件依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>image_upload</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
    </parent>
    <packaging>pom</packaging>

    <modules>
        <module>image_upload_local</module>
        <module>image_upload_server</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


</project>

local端

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>image_upload</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>image_upload_local</artifactId>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <build>
        <finalName>image_upload_local</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.unique.local.ImageUploadLocal</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

server端也一样

如果部署windows应用的话可以参考如下做法

1.github上下载对应windows位数的exe文件

Releases · winsw/winsw · GitHub

2.将exe文件名和jar文件名配置一样

原始

 修改后

 

xml文件配置,其中BASE为当前exe文件所在目录

<service>
  
  <!-- ID of the service. It should be unique across the Windows system-->
  <id>image_upload_local</id>
  <!-- Display name of the service -->
  <name>image_upload_local</name>
  <!-- Service description -->
  <description>image_upload_local upload</description>
  
  <!-- Path to the executable, which should be started -->
  <executable>java</executable>
  <arguments>-jar %BASE%\image_upload_local.jar --server.port=9002</arguments>
  <logpath>%BASE%\logs</logpath>

</service>

安装:image_upload_local.exe install

 

更新:先停服务 win+r进入服务右键停止服务 

卸载程序:image_upload_local.exe uninstall

C:\Users\haoha\Desktop\新建文件夹 (2)\新建文件夹>image_upload_local.exe install
2021-11-13 21:19:20,382 INFO  - Installing service 'image_upload_local (image_upload_local)'...
2021-11-13 21:19:20,406 INFO  - Service 'image_upload_local (image_upload_local)' was installed successfully.

C:\Users\haoha\Desktop\新建文件夹 (2)\新建文件夹>image_upload_local.exe uninstall
2021-11-13 21:21:37,650 INFO  - Uninstalling service 'image_upload_local (image_upload_local)'...
2021-11-13 21:21:37,655 INFO  - Service 'image_upload_local (image_upload_local)' was uninstalled successfully.

C:\Users\haoha\Desktop\新建文件夹 (2)\新建文件夹>

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/haohaounique/article/details/119812502

智能推荐

鸿蒙系统可以替代安卓吗,华为今天发布的鸿蒙系统,到底能不能替代安卓?-程序员宅基地

文章浏览阅读570次。对于大部分差友们来说,“开发者大会”这个词一定显得陌生而又遥远,跟普通的产品发布会不一样,他们面向的对象并不是普通的消费者,而是各种程序猿和攻城狮。话又说回来,能开“ 开发者大会”,也说明这个企业已经牛逼到了一定程度。。譬如每年的谷歌开发者大会,苹果的WWDC 都受到超多人关注,因为千千万万开发者就是依靠着安卓和iOS 生态创造价值,它们的任何更新和变动都引领着未来互联网的走向。这种会议完全是企业..._华为鸿蒙系统能代替安卓吗

Linux运行jar包命令_linux执行jar包命令带参数-程序员宅基地

文章浏览阅读1.4w次,点赞2次,收藏23次。一. linux下运行jar包的命令1、java -jar xxxxx.jar // 当前xshell窗口被锁定,可按CTRL + C打断程序运行,或直接关闭窗口,程序退出2、java -jar xxxxx.jar & //当前shell窗口不被锁定,但是当窗口关闭时,程序中止运行。3、nohup Java -jar xxxxxx.jar & //意思是不挂断运行命令,当账户退出或终端关闭时,程序仍然运行二. 下面详细介绍第三种运行方式/*下面介绍如何关闭第三种运行的jar包程序/_linux执行jar包命令带参数

C#LeetCode刷题之#344-反转字符串​​​​​​​(Reverse String)_c#将任意字符串反转输出-程序员宅基地

文章浏览阅读1.2w次。问题编写一个函数,其作用是将输入的字符串反转过来。输入: "hello"输出: "olleh"输入: "A man, a plan, a canal: Panama"输出: "amanaP :lanac a ,nalp a ,nam A"Write a function that takes a string as input and returns the str..._c#将任意字符串反转输出

实现游戏数据文件读取和写入_游戏data文件怎么编辑-程序员宅基地

文章浏览阅读1.6k次。前言 关于代码与文件交互方式的选择,基本常用的有以下三种,与txt格式的文本文件交互,与ini格式的配置文件交互,与xml类型的文件交互。考虑到交互数据为游戏数据,因此采用第3种。文本利用C#的正反序列化技术来实现游戏数据文件的读取和写入。准备工作 本人使用的开发环境为VisualStudio2019..._游戏data文件怎么编辑

hive if 用法-程序员宅基地

文章浏览阅读4.2w次,点赞6次,收藏20次。1.If函数:if和case差不多,都是处理单个列的查询结果语法: if(boolean testCondition, T valueTrue, T valueFalseOrNull)返回值: T说明:当条件testCondition为TRUE时,返回valueTrue;否则返回valueFalseOrNull举例:if(条件表达式,结果1,结果2)相当于java中的三目运算符..._hive if

如何做好腾讯视频号?腾讯视频号的机会!_腾讯视频号服务外包怎么做-程序员宅基地

文章浏览阅读1k次。腾讯扩大了视频号的内测范围,很多的朋友都收到了视频号的邀请,每个人都觉得视频号是一个创业的契机,最起码站在了风口的边缘,似乎明天注册公司,后天就能出任CEO迎接白富美了。对于视频号的这个机会,很不好意思,我先帮您泼一盆冷水,没有正确的追风的姿势,最后还是安然离场! 历来的风口,抓住了哪个? 从微博到公众号,从公众号到头条,从头条到抖音,每一次都是一个机会,现在看看很多人还不是一样,看..._腾讯视频号服务外包怎么做

随便推点

信号完整性之阻抗匹配与端接方法_传输线的阻抗匹配和端接方式-程序员宅基地

文章浏览阅读9.4k次,点赞15次,收藏98次。信号完整性之阻抗匹配与端接方法1. 前言随着电子技术的发展,电路的规模越来越大,单个器件集成的功能越来越多,速率越来越高,而器件的尺寸越来越小。由于器件尺寸的减小,器件引脚信号变化沿的速率变得越来越高,导致SI问题越来越突出。SI (Signal Integrity,信号完整性)与以下几个因素有关:反射、串扰、辐射。反射是由信号传输路径上的阻抗不连续造成的;串扰与信号的间距有关;辐射则与高速器件自身以及PCB设计均有关。2. 信号的阻抗匹配信号的阻抗匹配是影响信号完整性最主要的._传输线的阻抗匹配和端接方式

Ae 入门系列之一:了解 Ae 及工作流程-程序员宅基地

文章浏览阅读1.7k次,点赞2次,收藏5次。Adobe After Efftects(简称为 Ae )可以帮助用户高效且精确地创建无数种引人注目的动态图形和震撼人心的视觉效果,利用与其他 Adobe 软件紧密集成和高度灵活的二维和三..._ae视频渲染从入门到精通 csdn

Android进程间通信--消息机制及IPC机制实现_ipc机制和消息机制-程序员宅基地

文章浏览阅读479次。Android为了屏蔽进程的概念,利用不同的组件[Activity、Service]来表示进程之间的通信!组件间通信的核心机制是Intent,通过Intent可以开启一个Activity或Service,不论这个Activity或Service是属于当前应用还是其它应用的! _ipc机制和消息机制

mysql中文编码问题-程序员宅基地

文章浏览阅读38次。我比较推荐的方法是在创建数据库时便设置中文编码create database bp default character set utf8; #注意是utf8不是utf-8以下方法只适用于mysql5.5以上版本的(其实我的是mariadb5.5版本的) 编辑mysql配置文件[root@localhost ~]# cat /etc/my.cnf..._群晖 mysql5.6 中文乱码

HRNet提取骨架特征点+ ST-GCN训练自己的数据集代码实践问题记录-程序员宅基地

文章浏览阅读1w次,点赞12次,收藏140次。软硬件环境:python 3.6.5Ubuntu 18.04 LTSPytorch 1.1.0NVIDIA TITAN XP 8GB项目链接https://github.com/open-mmlab/mmskeleton准备工作先准备数据集准备环境,包含两步:第一步: 进入文件夹./deprecated/origin_stgcn_repo/ ,打开requirements.txt看下需要满足的环境条件,conda list:我不直接安装requirements.txt中的需求原因是,_hrnet提取骨架

分支限界法TSP问题-程序员宅基地

文章浏览阅读1.7k次。分支限界法TSP问题//分支限界法#include<iostream>#include<algorithm>#include<cstdio>#include<queue>const int INF = 100000;const int MAX_N = 22;using namespace std;//n*n的一个矩阵int n;int cost[MAX_N][MAX_N];//最少3个点,最多MAX_N个点struct Node{