博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
轻应用高并发构建方案
阅读量:6758 次
发布时间:2019-06-26

本文共 22198 字,大约阅读时间需要 73 分钟。

hot3.png

 

1.现状介绍

先来看下整个离线H5组件构建活动图:

由上图可知,jenkins构建离线H5组件时,是依次执行离线H5组件,直到没有离线H5组件为止。这样,不仅构建耗时长,还效率低。

那有什么办法可以提高效率?相比单线程,可以考虑多线程并发执行构建。

2.解决方案

我们再来看下并发下整个离线H5组件构建活动图:

由上图可知,通过高并发多线程执行离线H5组件构建,实现所有离线H5组件同时构建的场景。注意的是,当向文件追加数据的时候,需要对文件加锁,避免多线程读写该文件导致读写错误问题。

  • 线程池

以前当我们每次执行一个任务时用new Thread,频繁创建对象会导致系统性能差,线程缺乏统一管理,可能无限制新建线程,相互之间竞争导致系统耗尽,并且缺乏定时任务,中断等功能。线程池可以有效的提高系统资源的使用率,同时避免过多资源竞争,重用存在的线程,减少对象创建。

ExecutorService提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。可以关闭 ExecutorService,这将导致其拒绝新任务。

线程池客户端类:NpmBuildClient.java

import hudson.Launcher;import hudson.model.BuildListener;import hudson.model.AbstractBuild;import java.util.ArrayList;import java.util.List;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class NpmBuildClient {    private ExecutorService executorService;    private List
> futureResultList = new ArrayList
>(); private List
npmDTOList = new ArrayList
(); private AbstractBuild
build; private Launcher launcher; private BuildListener listener; public NpmBuildClient(AbstractBuild
build, Launcher launcher, BuildListener listener) { super(); this.build = build; this.launcher = launcher; this.listener = listener; this.executorService = Executors.newCachedThreadPool(); } public static void main(String[] args) { NpmDTO npmDTO = new NpmDTO(); npmDTO.setBizName("bizName"); npmDTO.setNamespace("namespace"); npmDTO.setNpm("E:\\app\\components.json"); npmDTO.setH5Version("E:\\app\\components1.json"); NpmDTO npmDTO2 = new NpmDTO(); npmDTO2.setBizName("bizName2"); npmDTO2.setNamespace("namespace2"); npmDTO2.setNpm("E:\\app\\components.json"); npmDTO2.setH5Version("E:\\app\\components1.json"); NpmDTO npmDTO3 = new NpmDTO(); npmDTO3.setBizName("h5111601"); npmDTO3.setNamespace("com.nd.sdp.component.h5111601"); npmDTO3.setNpm("E:\\app\\components.json"); npmDTO3.setH5Version("E:\\app\\components1.json"); NpmBuildClient npmBuildClient = new NpmBuildClient(null, null, null); npmBuildClient.addData(npmDTO); npmBuildClient.addData(npmDTO2); npmBuildClient.addData(npmDTO3); for (int i = 0; i < 3; i++) { NpmDTO npmDTO4 = new NpmDTO(); npmDTO4.setBizName("h5111601" + i); npmDTO4.setNamespace("com.nd.sdp.component.h5111601" + i); npmDTO4.setNpm("E:\\app\\components.json"); npmDTO4.setH5Version("E:\\app\\components1.json"); npmBuildClient.addData(npmDTO4); } npmBuildClient.start(); } public void addData(NpmDTO npmDTO) { npmDTOList.add(npmDTO); } public void start() { for (NpmDTO npmDTO : npmDTOList) { // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 Future
future = executorService.submit(new NpmBuildCallable(build, launcher, listener, npmDTO)); // 将任务执行结果存储到List中 futureResultList.add(future); } //遍历任务的结果 for (Future
futureResult : futureResultList) { try { while (!futureResult.isDone());// Future返回如果没有完成,则一直循环等待,直到Future返回完成 System.out.println(futureResult.get());// 打印各个线程(任务)执行的结果 } catch (InterruptedException e){ e.printStackTrace(); } catch (ExecutionException e){ e.printStackTrace(); } } close(); } private void close() { executorService.shutdown(); } }

任务类:NpmBuildCallable.java

import hudson.Launcher;import hudson.model.BuildListener;import hudson.model.AbstractBuild;import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;import java.io.UnsupportedEncodingException;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;import java.util.concurrent.Callable;import org.apache.commons.lang3.StringUtils;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import com.nd.sdp.portal.jenkins.factory.local.h5.model.NpmDTO;import com.nd.sdp.portal.jenkins.factory.local.h5.service.LocalH5Service;public class NpmBuildCallable implements Callable
{ private AbstractBuild
build; private Launcher launcher; private BuildListener listener; private NpmDTO npmDTO; public NpmBuildCallable(AbstractBuild
build, Launcher launcher, BuildListener listener, NpmDTO npmDTO) { super(); this.build = build; this.launcher = launcher; this.listener = listener; this.npmDTO = npmDTO; } public String call() throws IOException, InterruptedException { test(npmDTO.getNamespace(), npmDTO.getBizName(), npmDTO.getNpm(), npmDTO.getH5Version()); return "SUCCESS"; } private void test(String namespace, String bizName, String npm, String h5Version) throws UnsupportedEncodingException, IOException, InterruptedException { appendComponentsFileData(npm, namespace, bizName); appendFileData(h5Version, namespace, bizName); } private void appendFileData(String h5Version, String namespace, String bizName) throws UnsupportedEncodingException, IOException, InterruptedException { String componentsContent = FileUtil.readFileToString(new File(h5Version),System.out); JSONArray componentsJsonArr = JSONArray.parseArray(componentsContent); for (Object componentsJson : componentsJsonArr) { if (!(componentsJson instanceof JSONObject)) { continue; } JSONObject components = (JSONObject) componentsJson; JSONObject componentJson = components.getJSONObject("component"); String name_space = componentJson.getString("namespace"); String name = componentJson.getString("name"); if(StringUtils.equals(namespace, name_space) && StringUtils.equals(bizName, name)) { JSONObject localH5Json = components.getJSONObject("local-h5"); localH5Json.put("host", namespace); localH5Json.put("build_time", bizName); break; } } System.out.println(componentsJsonArr); FileUtil.write(new File(h5Version), componentsJsonArr.toJSONString(), "UTF-8", false,System.out); } private void appendComponentsFileData(String npm, String namespace, String bizName) throws UnsupportedEncodingException, IOException, InterruptedException { String componentsContent = FileUtil.readFileToString(new File(npm),System.out); JSONArray componentsJsonArr = JSONArray.parseArray(componentsContent); for (Object componentsJson : componentsJsonArr) { if (!(componentsJson instanceof JSONObject)) { continue; } JSONObject components = (JSONObject) componentsJson; JSONObject componentJson = components.getJSONObject("component"); String name_space = componentJson.getString("namespace"); String name = componentJson.getString("name"); if(StringUtils.equals(namespace, name_space) && StringUtils.equals(bizName, name)) { JSONObject localH5Json = components.getJSONObject("local-h5"); localH5Json.put("host", namespace); localH5Json.put("build_time", bizName); break; } } System.out.println(componentsJsonArr); FileUtil.write(new File(npm), componentsJsonArr.toJSONString(), "UTF-8", false,System.out); } }

文件操作类:FileUtil.java

import java.io.RandomAccessFile;import java.io.UnsupportedEncodingException;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;public class FileUtil {        /**     * 

Description: 高并发写文件

* @param file * @param data * @param encoding * @param append * @throws UnsupportedEncodingException * @throws IOException * @throws InterruptedException */ public static void write(File file, String data, String encoding, boolean append, PrintStream printStream) throws UnsupportedEncodingException, IOException, InterruptedException { if(!file.exists()) { file.createNewFile(); } //对该文件加锁 RandomAccessFile out = new RandomAccessFile(file, "rw"); FileChannel fcout = out.getChannel(); FileLock flout = null; while(true) { try { flout = fcout.tryLock();// 独占锁, 其他线程不可读也不可写 printStream.println(Thread.currentThread().getName()+" get write lock: " + file.getName());// Thread.sleep(1000);// logger.info(Thread.currentThread().getName()+" get write lock: " + file.getName()); break; } catch (Exception e) {// logger.info(Thread.currentThread().getName()+" is block: " + file.getName()); printStream.println(Thread.currentThread().getName()+" is block: " + file.getName()); Thread.sleep(1000); } } if(append) { out.seek(out.length());// 将文件的读写指针定位到文件的末尾 } else { fcout.truncate(0);// 清空文件 } out.write(data.getBytes(encoding)); flout.release(); printStream.println(Thread.currentThread().getName()+" release write lock: " + file.getName()); out.close(); fcout.close(); } /** *

Description: 高并发读文件

* @param file * @param printStream * @return * @throws InterruptedException * @throws UnsupportedEncodingException * @throws IOException */ public static String readFileToString(File file, PrintStream printStream) throws InterruptedException, UnsupportedEncodingException, IOException { //给该文件加锁 RandomAccessFile fis = new RandomAccessFile(file, "rw"); FileChannel fcin = fis.getChannel(); FileLock flin = null; while(true){ try {// flin = fcin.tryLock(0,Long.MAX_VALUE, true);// 不断的请求锁,如果请求不到,等1秒再请求 (共享锁:其他线程可读但不可写) flin = fcin.tryLock(); printStream.println(Thread.currentThread().getName()+" get read lock: " + file.getName());// logger.info(Thread.currentThread().getName()+"get read lock: " + file.getName());// Thread.sleep(1000); break; } catch (Exception e) {// 如果是同一进程的多线程,重复请求tryLock()会抛出异常// printStream.println(Thread.currentThread().getName()+" is block: " + file.getName()); Thread.sleep(1000); } } byte[] buf = new byte[(int) fis.length()]; StringBuffer sb = new StringBuffer(); while((fis.read(buf))!=-1){ sb.append(new String(buf,"utf-8")); } flin.release();// 释放锁 printStream.println(Thread.currentThread().getName()+" release read lock: " + file.getName()); fis.close(); fcin.close(); return sb.toString(); } }

原来components.json文件内容:

[    {        "component": {            "name": "bizName",            "namespace": "namespace"        },        "local-h5": {            "build_time": 123,            "host": "",            "npm": "123"        },        "type": [            "local-h5"        ]    },    {        "component": {            "name": "bizName2",            "namespace": "namespace2"        },        "local-h5": {            "build_time": 123,            "host": "",            "npm": "123"        },        "type": [            "local-h5"        ]    },    {        "component": {            "name": "h5111601",            "namespace": "com.nd.sdp.component.h5111601"        },        "local-h5": {            "build_time": 123,            "host": "",            "npm": "123"        },        "type": [            "local-h5"        ]    }]

原来components1.json文件内容:

[    {        "component": {            "name": "bizName",            "namespace": "namespace"        },        "local-h5": {            "build_time": 345,            "host": "",            "npm": "345"        },        "type": [            "local-h5"        ]    },    {        "component": {            "name": "bizName2",            "namespace": "namespace2"        },        "local-h5": {            "build_time": 345,            "host": "",            "npm": "345"        },        "type": [            "local-h5"        ]    },    {        "component": {            "name": "h5111601",            "namespace": "com.nd.sdp.component.h5111601"        },        "local-h5": {            "build_time": 345,            "host": "",            "npm": "345"        },        "type": [            "local-h5"        ]    }]

测试:

public static void main(String[] args) {        NpmDTO npmDTO = new NpmDTO();        npmDTO.setBizName("bizName");        npmDTO.setNamespace("namespace");        npmDTO.setNpm("E:\\app\\components.json");        npmDTO.setH5Version("E:\\app\\components1.json");                NpmDTO npmDTO2 = new NpmDTO();        npmDTO2.setBizName("bizName2");        npmDTO2.setNamespace("namespace2");        npmDTO2.setNpm("E:\\app\\components.json");        npmDTO2.setH5Version("E:\\app\\components1.json");                NpmDTO npmDTO3 = new NpmDTO();        npmDTO3.setBizName("h5111601");        npmDTO3.setNamespace("com.nd.sdp.component.h5111601");        npmDTO3.setNpm("E:\\app\\components.json");        npmDTO3.setH5Version("E:\\app\\components1.json");                NpmBuildClient npmBuildClient = new NpmBuildClient(null, null, null);        npmBuildClient.addData(npmDTO);        npmBuildClient.addData(npmDTO2);        npmBuildClient.addData(npmDTO3);                for (int i = 0; i < 3; i++) {            NpmDTO npmDTO4 = new NpmDTO();            npmDTO4.setBizName("h5111601" + i);            npmDTO4.setNamespace("com.nd.sdp.component.h5111601" + i);            npmDTO4.setNpm("E:\\app\\components.json");            npmDTO4.setH5Version("E:\\app\\components1.json");            npmBuildClient.addData(npmDTO4);        }                npmBuildClient.start();}

测试结果:

pool-1-thread-2 get read lock: components.jsonpool-1-thread-2 release read lock: components.jsonpool-1-thread-2 get write lock: components.jsonpool-1-thread-2 release write lock: components.jsonpool-1-thread-2 get read lock: components1.jsonpool-1-thread-2 release read lock: components1.jsonpool-1-thread-2 get write lock: components1.jsonpool-1-thread-2 release write lock: components1.jsonpool-1-thread-4 get read lock: components.jsonpool-1-thread-4 release read lock: components.jsonpool-1-thread-4 get write lock: components.jsonpool-1-thread-4 release write lock: components.jsonpool-1-thread-4 get read lock: components1.jsonpool-1-thread-4 release read lock: components1.jsonpool-1-thread-4 get write lock: components1.jsonpool-1-thread-4 release write lock: components1.jsonpool-1-thread-5 get read lock: components.jsonpool-1-thread-5 release read lock: components.jsonpool-1-thread-5 get write lock: components.jsonpool-1-thread-5 release write lock: components.jsonpool-1-thread-5 get read lock: components1.jsonpool-1-thread-5 release read lock: components1.jsonpool-1-thread-5 get write lock: components1.jsonpool-1-thread-5 release write lock: components1.jsonpool-1-thread-1 get read lock: components.jsonpool-1-thread-1 release read lock: components.jsonpool-1-thread-1 get write lock: components.jsonpool-1-thread-1 release write lock: components.jsonpool-1-thread-1 get read lock: components1.jsonpool-1-thread-1 release read lock: components1.jsonpool-1-thread-1 get write lock: components1.jsonpool-1-thread-1 release write lock: components1.jsonSUCCESSSUCCESSpool-1-thread-6 get read lock: components.jsonpool-1-thread-6 release read lock: components.jsonpool-1-thread-6 get write lock: components.jsonpool-1-thread-6 release write lock: components.jsonpool-1-thread-6 get read lock: components1.jsonpool-1-thread-6 release read lock: components1.jsonpool-1-thread-6 get write lock: components1.jsonpool-1-thread-6 release write lock: components1.jsonpool-1-thread-3 get read lock: components.jsonpool-1-thread-3 release read lock: components.jsonpool-1-thread-3 get write lock: components.jsonpool-1-thread-3 release write lock: components.jsonpool-1-thread-3 get read lock: components1.jsonpool-1-thread-3 release read lock: components1.jsonpool-1-thread-3 get write lock: components1.jsonpool-1-thread-3 release write lock: components1.jsonSUCCESSSUCCESSSUCCESSSUCCESS

通过控制台日志,可以看到当线程获取到读锁时,其他线程会等待且不断请求该读锁;当该线程释放读锁,接着获取写锁,然后再释放锁时其他线程才能获取到该读写锁。好像没啥毛病这逻辑,后来我发现我错了。如果线程在释放读锁后获取写锁前,这个业务处理时间有点长,那么该线程在还没有获取写锁前,已经被其他线程获取到该写锁;那么其他线程读取到的文件内容就不是该线程准备写操作的文件内容了。这样会导致每个线程只会去更新它自己的内容,无法保证所有线程运行后,文件内容被全部更新。

怎么办?其实可以在每个线程释放读锁前,进行文件内容更新操作,然后再释放锁出来给其他线程进行读写操作。这样问题不就解决了。哈哈...

修改后的任务类:NpmBuildCallable.java

import hudson.Launcher;import hudson.model.BuildListener;import hudson.model.AbstractBuild;import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;import java.io.UnsupportedEncodingException;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;import java.util.concurrent.Callable;import org.apache.commons.lang3.StringUtils;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import com.nd.sdp.portal.jenkins.factory.local.h5.model.NpmDTO;public class NpmBuildCallable implements Callable
{ private AbstractBuild
build; private Launcher launcher; private BuildListener listener; private NpmDTO npmDTO; public NpmBuildCallable(AbstractBuild
build, Launcher launcher, BuildListener listener, NpmDTO npmDTO) { super(); this.build = build; this.launcher = launcher; this.listener = listener; this.npmDTO = npmDTO; } public String call() throws IOException, InterruptedException { test(npmDTO.getNamespace(), npmDTO.getBizName(), npmDTO.getNpm(), npmDTO.getH5Version()); return "SUCCESS"; } private void test(String namespace, String bizName, String npm, String h5Version) throws UnsupportedEncodingException, IOException, InterruptedException { appendComponentsFileData(npm, namespace, bizName); appendFileData(h5Version, namespace, bizName); } private void appendFileData(String h5Version, String namespace, String bizName) throws UnsupportedEncodingException, IOException, InterruptedException { //给该文件加锁 RandomAccessFile randomAccessFile = new RandomAccessFile(new File(h5Version), "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); FileLock fileLock = null; while(true){ try { fileLock = fileChannel.tryLock(); break; } catch (Exception e) { Thread.sleep(1000); } } byte[] buf = new byte[(int) randomAccessFile.length()]; StringBuffer sb = new StringBuffer(); while((randomAccessFile.read(buf))!=-1){ sb.append(new String(buf,"utf-8")); } String componentsContent = sb.toString(); JSONArray componentsJsonArr = JSONArray.parseArray(componentsContent); for (Object componentsJson : componentsJsonArr) { if (!(componentsJson instanceof JSONObject)) { continue; } JSONObject components = (JSONObject) componentsJson; JSONObject componentJson = components.getJSONObject("component"); String name_space = componentJson.getString("namespace"); String name = componentJson.getString("name"); if(StringUtils.equals(namespace, name_space) && StringUtils.equals(bizName, name)) { JSONObject localH5Json = components.getJSONObject("local-h5"); localH5Json.put("host", namespace); localH5Json.put("build_time", bizName); break; } } fileChannel.truncate(0);// 清空文件 randomAccessFile.write(componentsJsonArr.toJSONString().getBytes("UTF-8")); fileLock.release(); randomAccessFile.close(); fileChannel.close(); } private void appendComponentsFileData(String npm, String namespace, String bizName) throws UnsupportedEncodingException, IOException, InterruptedException { //给该文件加锁 RandomAccessFile randomAccessFile = new RandomAccessFile(new File(npm), "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); FileLock fileLock = null; while(true){ try { fileLock = fileChannel.tryLock(); break; } catch (Exception e) { Thread.sleep(1000); } } byte[] buf = new byte[(int) randomAccessFile.length()]; StringBuffer sb = new StringBuffer(); while((randomAccessFile.read(buf))!=-1){ sb.append(new String(buf,"utf-8")); } String componentsContent = sb.toString(); JSONArray componentsJsonArr = JSONArray.parseArray(componentsContent); for (Object componentsJson : componentsJsonArr) { if (!(componentsJson instanceof JSONObject)) { continue; } JSONObject components = (JSONObject) componentsJson; JSONObject componentJson = components.getJSONObject("component"); String name_space = componentJson.getString("namespace"); String name = componentJson.getString("name"); if(StringUtils.equals(namespace, name_space) && StringUtils.equals(bizName, name)) { JSONObject localH5Json = components.getJSONObject("local-h5"); localH5Json.put("host", namespace); localH5Json.put("build_time", bizName); break; } } fileChannel.truncate(0);// 清空文件 randomAccessFile.write(componentsJsonArr.toJSONString().getBytes("UTF-8")); fileLock.release(); randomAccessFile.close(); fileChannel.close(); } }

通过RandomAccessFile实现非阻塞式的读写,通过锁机制完成高并发下的文件的操作。

转载于:https://my.oschina.net/lienson/blog/1506822

你可能感兴趣的文章
将博客搬至CSDN
查看>>
大三学长带我学习JAVA。作业1. 第1讲.Java.SE入门、JDK的下载与安装、第一个Java程序、Java程序的编译与执行 大三学长带我学习JAVA。作业1....
查看>>
在ie9浏览器中ajax请求数据始终执行error的问题解决
查看>>
类和原型之工厂模式!
查看>>
hdu 5396 Expression
查看>>
时间选择器(js,css,html)
查看>>
xshell常用命令大全
查看>>
Day01_变量,数据类型_程序交互_流程控制
查看>>
POJ 3087 Shuffle'm Up 模拟,看着不像搜索啊
查看>>
你知道 GNU Binutils 吗?【binutils】
查看>>
OC与swift相互调用
查看>>
quartus ii 中文注释乱码解决办法
查看>>
Linux网卡配置与绑定
查看>>
java学习之路--String类方法的应用
查看>>
auto,register,static分析
查看>>
百度BAE JAVA环境项目部署和调试
查看>>
CSS盒模型
查看>>
Log4Net 添加自定义字段并保存到数据库
查看>>
Redis集群(三)Cluster集群
查看>>
NSURLSession
查看>>