一:Rabbitmq
1.介绍
# 官网
https://www.rabbitmq.com/getstarted.html
# 消息队列
-中间件(概念很大)---》叫消息队列中间件
-使用 redis 当做消息队列来用
- blpop 阻塞式弹出,实现队列,先进先出
# MQ消息队列,MessageQueue 是什么
消息队列就是基础数据结构中的“先进先出”的一种数据机构。想一下,生活中买东西,需要排队,先排的人先买消费,就是典型的“先进先出”
# MQ解决什么问题
-应用解耦
-流量削峰
-消息分发(发布订阅)
-异步消息
-IPC 进程间通信也可以通过消息队列
2.rabbitmq 安装
# win:https://www.rabbitmq.com/install-windows-manual.html
-erlang 解释器
-rabbitmq 的软件
# centos
yum -y install erlang
yum -y install rabbitmq-server
# docker 安装
docker pull rabbitmq:management
docker run -di --name Myrabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
# 访问虚拟机的 15672 端口,就可以看到图形化界面(官方提供的),手动点点点操作
http://47.100.163.117:15672/#/
3.基于 queue 实现生产者消费者
import Queue
import threading
message = Queue.Queue(10)
def producer(i):
while True:
message.put(i)
def consumer(i):
while True:
msg = message.get()
for i in range(12):
t = threading.Thread(target=producer, args=(i,))
t.start()
for i in range(10):
t = threading.Thread(target=consumer, args=(i,))
t.start()
4.基本使用
4.1 发送者
import pika
# 第一步:登录
# 无密码登录
# connection = pika.BlockingConnection(pika.ConnectionParameters(host='47.100.163.117', port=5672))
# 有密码登录
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
# 第二步: 连接 channel
channel = connection.channel()
# 第三步: 创建一个队列,名字叫 hello
channel.queue_declare(queue='hello2')
# 第四步:向 hello 队列中,发送 Hello World
# body 中是发送的内容
channel.basic_publish(exchange='', routing_key='hello2', body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()
4.2 消费者
import pika
# 连接 rabbitmq
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
# 假设队列不存在,创建
channel = connection.channel()
channel.queue_declare(queue='hello2')
# 回调函数
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
channel.basic_consume(queue='hello2', on_message_callback=callback, auto_ack=True)
print('[*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming() # 程序会夯在这里,等待从消息队列中取消息
5 消息安全(消费者)
# 通知中间件,逻辑处理完了,可以删除数据了
ch.basic_ack(delivery_tag=method.delivery_tag)
# 消费完,确认后,再删除消息
import pika
# 连接 rabbitmq
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
# 假设队列不存在, 创建
channel = connection.channel()
channel.queue_declare(queue='hello2')
# 回调函数
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 拿到消息队列中的数据,要开始消费,处理我的业务逻辑,在处理过程中,报惜了
# raise Exception('异常了')
# 通知中间件,逻辑处理完了,可以删除数据了
ch.basic_ack(delivery_tag=method.delivery_tag)
# 消息安全机制:auto_ack=False
channel.basic_consume(queue='hello2', on_message_callback=callback, auto_ack=False)
print('[*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming() # 程序会夯在这里,等待从消息队列中取消息
6 持久化(生产者)
# 第三步: 创建一个队列,名字叫 hello
channel.queue_declare(queue='hello2', durable=True) # durable=True 使 queue 做持久化
# 第四步:向 hello 队列中,发送 Hello World
# properties 消息做持久化
channel.basic_publish(exchange='', routing_key='hello2', body='Hello World!', properties=pika.BasicProperties(delivery_mode=2))
# queue 和 消息 都要持久化
import pika
# 第一步:登录
# 无密码登录
# connection = pika.BlockingConnection(pika.ConnectionParameters(host='47.100.163.117', port=5672))
# 有密码登录
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
# 第二步: 连接 channel
channel = connection.channel()
# 第三步: 创建一个队列,名字叫 hello
channel.queue_declare(queue='hello2', durable=True) # durable=True 使 queue 做持久化
# 第四步:向 hello 队列中,发送 Hello World
# body 中是发送的内容
# properties 消息做持久化
channel.basic_publish(exchange='', routing_key='hello2', body='Hello World!', properties=pika.BasicProperties(delivery_mode=2))
print(" [x] Sent 'Hello World!'")
connection.close()
7 闲置消费(消费者)
# 在接收消息之前加
channel.basic_qos(prefetch_count=1) # 就只有这一句话 谁闲置谁获取,没必要按照顺序一个一个来
# 正常情况如果有多个消费者,是按照顺序第一个消息给第一个消费者,第二个消息给第二个消费者
# 但是可能第一个消息的消费者处理消息很耗时,一直没结束,就可以让第二个消费者优先获得闲置的消息
import pika
# 连接 rabbitmq
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
# 假设队列不存在,创建
channel = connection.channel()
channel.queue_declare(queue='hello2')
# 回调函数
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
# 拿到消息队列中的数据,要开始消费,处理我的业务逻辑,在处理过程中,报惜了
# raise Exception('异常了')
# 通知中间件,逻辑处理完了,可以删除数据了
ch.basic_ack(delivery_tag=method.delivery_tag)
# 闲置消费
channel.basic_qos(prefetch_count=1) # 就只有这一句话 谁闲置谁获取,没必要按照顺序一个一个来
# 消息安全机制:auto_ack=False
channel.basic_consume(queue='hello2', on_message_callback=callback, auto_ack=False)
print('[*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming() # 程序会夯在这里,等待从消息队列中取消息
二:其他模式
1.发布订阅 fanout 模式
![]()
1.1 发布者
# 声明 exchange='y1'
channel.exchange_declare(exchange='y1', exchange_type='fanout')
import pika
credentials = pika.PlainCredentials('admin', 'admin')
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
channel = connection.channel()
# 声明 exchange='y1'
channel.exchange_declare(exchange='y1', exchange_type='fanout')
channel.basic_publish(exchange='y1',
routing_key='',
body='ysg 1231231')
connection.close()
1.2 订阅者
# 声明 exchange='m1' 交换机
channel.exchange_declare(exchange='y1', exchange_type='fanout')
# 队列 queue 绑定交换机
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='y1', queue=queue_name)
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
channel = connection.channel()
# exchange='m1', exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
# 声明 exchange='m1' 交换机
channel.exchange_declare(exchange='y1', exchange_type='fanout')
# 随机生成一个队列
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print('随机生成的queue的名字', queue_name)
# 队列 queue 绑定交换机
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='y1', queue=queue_name)
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
2.Routing 按关键字匹配
![]()
![]()
2.1 发布者
channel.basic_publish(exchange='y1',
# routing_key='error',
routing_key='info',
body='ysg info')
'''
# -*- coding:utf-8 -*-
# @Project: rabbitMQ
# @Date: 2023/5/14 - 9:49
# @Author: ysg
# @Describe:
send.py
'''
import pika
credentials = pika.PlainCredentials('admin', 'admin')
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
channel = connection.channel()
# 声明 exchange='y1'
channel.exchange_declare(exchange='y1', exchange_type='direct')
channel.basic_publish(exchange='y1',
# routing_key='error',
routing_key='info',
body='ysg info')
connection.close()
2.2 订阅者
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='y1', queue=queue_name, routing_key='error')
import pika
credentials = pika.PlainCredentials("admin", "admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('47.100.163.117', credentials=credentials))
channel = connection.channel()
# exchange='m1', exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
# 声明 exchange='m1' 交换机
channel.exchange_declare(exchange='y1', exchange_type='direct')
# 随机生成一个队列
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
print('随机生成的queue的名字', queue_name)
# 队列 queue 绑定交换机
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='y1', queue=queue_name, routing_key='error')
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()
3.topic 模式
3.1 发布者
### 声明exchange
channel.exchange_declare(exchange='m1',exchange_type='topic')
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('10.0.0.101',credentials=credentials))
channel = connection.channel()
### 声明exchange
channel.exchange_declare(exchange='m1',exchange_type='topic')
channel.basic_publish(exchange='m1',
routing_key='lqz.handsome.ssda',
body='aaa')
connection.close()
3.2 订阅者
# 1 声明 exchange='m1' 交换机
channel.exchange_declare(exchange='m1',exchange_type='topic')
# 2 队列 queue 绑定交换机
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='m1',queue=queue_name,routing_key='lqz.#') #代表一个或多个部分
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('10.0.0.101',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
# 1 声明 exchange='m1' 交换机
channel.exchange_declare(exchange='m1',exchange_type='topic')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print('随机生成的queue的名字',queue_name)
# 2 队列 queue 绑定交换机
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='m1',queue=queue_name,routing_key='lqz.#') #代表一个或多个部分
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
# 1 声明 exchange='m1' 交换机
channel.exchange_declare(exchange='m1', exchange_type='topic')
# 2 队列 queue 绑定交换机
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='m1',queue=queue_name,routing_key='lqz.*') # 代表一个部分
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('10.0.0.101',credentials=credentials))
channel = connection.channel()
# exchange='m1',exchange(秘书)的名称
# exchange_type='fanout' , 秘书工作方式将消息发送给所有的队列
# 1 声明 exchange='m1' 交换机
channel.exchange_declare(exchange='m1', exchange_type='topic')
# 随机生成一个队列
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue
print('随机生成的queue的名字',queue_name)
# 2 队列 queue 绑定交换机
# 让 exchange 和 queque 进行绑定.
channel.queue_bind(exchange='m1', queue=queue_name, routing_key='lqz.*') # 代表一个部分
def callback(ch, method, properties, body):
print("消费者接受到了任务: %r" % body)
channel.basic_consume(queue=queue_name,on_message_callback=callback,auto_ack=True)
channel.start_consuming()
三:RPC
1.rpc 介绍
# RPC(Remote Procedure Call) 是指远程过程调用,也就是说两台服务器 A,B 一个应用部署在 A 服务器上,想要调用 B 服务器上应用提供的函数或方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据
# 为什么要用 RPC
就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如比如不同的系统间的通讯,甚至不同的组织间的通讯。由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用
# 常见 RPC 框架
-dubbo java
-gRpc 跨语言
# Spring Cloud : Spring 全家桶,用起来很舒服,只有你想不到,没有它做不到。可惜因为发布的比较晚,国内还没出现比较成功的案例,大部分都是试水,不过毕竟有 Spring 作背书,还是比较看好。
# Dubbox: 相对于 Dubbo 支持了REST,估计是很多公司选择 Dubbox 的一个重要原因之一,但如果使用 Dubbo 的RPC调用方式,服务间仍然会存在API强依赖,各有利弊,懂的取舍吧。
# Thrift:如果你比较高冷,完全可以基于 Thrift 自己搞一套抽象的自定义框架吧。
# Montan:可能因为出来的比较晚,目前除了新浪微博 16 年初发布的,
# Hessian:如果是初创公司或系统数量还没有超过 5 个,推荐选择这个,毕竟在开发速度、运维成本、上手难度等都是比较轻量、简单的,即使在以后迁移至SOA,也是无缝迁移。
# rpcx/gRPC: 在服务没有出现严重性能的问题下,或技术栈没有变更的情况下,可能一直不会引入,即使引入也只是小部分模块优化使用。
# protohuf grpc 自己定制的远程过程调用的通信格式
2.python 实现 rpc
# SimpleXMLRPCServer 自带的
# ZeroRPC
2.1 内置的
# 服务端
from xmlrpc.server import SimpleXMLRPCServer
# 通信使用xml格式
class RPCServer(object):
def __init__(self):
super(RPCServer, self).__init__()
print(self)
self.send_data = 'lqz nb'
self.recv_data = None
def getObj(self):
print('get data')
return self.send_data
def sendObj(self, data):
print('send data')
self.recv_data = data
print(self.recv_data)
return '收到了'+data
# SimpleXMLRPCServer
server = SimpleXMLRPCServer(('localhost', 4242), allow_none=True)
server.register_introspection_functions()
server.register_instance(RPCServer())
server.serve_forever()
# 客户端
import time
from xmlrpc.client import ServerProxy
# SimpleXMLRPCServer
def xmlrpc_client():
print('xmlrpc client')
c = ServerProxy('http://localhost:4242')
# res=c.getObj()
# print(res)
# res=c.sendObj('xxx')
# print(res)
data = 'lqz nb'
start = time.time()
for i in range(500):
a=c.getObj()
print(a)
for i in range(500):
c.sendObj(data)
print('xmlrpc total time %s' % (time.time() - start))
if __name__ == '__main__':
xmlrpc_client()
2.2 zeroRpc
# 服务端
import zerorpc
class RPCServer(object):
def __init__(self):
super(RPCServer, self).__init__()
print(self)
self.send_data = 'lqz nb'
self.recv_data = None
def getObj(self):
print('get data')
return self.send_data
def sendObj(self, data):
print('send data')
self.recv_data = data
print(self.recv_data)
# zerorpc
s = zerorpc.Server(RPCServer())
s.bind('tcp://0.0.0.0:4243')
s.run()
# 客户端
import zerorpc
import time
# zerorpc
def zerorpc_client():
print('zerorpc client')
c = zerorpc.Client()
c.connect('tcp://127.0.0.1:4243')
data = 'lqz nb'
start = time.time()
for i in range(500):
a=c.getObj()
print(a)
for i in range(500):
c.sendObj(data)
print('total time %s' % (time.time() - start))
if __name__ == '__main__':
zerorpc_client()
3.rabbitmq 实现 rpc
# 跨语言
![]()
# 服务端
import pika
credentials = pika.PlainCredentials("admin","admin")
connection = pika.BlockingConnection(pika.ConnectionParameters('10.0.0.101',credentials=credentials))
channel = connection.channel()
# 声明一个队列rpc_queue
channel.queue_declare(queue='rpc_queue')
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
def on_request(ch, method, props, body):
n = int(body)
print(" [.] fib(%s)" % n)
response = fib(n)
ch.basic_publish(exchange='',
routing_key=props.reply_to,
properties=pika.BasicProperties(correlation_id = \
props.correlation_id),
body=str(response))
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='rpc_queue', on_message_callback=on_request)
print(" [x] Awaiting RPC requests")
channel.start_consuming()
# 客户端
import pika
import uuid
class FibonacciRpcClient(object):
def __init__(self):
credentials = pika.PlainCredentials("admin", "admin")
self.connection = pika.BlockingConnection(pika.ConnectionParameters('10.0.0.101', credentials=credentials))
self.channel = self.connection.channel()
# 随机生成一个消息队列(用于接收结果)
result = self.channel.queue_declare(queue='', exclusive=True)
self.callback_queue = result.method.queue
# 监听消息队列中是否有值返回,如果有值则执行 on_response 函数(一旦有结果,则执行on_response)
self.channel.basic_consume(queue=self.callback_queue, on_message_callback=self.on_response, auto_ack=True)
def on_response(self, ch, method, props, body):
if self.corr_id == props.correlation_id:
self.response = body
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())
# 客户端 给 服务端 发送一个任务: 任务id = corr_id / 任务内容 = '30' / 用于接收结果的队列名称
self.channel.basic_publish(exchange='',
routing_key='rpc_queue', # 服务端接收任务的队列名称
properties=pika.BasicProperties(
reply_to=self.callback_queue, # 用于接收结果的队列
correlation_id=self.corr_id, # 任务ID
),
body=str(n))
while self.response is None:
self.connection.process_data_events()
return self.response
fibonacci_rpc = FibonacciRpcClient()
response = fibonacci_rpc.call(9)
print('返回结果:', response)