微服務下接口冪等性的思考

概念:

一次和多次請求某一個資源悦屏,對資源本身所產(chǎn)生的的影響均與一次執(zhí)行的影響相同; 需要考慮到冪等的場景就是新增和更新三痰,對于查詢,刪除的操作執(zhí)行一次和執(zhí)行多次不影響最終結(jié)果集所以Select, delete 天生就是冪等的

常見解決方案

1:分布式鎖保證冪等

1.1 場景:秒殺窜管,搶票

在秒殺情況下同一個用戶只能搶購一件商品,在買票的情況下同一個用戶稚机,同一個班次的火車票也能買一張幕帆;如果此時用戶是寫的腳本程序或者網(wǎng)絡抖動發(fā)生重試,執(zhí)行了多次request 則會導致一個用戶搶購多個商品或者出票重復

1.2 解決方案:redis 鎖

用戶ID + 火車班次號 生成一個唯一key 存儲到redis中使用setnx / rédission 保證某一段時間內(nèi)的數(shù)據(jù)唯一性

1.3 DEMO 代碼

redis設置全局鎖代碼

 public ResponseCode setLockAndExecute(String key, String value, long timeOut, TimeUnit time, Supplier<ResponseCode> execute) {
   boolean lock = false;
   try {
     lock = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeOut, time);
     if (lock) {
       log.info(“重復請求:{}”, key);
       return execute.get();
     }
     //加鎖失敗
     log.info("重復請求:{}", key);
     return ResponseCode.REPEAT_ERROR;
   } catch (Exception ex) {
     log.error("Redis加鎖異常", ex);
     return ResponseCode.UNKNOWN_ERROR;
   } finally {
     if (lock) {
       log.info("任務執(zhí)行完成:刪除鎖:{}",key);
       this.del(key);
     }
   }
}

需要做冪等的業(yè)務方法

return setLockAndExecute(userId+productId, 1, 1, TimeUnit.HOURS, () -> {
   User user = userService.getUserById(userId);
   if (ObjectUtils.isEmpty(user)) {
     log.info(“不存在該用戶:{}”, userid);
     return ResponseCode.USER_NOT_FIND;
   }
   Product product = productService.getProductById(productId);
   if (ObjectUtils.isEmpty(product)) {
       log.info(“不存在該商品:{}”, productid);
       return ResponseCode.PRODUCT_NOT_FIND;
   }
   String orderId = orderService.insertNewOrder(user, product);
   if (StringUtils.hasLength(orderId)) {
       log.info(“訂單生成成功:{}”, orderId);
       return ResponseCode.SUCCESS;
   }
   log.info("訂單生成失敗);
   return ResponseCode.FAIL;
});

2:token保證冪等

2.1 場景:用戶注冊赖条,創(chuàng)建子部門

當有創(chuàng)建新數(shù)據(jù)的時候因為都是新數(shù)據(jù)沒有ID失乾,當用戶點擊多次提交會導致創(chuàng)建重復數(shù)據(jù)到DB中

2.2 解決方案:在正式請求前做一次獲取令牌的預請求

當點擊新增,或者注冊的時候先請求一次后端接口后端接口生成一個token保存到redis中(setnx)同時返回給前端請求纬乍,等前端填寫完注冊信息并提交保存的時候攜帶上上次返回的token, 后端收到請求后獲取token并且判斷redis中是否存在,如果存在從redis中刪除然后保存數(shù)據(jù)庫碱茁,如果不存在返回錯誤信息給前端

2.3 DEMO 代碼:

創(chuàng)建注冊用戶的冪等攔截注解,以后對于需要冪等的方法只需要在方法上添加該注解即可

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RegisterRestriction {
    boolean value() default true;
}

在注冊用戶的controller 上添加冪等判斷注解

@PostMapping(“/register")
@RegisterRestriction
public ResponseData registerUser(@RequestHeader(value = "Authorization") String token, HttpServletResponse response) {
    return userService.registerUser(token, response);
}

生成正式請求的令牌代碼

Public ResponseCode generateUniqueKey(String userId) {
   User user = userService.getUserById(userId);
   if (ObjectUtils.isEmpty(user)) {
     log.info(“不存在該用戶:{}”, userid);
     return ResponseCode.USER_NOT_FIND;
   }
  //生成唯一key 給response
  String uniqueKey = String.*format*("%s%s", Instant.*now*().toEpochMilli(), userId);
  stringRedisTemplate.opsForValue().set(uniqueKey, uniqueKey, 5 * 60, TimeUnit.SECONDS);
  response.setHeader(CommonConst.*REGISTER_REQUEST_ID*, uniqueKey);
  // 成功更新仿贬,返回
  return ResponseCode.SUCCESS;
}

冪等攔截器代碼

@Slf4j
public class RegisterInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisUtils redisUtils;

    private boolean checkRequestId(HttpServletRequest request, HttpServletResponse response) throws InterceptorException {
        String requestId = request.getHeader("X-Request-Id");
        if (!StringUtils.hasLength(requestId)) {
            log.info("header中未獲取到預先生成的注冊令牌纽竣,請求非法");
            throw new InterceptorException(40012, "header中未獲取到預先生成的注冊令牌,請求非法");
        }
        //判斷redis中是否存在requestId, 如果存在 -> return true
        //如果不存在(可能是前端造的假ID-> return false)
        if (redisUtils.exists(requestId)) {
            //刪除
            if (redisUtils.del(requestId)) {
                return true;
            }

            log.info("請勿重復請求");
            throw new InterceptorException(ResponseCode.REPEAT_ERROR);
        }

        log.info("redis 中未獲取到預先生成的注冊令牌茧泪,請求非法");
        throw new InterceptorException(ResponseCode.REPEAT_ERROR);
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            Method method = handlerMethod.getMethod();
            if (!ObjectUtils.isEmpty(method) && method.isAnnotationPresent(RegisterRestriction.class)) {
                if (method.getAnnotation(RegisterRestriction.class).value()) {
                    return checkRequestId(request, response);
                }
            }
        }

        return true;
    }
}

3:mysql unique key 保證冪等

3.1 解決方案:

通過對mysql 表創(chuàng)建唯一key來達到冪等性蜓氨,比如用戶注冊手機號設置唯一key 當發(fā)生重復請求后mysql判重拒絕寫入db,
創(chuàng)建子部門,可以創(chuàng)建parent_id + 部門名稱做唯一key 來保證冪等性

3.3 DEMO 代碼:

ALTER TABLE tb_department ADD CONSTRAINT department_info_UN UNIQUE KEY (parent_id,department_name);

4:樂觀鎖保證冪等

4.1 場景:

當涉及到自增队伟,自減更新的時候需要考慮冪等性問題穴吹,因為普通的更新就是給字段賦值執(zhí)行一次和執(zhí)行多次效果都是一樣的,比如:更新用戶名稱嗜侮,更新數(shù)據(jù)狀態(tài)
Update user set name= ‘張三’ where id = ‘zhangsan’
Update user set status=‘1’ where id = ‘zhansan’

4.2 解決方案:樂觀鎖

對于自增自減類的update,比如:update goods set count=count -1 where id = ‘123’ 就需要做冪等處理因為執(zhí)行多次就會自減多次港令,使用樂觀鎖啥容,給表中新增一個version字段,每次讀取的時候?qū)ersion字段讀取顷霹,更新的時候再判斷version字段是否是自己持有的值同時將version +1

Update goods set count=count -1 , version=version +1 where id = ‘123’ and version = ‘1’
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咪惠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泼返,更是在濱河造成了極大的恐慌硝逢,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绅喉,死亡現(xiàn)場離奇詭異渠鸽,居然都是意外死亡,警方通過查閱死者的電腦和手機柴罐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門徽缚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人革屠,你說我怎么就攤上這事凿试。” “怎么了似芝?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵那婉,是天一觀的道長。 經(jīng)常有香客問我党瓮,道長详炬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任寞奸,我火速辦了婚禮呛谜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枪萄。我一直安慰自己隐岛,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布瓷翻。 她就那樣靜靜地躺著聚凹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪齐帚。 梳的紋絲不亂的頭發(fā)上元践,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音童谒,去河邊找鬼单旁。 笑死,一個胖子當著我的面吹牛饥伊,可吹牛的內(nèi)容都是我干的象浑。 我是一名探鬼主播蔫饰,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼愉豺!你這毒婦竟也來了篓吁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蚪拦,失蹤者是張志新(化名)和其女友劉穎杖剪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驰贷,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡盛嘿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了括袒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片次兆。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锹锰,靈堂內(nèi)的尸體忽然破棺而出芥炭,到底是詐尸還是另有隱情,我是刑警寧澤恃慧,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布园蝠,位于F島的核電站,受9級特大地震影響痢士,放射性物質(zhì)發(fā)生泄漏彪薛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一良瞧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧训唱,春花似錦褥蚯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至澳骤,卻和暖如春歧强,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背为肮。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工摊册, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颊艳。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓茅特,卻偏偏與公主長得像忘分,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子白修,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一妒峦、背景 我們實際系統(tǒng)中有很多操作,是不管做多少次兵睛,都應該產(chǎn)生一樣的效果或返回一樣的結(jié)果肯骇。例如 前端重復提交選中的...
    JANME丶閱讀 156評論 0 0
  • 一、什么是冪等性 冪等是一個數(shù)學與計算機學概念祖很,在數(shù)學中某一元運算為冪等時笛丙,其作用在任一元素兩次后會和其作用一次的...
    life_niu閱讀 181評論 0 0
  • 一、冪等性概念 在編程中.一個冪等操作的特點是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同突琳。冪等函數(shù)若债,或冪等方...
    小波同學閱讀 1,131評論 1 18
  • 一、冪等性概念 二拆融、分布式系統(tǒng) 中的冪等性 在編程中蠢琳,一個冪等操作的特點是其任意多次執(zhí)行所產(chǎn)生的影響與一次執(zhí)行的影...
    逗逼程序員閱讀 1,401評論 0 27
  • 分布式服務接口的冪等性如何設計 什么是冪等性 一個分布式系統(tǒng)中的某個接口,要保證冪等性镜豹,該如何保證傲须?這個事兒其實是...
    wanggs閱讀 435評論 0 0