java幂等性的控制(技术论坛上整理成文)

本文深入浅出地介绍了幂等性的概念及其在软件系统中的应用,包括HTTP协议、数据库操作及客户端控制等方面,并提供了具体的实现方法。

本文是从技术论坛上大家一人一句没有条理的讲解,我整理一下发到优快云上,希望对大家有用。

什么是幂等性

抄用一段数学上的定义:f(f(x)) = f(x)。x被函数f作用一次和作用无限次的结果是一样的。幂等性应用在软件系统中,我把它简单定义为:某个函数或者某个接口使用相同参数调用一次或者无限次,其造成的后果是一样的,在实际应用中一般针对于接口进行幂等性设计。举个栗子,在系统中,调用方A调用系统B的接口进行用户的扣费操作时,由于网络不稳定,A重试了N次该请求,那么不管B是否接收到多少次请求,都应该保证只会扣除该用户一次费用。

加深对幂等性的了解

幂等性一般应用于协议设计,TCP协议支持幂等吗?答案是肯定的,在网络不稳定时,操作系统可以肆无忌惮的重发TCP报文片段。TCP协议能够保证幂等的核心在于sequence number字段,一个序列号的在较长的一段时间内均不会出现重复。对于应用层的协议设计,原理和TCP是类似的,我们需要一个不重复的序列号。再简单一点说,在一个业务流程的处理中,我们需要一个不重复的业务流水号,以保证幂等性。
举个实际应用场景:用户A在网页上发起一笔游戏充值请求,浏览器引导用户去银行支付,支付成功后系统给用户进行充值。
协议设计上,我们通过全局唯一的充值订单号贯穿整个业务流程,使该业务支持幂等。
我们先从一个简单的程序理解一下幂等性:
public class Main {
    private int i = 0;
    
    //这个方法不具有幂等性,每调用一次,它就会改变Main的状态(即改变了i)
    public void idempotent() {
        i++;
    }
    
    //幂等性,无论这个方法调用多少次,它都不会改变Main类的状态。
    public void simple() {
        System.out.println(i);
    }
}
看完这些,你似乎对幂等性有了更深的了解。那么幂等性问题会出现在哪些场景呢?
电商,第三方支付,抢红包等场景。
这些应用场景,你似乎看到了他们的共同特征。对,那就是高并发。

幂等控制的实现

HTTP的幂等性

等表示:请求服务器一次或是多次,返回的结果均是一样的【select 】一般是GET请求

非幂等表示:请求服务器不同的次数,返回的结果将是不一样的[update   delete] 一般是POST请求

HTTP协议本身是一种面向资源的应用层协议,但对HTTP协议的使用实际上存在着两种不同的方式:一种是restful,它把HTTP当成应用层协议,另一种是SOA,它并没有完全把HTTP当成应用层协议,而是把HTTP协议作为了传输层协议,然后在HTTP之上建立了自己的应用层协议。

restful风格,想了解的可以去看看webservice编程,这里不是本文的主题。

本文所讨论的HTTP幂等性主要针对RESTful风格的,不过正如上一节所看到的那样,幂等性并不属于特定的协议,它是分布式系统的一种特性;所以,不论是SOA还是RESTful的Web API设计都应该考虑幂等性。

重要方法安全幂等
GET
POST
PUT
DELETE

数据库幂等

数据库上的幂等和事务是一体的。
1. 查询操作 
查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作 
 2. 删除操作 
删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个) 
3.唯一索引,防止新增脏数据 
比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录 
4.悲观锁 
获取数据的时候加锁获取 
select * from table_xxx where id='xxx' for update; 
注意:id字段一定是主键或者唯一索引,不然是锁表,会死人的 
悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用 
5. 乐观锁 
乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。

客户端幂等控制机制-token

业务要求: 
页面的数据只能被点击提交一次 
发生原因: 
由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交 
解决办法: 
集群环境:采用token加redis(redis单线程的,处理需要排队) 
单JVM环境:采用token加redis或token加jvm内存 
处理流程: 
1. 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间 
2. 提交后后台校验token,同时删除token,生成新的token返回 
token特点: 
要申请,一次有效性,可以限流 

后期整理电商案例,直接上图来说一下一个电商网站在幂等控制上的方法。(待续。。。)

<think>我们基于用户提供的“8MSPI,5ms周期每包发送1024字节”的要求,编写软件开发规范文档。参考之前的回答和引用内容,我们需要将技术细节转化为文档形式。文档应包含系统参数、硬件设计、软件架构、协议设计、错误处理、性能优化和验证方案等部分。同时,根据引用内容,我们可以借鉴一些设计思想(如使用DMA双缓冲、CRC校验、低功耗设计等)。注意:引用[1]提到I2C的初始化及读写流程,虽然接口不同,但初始化流程和读写函数的设计思想可以借鉴;引用[3]提到使用随机数填充空闲位,可用于我们的协议设计(如填充未使用的字节);引用[4]提到AUTOSAR架构的分层和通信,可参考其规范化的描述方式。下面我们按照软件开发规范文档的格式进行编写,同时注意数学表达式的格式要求。#SPI通信软件开发规范文档(V1.0)##1.概述本规范基于以下系统参数制定:-SPI时钟频率:8MHz-传输周期:5ms-每包数据量:1024字节##2.系统参数验证###2.1带宽需求-每包数据量:$1024\times8=8192$bit-理论带宽需求:$\frac{8192}{0.005}=1.6384$Mbps-SPI实际带宽:8MHz>1.6384Mbps,满足要求。###2.2时序裕量-单包传输时间:$T_{trans}=\frac{8192}{8\times10^6}=1.024$ms-周期内剩余时间:$5-1.024=3.976$ms,可用于数据处理和系统休眠。##3.硬件接口配置###3.1SPI初始化参考引用[1]中I2C初始化的设计思想,SPI初始化应包含以下配置(以STM32为例):```cSPI_HandleTypeDefhspi;hspi.Instance=SPI1;hspi.Init.Mode=SPI_MODE_MASTER;hspi.Init.Direction=SPI_DIRECTION_2LINES;hspi.Init.DataSize=SPI_DATASIZE_8BIT;hspi.Init.CLKPolarity=SPI_POLARITY_LOW;hspi.Init.CLKPhase=SPI_PHASE_1EDGE;hspi.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_2;//当系统时钟为16MHz时,分频后为8MHzhspi.Init.FirstBit=SPI_FIRSTBIT_MSB;HAL_SPI_Init(&hspi);```###3.2信号完整性-最大布线长度:$L_{max}=\frac{3\times10^8}{10\times8\times10^6}=3.75$m-阻抗匹配:50Ω差分阻抗(高速模式)-串联电阻:22Ω(抑制振铃)##4.软件架构设计###4.1数据传输机制采用DMA双缓冲机制(参考引用[1]中I2C读写流程的设计思想):```c//双缓冲定义uint8_ttxBuffer[2][1024];uint8_trxBuffer[2][1024];volatileuint8_tactiveBuffer=0;//定时器中断服务函数(5ms周期)voidTIMx_IRQHandler(void){if(TIM_GetITStatus(TIMx,TIM_IT_Update)!=RESET){//启动DMA传输当前缓冲区HAL_SPI_TransmitReceive_DMA(&hspi,txBuffer[activeBuffer],rxBuffer[activeBuffer],1024);//切换缓冲区并准备下一包数据activeBuffer^=1;PrepareData(txBuffer[activeBuffer]);//填充新数据TIM_ClearITPendingBit(TIMx,TIM_IT_Update);}}```###4.2协议设计数据帧结构如下(参考引用[3]中利用空闲位的思想):```┌───────────┬─────────────┬──────────┐│帧头(2B)│数据(1020B)│CRC(2B)│└───────────┴─────────────┴──────────┘```-**帧头**:0x55AA(同步码)-**数据域**:实际有效数据不足1020字节时,剩余字节用随机数填充(引用[3]的思想)-**CRC16**:采用CCITT多项式$x^{16}+x^{12}+x^5+1$,计算范围包括帧头和数据域##5.错误处理###5.1传输错误处理参考引用[1]的错误处理机制:```cvoidHAL_SPI_ErrorCallback(SPI_HandleTypeDef*hspi){if(hspi->ErrorCode&HAL_SPI_ERROR_OVR){//重新初始化SPIHAL_SPI_DeInit(hspi);HAL_SPI_Init(hspi);//重传当前包HAL_SPI_TransmitReceive_DMA(hspi,txBuffer[activeBuffer^1],rxBuffer[activeBuffer^1],1024);}}```###5.2CRC校验失败处理-接收端检测到CRC错误时,丢弃该包并发送NAK信号-发送端收到NAK后,在下一个周期重传上一包数据##6.性能优化###6.1低功耗设计在空闲时段进入低功耗模式(参考引用[4]的AUTOSAR架构思想):```cvoidSPI_TransferComplete_Callback(){//进入STOP模式HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);}```###6.2实时性保障-DMA传输优先级设置为最高(防止总线竞争)-使用硬件定时器精确触发传输(周期误差<±1%)##7.验证方案###7.1逻辑分析仪测试-测试指标:实际传输时间、时钟占空比(45%~55%)、信号完整性-测试方法:捕获连续10个周期,验证时序符合性###7.2压力测试-连续传输24小时,误码率<$10^{-9}$-周期抖动<±50μs(即±1%)##8.附录###8.1设计约束-DMA缓冲区必须32字节对齐(避免Cache一致性问题)-片选信号切换延迟>100ns-禁止在SPI传输过程中执行Flash写操作§§相关问题§§1.如何确保DMA双缓冲切换时的数据一致性?2.在SPI传输过程中如何处理突发高优先级中断?3.如何验证CRC随机错误检测能力?4.低功耗模式下如何快速唤醒响应SPI传输?5.是否有必要在协议层增加序列号机制?>本规范文档遵循AUTOSAR架构的分层设计思想(引用[4]),确保模块化实现。同时借鉴了I2C设备驱动设计模式(引用[1])和随机数填充策略(引用[3])。</think># SPI通信软件开发规范文档 **基于8MHz SPI、5ms周期、1024字节/包传输要求** --- ## 一、系统参数规范 ### 1.1 带宽与时序约束 - **理论带宽需求**: $$ \text{带宽} = \frac{1024 \times 8 \ \text{bit}}{0.005 \ \text{s}} = 1.6384 \ \text{Mbps} $$ - **SPI时钟能力**:8 MHz > 1.6384 Mbps ✅ - **单包传输时间**: $$ T_{\text{trans}} = \frac{1024 \times 8}{8 \times 10^6} = 1.024 \ \text{ms} $$ - **周期裕量**: $$ T_{\text{reserve}} = 5 - 1.024 = 3.976 \ \text{ms} $$ ### 1.2 时序分配 ```mermaid gantt title 5ms周期时序分配 dateFormat ms axisFormat %L section 传输周期 数据发送 :a1, 0, 1.024 数据处理 :a2, after a1, 1.5 系统休眠 :a3, after a2, 2.476 ``` --- ## 二、硬件层设计规范 ### 2.1 SPI接口配置 ```c // STM32 HAL库配置示例 SPI_HandleTypeDef hspi; hspi.Instance = SPI1; hspi.Init.Mode = SPI_MODE_MASTER; hspi.Init.Direction = SPI_DIRECTION_2LINES; hspi.Init.DataSize = SPI_DATASIZE_8BIT; // 8位传输 hspi.Init.CLKPolarity = SPI_POLARITY_LOW; // 模式0 hspi.Init.CLKPhase = SPI_PHASE_1EDGE; hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; // 8MHz @ 16MHz APB2 hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; HAL_SPI_Init(&hspi); ``` ### 2.2 信号完整性要求 | 参数 | 要求 | 计算公式 | |------|------|----------| | 最大布线长度 | ≤ 3.75 m | $ L_{\max} = \frac{3 \times 10^8}{10 \times 8 \times 10^6} $ | | 阻抗匹配 | 50Ω ±10% | - | | 串联电阻 | 22Ω | 抑制振铃 | --- ## 三、软件架构设计 ### 3.1 DMA双缓冲机制 ```c #define PACKET_SIZE 1024 uint8_t tx_buf[2][PACKET_SIZE] __attribute__((aligned(32))); // 32字节对齐 uint8_t rx_buf[2][PACKET_SIZE] __attribute__((aligned(32))); volatile uint8_t active_buf = 0; // 定时器中断回调(5ms触发) void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // 启动非阻塞传输 HAL_SPI_TransmitReceive_DMA(&hspi, tx_buf[active_buf], rx_buf[active_buf], PACKET_SIZE); // 切换并准备下一包 active_buf ^= 0x01; Prepare_Next_Packet(tx_buf[active_buf]); // 数据封装函数 } ``` ### 3.2 协议帧结构 ``` ┌────────────┬──────────────────┬──────────┐ │ 帧头(2字节) │ 有效数据(1020字节) │ CRC(2字节)│ └────────────┴──────────────────┴──────────┘ ``` - **帧头**:0x55AA(同步标志) - **有效数据**: - 空闲位填充随机数(引用[3]策略) - **CRC16**:CCITT多项式 $x^{16} + x^{12} + x^5 + 1$ --- ## 四、错误处理机制 ### 4.1 传输错误恢复 ```c void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if(hspi->ErrorCode & HAL_SPI_ERROR_OVR) { __disable_irq(); SPI_Reinit(); // 硬件复位 Retransmit_Packet(tx_buf[active_buf^0x01]); __enable_irq(); } } ``` ### 4.2 CRC校验策略 ```c // 接收端校验流程 if(Verify_CRC16(rx_buf, PACKET_SIZE) != SUCCESS) { Send_NAK(); // 请求重传 Log_Error(ERR_CRC_MISMATCH); } ``` --- ## 五、低功耗设计 ### 5.1 睡眠模式管理 ```c void SPI_TransferCompleteCallback() { HAL_SPI_DMAStop(&hspi); // 进入STOP模式(保留RAM状态) HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); } ``` --- ## 六、验证与测试 ### 6.1 测试指标 | 测试项 | 要求 | 工具 | |--------|------|------| | 周期抖动 | < ±50μs | 逻辑分析仪 | | 误码率 | < $10^{-9}$ | 压力测试工具 | | 唤醒延迟 | < 10μs | 示波器 | ### 6.2 自动化测试脚本 ```python # Saleae逻辑分析仪验证脚本 analyzer.capture_settings = { 'SPI': { 'clock': 8000000, 'bits_per_transfer': PACKET_SIZE * 8 }, 'duration': 0.005, # 5ms周期 'trigger': 'CS_FALLING' } ``` --- ## 七、设计约束 1. **内存对齐**:DMA缓冲区必须32字节对齐 2. **时序约束**: - 片选信号切换延迟 > 100ns - 时钟占空比 45%~55% 3. **中断优先级**: - DMA传输中断 > 定时器中断 > 应用中断 > **设计准则**:采用AUTOSAR分层架构思想(引用[4]),通信层与业务逻辑层隔离,确保实时性[^1]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值