Spring Cache
接口的幂等性
每年的双十一,618,电商系统都会面临这超高的流量,如果一个订单被反复提交,那电商系统如何保证这个订单之后执行一次减库存,扣款的操作?
概念
接口幂等性是指无论对同一个接口进行多少次相同的操作,最终的结果都是相同的。换句话说,即使对同一个接口发送多次相同的请求,也不会改变服务器的状态或产生额外的副作用。
防重和幂等性
防重
只执行一次请求
防重(Duplicate Request Prevention)是一种机制,用于阻止或处理重复的请求。在防重机制中,系统会对每个请求进行标记或记录,以确定该请求是否已经处理过或正在处理中。当系统接收到重复的请求时,防重机制可以根据之前的标记或记录来判断是否需要忽略该请求或返回已处理的结果。防重通常用于防止重复提交表单、重复发起支付请求等场景。
幂等性
保证执行多次同样接口的结果不变
幂等性(Idempotence)是一种属性,用于描述一个操作或接口的特性。一个幂等的操作意味着无论执行多少次,最终的效果都相同。对于一个幂等的接口,重复调用相同的请求不会对系统的状态造成变化或产生额外的副作用。幂等性通常与接口的设计和实现有关,确保在重复请求的情况下,系统的状态仍然保持一致。
防重更多是从防止错误的重复执行角度出发,确保操作不被执行多次;而幂等性是从操作结果的一致性角度出发,强调的是无论操作执行多少次,系统的最终状态都应该是相同的。
幂等性是目标,防重是达成这一目标的策略之一
SQL幂等性
select
天然自带幂等性
select作用为查询数据库,无论执行多少次都不会对数据库的数据有影响
insert
情况1:主键自增,执行多次,会添加多条数据
情况2:使用指定使用业务id,无论执行多少次,结果只会插入一条
delete
第一种情况:绝对删除,具有幂等性。
eg;delete from order where id = 3 。
无论该sql执行多少次,对结果集产生的效果都是一样只删除了一条数据。
第二种情况: 相对删除,不具有幂等性。
eg:delete from order where id > 23 .
该操作每执行一次,对结果集产生的结果,可能都不一样,同一操作多次执行对数据产生了副作用。
update
第一种情况:绝对更新,具有幂等性。
eg:update good set stock= 586 where id = 10;
该操作无论执行多少次操作对结果的影响都是一样。
第二种情况:相对更新,不具有幂等性。
eg: update good set stock = stock+10 where id= 10 ;
每次执行该操作库存数量都会加10,所以不具备幂等操作。
Restful API 幂等性
为什么要实现幂等性
保证幂等性的原因有以下几点:
- 防止前端重复提交表单:当用户在前端页面上多次点击提交按钮时,可能会导致后端接收到多个相同的请求。为了保证数据的一致性和避免重复处理,需要确保每个请求只被处理一次。
案例:用户在购物车中添加了多个相同的商品,然后多次点击提交订单按钮。如果不保证幂等性,可能会导致订单被重复创建或者库存被重复扣减。
- 防止用户恶意发送请求:有些恶意用户可能会通过模拟请求或使用自动化工具来发送大量重复的请求,以试图对系统造成破坏或获取非法利益。为了保证系统的稳定和安全,需要确保每个请求只被处理一次。
案例:黑客利用漏洞向服务器发送大量重复的请求,试图使服务器崩溃或导致其他用户无法正常使用服务。
- 防止接口超时重复提交:在某些情况下,由于网络延迟或其他原因,客户端可能无法及时收到服务器的响应。如果客户端认为请求失败并重新发送请求,可能会导致服务器接收到多个相同的请求。为了保证数据的一致性和避免重复处理,需要确保每个请求只被处理一次。
案例:用户在支付过程中因为网络延迟而多次点击支付按钮,导致服务器接收到多个相同的支付请求。如果不保证幂等性,可能会导致重复扣款或订单状态错误。
- 防止消息出现重复消费:在使用消息队列进行异步处理的场景中,由于网络故障、消费者异常等原因,可能会导致消息被重复消费。为了保证业务逻辑的正确性和数据的准确性,需要确保每个消息只被处理一次。
案例:在电商系统中,用户下单后会生成一个订单消息发送到消息队列中。如果消费者没有正确处理重复消费的情况,可能会导致订单被重复处理,从而影响库存和订单状态。
实现幂等性的方案
- 基于防重Token令牌实现
①. 服务端提供获取TOKEN的接口;
②. 客户端向服务端发起请求前,第一步先获取TOKEN,将该TOKEN存入REDIS后并将TOKEN返回给客户端;
③. 第二步,客户端在HEADER中携带该TOKEN向服务端发起业务请求;
④. 服务端接收请求从HEADER中解析TOKEN,并在REDIS中查找该TOKEN;
⑤. 如果TOKEN存在,则删掉该TOKEN,继续执行下面的业务操作;
⑥. 如果TOKEN不存在,说明第一次请求已将TOKEN删除,当前的请求并不是第一次请求,则返回重复操作的信息。
- 业务唯一id(项目采用)
幂等,可以通过唯一的业务单号来保证。也就是说相同的业务单号,无论请求多少次认为是同一笔业务,相同的业务单号的处理逻辑和执行效果是一致的。
比如银行第三方支付,需要传递订单号,保证业务的唯一性
- 缓存结果:将操作的结果暂时缓存起来,下次同样的请求直接返回缓存结果,避免重复执行。
Spring Cache
概念
Spring Cache提供了一套简单而强大的机制,通过少量的注解即可为Spring应用加入缓存功能,极大地简化了缓存逻辑的实现和维护工作。对于希望优化性能的Spring应用来说,掌握并合理利用Spring Cache是提升响应速度和系统吞吐量的有效手段。
- Spring Cache是Spring提供的一个缓存框架,在Spring3.1版本开始支持将缓存添加到现有的spring应用程序中,在4.1开始,缓存已支持JSR-107注释和更多自定义的选项。
- Spring Cache利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了,做到了对代码侵入性做小。
- 由于市面上的缓存工具实在太多,SpringCache框架还提供了CacheManager接口,可以实现降低对各种缓存框架的耦合。它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如Caffeine、Guava Cache、Ehcache、Redis。
注解使用
哪些注解
- CacheManager:负责管理缓存组件的创建、配置和获取。
- Cache:实际的缓存组件,用于存储缓存数据。
- @EnableCaching:开启基于注解的缓存支持。(加在配置类)
- @Cacheable:标记在方法上,表示该方法的返回结果需要被缓存。(结合GetMapping请求的方法)
- @CachePut:用于保证方法被调用后,将结果放入缓存中。(结合PutMapping请求的方法)
- @CacheEvict:用于清除缓存的数据。(结合DeleteMapping请求的方法)
解决缓存击穿
@Cacheable(value = "product.selectOne#1800",sync = true)
作用
作用是在执行方法之前检查缓存中是否存在对应的数据,如果存在则直接返回缓存数据,否则执行方法并将结果存入缓存中。
value
属性指定了缓存的名称和过期时间(#1800,单位:秒)sync
属性表示是否同步执行缓存操作。sync = true
:- 当
sync
属性设置为true
时,表示缓存更新操作将以同步方式进行。这意味着在缓存中存储数据的过程中,当前线程将阻塞,直到缓存操作完成。这保证了缓存操作的原子性,即要么全部完成,要么全部不完成,这对于确保缓存数据的一致性非常关键,特别是在高并发环境下。
- 当
sync = false
:- 当
sync
属性设置为false
(这是默认值)时,表示缓存更新操作将以异步方式进行。这意味着在缓存中存储数据的操作将不会阻塞当前线程,而是由一个内部的异步机制处理。这可以提高应用的响应速度,因为主线程不需要等待缓存操作完成就可以继续执行。但是,这也意味着缓存更新操作的成功与否不能立即得知,存在一定的数据不一致风险,尤其是当缓存更新失败时,应用可能不会立即意识到这个问题。
- 当
解决缓存穿透
spring.cache.redis.cache-null-values=true
当你设置spring.cache.redis.cache-null-values=true
时,即使方法返回了null,这个返回值(采用官方提供的”org.springframework.cache.support.NullValue“作为null的占位符)也会被存储在Redis缓存中。这样,在下一次调用相同的方法时,如果缓存有效,将直接从缓存中获取到null值,而不会执行实际的方法代码。这在某些特定的应用场景下可能是有用的,例如,当方法可能返回null,且这个信息本身就是有意义的(比如表示查询的结果不存在)时候。
源码地址
https://codeup.aliyun.com/62858d45487c500c27f5aab5/Springcache-Redis
中获取到null值,而不会执行实际的方法代码。这在某些特定的应用场景下可能是有用的,例如,当方法可能返回null,且这个信息本身就是有意义的(比如表示查询的结果不存在)时候。
源码地址
https://codeup.aliyun.com/62858d45487c500c27f5aab5/Springcache-Redis