使用Sanic和Redis遇到麻烦

时间:2020-08-10 23:10:49

标签: python api redis multiprocessing sanic

我正在和2名工人一起使用Sanic。我正在尝试使计费系统正常运行,即计算用户点击API端点的次数。以下是我的代码:

class User(object):
    def __init__(self, id, name, age, address, mobile, credits=0):
        self.id = id
        self.name = name
        self.credits = count
        self.details = {"age": age, "address": address, "mobile_number": mobile}

上面的Users类用于使用以下另一个python脚本制作我已上传到Redis的对象:

user = User(..., credits = 10)
string_obj = json.dumps(user)
root.set(f"{user.user_id}", string_obj)

当我想维护端点收到的命中次数并与用户对象一起跟踪并将其上传回Redis时,就会出现主要问题。我的代码如下:

from sanic_redis_ext import RedisExtension

app = Sanic("Testing")
app.config.update(
{
    "REDIS_HOST": "127.0.0.1",
    "REDIS_PORT": 6379,
    "REDIS_DATABASE": 0,
    "REDIS_SSL": None,
    "REDIS_ENCODING": "utf-8",
    "REDIS_MIN_SIZE_POOL": 1,
    "REDIS_MAX_SIZE_POOL": 10,
})

@app.route("/test", methods=["POST"])
@inject_user()
@protected()
async def foo(request, user):
    user.credits -= 1
    if user.credits < 0:
        user.credits = 0
        return sanic.response.text("Credits Exhausted")

    result = process(request)

    if not result:
        user.credits += 1

    await app.redis.set(f"{user.user_id}", json.dumps(user))
    return sanic.response.text(result)

这就是我检索用户的方式:

async def retrieve_user(request, *args, **kwargs):
    if "user_id" in kwargs:
        user_id = kwargs.get("user_id")
    else:
        if "payload" in kwargs:
            payload = kwargs.get("payload")
        else:
            payload = await request.app.auth.extract_payload(request)
        if not payload:
            raise exceptions.MissingAuthorizationHeader()
        user_id = payload.get("user_id")

    user = json.loads(await app.redis.get(user_id))
    return user

当我使用JMeter来测试具有10个作为同一用户的线程的API端点时,信用系统似乎无法正常工作。在这种情况下,由于用户以10个学分开始,所以他们最终可能剩下7或8个(不可预测的)学分,而他们应该还有0个学分。根据我的说法,这是由于工人没有共享用户对象,并且没有变量的更新副本,这导致他们彼此覆盖更新。任何人都可以帮我找到解决方法,以便即使同一位用户同时击中该端点,也应该向他/她收取合理费用,并将该用户对象保存回Redis。

1 个答案:

答案 0 :(得分:2)

问题是您从Redis中读取了信用额信息,将其扣除,然后将其保存回Redis,这不是一个原子过程。这是一个并发问题。

我不了解Python,所以我只会使用伪代码。

首先为用户{user_id}设置10个积分。

app.redis.set("{user_id}:credits", 10)

然后该用户进来

# deduct 1 from the user credits and get the result
int remaining_credits=app.redis.incryBy ("{user_id}:credits",-1) 
if(remaining_credits<=0){
   return sanic.response.text("Credits Exhausted")} else{
   return "sucess" # or some other result}

使用有效负载将您的用户信息保存在其他地方,并检索“ {user_id}:信用”,并在检索用户时将其合并。