使用django+rpc进行服务内部交互
一、为什么使用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进行服务内部交互的更多相关文章
- Hadoop RPC源码阅读-交互协议
Hadoop版本Hadoop2.6 RPC主要分为3个部分:(1)交互协议(2)客户端 (3)服务端 (1)交互协议 协议:把某些接口和接口中的方法称为协议,客户端和服务端只要实现这些接口中的方法就可 ...
- 发布Hessian服务作为服务内部基础服务
摘要:Hessian经常作为服务内部RPC工具来使用,速度快效率高.重构代码的核心思想就是把共用的代码段提出来,使代码结构优化:架构设计类似,把基本的共用的服务提出来,使架构优化.下面讲述一下我在具体 ...
- 解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离
在某国外大型汽车公司BI项目中,有一个子项目,需要通过大屏幕展示销售报表,程序需要自动启动和关闭.开发人员在开发过程中,发现在Win7的service中不能直接操作UI进程,调查过程中,发现如 ...
- Android客户端与服务端交互之登陆示例
Android客户端与服务端交互之登陆示例 今天了解了一下android客户端与服务端是怎样交互的,发现其实跟web有点类似吧,然后网上找了大神的登陆示例,是基于IntentService的 1.后台 ...
- java客户端与服务端交互通用处理 框架解析
一.综述 java 客户端与服务端交互过程中,采用NIO通讯是异步的,客户端基本采用同一处理范式,来进行同异步的调用处理. 处理模型有以下几个要素: 1. NIO发送消息后返回的Future 2. 每 ...
- IOS开发系列之阿堂教程:玩转IPhone客户端和Web服务端交互(客户端)实践
说到ios的应用开发,我们不能不提到web server服务端,如果没有服务端的支持,ios应用开发就没有多大意义了,因为从事过手机开发的朋友都知道(Android也一样),大量复杂业务的处理和数据库 ...
- c++ 网络编程(一)TCP/UDP windows/linux 下入门级socket通信 客户端与服务端交互代码
原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html c++ 网络编程(一)TCP/UDP 入门级客户端与服务端交互代码 网 ...
- 在这个应用中,我使用了 MQ 来处理异步流程、Redis 缓存热点数据、MySQL 持久化数据,还有就是在系统中调用另外一个业务系统的接口,对我的应用来说这些都是属于 RPC 调用,而 MQ、MySQL 持久化的数据也会存在于一个分布式文件系统中,他们之间的调用也是需要用 RPC 来完成数据交互的。
在这个应用中,我使用了 MQ 来处理异步流程.Redis 缓存热点数据.MySQL 持久化数据,还有就是在系统中调用另外一个业务系统的接口,对我的应用来说这些都是属于 RPC 调用,而 MQ.MySQ ...
- 0202年,您真的需要Thrift这样一个RPC微服务框架来拯救一下传统HTTP接口(api)了
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_104 目前市面上类似Django的drf框架基于json的http接口解决方案大行其道,人们也热衷于在接口不多.系统与系统交互较少 ...
随机推荐
- BOSCH汽车工程手册————混合驱动
首先放一波资源,一千两百多页的pdf 链接:https://pan.baidu.com/s/15IsvHqOFCnqAKwY_SR4-lA提取码:6wmz 混合驱动 混合驱动有串联驱动并联驱动以及两种 ...
- 使用hash方法切割文件
如果有大型数据文件(如每行为url或者ip或者单词等的),以G为单位的,处理的时候需先切分.普通切分方法直接根据数据条数切分,得到的每个文件大小相近. 但是有时需要将相同数据放到相同文件中.可以使用h ...
- office 安装破解
1. 打开Office Tool Plus.exe部署 2. 添加产品 `excel` `prowerpoint` `word` 3. 选择安装文件管理 选择下载安装 4.安装完成后点击开始部署 5. ...
- 第 10 章 python进程与多进程
一.背景知识 顾明思义,进程即正在执行的一个过程,进程是对正在云的程序的一个抽象. 进程的概念起源与操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一,操作系统的其他所 ...
- Flume HA
flume提供fail over和load balance功能 1.添加collector配置(配置两个collector) # Name the components on this agents1 ...
- vue2创建webpack项目build之后无法正常显示页面的问题
最近在做vue项目的时候,项目正常运行,但是当我打包上线之后,却出现无法出现页面空白的情况,打开控制台,发现无法加载到css和js文件. 仔细观察发现路径中少了一个dis文件夹,于是我加上dist文件 ...
- 用poi来导出数据到excel文档
package cn.com.dyg.work.common.utils; import org.apache.poi.hssf.usermodel.HSSFRichTextString; impor ...
- java课堂疑问解答与思考1
问题一 Java类中只能有一个公有类吗?用Eclipse检测以下程序是否正确.是否在接口中同样适用. 答:一个源文件里必须稚嫩发有一个公有类,名称必须与文件名一致.以上程序经过编译没有提示错误.jav ...
- Java中volatile关键字的最全总结
转载至:https://blog.csdn.net/u012723673/article/details/80682208 关于volatile很重要的一点: 它保证了可见性,即其他线程对volati ...
- Akka系列(六):Actor解决了什么问题?
前言..... 文档来源于 : What problems does the actor model solve? Actor解决了什么问题? Akka使用Actor模型来克服传统面向对象编程模型的 ...