由於rabbitmq是基於 erlang語言開發,全部要先安裝erlangnode
這裏我下載的是19.2的版本,地址爲https://www.erlang.org/downloads/19.2linux
下載編譯安裝包:c++
cd /mnt wget http://erlang.org/download/otp_src_19.2.tar.gz
解縮web
tar -zxvf otp_src_19.2.tar.gz
安裝編譯依賴正則表達式
yum -y install gcc gcc-c++ autoconf automake zlib zlib-devel openssl openssl-devel pcre-devel ncurses-devel
編譯安裝瀏覽器
cd /mnt/otp_src_19.3 && ./configure --prefix=/opt/er1ang && make && make install
設置環境變量安全
修改 /etc/profile在最後添加以下代碼 ERLANG_HOME=/opt/erlang export PATH=$PATH:$ERLANG_HOME/bin export ERLANG_HOME source /etc/profile
進入官網選擇下載的版本服務器
http://www.rabbitmq.com/releases/rabbitmq-server/
我選擇的是3.6.10版本cookie
cd /opt
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-generic-unix-3.6.10.tar.xz
xz -d rabbitmq-server-generic-unix-3.6.10.tar.xz
tar -xvf rabbitmq-server-generic-unix-3.6.10.tar
mv rabbitmq-server-generic-unix-3.6.10 rabbitmq
設置環境變量app
編輯/etc/profile文件,在最後添加以下代碼 export PATH=$PATH:/opt/rabbitmq/sbin export RABBITMQ_HOME=/opt/rabbitmq source /etc/profile
運行rabbitmq
rabbitmq-server -detached
查看啓動狀態 rabbitmqctl status
[root@node-2 opt]# rabbitmqctl status
Status of node 'rabbit@node-2'
[{pid,27273},
{running_applications,
[{rabbit,"RabbitMQ","3.6.10"},
{mnesia,"MNESIA CXC 138 12","4.14.3"},
{ranch,"Socket acceptor pool for TCP protocols.","1.3.0"},
{ssl,"Erlang/OTP SSL application","8.1.1"},
{public_key,"Public key infrastructure","1.4"},
{crypto,"CRYPTO","3.7.3"},
{os_mon,"CPO CXC 138 46","2.4.2"},
{rabbit_common,
"Modules shared by rabbitmq-server and rabbitmq-erlang-client",
"3.6.10"},
{xmerl,"XML parser","1.3.13"},
{compiler,"ERTS CXC 138 10","7.0.4"},
{asn1,"The Erlang ASN1 compiler version 4.0.4","4.0.4"},
{syntax_tools,"Syntax tools","2.1.1"},
{sasl,"SASL CXC 138 11","3.0.3"},
{stdlib,"ERTS CXC 138 10","3.3"},
{kernel,"ERTS CXC 138 10","5.2"}]},
{os,{unix,linux}},
{erlang_version,
"Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:2:2] [async-threads:64] [hipe] [kernel-poll:true]\n"},
{memory,
[{total,49620264},
{connection_readers,0},
{connection_writers,0},
{connection_channels,0},
{connection_other,0},
{queue_procs,2832},
{queue_slave_procs,0},
{plugins,0},
{other_proc,17182792},
{mnesia,61040},
{metrics,184080},
{mgmt_db,0},
{msg_index,43528},
{other_ets,2088952},
{binary,49848},
{code,21385570},
{atom,891849},
{other_system,7911021}]},
{alarms,[]},
{listeners,[{clustering,25672,"::"},{amqp,5672,"::"}]},
{vm_memory_high_watermark,0.4},
{vm_memory_limit,779028070},
{disk_free_limit,50000000},
{disk_free,45861027840},
{file_descriptors,
[{total_limit,924},{total_used,2},{sockets_limit,829},{sockets_used,0}]},
{processes,[{limit,1048576},{used,152}]},
{run_queue,0},
{uptime,5047},
{kernel,{net_ticktime,60}}]
每個RabbitMQ 服務器都能建立虛擬的消息服務器,咱們稱之爲虛擬主機 (virtual host) , 簡稱爲 vhost 。每個 vhost 本質上是一個獨立的小型 RabbitMQ 服務器,擁有本身獨立的隊列、 交換器及綁定關係等,井且它擁有本身獨立的權限。 vhost 就像是虛擬機與物理服務器同樣,它 們在各個實例間提供邏輯上的分離,爲不一樣程序安全保密地運行數據,它既能將同 一個 RabbitMQ 中的衆多客戶區分開,又能夠避免隊列和交換器等命名衝突。 vhost 之間是絕對隔離 的,沒法將 vhostl 中的交換器與 vhost2 中的隊列進行綁定,這樣既保證了安全性,又能夠確保 可移植性。若是在使用 RabbitMQ 達到必定規模的時候,建議用戶對業務功能、場景進行歸類 區分,併爲之分配獨立的 vhost。
vhost是AMQP 概念的基礎,客戶端在鏈接的時候必須制定一個 vhost. RabbitMQ 默認創 建的 vhost爲 「/」, 若是不須要多個 vhost 或者對 vhost 的概念不是很理解,那麼用這個默認的 vhost 也是很是合理的,使用默認的用戶名 guest 和密碼 guest 就能夠訪問它。可是爲了安全和 方便,建議從新創建 個新的用戶來訪問它。
可使用 rabbitmqctl add vhost {vhost} 命令建立一個新的 vhost ,大括號裏的參 數表示 vhost 的名稱。
[root@node-2 opt]# rabbitmqctl add_vhost vhost Creating vhost "vhost"
查看vhosts列表
[root@node-2 opt]# rabbitmqctl list_vhosts Listing vhosts / vhost
對應的刪除 vhost 的命令是: rabbitmqctl delete_vhost {vhost) ,其中大括號裏 面的參數表示 vhost 的名稱。刪除一個 vhost 同時也會刪除其下全部的隊列、交換器、綁定關係、 用戶權限、參數和策略等信息。
[root@node-2 opt]# rabbitmqctl delete_vhost vhost Deleting vhost "vhost" [root@node-2 opt]# [root@node-2 opt]# rabbitmqctl list_vhosts Listing vhosts /
AMQP 協議中並無指定權限在 host 級別仍是在服務器級別實現,由具體的應用自定義。 在RabbitMQ 中,權限控制則是以 host 爲單位的 。當建立一個用戶時,用戶一般會被指派給至少一個 vhost ,而且只能訪問被指派的 vhost 內的隊列、交換器和綁定關係等。所以, RabbitMQ 中的授予權限是指在 vhost 級別對用戶而言的權限授予。
相關的授予權限命令爲: rabbitmqctl set permissions [-p vhost] {user) {conf) {write) {read) 。其 中各個參數的含義以下所述。 ~ vhost 授予用戶訪問權限的 host 名稱,能夠設置爲默認值,即vhosts爲 「/」
user:能夠訪問指定 vhost 的用戶名。
conf: 一個用於匹配用戶在哪些資源上擁有可配置權限的正則表達式。
write:一個用於匹配用戶在哪些資源上擁有可寫權限的正則表達式。
read:一個用於匹配用戶在哪些資源上擁有可讀權限的正則表達式
授予root用戶能夠訪問虛擬主機vhost,而且在全部資源上都具有可配置、可讀、可寫的權限,代碼以下:
[root@node-1 ~]# rabbitmqctl set_permissions -p vhost root ".*" ".*" ".*" Setting permissions for user "root" in vhost "vhost"
授予root用戶可訪問虛擬主機vhost2,在以「queue」開頭的資源上具有可配置權限,而且在全部資源上擁有可寫、可讀權限
[root@node-1 ~]# rabbitmqctl set_permissions -p vhost2 root "^queue.*" ".*" ".*" Setting permissions for user "root" in vhost "vhost2"
在 RabbitMQ 中有兩個 Shell 命令能夠列舉權限信息。第一個命令是 rabbitmqctl list_permissions [-p vhost) ,用來顯示虛擬主機上的權限;第二個命令是 rabbitmqctl list_user permissions {username) ,用來顯示用戶的權限。
,
清除也是在vhost級別對用戶而言的,清除權限的命令爲clear_permissions [-p <vhost>] <username> 其中vhost用於設置禁止用戶訪問的虛擬主機名稱,默認爲「/」, username爲禁止訪問虛擬主機的用戶名
[root@node-1 ~]# rabbitmqctl clear_permissions -p vhost root Clearing permissions for user "root" in vhost "vhost"
在RabbitMQ 中,用 戶是訪問控制 (Access Control)的基本單元,且單個用戶能夠跨越 vhost 進行受權。針對一至多個 vhost ,用戶能夠被賦予不一樣級別的訪問權限,並使用標準的 用戶名和密碼來認證用戶。
建立用戶的命令爲 rabbitmqctl add user {username} {password} ,username表示建立的用戶,password表示爲建立用戶的登陸密碼
建立一個用戶爲root,密碼爲root的用戶
rabbitmqct1 add_user root root
能夠經過 rabbitmqctl change_password {username} {newpassword} 命令來更 改指定用戶的密碼 其中 username 表示要變動密碼的用戶名稱, newpassword 表示要變動的新的密碼。
一樣能夠清除密碼,這樣用戶就不能使用密碼登陸了,對應的操做命令爲 rabbitmqctl clear_password {username} ,其中 username 表示要清除密碼的用戶名稱。
使用 rabbitmqctl authentiçate_user {userηame} {password} 能夠經過密碼來驗證用戶,其中 username 表示須要被驗證的用戶名稱, password 表示密碼
下面示例中分別採用root123和root321來驗證root用戶
[root@node-1 ~]# rabbitmqctl authenticate_user root root321 Authenticating user "root" Error: failed to authenticate user "root" [root@node-1 ~]# [root@node-1 ~]# rabbitmqctl authenticate_user root root123 Authenticating user "root" Success
刪除用戶命令爲 rabbitmqctl delete_user <username>
刪除root用戶
[root@node-1 ~]# rabbitmqctl delete_user root Deleting user "root"
羅列全部用戶
[root@node-1 ~]# rabbitmqctl list_users Listing users guest [administrator]
用戶的角色分爲 5種類型。
none:無任何角色。新建立的用戶的角色默認爲none
management:能夠訪問 Web 管理頁面。
policymaker:包含 management 的全部權限。
monitoring:包含 management 的全部權限,而且能夠看到全部鏈接、信道及節點相關的信息。
administartor:包含 monitoring 的全部權限,井且能夠管理用戶、 機、 權限、策略、參數等。 administator 表明了最高的權限。
用戶的角色能夠經過rabbitmqctl set_user_tags <username> <tag> 設置,其中 username 參數表示須要設置角色的用戶名稱;tag 參數用於設置 0個、1 個或者多個的角色,設置以後任何以前現有的身份都會被刪除。
[root@node-1 ~]# rabbitmqctl list_users Listing users guest [administrator] root [] [root@node-1 ~]# rabbitmqctl set_user_tags root monitoring Setting tags for user "root" to [monitoring] [root@node-1 ~]# rabbitmqctl list_users -q guest [administrator] root [monitoring] [root@node-1 ~]# rabbitmqctl set_user_tags root policymaker,management Setting tags for user "root" to ['policymaker,management'] [root@node-1 ~]# [root@node-1 ~]# rabbitmqctl list_users -q guest [administrator] root [policymaker,management]
前面講述的都是使用 rabbitmqctl 工具來管理 RabbitMQ,有些時候是否會以爲這種方式是否是不太友好?並且爲了可以運行 rabbitmqctl 工具,當前的用戶須要擁有訪問 Erlang cookie 的權限,因爲服務器多是 guest 或者 root 用戶身份來運行的,所以你須要得到這些文件的訪問權限 ,這樣就引伸出來 些權限管理的問題。
RabbitMQ開發團隊也考慮到了這種狀況,而且開發了 RabbitMQ management 插件 RabbitMQ management 插件一樣是由 Erlang 語言編寫 的,而且和 RabbitMQ 服務運行在同一個 Erlang 虛擬機中。
爲root用戶設置全部權限
RabbitMQ management 插件能夠提供 Web 管理界面用來管理如前面所述的虛擬主機、用 戶等,也能夠用來管理隊列、交換器 、綁定關係、策略、 參數等 ,還能夠用來監控 RabbitMQ 服務的狀態及一些數據統計類信息,可謂是功能強大,基本上可以涵蓋全部 RabbitMQ 管理的 功能。
在使用 Web 管理界面以前須要先啓用 RabbitMQ management 插件, RabbitMQ 提供了不少 的插件,默認存放在 $RABBITMQ_HOME /plugins 目錄下,以下所示
上面以.ez結尾的都是rabbitmq的插件,其中rabbitmq_management-3.6.10.ez就是rabbitmq management插件,啓動插件的命令是rabbitmq-plugins,其語法格式爲
rabbitmq-plugins [-n <node>] <command> [<command options>] 啓動插件是使用rabbitmq-plugins enable [plugin-name] 關閉插件的命令是 rabbitmq-plugins disable [plugin-name]
執行rabbitmq-plugins enable rabbitmq_management來啓動rabbitmq management插件
[root@node-1 plugins]# rabbitmq-plugins enable rabbitmq_management The following plugins have been enabled: amqp_client cowlib cowboy rabbitmq_web_dispatch rabbitmq_management_agent rabbitmq_management Applying plugin configuration to rabbit@node-1... started 6 plugins.
能夠經過 rabbitmq-plugins list 命令來查看當前插件的使用狀況 以下所示。其中 標記爲 [E*]的爲顯式啓動,而 [e*] 爲隱式啓動, 如顯式啓動 rabbitmq_management 插件 ,會同時隱式啓動 amqp_client 、cowboy、 cowlib rabbitmq_management_age、rabbitmq_web_dispatch 等另外 5個插件。
[root@node-1 plugins]# rabbitmq-plugins list Configured: E = explicitly enabled; e = implicitly enabled | Status: * = running on rabbit@node-1 |/ [e*] amqp_client 3.6.10 [e*] cowboy 1.0.4 [e*] cowlib 1.0.2 [ ] rabbitmq_amqp1_0 3.6.10 [ ] rabbitmq_auth_backend_ldap 3.6.10 [ ] rabbitmq_auth_mechanism_ssl 3.6.10 [ ] rabbitmq_consistent_hash_exchange 3.6.10 [ ] rabbitmq_event_exchange 3.6.10 [ ] rabbitmq_federation 3.6.10 [ ] rabbitmq_federation_management 3.6.10 [ ] rabbitmq_jms_topic_exchange 3.6.10 [E*] rabbitmq_management 3.6.10 [e*] rabbitmq_management_agent 3.6.10 [ ] rabbitmq_management_visualiser 3.6.10 [ ] rabbitmq_mqtt 3.6.10 [ ] rabbitmq_recent_history_exchange 3.6.10 [ ] rabbitmq_sharding 3.6.10 [ ] rabbitmq_shovel 3.6.10 [ ] rabbitmq_shovel_management 3.6.10 [ ] rabbitmq_stomp 3.6.10 [ ] rabbitmq_top 3.6.10 [ ] rabbitmq_tracing 3.6.10 [ ] rabbitmq_trust_store 3.6.10 [e*] rabbitmq_web_dispatch 3.6.10 [ ] rabbitmq_web_mqtt 3.6.10 [ ] rabbitmq_web_mqtt_examples 3.6.10 [ ] rabbitmq_web_stomp 3.6.10 [ ] rabbitmq_web_stomp_examples 3.6.10 [ ] sockjs 0.3.4 [root@node-1 plugins]#
默認guest用戶只能在本機上訪問進入管理頁面,其餘用戶需設置
[root@node-1 plugins]# rabbitmqctl set_user_tags root administrator Setting tags for user "root" to [administrator] [root@node-1 plugins]# [root@node-1 plugins]# rabbitmqctl list_users Listing users guest [administrator] root [administrator] [root@node-1 plugins]#
瀏覽器中輸入http://192.168.10.129:15672
輸入root的用戶名和密碼成功登陸
這裏演示爲三臺服務器搭建的一個集羣環境,每臺服務器上都安裝rabbitmq
vi /etc/hosts
192.168.10.129 node-1
192.168.10.130 node-2
192.168.10.131 node-3
以節點 node-1的.erlang.cookie爲目標,修改node2和node3上的.erlang.cookie
注意:如不能保存,先chmod 600 .erlang.cookie 修改後在改回400權限chmod 400 .erlang.cookie
[root@node-1 sbin]# rabbitmq-server -detached [root@node-2 sbin]# rabbitmq-server -detached [root@node-3 sbin]# rabbitmq-server -detached
接下來爲了將每一個節點組成一個集羣,須要以 node-l 節點爲基準,將 node-2 、node-3 節點 加入 node-l 節點的集羣中。這3 個節點是平等的,若是想調換彼此的加入順序也何嘗不可。首 先將 node2 節點加入 nodel 節點的集羣中,須要執行以下4 個命令步驟。
[root@node-2 sbin]# rabbitmqctl stop_app Stopping rabbit application on node 'rabbit@node-2' [root@node-2 sbin]# [root@node-2 sbin]# rabbitmqctl reset Resetting node 'rabbit@node-2' [root@node-2 sbin]# rabbitmqctl join_cluster rabbit@node-1 Clustering node 'rabbit@node-2' with 'rabbit@node-1' [root@node-2 sbin]# [root@node-2 sbin]# rabbitmqctl start_app Starting node 'rabbit@node-2' [root@node-2 sbin]# [root@node-2 sbin]# rabbitmqctl cluster_status Cluster status of node 'rabbit@node-2' [{nodes,[{disc,['rabbit@node-1','rabbit@node-2']}]}, {running_nodes,['rabbit@node-1','rabbit@node-2']}, {cluster_name,<<"rabbit@node-1">>}, {partitions,[]}, {alarms,[{'rabbit@node-1',[]},{'rabbit@node-2',[]}]}] [root@node-2 sbin]# [root@node-2 sbin]#
最後將 node-3 節點也加入 node-l 節點所在的集羣中,這3 個節點組成了一個完整的集羣。 在任意一個節點中均可以看到以下的集羣狀態。
[root@node-3 ~]# rabbitmqctl stop_app Stopping rabbit application on node 'rabbit@node-3' [root@node-3 ~]# [root@node-3 ~]# rabbitmqctl reset Resetting node 'rabbit@node-3' [root@node-3 ~]# rabbitmqctl join_cluster rabbit@node-1 Clustering node 'rabbit@node-3' with 'rabbit@node-1' [root@node-3 ~]# rabbitmqctl start_app Starting node 'rabbit@node-3' [root@node-3 ~]# rabbitmqctl cluster_status Cluster status of node 'rabbit@node-3' [{nodes,[{disc,['rabbit@node-1','rabbit@node-2','rabbit@node-3']}]}, {running_nodes,['rabbit@node-1','rabbit@node-2','rabbit@node-3']}, {cluster_name,<<"rabbit@node-1">>}, {partitions,[]}, {alarms,[{'rabbit@node-1',[]},{'rabbit@node-2',[]},{'rabbit@node-3',[]}]}] [root@node-3 ~]# [root@node-3 ~]#
若是在node-2上用命令rabbitmqctl stop_app關閉一個節點,在node-1和node-3上查看集羣節點信息
node-2上關閉
[root@node-2 sbin]# rabbitmqctl stop_app Stopping rabbit application on node 'rabbit@node-2' [root@node-2 sbin]#
node-1上查看集羣狀況
[root@node-1 ~]# rabbitmqctl cluster_status Cluster status of node 'rabbit@node-1' [{nodes,[{disc,['rabbit@node-1','rabbit@node-2','rabbit@node-3']}]}, {running_nodes,['rabbit@node-3','rabbit@node-1']}, {cluster_name,<<"rabbit@node-1">>}, {partitions,[]}, {alarms,[{'rabbit@node-3',[]},{'rabbit@node-1',[]}]}]
node-3上查看集羣狀況
[root@node-3 ~]# rabbitmqctl cluster_status Cluster status of node 'rabbit@node-1' [{nodes,[{disc,['rabbit@node-1','rabbit@node-2','rabbit@node-3']}]}, {running_nodes,['rabbit@node-3','rabbit@node-1']}, {cluster_name,<<"rabbit@node-1">>}, {partitions,[]}, {alarms,[{'rabbit@node-3',[]},{'rabbit@node-1',[]}]}]
若是關閉了集羣中的全部節點,必須保證最後關閉的節點第一個啓動,如,我按照前後順序關閉了node-一、node-二、node-3 也就是node-3是最後一個關閉的。那麼在啓動是先啓動node-3節點,node-1和node-2誰先啓動均可以
不然機會啓動失敗(10次30秒的等待時間)
若是最後一個關閉的節點最終因爲某些異常而沒法啓動,則能夠經過 rabbitrnqctl forget_cluster_node 命令來將此節點剔出當前集羣。若是集 羣中的全部節點因爲某些非正常因素,好比斷電而關閉,那麼集羣中的節點都會認爲還有其餘 節點在它後面關閉,
此時須要調用 rabbitrnqctl force_boot 命令來啓動一個節點,以後 集羣才能正常啓動。
在使用 rabbitrnqctl cluster_status 命令來查看集羣狀態時會有 {nodes [{disc , [rabbit@node-l , rabbit@node-2 , rabbit@node-3] 這一項信息,其中的 disc 標註了 RabbitMQ 節點的類型。 RabbitMQ 中的每個節點,不論是單一節點系統或者是集羣中的一部分 要麼是內存節點,要麼是磁盤節點。 內存節點將全部的隊列、 交換器、綁定關係、用戶、權限和 host 的元數據定義都存儲在內存中,而磁盤節點則將這些信息存儲到磁盤中。單節點的集羣中必然只有 磁盤類型的節點,不然當重啓 RabbitMQ以後,全部關於系統的配置信息都會丟失。不過在集羣中, 能夠選擇配置部分節點爲內存節點,這樣能夠得到更高的性能。
好比將 node-2 節點加入 nod 節點的時候能夠指定 node-2節點的類型爲內存節點。
[root@node-2 /] rabbitmqctl join_cluster rabbit@node-l --ram
這樣在以 node-l和 node-2 組成的集羣中就會有一個磁盤節點和一個內存節點,默認不添加 "-ram"參數則表示此節點爲磁盤節點。
若是集羣已經搭建好了,那麼也可使用 rabbitmqctl change_cluster_node_type {disc ,ram} 命令來切換節點的類型,其中 disc 表示磁盤節點,而 ram 表示內存節點。舉例, 這裏將上面 node-2 節點由磁盤節點轉變爲內存節點。
[root@node-2 /]# rabbitmqctl stop_app
Stopping rabbit application on node 'rabbit@node-2' [root@node-2 /]# rabbitmqctl change_cluster_node_type ram Turning 'rabbit@node-2' into a ram node [root@node-2 /]# [root@node-2 /]# rabbitmqctl start_app Starting node 'rabbit@node-2' [root@node-2 /]# rabbitmqctl cluster_status Cluster status of node 'rabbit@node-2' [{nodes,[{disc,['rabbit@node-3','rabbit@node-1']},{ram,['rabbit@node-2']}]}, {running_nodes,['rabbit@node-1','rabbit@node-3','rabbit@node-2']}, {cluster_name,<<"rabbit@node-1">>}, {partitions,[]}, {alarms,[{'rabbit@node-1',[]},{'rabbit@node-3',[]},{'rabbit@node-2',[]}]}]
有兩種方式將一個節點從集羣中剔除,好比將node-2從集羣中踢出。
在node-2上關閉服務rabbitmqctl stop_app,在node-1或者node-3上執行
[root@node-3 ~]# rabbitmqctl forget_cluster_node rabbit@node-2 Removing node 'rabbit@node-2' from cluster [root@node-3 ~]# [root@node-3 ~]# rabbitmqctl cluster_status Cluster status of node 'rabbit@node-3' [{nodes,[{disc,['rabbit@node-1','rabbit@node-3']}]}, {running_nodes,['rabbit@node-1','rabbit@node-3']}, {cluster_name,<<"rabbit@node-1">>}, {partitions,[]}, {alarms,[{'rabbit@node-1',[]},{'rabbit@node-3',[]}]}]
直接在node-2節點上執行
[root@node-2 /]# rabbitmqctl stop_app Stopping rabbit application on node 'rabbit@node-2' [root@node-2 /]# rabbitmqctl reset Resetting node 'rabbit@node-2' [root@node-2 /]# [root@node-2 /]# rabbitmqctl start_app Starting node 'rabbit@node-2' [root@node-2 /]# [root@node-2 /]# rabbitmqctl cluster_status Cluster status of node 'rabbit@node-2' [{nodes,[{disc,['rabbit@node-2']}]}, {running_nodes,['rabbit@node-2']}, {cluster_name,<<"rabbit@node-2">>}, {partitions,[]}, {alarms,[{'rabbit@node-2',[]}]}] [root@node-2 /]#