一、RabbitMQ队列

1.1 介绍

      rabbitMQ是消息队列;

      AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
  AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
  RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

     Threading QUEU(线程QUEU)、进程QUEU两种;这两种都是python自带的QUEU,这两个queu只能在同一个进程下的所有子进程或父进程与子进程之间的通讯所以就有了第三方的消息队列,主流的消息队列有很多,如:rabbitmq,RocketMQ等。

     rabbitMQ的优点:

     (1).省去维护网络通讯的繁琐事情

     (2). 可以在多个应用之间起到消息通讯

1.2  代码演示

消息生产端




python 消息队列 网络 python轻量级消息队列_python 消息队列 网络

python 消息队列 网络 python轻量级消息队列_数据库_02

# -*- coding:utf-8 -*-
import pika
#在建立连接的时候,我们可以指定N多参数
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
#声明queue
channel.queue_declare(queue='hello')
#发送一条信息到rabbitmq服务器,消息队列为hello,消息内容为hello world!
channel.basic_publish(exchange='',routing_key='hello',body='Hello World!')
#发送完成后在本地打印一条信息
print("[x] Sent 'Hello World!'")
#关闭这个连接
connection.close()


View Code


消息消费端




python 消息队列 网络 python轻量级消息队列_python 消息队列 网络

python 消息队列 网络 python轻量级消息队列_数据库_02

# -*- coding:utf-8 -*-
import  pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
#声明
channel.queue_declare(queue='hello')
def callback(ch,method,properties,body):
    print("[x] Recevied %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(callback,queue='hello',no_ack=True)
print('[*] Waiting for messages.To exit press CTRL+C')
channel.start_consuming()


View Code


结果显示:
消息生产端:[x] Sent 'Hello World!'

消息消费端:

[*] Waiting for messages.To exit press CTRL+C
[x] Recevied b'Hello World!'

生产者运行完毕后,一次性生效,如果还要发消息,需要再次运行;消费者运行后,永久接收消息,如想终止消息接收,需要按Ctrl+C终止。

 

MQ如何才能确保消费者真正成功的消费一条信息

上面的代码,如果有多个消费者,对应一个mq,那么某个消费被消费者消费成功后是否成功,mq并不知道,只要你消费了,成功与否不管,那么就会在mq中去掉这条消息,那么问题来了,假如消费者并没真正的处理完消息,就宕机了,那怎么办呢?只需要改消费者。

测试过程:可以先后启动多个消费者,然后消费的过程sleep30秒,假如mq中国有一个消息,启动第一个消费者,然后关掉,再启动第二个消费者,看是否能收到消息。




python 消息队列 网络 python轻量级消息队列_python 消息队列 网络

python 消息队列 网络 python轻量级消息队列_数据库_02

# -*- coding:utf-8 -*-
import pika,time
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch,method,properties,body):
    print("[x] Received %r" % body)
    time.sleep(30)
    print('处理完毕')
    ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(callback,queue='hello',
                      #不给mq发送处理完成的确认请求,那么注释掉,默认消费者如果消费失败,那么就不会给mq发送确认信息,自然mq消息还在等待下一个消费者继续消费
                      #no_ack=True
                      )
print('[*]Waiting for messages.To exit press CTRL+C')
channel.start_consuming()


View Code


队列持久化

上面的代码能保证消息成功消费了;假如mq宕机了怎么办?这样mq消息就没了,解决办法:




python 消息队列 网络 python轻量级消息队列_python 消息队列 网络

python 消息队列 网络 python轻量级消息队列_数据库_02

# -*- coding:utf-8 -*-
import pika,time
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
#声明queue,在这里只需要加一个durable=True就可以了
channel.queue_declare(queue='hello2',durable=True)
#发送一条消息到rabbitmq服务器,消息队列为hello,消息内容为hello world!
channel.basic_publish(exchange='',routing_key='hello2',body='Hello World!')
#发送完成后在本地打印一条信息
print("[x] Sent 'Hello World!'")
#关闭连接
connection.close()


View Code


消息持久化




python 消息队列 网络 python轻量级消息队列_python 消息队列 网络

python 消息队列 网络 python轻量级消息队列_数据库_02

# -*- coding:utf-8 -*-
import pika,time
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
#声明queue
channel.queue_declare(queue='hello2',
                      #持久化队列
                      durable=True)
channel.basic_publish(exchange='',routing_key='hello2',body='Hello World!',
                      #持久化消息
                      properties = pika.BasicProperties(
                      delivery_mode=2,    
                      ))
#发送完成后在本地打印一条信息
print("[x] Sent 'Hello World!'")
#关闭这个连接
connection.close()


View Code


消息公平分发:

如果Rabbit只管按顺序把消息发到各个消费者身上,不考虑消费负载的话,很可能出现,一个机器配置不高的消费者那里堆积了很多消费处理不完,同时配置高的消费者却一直很轻松。为解决此问题,可以在各个消费者端,配置perfetch=1,意思就是告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了。

Publish\Subscribe(消息发布\订阅)

之前的例子基本都是1对1的消息发送和接收,即消息只能发送到指定的queue里,但有些时候你想让你的所有消息被所有的Queue收到,类似广播的效果,这时候就要用到exchange了。

Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息。

fanout:所有bind到此exchange的queue都可以接收消息。

direct:通过routingKey和exchange决定的那个唯一的queue可以接收消息。

topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息。

表达式符号说明:#代表一个或多个字符,*代表任何字符。

headers:通过headers来决定把消息发给哪些queue。

二、缓存

mongodb 直接持久化,现在用的比较少

redis  半持久化,需要手动配置,才会持久化

memcache 轻量级缓存,不会持久化

redis ------->异步缓存,读写速度惊人!

介绍:

查看redis当下有什么key:

> keys *

设置一个key name 存活时间2秒钟

>set name jack ex 2

使用python调用本机redis

方法一:



# -*- coding:utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.1.220',port=6379)
r = redis.Redis(connection_pool=pool)
r.set('name','sean')
print(r.get('name'))



方法二:

由于后期写代码使用redis会频繁建立socket连接,我们可以建立一个连接池,所有的连接都从这个池子中去建立好的连接



# -*- coding:utf-8 -*-
import  redis
pool = redis.ConnectionPool(host='192.168.1.220',port=6379)
r = redis.Redis(connection_pool=pool)
r.set('name','sean')
print(r.get('name'))



redis不简单支持一个key-value的操作,还支持下面的操作:
String操作,Hash操作,List操作,Set操作,Sort Set操作

先看下string操作:

set(name,value,ex=None,px=None,nx=False,xx=False)

在Redis中设置值,默认,不存在则创建,存在则修改

参数:

      ex,过期时间(秒)

      px,过期时间(毫秒)

      nx,如果设置为True,则只有name不存在时,当前set操作才执行

      xx,如果设置为True,则只有name存在时,当前set操作才执行

setnx(name,value)

设置值,只有name不存在时,执行设置操作(添加)

setex(name,value,time)

设置值,参数:#time,过期时间(数字秒或timedelta对象)

psetex(name,time_ms,value)

设置值,参数:#time_ms,过期时间(数字毫秒或timedelta对象)

mset(*args,**kwargs)

批量设置值

如:mset(k1='v1',k2='v2')或mget({'k1':'v1','k2','v2'})

获取值:get(name)

批量获取:mget(keys,*args)

设置新值并获取原来的值:getset(name,value)

获取子序列(根据字节获取,非字符)

参数:

    #name, Redis 的 name

    # start,起始位置(字节)

    #end,结束位置(字节)

#如:‘帅锅来嘛’,0-3 表示"武":getrange(key,start,end)

修改字符串内容,从指定字符串引开始向后替换(新值太长时,则向后添加)

参数:

      # offset,字符串的索引,字节(一个汉字三个字节)

      # value,要设置的值

使用二进制的方法对key进行操作,下面是一个例子:

>>> ord('s') #查看s对应的assci的编码号
115
>>> ord('S') #查看S对应的assci的编码号
83
>>> bin(115) #查看115转成二进制是多少
'0b1110011'
>>> bin(83) #查看83转成二进制是多少
'0b1010011'

#想办法把115变成83,那就是把115的第2位,由1-》0,这个是从左边数

127.0.0.1:6379> set name sean  #设置一个变量
OK
127.0.0.1:6379> setbit name 2 0 #将第2位的数变为0
(integer) 1
127.0.0.1:6379> get name #再次查看值
"Sean"

再来一个例子:

现在我们想要把第四位的n变为大写的N:

>>> ord('n')
110
>>> ord('N')
78
>>> bin(110)
'0b1101110'
>>> bin(78)
'0b1001110'

#这里需要注意的是,我们要更改的是name的第四个字符,这个字符把n->N前面有三位占据了3*8=24位,但是计算机是从0开始的,也就是23位,更改n就要从24位开始数,第24位是0,25不变,26从1变成0.

127.0.0.1:6379> setbit name 26 0
(integer) 1
127.0.0.1:6379> get name
"SeaN"

获取name对应的值得二进制表示中1的个数

参数:

      # key,Redis的name

      # start 位起始位置

      # end 位结束位置 :bitcount(key,start=None,end=None)

>>> ord('b')
98
>>> bin(98)
'0b1100010'

127.0.0.1:6379> set n1 b
OK
127.0.0.1:6379> bitcount n1
(integer) 3

使用这个bitcount和setbit可以实现一个很牛逼的功能。假如要统计一个微博现在在线的人数,我们可以使用这个方法:

使用setbit每登录一个账户并把这个账户的id对应的位数设置成1,最后使用bitcount来统计这个变量中一共有多少个1,就有多少个在线人数。

127.0.0.1:6379> setbit n5  12 1
(integer) 0
127.0.0.1:6379> setbit n5  22 1
(integer) 0
127.0.0.1:6379> setbit n5  33 1
(integer) 0
127.0.0.1:6379> bitcount n5
(integer) 3

登录了三个账户,最后统计一下n5有多少个1,就说明有多少个在线用户数。

自增name对应的值,当name不存在时,则创建name=amount,否则自增。

参数:

      # name,Redis的name

      # amount,自增数(必须是整数)

      incr(self,name,amount=1)

自减name对应的值,当name不存在时,则创建name=amount,否则则自减。

参数:

      # name,Redis的name

      # amount 自减数(整数)

      decr(self,name,amount=1)

strlen(name)#返回name对应值的字节长度(一个汉字3个字节)

incrbyfloat(self,name,amount=1.0) #指定小数的增加

append(key,value) #在redis name对应的值后面追加内容,#参数key, redis的ame,value要追加的字符串



127.0.0.1:6379> set name ak47
OK
127.0.0.1:6379> append name xin
(integer) 7
127.0.0.1:6379> get name
"ak47xin"



Hash操作,redis中Hash在内存中的存储格式如下图:

       hash就是通过一些算法操作,把一个字符串生成一个唯一的数字串。

 

python 消息队列 网络 python轻量级消息队列_数据库_11

hset(name,key,value)

#name对应的hash中设置一个键值对(不存在则创建;否则修改)

#参数

       #name,redis 的name

       #key,name对应的hash中的key

        #value,name对应的hash的calue

#注:

      #hsetnx(name,key,value)当name对应的hash中不存在当前key时则创建(相当于添加)

127.0.0.1:6379> hset info name sean
(integer) 0
127.0.0.1:6379> hset info age 22
(integer) 0
127.0.0.1:6379> hset info id 999 
(integer) 0
127.0.0.1:6379> hget info id #获取单个
"999"
127.0.0.1:6379> hgetall info  #获取所有
1) "name"
2) "sean"
3) "age"
4) "22"
5) "id"
6) "999"

hkeys(name) #获取name对应的hash中所有的value的值

hvals(name) #获取name对应的hash中所有的value的值

hmset(name,mapping) #在name对应的hash中批量设置键值对

#参数:

        # name, redis的name

        # mapping,字典,如:{‘k1’:'v1','k2':'v2'}



127.0.0.1:6379> hmset info2 n1 sjua
OK
127.0.0.1:6379> hmset info2 n1 shuaiguo n2 meinv
OK
127.0.0.1:6379> hkeys info2
1) "n1"
2) "n2"
127.0.0.1:6379> hvals info2
1) "shuaiguo"
2) "meinv"



hmget(name,keys,*args)#在name对应的hash中获取多key的值

#参数:

        #name, redis对应的name

        # keys, 要获取key集合,如[‘k1’,'k2','k3']

        # *args,要获取的key,如:k1,k2,k3

#如: r.mget('xx',['k1','k2']) 或者 print r.hmget('xx','k1','k2')



127.0.0.1:6379> hmget info2 n1 n2
1) "shuaiguo"
2) "meinv"



hlen(name)#获取name对应的hash中键值对的个数

hexists(name,key) #检查name对应的hash是否存在当前传入的key有就返回1



127.0.0.1:6379> hexists info2 n1
(integer) 1
127.0.0.1:6379> hexists info2 n3
(integer) 0



hdel(name,*keys)#将name对应的hash中的指定key的值,不存在则创建key=amount

#参数:

        #name, redis中的name

        #key, hash对应的key

        # amount, 自增数(整数)



127.0.0.1:6379> HGET info2 n1
"shuaiguo"
# 如果这个key存在且对应的value不是数字,那么抛出异常
127.0.0.1:6379> HINCRBY info2 n1 1
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBY info2 n3 1
(integer) 1
127.0.0.1:6379> HINCRBY info2 n3 1
(integer) 2
127.0.0.1:6379> HINCRBY info2 n3 1
(integer) 3
127.0.0.1:6379> HINCRBY info2 n3 1
(integer) 4



hincrbyfloat(name,key,amount=1.0)

#自增name对应的hash中的指定key的值,不存在则创建key=amount

#参数:

        # name, redis中的name

        # key,hash 对应的key

        # amount,自增数(浮点数)

#自增name对应的hash中的指定key的值,不存在则创建key=amount

hscan(name,cursor=0,match=None,count=None)



# 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆
 
# 参数:
    # name,redis的name
    # cursor,游标(基于游标分批取获取数据)
    # match,匹配指定key,默认None 表示所有的key
    # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
 
# 如:
    # 第一次:cursor1, data1 = r.hscan('xx', cursor=0, match=None, count=None)
    # 第二次:cursor2, data1 = r.hscan('xx', cursor=cursor1, match=None, count=None)
    # ...
    # 直到返回值cursor的值为0时,表示数据已经通过分片获取完毕



一个info可以存放200亿个key,如果使用hkeys,查找出来消耗资源居多,可以使用scan进行过滤查找了。



127.0.0.1:6379> hkeys info2
1) "n1"
2) "n2"
3) "n3"
127.0.0.1:6379> hscan info2 0 match key*
1) "0"
2) (empty list or set)
127.0.0.1:6379> hscan info2 0 match n*
1) "0"
2) 1) "n1"
   2) "shuaiguo"
   3) "n2"
   4) "meinv"
   5) "n3"
   6) "1"



hscan_iter(name,match=None,count=None)



# 利用yield封装hscan创建生成器,实现分批去redis中获取数据
 
# 参数:
    # match,匹配指定key,默认None 表示所有的key
    # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
 
# 如:
    # for item in r.hscan_iter('xx'):
    #     print item



加入200亿个key,扫描出来10亿个数据,还嫌多的话,就可以使用hscan_iter进行操作,生成结果是一个迭代器,在对这个结果进行循环。

 List操作,redis中的List在内存中按照一个name对应一个List来存储。如图:

python 消息队列 网络 python轻量级消息队列_python_12

lpush(name,values)先进后出


# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边 # 如: # r.lpush('oo', 11,22,33) # 保存顺序为: 33,22,11 # 扩展: # rpush(name, values) 表示从右向左操作

127.0.0.1:6379> LPUSH names sean hanyang xiaoxin jack
(integer) 4