扣扣技术分享交流群:1125844267
Federation插件的最高目标是在节点间交换信息而不需要集群,这是使用这个插件最大的原因。
这个插件允许绑定虚拟主机和队列,一个绑定的虚拟主机和队列可以接收一个或多个上游(upstreams)的信息。一个绑定的交换机可以过滤上游发布的信息到本地。一个绑定的队列可以让一个本地的消费者接收上游队列的信息。
1、这个插件可以交换节点或者集群间的信息;
2、可以有不同的用户和虚拟主机;
3、可以是不同版本的RabbitMQ和Erlang;
4、插件使用AMQP协议,可以容忍网络的间歇性连接;
5、扩展性很好,不会限制节点个数;
安装socat:yum install socat;再安装erlang:yum install erlang;最后:yum install rabbitmq-server。同时也不用复制配置文件,/etc下会自己创建,默认安装的是最新版。
rabbitmq-plugins enable rabbitmq_federation(直接执行命令)
rabbitmq-plugins enable rabbitmq_federation_management(直接执行命令)
启动rabbitMQ之后,管理平台会多出这两个配置选项:
systemctl start rabbitmq-server
服务器32的mq上添加新用户test,另外一个服务器114的mq配置用户test1。注意用户的tags,选择administrator,否则无法用新建用户登录管理平台,那么也就无法用管理平台来配置federation了,相对来说,管理平台来配置还是要直观简单一些。
1、两个mq需要配置相同名称的虚拟主机(virtualForTestExchange),同时将虚拟主机与我们新创建的用户绑定。刚开始虚拟主机名称不一样,无法同步数据,状态总是报错。所以,为了确保正确运行,两个mq的虚拟主机名称、交换机名称、队列名称、Federation Upstreams名称、Policies名称都保持一致。
2、创建相同名称的交换机(exchangeForTest)
3、创建相同名称的队列(queueForTestExchange)
4、将队列绑定到交换机上
注意:一个交换机可以绑定多个队列,每次绑定指定具体Routing key(路由键),消费者消费的时候通过指定具体的Routing key来确定消费哪个队列中的数据。当然,同一个队列中的数据只可以消费一次,之前的理解有误,以为发布/订阅模式下不管是不是同一个队列都可以消费多次,这是不对的。发布/订阅是交换机相对于不同的队列来说的,不同的队列可以获取到同一个交换机相同的数据,但是同一个队列中的数据只能消费一次。
5、创建Federation Upstreams
创建Federation Upstreams,这个其实可以是单向的,也可以是双向的。所以,可以一个mq配置,也可以两个都配置。如果是32上配置的话,其实是同步114上的数据,将114当做是上游,而114无法同步32的数据。
Expires:过期时间,如果长时间未与上游连接,到了过期时间后,上游的队列会被删除,不填的话为永远不删除;
Message TTL:消息过期时间,如果长时间未取上游的信息,会造成上游队列信息堆积,设置过期时间后,队列中的信息会被清理,避免信息堆积。不填的话为永远不删除;
注意:因为是在32上配置的,会去拉取114上的数据,所以这个URI是指向114的连接amqp://test1(mq新创建的用户名):test1(密码)@114的ip地址:5672
6、配置Policies(policyForTestExchange)
一开始以为这个不是必须的,但是不配还不行。这是一个配置的例子,不是我们实际配置的内容。
这是实际配置好的:
Pattern:正则匹配交换机;
Apply to:可以根据自己的配置选择,这个可以只选Exchanges;
Definition:这个还是个必填项,federation-upstream-set=all,也可以针对具体上游名称的单个upstream
配置方式和配置有交换机的类似,只是不再配置交换机了,不再记录具体配置过程。
翻译:联邦交换机的方式会复制上游的消息到一个或者多个下游,联邦队列则是消费者在哪儿数据就移动到哪儿,经常是偏向本地的消费者(就近原则)。联邦队列被认为是平等的,它不像联邦交换机那样有“领导者”和“跟随者”。
* 刚刚配置完的时候,分别启动两个消费者去消费两个mq联邦队列的消息,本以为会和有交换机一样,两个消费者都会收到消息,但实际是给哪个队列发的消息,哪个消费者就收到了消息,另外一个没有收到。那么从上边官方文档我们可以看出,它遵循就近原
则,其实就是一个队列,一条消息只能消费一次,虽然配置了federation。那么把收到消息的消费者停掉后再发消息,另外一个消费者就能收到消息了。
通过java代码获取rabbitmq的一些参数,其实最简单的办法就是利用web管理页面的请求地址,java通过后台http请求来获取页面参数。同时也可以点击队列下的HTTP API来查看官方给定的api。需要注意的是,后端请求需要basic验证。
例如获取指定队列的深度(因为是模拟管理平台请求,所以端口是15672,官方给定的api文档也是15672)
我们可以在管理平台中点击进queue页面,java模拟这个请求http://ip:15672/api/queues/虚拟主机名称/队列名称,那么可以请求到页面的数据,也就可以获取到例如Total、Ready这些数据。
public static void main(String[] args) throws IOException {
String url = "http://ip:15672/api/queues/虚拟主机名称/队列名称";
String userName = "xxx";
String password = "xxx";
getMQQueueDeepth(url,userName,password);
}
public static CloseableHttpClient getBasicHttpClient(String username,String password){
CredentialsProvider provider = new BasicCredentialsProvider();
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM);
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username,password);
provider.setCredentials(scope, credentials);
httpClientBuilder.setDefaultCredentialsProvider(provider);
return httpClientBuilder.build();
}
/**
* 获取当前指定队列的深度
* 获取当前指定队列的消费者个数
* @param url
* @param username
* @param password
* @return
*/
private static void getMQQueueDeepth(String url,String username,String password) {
HttpGet httpGet = new HttpGet(url);
CloseableHttpClient client = getBasicHttpClient(username,password);
CloseableHttpResponse response = null;
Long queueDeepth = 0L;
Integer queueCustomers = 0;
try {
response = client.execute(httpGet);
int state = response.getStatusLine().getStatusCode();
if (state == HttpStatus.SC_OK) { //等于200
InputStream inputStream = response.getEntity().getContent();
InputStreamReader isr = new InputStreamReader(inputStream);
char[] buffer = new char[1024];
StringBuilder sb = new StringBuilder();
int length = 0;
while ((length = isr.read(buffer)) > 0) {
sb.append(buffer, 0, length);
}
String contentStr = sb.toString();
System.out.println("返回的内容:" + contentStr);
JSONObject contentJson = JSON.parseObject(contentStr);
queueDeepth = contentJson.get("messages") == null ? 0 : Long.valueOf(String.valueOf(contentJson.get("messages")));
queueCustomers = contentJson.get("consumers") == null ? 0 : Integer.valueOf(String.valueOf(contentJson.get("consumers")));
System.out.println("指定队列的深度:"+queueDeepth);
System.out.println("指定队列的消费者:"+queueCustomers);
} else {
System.out.println("请求返回的状态值:" + state);
}
}catch (Exception e){
e.printStackTrace();
} finally {
closeAll(response,client);
}
}
private static void closeAll(CloseableHttpResponse response,CloseableHttpClient client){
try {
if(response != null){
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(client != null){
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}