python学习笔记-(十五)RabbitMQ队列

rabbitMQ是消息队列;想想之前的我们学过队列queue:threading queue(线程queue,多个线程之间进行数据交互)、进程queue(父进程与子进程进行交互或者同属于同一父进程下的多个子进程进行交互);如果两个独立的程序,那么之间是不能通过queue进行交互的,这时候我们就需要一个中间代理即rabbitMQ

消息队列:

  • RabbitMQ
  • ZeroMQ
  • ActiveMQ
  • ...........

一. 安装

1. ubuntu下安装rabbitMQ:

1.1 安装: sudo apt-get install rabbitmq-server

1.2 启动rabbitmq web服务:

sudo invoke-rc.d rabbitmq-server stop

sudo invoke-rc.d rabbitmq-server start

启动web管理:sudo rabbitmq-plugins enable rabbitmq_management

1.3 远程访问rabbitmq,自己增加一个用户,步骤如下:

  • 创建一个admin用户:sudo rabbitmqctl add_user admin 123123
  • 设置该用户为administrator角色:sudo rabbitmqctl set_user_tags admin administrator
  • 设置权限:sudo  rabbitmqctl  set_permissions  -p  ‘/‘  admin ‘.‘ ‘.‘ ‘.‘
  • 重启rabbitmq服务:sudo service rabbitmq-server restart

之后就能用admin用户远程连接rabbitmq server了。

2. 安装python rabbitMQ modul:

  • 管理员打开cmd,切换到python的安装路径,进入到Scripts目录下(如:C:\Users\Administrator\AppData\Local\Programs\Python\Python35\Scripts);
  • 执行以下命令:pip install pika
  • 校验是否安装成功:进入到python命令行模式,输入import pika,无报错代表成功;

二. 代码实现

1. 实现最简单的队列通信

发送端:

import pika
credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials))
channel = connection.channel()
#声明queue
channel.queue_declare(queue=‘hello‘)
#n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange=‘‘,
                      routing_key=‘hello‘,
                      body=‘Hello World!‘)
print(" [x] Sent ‘Hello World!‘")
connection.close() 

接收端:

import pika
credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials))
channel = connection.channel()
channel.queue_declare(queue=‘hello‘)
def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
channel.basic_consume(callback,
                      queue=‘hello‘,
                      no_ack=True)
print(‘ [*] Waiting for messages. To exit press CTRL+C‘)
channel.start_consuming()

2. RabbitMQ消息分发轮询

先启动消息生产者,然后再分别启动3个消费者,通过生产者多发送几条消息,你会发现,这几条消息会被依次分配到各个消费者身上

在这种模式下,RabbitMQ会默认把p发的消息公平的依次分发给各个消费者(c),跟负载均衡差不多

import pika

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()      #声明一个管道(管道内发消息)

channel.queue_declare(queue=‘cc‘)    #声明queue队列

channel.basic_publish(exchange=‘‘,
                      routing_key=‘cc‘,  #routing_key 就是queue名
                      body=‘Hello World!‘
)
print("Sent ‘Hello,World!‘")
connection.close()      #关闭

publish.py

import pika

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()

channel.queue_declare(queue=‘cc‘)

def callback(ch,method,properties,body):
    print(ch,method,properties)
    #ch:<pika.adapters.blocking_connection.BlockingChannel object at 0x002E6C90>    管道内存对象地址
    #methon:<Basic.Deliver([‘consumer_tag=ctag1.03d155a851b146f19cee393ff1a7ae38‘,   #具体信息
            # ‘delivery_tag=1‘, ‘exchange=‘, ‘redelivered=False‘, ‘routing_key=lzl‘])>
    #properties:<BasicProperties>
    print("Received %r"%body)

channel.basic_consume(callback,     #如果收到消息,就调用callback函数处理消息
                      queue="cc",
                      no_ack=True)
print(‘ [*] Waiting for messages. To exit press CTRL+C‘)
channel.start_consuming()   #开始收消息

consume.py

通过执行pubulish.py和consume.py可以实现上面的消息公平分发,那假如c1收到消息之后宕机了,会出现什么情况呢?rabbitMQ是如何处理的?现在我们模拟一下:

import pika

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()      #声明一个管道(管道内发消息)

channel.queue_declare(queue=‘cc‘)    #声明queue队列

channel.basic_publish(exchange=‘‘,
                      routing_key=‘cc‘,  #routing_key 就是queue名
                      body=‘Hello World!‘
)
print("Sent ‘Hello,World!‘")
connection.close()      #关闭

publish.py

import pika,time

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()

channel.queue_declare(queue=‘cc‘)

def callback(ch,method,properties,body):
    print("->>",ch,method,properties)
    time.sleep(15)              # 模拟处理时间
    print("Received %r"%body)

channel.basic_consume(callback,     #如果收到消息,就调用callback函数处理消息
                      queue="cc",
                      no_ack=True)
print(‘ [*] Waiting for messages. To exit press CTRL+C‘)
channel.start_consuming()   #开始收消息

consume.py

在consume.py的callback函数里增加了time.sleep模拟函数处理,通过上面程序进行模拟发现,c1接收到消息后没有处理完突然宕机,消息就从队列上消失了,rabbitMQ把消息删除掉了;如果程序要求消息必须要处理完才能从队列里删除,那我们就需要对程序进行处理一下:

import pika

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()      #声明一个管道(管道内发消息)

channel.queue_declare(queue=‘cc‘)    #声明queue队列

channel.basic_publish(exchange=‘‘,
                      routing_key=‘cc‘,  #routing_key 就是queue名
                      body=‘Hello World!‘
)
print("Sent ‘Hello,World!‘")
connection.close()      #关闭

publish.py

import pika,time

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()

channel.queue_declare(queue=‘cc‘)

def callback(ch,method,properties,body):
    print("->>",ch,method,properties)
    #time.sleep(15)              # 模拟处理时间
    print("Received %r"%body)
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_consume(callback,     #如果收到消息,就调用callback函数处理消息
                      queue="cc",
                      )
print(‘ [*] Waiting for messages. To exit press CTRL+C‘)
channel.start_consuming()   #开始收消息

consume.py

通过把consume.py接收端里的no_ack=True去掉之后并在callback函数里面添加ch.basic_ack(delivery_tag = method.delivery_tag,就可以实现消息不被处理完不能在队列里清除。

查看消息队列数:

3. 消息持久化

如果消息在传输过程中rabbitMQ服务器宕机了,会发现之前的消息队列就不存在了,这时我们就要用到消息持久化,消息持久化会让队列不随着服务器宕机而消失,会永久的保存下去

发送端:

import pika

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()      #声明一个管道(管道内发消息)

channel.queue_declare(queue=‘cc‘,durable=True)    #队列持久化

channel.basic_publish(exchange=‘‘,
                      routing_key=‘cc‘,  #routing_key 就是queue名
                      body=‘Hello World!‘,
                      properties=pika.BasicProperties(
                          delivery_mode = 2     #消息持久化
                      )
)
print("Sent ‘Hello,World!‘")
connection.close()      #关闭

接收端:

import pika,time

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()

channel.queue_declare(queue=‘cc‘,durable=True)

def callback(ch,method,properties,body):
    print("->>",ch,method,properties)
    time.sleep(15)              # 模拟处理时间
    print("Received %r"%body)
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_consume(callback,     #如果收到消息,就调用callback函数处理消息
                      queue="cc",
                      )
print(‘ [*] Waiting for messages. To exit press CTRL+C‘)
channel.start_consuming()   #开始收消息

4. 消息公平分发

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

channel.basic_qos(prefetch_count=1)

带消息持久化+公平分发:

import pika

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()      #声明一个管道(管道内发消息)

channel.queue_declare(queue=‘cc‘,durable=True)    #队列持久化

channel.basic_publish(exchange=‘‘,
                      routing_key=‘cc‘,  #routing_key 就是queue名
                      body=‘Hello World!‘,
                      properties=pika.BasicProperties(
                          delivery_mode = 2     #消息持久化
                      )
)
print("Sent ‘Hello,World!‘")
connection.close()      #关闭

publish.py

import pika,time

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()

channel.queue_declare(queue=‘cc‘,durable=True)

def callback(ch,method,properties,body):
    print("->>",ch,method,properties)
    time.sleep(15)              # 模拟处理时间
    print("Received %r"%body)
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,     #如果收到消息,就调用callback函数处理消息
                      queue="cc",
                      )
print(‘ [*] Waiting for messages. To exit press CTRL+C‘)
channel.start_consuming()   #开始收消息

consume.py

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

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

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

  • fanout: 所有bind到此exchange的queue都可以接收消息
  • direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
  • topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息

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

例:#.a会匹配a.a,aa.a,aaa.a等
                       *.a会匹配a.a,b.a,c.a等
         注:使用RoutingKey为#,Exchange Type为topic的时候相当于使用fanout 

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

5.1 fanout接收所有广播:

import pika
import sys

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()

channel.exchange_declare(exchange=‘logs‘,
                         type=‘fanout‘)

message = "info: Hello World!"
channel.basic_publish(exchange=‘logs‘,
                      routing_key=‘‘,   #广播不用声明queue
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

publish.py

import pika

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

channel = connection.channel()

channel.exchange_declare(exchange=‘logs‘,
                         type=‘fanout‘)

result = channel.queue_declare(exclusive=True)  # 不指定queue名字,rabbit会随机分配一个名字,
                                                # exclusive=True会在使用此queue的消费者断开后,自动将queue删除
queue_name = result.method.queue

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()

consume.py

5.2 有选择的接收消息 direct:

RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列

import pika
import sys

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

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()

publish.py

import pika
import sys

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

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)

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()

consume.py

consume.py

5.3 更细致的消息过滤 topic:

import pika
import sys

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

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()

publish.py

publish.py

import pika
import sys

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

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()

consume.py

5.4 RPC(Remote procedure call )双向通信:

import pika
import time

credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

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)

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(on_request, queue=‘rpc_queue‘)

print(" [x] Awaiting RPC requests")
channel.start_consuming()

rpc server

import pika
import uuid

class FibonacciRpcClient(object):
    def __init__(self):
        credentials = pika.PlainCredentials(‘admin‘, ‘123123‘)
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(
    ‘192.168.16.82‘, 5672, ‘/‘, credentials)) 

        self.channel = self.connection.channel()

        result = self.channel.queue_declare(exclusive=True)
        self.callback_queue = result.method.queue

        self.channel.basic_consume(self.on_response, no_ack=True,
                                   queue=self.callback_queue)

    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())
        self.channel.basic_publish(exchange=‘‘,
                                   routing_key=‘rpc_queue‘,
                                   properties=pika.BasicProperties(
                                         reply_to = self.callback_queue,
                                         correlation_id = self.corr_id,
                                         ),
                                   body=str(n))
        while self.response is None:
            self.connection.process_data_events()
        return int(self.response)

fibonacci_rpc = FibonacciRpcClient()

print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)
print(" [.] Got %r" % response)

rpc client 

时间: 2024-12-20 08:03:04

python学习笔记-(十五)RabbitMQ队列的相关文章

python学习笔记十五 web框架

python Web程序 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. Python的WEB框架分为两类: 自己写socket,自己处理请求 基于wsgi(Web Server Gateway Interface WEB服务网关接口,实现socket功能),自己处理请求 如图示: 自己写的web框架 #!/usr/bin/env python #coding:utf-8 import socket def handle_req

python学习笔记十五 django基础

Python的WEB框架有Django.Tornado.Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM.模型绑定.模板引擎.缓存.Session等诸多功能. 1.创建django程序 通过命令行 django-admin startproject mysite 创建project 一个工程可以包含多个app,app共用一个project的配置文件 cd mysite python manage.py startapp app01 创建app01 pyt

python学习笔记(十五) - python连接mysql数据库

一. 安装mysql驱动: 由于mysql服务器以独立的进程运行,并通过网络对外服务,所以,需要支持python的mysql驱动来连接mysql服务器. 安装驱动:easy_install mysql-connector-python 二. 连接数据库: 下面演示使用python代码连接mysql: #!/usr/bin/env python # -*- coding: utf-8 -*- # utility @ Python # 导入MySQL驱动: import mysql.connecto

Python学习笔记十五_开发接口

1.mock接口,模拟一些接口,在别的接口没有开发好的时候,你需要用它 假的支付接口,模拟支付成功 2. 3.查看数据,避免直接操作数据库flask web开发框架 实例化server 装饰器,下面的函数变为一个接口 启动服务 import flask,json #__name__代表当前这个python文件 server = flask.Flask(__name__)#实例化server,把当前这个python文件,当做一个服务 def my_db(sql): import pymysql c

python学习笔记十五:日期时间处理笔记

#-*- coding: utf-8 -*- import datetime #给定日期向后N天的日期 def dateadd_day(days): d1 = datetime.datetime.now() d3 = d1 + datetime.timedelta(days) return d3 #昨天 def getYesterday(): today = datetime.date.today() oneday = datetime.timedelta(days=1) yesterday =

python学习笔记(十五)-异常处理

money = input('输入多少钱:') months = input('还几个月:') try: res = calc(int(money),int(months)) except ZeroDivisionError as e: #try里面的代码如果出错了,走except里面的代码 traceback.print_exc()#只是输出报错的详细信息而已 print('还款的月数不能小于1',e) #months输入0时 except ValueError as e: print('输入

python学习笔记十——异常处理

1.try: command except 错误类型,记录错误信息变量: command finally: command try...finally的用处是无论是否发生异常都要确保资源释放代码的执行.一般来说,如果没有发生错误,执行过try语句块之后执行finally语句块,完成整个流程.如果try语句块发生了异常,抛出了这个异常,此时就马上进入finally语句块进行资源释放处理.如下从几个细节讨论finally的特性. 1).try中的return: 当在try语句块中含有return语句

python学习笔记(五岁以下儿童)深深浅浅的副本复印件,文件和文件夹

python学习笔记(五岁以下儿童) 深拷贝-浅拷贝 浅拷贝就是对引用的拷贝(仅仅拷贝父对象) 深拷贝就是对对象的资源拷贝 普通的复制,仅仅是添加了一个指向同一个地址空间的"标签",东西都是一样的.改变或者添加,复制出来的变量也会改变 样例: >>> a=[1,2,3] >>> b=a >>> a [1, 2, 3] >>> b [1, 2, 3] >>> a[1]=0 >>>

Swift 学习笔记十五:扩展

扩展就是向一个已有的类.结构体或枚举类型添加新功能(functionality).扩展和 Objective-C 中的分类(categories)类似.(不过与Objective-C不同的是,Swift 的扩展没有名字.) Swift 中的扩展可以: 1.添加计算型属性和计算静态属性 2.定义实例方法和类型方法 3.提供新的构造器 4.定义下标 5.定义和使用新的嵌套类型 6.使一个已有类型符合某个协议 一.扩展属性,构造器,方法 class Human{ var name:String? va

laravel3学习笔记(十五)

原作者博客:ieqi.net ==================================================================================================== 异常与日志 在应用中,我们总会遇到各种问题.各种异常,这时,记录异常发生时的状态就很重要,所以异常与日志是有着天然的关系的. 关于异常与日志的配置在文件 application/config/error.php 中. 文件中有四个配置项: 'ignore' => ar