商用版关于API调用

我的小游戏今天测试大概才5、6个人,3个小时,就超过了开发版的API调用次数限制,我看网页上说的开发版API调用限制是30000/天。

请问商用版API调用是什么价格的收费?是否指的就是对象存储次数的意思(1元/万次)?

另外我是第一次开发网络游戏,请问我这个数据API存储的访问量设计是不是有问题啊,那么快就用了那么多。

我在考虑如何进行重构,有没有办法得知某一条云函数调用API的量啊?

谢谢~

在另外一个帖子回复了你:

后续请在这个帖子讨论吧,可以把你的游戏类型描述一下。

感谢回复!

我目前用了4个自定义类,每个用户各1个自定义的“userData”表、每个userData会引用card表中多个数据、一个保存临时card数据的cardCopy表、还一个类似于装备表(一张卡多个装备)。因为这样的话云函数每调用一次,在服务器端都会查询好几次、以及存储删除等操作,就导致API调用过多。

我思考后计划重构为只用一个表,所有的数据全部存在userData中,虽然会让这个表的数据结构非常复杂,但根据昨天的API调用统计,这样会节省一半的API调用。但是重构后的调用数量级也很可怕,3万调用才支持20个玩家玩3小时,跟你之前提到支持上千用户也差的很远。我是个人辞职自学的开发,比较担心正式上线用户多起来后这个成本我个人承担不了。

不管怎么样我先进行重构后看一下统计。BTW因为我编程经验少水平低,重构可能会花些时间,完成后会再来这里更新一下情况。

谢谢~

您将各个类型的数据拆成多个表是合理的设计。

您现在可能需要看一下下面几点是否有改进的空间:

  1. 客户端访问 server 过于频繁。按照 6 人,3 h,每次云引擎请求消耗 4 个 API 请求来计算,您的游戏平均每 8.6s 就会发生一次云引擎调用。这对于非实时类型的游戏来说过于频繁了。
  2. 在一次云引擎接口调用重,需要查询好几次 API,是可以改进的。比如对装备表的操作可以由客户端提供用户的 id,而不需要重新查询用户表。对于一些热点的数据,可以使用内存或者 LeanCache 做缓存等。

谢谢!我再仔细梳理一下自己的代码,看看有没有可以优化的地方。

1 4个表合成1个,能节省一半API调用,这个优化不好吗?

2 请问“可以使用内存”是什么意思?我有一些热点的固定的游戏配置数据是写死在python代码中而不是保存在存储服务中,这就算是利用了内存吧。但是这必须是固定不能改变的数据,如果需要改动,就需要改代码后重新deploy一次。是不是指的使用redis(1元128MB/天的云缓存实例)?

3 综合你的1、2点,无论用几个表,每次调用云引擎函数都尽量只用到1~3次API调用是不是我的优化目标?(情况1 查询用户数据,则只需要fetch1次;情况2 客户端有数据变化,fetch1次,然后改变后save1次;情况3 服务器端也需要查询的操作比如匹配对手,则fetch1次自己的user、根据条件查询一次对手user、做各种运算后save1次,这样看还是用1个表会最节约API调用)。

===========更新===========
我了解了一下redis,准备用云缓存或者python cloud.py里全局声明个dctionary来缓存,所以还是分成多个表结构更清晰一些吧,不合为1张表了。

我有个细节设计部分想要请教一下。
目前我每个用户的主要数据存储在一个叫DEUser的表中,因此每次客户端调用云函数,几乎都是以这样的开头写的:

current_user = engine.current.user #第1次查询请求
if current_user is not None:
    DEUser = current_user.get("DEUser")
    DEUser.fetch() #第2次查询请求
    #下面是业务操作代码

问题1:

current_user = engine.current.user #查询请求1次

这个代码的查询请求是否没有计入API调用请求中?因为在API统计中看到_User表的请求总计才49次而已,但所有带这样开头代码的云函数被调用的次数至少是几千次,所以我这样猜想。

问题2:
是否应该将DEUser的数据都保存在_User表中,这样每次调用了engine.current.user就可以用数据了。如果问题1中的调用不耗费API请求数,那么这样优化都合并在_User表中之后,会节省非常巨大量的请求数。

=========更新==========
这个回复中的两个问题还请回答一下,谢谢!

我看了一下LeanCache相关的文档,请问:

1 开发版的LeanCache有什么限制?因为有最低限制的消费,如果要使用Redis,就算在开发期间,也需要至少每天30元吗?还是说可以用开发版的云引擎配合按需求单独计费的比如1元128MB/天的LeanCache?
===更新===
这个问题找到了一个销售咨询,没错,开发版+LeanCache没有30元最低消费限制。

2 我准备把所有表的数据都以objectId为key都set到Redis里或者是自己声明的dictionary里,不同项目不同表之间、相同项目不同表之间的objectId是否会有重复的?

3 每次Deploy python云引擎的时候,云引擎实例会重启。Redis不会跟着一并重启吧?因为云引擎会经常deploy进行重启,不希望Redis每次都重启清空。

谢谢!

用内存做缓存不是指写死在代码里,是指从服务端取到数据后就存在客户端程序的数据结构里,以后直接使用,不再从服务端查询。对于一些不太会变,只会偶尔调整的数据,可以考虑保存在客户端本地的数据库里,过一段时间再查询服务端是否有新版本需要更新。

我明白了,就好比在python cloud.py文件的全局处声明的dictionary就可以用来做缓存, 谢谢! smile

我学习了Redis并给LeanCache充了钱开了一个Redis实例进行了测试,存储、读取普通数据都正常。但当使用cPickle进行dumps时遇到了报错,可否请人帮我看一下,谢谢!

错误:

STDERR web1 22:36:37
Traceback (most recent call last):
STDERR web1 22:36:37
  File "/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages/leancloud/engine/leanengine.py", line 128, in dispatch_request
STDERR web1 22:36:37
    result = {'result': dispatch_cloud_func(self.cloud_codes, app_params, decode_object=False, **values)}
STDERR web1 22:36:37
  File "/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages/leancloud/engine/leanengine.py", line 222, in dispatch_cloud_func
STDERR web1 22:36:37
    result = func(**params)
STDERR web1 22:36:37
  File "/home/leanengine/app/cloud.py", line 148, in cacheTest
STDERR web1 22:36:37
    cache.test()
STDERR web1 22:36:37
  File "/home/leanengine/app/cacheModule.py", line 31, in test
STDERR web1 22:36:37
    u = cPickle.dumps(user, protocol=2)
STDERR web1 22:36:37
PicklingError: Can't pickle <class 'leancloud.object_.DEUser'>: attribute lookup leancloud.object_.DEUser failed

代码:

q = leancloud.Query('DEUser')
key = '5a7f8747d50eee0042dab717'
user = q.get(key)
print 'user deckAll = ', user.get('deckAll'), ', type = ', user.__class__.__name__
u = cPickle.dumps(user, protocol=2)
u0 = cPickle.dumps(user)
print 'user size = ', sys.getsizeof(user), ', u size = ', sys.getsizeof(u), ', u0 size = ', sys.getsizeof(u0), ', user type = ', user.__class__.__name__
self.r.set(key, u)
print 'exists(key1) = ', self.r.exists(key)

loadData = self.r.get(key)
d = cPickle.loads(loadData)
print 'deckAll = ', d.get('deckAll'), ', type = ', d.__class__.__name__

============更新===============
我尝试了不用pickle,直接将查询到的数据set到Redis里,然后从中读取,但是读到的数据结果直接print看类型没错,但type其实不对,是str不是DEUser(我的数据类型):
代码:

q = leancloud.Query('DEUser')
key = '5a7f8747d50eee0042dab717'
user = q.get(key)
print 'user deckAll = ', user.get('deckAll'), 'user = ', user, ', type = ', user.__class__.__name__

self.r.set(key, user)
print 'exists(key) = ', self.r.exists(key)


loadData = self.r.get(key)
print 'loadData = ', loadData, ', type = ', loadData.__class__.__name__
print 'deckAll = ', loadData.get('deckAll')

输出:

STDOUT web1 23:57:05
user deckAll =  [u'5a82a7dd128fe1003799fd44', u'5a82ab299f54544d44178f45', u'5a82ac079f54544d44179512', u'5a82ae64fe88c200385329fc', u'5a82d7e69f54544fd1453da7', u'5a82dde39f545441043aa7c6', u'5a82e0cf128fe100379b9c3c', u'5a82e1409f54544fd1459fdc', u'5a82e1d09f545441043ad20d', u'5a82e301756571003c81e5d3', u'5a82e3539f545441043ae226', u'5a82e3dc128fe100379bbdd5', u'5a82e5949f54544d44194a66', u'5a82e6179f545441043afe44', u'5a82e647ee920a004468a9f2', u'5a82e71e9f545441043b078d', u'5a82e7af9f545441043b0e0f', u'5a82e7f3fe88c2003854e746', u'5a82e950ac502e00327ea4a8', u'5a82e98cd50eee0042ed8c41'] user =  <leancloud.object_.DEUser object at 0x7ff00799fdd0> , type =  DEUser
STDOUT web1 23:57:05
exists(key) =  True
STDOUT web1 23:57:05
loadData =  <leancloud.object_.DEUser object at 0x7ff00799fdd0> , type =  str
STDERR web1 23:57:05
Traceback (most recent call last):
STDERR web1 23:57:05
  File "/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages/leancloud/engine/leanengine.py", line 128, in dispatch_request
STDERR web1 23:57:05
    result = {'result': dispatch_cloud_func(self.cloud_codes, app_params, decode_object=False, **values)}
STDERR web1 23:57:05
  File "/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages/leancloud/engine/leanengine.py", line 222, in dispatch_cloud_func
STDERR web1 23:57:05
    result = func(**params)
STDERR web1 23:57:05
    cache.test()
STDERR web1 23:57:05
  File "/home/leanengine/app/cacheModule.py", line 34, in test
STDERR web1 23:57:05
    print 'deckAll = ', loadData.get('deckAll')
STDERR web1 23:57:05
AttributeError: 'str' object has no attribute 'get'

hi,

redis 并不知道 python 的数据类型,只认识 bytes(或者 str,如果是 python2),所以需要序列化。

Python SDK 不支持序列化后再还原的用法,所以你这边只能通过 Object.dump 方法将 object 的内容转换成 dict,然后序列化后(pickle/json/msgpack 都可以)存入 redis。拿出来后再反序列化,直接使用反序列化后得到的 dict。

非常感谢回复!
我也是昨天发现了Object.dump这个方法,正准备尝试一下。代码是不是就是这样

bar = foo.dump() #foo是查询好的leancloud数据Objecct类

1 请问怎么从反序列化后得到的dict再处理成为leancloud.Object呢?
2 我看Redis好像是支持dict(hash表)类型的,所以以上的bar不进行cPickle直接set进Redis也是可以的吧?是否推荐进行pickle呢?
3 我之前尝试的直接将leancloud.Object用set存入Redis,再取出来,那个存进去的实际上是失败的吗?是不是并没有包含各个字段的数据?

  1. 不支持这种用法,所以麻烦直接使用 dump 出来的数据吧。这里确实不太合理,之后会修正。
  2. 推荐不要用 pickle,用 json 或者 msgpack 吧。 另外 redis 的 hash 不是这样用的,如果你没有存取单个值的需求,就不要用 hash,直接将整个对象序列话存进去就行。
  3. 不知道你是怎么操作的,但是你说的这个操作是不可能会成功的。

请问你指的是不支持bar = foo.dump()这个用法吗?那么应该如何使用Object.dump方法呢?可否给一下示例代码看一下,谢谢!

另外1我主要想问的是这个:我明白如何存储了(当你dump后再进行pickle/json/msgpack处理后,再set进Redis);但是不太明白如何恢复成leancloud.Object(我会从Redis中get出来,然后用pickle/json/msgpack恢复成dictionary;但是怎么将这个dicttionary处理成为和查询到的数据格式一样的leancloud.Object呢?)

谢谢!!

不支持从 dump 出来的 dict 还原成相应的 object

那存在Redis里怎么起到缓存作用啊?...必须得根据业务每个数据表都各自分别设计处理吗?如果这样还不如全局声明一个dict做缓存方便.......

有没有办法让Object支持cPickle也行。或者有没有leancache推荐的比较通用的设计方案思路或者示例?

你看我这样的思路对不对:
1 以前旧有的代码使用'Object.set','Object.get'方法,现在用了Redis后get出来的虽然是dictionary,但也是用set、get方法,语法上一样,所以如何使用数据的代码其实并不需要修改(这样就没有恢复成leancloud.Object类型的需求了),只要把查询替换为先从Redis中查,查不到再去存储服务里查。
2 缓存的策略就是在各个需要缓存的类的@engine.before_save('Todo')方法里,将数据进行dump()、cPickle后按key存入Redis里。

  1. 如果你的代码能考虑到这种情况,那么是可以的。不过实际中这种混用导致的问题会在之后不停的冒出来,管理起来比较麻烦。
  2. 这个看起来没问题。

1 非常感谢!我正在测1,准备按1的方法去将项目完全重构一遍,这是我目前能想到的办法。请问你们没有官方推荐的设计思路吗?

我正准备再测试一下2,看看before_save这样的hook函数是不是可以正常运行。

2 按照我的理解before_save是新建一条数据项目时被触发、before_update是更新一条已存在数据时被触发;所以按照我的设计思路,应该在两个函数里都进行dumps、pickle后存在Redis里,请问对吗?

3 另外您建议是用before_save和before_update方案,还是用after_save和after_update方案呢?谢谢!

  1. 目前并没有。这个算是 Python SDK 的缺陷了,之后会修正的。
  2. after_save 吧。before_save 可能之后会用作其他的逻辑,放在一块容易冲突。

所以是after_save和after_update里都进行dumps、pickle、set到Redis里的操作对吧?