【问题标题】:Python web service subscribed to reactive source produces strange behavior in object订阅响应式源的 Python Web 服务在对象中产生奇怪的行为
【发布时间】:2018-05-14 06:59:00
【问题描述】:

我已经使用 Falcon 实现了一个网络服务。该服务存储了一个状态机 (pytransitions),它在构造函数中传递给服务的资源。该服务使用 gunicorn 运行。

Web 服务在启动时使用 RxPy 启动一个进程。 on_next(event) 中返回的事件用于触发状态机中的转换。

错误

我希望状态机在服务和资源中具有一致的状态,但似乎资源中的状态永远不会改变。

我们有一个尝试重现此行为的测试,但令人惊讶的是该测试有效

class TochoLevel(object):

    def __init__(self, tochine):
        self.tochine = tochine

    def on_get(self, req, res):
        res.status = falcon.HTTP_200
        res.body = self.tochine.state


def get_machine():
    states = ["low", "medium", "high"]

    transitions = [
        {'trigger': 'to_medium', 'source': ['low', 'medium', 'high'], 'dest': 'medium'},
        {'trigger': 'to_high', 'source': ['low', 'medium', 'high'], 'dest': 'high'},
        {'trigger': 'to_low', 'source': ['low', 'medium', 'high'], 'dest': 'low'}
    ]

    locked_factory = MachineFactory.get_predefined(locked=True)

    return locked_factory(
        states=states,
        transitions=transitions,
        initial='low',
        auto_transitions=False,
        queued=False
    )

def _level_observable(observer):
    for i in range(1, 21):
        sleep(0.1)
        next_val = 'to_low'

        if 8 <= i <= 15:
            next_val = 'to_medium'
        elif i > 15:
            next_val = 'to_high'
        observer.on_next(next_val)

    observer.on_completed()

def get_level_observable():
    return Observable.create(_level_observable)

class NotBlockingService(falcon.API):
    def __init__(self):
        super(NotBlockingService, self).__init__()

        self.tochine = get_machine()
        self.add_route('/tochez', TochoLevel(self.tochine))

    def _run_machine(self, val):
        self.tochine.trigger(val)
        print('machine exec: {}, state: {}'.format(val, self.tochine.state))
        return self.tochine.state

    def start(self):
        source = get_level_observable()
        (source.subscribe_on(ThreadPoolScheduler(2))
            .subscribe(self._run_machine))


def test_can_query_falcon_service_while_being_susbcribed_as_observer():

    svc = NotBlockingService()
    client = testing.TestClient(svc)

    assert client.simulate_get('/tochez').text == 'low'

    start = time()
    svc.start()
    sleep(1.2)

    assert client.simulate_get('/tochez').text == 'medium'
    end = time()

    sleep(1.2)

    assert client.simulate_get('/tochez').text == 'high'
    assert (end - start) < 2

问题

当我使用 gunicorn 启动服务并在 rxpyon_next 方法中传播状态时,为什么状态机不会更改资源 TochoLevel 中的状态>?

【问题讨论】:

  • 你能提供一个最小的 git repo,我可能知道出了什么问题,但需要尝试一些事情

标签: python gunicorn falconframework rx-py


【解决方案1】:

当然,当您在开发模式下执行服务时,您只使用了一个 fork(一个执行过程)。当您使用 Gunicorn 等软件时,您正在使用预分叉策略在生产环境中提供可靠的服务。

Preforking 策略生成许多子流程来解决请求,并且逻辑是独立的,每个 fork 在不同请求之间以独立模式工作。

Gunicorn,感谢 Python 中 WSGI 的标准化 App 方案(Python2_PEP-333 & Python3_PEP-3333),接收一个 APP 对象。 Gunicorn 启动其配置中指示的尽可能多的实例(预分叉)。 Gunicorn 将这样的分叉称为 workersby default it uses 1 worker。每个工作人员都将使用其状态,也许 Gunicorn 还会为每个请求创建新的 App 对象实例...

这就是你的状态机没有持久性的原因。

? 提示:首先尝试使用 1 个 worker 启动 Gunicorn,并检查状态机的状态持久性。如果你实现了状态机的持久化,第二个要解决的问题就是状态机同步所有的worker。

【讨论】:

  • 我在配置中使用 workers = 1。我还尝试了选项preload_app = True,它应该在分叉之前预加载应用程序。问题是,如果我在没有 rx-py 的情况下启动服务,状态机将正常工作。
  • 也许使用 Gunicorn 是没有意义的,它专注于只使用一个工作人员的预分叉解决方案......注意状态机一致性,因为 Gunicorn 只确保您的应用程序在工作人员数量下运行你想要的,他们会回应请求。在执行期间,Gunicorn 可以杀死工人并在必要时重新启动它。也许你需要一些专注于池请求而不是预分叉策略的东西。关于rx-py,我对库不熟悉。
  • 我们终于在内存中使用了缓存来存储状态机的状态,这样就可以有不同的worker了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 2021-09-20
  • 1970-01-01
相关资源
最近更新 更多