epoll和gevent的区别:
解:在linux底层都是调用libevent.so模块文件实现的,也就是说在底层都是一样的,协程gevent也是I/O多路复用,epoll关注点更多的是I/O多路复用本身;不同函数,方法之间的切换,协程则是I/O多路复用是默认设置,关注点更多的是任务之间的切换,上层封装了函数以及方法之间的切换。
1.消息队列rabbitmq
注:其实作用和Python的queue一样,就是生产者消费者模型
thread queue,多线程数据交互以及同步
进程queue,父进程以及子进程的数据交互,或者同属于一个父进程下多个子进程进行交互
last:也就是说,即便是两个独立的Python程序,也依然是不能通过进程queue来通信的,那么问题来了,现在就是有两个独立的Python程序,或者两个不同语言的程序,如何通信?只能通过一个中间的代理,就是rabbitmq消息队列,其实如果要想实现不同程序(独立进程)之间的通信,直接通过建立socket在速度还更加有优势,但是劣势是需要定义不同进程传达消息的消息类型和特点,每种进程都需要解决黏包等等问题,什么样的指令代表什么如何处理都需要自己重新编写和定义,如果通过中间商,这个中间商已经处理好了,中间商的socket已经是特殊定制过的,只要连接上中间商就可以直接不同进程传达消息通信了,这样显得更加便捷无须维护复杂的链接关系
1.producer.py
#!/usr/bin/env python3
# this is Charlie scripts!
# rabbitmq端口默认:5672
import pika
# 先建立一个pika对象(相当于建立一个简单的socket链接对象)
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
# 声明一个管道
channel = connection.channel()
# 声明queue,相当于队列中给队列起名字,hello_queue一样
channel.queue_declare(queue='hello')
# 通过管道发送消息,exchange先不管它,routing key就是queue名字,body就是发送的消息
channel.basic_publish(exchange='',routing_key='hello',body='Hello,World!')
print(" [x] Sent 'Hello World!'")
# 关闭队列
connection.close()
############################################################
[x] Sent 'Hello World!'
2.consumer.py
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
# 消费者可能在其他机器上
# 建立一个pika对象(相当于建立一个简单的socket链接对象)
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
# 声明一个管道
channel = connection.channel()
# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
# 声明队列
channel.queue_declare(queue='hello')
# 注:为什么在消费者端也要声明队列?因为可以避免如果这个queue在生产端还没有被定义,但是在消费者先运行的、
# 情况下,那么消费者就会报错,所以为了避免这个问题,消费者也声明队列
def callback(ch, method, properties, body):
print(ch,method,properties)
print(" [x] Received %r" % body)
# 开始消费消息了,callback的作用是如果收到消息就调用这个函数处理消息,
channel.basic_consume(callback,queue='hello',no_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
# 这里开始才是真正收消息,而且永远收消息,没有消息就卡住
channel.start_consuming()
#############################################################
[*] Waiting for messages. To exit press CTRL+C
<pika.adapters.blocking_connection.BlockingChannel object at 0x7f06027ec828> <Basic.Deliver(['consumer_tag=ctag1.270eed97e23641f896b30314e54c5a7d', 'delivery_tag=1', 'exchange=', 'redelivered=False', 'routing_key=hello'])> <BasicProperties>
注:ch保存的是刚才声明的管道的内存对象地址,method…不用管,第三个也不用管…,no_ack的作用是不管消费者处理完了生产者发送过来的消息还是没有处理完(突然宕机断电)消息都不给生产者主动发送确认包,默认不要加这项,确保如果当某一生产者生产消息发送给某一消费者时,这消费者突然断电了或者没有完成消费就结束进程那么这是socket断开,消费者会把这一消息转移给其他正在运行的消费者,生产者没有收到刚刚消费者的确认包,就不会删除刚刚生产的消息,继续发送以及等待其他的消费者发送过来的消费确认包…
3.服务端持久化队列以及消息永久保存server:
#!/usr/bin/env python3
# this is Charlie scripts!
# rabbitmq端口默认:5672
import pika
# 先建立一个pika对象(相当于建立一个简单的socket链接对象)
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
# 声明一个管道
channel = connection.channel()
# 声明queue,相当于队列中给队列起名字,hello_queue一样,durable的作用是即使服务端rabbitmq服务出问题了,\
# 队列也会持久化保存,但消息不会
channel.queue_declare(queue='hello7',durable=True)
# 通过管道发送消息,exchange先不管它,routing key就是queue名字,body就是发送的消息
channel.basic_publish(exchange='',routing_key='hello7',body='Hello,World!',properties=pika.BasicProperties(delivery_mode=2,))
print(" [x] Sent 'Hello World!'")
# 关闭队列
connection.close()
4.服务端持久化队列以及消息永久保存client:
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
import time
# 消费者可能在其他机器上
# 建立一个pika对象(相当于建立一个简单的socket链接对象)
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
# 声明一个管道
channel = connection.channel()
# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
# 声明队列,durable的作用是即使服务端rabbitmq服务出问题了,队列也会持久化保存,但消息不会
channel.queue_declare(queue='hello7',durable=True)
# 注:为什么在消费者端也要声明队列?因为可以避免如果这个queue在生产端还没有被定义,但是在消费者先运行的、
# 情况下,那么消费者就会报错,所以为了避免这个问题,消费者也声明队列
def callback(ch, method, properties, body):
print(ch,method,properties)
# time.sleep(30)
print(" [x] Received %r" % body)
# 这条代码的意思是发送确认包给生产者
ch.basic_ack(delivery_tag=method.delivery_tag)
# 开始消费消息了,callback的作用是如果收到消息就调用这个函数处理消息,
channel.basic_consume(callback,queue='hello7')
print(' [*] Waiting for messages. To exit press CTRL+C')
# 这里开始才是真正收消息,而且永远收消息,没有消息就卡住
channel.start_consuming()
注:上文演示了如果客户端(消费者)断开链接所出现的问题,那么如果生产者自己本身就发生了类似宕机事件怎么办?这时需要针对消息队列(queue)以及消息message进行永久保存,等到服务端rabbitmq恢复正常之后消费者可以正常的消费消息
5.以广播的方式发送消息server:
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs',type='fanout')
# message = ' '.join(sys.argv[1:]) or "info: Hello World!"
message = "info: Hello World!"
channel.basic_publish(exchange='logs',
routing_key='',
body=message)
print(" [x] Sent %r" % message)
connection.close()
##############################################################
忽略执行结果...
6.以广播的方式发送消息client:
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs',type='fanout')
# exclusive 的意思是排他,唯一的意思
result = channel.queue_declare(exclusive=True) # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
queue_name = result.method.queue
# 广播方式的rabbitmq的方式发送端是没有定义队列的,所以接收端需要绑定转发器
channel.queue_bind(exchange='logs',queue=queue_name)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)
channel.start_consuming()
###########################################################
忽略执行结果...
注:多开几个客户端链接,在服务端多发送几次消息会发现多个客户端会同时接收到生产者发送的消息,但是如果某一台客户端断开了那么消息就发送失败,重新连接也不会重新接收上次接收失败的消息,就和广播电台是一个道理
7.以广播并且可选择消息类型的方式发送消息server:
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',type='direct')
severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='direct_logs',routing_key=severity,body=message)
print(" [x] Sent %r:%r" % (severity, message))
connection.close()
8.以广播并且可选择消息类型的方式发送消息client:
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',type='direct')
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
severities = sys.argv[1:]
if not severities:
sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
sys.exit(1)
print(severities)
for severity in severities:
channel.queue_bind(exchange='direct_logs',queue=queue_name,routing_key=severity)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r:%r" % (method.routing_key, body))
channel.basic_consume(callback,queue=queue_name,no_ack=True)
channel.start_consuming()
注:可以指定接收消息的类型
9.更为细致的消息筛选server:
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='topic_logs',type='topic')
routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='topic_logs',routing_key=routing_key,body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()
10.更为细致的消息筛选client:
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='topic_logs',type='topic')
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
binding_keys = sys.argv[1:]
if not binding_keys:
sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
sys.exit(1)
for binding_key in binding_keys:
channel.queue_bind(exchange='topic_logs',queue=queue_name,routing_key=binding_key)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r:%r" % (method.routing_key, body))
channel.basic_consume(callback,queue=queue_name,no_ack=True)
channel.start_consuming()
注:运行的结果是可以通过mysql.error根据消息等级对应的应用从而选择接收消息,和上文的区别基本上除了direct级别不同剩下基本一样
注:这里设计到一个问题,至今的消息分发模式都是单向的,如何做到服务端发送消息给客户端,客户端处理之后再返回给服务端,这就是典型的RPC应用(类似linux中的SNMP服务),如何利用rabbitmq实现RPC应用呢?
11.rpc-client
#!/usr/bin/env python3
# this is Charlie scripts!
import pika
import uuid
# 远程代码发送者,也就是发送命令到远程机器执行的客户端,等待远程机器执行完毕后接收返回结果
class FibonacciRpcClient(object):
def __init__(self):
# 连接远程对象
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
# 声明管道
self.channel = self.connection.channel()
# 生成随机queue,下文用于指定服务器端执行完消息要返回的队列
result = self.channel.queue_declare(exclusive=True)
self.callback_queue = result.method.queue
# 准备开始消费消息,on_response的作用是如果收到消息就调用这个函数处理消息
self.channel.basic_consume(self.on_response,
no_ack=True,
queue=self.callback_queue)
# 相当于callback,想干什么干什么
# 四个参数写死的,必须这么写,没有为什么
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())
# 发送消息到rpc_queue,body是内容,BasicProperties是持久化序列消息的作用
self.channel.basic_publish(exchange='',
routing_key='rpc_queue',#reply_to的意思是对端机器执行完操作后返回的消息到callback_queue
properties=pika.BasicProperties(reply_to=self.callback_queue,
correlation_id=self.corr_id,)
,body=str(n))
# 上文basic_publish已经发送消息,按道理要开始接收消息,start_consuming虽然开始收消息,\
# 但是会进入阻塞模式,如果想不阻塞,每过一段时间再回来检测有没有消息,\
# 就要用下文的connection.process_data_events,可以理解为非阻塞版start_consuming, \
# 如果没有消息或者有消息都会返回
while self.response is None:
# 如果收到消息,就会触发basic_consume里的on_response函数,、
# 然后response会被body赋值,那么就会跳出这个循环了
self.connection.process_data_events()
print("no msg...")
return int(self.response)
fibonacci_rpc = FibonacciRpcClient()
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)
print(" [.] Got %r" % response)
12.rpc-server
#!/usr/bin/env python3
# this is Charlie scripts!
# 远程代码执行者,也就是接收客户端发送的命令,经过处理后返回给客户端
import pika
import time
# 建立连接对象
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
# 声明管道
channel = connection.channel()
# 定义队列名称
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)
# 要想收到客户端的BasicProperties中的reply_to消息,必须带入参数props
def on_request(ch, method, props, body):
# 收到队列rpc_queue中的消息body
n = int(body)
print(" [.] fib(%s)" % n)
# 计算出命令结果
response = fib(n)
# 返回结果,写入到一个新queue,但是这个queue要重新定义,要约定好,也就是reply_to
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_consume(on_request, queue='rpc_queue')
print(" [x] Awaiting RPC requests")
# 发起收消息操作
channel.start_consuming()
注:这里解释一下uuid的作用,为什么要传送消息到队列的时候要客户端要将自己随机生成的UUID发送给服务端,然后服务端原封不动的发送回来客户端还要验证是不是自己发送的呢?因为如果在while消息没收到的时候客户端其实也可以继续发送执行命令给服务端操作,这时等待接收的消息就有两条,并不能确定两条消息哪条先返回,为了确保客户端发送出去的指令返回时不要调错顺序,所以需要需要加入UUID验证
问题:QQ和微信两个独立的程序如果想进行数据共享,如何实现?(不通过硬盘,但是硬盘速度慢效率低)
答:通过队列呢?也可以,但是这种是属于消息的传递,但是数据是多分的,要求是数据只有一份,直接达到共享的效果,答案是缓存。缓存的数据是放在内存里的,问题就是两个程序如何实现内存共享呢?QQ是不可以访问微信的内存的,这时通过一个broker(中间商),broker自己开启一片内存存放数据,但是QQ和微信也是不可以访问broker的内存的,他俩的通信其实是通过socket与中间商进行通信的
13.redis缓存系统
注:redis是单线程,通过epoll的方式进行高并发的
127.0.0.1:6379> set charlie handsome
OK
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> keys *
1) "age"
2) "charlie"
127.0.0.1:6379>
14.修改redis.conf配置文件
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST COMMENT THE FOLLOWING LINE.
bind 0.0.0.0
注:记得重启 redis.service服务
#!/usr/bin/env python3
# this is Charlie scripts!
import redis
r = redis.Redis(host='10.0.0.111', port=6379)
r.set('foo', 'Bar')
print(r.get('foo'))
[root@linux ~]# python3 redis_conn.py
b'Bar'
[root@linux ~]#
注:最简单的连接redis的连接方法