带你读《2022技术人的百宝黑皮书》——谈一谈凑单页的那些优雅设计(3)

简介: 带你读《2022技术人的百宝黑皮书》——谈一谈凑单页的那些优雅设计(3)

带你读《2022技术人的百宝黑皮书》——谈一谈凑单页的那些优雅设计(2)https://developer.aliyun.com/article/1338385?groupCode=taobaotech

以上的代码相对比较完美了,却忽略了一个细节点,如果多台机器的本地缓存同时失效,恰好redis的可更新时间失    效了,这时就会有多个请求并发打到下游(由于凑单有本地缓存兜底,并发打到下游的个数非常有限,基本可以忽略)。但遇到问题就需要去解决,追求完美代码。我做了如下的改造:

private List<V> getCenter(String key, Callable<List<V>> loader, Function<String, List<V>> parse) throws Exception {
String updateKey = getUpdateKey(key); String value = centerCache.get(key);
boolean blankValue = StringUtils.isBlank(value);
List<V> cache = blankValue ? Collections.emptyList() : parse.apply(value);
// 如果抢不到锁,并且value没有过期
if (!centerCache.setNx(updateKey, currentTime) && !blankValue) { return cache;
}
centerCache.set(updateKey, currentTime, cacheUpdateSecond);
// 使用异步线程去更新value
CompletableFuture.runAsync(() -> updateCache(key, loader)); return cache;
}
private void updateCache(String key, Callable<List<V>> loader) { List<V> newCache = loader.call();
if (CollectionUtils.isNotEmpty(newCache)) {
centerCache.set(key, JSON.toJSONString(newCache), cacheExpireSecond);
}
}


 

本方案使用分布式锁 + 异步线程的方式来处理更新。只会有一个请求抢到更新锁,并发情况下,其他请求在可更新时间段内还是返回老数据。由于redis封装的方法中并没有抢锁后同时设置过期时间的原子性操作,我这里用了先抢    锁,再赋值过期时间的方式,在极端场景下可能会出现死锁的情况,就是刚好抢到了锁,然后机器出现异常宕机, 导致过期时间没有赋值上去,就会出现永远无法更新的情况。这种情况虽然极端,但还是要解,以下是我能想到的两个方案,我选择了第二种方式:

  1. 通过使用lua脚本将两步操作合成一个原子性操作
  2. image.png
  3. 利用value的过期时间来解该死锁问题

P.S. 一些从ThreadLocal中拿的通用信息,在使用异步线程处理的时候是拿不到的,得重新赋值

 

凑单核心处理流程设计

 


凑单本身是没有自己的数据源的,都是从其他服务读取,做各种加工后展示。这样的代码是最好写的,也是最难写的。就好比最简单的组装商品信息,一般的代码都会这么写:

 

  // 获取推荐商品
List<Map<String, String>> summaryItemList = recommend();
  List<ItemShow> itemShowList = summaryItemList.stream().map(v -> {
  ItemShow itemShow = new ItemShow();
// 设置商品基本信息
  itemShow.setItemId(NumberUtils.createLong(v.get("itemId")));
  itemShow.setItemImg(v.get("pic"));
  // 获取利益点
  GuideInfoDTO guideInfoDTO = new GuideInfoDTO();
  AtmosphereResult<Map<Long, List<AtmosphereFullDTO>>> atmosphereResult = guideAtmo-
  sphereClient
  .extract(guideInfoDTO, "gather", "item");
  List<IconText> iconTexts = parseAtmosphere(atmosphereResult);
  itemShow.setItemBenefits(iconTexts);
  // 预售处理
  String preSalePrice = getPreSale(v);
  if (Objects.nonNull(preSalePrice)) {
  itemShow.setItemPrice(preSalePrice); 18 }
 // ......
  return itemShow;
  }).collect(Collectors.toList());

 


能快速写好代码并投入使用,但代码有点杂乱无章,对代码要求比较高的开发者可能会做如下的改进

 

  // 获取推荐商品
  List<Map<String, String>> summaryItemList = recommend();
  List<ItemShow> itemShowList = summaryItemList.stream().map(v -> {
  ItemShow itemShow = new ItemShow();
  // 设置商品基本信息
  buildCommon(itemShow, v);
  // 获取利益点
  buildAtmosphere(itemShow, v);
  // 预售处理
  buildPreSale(itemShow, v); 11 // ......
  return itemShow;
  }).collect(Collectors.toList());


一般这样的代码算是比较优质的处理了,但这仅仅是针对单个业务,如果遇到多个业务需要使用该组装后,最简单但就是需要判断是来自feeds流模块的请求商品组装不需要利益点,来自前N秒杀模块的不需要处理预售价格。

 

  // 获取推荐商品
  List<Map<String, String>> summaryItemList = recommend();
  List<ItemShow> itemShowList = summaryItemList.stream().map(v -> {
  ItemShow itemShow = new ItemShow();
  // 设置商品基本信息
  buildCommon(itemShow, v);
  // 获取利益点
  if (!Objects.equals(soluction, FiltrateFeedsSolution.class)) {
  buildAtmosphere(itemShow, v);
 }
  // 预售处理
  if (!Objects.equals(source, "seckill")) {
  buildPreSale(itemShow, v);
 }
 // ......
  return itemShow;
  }).collect(Collectors.toList());

 

带你读《2022技术人的百宝黑皮书》——谈一谈凑单页的那些优雅设计(4)https://developer.aliyun.com/article/1338378?groupCode=taobaotech

相关文章
|
数据采集 机器学习/深度学习 数据挖掘
python数据分析——数据预处理
数据预处理是数据分析过程中不可或缺的一环,它的目的是为了使原始数据更加规整、清晰,以便于后续的数据分析和建模工作。在Python数据分析中,数据预处理通常包括数据清洗、数据转换和数据特征工程等步骤。
702 0
|
SQL 分布式计算 Hadoop
大数据行业部署实战1:Hadoop伪分布式部署
大数据行业部署实战1:Hadoop伪分布式部署
935 0
|
安全 网络安全 API
163邮箱IMAP服务器设置方法
```markdown 使用IMAP协议同步163邮箱:登录邮箱→设置→账户→IMAP/SMTP→开启服务→配置服务器(imap.163.com:993, SSL/TLS)→设置用户名和密码→保存并在邮件客户端添加账号。确保多设备邮件同步,定期更新设置。[≤240字符] ```
|
存储 缓存 Rust
一文读懂 Deno
一文读懂 Deno
648 0
|
机器学习/深度学习 算法 数据挖掘
【Python机器学习】Mean Shift、Kmeans聚类算法在图像分割中实战(附源码和数据集)
【Python机器学习】Mean Shift、Kmeans聚类算法在图像分割中实战(附源码和数据集)
637 0
【Python机器学习】Mean Shift、Kmeans聚类算法在图像分割中实战(附源码和数据集)
|
11月前
|
编解码 算法 安全
低功耗蓝牙心电采集处理模块
一款低功耗蓝牙心电采集处理模块
|
存储 内存技术
内存条RAM详细指南
内存条(RAM)是电脑中用于临时存储数据和程序的部件,CPU依赖它执行操作。内存条经历了从主内存扩展到读写内存整体的发展,常见类型包括SDRAM和DDR SDRAM。内存容量、存取时间和奇偶校验是衡量其性能的关键指标。在选购时,应考虑类型、容量、速度和品牌,知名品牌的内存条提供更好的可靠性和稳定性。
5575 2
|
设计模式 存储 缓存
前端必须掌握的设计模式——策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,旨在将多分支复杂逻辑解耦。每个分支类只关心自身实现,无需处理策略切换。它避免了大量if-else或switch-case代码,符合开闭原则。常见应用场景包括表单验证、风格切换和缓存调度等。通过定义接口和上下文类,策略模式实现了灵活的逻辑分离与扩展。例如,在国际化需求中,可根据语言切换不同的词条包,使代码更加简洁优雅。总结来说,策略模式简化了多条件判断,提升了代码的可维护性和扩展性。
|
算法 关系型数据库 MySQL
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现
在分布式系统中,确保每个节点生成的 ID 唯一且高效至关重要。Snowflake 算法由 Twitter 开发,通过 64 位 long 型数字生成全局唯一 ID,包括 1 位标识位、41 位时间戳、10 位机器 ID 和 12 位序列号。该算法具备全局唯一性、递增性、高可用性和高性能,适用于高并发场景,如电商促销时的大量订单生成。本文介绍了使用 Go 语言的 `bwmarrin/snowflake` 和 `sony/sonyflake` 库实现 Snowflake 算法的方法。
859 1
分布式唯一ID生成:深入理解Snowflake算法在Go中的实现