一、为什么使用rpc。

1)相比uwsgi,使用rpc的长连接可以不需要频繁创建连接,提高传输效率。

2)rpc支持同步和异步,对于不需要等待返回的消息可以不等待返回继续运行,减少客户端等待时间。

3)使用rpc入口是我们自己定义的,可以根据不同消息类型定制不同的策略。

二、设计思路

使用统一入口,采用django的url resolve匹配,然后完成调用,不改变django rest接口的开发模式。

服务端处理采用同步异步分离,异步任务用单独的进程处理,并为异步任务制定处理策略:

1)对于同步任务,仍然需要立即调用返回。

2)对于异步任务,可以进行任务分级:

一级是重要任务,属于系统能力不足时必须优先保障的;

二级任务,在系统能力足够时仍然需要执行,一旦能力不足,优先保障一级任务;

3)对异步任务,制定执行策略:

一是必须执行的任务,这部分任务即使积压也有一条条全部执行完成;

二是只需要执行最后一条的,常见于更新信息,对于积压多条的同一消息,丢弃前面的,保留最后一条;

三是可丢弃的,遇到性能不足,这一类消息不执行,直接丢弃。

三、 grpc的proto文件

syntax = "proto3";
package rpc;
service RPCServer {
rpc handel(Input) returns (Output){}
} message Input {
string params = 1;
} message Output {
string content = 1;
}

入参为Input,返回为Output,所有接口调用都走这边。

四、客户端调用

import grpc
import time
import json
import traceback
import threading
import uuid
from datetime import datetime from . import data_pb2, data_pb2_grpc _HOST = ''
_PORT = ''
CHANNEL = grpc.insecure_channel(_HOST + ':' + _PORT) class ManoEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return str(obj)
if isinstance(obj, uuid.UUID):
return str(obj)
return json.JSONEncoder.default(self, obj) def mano_encode(data):
return json.dumps(data, cls=ManoEncoder) def call_rpc(url, headers, resource, content, logger):
try:
params = json.dumps({
'url': url,
'headers': headers,
'method': resource['method'],
'content': content
})
timeout = resource.get('timeout', 5)
client = data_pb2_grpc.RPCServerStub(CHANNEL)
response = client.handel.future(data_pb2.Input(params=params), timeout)
while not response.done():
time.sleep(0.01)
result = json.loads(response.result().content)
print(result['status_code'])
return result['status_code'], mano_encode(result['data'])
except Exception as err:
logger.error(traceback.format_exc())
logger.error('call url %s failed, msg is %s' % (url, err.message))
return '', err.message

入参params需包含:rest url,头信息headers,rest类型,以及request body;

结果采用异步获取,不持续占用连接,对于不需要结果的,可以不等待,这边没写。

五、服务端实现

import os
import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "*.settings")
django.setup() import grpc
import json
import time
import random
import traceback
import threading
import uuid
import logging
from datetime import datetime from concurrent import futures
from multiprocessing import Process, Queue, Value
from Queue import Queue as ManoQueue from . import data_pb2, data_pb2_grpc
from django.urls import get_resolver
from django.utils.functional import cached_property _ONE_DAY_IN_SECONDS = 60 * 60 * 24
_HOST = '[::]'
_PORT = ''
_PROCESS_COUNT = 2
RESOLVER = get_resolver()
logger = logging.getLogger(__name__) message_queue = Queue() # 异步任务队列,用于进程通信
status_level2 = Value('I', 1) # 二级队列状态,用于进程通信 class ManoEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return str(obj)
if isinstance(obj, uuid.UUID):
return str(obj)
return json.JSONEncoder.default(self, obj) def mano_encode(data):
return json.dumps(data, cls=ManoEncoder) class RPCServer(data_pb2_grpc.RPCServerServicer):
def handel(self, request, context):
input_info = json.loads(request.params)
if input_info.get('reply', True) is True: # reply为True代表同步,否则异步
res_url = input_info['url']
headers = input_info['headers']
method = input_info['method']
content = input_info['content']
status_code, data = self.call_sync(res_url, headers, method, content)
return data_pb2.Output(content=mano_encode({'data': data, 'status_code': status_code}))
else:
if input_info['queue_detail']['level'] == 2 and not status_level2:
data = 'queue of status level2 is not active'
status_code = ''
else:
message_queue.put(request.params)
data = 'success'
status_code = ''
return data_pb2.Output(content=mano_encode({'data': data, 'status_code': status_code})) @staticmethod
def call_sync(res_url, headers, method, content):
try:
resp_status, resp_body = call_inner(res_url, headers, method, content, logger)
return resp_status, resp_body
except Exception as err:
logger.error(traceback.format_exc())
logger.error('call url %s failed, msg is %s' % (res_url, err.message))
return '', err.message def main(): # rpc 服务主进程
bind_address = '%s:%s' % (_HOST, _PORT)
_run_server(bind_address) # 启动rpc进程
_run_queue_process() # 启动异步任务处理进程 def _run_server(bind_address):
grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=100, ))
data_pb2_grpc.add_RPCServerServicer_to_server(RPCServer(), grpc_server)
grpc_server.add_insecure_port(bind_address)
grpc_server.start() def _run_queue_process():
worker = Process(target=_handle_no_wait_request, args=(message_queue, status_level2,))
worker.start()
worker.join() def _handle_no_wait_request(q, status_2): # 异步任务分类
first_order_queue = [ManoQueue(maxsize=0), list()]
second_order_queue = [ManoQueue(maxsize=1000), list()]
mano_queue = [first_order_queue, second_order_queue]
thread_pool = futures.ThreadPoolExecutor(max_workers=50)
threading.Thread(target=_start_message_monitor, args=(q, mano_queue, status_2,)).start() # 根据策略进行异步任务分类
while True:
num_threads = len(thread_pool._threads)
if num_threads < 50:
input_info = _get_request(mano_queue) # 获取本次需执行的任务,每个队列机会均等
res_url = input_info['url']
headers = input_info['headers']
method = input_info['method']
content = input_info['content']
thread_pool.submit(RPCServer.call_sync, res_url, headers, method, content) # 交给工作线程
logger.info('handle success')
else:
logger.info('process busy')
time.sleep(0.1) def _start_message_monitor(q, mano_queue, status_2):
while True:
data = q.get()
_handel_by_queue(data, mano_queue, status_2) def _get_request(mano_queue):
active_index = _get_active_queue(mano_queue)
if active_index:
index = random.choice(active_index)
i, k = int(index.split('_')[0]), int(index.split('_')[1])
q = mano_queue[i][k]
if isinstance(q, ManoQueue):
request_info = json.loads(q.get())
else:
request_info = json.loads(q.pop(0))
else:
request_info = {}
return request_info def _get_active_queue(mano_queue):
active_index = []
if not mano_queue[0][0].empty():
active_index.append('0_0')
if not mano_queue[1][0].empty():
active_index.append('1_0')
if len(mano_queue[0][1]) != 0:
active_index.append('0_1')
if len(mano_queue[1][1]) != 0:
active_index.append('1_1')
return active_index def _handel_by_queue(data, mano_queue, status_2): # 根据请求级别进行消息分类
input_info = json.loads(data)
level = input_info['queue_detail']['level']
policy = input_info['queue_detail']['limit_policy']
if level == 1:
_handel_by_policy(mano_queue[0], policy, data)
elif level == 2:
request_queue = mano_queue[1]
_handel_by_policy(mano_queue[1], policy, data)
if request_queue[0].qsize() > 0.8 * request_queue[0].maxsize:
status_2.value = 0
elif request_queue[0].qsize() < 0.6 * request_queue[0].maxsize:
status_2.value = 1 def _handel_by_policy(request_queue, policy, data): # 根据请求策略进行消息分类
if policy == 'execute': # 必须执行的异步任务
request_queue[0].put(data)
elif policy == 'last': # 阻塞时可以只执行最后一次的异步任务
try:
while True:
request_queue[1].remove(data)
except ValueError:
request_queue[1].append(data)
else: # 阻塞时可以丢弃的异步任务
if request_queue[0].qsize < request_queue[0].maxsize * 0.6:
request_queue[0].put(data) # 先丢弃前面的 def call_inner(res_url, headers, method, content, logger):
logger.info('[call_inner] url is %s' % res_url)
url, params = get_url_and_params(res_url)
meta = get_meta(headers)
request = Request(url=url, full_url=res_url, params=params, content=content, meta=meta, method=method)
resolver_match = RESOLVER.resolve(url) # URL 匹配
callback, callback_args, callback_kwargs = resolver_match
call_method = getattr(callback.view_class(), method.lower())
if not method:
return '', 'not support this operate'
try:
if callback_kwargs:
result = call_method(request, '', **callback_kwargs)
else:
result = call_method(request)
except BaseException as err:
logger.error(traceback.format_exc())
logger.error('call url %s failed, msg is %s' % (res_url, err.message))
return '', err.message
return str(result.status_code), result.data def get_url_and_params(full_url):
params = {}
if '?' in full_url:
url, params_str = full_url.split('?')[0], full_url.split('?')[1]
for key_value in params_str.split('&'):
key, value = key_value.split('=')[0], key_value.split('=')[1]
params[key] = value
else:
url = full_url
return url, params def get_meta(headers):
meta = {}
# custom
return meta class Request(object):
def __init__(self, **kwargs):
self.data = self.get_content(kwargs['content'])
self.query_params = kwargs['params']
self.path = kwargs['url']
self.full_path = kwargs['full_url']
self.FILES = {}
self.META = kwargs['meta']
self.COOKIES = {}
self._request = InnerOBJ(kwargs['method']) @staticmethod
def get_content(content):
if not content:
req_data = {}
else:
req_data = content if isinstance(content, dict) else json.loads(content)
return req_data def __str__(self):
return '<Request> %s' % self.path @cached_property
def GET(self):
return self.query_params def get_full_path(self):
return self.full_path class InnerOBJ(object):
def __init__(self, method):
self.method = method.upper() if __name__ == '__main__':
main()

使用django+rpc进行服务内部交互的更多相关文章

  1. Hadoop RPC源码阅读-交互协议

    Hadoop版本Hadoop2.6 RPC主要分为3个部分:(1)交互协议(2)客户端 (3)服务端 (1)交互协议 协议:把某些接口和接口中的方法称为协议,客户端和服务端只要实现这些接口中的方法就可 ...

  2. 发布Hessian服务作为服务内部基础服务

    摘要:Hessian经常作为服务内部RPC工具来使用,速度快效率高.重构代码的核心思想就是把共用的代码段提出来,使代码结构优化:架构设计类似,把基本的共用的服务提出来,使架构优化.下面讲述一下我在具体 ...

  3. 解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离

        在某国外大型汽车公司BI项目中,有一个子项目,需要通过大屏幕展示销售报表,程序需要自动启动和关闭.开发人员在开发过程中,发现在Win7的service中不能直接操作UI进程,调查过程中,发现如 ...

  4. Android客户端与服务端交互之登陆示例

    Android客户端与服务端交互之登陆示例 今天了解了一下android客户端与服务端是怎样交互的,发现其实跟web有点类似吧,然后网上找了大神的登陆示例,是基于IntentService的 1.后台 ...

  5. java客户端与服务端交互通用处理 框架解析

    一.综述 java 客户端与服务端交互过程中,采用NIO通讯是异步的,客户端基本采用同一处理范式,来进行同异步的调用处理. 处理模型有以下几个要素: 1. NIO发送消息后返回的Future 2. 每 ...

  6. IOS开发系列之阿堂教程:玩转IPhone客户端和Web服务端交互(客户端)实践

    说到ios的应用开发,我们不能不提到web server服务端,如果没有服务端的支持,ios应用开发就没有多大意义了,因为从事过手机开发的朋友都知道(Android也一样),大量复杂业务的处理和数据库 ...

  7. c++ 网络编程(一)TCP/UDP windows/linux 下入门级socket通信 客户端与服务端交互代码

    原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html c++ 网络编程(一)TCP/UDP  入门级客户端与服务端交互代码 网 ...

  8. 在这个应用中,我使用了 MQ 来处理异步流程、Redis 缓存热点数据、MySQL 持久化数据,还有就是在系统中调用另外一个业务系统的接口,对我的应用来说这些都是属于 RPC 调用,而 MQ、MySQL 持久化的数据也会存在于一个分布式文件系统中,他们之间的调用也是需要用 RPC 来完成数据交互的。

    在这个应用中,我使用了 MQ 来处理异步流程.Redis 缓存热点数据.MySQL 持久化数据,还有就是在系统中调用另外一个业务系统的接口,对我的应用来说这些都是属于 RPC 调用,而 MQ.MySQ ...

  9. 0202年,您真的需要Thrift这样一个RPC微服务框架来拯救一下传统HTTP接口(api)了

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_104 目前市面上类似Django的drf框架基于json的http接口解决方案大行其道,人们也热衷于在接口不多.系统与系统交互较少 ...

随机推荐

  1. leetcode 200岛屿的个数

    主要考察图搜索: 方法一:染色法,时间O(mn) 遍历一遍,再通过BFS或DFS将所有临近岛屿染色,使用dfs时将numIslands中的bfs换成dfs即可: /***** 遍历所有的点: 只要遇见 ...

  2. rocketMQ 订阅关系

    场景:2 个消费者进程中,创建了 2 个消费者,同属于 1 个消费组,但是订阅了不同的 topic,会因为订阅信息相互覆盖,导致拉不到消息. 原因是 rocketMQ 的订阅关系,是根据 group ...

  3. Linux常用命令:修改文件权限chmod 754/744

    常用命令:chmod 777  文件或目录 chmod  777 /etc/squid 运行命令后,squid文件夹(目录)的权限就被修改为777(可读可写可执行). Linux系统中,每个用户的角色 ...

  4. Windows服务器连接

    公司部分服务器是使用的Windows系统,需要远程连接 首先,我们要做的就是打开一个运行界面.按住win+r. 然后,我们在打开一行里输入mstsc. 这个时候我们点击确定,就会弹出一个远程桌面连接的 ...

  5. opencv的曲线拟合polyfit

    推荐一个不错的网页,可以直接用solve函数求解方程组: http://m.blog.csdn.net/u014652390/article/details/52789591 4.1 曲线拟合的最小二 ...

  6. 【BW系列】SAP BW on HANA 迁移问题

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[BW系列]SAP BW on HANA 迁移问 ...

  7. python学习之内存驻留机制简述

    第四章 4.1 小数据池 4.1.1 代码块 一个模块,一个函数,一个类,甚至一个command命名都可以称之为一个代码块. 官方解释: A Python program is constructed ...

  8. 【VS开发】raw socket 的例子

    raw socket 的例子 一. 摘要    Raw Socket: 原始套接字    可以用它来发送和接收 IP 层以上的原始数据包, 如 ICMP, TCP, UDP... int sockRa ...

  9. 【VS开发】使用MFC创建并调用ActiveX控件

    使用MFC创建并调用ActiveX控件 今天做了一下ActiveX的使用测试,总结一下: 首先使用MFC创建一个activeX的控件譬如ActiveXTest,编译成ocx并注册,然后另外编写一个测试 ...

  10. [转帖]Chrome 错误代码:ERR_UNSAFE_PORT

    Chrome 错误代码:ERR_UNSAFE_PORT 2018年07月18日 09:07:50 孤舟听雨 阅读数 182 https://blog.csdn.net/u013043762/artic ...