python 消息中间件_消息队列中间件 RabbitMQ 详细介绍——安装与基本应用(Python)...

RabbitMQ 是当前最流行的消息中间件(Message Broker)之一,支持多种消息协议(如 AMQP、MQTT)。

同时它也是一个轻量级的非常易于部署的开源软件,可以运行在当前大多数操作系统及云端环境中,也能够部署在分布式的集群环境里以达到高可用、可伸缩的需求。

此外,RabbitMQ 还为目前主流的编程语言提供了丰富的开发工具。

一、软件安装

可以进入 官方下载界面 阅读针对自己操作系统版本的安装手册,根据需求选择适合的安装方式。

Windows 系统可以直接在该页面中获取二进制安装包(还需要安装 Erlang 环境),Linux 系统也可以根据发行版的不同添加特定的软件镜像源。

我这里是 Ubuntu 19.04,没有特别的需求,所以直接从系统默认的软件镜像源里下载安装,命令如下:

$ sudo apt-get install rabbitmq-server

安装完成以后,运行 systemctl status rabbitmq-server 命令查看 RabbitMQ 服务的运行状态:

$ systemctl status rabbitmq-server

● rabbitmq-server.service - RabbitMQ Messaging Server

Loaded: loaded (/lib/systemd/system/rabbitmq-server.service; enabled; vendor preset: enabled)

Active: active (running) since Fri 2019-07-26 01:03:27 CST; 2min 55s ago

Main PID: 770 (beam.smp)

Status: "Initialized"

Tasks: 85 (limit: 2302)

Memory: 85.8M

CGroup: /system.slice/rabbitmq-server.service

├─ 741 /bin/sh /usr/sbin/rabbitmq-server

├─ 770 /usr/lib/erlang/erts-10.2.4/bin/beam.smp -W w -A 64 -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -P 1048576 -t 5000000 -stbt db -zdbbl 128000 -K true -- -root /usr/lib/erlang -progname erl -- -home /var/lib/rabbitmq -- -pa /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.8/ebin -noshell -noinput -s rabbit boot -sname rabbit@server1 -boot start_sasl -kernel inet_default_connect_options [{nodelay,true}] -sasl errlog_type error -sasl sasl_error_logger false -rabbit lager_log_root "/var/log/rabbitmq" -rabbit lager_default_file "/var/log/rabbitmq/rabbit@server1.log" -rabbit lager_upgrade_file "/var/log/rabbitmq/rabbit@server1_upgrade.log" -rabbit enabled_plugins_file "/etc/rabbitmq/enabled_plugins" -rabbit plugins_dir "/usr/lib/rabbitmq/plugins:/usr/lib/rabbitmq/lib/rabbitmq_server-3.7.8/plugins" -rabbit plugins_expand_dir "/var/lib/rabbitmq/mnesia/rabbit@server1-plugins-expand" -os_mon start_cpu_sup false -os_mon start_disksup false -os_mon start_memsup false -mnesia dir "/var/lib/rabbitmq/mnesia/rabbit@server1" -kernel inet_dist_listen_min 25672 -kernel inet_dist_listen_max 25672

├─1243 erl_child_setup 65536

├─1286 inet_gethost 4

└─1287 inet_gethost 4

7月 26 01:02:44 server1 systemd[1]: Starting RabbitMQ Messaging Server...

7月 26 01:03:27 server1 systemd[1]: rabbitmq-server.service: Supervising process 770 which is not our child. We'll most likely not notice when it exits.

7月 26 01:03:27 server1 systemd[1]: Started RabbitMQ Messaging Server.

Web Admin

RabbitMQ 还提供了可以远程访问的 Web 管理与监控工具,默认以插件的形式安装到系统中,需要使用 rabbitmq-plugins 命令开启。

具体命令如下:

$ sudo rabbitmq-plugins enable rabbitmq_management

RabbitMQ 默认创建了一个用户名密码分别为 guest/guest 的用户,只是该用户只允许本地登录。(我这里是远程。。。)

如果需要远程访问 Web 控制台,可以通过 rabbitmqctl 命令创建一个新的管理账户:

$ sudo rabbitmqctl add_user

此时新创建的账户仍无法登录,还需要为其分配用户角色以及对 vhost 的管理权限,命令如下:

$ sudo rabbitmqctl set_user_tags administrator

$ sudo rabbitmqctl set_permissions -p / ".*" ".*" ".*"

权限设置完毕后,即可用之前指定的用户名密码远程登录 Web 管理系统,界面如下图:

Web Admin

Web 形式的后台界面为管理工作与监控需求提供了便捷的接口,同时大部分管理操作也可直接通过 rabbitmqctl 命令完成,具体可参考该命令的帮助信息:

$ sudo rabbitmqctl

Usage:

rabbitmqctl [-n ] [-l] [-q] []

...

Commands:

add_user

add_vhost

authenticate_user

await_online_nodes [-t ]

cancel_sync_queue [-p ] queue

change_cluster_node_type

change_password

...

二、架构解析

RabbitMQ 是一种高性能、稳定、可伸缩(集群部署)的消息中间件,由 Erlang 语言编写。

Erlang 是一种函数式编程语言,专注于分布式、高容错的软件类实时系统等应用场景。它通过轻量级的进程设计以及进程之间的消息通信,提供了一个高层次的不需要共享状态的并发模型。

RabbitMQ 集群通过 Erlang VM 原生的 IPC (inter-process communication) 机制完成跨节点的消息通信。

松耦合架构

对于传统的应用架构,比如一个 Web 应用的登录程序,往往需要对后端的数据库表格进行多项实时的写入操作。而当用户的访问量大增时,此时的表格更新操作很容易成为瓶颈并影响到整体的响应速度。

相对于登录程序直接更新表格数据的紧耦合架构,可以将前端的请求数据推送到基于消息的中间件或者某个中心化的消息队列应用,再通过中间件分发消息到多个消费者(Consumer)应用,由消费者独立、异步地完成最终的数据库更新操作。

image.png

基于消息的中间件对于创建数据导向的灵活的应用架构有非常大的优势。RabbitMQ 支持的松耦合设计可以使应用不再受类似于数据库写操作的性能限制。

同时这种架构也非常易于横向扩展,可以在添加作用于相同数据的应用实例时不影响现有的核心功能。

tightly coupled application

loosely coupled application

三、消息应用示例代码

下文中将使用 Python 语言及其 RabbitMQ 客户端 Pika 创建 5 个基本的消息应用,结构由简单到复杂,源代码均参考自官网 RabbitMQ Tutorials 。

安装 pika 库:pip install pika

Hello World

该应用的结构示意图如下:

helloworld

由 P (Producer) 发送一条消息到队列(Queue),再由队列转发消息到 C (Consumer) 。

发送端代码 send.py 如下:

#!/usr/bin/env python

import pika

# 初始化与 RabbitMQ 服务器的连接

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

# 队列声明

channel.queue_declare(queue='hello')

# 发送消息

channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')

print(" [x] Sent 'Hello World!'")

connection.close()

接收端 reveive.py 代码如下:

#!/usr/bin/env python

import pika

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.queue_declare(queue='hello')

# 接收到消息后触发的回调函数

def callback(ch, method, properties, body):

print(" [x] Received %r" % body)

# 消费者声明与消息监听

channel.basic_consume(

queue='hello', on_message_callback=callback, auto_ack=True)

print(' [*] Waiting for messages. To exit press CTRL+C')

channel.start_consuming()

测试

首先运行 4 次发送程序:

$ python send.py

[x] Sent 'Hello World'

$ python send.py

[x] Sent 'Hello World'

$ python send.py

[x] Sent 'Hello World'

$ python send.py

[x] Sent 'Hello World'

从 Web 管理界面中可以看到,此时队列中缓存了 4 条消息。

overview

运行接收端程序:

$ python receive.py

[x] Waiting for messages. To exit press CTRL+C

[x] Received b'Hello World'

[x] Received b'Hello World'

[x] Received b'Hello World'

[x] Received b'Hello World'

发送端连续 4 次发送的消息被接收端收取,队列中缓存的消息被清空。同时接收端保持运行状态等待新的消息被转发给自己。

overview 2

消息队列一直处于等待生产者发送消息和将收到或缓存的消息转发给消费者的状态。如未有消费者及时接收和处理被转发的消息,则这部分消息缓存在队列中等待进一步操作。

Work Queue

结构示意图:

Work Queue

本例中将创建一个 Work Queue 用来将消耗时间长的任务以轮询的方式分发给多个消费者处理。

生产者源代码 new_task.py :

#!/usr/bin/env python

import pika

import sys

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)

message = ' '.join(sys.argv[1:]) or "Hello World!"

channel.basic_publish(

exchange='',

routing_key='task_queue',

body=message,

properties=pika.BasicProperties(

delivery_mode=2, # make message persistent

))

print(" [x] Sent %r" % message)

connection.close()

消费者源代码 worker.py :

#!/usr/bin/env python

import pika

import time

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)

print(' [*] Waiting for messages. To exit press CTRL+C')

def callback(ch, method, properties, body):

print(" [x] Received %r" % body)

time.sleep(body.count(b'.'))

print(" [x] Done")

ch.basic_ack(delivery_tag=method.delivery_tag) # Message acknowledgment

channel.basic_qos(prefetch_count=1)

channel.basic_consume(queue='task_queue', on_message_callback=callback)

channel.start_consuming()

Message acknowledgment

消费者在处理接收到的任务或消息时有可能会消耗比较多的时间,在此过程中,如消费者端出现软硬件故障,则会出现消息丢失的情况。

RabbitMQ 支持 Message acknowledgment 。即消费者在接收和处理完一个特定的消息后会向 RabbitMQ 返回一个应答(ack),说明该消息可以从队列中移除。

如果消费者在返回应答之前丢失与队列的连接,则 RabbitMQ 判定对应的消息未由消费者完全处理,会将该消息保留在队列中并重新分发给其他在线的消费者。

Message durability

消息应答的机制可以确保即使消费者宕机的情况下任务仍不会丢失。但是当 RabbitMQ 服务本身出现故障时,队列以及队列中缓存的消息仍旧会被清理掉。

为了保证 RabbitMQ 中队列以及消息的持久化,首先需要在生产者和消费者代码中同时声明队列为 durable :

channel.queue_declare(queue='task_queue', durable=True)

此外还需要将生产者代码中的 delivery_mode 属性设置为 2 确保消息的持久化:

properties=pika.BasicProperties(delivery_mode=2,)

测试

打开两个命令行终端,分别运行 worker.py 程序:

# Shell 1

$ python worker.py

[x] Waiting for messages. To exit press CTRL+C

# Shell 2

$ python worker.py

[x] Waiting for messages. To exit press CTRL+C

打开另一个终端窗口运行 new_task.py 程序发送 4 条消息:

# Shell 3

$ python new_task.py First Message

[x] Sent 'First Message'

$ python new_task.py Second Message

[x] Sent 'Second Message'

$ python new_task.py Third Message

[x] Sent 'Third Message'

$ python new_task.py Forth Message

[x] Sent 'Forth Message'

最终两个消费者分别接收到队列分发的两条消息:

# Shell 1

$ python worker.py

[x] Waiting for messages. To exit press CTRL+C

[x] Received b'First Message'

[x] Done

[x] Received b'Third Message'

[x] Done

# Shell 2

$ python worker.py

[x] Waiting for messages. To exit press CTRL+C

[x] Received b'Second Message'

[x] Done

[x] Received b'Forth Message'

[x] Done

Fair dispatch

当 RabbitMQ 以轮询的方式(即平均分配)将队列中的消息转发给多个消费者时,如果这些消费者接收到的任务繁重程度差异很大,则会导致某些消费者端任务的积压。

为了避免这种情况发生,可以使用 basic_qos 方法设置 prefetch 的值,如 worker.py 程序中的以下代码:

channel.basic_qos(prefetch_count=1) 。

该行代码可以确保同一个消费者在任意时间点最多只接受 1 个任务分配给自己。即如果某个消费者当前有未处理完的消息,则不再接收新的消息直到当前的任务处理完。

Publish/Subscribe

结构示意图:

Publish-Subscribe

Exchange

在之前的示例中,用到了消息队列模型中的以下几个组件:

producer :生产者,即发送消息的应用

queue :队列,即存储消息的缓存

consumer :消费者,即接收消息的应用

实际上在 RabbitMQ 的消息模型中,生产者从来不会将消息直接发送到队列中,而是将消息发送给一个名为 exchange 的组件。

exchange 的一端用来接收生产者发送的消息,一端用来将消息推送到队列中。它通过 exchange type 中的定义判断特定的消息是该推送给某个对应的队列,还是将其广播给多个队列,又或者直接丢弃。

RabbitMQ 主要提供了 4 种 exchange 类型:direct、topic、headers 和 fanout。

本例中使用 fanout,即 exchange 会将接收到的消息以广播的形式发送给所有关联的队列,再由队列传递给消费者处理。

源代码(emit_log.py)如下:

#!/usr/bin/env python

import pika

import sys

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.exchange_declare(exchange='logs', exchange_type='fanout')

message = ' '.join(sys.argv[1:]) or "info: Hello World!"

channel.basic_publish(exchange='logs', routing_key='', body=message)

print(" [x] Sent %r" % message)

connection.close()

receive_logs.py:

#!/usr/bin/env python

import pika

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.exchange_declare(exchange='logs', exchange_type='fanout')

result = channel.queue_declare(queue='', exclusive=True)

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(

queue=queue_name, on_message_callback=callback, auto_ack=True)

channel.start_consuming()

receive_logs.py 文件中有一行 result = channel.queue_declare(queue='', exclusive=True) 代码,用来声明一个临时队列( queue='' 没有指定名称,因此会由 RabbitMQ 设置随机的名称),同时 exclusive=True 设置该队列在消费者断开连接后自行删除。

测试

同时打开两个命令行窗口分别运行 receive_logs.py 文件:

# Shell 1

$ python receive_logs.py

[*] Waiting for logs. To exit press CTRL+C

# Shell 2

$ python receive_logs.py

[*] Waiting for logs. To exit press CTRL+C

再打开第三个终端执行 emit_log.py 命令 4 次:

# Shell 3

$ python emit_log.py

[x] Sent 'info: Hello World!'

$ python emit_log.py

[x] Sent 'info: Hello World!'

$ python emit_log.py

[x] Sent 'info: Hello World!'

$ python emit_log.py

[x] Sent 'info: Hello World!'

此时之前运行的两个 receive 程序同时收到发送的 4 条消息:

$ python receive_logs.py

[*] Waiting for logs. To exit press CTRL+C

[x] b'info: Hello World!'

[x] b'info: Hello World!'

[x] b'info: Hello World!'

[x] b'info: Hello World!'

$ python receive_logs.py

[*] Waiting for logs. To exit press CTRL+C

[x] b'info: Hello World!'

[x] b'info: Hello World!'

[x] b'info: Hello World!'

[x] b'info: Hello World!'

Routing

结构示意图:

Routing

与上一个例子中以广播的形式转发消息不同,本例中允许消费者通过队列有选择地订阅生产者发送的部分消息。

Binding 和 Direct exchange

在 RabbitMQ 中,binding 代表 exchange 与队列的对应关系,即队列会根据 binding 的设置对 exchange 转发的消息有选择性地接收。

因此 binding 的最终效果也依赖于 exchange 的类型。比如之前用到的 fanout 类型,由于是广播的形式(转发给所有关联的队列)并不需要选择的动作,则 binding 的值被忽略。

但是对于 direct 类型的 exchange ,则可以通过 binding 对消息进行筛选。在 direct exchange 下,只有当队列的 binding_key 与消息的 routing_key 一致时,队列才会收到 exchange 转发的消息。

emit_log_direct.py:

#!/usr/bin/env python

import pika

import sys

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.exchange_declare(exchange='direct_logs', exchange_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()

receive_logs_direct.py:

#!/usr/bin/env python

import pika

import sys

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.exchange_declare(exchange='direct_logs', exchange_type='direct')

result = channel.queue_declare(queue='', 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(

queue=queue_name, on_message_callback=callback, auto_ack=True)

channel.start_consuming()

测试

首先运行 receive_logs_direct.py 程序并指定参数为 error(即只接收标记为“error”的消息):

# Shell 1

$ python receive_logs_direct.py error

[*] Waiting for logs. To exit press CTRL+C

打开另一终端同样运行 receive_logs_direct.py 程序并指定参数为 info warning(即接收标记为 info 或 warning 的消息):

# Shell 2

$ python receive_logs_direct.py info warning

[*] Waiting for logs. To exit press CTRL+C

打开第三个终端并运行 emit_log_direct.py 程序发送 4 条日志消息:

# Shell 3

$ python emit_log_direct.py error "This is an error"

[x] Sent 'error':'This is an error'

$ python emit_log_direct.py info "Hi, I am an info"

[x] Sent 'info':'Hi, I am an info'

$ python emit_log_direct.py warning "Yeah, it's a warning"

[x] Sent 'warning':"Yeah, it's a warning"

$ python emit_log_direct.py error "Hi, it's an error again"

[x] Sent 'error':"Hi, it's an error again"

此时 Shell 1 中只接收到了标记为 error 的消息:

$ python receive_logs_direct.py error

[*] Waiting for logs. To exit press CTRL+C

[x] 'error':b'This is an error'

[x] 'error':b"Hi, it's an error again"

而 Shell 2 中接收到了标记为 info 和 warning 的消息:

$ python receive_logs_direct.py info warning

[*] Waiting for logs. To exit press CTRL+C

[x] 'info':b'Hi, I am an info'

[x] 'warning':b"Yeah, it's a warning"

Topics

结构示意图:

Topics

direct 类型的 exchange 虽然可以根据消息的 routing_key 以及队列的 binding_key 有选择性的推送消息到队列,但是并不适合更复杂的场景。

而 topic 类型的 exchange 与 direct 类型逻辑上大致相同,只是 topic 类型的 exchange 并没有一个明确的 routing_key,而是由几个点号(.)分隔的单词(如 lazy.orange.cat)进行定义。

与之对应的 binding_key 也需要遵循同样的形式,只不过 binding_key 额外支持两个特殊含义的字符:

星号(*)可以表示某一个任意的单词

井号(#)可以表示任意 0 个或多个单词

因此对于上图(Topics)中的情形,routing_key 为 quick.orange.rabbit 的消息会被转发给 Q1 和 Q2 队列,quick.orange.fox 则只会转发给 Q1 队列,lazy.orange.male.rabbit 被转发给 Q2 队列。

emit_log_topic:

#!/usr/bin/env python

import pika

import sys

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.exchange_declare(exchange='topic_logs', exchange_type='topic')

routing_key = sys.argv[1] if len(sys.argv) > 2 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()

receive_logs_topic.py:

#!/usr/bin/env python

import pika

import sys

connection = pika.BlockingConnection(

pika.ConnectionParameters(host='localhost'))

channel = connection.channel()

channel.exchange_declare(exchange='topic_logs', exchange_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(

queue=queue_name, on_message_callback=callback, auto_ack=True)

channel.start_consuming()

测试

先运行接收端程序(Shell 1 和 Shell 2),再运行发送端(Shell 3),效果如下:

# Shell 3

$ python emit_log_topic.py "kern.warning" "A kernel warning message"

[x] Sent 'kern.warning':'A kernel warning message'

$ python emit_log_topic.py "network.critical" "A critical network error"

[x] Sent 'network.critical':'A critical network error'

$ python emit_log_topic.py "kern.critical" "A critical kernel error"

[x] Sent 'kern.critical':'A critical kernel error'

# Shell 1

$ python receive_logs_topic.py "kern.*"

[*] Waiting for logs. To exit press CTRL+C

[x] 'kern.warning':b'A kernel warning message'

[x] 'kern.critical':b'A critical kernel error'

# Shell 2

$ python receive_logs_topic.py "*.critical"

[*] Waiting for logs. To exit press CTRL+C

[x] 'network.critical':b'A critical network error'

[x] 'kern.critical':b'A critical kernel error'

参考资料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457609.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用 Flask 来写个轻博客 (1) — 创建项目

目录 目录前言扩展阅读部署开发环境创建 Github 项目前言 一步一步的实现一个 Flask 轻博客项目启动,最新的代码会上传到 Github。 扩展阅读 欢迎使用 Flask — virtualenv 部署开发环境 连接 GitHubhostnamectl set-hostname flask-dev # 设置 hostname ssh-keyg…

python静态方法,类方法,属性方法,实例方法

DAY 3. 静态方法,类方法,属性方法,实例方法 有四种方法,实例方法,类方法,静态方法,属性方法 实例方法 实例方法的第一个参数是self,他会指向类的实例化对象,只能被对象…

ubuntu 软件包降级

ubuntu 软件包降级 sudo aptitude install libssl-dev 1. 是否接受该解决方案? [Y/n/?] n 2. 是否接受该解决方案? [Y/n/?] y 3. 您要继续吗? [Y/n/?] ysudo aptitude install libcairo21.4.10-1ubuntu4 # 强制降级 sudo aptitude forbid…

java后期发展方向_Java程序员的4个职业发展方向,该如何把握黄金5年?

在Java程序界流行着一种默认的说法叫“黄金5年”,意思是说,一个Java程序员从入职的时候算起,前五年我选择直接影响着整个职业生涯的发展方向和薪资走向。而这5年,也决定了一个程序员能否成为职业大牛的可能。那么,在这…

python 类变量(属性)和实例变量(属性

DAY 4. 类变量(属性)和实例变量(属性) 类变量:在所有类的实例之间都可以共享的变量,类变量在所有对象间只保留一份 在类体中定义类的所有实例对象都可以访问类变量类变量只能由类修改,实例对象…

MySQL 关联表批量修改(数据同步)

update table1 t1 ,table2 t2 set t1.field1 t2.field2 where t1.id t2.id 转载于:https://www.cnblogs.com/52php/p/5677908.html

sourcetree不好做到的一些git操作

2019独角兽企业重金招聘Python工程师标准>>> 日常中我们有很多操作通过sourcetree就可以实现界面化操作,但是有一些场景不好去实现,这里总结下: 场景1:我们有个A分支,需要跟master分支合并等待上线&#xf…

vue大括号里接受一个函数_vue源码探究(第四弹)

vue源码探究(第四弹)结束了上一part的数据代理,这一部分主要讲讲vue的模板解析,感觉这个有点难理解,而且内容有点多,hhh。模板解析废话不多说,先从简单的入手。按照之前的套路,先举一…

类级别的分装 ---四种访问级别

privateprivate成员为类的私有性质,仅有类本身和友元可以访问;protected和private类似,区别于protected可以被该类所有派生类访问;publicpublic的成员可以被外界的所有客户代码直接访问published和public的区别仅在于published的成…

python自省与反射

DAY 5. python自省 这是很久之前写的,当时对自省和反射的概念没理解,学习Java以后多了一点理解,自省是获取对象的能力,反射是操纵对象的能力,python中使用getattr()和setattr()实现反射,而其他的则是自省&…

vb.net 窗体接收键盘事件_(十五)C#WinFrom自定义控件系列-键盘(二)

前提入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。本系列文章将讲解各种控件的开发及思路,欢迎各位批评指正。此系列控件开发教程将全部在原生控件基础上进行重绘开发,目标的扁平化、漂亮、支持触屏。如…

centos下cmake安装

步骤一、安装gcc等必备程序包(已安装则略过此步,用gcc -v检测) yum install -y gcc gcc-c make automake 步骤二、安装wget (已安装则略过此步) yum install -y wget 步骤三、获取CMake源码包 wget http://www.cmake.…

python 生成式,迭代器,生成器

DAY 6. 生成式,迭代器,生成器 6.1 生成式 6.1.1 列表生成式 list [index for index in range(10)]6.1.2 字典生成式 dict {zhangsan: 10,lisi: 12,wangwu: 18 } # 实现键值互换 dict {k:v for v,k in dict.items() if k > 12}6.1.3 集合生成式 # 100以内…

shell MAC 地址 校验

/**************************************************************************************** shell MAC 地址 校验* 说明:* 要对MAC地址进行校验,记录一下正则表达式写法,有些方法在PC上验证是可行的&…

移动端Web开发如何处理横竖屏

<!Doctype html> <html> <head> <meta charset"utf-8"> <meta id"viewport" name"viewport" content"widthdevice-width,initial-scale1.0;"> <title>横竖屏切换检测</title> <style ty…

恩智浦智能车大赛2020_内蒙古科技大学第九届智能车大赛校内公开赛总决赛

为了激发学生的创新意识&#xff0c;提高学生的动手能力&#xff0c;培养团队合作意识&#xff0c;秉承“实践源于真知&#xff0c;创新放飞梦想”的思想。2020年12月6日&#xff0c;内蒙古科技大学第九届智能车大赛总决赛如约而至。本次大赛有来自各院系的223支队伍报名参加了…

python格式化字符串的三种方法(%,format,f-string)

DAY 7. 格式化字符串 到目前为止&#xff0c;我所知道的&#xff0c;python格式化字符串有三种方法&#xff0c;第一是早期就有的%&#xff0c;其次是2.5之后的format(),还有就是3.6添加的f字符串调试 7.1 %格式化字符串 %格式化字符串是python最早的&#xff0c;也是能兼容…

Android App 优化之 ANR 详解

为了便于阅读, 应邀将Android App性能优化系列, 转移到掘金原创上来.掘金的新出的"收藏集"功能可以用来做系列文集了. 今天先来聊聊ANR. 1, 你碰到ANR了吗 在App使用过程中, 你可能遇到过这样的情况: 恭喜你, 这就是传说中的ANR. 1.1 何为ANR ANR全名Application Not…

微信高级群发接口正文乱码解决方案

content里面的内空如果含有html标签的话&#xff0c;需要对内容进行一下转义。如果里面含有style".."类似于这样的带""号的内容的话&#xff0c;就更需要注意了。 foreach ($news as &$item) {foreach ($item as $key > $val){if ($key content){$…

python *args和**kwargs以及序列解包

DAY 8. *args和**kwargs *args&#xff1a;多值元组&#xff0c;**kwargs多值字典&#xff0c;他们是python函数传参时两个特殊的参数&#xff0c;args和kwargs并不是强制的&#xff0c;但习惯使用这两个&#xff0c;如果在函数参数列表中声明了*args&#xff0c;则允许传递任…