欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解如何利用Redis實現(xiàn)生成唯一ID

 更新時間:2022年11月17日 09:01:59   作者:鴨血粉絲Tang  
隨著下單流量逐漸上升,為了降低數(shù)據(jù)庫的訪問壓力,需要通過請求唯一ID+redis分布式鎖來防止接口重復(fù)提交。今天我們就一起來看探討一下,如何通過服務(wù)端來完成請求唯一?ID?的生成

一、摘要

上一篇文章中,我們詳細(xì)的介紹了隨著下單流量逐漸上升,為了降低數(shù)據(jù)庫的訪問壓力,通過請求唯一ID+redis分布式鎖來防止接口重復(fù)提交,流程圖如下!

每次提交的時候,需要先調(diào)用后端服務(wù)獲取請求唯一ID,然后才能提交。

對于這樣的流程,不少的同學(xué)可能會感覺到非常雞肋,尤其是單元測試,需要每次先獲取submitToken值,然后才能提交!

能不能不用這么麻煩,直接服務(wù)端通過一些規(guī)則組合,生成本次請求唯一ID呢?

答案是可以的!

今天我們就一起來看看,如何通過服務(wù)端來完成請求唯一 ID 的生成?

二、方案實踐

我們先來看一張圖,這張圖就是本次方案的核心流程圖。

實現(xiàn)的邏輯,流程如下:

  • 1.用戶點擊提交按鈕,服務(wù)端接受到請求后,通過規(guī)則計算出本次請求唯一ID值
  • 2.使用redis的分布式鎖服務(wù),對請求 ID 在限定的時間內(nèi)嘗試進(jìn)行加鎖,如果加鎖成功,繼續(xù)后續(xù)流程;如果加鎖失敗,說明服務(wù)正在處理,請勿重復(fù)提交
  • 3.最后一步,如果加鎖成功后,需要將鎖手動釋放掉,以免再次請求時,提示同樣的信息

引入緩存服務(wù)后,防止重復(fù)提交的大體思路如上,實踐代碼如下!

2.1、引入 redis 組件

本次 demo 項目是基于SpringBoot版本進(jìn)行構(gòu)建,添加相關(guān)的redis依賴環(huán)境如下:

<!--?引入springboot?-->
<parent>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-parent</artifactId>
????<version>2.1.0.RELEASE</version>
</parent>

......

<!--?Redis相關(guān)依賴包,采用jedis作為客戶端?-->
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-data-redis</artifactId>
????<exclusions>
????????<exclusion>
????????????<groupId>redis.clients</groupId>
????????????<artifactId>jedis</artifactId>
????????</exclusion>
????????<exclusion>
????????????<artifactId>lettuce-core</artifactId>
????????????<groupId>io.lettuce</groupId>
????????</exclusion>
????</exclusions>
</dependency>
<dependency>
????<groupId>redis.clients</groupId>
????<artifactId>jedis</artifactId>
</dependency>
<dependency>
????<groupId>org.apache.commons</groupId>
????<artifactId>commons-pool2</artifactId>
</dependency>

2.2、添加 redis 環(huán)境配置

在全局配置application.properties文件中,添加redis相關(guān)服務(wù)配置如下

#?項目名
spring.application.name=springboot-example-submit

#?Redis數(shù)據(jù)庫索引(默認(rèn)為0)
spring.redis.database=1
#?Redis服務(wù)器地址
spring.redis.host=127.0.0.1
#?Redis服務(wù)器連接端口
spring.redis.port=6379
#?Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=
#?Redis服務(wù)器連接超時配置
spring.redis.timeout=1000

#?連接池配置
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=1000
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.time-between-eviction-runs=100

2.3、編寫服務(wù)驗證邏輯,通過 aop 代理方式實現(xiàn)

首先創(chuàng)建一個@SubmitLimit注解,通過這個注解來進(jìn)行方法代理攔截!

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public?@interface?SubmitLimit?{

????/**
?????*?指定時間內(nèi)不可重復(fù)提交(僅相對上一次發(fā)起請求時間差),單位毫秒
?????*?@return
?????*/
????int?waitTime()?default?1000;

????/**
?????*?指定請求頭部key,可以組合生成簽名
?????*?@return
?????*/
????String[]?customerHeaders()?default?{};


????/**
?????*?自定義重復(fù)提交提示語
?????*?@return
?????*/
????String?customerTipMsg()?default?"";
}

編寫方法代理服務(wù),增加防止重復(fù)提交的驗證,實現(xiàn)了邏輯如下!

@Order(1)
@Aspect
@Component
public?class?SubmitLimitAspect?{

????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(SubmitLimitAspect.class);

????/**
?????*?redis分割符
?????*/
????private?static?final?String?REDIS_SEPARATOR?=?":";

????/**
?????*?默認(rèn)鎖對應(yīng)的值
?????*/
????private?static?final?String?DEFAULT_LOCK_VALUE?=?"DEFAULT_SUBMIT_LOCK_VALUE";

????/**
?????*?默認(rèn)重復(fù)提交提示語
?????*/
????private?static?final?String?DEFAULT_TIP_MSG?=?"服務(wù)正在處理,請勿重復(fù)提交!";


????@Value("${spring.application.name}")
????private?String?applicationName;

????@Autowired
????private?RedisLockService?redisLockService;


????/**
?????*?方法調(diào)用環(huán)繞攔截
?????*/
????@Around(value?=?"@annotation(com.example.submittoken.config.annotation.SubmitLimit)")
????public?Object?doAround(ProceedingJoinPoint?joinPoint){
????????HttpServletRequest?request?=?getHttpServletRequest();
????????if(Objects.isNull(request)){
????????????return?ResResult.getSysError("請求參數(shù)不能為空!");
????????}
????????//獲取注解配置的參數(shù)
????????SubmitLimit?submitLimit?=?getSubmitLimit(joinPoint);
????????//組合生成key,通過key實現(xiàn)加鎖和解鎖
????????String?lockKey?=?buildSubmitLimitKey(joinPoint,?request,?submitLimit.customerHeaders());
????????//嘗試在指定的時間內(nèi)加鎖
????????boolean?lock?=?redisLockService.tryLock(lockKey,?DEFAULT_LOCK_VALUE,?Duration.ofMillis(submitLimit.waitTime()));
????????if(!lock){
????????????String?tipMsg?=?StringUtils.isEmpty(submitLimit.customerTipMsg())???DEFAULT_TIP_MSG?:?submitLimit.customerTipMsg();
????????????return?ResResult.getSysError(tipMsg);
????????}
????????try?{
????????????//繼續(xù)執(zhí)行后續(xù)流程
????????????return?execute(joinPoint);
????????}?finally?{
????????????//執(zhí)行完畢之后,手動將鎖釋放
????????????redisLockService.releaseLock(lockKey,?DEFAULT_LOCK_VALUE);
????????}
????}

????/**
?????*?執(zhí)行任務(wù)
?????*?@param?joinPoint
?????*?@return
?????*/
????private?Object?execute(ProceedingJoinPoint?joinPoint){
????????try?{
????????????return?joinPoint.proceed();
????????}?catch?(CommonException?e)?{
????????????return?ResResult.getSysError(e.getMessage());
????????}?catch?(Throwable?e)?{
????????????LOGGER.error("業(yè)務(wù)處理發(fā)生異常,錯誤信息:",e);
????????????return?ResResult.getSysError(ResResultEnum.DEFAULT_ERROR_MESSAGE);
????????}
????}


????/**
?????*?獲取請求對象
?????*?@return
?????*/
????private?HttpServletRequest?getHttpServletRequest(){
????????RequestAttributes?ra?=?RequestContextHolder.getRequestAttributes();
????????ServletRequestAttributes?sra?=?(ServletRequestAttributes)ra;
????????HttpServletRequest?request?=?sra.getRequest();
????????return?request;
????}

????/**
?????*?獲取注解值
?????*?@param?joinPoint
?????*?@return
?????*/
????private?SubmitLimit?getSubmitLimit(JoinPoint?joinPoint){
????????MethodSignature?methodSignature?=?(MethodSignature)?joinPoint.getSignature();
????????Method?method?=?methodSignature.getMethod();
????????SubmitLimit?submitLimit?=?method.getAnnotation(SubmitLimit.class);
????????return?submitLimit;
????}

????/**
?????*?組合生成lockKey
?????*?生成規(guī)則:項目名+接口名+方法名+請求參數(shù)簽名(對請求頭部參數(shù)+請求body參數(shù),取SHA1值)
?????*?@param?joinPoint
?????*?@param?request
?????*?@param?customerHeaders
?????*?@return
?????*/
????private?String?buildSubmitLimitKey(JoinPoint?joinPoint,?HttpServletRequest?request,?String[]?customerHeaders){
????????//請求參數(shù)=請求頭部+請求body
????????String?requestHeader?=?getRequestHeader(request,?customerHeaders);
????????String?requestBody?=?getRequestBody(joinPoint.getArgs());
????????String?requestParamSign?=?DigestUtils.sha1Hex(requestHeader?+?requestBody);
????????String?submitLimitKey?=?new?StringBuilder()
????????????????.append(applicationName)
????????????????.append(REDIS_SEPARATOR)
????????????????.append(joinPoint.getSignature().getDeclaringType().getSimpleName())
????????????????.append(REDIS_SEPARATOR)
????????????????.append(joinPoint.getSignature().getName())
????????????????.append(REDIS_SEPARATOR)
????????????????.append(requestParamSign)
????????????????.toString();
????????return?submitLimitKey;
????}


????/**
?????*?獲取指定請求頭部參數(shù)
?????*?@param?request
?????*?@param?customerHeaders
?????*?@return
?????*/
????private?String?getRequestHeader(HttpServletRequest?request,?String[]?customerHeaders){
????????if?(Objects.isNull(customerHeaders))?{
????????????return?"";
????????}
????????StringBuilder?sb?=?new?StringBuilder();
????????for?(String?headerKey?:?customerHeaders)?{
????????????sb.append(request.getHeader(headerKey));
????????}
????????return?sb.toString();
????}


????/**
?????*?獲取請求body參數(shù)
?????*?@param?args
?????*?@return
?????*/
????private?String?getRequestBody(Object[]?args){
????????if?(Objects.isNull(args))?{
????????????return?"";
????????}
????????StringBuilder?sb?=?new?StringBuilder();
????????for?(Object?arg?:?args)?{
????????????if?(arg?instanceof?HttpServletRequest
????????????????????||?arg?instanceof?HttpServletResponse
????????????????????||?arg?instanceof?MultipartFile
????????????????????||?arg?instanceof?BindResult
????????????????????||?arg?instanceof?MultipartFile[]
????????????????????||?arg?instanceof?ModelMap
????????????????????||?arg?instanceof?Model
????????????????????||?arg?instanceof?ExtendedServletRequestDataBinder
????????????????????||?arg?instanceof?byte[])?{
????????????????continue;
????????????}
????????????sb.append(JacksonUtils.toJson(arg));
????????}
????????return?sb.toString();
????}
}

部分校驗邏輯用到了redis分布式鎖,具體實現(xiàn)邏輯如下:

/**
?*?redis分布式鎖服務(wù)類
?*?采用LUA腳本實現(xiàn),保證加鎖、解鎖操作原子性
?*
?*/
@Component
public?class?RedisLockService?{

????/**
?????*?分布式鎖過期時間,單位秒
?????*/
????private?static?final?Long?DEFAULT_LOCK_EXPIRE_TIME?=?60L;

????@Autowired
????private?StringRedisTemplate?stringRedisTemplate;

????/**
?????*?嘗試在指定時間內(nèi)加鎖
?????*?@param?key
?????*?@param?value
?????*?@param?timeout?鎖等待時間
?????*?@return
?????*/
????public?boolean?tryLock(String?key,String?value,?Duration?timeout){
????????long?waitMills?=?timeout.toMillis();
????????long?currentTimeMillis?=?System.currentTimeMillis();
????????do?{
????????????boolean?lock?=?lock(key,?value,?DEFAULT_LOCK_EXPIRE_TIME);
????????????if?(lock)?{
????????????????return?true;
????????????}
????????????try?{
????????????????Thread.sleep(1L);
????????????}?catch?(InterruptedException?e)?{
????????????????Thread.interrupted();
????????????}
????????}?while?(System.currentTimeMillis()?<?currentTimeMillis?+?waitMills);
????????return?false;
????}

????/**
?????*?直接加鎖
?????*?@param?key
?????*?@param?value
?????*?@param?expire
?????*?@return
?????*/
????public?boolean?lock(String?key,String?value,?Long?expire){
????????String?luaScript?=?"if?redis.call('setnx',?KEYS[1],?ARGV[1])?==?1?then?return?redis.call('expire',?KEYS[1],?ARGV[2])?else?return?0?end";
????????RedisScript<Long>?redisScript?=?new?DefaultRedisScript<>(luaScript,?Long.class);
????????Long?result?=?stringRedisTemplate.execute(redisScript,?Collections.singletonList(key),?value,?String.valueOf(expire));
????????return?result.equals(Long.valueOf(1));
????}


????/**
?????*?釋放鎖
?????*?@param?key
?????*?@param?value
?????*?@return
?????*/
????public?boolean?releaseLock(String?key,String?value){
????????String?luaScript?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end";
????????RedisScript<Long>?redisScript?=?new?DefaultRedisScript<>(luaScript,?Long.class);
????????Long?result?=?stringRedisTemplate.execute(redisScript,?Collections.singletonList(key),value);
????????return?result.equals(Long.valueOf(1));
????}
}

部分代碼使用到了序列化相關(guān)類JacksonUtils,源碼如下:

public?class?JacksonUtils?{

????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(JacksonUtils.class);


????private?static?final?ObjectMapper?objectMapper?=?new?ObjectMapper();

????static?{
????????//?對象的所有字段全部列入
????????objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
????????//?忽略未知的字段
????????objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,?false);
????????//?讀取不認(rèn)識的枚舉時,當(dāng)null值處理
????????objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL,?true);
//????????序列化忽略未知屬性
????????objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,?false);
????????//忽略字段大小寫
????????objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES,?true);

????????objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE,?true);
????????SimpleModule?module?=?new?SimpleModule();
????????module.addSerializer(Long.class,?ToStringSerializer.instance);
????????module.addSerializer(Long.TYPE,?ToStringSerializer.instance);
????????objectMapper.registerModule(module);
????}

????public?static?String?toJson(Object?object)?{
????????if?(object?==?null)?{
????????????return?null;
????????}
????????try?{
????????????return?objectMapper.writeValueAsString(object);
????????}?catch?(Exception?e)?{
????????????LOGGER.error("序列化失敗",e);
????????}
????????return?null;
????}

????public?static?<T>?T?fromJson(String?json,?Class<T>?classOfT)?{
????????if?(json?==?null)?{
????????????return?null;
????????}
????????try?{
????????????return?objectMapper.readValue(json,?classOfT);
????????}?catch?(Exception?e)?{
????????????LOGGER.error("反序列化失敗",e);
????????}
????????return?null;
????}

????public?static?<T>?T?fromJson(String?json,?Type?typeOfT)?{
????????if?(json?==?null)?{
????????????return?null;
????????}
????????try?{
????????????return?objectMapper.readValue(json,?objectMapper.constructType(typeOfT));
????????}?catch?(Exception?e)?{
????????????LOGGER.error("反序列化失敗",e);
????????}
????????return?null;
????}
}

2.4、在相關(guān)的業(yè)務(wù)接口上,增加SubmitLimit注解即可

@RestController
@RequestMapping("order")
public?class?OrderController?{

????@Autowired
????private?OrderService?orderService;

????/**
?????*?下單,指定請求頭部參與請求唯一值計算
?????*?@param?request
?????*?@return
?????*/
????@SubmitLimit(customerHeaders?=?{"appId",?"token"},?customerTipMsg?=?"正在加緊為您處理,請勿重復(fù)下單!")
????@PostMapping(value?=?"confirm")
????public?ResResult?confirmOrder(@RequestBody?OrderConfirmRequest?request){
????????//調(diào)用訂單下單相關(guān)邏輯
????????orderService.confirm(request);
????????return?ResResult.getSuccess();
????}
}

其中最關(guān)鍵的一個步就是將唯一請求 ID  的生成,放在服務(wù)端通過組合來實現(xiàn),在保證防止接口重復(fù)提交的效果同時,也可以顯著的降低接口測試復(fù)雜度!

三、小結(jié)

本次方案相比于上一個方案,最大的改進(jìn)點在于:將接口請求唯一 ID 的生成邏輯,放在服務(wù)端通過規(guī)則組合來實現(xiàn),不需要前端提交接口的時候強(qiáng)制帶上這個參數(shù),在滿足防止接口重復(fù)提交的要求同時,又能減少前端和測試提交接口的復(fù)雜度!

需要特別注意的是:使用redis的分布式鎖,推薦單機(jī)環(huán)境,如果redis是集群環(huán)境,可能會導(dǎo)致鎖短暫無效!

到此這篇關(guān)于詳解如何利用Redis實現(xiàn)生成唯一ID的文章就介紹到這了,更多相關(guān)Redis生成唯一ID內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis的大Key對持久化影響分析

    Redis的大Key對持久化影響分析

    為了保證數(shù)據(jù)的持久性,Redis提供了兩種持久化的方式,本文主要介紹了Redis的大Key對持久化影響分析,具有一定的參考價值,感興趣的可以了解一下
    2024-04-04
  • Redis實現(xiàn)排行榜及相同積分按時間排序功能的實現(xiàn)

    Redis實現(xiàn)排行榜及相同積分按時間排序功能的實現(xiàn)

    這篇文章主要介紹了Redis實現(xiàn)排行榜及相同積分按時間排序,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-08-08
  • redis?protocol通信協(xié)議及使用詳解

    redis?protocol通信協(xié)議及使用詳解

    這篇文章主要為大家介紹了redis?protocol通信協(xié)議及使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 阿里云官方Redis開發(fā)規(guī)范總結(jié)

    阿里云官方Redis開發(fā)規(guī)范總結(jié)

    本文主要介紹了阿里云官方Redis開發(fā)規(guī)范總結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Linux中設(shè)置Redis開機(jī)啟動的方法

    Linux中設(shè)置Redis開機(jī)啟動的方法

    這篇文章主要給大家介紹了關(guān)于Linux中設(shè)置Redis開機(jī)啟動的方法,主要包括在CentOS7.0系統(tǒng)和Debian 8.0系統(tǒng)下實現(xiàn)方法,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-04-04
  • 如何基于Session實現(xiàn)短信登錄功能

    如何基于Session實現(xiàn)短信登錄功能

    對比起Cookie,Session是存儲在服務(wù)器端的會話,相對安全,并且不像Cookie那樣有存儲長度限制,下面這篇文章主要給大家介紹了關(guān)于如何基于Session實現(xiàn)短信登錄功能的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • redis 集群批量操作實現(xiàn)

    redis 集群批量操作實現(xiàn)

    這篇文章主要介紹了redis 集群批量操作,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • 解決redis sentinel 頻繁主備切換的問題

    解決redis sentinel 頻繁主備切換的問題

    這篇文章主要介紹了解決redis sentinel 頻繁主備切換的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • python中使用redis用法詳解

    python中使用redis用法詳解

    Redis擁有豐富的數(shù)據(jù)結(jié)構(gòu),擁有事務(wù)功能,保證命令的原子性。由于是內(nèi)存數(shù)據(jù)庫,讀寫非常高速,可達(dá)10w/s的評率,所以一般應(yīng)用于數(shù)據(jù)變化快、實時通訊、緩存等。這篇文章給大家講解一下Python如何使用Redis,并進(jìn)行相關(guān)的實戰(zhàn)操作。
    2022-12-12
  • Redis中秒殺場景下超時與超賣問題的解決方案

    Redis中秒殺場景下超時與超賣問題的解決方案

    當(dāng)我們在linux中使用ab來模擬高并發(fā)秒殺時可能會遇到兩種問題,“超時和超賣”,本文就詳細(xì)介紹了Redis中秒殺場景下超時與超賣問題的解決方案,感興趣的可以了解一下
    2022-05-05

最新評論