您的位置:首页 > 娱乐 > 八卦 > 网站设计机构排行榜_广州有什么好玩的景点_google引擎入口_广州番禺最新发布

网站设计机构排行榜_广州有什么好玩的景点_google引擎入口_广州番禺最新发布

2025/4/20 23:44:02 来源:https://blog.csdn.net/qq_73380430/article/details/147226155  浏览:    关键词:网站设计机构排行榜_广州有什么好玩的景点_google引擎入口_广州番禺最新发布
网站设计机构排行榜_广州有什么好玩的景点_google引擎入口_广州番禺最新发布

背景介绍

在跨境电商系统中,物流标签的批量生成是一个常见需求。我们的系统需要支持1x1、10x4、11x4等多种标签模板,每个标签包含商品信息、条形码等内容。随着业务量增长,原有的标签生成模块性能问题逐渐显现,特别是在批量生成场景下,响应时间明显延长。

问题分析

通过性能分析,发现主要瓶颈:

  1. 重复计算:每次生成标签都要重新计算布局参数
  2. 资源浪费:相同模板的配置信息重复创建
  3. 内存压力:频繁创建对象导致GC负担增加

原始代码示例:

public void createLabelPdf(String destFilePath, String productName, String barcodeValue) {// 每次都要计算这些参数float pageWidth = 595;float pageHeight = 700;float margin = 20;float xStep = (pageWidth - 2 * margin) / cols;float yStep = (pageHeight - 2 * margin) / rows;// 计算每个标签的位置for (int row = 0; row < rows; row++) {for (int col = 0; col < cols; col++) {// 重复的位置计算...}}
}

优化方案

1. 静态动态分离

将标签生成过程分为静态配置和动态内容两部分:

@Data
public class TemplateConfig {private final TemplateType type;private final float pageWidth;private final float pageHeight;private final float margin;private final float xStep;private final float yStep;private final List<LabelPosition> positions;// 预计算所有标签位置public TemplateConfig(TemplateType type) {this.type = type;// 初始化基础参数...this.positions = calculatePositions();}
}

2. 引入Caffeine缓存

选择Caffeine作为缓存方案,主要考虑:

  • 超高性能:接近ConcurrentHashMap的读取速度
  • 智能回收:采用Window TinyLFU算法
  • 可观测性:完善的统计功能
public class LabelPdfCacheUtils {private static final Cache<String, TemplateConfig> templateCache = Caffeine.newBuilder().maximumSize(10).expireAfterWrite(1, TimeUnit.HOURS).recordStats().build();public static void createLabelPdf(...) {TemplateConfig config = templateCache.get(templateType.code,k -> new TemplateConfig(templateType));// 使用配置生成PDF...}
}

3. 优化PDF生成流程

private static void renderLabel(Document doc, PdfDocument pdfDoc,TemplateConfig config, PdfFont font, LabelPosition pos,String title, String condition, String barcodeValue) {// 1. 生成条形码Barcode128 barcode = new Barcode128(pdfDoc);barcode.setCode(barcodeValue);// 2. 创建图像Image barcodeImage = new Image(barcode.createFormXObject(pdfDoc));barcodeImage.setFixedPosition(pos.x, pos.y);// 3. 添加文本内容Paragraph titlePara = new Paragraph(title).setFont(font).setFontSize(config.fontSize);// 4. 设置位置并添加到文档doc.add(barcodeImage);doc.add(titlePara);
}

性能测试

编写全面的测试用例验证优化效果:

package com.sealinkin.oms.common.utils;import com.github.benmanes.caffeine.cache.stats.CacheStats;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;@Slf4j
public class LabelPdfGenerationTest {private static final String[] TEST_PRODUCTS = {"GeschenPark Gifts for 3-12 Year Old Girls, Kids Microphones for Kids Toys","Cool Mini Karaoke Machine Toys: Kids Toys Birthday Gifts Age 3-12+","助光 顔美化/肌美化ライト 仕事/勉強/美容化粧/ビデオカメラ撮影用","Test Product with Normal Length Name","Short Name"};private static final String[] TEST_CONDITIONS = {"NEW", "新品", "Used", "Refurbished"};private static final String[] TEST_BARCODES = {"X001LXRZRT", "X004IF0F27", "X001ZVG01J", "X00TESTBAR"};private static final String BASE_OUTPUT_DIR = "D:\\workspace\\label-test-output";private String outputPath;@BeforeEachvoid setUp() {// 创建输出目录File outputDir = new File(BASE_OUTPUT_DIR);if (!outputDir.exists()) {outputDir.mkdirs();}// 为每次测试创建带时间戳的子目录String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));outputPath = new File(outputDir, timestamp).getAbsolutePath();new File(outputPath).mkdirs();log.info("Test output directory: {}", outputPath);// 清除之前的缓存LabelPdfCacheUtils.clearCache();// 预热缓存LabelPdfCacheUtils.warmupCache();}@Testvoid testSingleLabelGeneration() {String fileName = "single_label_test.pdf";String filePath = new File(outputPath, fileName).getAbsolutePath();log.info("Testing single label generation...");// 使用原始方法生成long startTime = System.nanoTime();LabelPdfUtils.createLabelPdf1(filePath,TEST_PRODUCTS[0],TEST_CONDITIONS[0],TEST_BARCODES[0]);long originalTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);// 使用缓存方法生成startTime = System.nanoTime();LabelPdfCacheUtils.createLabelPdf(filePath,TEST_PRODUCTS[0],TEST_CONDITIONS[0],TEST_BARCODES[0],LabelPdfCacheUtils.TemplateType.TEMPLATE_1);long cachedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);log.info("Single label generation comparison:");log.info("Original implementation: {}ms", originalTime);log.info("Cached implementation: {}ms", cachedTime);log.info("Performance improvement: {:.2f}%",((double)(originalTime - cachedTime) / originalTime) * 100);}@Testvoid testBatchLabelGeneration() {int batchSize = 5; // 每种模板生成5个文件List<Long> original40Times = new ArrayList<>();List<Long> cached40Times = new ArrayList<>();List<Long> original44Times = new ArrayList<>();List<Long> cached44Times = new ArrayList<>();// 测试1出40标签log.info("Testing 1x40 label generation...");for (int i = 0; i < batchSize; i++) {String fileName = String.format("batch_40_test_%d.pdf", i);String filePath = new File(outputPath, fileName).getAbsolutePath();// 原始方法long startTime = System.nanoTime();LabelPdfUtils.createLabelPdf40(filePath,TEST_PRODUCTS[i % TEST_PRODUCTS.length],TEST_CONDITIONS[i % TEST_CONDITIONS.length],TEST_BARCODES[i % TEST_BARCODES.length]);original40Times.add(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));// 缓存方法startTime = System.nanoTime();LabelPdfCacheUtils.createLabelPdf(filePath,TEST_PRODUCTS[i % TEST_PRODUCTS.length],TEST_CONDITIONS[i % TEST_CONDITIONS.length],TEST_BARCODES[i % TEST_BARCODES.length],LabelPdfCacheUtils.TemplateType.TEMPLATE_40);cached40Times.add(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));}// 测试1出44标签log.info("Testing 1x44 label generation...");for (int i = 0; i < batchSize; i++) {String fileName = String.format("batch_44_test_%d.pdf", i);String filePath = new File(outputPath, fileName).getAbsolutePath();// 原始方法long startTime = System.nanoTime();LabelPdfUtils.createLabelPdf44(filePath,TEST_PRODUCTS[i % TEST_PRODUCTS.length],TEST_CONDITIONS[i % TEST_CONDITIONS.length],TEST_BARCODES[i % TEST_BARCODES.length]);original44Times.add(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));// 缓存方法startTime = System.nanoTime();LabelPdfCacheUtils.createLabelPdf(filePath,TEST_PRODUCTS[i % TEST_PRODUCTS.length],TEST_CONDITIONS[i % TEST_CONDITIONS.length],TEST_BARCODES[i % TEST_BARCODES.length],LabelPdfCacheUtils.TemplateType.TEMPLATE_44);cached44Times.add(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));}// 输出性能统计printPerformanceStats("1x40 Labels", original40Times, cached40Times);printPerformanceStats("1x44 Labels", original44Times, cached44Times);// 输出缓存统计printCacheStats();}@Testvoid testConcurrentLabelGeneration() throws InterruptedException {int threadCount = 4;int labelsPerThread = 5;Thread[] threads = new Thread[threadCount];log.info("Testing concurrent label generation with {} threads, {} labels per thread...",threadCount, labelsPerThread);for (int i = 0; i < threadCount; i++) {final int threadIndex = i;threads[i] = new Thread(() -> {for (int j = 0; j < labelsPerThread; j++) {String fileName = String.format("concurrent_test_thread%d_label%d.pdf",threadIndex, j);String filePath = new File(outputPath, fileName).getAbsolutePath();LabelPdfCacheUtils.createLabelPdf(filePath,TEST_PRODUCTS[j % TEST_PRODUCTS.length],TEST_CONDITIONS[j % TEST_CONDITIONS.length],TEST_BARCODES[j % TEST_BARCODES.length],LabelPdfCacheUtils.TemplateType.TEMPLATE_44);}});threads[i].start();}// 等待所有线程完成for (Thread thread : threads) {thread.join();}// 输出缓存统计printCacheStats();}private void printPerformanceStats(String testName, List<Long> originalTimes,List<Long> cachedTimes) {double avgOriginal = calculateAverage(originalTimes);double avgCached = calculateAverage(cachedTimes);double improvement = ((avgOriginal - avgCached) / avgOriginal) * 100;log.info("\nPerformance Statistics for {}:", testName);log.info("Original Implementation:");log.info("  Average: {:.2f}ms", avgOriginal);log.info("  Min: {}ms", originalTimes.stream().mapToLong(Long::longValue).min().orElse(0));log.info("  Max: {}ms", originalTimes.stream().mapToLong(Long::longValue).max().orElse(0));log.info("Cached Implementation:");log.info("  Average: {:.2f}ms", avgCached);log.info("  Min: {}ms", cachedTimes.stream().mapToLong(Long::longValue).min().orElse(0));log.info("  Max: {}ms", cachedTimes.stream().mapToLong(Long::longValue).max().orElse(0));log.info("Performance Improvement: {:.2f}%", improvement);}private void printCacheStats() {CacheStats stats = LabelPdfCacheUtils.getCacheStats();log.info("\nCache Statistics:");log.info("Hit Count: {}", stats.hitCount());log.info("Miss Count: {}", stats.missCount());log.info("Hit Rate: {:.2f}%", stats.hitRate() * 100);log.info("Average Load Penalty: {:.2f}ms", stats.averageLoadPenalty() / 1_000_000);log.info("Eviction Count: {}", stats.evictionCount());}private double calculateAverage(List<Long> times) {return times.stream().mapToLong(Long::longValue).average().orElse(0.0);}@AfterEachvoid tearDown() {log.info("\n========================================");log.info("Test output directory: {}", outputPath);log.info("Please check the generated files in this directory");log.info("========================================\n");}
}

测试结果:

  • 单标签生成:平均耗时从180ms降至110ms
  • 批量生成(40张):总耗时从7.2s降至4.3s
  • 内存使用:峰值降低约30%
  • 缓存命中率:稳定在95%以上

实现要点

1. 线程安全保证

public class LabelPdfCacheUtils {// Caffeine保证缓存操作的线程安全private static final Cache<String, TemplateConfig> templateCache;// 模板配置不可变@Valueprivate static class TemplateConfig {private final List<LabelPosition> positions;// 其他final字段...}
}

2. 流程图

参考资料

  1. iText 7文档:iText Core: an open-source PDF development library for Java and .NET
  2. Caffeine官方文档:https://github.com/ben-manes/caffeine
  3. Java性能优化实践:JDK 24 Documentation - Home

本文通过实际项目优化案例,展示了如何通过合理的技术方案提升系统性能。希望这些经验能够帮助到遇到类似问题的开发者。如果您有任何问题或建议,欢迎在评论区讨论交流。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com