博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
妥善的处理重试请求
阅读量:6608 次
发布时间:2019-06-24

本文共 4119 字,大约阅读时间需要 13 分钟。

hot3.png

前言

  手机游戏项目中,由于用户在很多时间使用的是移动网络,和服务器连接不稳定在所难免。客户端发送给服务端的请求没接收到应答,也是经常碰到的情况。

  同样是没有接收到应答,是因为服务端未接收到请求,还是发送应答给客户端失败,客户端很难区分。对客户端来说,这两种情况几乎没有什么分别。
  这会带来一个问题:客户端在无法接收到应答的时候,是否发送重试请求?
  如果是因为服务端没收到请求造成的无应答,那么发送重试请求并没有什么问题。但如果是因为服务端发送应答给客户端失败造成的无应答,那么发送重试请求,会让服务端重复处理已处理过的请求。
  如果只是强化、升级这种请求,重复处理请求也许问题也不是太大。但如果是购买、消费这种请求,重复消费恐怕会引起玩家的重度不适,收到很多吐槽和投诉。

解决方案

  我们需要解决的核心问题,是让客户端可以安全的发送重试请求。服务端应该能够正确的区分哪些请求是重试请求,避免重复处理。但如何实现这一点呢?

  经过一些思考,我初步的实现了一个解决方案。

客户端发送请求唯一标识

  对于手机游戏项目,大部分请求是带有用户属性的。首先,我们可以将请求区分的范围,缩小到同一用户的请求中。比如,在我们的项目中,通过传递 token 参数实现对用户身份的认证。

  客户端在发送请求时,多传递一个 flag 参数,这是一个随机数。我们约定,客户端发送的每个新请求,都应该具有不同的 flag 值,而发送的重试请求,则使用失败的原请求的 flag 值。
  服务端通过应答数据缓存和接收到请求的 flag 值,就可以区分是新请求还是重试请求。

# 新请求curl -v ""# 新请求curl -v ""# 重试请求curl -v ""

服务端缓存应答

  服务端将缓存每个用户最后一个请求的应答数据,缓存数据的键名使用 token 参数构造,存储请求的动作 action、应答数据 reply 和唯一标识 flag 值,如图:

服务端区分请求类型 

  服务端收到客户端的请求后,首先使用 token 参数组织键名,并从缓存中获取用户上一个请求的应答数据。

  1. 如果请求的动作 action 和唯一标识 flag 与缓存数据一致

    判定为重试请求,直接将缓存的应答数据 reply 发送给客户端。

  2. 如果请求的动作 action 和唯一标识 flag 与缓存数据不一致

    判定为新请求,根据动作 action 将请求数据分发给对应的业务处理逻辑,并将处理结果组织成应答后发送给客户端。

分析和实例

  通过缓存的应答数据和请求唯一标识,我们能够区分请求是新请求还是重试请求,从而确定对应的处理策略,避免请求被重复处理。

  以下是目前线上项目使用的代码实例,其中 Response:send 是发送应答的方法,Response:checkRetry 是检查请求是否为重试请求的方法。   

local xxtea = loadMod("xxtea")local util = loadMod("core.util")local exception = loadMod("core.exception")local request = loadMod("core.request")local counter = loadMod("core.counter")local sysConf = loadMod("config.system")local changeLogger = loadMod("core.changes")local redis = loadMod("core.driver.redis")local cacheConf = loadMod("config.cache")local shmDict = loadMod("core.driver.shm")local shmConf = loadMod("config.shm")--- Response模块local Response = {    --- 请求缓存键名前缀    CACHE_KEY_PREFIX = "lastRes",    --- Response存储处理器实例    cacheHelper = nil,}--- 生成重试缓存键名---- @param number userId 用户ID-- @return string 重试缓存键名function Response:getCacheKey(userId)    return util:getCacheKey(self.CACHE_KEY_PREFIX, userId)end--- Response模块初始化---- @return table Response模块function Response:init()    if sysConf.PRIORITY_USE_SHM then        self.cacheHelper = shmDict:getInstance(shmConf.DICT_DATA)    else        self.cacheHelper = redis:getInstance(cacheConf.INDEX_CACHE)    end    return selfend--- 发送应答---- @param string message 应答数据-- @param table headers 头设置function Response:say(message, headers)    ngx.status = ngx.HTTP_OK    for k, v in pairs(headers) do        ngx.header[k] = v    end    ngx.print(message)    ngx.eof()end--- 构造并发送应答数据---- @param table|string message 消息-- @param boolean noCache 不缓存消息function Response:send(message, noCache)    local headers = {        charset = sysConf.DEFAULT_CHARSET,        content_type = request:getCoder():getHeader()    }    if sysConf.DEBUG_MODE then        ngx.update_time()        headers.mysqlQuery = counter:get(counter.COUNTER_MYSQL_QUERY)        headers.redisCommand = counter:get(counter.COUNTER_REDIS_COMMAND)        headers.execTime = ngx.now() - request:getTime()    end    if sysConf.ENCRYPT_RESPONSE then        message = xxtea.encrypt(message, sysConf.ENCRYPT_KEY)    end    self:say(message, headers)    if not noCache then        local action = request:getAction()        local token = request:getToken(false)        local flag = request:getRandom()        if token ~= "" and flag ~= "" then            local cacheKey = self:getCacheKey(token)            local cacheData = { action = action, flag = flag, headers = headers, reply = message }            self.cacheHelper:set(cacheKey, cacheData, sysConf.REQUEST_RETRY_EXPTIME)        end    endend--- 检查重试请求,如果存在缓存则返回缓存---- @return booleanfunction Response:checkRetry()    local action = request:getAction()    local token = request:getToken(false)    local flag = request:getRandom()    if token ~= "" and flag ~= "" then        local cacheKey = self:getCacheKey(token)        local cacheData = self.cacheHelper:get(cacheKey)        if cacheData and cacheData.action == action and cacheData.flag == flag then            self:say(cacheData.reply, cacheData.headers)            return true        end    end    return falseendreturn Response:init()  

   

转载于:https://my.oschina.net/u/232595/blog/1583602

你可能感兴趣的文章
select下拉框,选择其中一个,然后进行查询,完成之后,页面上的select框不回显当前查询时选中的值...
查看>>
python3基础——函数(2)
查看>>
TOKEN设计
查看>>
yum更换国内源、yum下载rpm包 、源码包安装
查看>>
常用Oracle旳SQL函数
查看>>
BCH或将在日本迎来新的上涨点
查看>>
从HelloWorld看Knative Serving代码实现
查看>>
linux`操作文本的三大利器
查看>>
【CentOS 7笔记39】,监控系统状态1#
查看>>
LAMP架构PHP模块支持与虚拟主机配置
查看>>
一块GPU就能训练语义分割网络,百度PaddlePaddle是如何优化的?
查看>>
详解netty原理分析
查看>>
ubuntu sublime text中文输入法和乱码问题
查看>>
病人spark处理-元组和case class 对数据进行结构化
查看>>
CSS布局1:用三个div实现左右两列固定,中间自适应
查看>>
Nginx功能配置(反向代理、SSL)
查看>>
linux杂项
查看>>
数组的 基本操作 【增删查改】
查看>>
IT兄弟连 JavaWeb教程 AJAX常见问题
查看>>
感谢,终于有人把云计算、大数据和人工智能讲明白了!
查看>>