一、介绍

RabbitMQ是基于AMQP协议的消息中间件,服务器端用Erlang语言编写,支持多种客户端,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

二、安装

去官网下载对应的版本,在安装rabbitmq之前需要安装对应Erlang环境,安装完成后通过命令/sbin/service rabbitmq-server start启动。

http://www.rabbitmq.com/download.html

三、基本配置

  1. 用户权限配置:rabbitmq默认的guest/guest用户只能在本机访问,如果需要外网用户访问,需要单独创建用户,通过命令创建用户及赋予权限 ,通过命令:./rabbitmqctl add_user test test ,./rabbitmqctl set_user_tags test administrator添加test用户和赋予admin权限。注:命令行创建的用户需要在接下来的web监控页面用户管理里设定权限(下图的Set permisson),否则无法连接成功。
  2. WEB监控配置:通过./rabbitmq-plugins enable rabbitmq_management命令开启web监控页面显示,默认端口15672,可通过localhost:15672查看web监控页面,方便后期管理和查看队列消息。

四、Java Demo

消息提供者Provider:

package org.rabbitmq.RabbitMq;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; /**
* java访问mq基础demo --provider
* @author Mr.tanzc
*
*/
public class MySpreadSend { //发送消息
public static void main(String[] args) throws IOException {
/*使用工厂类建立Connection和Channel,并且设置参数*/
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");//MQ的IP
factory.setPort(5672);//MQ端口
factory.setUsername("test");//MQ用户名
factory.setPassword("test");//MQ密码
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); /*定义交换机*/
channel.exchangeDeclare("COLOR_EXCHANGE", "direct"); /*创建多个消息队列*/
channel.queueDeclare("BLACK_QUEUE", false, false, false, null);
channel.queueDeclare("RED_QUEUE", false, false, false, null); /*绑定交换机和队列*/
channel.queueBind("BLACK_QUEUE", "COLOR_EXCHANGE", "black");
channel.queueBind("RED_QUEUE", "COLOR_EXCHANGE", "red"); /*通过交换机发送不同类别的消息到不同的队列中,注意,消息是由一个交换机来根据标志发往不同的队列中去*/
String message = "black";
channel.basicPublish("COLOR_EXCHANGE", "black", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'"); message="red";
channel.basicPublish("COLOR_EXCHANGE", "red", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'"); /*关闭连接*/
channel.close();
connection.close();
} }

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

定义两个消费者Comsumer:

第一个消费者MySpreadRecvRed

package org.rabbitmq.RabbitMq;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException; public class MySpreadRecvRed { public static void main(String[] args) throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
/*建立连接*/
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");//MQ的IP
factory.setPort(5672);//MQ端口
factory.setUsername("test");//MQ用户名
factory.setPassword("test");//MQ密码
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); /*声明要连接的队列*/
/*定义交换机*/
channel.exchangeDeclare("COLOR_EXCHANGE", "direct");
/*绑定交换机和队列*/
channel.queueBind("RED_QUEUE", "COLOR_EXCHANGE", "red");
/*创建消费者对象,用于读取消息*/
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume("RED_QUEUE", true, consumer);
/* 读取队列,并且阻塞,即在读到消息之前在这里阻塞,直到等到消息,完成消息的阅读后,继续阻塞循环*/
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
}

第二个消费者MySpreadRecvBlack:

package org.rabbitmq.RabbitMq;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException; /**
* 获取指定队列的消息
* @author Mr.tanzc
*
*/
public class MySpreadRecvBlack { public static void main(String[] args) throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
/*建立连接*/
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.25");//MQ的IP
factory.setPort(5672);//MQ端口
factory.setUsername("twk");//MQ用户名
factory.setPassword("twk");//MQ密码
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); /*声明要连接的队列*/
/*定义交换机*/
channel.exchangeDeclare("COLOR_EXCHANGE", "direct");
/*绑定交换机和队列*/
channel.queueBind("BLACK_QUEUE", "COLOR_EXCHANGE", "black");
/*创建消费者对象,用于读取消息*/
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume("BLACK_QUEUE", true, consumer);
/* 读取队列,并且阻塞,即在读到消息之前在这里阻塞,直到等到消息,完成消息的阅读后,继续阻塞循环*/
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
}

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

先运行Provider,查看到mq监控台队列里面有消息,再运行Comsumer,可以看到打印出来的消费信息,队列里面对应数据清空。

五、基础API使用

主要对使用过的RabbitMQ一些概念和功能进行说明。

1.MQ使用流程

  • 客户端连接到消息队列服务器,打开一个channel。
  • 客户端声明一个exchange,并设置相关属性。
  • 客户端声明一个queue,并设置相关属性。
  • 客户端使用routing key,在exchange和queue之间建立好绑定关系。
  • 客户端投递消息到exchange。
  • ​ exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。

2.ConnectionFactory

连接工厂类,我们通过这个类可以设定一些连接参数

    ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");//MQ的IP
factory.setPort(5672);//MQ端口
factory.setUsername("test");//MQ用户名
factory.setPassword("test");//MQ密码

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

3.Connection

是通过工厂类New出来的rabbitMq连接,我们后续跟mq的交互都是基于这个连接。

Connection connection = factory.newConnection();

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

4.Channel 通道

进行消息读写的通道,可以理解为指向队列的路径

5.交换机Exchange

定义了消息路由规则

6.队列Queue

是存储消息的基本单元

7.Bind

绑定了Queue和Exchange,意即为符合什么样路由规则的消息,将会放置入哪一个消息队列

使用队列之前都需要用channel声明队列,channel.queueDeclare方法会在队列不存在的时候创建队列,如果队列存在,则不创建。

    Channel channel = connection.createChannel();

    /*定义交换机*/
channel.exchangeDeclare("COLOR_EXCHANGE", "direct");
/*创建多个消息队列*/
channel.queueDeclare("BLACK_QUEUE", false, false, false, null);
channel.queueDeclare("RED_QUEUE", false, false, false, null);
/*绑定交换机和队列*/
channel.queueBind("BLACK_QUEUE", "COLOR_EXCHANGE", "black");
channel.queueBind("RED_QUEUE", "COLOR_EXCHANGE", "red");

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

8.publish

发送消息到队列:通过channel.basicPublish方法来发送消息到指定队列,第一个参数是交换机名称,第二个参数是队列名称,第三个参数是用于优先级队列(后面再谈,若不适用为null),最后一个参数为消息内容的字节

/*通过交换机发送不同类别的消息到不同的队列中,注意,消息是由一个交换机来根据标志发往不同的队列中去*/
String message = "black";
channel.basicPublish("COLOR_EXCHANGE", "black", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'"); message="red";
channel.basicPublish("COLOR_EXCHANGE", "red", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

9.consume

消费者消费消息也需要创建通道,声明指定交换机和队列,然后通过consumer对象的basicConsume方法来绑定队列和消费者,第一个参数是队列名称,第二个参数为是否自动ack

nextDelivery(long timeout)方法可以获取消息,参数为最多等待时间,如果在这个时间内获取到消息,则返回消息,若无,将会最多等待这个时间,仍没有消息数据就会返回null。

再通过consumer.nextDelivery().getBody()方法获取消息内容

    /*创建消费者对象,用于读取消息*/
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume("RED_QUEUE", false, consumer);
/* 读取队列,并且阻塞,即在读到消息之前在这里阻塞,直到等到消息,完成消息的阅读后,继续阻塞循环*/
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

六、ACK机制

QUEUE里面的消息存在一个ACK机制,即消息的确认机制。

channel.basicConsume(queueName, false, consumer)

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

第二个参数就是是否自动ack。

在实际项目应用中,消费者拿到消息进行处理需要一段时间,中间因为用户中止操作或者因为网络问题宕机时,如果设置为自动ack,这个消费就会丢失。为防止这种情况的出现,一般设置为取消auto ack,需要消费者发送确认回执后才从队列中删除,确保消息能够被正确消费。

消费者处理完消息后可通过

channel.basicAck(delivery .getEnvelope().getDeliveryTag(), false);

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

来确认ack消息,这个方法的第一个参数是消息的标识tag,第二个为是否重新放入队列,设置为false消息会从队列里面删除,true的话会重新放入队列,等待再次消费。

七、消息的持久化

MQ的消息是支持持久化操作的,会将数据保存到硬盘里面,防止因为服务器宕机或者RabbitMQ重启后的数据丢失。我们要分别设置队列的持久化(重启后队列名称恢复)和消息的持久化(重启后数据保留)。

队列的持久化:

channel.queueDeclare(queueName, true, false, false, null);

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

第一个参数为队列名称,第二个参数即是否持久化操作,设置为true即实现队列信息的持久化

消息的持久化:

channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

其中第三个参数MessageProperties.PERSISTENT_TEXT_PLAIN即设置消息的持久化

八、消息的公平分发

如果有多个消费者监听同一队列,MQ默认会把消息平摊给多个消费者,在不同消费者处理时间不同的情况下,就有可能造成某个消费者堆积了很多消息未处理而另外一个消费者无消息可处理的情况,为避免这种情况,我们可以通过

    channel.basicQos(1,true);

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

方法设置每个消费者的分发数量,1即代表这个消费者每次最多处理一个消息,如果要拿下一个消息,必须把未ack的消息ack掉以后才能拿到下一个消息,这样就实现了消息的公平分发机制。

九、消息的优先级

队列的消息默认是先进先出的,这也是RabbitMQ默认支持的队列。

3.5.0版本之前的MQ默认是不支持优先级队列的,只能通过插件安装的方式来实现。

3.5.0版本之后的MQ已经集成了这一功能,可以直接使用。

首先,我们需要声明优先级队列:

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-max-priority", 100);
channel.queueDeclare(queueName, true, false, false, args);

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

queueDeclare方法的最后一个参数之前设置为null,即默认的非优先级队列,这么我们传递一个包含key为x-max-priority的map作为参数,就可以创建一个优先级的队列,100的数值设定队列最大支持优先级的数字。

注:这里需要注意的是,在第一次声明队列并创建队列后,后续使用这个队列时的声明如果属性不一致的话,会报错,必须声明相同属性的相同队列。

接下来,我们放入消息时传递消息的优先级:

BasicProperties props = MessageProperties.PERSISTENT_BASIC.builder().priority(1).build();
channel.basicPublish("", queueCheckName, props, message.getBytes());

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

首先定义一个BasicProperties的变量,传递一个优先级为1的变量,再将这个参数传递到

basicPublish方法的第三个参数(即之前持久化的参数,MessageProperties.PERSISTENT_BASIC.builder()方法也是持久化的)

十、消息的路由分发

RabbitMQ常用的Exchange Type有fanout、direct、topic、headers这四种(AMQP规范里还提到两种Exchange Type,分别为system与自定义,这里不予以描述)。

默认的是direct直接发送,如上文中queueDeclare方法中不指定交换机,而直接指定队列名称,就是默认的交换机,将消息放到对应名称的队列中。这种形式在发送和消费消息的时候都需要指定对应队列名称。

fanout即广播形式,通过交换机将消息发到绑定的所有队列上面。

// 声明一个名称为"exchange_fanout"的exchange
channel.exchangeDeclare("exchange_fanout", "fanout");
// 将消息发送给exchange
channel.basicPublish("exchange_fanout", "", null, msg.getBytes());

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

topic即匹配模式,我们可以为每一个消息类型设定一个主题topic,放入队列的时候带上topic,消费者获取消息的时候可以通过指定topic来获取消息。

消息提供者:

    //指定topic类型的交换机
String exchange = "exchange03";
String msgType= "type1";
channel.exchangeDeclare(exchange , "topic") ;
String msg = "Hello World!";
//发送指定主题的消息
channel.basicPublish( exchange , msgType, null , msg.getBytes());

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

消息消费者:

    String exchange = "exchange03";
channel.exchangeDeclare(exchange , "topic") ;
String queueName = channel.queueDeclare().getQueue() ; //第三个参数就是type
channel.queueBind(queueName, exchangeName, "type1") ;
QueueingConsumer consumer = new QueueingConsumer(channel) ;
channel.basicConsume(queueName, true, consumer) ; //循环获取消息
while(true){
//获取消息,如果没有消息,这一步将会一直阻塞,可以设定超时时间
Delivery delivery = consumer.nextDelivery() ;
String msg = new String(delivery.getBody()) ;
System.out.println("received message[" + msg + "] from " + exchangeName);
}

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

十一、Spring集成

RabbitMQ可以和Spring无缝集成,就无需跟client打交道,使用更为方便。

spring-rabbit.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <description>rabbitmq 连接服务配置</description>
<context:component-scan base-package="com.mop.self.mq"/>
<context:property-placeholder location="classpath:conf/rabbitmq.properties"/> <!-- 连接配置 -->
<rabbit:connection-factory id="connectionFactory" host="${mq.host}" username="${mq.username}" password="${mq.password}" port="${mq.port}"/>
<rabbit:admin connection-factory="connectionFactory"/> <!-- spring template声明-->
<rabbit:template exchange="test-mq-exchange" id="amqpTemplate" connection-factory="connectionFactory" message-converter="jsonMessageConverter" /> <!-- 队列说明 -->
<rabbit:queue id="test_queue" name="test_queue" durable="true" auto-delete="false" exclusive="false" /> <!-- direct 交换器 -->
<rabbit:direct-exchange name="test-mq-exchange" durable="false" auto-delete="false" id="test-mq-exchange">
<rabbit:bindings>
<rabbit:binding queue="test_queue" key="test_queue_key"/>
</rabbit:bindings>
</rabbit:direct-exchange> <rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto" message-converter="jsonMessageConverter">
<rabbit:listener ref="mqListener" queues="test_queue" />
</rabbit:listener-container> <!-- 消息对象json转换类 -->
<bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter" />
</beans>

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

rabbitmq.propreties:

mq.host=127.0.0.1
mq.username=tzc
mq.password=tzc
mq.port=5672

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

定义MQ消息传输类和生产消费者:

MqMessage:

package com.mop.self.mq;

/**
* Author: Mr.tan
* Date: 2017/08/04
* Description:MQ消息封装
*/
public class MqMessage { private String id;
private String name; public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "MqMessage{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

MqProducer:

package com.mop.self.mq;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* Author: Mr.tan
* Date: 2017/08/04
* Description: MQ生产者
*/
@Component
public class MqProducer { private static final Logger logger = LoggerFactory.getLogger(MqProducer.class); @Autowired
private AmqpTemplate amqpTemplate; public void sendMessage(Object message){
logger.debug("produce message:"+message);
amqpTemplate.convertAndSend("test_queue_key",message);
}
}

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

MqListener:

package com.mop.self.mq;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.stereotype.Component; /**
* Author: Mr.tan
* Date: 2017/08/04
* Description: mq监听
*/
@Component
public class MqListener implements MessageListener { private static final Logger logger = LoggerFactory.getLogger(MqListener.class); public void onMessage(Message message) {
MqMessage mqMessage = JSON.parseObject(new String(message.getBody()),MqMessage.class);
logger.debug("get message:"+mqMessage);
} }

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

测试代码:

package com.mop.self.mq;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; /**
* Author: Mr.tan
* Date: 2017/08/04
* Description:
*/
public class MqTest { public static void main(String[]args){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:conf/spring-core.xml");
MqProducer mqProducer = (MqProducer) applicationContext.getBean("mqProducer");
MqMessage mqMessage = new MqMessage();
mqMessage.setId("34");
mqMessage.setName("测试");
mqProducer.sendMessage(mqMessage);
}
}

aaarticlea/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" alt="点击并拖拽以移动">

执行方法,生产者发送消息,监听获取到消息并打印

RabbitMQ:从零开始的更多相关文章

  1. 85. Spring Boot集成RabbitMQ【从零开始学Spring Boot】

    这一节我们介绍下Spring Boot整合RabbitMQ,对于RabbitMQ这里不过多的介绍,大家可以参考网络上的资源进行安装配置,本节重点是告诉大家如何在Spring Boot中使用Rabbit ...

  2. 从零开始学 Java - Spring 集成 ActiveMQ 配置(一)

    你家小区下面有没有快递柜 近两年来,我们收取快递的方式好像变了,变得我们其实并不需要见到快递小哥也能拿到自己的快递了.对,我说的就是类似快递柜.菜鸟驿站这类的代收点的出现,把我们原来快递小哥必须拿着快 ...

  3. CentOS 7 安装RabbitMQ 3.3

    1.安装erlang 语言环境 安装依赖文件 #yum install ncurses-devel 进入 http://www.erlang.org/download.html 选择源文件下载 wge ...

  4. CentOS7 安装RabbitMQ

    第一.下载erlang和rabbitmq-server的rpm: http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.centos. ...

  5. [从零开始搭网站七]CentOS上安装Mysql

    点击下面连接查看从零开始搭网站全系列 从零开始搭网站 通过前面6章,我们买好了服务器,配置了服务器连接,服务器上配置了JDK和Tomcat,准备了域名(这个我没教,自己去阿里/百度/腾讯买,买东西我相 ...

  6. Linux(centOS6.5)安装RabbitMQ

    第一.下载erlang和rabbitmq-server的rpm: wget  http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.c ...

  7. Python爬虫系列(一):从零开始,安装环境

    在上一个系列,我们学会使用rabbitmq.本来接着是把公司的celery分享出来,但是定睛一看,celery4.0已经不再支持Windows.公司也逐步放弃了服役多年的celery项目.恰好,公司找 ...

  8. 【从零开始撸一个App】Kotlin

    工欲善其事必先利其器.像我们从零开始撸一个App的话,选择最合适的语言是首要任务.如果你跟我一样对Java蹒跚的步态和僵硬的语法颇感无奈,那么Kotlin在很大程度上不会令你失望.虽然为了符合JVM规 ...

  9. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

随机推荐

  1. 横纵方向走马灯滚动,纯javascript代码

    <body onload="beginmarquee()"> <table width="1024" border="0" ...

  2. Laravel 事务中使用悲观锁

    laravel 提供了方便快捷的数据库事务使用方式,在使用中遇到过几个容易混淆和被误导的地方,这里做个记录,希望哪里写的不对的地方各位大神指点一下 laravel 事务分为手动方式和自动方式. 但如果 ...

  3. mysql内连接(inner join 找两个表的交集)、左连接(left join 交集并且左表所有)、右连接(right join 交集并且右表所有)、全连接(mysql不支持)

    用两个表(a_table.b_table),关联字段a_table.a_id和b_table.b_id来演示一下MySQL的内连接.外连接( 左(外)连接.右(外)连接.全(外)连接). MySQL版 ...

  4. 在 IntelliJ IDEA 中这样使用 Git,效率提升2倍以上

    1.Git简介 Git是目前流行的分布式版本管理系统.它拥有两套版本库,本地库和远程库,在不进行合并和删除之类的操作时这两套版本库互不影响.也因此其近乎所有的操作都是本地执行,所以在断网的情况下任然可 ...

  5. 版本控制系统之SVN和GIT的区别

    版本控制器的作用: 1. 可以协同代码管理,让多人开发代码得以实现. 2. 回归到以前的任何一个时间点的代码处(好比:开始写了很多代码,后面有修改了一些,突然IDE崩溃,但是发现还是以前的代码更好,这 ...

  6. UVa 699 The Falling Leaves (树水题)

    Each year, fall in the North Central region is accompanied by the brilliant colors of the leaves on ...

  7. svn服务端安装、迁移教程、Eclipse切换svn连接库

    svn服务端安装.迁移教程.Eclipse切换svn连接库 安装教程 1.下载svn服务端安装程序 2.运行程序,安装 这里端口号根据自己定义,避免与其他程序冲突 到这里就安装完成 迁移教程 这里举例 ...

  8. vuedraggable 实现拖动数据改变

    // 引入组件 import Draggable from 'vuedraggable' // 使用组件 DragList 为自己封装好的组件 注意一定要使用vue的sync <DragList ...

  9. “void * __cdecl operator new(unsigned int)”(??2@YAPAXI@Z) already defined in LIBCMTD.lib(new.obj)

    转自VC错误:http://www.vcerror.com/?p=1377 问题描述: 当 C 运行时 (CRT) 库和 Microsoft 基础类 (MFC) 库的链接顺序有误时,可能会出现以下 L ...

  10. sql审核工具调研安装-sqlAdvisor和soar

    sql审核工具调研  基于soar的sql审核查询平台: https://github.com/beiketianzhuang/data-platform-soar 1.美团工具sqlAdvisor工 ...