1.消息持久化
服务器会把消息写到磁盘上,性能最高可以达到10倍,一般正常运行也会达到三四倍
2.消息确认
订阅队列时,no-ack设置为true,那么处理完消息之后就无须再发送确认消息回服务器,这样就能极大加快消费者消费消息的速度
3.路由算法和绑定规则
在服务器端,交换器和绑定作为记录存储在Mnesia,会将这些信息复制到集群其他节点,基于ETS(Erlang Term Storage Erlang数据存储基于内存)和DETS表(基于磁盘的存储方案),ETS表的访问时间与数据库条目成对数关系,RabbitMQ路由表是由Mnesia提供一致性,而由普通ETS来提供数据查询速度的保证
direct和fanout交换器的区别在于后者忽略了路由键
topic交换器是RabbitMQ实现了trie数据结构(多叉树结构,详情请看http://dongxicheng.org/structure/trietree/)用来存储绑定路由模式以支持快速查询,在2.3GHZ的机器上,11秒左右的时间针对2000个模式匹配1000000个topic,但是他的绑定比direct和fanout交换器占用更多内存
4.消息投递
消息不持久化的情况下内存不足时,会将消息写入到磁盘存储到瞬态存储中,持久化的情况下会写入到磁盘和内存,如果内存不足则刷出磁盘,表现为消费者开始滞后,队列被填满,某一段时间之后服务器收到内存警告然后刷出到磁盘
消息投递流程如下图所示:
持久化队列绑定到持久化的交换器占用234个字 1872个字节(64位OS)
x:代表向哪些表添加记录
队列元数据:
rabbit_queue | rabbit_durable_queue | |
持久化队列 | x | x |
瞬时队列 | x | |
字/记录 | 29 | 29 |
交换器元数据:
rabbit_exchange | rabbit_durable_exchange | |
持久化交换器 | x | x |
瞬时交换器 | x | |
字/记录 | 29 | 29 |
绑定元数据:
rabbit_route(绑定信息) | rabbit_durable_ route(绑定信息) | rabbit_semi_durable_ route(瞬时记录) | Rabbit_reverse_route(绑定信息) | |
持久化队列绑定到 持久化交换器 |
x | x | x | x |
持久化队列绑定到 瞬时交换器 |
x | x | x | |
瞬时队列绑定到 瞬时交换器 |
x | x | ||
瞬时队列绑定到 持久化交换器 |
x | x | ||
字/记录 | 44 | 44 | 44 | 44 |
Erlang进程数默认设置是每个节点2^20=1048576,而进程数是通过以下事件增加:
到服务器的连接、创建新的信道和队列声明
进程数 | |
新建连接 | 4 |
新建信道 | 4 |
队列声明 | 1 |
使用rmqca作为RabbitMQ的认证中心,certs文件用于存放CA产生的证书,private存放CA的密钥,改变其权限不允许第三方访问,serial存放CA证书的序列号,index.txt存放CA颁发的证书
# mkdir rmqca # cd rmqca # mkdir certs private # chmod 700 private # echo 01 > serial # touch index.txt
[ ca ] default_ca = rmqca [rmqca] dir = . certificate = $dir/cacert.pem database = $dir/index.txt new_certs_dir = $dir/certs private_key = $dir/private/cakey.pem serial = $dir/serial default_crl_days = 7 default_days = 365 default_md = sha1 policy = rmqca _policy x509_extensions = certificate_extensions [ rmqca _policy ] commonName = supplied stateOrProvinceName = optional countryName = optional emailAddress = optional organizationName = optional organizationalUnitName = optional [ certificate_extensions ] basicConstraints = CA:false [ req ] default_bits = 2048 default_keyfile = ./private/cakey.pem default_md = sha1 prompt = yes distinguished_name = root_ca_distinguished_name x509_extensions = root_ca_extensions [ root_ca_distinguished_name ] commonName = hostname [ root_ca_extensions ] basicConstraints = CA:true keyUsage = keyCertSign, cRLSign [ client_ca_extensions ] basicConstraints = CA:false keyUsage = digitalSignature extendedKeyUsage = 1.3.6.1.5.5.7.3.2 [ server_ca_extensions ] basicConstraints = CA:false keyUsage = keyEncipherment extendedKeyUsage = 1.3.6.1.5.5.7.3.1
[ ca ]
是ca的名称设置,
[rmqca]
设置CA颁发证书和密钥存放路径以及过期时间 (365天),每隔7天提供一个CRL文件,并且使用shal作为哈希函数生成证书;
[ rmqca _policy ]
告诉openssl在证书中哪些是必填项,supplied为必选,optional为可选
[ certificate_extensions ]
false值代表CA不能将自己作为CA----无法用于签名和颁发新证书
[ req ]
指明书生成2048位的密钥,密钥安全方面来说这是最小的数字,,密钥被写入private下的cakey.pem文件,默认使用shal作为默认的哈希函数
[ root_ca_extensions ]
根扩展用于签名其他证书
[ client_ca_extensions ]
用于客户端的证书认证
[ server_ca_extensions ]
用于加密数据以及认证服务器
# openssl req -x509 -config openssl.cnf -newkey rsa:2048 -days 365 \ -out cacert.pem -outform PEM -subj /CN=MyRmqca/ -nodes # openssl x509 -in cacert.pem -out cacert.cer -outform DER
生成RSA密钥然后为其提供证书
# cd .. # ls rmqca # mkdir server # cd server # openssl genrsa -out key.pem 2048 # openssl req -new -key key.pem -out req.pem -outform PEM \ -subj /CN=$(hostname)/O=server/ -nodes # cd ../rmqca # openssl ca -config openssl.cnf -in ../server/req.pem -out \ ../server/cert.pem -notext -batch -extensions server_ca_extensions # cd ../server # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword
生成RSA密钥然后为其提供证书
# cd .. # ls rmqca # mkdir server # cd server # openssl genrsa -out key.pem 2048 # openssl req -new -key key.pem -out req.pem -outform PEM \ -subj /CN=$(hostname)/O=server/ -nodes # cd ../rmqca # openssl ca -config openssl.cnf -in ../server/req.pem -out \ ../server/cert.pem -notext -batch -extensions server_ca_extensions # cd ../server # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword
这样就生成了三份证书,此时serial已经变为03,index.txt也列出了你颁发过的证书
为方便,将生成的目录拷贝到/etc/rabbitmq/ssl下
# cp -r rmqca /etc/rabbitmq/ssl # cp -r server /etc/rabbitmq/ssl # cp -r client /etc/rabbitmq/ssl
启用:
# vim rabbitmq.config [ {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]}, {rabbit, [ {tcp_listeners, [5672]}, {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/etc/rabbitmq/ssl/rmqca/cacert.pem"}, {certfile,"/etc/rabbitmq/ssl/server/cert.pem"}, {keyfile,"/etc/rabbitmq/ssl/server/key.pem"}, {verify, verify_peer}, {fail_if_no_peer_cert, true}, {versions, ['tlsv1.2', 'tlsv1.1']} ]} ]} ].
这样就可以支持普通连接和ssl连接,端口分别为5672和5671
重启rabbitmq服务即可看到已经监听5671端口
将连接服务器所需要的证书导入到密钥库中
# keytool -import -alias server1 -file /etc/rabbitmq/ssl/server/cert.pem -keystore /etc/rabbitmq/ssl/rabbitstore
util类:
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.*; import java.security.cert.CertificateException; public class RabbitMQUtils extends ConnectionFactory{ /** * 使用我们的密钥存储密钥管理器和信任管理器 * @return */ public static SSLContext getSSLContext(){ char[] keyPassphrase = "MySecretPassword".toCharArray(); KeyStore ks = null; SSLContext c = null; try { //通过PKCS12的证书格式得到密钥对象 ks = KeyStore.getInstance("PKCS12");//交换数字证书的标准 //keycert.p12包含客户端的证书和key String clientName = "F:" + File.separator + "git-root" + File.separator + "keycert.p12"; ks.load(new FileInputStream(clientName), keyPassphrase); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, keyPassphrase); char[] trustPassphrase = "rabbitlzq".toCharArray(); KeyStore tks = KeyStore.getInstance("JKS"); //用CA为服务器提供证书,若想连接服务器则将他添加到密钥库中 String storeName = "F:" + File.separator + "git-root" + File.separator + "rabbitstore"; tks.load(new FileInputStream(storeName), trustPassphrase); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(tks); c = SSLContext.getInstance("TLSv1.1"); c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); } catch (KeyStoreException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return c; } }
SSLConnection:
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.GetResponse; import com.rayootech.rabbitmq.demo.inaction.utils.RabbitMQUtils; import javax.net.ssl.SSLContext; public class SSLConnection { public static void main(String[] args) throws Exception { SSLContext c = RabbitMQUtils.getSSLContext(); ConnectionFactory factory = new ConnectionFactory(); factory.setHost("192.168.111.128"); factory.setPort(5671); factory.setUsername("admin"); factory.setPassword("admin"); //使用SSL连接服务器 factory.useSslProtocol(c); Connection conn = factory.newConnection(); Channel channel = conn.createChannel(); channel.queueDeclare("rabbitmq-java-test", false, true, true, null); channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes()); GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false); if (chResponse == null) { System.out.println("No message retrieved"); } else { byte[] body = chResponse.getBody(); System.out.println("Recieved: " + new String(body)); } channel.close(); conn.close(); } }