0

我需要构建一个 REST API/服务器,它在 80 毫秒内每秒响应超过 HTTP GET 15,000 个请求。如有必要,我可以使用负载均衡器运行多个实例。

服务器收到一个带有标准列表(大约 20 个)的请求,它们需要被解析并与一个规则集(大约 2000 个规则对所有 20 个标准具有不同的值和一个最终决定)进行比较,该规则集决定响应(是或否)。

示例请求有效负载:

{"Country" : "DE",
 "ID" : "998423-423432-4234234-234234",
 "Criteria1": "8748r78",
 "Criteria2": "Some String",
  [...]
}

示例规则集(仍有待决定,但让我们从一个简单的设计开始):

+--------+---------+-------------+--------------+
| RuleId | Country |  Criteria1  |  Criteria2   | etc...
+--------+---------+-------------+--------------+
|      1 | UK      | SomeString1 | SomeString3  |
|      2 | UK      | SomeString1 | SomeString2  |
|      3 | US      | SomeString4 | * (Wildcard) |
+--------+---------+-------------+--------------+

每个标准可以包含 1 到大约 400 个不同的值,所有字符串(例如 ISO 代码中的 GEO)。有些可能为空并被视为通配符。理论上可能存在所有 20 个标准具有相同值的条目,但这是尚未编写的规则引擎要整理的主题。

我做了一些研究如何实现这一目标:

  1. 使用 sanic 作为高吞吐量的网络服务器,根据我的研究,这是除了 alpha 的 japronto 之外的 python 最快的;编辑:有没有人对类似用例的基于 python 的 webserver+webframework 的性能有经验?我只阅读通常有一个非常简单的测试用例的基准(只需对请求响应一个固定字符串,因此在所有基准中每秒可能的请求数很高)
  2. 使用 sqlite3(在内存中)进行规则查找;不确定具有 20 个约束的 SQL 语句是否足够快?也许还有另一种方法可以将每个请求与超过 20 个标准的规则集进行比较(每个标准都是一个字符串比较)。编辑:感谢评论者,我可能会将规则预先计算为散列并使用散列进行查找,因此不需要用于实时查找的数据库。
  3. 使用 redis 或其他数据库来存储预先计算的规则(这是另一个主题),并使它们准备好加载到 http 服务器的每个实例/worker 中,从而加载到 sqlite3 数据库中。
  4. 也许使用 pypy3 来提高速度,但我没有使用 pypy 的经验

我会在 Heroku 上主持这个。

所以问题是:哪些库和架构允许使用 python 实现这种速度?

4

1 回答 1

1

我会假设

  1. 所有给定的条件都是完全匹配的字符串
  2. 所有未指定的条件都匹配任何内容(通配符)
  3. 我们可以丢弃所有产生 False 的规则
  4. 规则可能包含匹配任何内容的 None (通配符)
  5. 如果至少有一条规则与所有给定条件匹配,则结果为 True,否则为 False

我们可以将快速查找构建为集合(匹配规则 id)的 dict(值)的 dict(列):

from collections import namedtuple

WILDCARD = None

Rule = namedtuple("Rule", ["Country", "Criteria1", "Criteria2"])

rules = [
    Rule("UK", "Somestring1", "Somestring3"),
    Rule("UK", "Somestring1", "Somestring2"),
    Rule("US", "Somestring4", WILDCARD)
]

def build_lookup(rules):
    columns = Rule._fields
    # create lookup table (special handling of wildcard entries)
    lookup = {column: {WILDCARD: set()} for column in columns}
    # index rules by criteria
    for id, rule in enumerate(rules):
        for column, value in zip(columns, rule):
            if value in lookup[column]:
                lookup[column][value].add(id)
            else:
                lookup[column][value] = {id}
    return lookup

rule_lookup = build_lookup(rules)

使用给定的样本数据,rule_lookup现在包含

{
    'Country':   {WILDCARD: set(), 'UK': {0, 1}, 'US': {2}},
    'Criteria1': {WILDCARD: set(), 'Somestring1': {0, 1}, 'Somestring4': {2}},
    'Criteria2': {WILDCARD: {2}, 'Somestring2': {1}, 'Somestring3': {0}}
}

然后我们可以快速将标准与规则匹配,例如

def all_matching_rules(criteria):
    """
    criteria is a dict of {column: value} to match

    Return a set of all rule ids which match criteria
    """
    if criteria:
        result = empty = set()
        first = True
        for column, value in criteria.items():
            ids = rule_lookup[column].get(value, empty) | rule_lookup[column][WILDCARD]
            if first:
                result = ids
                first = False
            else:
                result &= ids   # find intersection of sets
            # short-circuit evaluation if result is null set
            if not result:
                break
        return result
    else:
        # no criteria, return everything
        return set(range(len(rules)))

def any_rule_matches(criteria):
    """
    criteria is a dict of {column: value} to match

    Return True if any rule matches criteria, else False
    """
    if criteria:
        return bool(all_matching_rules(criteria))
    else:
        return bool(len(rules))

运行起来像

>>> all_matching_rules({"Country": "UK", "Criteria2": "Somestring8"})
set()

>>> all_matching_rules({"Country": "US", "Criteria2": "Somestring8"})
{2}

>>> any_rule_matches({"Country": "UK", "Criteria2": "Somestring8"})
False

>>> any_rule_matches({"Country": "US", "Criteria2": "Somestring8"})
True

Timeit 报告说这在我的机器上运行大约 930ns - 应该足够快;-)

于 2017-08-10T00:07:11.153 回答