首页 > 应用开发 > Ext JS > 在实际开发中,不建议重复造轮子,基于成熟框架
第一步:引入 Redisson 依赖(Spring Boot 项目):
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.23.3</version></dependency>
第二步:配置 Redis 连接信息(application.yml):
spring: redis: host: 127.0.0.1 port: 6379 password: 123456 database: 0
第三步:使用 Redisson 实现分布式锁:
@Servicepublic class StockService { @Autowired
private RedissonClient redissonClient; @Autowired
private StockMapper stockMapper; public void deductStock(Long productId) { // 1. 获取锁(锁 key 按业务标识设计)
RLock lock = redissonClient.getLock("lock:stock:" + productId); try { // 2. 加锁(默认加锁时间 30 秒,内置 Watch Dog 续约机制)
lock.lock(); // 3. 执行业务逻辑(库存扣减)
Stock stock byven.com= stockMapper.selectById(productId); if (stock.getCount() > 0) {
stock.setCount(stock.getCount() - 1);
stockMapper.updateById(stock);
}
} finally { // 4. 释放锁(必须在 finally 中执行,避免业务报错导致锁未释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
上述代码已具备可重入、锁续约、自动释放等特性,同时适配 Redis 集群,可直接用于生产环境。
锁 key 设计要精准:锁 key 需对应具体的资源标识(如商品 ID、订单 ID),避免使用全局锁(如 “lock:stock”),否则会导致所有资源操作互斥,大幅降低系统并发量。
避免长时间持有锁:分布式锁是跨服务的同步机制,长时间持有锁会阻塞其他服务的请求,降低系统吞吐量。业务逻辑需尽量精简,避免在锁内执行耗时操作(如数据库复杂查询、第三方接口调用),若必须执行,可考虑异步处理。
设置合理的锁过期时间:过期时间需根据业务逻辑耗时合理设置,既不能太短(导致锁提前释放),也不能太长(服务宕机后锁释放慢,影响并发)。建议结合压测结果设置,同时借助 Redisson 的 Watch Dog 机制兜底。
处理加锁失败场景:加锁失败后,不能直接抛出异常,需根据业务场景处理(如重试、返回失败提示、加入队列等待)。重试时需设置重试次数和间隔,避免频繁重试导致 Redis 压力过大。
警惕 Redis 集群数据一致性问题:使用主从集群时,需注意主从数据同步延迟,可能导致从节点切换后锁丢失。若对一致性要求极高,建议使用 Redlock 算法。
Redis 分布式锁适用于大多数分布式并发场景,但在某些极端场景下并不适用,需选择其他方案:
强一致性要求场景:若业务要求绝对的强一致性(如金融交易),Redis 分布式锁可能因网络延迟、集群数据同步问题出现微小偏差,建议使用 Zookeeper 分布式锁或数据库悲观锁。
高并发写场景:若同一资源的并发写请求极高(每秒数万次),Redis 可能成为性能瓶颈,建议通过分段锁(将资源拆分多个片段,分别加锁)提升并发量。
Redis 分布式锁的核心是基于原子操作保证互斥性,再通过过期时间、锁续约、集群部署等机制解决死锁、单点故障、锁提前释放等问题。手动实现分布式锁需兼顾诸多细节,容易出现漏洞,生产环境优先推荐使用 Redisson 框架,其封装了完整的特性,开箱即用,能大幅提升开发效率和系统稳定性。
实际开发中,无需追求最复杂的方案,需结合业务场景选择合适的实现方式:简单场景可使用基础的 SET 命令 + Lua 脚本,高可用、高一致性场景可使用 Redisson + Redlock 算法。同时,牢记锁 key 设计、避免长时间持锁等避坑要点,才能让分布式锁真正成为解决并发问题的利器,而不是系统的新瓶颈。
WebDataset遵循严格的命名约定: 同一样本的所有文件共享相同的前缀key,通过扩展名区分数据类型。
前缀key:tar文件内部,某个文件的路径的第一个句点之前的部分
文件可以有多个后缀,甚至没有后缀(这样在字典中的键就是空字符);而且相同前缀key的(同一样本中的)文件数量可以不固定。
示例TAR文件内容结构:
images17/image194.left.jpg images17/image194.right.jpg images17/image194.json images17/image12.left.jpg images17/image12.json images3/image14
读取之后,会得到像这样的字典
[
{ “__key__”: “images17/image194”, “left.jpg”: b”...”, “right.jpg”: b”...”, “json”: b”...”}
{ “__key__”: “images17/image12”, “left.jpg”: b”...”, “json”: b”...”}
{ “__key__”: “images3/image14”, “”: b””}
import webdataset as wds import json def create_webdataset ( output_path, samples ): """创建WebDataset格式数据集""" with wds.TarWriter(output_path) as sink: for i, (image_data, label, metadata) in enumerate (samples): sink.write({ wap.hhnmsj.com "__key__" : f"sample {i:06d}" , # 样本唯一标识 "jpg" : image_data, # 图像数据(字节格式)
import torch from torchvision import transforms # 定义数据预处理 preprocess = transforms.Compose([ transforms.RandomResizedCrop( 224 ), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean=[ 0.485 , 0.456 , 0.406 ], std=[ 0.229 , 0.224 , 0.225 ]) ]) # 创建WebDataset数据管道 dataset = (wds.WebDataset( "dataset-{000000..000099}.tar" ) # 100个分片 .shuffle( 1000 ) # 样本级打乱 .decode( "pil" ) # 解码为PIL图像 .to_tuple( "jpg" , "cls" ) # 提取图像和标签 .map_tuple(preprocess, lambda x: int (x)) # 应用预处理 .batched( 32 ) # 批处理 ) # 创建DataLoader dataloader = torch.utils.data.DataLoader( dataset, batch_size= None , # 批处理已在管道中完成 num_workers= 4
"""创建高级数据处理管道""" # 图像增强 image_augmentation = transforms.Compose([ transforms.RandomChoice([ transforms.ColorJitter(brightness= 0.2 , contrast= 0.2 ), transforms.GaussianBlur( 3 ),wap.feasz.com.cn transforms.RandomAffine(degrees= 15 , scale=( 0.9 , 1.1 )) ]), transforms.RandomHorizontalFlip(), ]) # 文本预处理 def text_preprocessing ( text_bytes ): text = text_bytes.decode( "utf-8" ).lower().strip() # 应用文本清洗和分词逻辑 return text dataset = (wds.WebDataset( "multimodal-{000000..000050}.tar" ) .shuffle( 5000 ) # 大缓冲区提高随机性 .decode( "pil" , handler=wds.warn_and_continue) # 错误处理 .rename(image=feasz.com.cn "jpg;png;jpeg" , text= "txt;json" , caption= "caption;text" ) .map_dict( # 对不同字段应用不同处理 image=image_augmentation, text=text_preprocessing, caption=text_preprocessing ) .to_tuple( "image" , "text" , "caption" ) # 多模态输出 .batched( 16 , partial= False ) # 精确批大小控制
import torch.distributed as dist def setup_distributed_training (): """设置分布式训练环境""" # 初始化进程组 dist.init_process_group(backend= "nccl" ) local_rank = int (os.environ[ "LOCAL_RANK" ]) world_size = dist.get_world_size() # 根据rank配置设备 torch.cuda.set_device(local_rank) return local_rank, world_size def create_distributed_loader ( url_pattern, batch_size= 32 ): """创建分布式数据加载器""" local_rank, world_size wap.yesLamp.cn= setup_distributed_training() dataset =m.gdgyd.cn (wds.WebDataset( url_pattern, resampled= True , # 启用重采样以支持无限数据流 nodesplitter=wds.split_by_node, splitter=wds.split_by_worker )shmcjc.com .shuffle( 1000 ) .decode( "pil" ) .to_tuple( "jpg" , "cls" ) .batched(batch_size) ) loader m.shmcjc.com= wds.WebLoader( dataset, batch_size= None , num_workers= 4 , shuffle= False # 打乱已在数据管道中处理 ) # 设置epoch长度 loader = loader.with_epoch( 10000 ) # 每个epoch处理10000个批次
dataset = (wds.WebDataset( "dataset-{000000..012345}.tar" ) .shuffle( 10000 ) .decode( "torchrgb" ) # 直接解码为PyTorch张量 .split_by_node zbmingze.cn # 自动按节点分割数据 .split_by_worker wap.gdgyd.cn # 按工作进程分割 .to_tuple( "image" , "label" ) .batched( 64 ) ) # 使用WebLoader优化性能 loader = wds.WebLoader( dataset, batch_size= None , num_workers= 8 , persistent_workers= True # 保持工作进程活跃 )
def optimize_shard_size ( base_url, target_size_mb= 1024 ): """根据目标大小优化分片策略""" # 计算样本平均大小 cdsen.com sample_size m.nbemc.cn= estimate_average_sample_size() samples_per_shard gdgyd.cn= (target_size_mb * 1024 * 1024 ) // sample_size """流式视频处理避免内存溢出""" dataset = (wds.WebDataset( "video-dataset.tar" ) .shuffle( 100 ) .decode( "rgb8" , handler=wds.ignore_and_continue) # 流式解码 . map (video_frame_sampling) # 帧采样 . slice ( 0 , 100 ) m.cdsen.com # 限制序列长度 .batched( 1 ) # 视频批处理大小为1 )
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3109159/ ,如需转载,请注明出处,否则将追究法律责任。