How to implement a async grpc python server?

后端 未结 3 1575
滥情空心
滥情空心 2020-12-14 11:52

I need to call a celery task for each GRPC request, and return the result. In default GRPC implementation, each request is processed in a separate thread from a threadpool.<

相关标签:
3条回答
  • 2020-12-14 12:30

    It can be done asynchronously if your call to res.get can be done asynchronously (if it is defined with the async keyword).

    While grpc.server says it requires a futures.ThreadPoolExecutor, it will actually work with any futures.Executor that calls the behaviors submitted to it on some thread other than the one on which they were passed. Were you to pass to grpc.server a futures.Executor implemented by you that only used one thread to carry out four hundred (or more) concurrent calls to EventReporting.ReportEvent, your server should avoid the kind of blocking that you describe.

    0 讨论(0)
  • 2020-12-14 12:36

    As noted by @Michael in a comment, as of version 1.32, gRPC now supports asyncio in its Python API. If you're using an earlier version, you can still use the asyncio API via the experimental API: from grpc.experimental import aio. An asyncio hello world example has also been added to the gRPC repo. The following code is a copy of the example server:

    import logging                                                                  
    import asyncio                                                                  
    from grpc import aio                                                            
                                                                                    
    import helloworld_pb2                                                           
    import helloworld_pb2_grpc                                                      
                                                                                    
                                                                                    
    class Greeter(helloworld_pb2_grpc.GreeterServicer):                             
                                                                                    
        async def SayHello(self, request, context):                                 
            return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)    
                                                                                    
                                                                                    
    async def serve():                                                              
        server = aio.server()                                                       
        helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)        
        listen_addr = '[::]:50051'                                                  
        server.add_insecure_port(listen_addr)                                       
        logging.info("Starting server on %s", listen_addr)                          
        await server.start()                                                        
        await server.wait_for_termination()                                         
                                                                                    
                                                                                    
    if __name__ == '__main__':                                                      
        logging.basicConfig(level=logging.INFO)                                     
        asyncio.run(serve())
    

    See my other answer for how to implement the client.

    0 讨论(0)
  • 2020-12-14 12:45

    In my opinion is good simple implementation async grpc server, same like http based on aiohttp.

    import asyncio
    from concurrent import futures
    import functools
    import inspect
    import threading
    
    from grpc import _server
    
    def _loop_mgr(loop: asyncio.AbstractEventLoop):
    
        asyncio.set_event_loop(loop)
        loop.run_forever()
    
        # If we reach here, the loop was stopped.
        # We should gather any remaining tasks and finish them.
        pending = asyncio.Task.all_tasks(loop=loop)
        if pending:
            loop.run_until_complete(asyncio.gather(*pending))
    
    
    class AsyncioExecutor(futures.Executor):
    
        def __init__(self, *, loop=None):
    
            super().__init__()
            self._shutdown = False
            self._loop = loop or asyncio.get_event_loop()
            self._thread = threading.Thread(target=_loop_mgr, args=(self._loop,),
                                            daemon=True)
            self._thread.start()
    
        def submit(self, fn, *args, **kwargs):
    
            if self._shutdown:
                raise RuntimeError('Cannot schedule new futures after shutdown')
    
            if not self._loop.is_running():
                raise RuntimeError("Loop must be started before any function can "
                                   "be submitted")
    
            if inspect.iscoroutinefunction(fn):
                coro = fn(*args, **kwargs)
                return asyncio.run_coroutine_threadsafe(coro, self._loop)
    
            else:
                func = functools.partial(fn, *args, **kwargs)
                return self._loop.run_in_executor(None, func)
    
        def shutdown(self, wait=True):
            self._loop.stop()
            self._shutdown = True
            if wait:
                self._thread.join()
    
    
    # --------------------------------------------------------------------------- #
    
    
    async def _call_behavior(rpc_event, state, behavior, argument, request_deserializer):
        context = _server._Context(rpc_event, state, request_deserializer)
        try:
            return await behavior(argument, context), True
        except Exception as e:  # pylint: disable=broad-except
            with state.condition:
                if e not in state.rpc_errors:
                    details = 'Exception calling application: {}'.format(e)
                    _server.logging.exception(details)
                    _server._abort(state, rpc_event.operation_call,
                           _server.cygrpc.StatusCode.unknown, _server._common.encode(details))
            return None, False
    
    async def _take_response_from_response_iterator(rpc_event, state, response_iterator):
        try:
            return await response_iterator.__anext__(), True
        except StopAsyncIteration:
            return None, True
        except Exception as e:  # pylint: disable=broad-except
            with state.condition:
                if e not in state.rpc_errors:
                    details = 'Exception iterating responses: {}'.format(e)
                    _server.logging.exception(details)
                    _server._abort(state, rpc_event.operation_call,
                           _server.cygrpc.StatusCode.unknown, _server._common.encode(details))
            return None, False
    
    async def _unary_response_in_pool(rpc_event, state, behavior, argument_thunk,
                                      request_deserializer, response_serializer):
        argument = argument_thunk()
        if argument is not None:
            response, proceed = await _call_behavior(rpc_event, state, behavior,
                                                     argument, request_deserializer)
            if proceed:
                serialized_response = _server._serialize_response(
                    rpc_event, state, response, response_serializer)
                if serialized_response is not None:
                    _server._status(rpc_event, state, serialized_response)
    
    async def _stream_response_in_pool(rpc_event, state, behavior, argument_thunk,
                                       request_deserializer, response_serializer):
        argument = argument_thunk()
        if argument is not None:
            # Notice this calls the normal `_call_behavior` not the awaitable version.
            response_iterator, proceed = _server._call_behavior(
                rpc_event, state, behavior, argument, request_deserializer)
            if proceed:
                while True:
                    response, proceed = await _take_response_from_response_iterator(
                        rpc_event, state, response_iterator)
                    if proceed:
                        if response is None:
                            _server._status(rpc_event, state, None)
                            break
                        else:
                            serialized_response = _server._serialize_response(
                                rpc_event, state, response, response_serializer)
                            print(response)
                            if serialized_response is not None:
                                print("Serialized Correctly")
                                proceed = _server._send_response(rpc_event, state,
                                                         serialized_response)
                                if not proceed:
                                    break
                            else:
                                break
                    else:
                        break
    
    _server._unary_response_in_pool = _unary_response_in_pool
    _server._stream_response_in_pool = _stream_response_in_pool
    
    
    if __name__ == '__main__':
        server = grpc.server(AsyncioExecutor())
        # Add Servicer and Start Server Here
    

    link to original:
    https://gist.github.com/seglberg/0b4487b57b4fd425c56ad72aba9971be

    0 讨论(0)
提交回复
热议问题