随便写点什么,巩固一下自己学习到的知识.
1.安装过程中的槽点
博主的安装过程仅供参考,正式还是以官网为准,因为RabbitMQ是需要先安装Erlang的,有的博主的安装方法这2者的版本根本匹配不了,导致报一些很奇怪的错误.比如说这种:init terminating in do_boot",noproc.按照官网的方法rpm --import 之后 yum list | grep rabbitmq yum install rabbitmq-server.noarch 3.7.9-1.el6就顺利安装好了rabbit'MQ.之后设置开机自启:chkconfig rabbitmq-server on 然后启动它/sbin/service rabbitmq-server start 再开启界面管理 /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.9/plugins/ rabbitmq-plugins enable rabbitmq_management 再然后是添加一名具有访问权限的用户.
访问ip:15672试图进入管理端界面的时候一直提示404,这种情况是防火墙的因素,放开就好了,还有另一种方法cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config 生成一份rabbitmq的配置文件 然后打开它 修改下里面的内容.
//改了这里面的访问端口号和ip就可以了
{listener, [{port, 8081},
{ip, "0.0.0.0"},
{ssl, false}
%% {ssl_opts, [{cacertfile, "/path/to/cacert.pem"},
%% {certfile, "/path/to/cert.pem"},
%% {keyfile, "/path/to/key.pem"}]}
]}
注意里面的标点符号和大括号,不然很容易就报erlang的错误,rabbitmq也有对应的启动日志放在/var/log/rabbitmq身上.
既然我们已经顺利的安装好了RabbitMQ,那么我们就来好好的使用一下他.
RabbitMQ是什么?能干嘛?
首先它是一个开源的消息框架,基于AMQP协议,可以进行进程中的通信,支持集群(分普通和镜像2种 需要自己进行负载均衡的配置)和持久化,也有消息重试和确认机制. 能干嘛?1.消息通信 2.应用解耦 3.提高系统并发能力 系统都是以CAP(最终一致性(数据强一致性) 高可用(不会挂) 和分区容错性(部分节点挂了没关系))为目标的.
下面就来了解一下它的用法吧
首先是四个基本的概念 : 生产者 消费者 队列 交换机
然后是5种队列的用法
1.普通 就是消费者生产者通过队列1对1
配置连接
/**
* 获取一个RabbitMQ的Connection实例
*
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(host);
factory.setPort(Integer.parseInt(port));
factory.setUsername(userName);
factory.setPassword(password);
return factory.newConnection();
}
定义生产者
Connection connection = RabbitConfig.getConnection();
Channel channel = connection.createChannel();
/**
* 声明 创建队列
*
* queue: 队列名称
* durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
* exclusive:是否排外的,有两个作用,一:当连接关闭时connection.close()该队列是否会自动删除;二:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,如果强制访问会报异常:com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=405, reply-text=RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'queue_name' in vhost '/', class-id=50, method-id=20)一般等于true的话用于一个队列只能有一个消费者来消费的场景
* autoDelete:是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除
* arguments:队列中的消息什么时候会自动被删除?
* 参数:
* */
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
/**
* 发送消息
* var1:交换机名称 空的话就为默认的交换机
* var2:队列名称 或者匹配的队列
* var3: AMQP.BasicProperties 提供了一个构造器,可以通过builder() 来设置一些属性;
* AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder();
* * properties.deliveryMode(2); // 设置消息是否持久化,1: 非持久化 2:持久化
* var4:消息
*/
for (int i = 0; i < 100; i++) {
channel.basicPublish("", QUEUE_NAME, null, ("你是狗吗? " + i).getBytes());
}
channel.close();
connection.close();
}
定义消费者
Connection connection = RabbitConfig.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME1, false, false, false, null);
/**
* RabbitMQ 默认将消息顺序发送给下一个消费者,这样,每个消费者会得到相同数量的消息。即轮询(round-robin)分发消息。
* 怎样才能做到按照每个消费者的能力分配消息呢?联合使用 Qos 和 Acknowledge 就可以做到。
* basicQos 方法设置了当前信道最大预获取(prefetch)消息数量为1。消息从队列异步推送给消费者,消费者的 ack 也是异步发送给队列,
* 从队列的视角去看,总是会有一批消息已推送但尚未获得 ack 确认,Qos 的 prefetchCount 参数就是用来限制这批未确认消息数量的。设为1时,
* 队列只有在收到消费者发回的上一条消息 ack 确认后,才会向该消费者发送下一条消息。prefetchCount 的默认值为0,即没有限制,
* 队列会将所有消息尽快发给消费者。
*/
/**
* 轮询分发 :使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,
* 使得系统的伸缩性更加容易。在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。
* 平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。
*
* 公平分发 :虽然上面的分配法方式也还行,但是有个问题就是:比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。
* 按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。
* 而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。
*
* 为了解决这个问题,我们使用basicQos( prefetchCount = 1)方法,来限制RabbitMQ只发不超过1条的消息给同一个消费者。
* 当消息处理完毕后,有了反馈,才会进行第二次发送。
* 还有一点需要注意,使用公平分发,必须关闭自动应答,改为手动应答。
*/
channel.basicQos(1);
/**
* 把队列绑定到交换机上面
*/
channel.queueBind(QUEUE_NAME1, FanoutProduceOne.EXCHANGE_NAME, "");
/**
* 构建队列的一个消费者
* 监听队列,false表示手动返回完成状态,true表示自动
*
* 模式1:自动确认
* 只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
* 模式2:手动确认
* 消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。
*/
channel.basicConsume(QUEUE_NAME1, false, new DefaultConsumer(channel) {
/**
* @param consumerTag 消费者的标志
* @param envelope 信道 Delivery Tag 用来标识信道中投递的消息 可见,两个信道的 delivery tag 分别从 1 递增到 5。
* (如果修改代码,将两个 Consumer 共享同一个信道,则 delivery tag 是从 1 递增到 10
* @param properties
* @param body 收到的byte数组
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.printf("in consumer A (delivery tag is %d): %s\n", envelope.getDeliveryTag(), message);
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 消息的确认(是否批量)
* basicAck 方法的第二个参数 是否是批量确认
* multiple 取值为 false 时,表示通知 RabbitMQ 当前消息被确认;如果为 true,则额外将比第一个参数指定的 delivery tag 小的消息一并确认
*/
// 返回确认状态,注释掉表示使用自动确认模式
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
/**
* 测试消息未被消费 依旧留在队列中的情况
*/
// channel.queueDeclare(QUEUE_NAME1, false, false, false, null);
// channel.basicPublish("", QUEUE_NAME1, null, "你是狗吗1?".getBytes());
// channel.basicPublish("", QUEUE_NAME1, null, "你是狗吗2?".getBytes());
// channel.basicPublish("", QUEUE_NAME1, null, "你是狗吗3?".getBytes());
TimeUnit.MILLISECONDS.sleep(1000 * 600);
channel.close();
connection.close();
}
2.一个生产者多个消费者
这里注意下模式:公平分发还是轮询,不过注意一个消息只能被一个消费者获取和消息的自动批量确认机制
3.订阅模式
就是把消息发送到交换机,所有与交换机绑定的队列都会有消息,但注意注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费.
4.路由模式
就是前面的扩展,现在消息发生和队列绑定的时候需要key,key一致才会收到消息.
5.主题模式
队列绑定的时候是一个通配符,会匹配所有满足条件的消息.
下面展示一下springBoot里面的用法
@Configuration
public class RabbitConfig {
private static final String host = ConfigFactory.load().getString("spring.rabbitmq.host");
private static final String port = ConfigFactory.load().getString("spring.rabbitmq.port");
private static final String userName = ConfigFactory.load().getString("spring.rabbitmq.username");
private static final String password = ConfigFactory.load().getString("spring.rabbitmq.password");
@Bean
public Queue helloQueue() {
return new Queue("hello");
}
@Bean
public Queue worldQueue() {
return new Queue("world");
}
@Bean
public Queue MessageQueue1() {
return new Queue("topic.message1");
}
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("top_exchange_springboot");
}
/**
* 绑定交换机与队列
* with 是""的话就是所有的都能收到
*
* @return
*/
@Bean
public Binding bindExchangeMessageAll() {
return BindingBuilder.bind(worldQueue()).to(topicExchange()).with("2.*");
}
@Bean
public Binding bindExchangeMessage1() {
return BindingBuilder.bind(MessageQueue1()).to(topicExchange()).with("1.*");
}
}
生产者:
@Component
public class SpringBootSender {
@Autowired
private AmqpTemplate rabbitmqTemplate;
public void send() {
String context = LocalDateTime.now().toString() + " Hello";
rabbitmqTemplate.convertAndSend("hello", context);
}
public void send1() {
String context = "hi, i am message 11111-----1.2";
System.out.println("Sender : " + context);
rabbitmqTemplate.convertAndSend("top_exchange_springboot", "1.2", context);
}
public void send2() {
String context = "hi, i am messages 22222222222------3.2";
System.out.println("Sender : " + context);
rabbitmqTemplate.convertAndSend("top_exchange_springboot", "3.2", context);
}
}
消费者:
@Component //@RabbitListener(queues = "hello") @RabbitListener(queues = "world") public class SpringBootReceiveTwo { @Autowired private AmqpTemplate rabbitMqTemplate; @RabbitHandler void receive(String hello) throws InterruptedException { TimeUnit.MILLISECONDS.sleep(60); System.err.println("Receiver22222 : " + hello); } }
注意一些RabbitMQ的特殊配置要么在配置文件里面配置,要么用代码动态动态配置下.
spring:
rabbitmq:
listener:
simple:
prefetch: 1
publisher-confirms: true
-------暂时就学习到这里啦
后面可能会看看特性以及集群方面的吧 待完善