Java日知录

一个坚持原创,有态度的博客!

0%

大家好,我是飘渺。如果你的微服务需要向第三方开放接口,如何确保你提供的接口是安全的呢?

1. 什么是安全接口

通常来说,要将暴露在外网的 API 接口视为安全接口,需要实现防篡改防重放的功能。

1.1 什么是篡改问题?

由于 HTTP 是一种无状态协议,服务端无法确定客户端发送的请求是否合法,也不了解请求中的参数是否正确。以一个充值接口为例:

http://localhost/api/user/recharge?user_id=1001&amount=10

如果非法用户通过抓包获取接口参数并修改 user_id 或 amount 的值,就能为任意账户添加余额。

1.1.1 如何解决篡改问题?

虽然使用 HTTPS 协议能对传输的明文进行加密,但黑客仍可截获数据包进行重放攻击。两种通用解决方案是:

  1. 使用 HTTPS 加密接口数据传输,即使被黑客破解,也需要耗费大量时间和精力。
  2. 在接口后台对请求参数进行签名验证,以防止黑客篡改。
阅读全文 »

大家好,我是飘渺。今天继续DDD&微服务专栏。

在之前的文章Dailymart21:基于DDD的订单创建流程中,我们留下了一个问题:在createOrder()方法中,我将调用远程接口获取购物车详情、远程库存校验、订单保存放在一个事务中,显然这并不是一个正确的做法,因为它会导致长事务,今天就让我们来解决这个问题。

image-20231222230113709

为什么会产生长事务

首先,让我们来分析一下产生长事务的原因。

在Spring中,@Transactional注解是基于AOP实现的,本质上是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在方法执行后,根据实际情况选择提交或回滚事务。

当Spring遇到该注解时,会自动从数据库连接池中获取连接并开启事务,然后绑定到ThreadLocal上,对于@Transactional注解包裹的整个方法都是使用同一个连接。如果出现耗时的操作,如第三方接口调用、业务逻辑复杂、大批量数据处理等,就会导致占用连接的时间很长,数据库连接一直被占用不释放。一旦类似操作过多,就会导致数据库连接池耗尽。

在开头的实例中,一个事务中执行RPC操作是典型的长事务问题。类似的操作还包括在事务中进行大量数据查询、业务规则处理等。

阅读全文 »

大家好,我是飘渺。今天继续更新DDD&微服务专栏,本篇主要与大家探讨一下在Dailymart中如何定时关闭未支付的订单。

概述

之前的文章提及过,在DailyMart项目中,我们采用了预扣模式进行库存扣减。预扣模式的核心思想是在用户下单时提前扣减库存,在规定时间内完成支付,否则系统将释放预扣的库存。

这种模式的应用需要确保及时关闭未支付订单并释放库存,以避免商家出现库存不足导致少卖的问题。在系统开发中,类似的场景也有很多,例如到期自动收货、超时自动退款、下单后自动发送短信等。

本文旨在从这类业务问题出发,深入探讨可行的技术方案、实现细节,以及相关方案的优缺点。最后,将回顾DailyMart是如何解决这一问题的。由于篇幅有限,本文将主要聚焦于方案的阐述,而不涉及具体的代码实现。

一、定时任务

定时任务关闭订单是一个较为直观的方案,很多小型项目均是基于此方案实现。

具体实现是通过调度平台执行定时任务,扫描所有即将到期的订单并执行关单动作。这种方案的优势在于简单易实现,可基于Timer、ScheduledThreadPoolExecutor,或像xxl-job这类调度框架来实现。然而,此方案会存在以下几个问题:

1、时间不精准 :

定时任务基于固定的频率或时间执行,可能导致一些订单已经超时,但定时任务尚未触发,使得实际关闭时间延迟。

2、无法处理大订单量

定时任务将关闭操作集中在一段时间内,当订单量较大时,任务执行时间可能较长,延迟订单扫描和关闭时间。

3、对数据库造成压力

定时任务集中扫描数据库表,可能在短时间内占用大量数据库IO,若未进行良好隔离,可能影响线上正常业务。

4、分库分表问题:

在订单系统中,一旦订单量大就可能会考虑分库分表,而在分库分表中执行全表扫描,这是一个极不推荐的做法。

阅读全文 »

1. 每次new一个

在SpringBoot项目中要实现对象与Json字符串的互转,每次都需要像如下一样new 一个ObjectMapper对象:

public UserEntity string2Obj(String json) throws JsonProcessingException {
	ObjectMapper objectMapper = new ObjectMapper();
	return objectMapper.readValue(json, UserEntity.class);
}

public String obj2String(UserEntity userEntity) throws JsonProcessingException {
	ObjectMapper objectMapper = new ObjectMapper();
	return objectMapper.writeValueAsString(car)
}

这样的代码到处可见,有问题吗?

你要说他有问题吧,确实能正常执行;可你要说没问题吧,在追求性能的同学眼里,这属实算是十恶不赦的代码了。

首先,让我们用JMH对这段代码做一个基准测试,让大家对其性能有个大概的了解。

阅读全文 »

微服务开发中经常会使用消息队列进行跨服务通信。在一个典型场景中,服务A执行一个业务逻辑,需要保存数据库,然后通知服务B执行相应的业务逻辑。在这种场景下,我们需要考虑如何发送消息。

image-20231217154706387

阅读全文 »

大家好,我是飘渺。在今天的DDD与微服务系列文章中,让我们探讨如何在DDD的分层架构中调用第三方服务以及在微服务中使用OpenFeign的最佳实践。

1. DDD中的防腐层

在应用服务中,经常需要调用外部服务接口来实现某些业务功能,这就在代码层面引入了对外部系统的依赖。例如,下面这段转账的代码逻辑需要调用外部接口服务RemoteService来获取汇率。

public class TransferServiceImpl implements TransferService{
	private RemoteService remoteService;
	@Override
  public void transfer(Long sourceUserId, String targetUserId, BigDecimal targetAmount){
		//...
		ExchangeRateRemote exchangeRate = remoteService.getExchangeRate(
sourceAccount.getCurrency(), targetCurrency);
		BigDecimal rate = exchangeRate.getRate();
  }
	  //...
}

这里可以看到,TransferService强烈依赖于RemoteServiceExchangeRateRemote对象。如果外部服务的方法或ExchangeRateRemote字段发生变化,都会影响到ApplicationService的代码。当有多个服务依赖此外部接口时,迁移和改造的成本将会巨大。同时,外部依赖的兜底、限流和熔断策略也会受到影响。

在复杂系统中,我们应该尽量避免自己的代码因为外部系统的变化而修改。那么如何实现对外部系统的隔离呢?答案就是引入防腐层(Anti-Corruption Layer,简称ACL)。

阅读全文 »

1 文章概述

商品在电商领域中是一个非常重要的领域,交易行为前提是有商品信息存在。本文我们分析商品表基本设计,其它复杂场景可以在此基础上进行扩展。需要说明第一本文所用数据是测试数据,可能与真实数据有偏差,仅供演示。第二本文展示商品核心字段,一些通用字段不展示。

2 商品类目

2.1 基本信息

类目表示商品分类并且具有层级关系:

  • 一级类目:图书

    • 二级类目:文学
      • 三级类目:小说
  • 一级类目:电脑

    • 二级类目:电脑配件
      • 三级类目:显卡
  • 一级类目:生鲜

    • 二级类目:水果
    • 三级类目:苹果

2.2 三种类目

2.2.1 后台类目

后台类目有两个特点:标准和稳定。标准表示后台类目是业界通用的,并且层级不宜过多,通常不超过三级。稳定表示后台类目一旦确定不能轻易修改,否则设计上下游大量数据变更,工作量非常大,所以变更权限必须收敛到平台运营。

阅读全文 »

欢迎回来,我是飘渺。今天继续更新DDD&微服务的系列文章。

在前面的文章中,我们深入探讨了DDD的核心概念。我理解,对于初次接触这些概念的你来说,可能难以一次性完全记住。但别担心,学习DDD并不仅仅是理论的理解,更重要的是将这些理论应用到实践中,理解其设计原则和实施方法。就如同编程界的一句流行格言所说:“Don’t talk, Show me the Code”。

今天,我们将以实现用户注册流程为例,一步步展示如何在实践中应用DDD的设计思想和技术手段,这将有助于你更好地理解并记住DDD的核心概念。让我们一起开始吧!

1. 实现领域层

在DDD的四层架构中,领域层扮演着核心角色。因此,我们首先着手实现这一层,其模块包结构如下:

阅读全文 »

大家好,我是飘渺。今天继续更新DDD&微服务的系列文章。

在专栏开篇提到过DDD(Domain-Driven Design,领域驱动设计)学习起来较为复杂,一方面因为其自身涉及的概念颇多,另一方面,我们往往缺乏实战经验和明确的代码模型指导。今天,我们将专注于DDD的分层架构和实体模型,期望为大家落地DDD提供一些有益的参考。首先,让我们回顾一下熟悉的MVC三层架构。

1. MVC 架构

在传统应用程序中,我们通常采用经典的MVC(Model-View-Controller)架构进行开发,它将整体的系统分成了 Model(模型),View(视图)和 Controller(控制器)三个层次,也就是将用户视图和业务处理隔离开,并且通过控制器连接起来,很好地实现了表现和逻辑的解耦,是一种标准的软件分层架构。

在遵循此分层架构的开发过程中,我们通常会建立三个Maven Module:Controller、Service 和 Dao,它们分别对应表现层、逻辑层和数据访问层,如下图所示:

image-20230602123152660

(图中多画了一个Model层是因为 Model 通常只是简单的 Java Bean,只包含数据库表对应的属性。有的应用会将其单独抽取出来作为一个Maven Module,但实际上它可以合并到 DAO 层。)

阅读全文 »

在我们之前设计的一个供应链系统中,它包含了商品、销售订单、加盟商、门店运营、门店工单等服务,涉及了各种用户角色,比如总部商品管理、总部门店管理、加盟商员工、门店人员等,而且每个部门的角色还会进行细分。而且这个系统中还包含了两个客户端 App:一个面向客户,另一个面向公司员工和加盟商。

此时,整个供应链系统的架构如下图所示:

上图中的网关层主要负责路由、认证、监控、限流熔断等工作。

阅读全文 »