RabbitMQ消息中间件
http://erlang.org/download/
安装步骤:
1.下载安装包
wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm
2.安装(为什么药选择rpm包安装?免得配置环境变量)
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
3.修改配置文件
配置文件:
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
比如修改密码、配置等等,例如:loopback_users 中的 <<“guest”>>,只保留guest
4.启动和停止
服务启动和停止:
启动 rabbitmq-server start &
停止 rabbitmqctl app_stop
命令:
修改服务器主机名:
查看有启动的端口号:
5.可视化的管理
管理插件:rabbitmq-plugins enable rabbitmq_management
访问地址:http://192.168.11.76:15672/
helloworld
package com.bfxy.rabbitmq.quickstart;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Procuder {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//4.通过channel发送数据
for (int i =0; i<5;i++){
String msg = "Hello RabbitMQ!";
//如果第一个参数不指定的话,就默认以你的routingkey去找交换机
channel.basicPublish("", "test001", null, msg.getBytes());
}
channel.close();
connection.close();
}
}
package com.bfxy.rabbitmq.quickstart;
import com.rabbitmq.client.*;
import com.rabbitmq.client.QueueingConsumer.Delivery;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//4.创建一个队列
String queueName = "test001";
channel.queueDeclare(queueName, true, false, false, null);
//5.创建一个消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//6.设置channel
//true表示自动签收,false表示手工签收 --回馈服务器
channel.basicConsume(queueName, true,queueingConsumer);
//获取消息 envelope很关键
while (true){
Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.err.println("消费端: "+msg);
Envelope envelope = delivery.getEnvelope();
//envelope.getDeliveryTag();
}
}
}
###消息确认代码
producer:
package com.bfxy.rabbitmq.api.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//指定我们的消息投递模式:消息的确认模式
channel.confirmSelect();
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.save";
//5.消息的发送
String msg = "Hello RabbitMQ Send confirm message";
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
//6.添加一个确认监听
channel.addConfirmListener(new ConfirmListener() {
//成功时进入这个方法
// deliveryTag:关键的唯一的这条消息的标签,会根据这个参数去确认消息投递是否成功
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("------handleAck");
}
//失败时会进入这个方法:磁盘写满、MQ出现异常、queue到达上限
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("------handleNack");
}
});
}
}
consumer:
package com.bfxy.rabbitmq.api.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//4.生命交换机和队列 然后进行绑定个设置,最后指定路由Key
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.#";//#:只能匹配一个单词 *:能匹配多个单词
String queueName = "test_confirm_queue";
//申明一个exchange durable是否持久化
channel.exchangeDeclare(exchangeName, "topic",false);
//申明一个队列 各个参数的理解?
channel.queueDeclare(queueName, true, false, false, null);
//将queue和exchange进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
//5.创建消费者
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
//消费者的消费模式
channel.basicConsume(queueName, queueingConsumer);
while (true) {
QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println(msg);
}
}
}
producer:
package com.bfxy.rabbitmq.api.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//指定我们的消息投递模式:消息的确认模式
channel.confirmSelect();
String exchangeName = "test_consumer_exchange";
String routingKey = "consumer.save";
//5.消息的发送
String msg = "Hello RabbitMQ Send consumer message";
//mandatory == true 在服务器端接收到没有路由到的消息不会删除,会返回给生产端,false会删除
for (int i =0 ;i<5;i++){
channel.basicPublish(exchangeName, routingKey, true,null, msg.getBytes());
}
}
}
consumer:
package com.bfxy.rabbitmq.api.consumer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//4.生命交换机和队列 然后进行绑定个设置,最后指定路由Key
String exchangeName = "test_consumer_exchange";
String routingKey = "consumer.#";//#:只能匹配一个单词 *:能匹配多个单词
String queueName = "test_consumer_queue";
//申明一个exchange durable是否持久化
channel.exchangeDeclare(exchangeName, "topic",true,false,null);
//申明一个队列 各个参数的理解?
channel.queueDeclare(queueName, true, false, false, null);
//将queue和exchange进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
//5.创建消费者
channel.basicConsume(queueName, new MyConsumer(channel));
}
}
MyConsumer:
package com.bfxy.rabbitmq.api.consumer;
import com.rabbitmq.client.*;
import com.rabbitmq.client.Consumer;
public class MyConsumer extends DefaultConsumer {
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
public MyConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body){
System.err.println("-----consumerTag-----:"+consumerTag);
System.err.println("-----envelope-----:"+envelope);
System.err.println("-----properties-----:"+properties);
System.err.println("-----body-----:"+new String(body));
}
}
prefetchSize:表示消息限制的大小
prefetchCount:一次消费多少条
工作中一定要注意:自动签收一定要设置为false
producer:
package com.bfxy.rabbitmq.api.limit;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//指定我们的消息投递模式:消息的确认模式
channel.confirmSelect();
String exchangeName = "test_qos_exchange";
String routingKey = "qos.save";
//5.消息的发送
String msg = "Hello Rabbi tMQ Send qos message";
//mandatory == true 在服务器端接收到没有路由到的消息不会删除,会返回给生产端,false会删除
for (int i =0 ;i<5;i++){
channel.basicPublish(exchangeName, routingKey, true,null, msg.getBytes());
}
}
}
package com.bfxy.rabbitmq.api.limit;
import com.bfxy.rabbitmq.api.limit.MyConsumer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//4.生命交换机和队列 然后进行绑定个设置,最后指定路由Key
String exchangeName = "test_qos_exchange";
String routingKey = "qos.#";//#:只能匹配一个单词 *:能匹配多个单词
String queueName = "test_qos_queue";
//申明一个exchange durable是否持久化
channel.exchangeDeclare(exchangeName, "topic",true,false,null);
//申明一个队列 各个参数的理解?
channel.queueDeclare(queueName, true, false, false, null);
//将queue和exchange进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
//5.创建消费者
//要做限流,怎么办?
// 1:autoAck必须为false,关闭自动签收的机制
// 2:使用basicQos限流接收prefetchSize:对消息的大小进行限制,prefetchCount:消费端一次过来多少条?, boolean global
channel.basicQos(0,1,false);
//6.创建消费者
channel.basicConsume(queueName,false,new MyConsumer(channel));
}
}
package com.bfxy.rabbitmq.api.limit;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
public class MyConsumer extends DefaultConsumer {
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
//ack是使用channel的,所以要接收下
private Channel channel;
public MyConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.err.println("-----consumerTag-----:"+consumerTag);
System.err.println("-----envelope-----:"+envelope);
System.err.println("-----properties-----:"+properties);
System.err.println("-----body-----:"+new String(body));
//这时会回送一个消息,告诉block已处理好消息
channel.basicAck(envelope.getDeliveryTag(),false);
//这边必须得做ACK
}
}
producer:
package com.bfxy.rabbitmq.api.ack;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//指定我们的消息投递模式:消息的确认模式
channel.confirmSelect();
String exchangeName = "test_ack_exchange";
String routingKey = "ack.save";
//5.消息的发送
//mandatory == true 在服务器端接收到没有路由到的消息不会删除,会返回给生产端,false会删除
for (int i =0 ;i<5;i++){
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("num", i);
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.headers(headers)
.build();
String msg = "Hello RabbitMQ ACK Message " + i;
channel.basicPublish(exchangeName, routingKey, true, properties, msg.getBytes());
}
}
}
consumer
package com.bfxy.rabbitmq.api.ack;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.bfxy.rabbitmq.api.ack.MyConsumer;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//1.创建一个ConnecttionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("47.98.106.189");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
//2.通过连接工程创建连接
Connection connection = connectionFactory.newConnection();
//3.通过connection创建一个channel
Channel channel = connection.createChannel();
//4.生命交换机和队列 然后进行绑定个设置,最后指定路由Key
String exchangeName = "test_ack_exchange";
String routingKey = "ack.#";//#:只能匹配一个单词 *:能匹配多个单词
String queueName = "test_ack_queue";
//申明一个exchange durable是否持久化
channel.exchangeDeclare(exchangeName, "topic",true,false,null);
//申明一个队列 各个参数的理解?
channel.queueDeclare(queueName, true, false, false, null);
//将queue和exchange进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
//5.创建消费者
channel.basicConsume(queueName, false,new MyConsumer(channel));
}
}
在这里插入代码片
myconsumer:
package com.bfxy.rabbitmq.api.ack;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
public class MyConsumer extends DefaultConsumer {
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
//ack是使用channel的,所以要接收下
private Channel channel;
public MyConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.err.println("-----------consume message----------");
System.err.println("body: " + new String(body));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if((Integer)properties.getHeaders().get("num") == 0) {
//requeue表示重回队列
channel.basicNack(envelope.getDeliveryTag(), false, true);
} else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
}
注意:死信队列很重要
在spring通过Bean中注入RabbitMq
package com.bfxy.spring;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.bfxy.spring"})
public class RabbitMQConfig {
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses("47.98.106.189:5672");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
return connectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
}
申明交换机、队列、将两者进行绑定
package com.bfxy.spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private RabbitAdmin rabbitAdmin;
@Test
public void testAdmin(){
//1.申明交换机
//交换机的类型申明,通过springframework的API来申明的new DirectExchange、TopicExchange
//非持久化的话,服务重启后会被清除掉
rabbitAdmin.declareExchange(new DirectExchange("test.direct",false,false));
rabbitAdmin.declareExchange(new TopicExchange("test.topic",false,false));
rabbitAdmin.declareExchange(new FanoutExchange("test.fanout",false,false));
//2.申明队列
rabbitAdmin.declareQueue(new Queue("test.direct.queue", false));
rabbitAdmin.declareQueue(new Queue("test.topic.queue", false));
rabbitAdmin.declareQueue(new Queue("test.fanout.queue", false));
//2.绑定交换机和队列
//destination绑定的队列是什么
//destinationType要绑定的类型时什么?有个对象的
//exchange
//routingKey
//arguments
rabbitAdmin.declareBinding(new Binding("test.direct.queue",
Binding.DestinationType.QUEUE,
"test.direct",
"test.direct",
new HashMap<>()));
//采用BindingBuilder的链式申明交换机和队列绑定方式
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("test.topic.queue", false))//建立队列
.to(new TopicExchange("test.topic",false,false))//建立关联关系
.with("user.#"));//指定路由key
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("test.fanout.queue", false))//建立队列
.to(new FanoutExchange("test.fanout",false,false)));//建立关联关系
//无.with("user.#"));不走路由key
//清除某个队列
rabbitAdmin.purgeQueue("test.topic.queue",false);
//删除某个队列
}
}
@Bean
public TopicExchange exchange001() {
return new TopicExchange("topic001", true, false);
}
@Bean
public Queue queue001() {
return new Queue("queue001", true); //队列持久
}
@Bean
public Binding binding001() {
return BindingBuilder.bind(queue001()).to(exchange001()).with("spring.*");
}
发送消息的三种方式:
1:
2:
3:
@Test
public void testSendMessage() throws Exception {
//1 创建消息
MessageProperties messageProperties = new MessageProperties();
messageProperties.getHeaders().put("desc", "信息描述..");
messageProperties.getHeaders().put("type", "自定义消息类型..");
Message message = new Message("Hello RabbitMQ".getBytes(), messageProperties);
rabbitTemplate.convertAndSend("topic001", "spring.amqp", message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
System.err.println("------添加额外的设置---------");
message.getMessageProperties().getHeaders().put("desc", "额外修改的信息描述");
message.getMessageProperties().getHeaders().put("attr", "额外新加的属性");
return message;
}
});
}
SimpleMessageListenerContainer 它就是一个消费者监听容器
@Bean
public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//放入一些实际的队列
container.setQueues(queue001(), queue002(), queue003(), queue_image(), queue_pdf());
//当前的消费者数量
container.setConcurrentConsumers(1);
//最大的时候
container.setMaxConcurrentConsumers(5);
//是否重回队列
container.setDefaultRequeueRejected(false);
//签收模式
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
//是否y漏?
container.setExposeListenerChannel(true);
//消费端的标签策略
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString();
}
});
//消息的监听
container.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
String msg = new String(message.getBody());
System.err.println("----------消费者: " + msg);
}
});
return container;
}
//适配器方式. 默认是有自己的方法名字的:handleMessage
// 可以自己指定一个方法的名字: consumeMessage
// 也可以添加一个转换器: 从字节数组转换为String
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
//修改默认的接收函数
adapter.setDefaultListenerMethod("consumeMessage");
//消息的转换
adapter.setMessageConverter(new TextMessageConverter());
container.setMessageListener(adapter);
主备在中小型企业用的比较多
感觉下面的做了解就好了
下面的要重点了解,互联网大厂几乎都是使用这种的模式
异地多活一定是使用下面的多活模式
set化架构要重点理解
单个集群 ->同城双活->两地三中心->set化架构
为了解决什么问题呢?
1.解决业务(容灾)和通用性的两个重大问题