一、为什么使用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. 在K8S上跑一个helloworld

    建立docker镜像 为了方便起见,这里直接使用一个js网页作为应用,以此创建镜像 hello world网页 创建server.js,输入以下代码创建helloworld网页: var http = ...

  2. Android地图开发获取sHA1值方法

    public static String sHA1(Context context) { try { PackageInfo info = context.getPackageManager().ge ...

  3. Python之输入输出

    python中变量的输出 # 打印提示 print('hello world') print('你好!') # 输出变量 url = 'loaderman' print('我是:',url) prin ...

  4. Jmeter (四)聚合报告详解

  5. 前端必须掌握的 nginx 技能(2)

    概述 作为一个前端,我觉得必须要学会使用 nginx 干下面几件事: 代理静态资源 设置反向代理(添加https) 设置缓存 设置 log 部署 smtp 服务 设置 redis 缓存(选) 下面我按 ...

  6. 精通CSS:高级Web标准解决方案(第二版) 不明白的地方

    P47 在图3-14中,当把框1向左浮动时,它脱离文档流并且向左移动,直到它的左边缘碰到包含框的左边缘.因为它不在处于文档流中,所以它不占据空间,实际上覆盖住了框2,使框2从视图中消失. 我的疑问是, ...

  7. Mysql事务特性

    事务概念 事务可由一条sql或者一组sql组成.事务是访问并更新数据库中各种数据项的一个程序执行单元. 事务会把数据库从一种一致状态转换为另一种一致状态.在数据提交工作时,可以确保要么所有修改都已经保 ...

  8. 【Qt开发】QT中用函数把float转化成QString

    QT中用函数把float转化成QString 最普通的用法,例如:     float f;     QString str = QString("float is %1").ar ...

  9. 【VS开发】【图像处理】GigE和USB3 vision选择?

    [VS开发][图像处理]GigE和USB3 vision选择? 具体得看你现场的应用吧,如 现场需要的工作距离,网线可达到100m以内,USB3.0一般般的5m以内: GigE双端都有卡扣,保证了与相 ...

  10. jenkins自动化部署工具

    jenkins自动化测试 & 持续集成 知识点: 1.下载地址:jenkins.io download: