一、为什么使用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. 【BZOJ 3709: [PA2014]Bohater】

    首先,这是我n久之前培训的时候老师讲的题目了,今天突然看到,那就讲讲吧. 首先,我们考虑怎么打怪... 显然,我们需要保证这个怪要尽可能的打死(就是尽量不被干死),并且保证尽可能的净获得血量大的在前面 ...

  2. Python的环境的搭建

    下载Python软件安装,配置环境变量 给Eclipse安装pydev插件即可

  3. Java学习之==>int和Integer的区别和联系

    一.区别 1.类型 int是java中原始八种基本数据类型之一: Integer是一个类,包装整型提供了很多日常的操作: 2.存储位置和大小 int是由jvm底层提供,由Java虚拟机规范,int型数 ...

  4. [译]深入 NGINX: 为性能和扩展所做之设计

    来自:http://ifeve.com/inside-nginx-how-we-designed-for-performance-scale/ 这篇文章写了nginx的设计,写的很仔细全面, 同时里面 ...

  5. Day01:API文档 / 字符串基本操作

    JDK API 什么是JDK API? JDK中包含大量的API类库,所谓AP就是一些写好的,可提供直接调用的功能(在Java语言中,这些功能以类的形式封装). JDK API包含的类库功能强大,经常 ...

  6. 关于glog使用中遇到的问题

    项目中需要打log,当初看到glog,觉得google出品应该差不了,而且简单易用,库不是很大,就选择他了. 但是在使用中还真的发现一些不顺手和库设计上的问题,反正和我的使用习惯有点不一样. 设置lo ...

  7. 删除mysl

    mysql卸载不干净会很麻烦 1.yum remove mysql mysql-server mysql-libs compat-mysql51 2.rm -rf /var/lib/mysql 检查是 ...

  8. 【Qt开发】在QLabel已经显示背景图片后绘制图形注意事项

    主要是要解决图形覆盖的问题,通常的办法就是对QLabel进行子类化,并重载函数: void myLabel::paintEvent(QPaintEvent *event)   {       QLab ...

  9. Java 并发编程:核心理论(一)

    前言......... 并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可 ...

  10. 【转帖】AMD:未向合资企业THATIC发放后续芯片设计授权

    AMD:未向合资企业THATIC发放后续芯片设计授权 https://www.cnbeta.com/articles/tech/854193.htm 海光和兆芯的CPU 都不靠谱啊. 在台北电脑展(C ...