[zt] 高并发处理

转载自: http://yizhyi.iteye.com/blog/765600

时常看到高并发的问题,但高并发其实是最不需要考虑的东西。为何,他虚无缥缈,很少有网站真的需要这些东西,而且其中很多技术,其实你已经在用了。有这个意识就够了,不需要时刻盯着这个问题。只有很少的网站真的能达到高并发。

简单做一个归纳,从低成本、高性能和高扩张性的角度来说有如下处理方案:
1、HTML静态化
2、图片服务器分离
3、数据库集群和库表散列
4、缓存
5、镜像
6、负载均衡;一个典型的使用负载均衡的策略就是,在软件或者硬件四层交换的基础上搭建squid集群,这种思路在很多大型网站包括搜索引擎上被采用,这样的架构低成本、高性能还有很强的扩张性,随时往架构里面增减节点都非常容易。

下面也是一个牛人所做的总结,跟上面部分相同。
高并发时,性能瓶颈及当前常用的应对措施


1.数据库瓶颈。Mysql并发链接100

2.apache 并发链接1500

3.程序执行效率


1.有数据库瓶颈时,当前处理方案无外乎 主从,集群。增加cache(memcached).

如:手机之家新系统介绍及架构分享(http://www.slideshare.net/Fenng/ss-1218991?from=ss_embed)

就是在cache层做优化

又拍网架构(http://www.bopor.com/?p=652)

是以增加数据库,分表分库的方法解决。

Sina增加了mq(消息队列)来分发数据。

还有风站用了key-value的数据库。其实这可以理解成一个持久化的缓存。


2.apache瓶颈。

增加服务器。负载均衡。如sina的F5

由于进程数的限制。会把一些基本不变的代码挪出来放到单独的服务器。如css/js/图片。

国内成功的案例是tom的cdn


又如nginx的横空出世和squid的反向代理都是基于这个原因出来的。


3.php的执行效率。原因有多个。

1).本身的效率低。

解决的成功案例是Zend Optimizer 和 facebooke的hiphop

Taobao是把php代码编译成模块解决效率问题。

2). 数据库查询效率问题。如可能有order by ,group by 等Sql数据问题。

这个其实应该归结到数据库设计问题。


解决的办法是建立正确的索引。增加memcache.。

对like表 用专用的sphinx.和lucence 等搜索服务。

程序员都应该会用explain对sql语句作分析。


说到底。解决高并发就是上面所列技术,程序员要做的就是把每个技术具体实现。
POST /zt_rec/stationif/receive HTTP/1.1 Host: 10.41.0.85 Remote_Addr: 10.141.1.181 X-REAL-IP: 10.141.1.181 X-Forwarded-For: 10.241.100.89, 10.41.0.85, 10.141.1.181 Content-Length: 1659 binfile-md5: mm binfile-gzip: false binfile-auth: myTicket Content-Type: multipart/form-data; boundary=ypm4nr-tfJntYDcZKjYbQoyJMsGzRR8Sk7z; charset=UTF-8 User-Agent: Apache-HttpClient/4.5.14 (Java/1.8.0_102) Accept-Encoding: gzip,deflate --ypm4nr-tfJntYDcZKjYbQoyJMsGzRR8Sk7z Content-Disposition: form-data; name="binFile"; filename="TRC_EXETCPU_REQ_S0085410010010_20250224160012808.json" Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: binary {"discountFee":2265,"feeMileage":50316,"exTollStationName":"郑州西南","splitProvince":[{"serProvinceId":"410201","id":"S008541001001020103202025022415453508000","tollFee":0}],"fee":0,"enTollLaneHex":"4101FDEC1F","etcCardType":1,"preBalance":45933,"obuTotalDiscountAmount":0,"totalCount":6,"feeRate":1.0,"spcRateVersion":"20250221001","identification":2,"payFee":2265,"cardTotalAmount":1980,"provTransCount":6,"mediaNo":"4101174103618496","vehicleClass":0,"enTime":"2025-02-24T15:24:04","id":"S008541001001020103202025022415453508","vehicleId":"豫A03898_0","vehicleType":1,"vehicleSign":"0xff","obuTotalAmount":2085,"terminalNo":"214100030AAA","exitFeeType":1,"transFee":0,"passId":"014101164922010207194220250224152404","mediaType":1,"shortFeeMileage":50316,"postBalance":45933,"enTollLaneId":"S00854100100501010310","transType":"09","provinceCount":1,"cardId":"41011649220102071942","TAC":"BEB33A0A","transPayType":1,"obuSign":2,"enTollStationName":"河南唐庄站","noCardCount":0,"exTime":"2025-02-24T15:58:28","exTollLaneId":"S00854100100102010320","terminalTransNo":"007FBB03","identifyVehicleId":"豫A03898_0","shortFee":2300,"obuProvinceFee":1980} --ypm4nr-tfJntYDcZKjYbQoyJMsGzRR8Sk7z Content-Disposition: form-data; name="filename" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TRC_EXETCPU_REQ_S0085410010010_20250224160012808.json --ypm4nr-tfJntYDcZKjYbQoyJMsGzRR8Sk7z-- 使用springboot框架接收上面的文件,每秒大概6m,几百个文件,要求能够接受并转发到其他服务器上,每天新建一个文件夹,发送失败时存到每天新建的文件夹后后重发,7天删除一次文件夹,避免内存泄露、代码写注释和log日志
03-13
<think>嗯,用户的问题是关于使用Spring Boot接收并处理大量文件,同时需要转发到其他服务器,并且有存储和清理的需求。首先,我得分析这个HTTP请求的结构。看起来是一个multipart/form-data请求,包含一个文件和一些表单字段。用户要求每秒处理大约6MB的数据,几百个文件,所以性能是关键。 首先,Spring Boot处理文件上传通常用MultipartFile,但高并发下需要注意内存和磁盘的使用。默认情况下,Spring Boot会将文件存储在内存中,大文件会写入临时目录。但大量文件可能导致内存溢出,所以可能需要配置更大的临时存储或者异步处理。 接下来,接收文件并转发。转发部分可能需要使用RestTemplate或者WebClient,但考虑到性能,异步非阻塞的方式更好,比如使用WebClient或者消息队列。不过用户可能希望直接转发,所以可能需要多线程处理,避免阻塞主线程。 存储方面,每天创建一个文件夹,可以用Java的NIO API,比如Paths和Files来创建目录。日期格式的文件夹,比如yyyyMMdd。发送失败的文件需要保存到当天的目录,可能需要一个重试机制,比如Spring Retry或者自定义的重试逻辑。 清理7天前的文件夹,可以用定时任务,Spring的@Scheduled注解,配合File类的遍历和删除操作。注意处理异常,避免删除失败。 避免内存泄漏方面,需要确保文件流正确关闭,及时释放资源。使用try-with-resources语句块,或者在finally块中关闭流。另外,异步处理时要注意线程池的管理,防止资源耗尽。 日志和注释部分,需要在关键步骤添加日志,比如文件接收、转发成功或失败、存储、删除等操作。使用SLF4J的Logger记录信息,方便排查问题。 现在,整合这些思路。接收文件使用@PostMapping处理multipart请求,保存到临时目录,然后异步转发。转发失败时,移动到当天的目录。定时任务清理旧目录。可能需要配置Spring的Multipart属性,调整文件大小和存储位置。 代码结构大致如下:控制器接收文件,服务类处理转发和存储,定时任务处理清理。使用线程池异步处理转发,避免阻塞。转发失败时,将文件保存到指定目录,并记录日志。定时任务每天创建目录,另一个任务清理7天前的目录。 需要注意的点:文件传输的可靠性,网络问题导致转发失败时的重试机制,并发处理时的线程安全,资源管理防止泄漏,以及正确的路径处理确保文件存储到正确的位置。 可能遇到的问题:高并发下磁盘IO瓶颈,转发服务器的响应时间影响整体性能,重试机制可能导致重复发送,需要幂等处理。还有,如何高效地移动文件,避免长时间的文件操作阻塞线程。 解决方案:使用生产者-消费者模式,将接收到的文件放入队列,由工作线程处理转发和存储。使用线程池控制并发数,避免资源耗尽。转发失败时,将文件路径加入重试队列,定期重试。同时,确保目录创建和文件移动的原子性,避免竞态条件。 最后,日志记录要详细,包括文件大小、处理时间、成功失败状态,方便监控和排查问题。注释要说明关键步骤和配置参数的作用,比如重试次数、线程池大小、目录结构等。</think><< 以下是基于Spring Boot实现文件接收、转发和存储管理的解决方案: 1. 配置文件上传参数(application.properties): ```java spring.servlet.multipart.max-file-size=100MB spring.servlet.multipart.max-request-size=100MB spring.servlet.multipart.location=/tmp/uploads ``` 2. 文件处理服务类: ```java import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.nio.file.*; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @Service public class FileTransferService { private static final Logger logger = LoggerFactory.getLogger(FileTransferService.class); @Value("${target.server.url}") private String targetServerUrl; @Value("${storage.base.path}") private String baseStoragePath; private final RestTemplate restTemplate; private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd"); public FileTransferService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Async("fileTransferTaskExecutor") public void processFile(MultipartFile file, String filename) { try { // 创建当日目录 String dateDir = LocalDate.now().format(dateFormatter); Path todayDir = Paths.get(baseStoragePath, dateDir); Files.createDirectories(todayDir); // 临时存储文件 Path tempFile = Files.createTempFile("upload_", ".tmp"); file.transferTo(tempFile); // 尝试转发文件 if (forwardFile(tempFile, filename)) { logger.info("File {} forwarded successfully", filename); Files.delete(tempFile); // 删除临时文件 } else { // 转移失败文件到当日目录 Path failedFile = todayDir.resolve(filename); Files.move(tempFile, failedFile, StandardCopyOption.REPLACE_EXISTING); logger.warn("File {} moved to failed directory", filename); } } catch (IOException e) { logger.error("File processing error: {}", e.getMessage()); } } private boolean forwardFile(Path file, String filename) { try { restTemplate.postForEntity(targetServerUrl, new FileSystemResource(file), String.class); return true; } catch (Exception e) { logger.error("Forward failed for {}: {}", filename, e.getMessage()); return false; } } @Scheduled(cron = "0 0 0 * * ?") // 每天执行 public void createDailyDirectory() { String dateDir = LocalDate.now().format(dateFormatter); Path newDir = Paths.get(baseStoragePath, dateDir); try { Files.createDirectories(newDir); logger.info("Created daily directory: {}", dateDir); } catch (IOException e) { logger.error("Directory creation failed: {}", e.getMessage()); } } @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 public void cleanOldDirectories() { LocalDate cutoffDate = LocalDate.now().minusDays(7); try (DirectoryStream<Path> dirs = Files.newDirectoryStream(Paths.get(baseStoragePath))) { for (Path dir : dirs) { if (Files.isDirectory(dir)) { LocalDate dirDate = LocalDate.parse(dir.getFileName().toString(), dateFormatter); if (dirDate.isBefore(cutoffDate)) { FileUtils.deleteDirectory(dir.toFile()); logger.info("Deleted old directory: {}", dir); } } } } catch (IOException e) { logger.error("Cleanup failed: {}", e.getMessage()); } } } ``` 3. 线程池配置: ```java @Configuration @EnableAsync public class AsyncConfig { @Bean(name = "fileTransferTaskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(500); executor.setThreadNamePrefix("FileTransfer-"); executor.initialize(); return executor; } } ``` 4. 控制器实现: ```java @RestController @RequestMapping("/zt_rec/stationif/receive") public class FileUploadController { private final FileTransferService fileTransferService; public FileUploadController(FileTransferService fileTransferService) { this.fileTransferService = fileTransferService; } @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<String> handleFileUpload( @RequestPart("binFile") MultipartFile file, @RequestPart("filename") String filename) { if (file.isEmpty()) { return ResponseEntity.badRequest().body("Empty file"); } fileTransferService.processFile(file, filename); return ResponseEntity.ok("File received"); } } ``` 实现要点解释: 1. 使用@Async和线程池实现异步处理,避免阻塞请求线程 2. 采用临时文件存储策略减少内存占用 3. 每日目录自动创建和7天自动清理机制 4. 失败文件转移和日志记录 5. 配置合理的线程池参数处理并发请求 6. 使用NIO文件操作提升IO性能 7. 异常处理和资源清理保证系统稳定性 内存泄漏防范措施: 1. 使用try-with-resources确保流关闭 2. 及时删除临时文件 3. 限制线程池队列大小 4. 定期清理旧文件 5. 配置合理的JVM内存参数 6. 使用FileSystemResource自动释放文件句柄 日志记录建议: 1. 记录文件接收时间、大小 2. 记录转发成功/失败状态 3. 记录目录操作事件 4. 记录异常堆栈信息 5. 监控线程池使用情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值