该博客文章的目标是提供适合大多数 neo4j 集群的合理 haproxy 配置。它涵盖了适用于典型部署的 Neo4j 特定需求(身份验证、粘性、请求路由)。
一些介绍的话
在运行 neo4j 集群时,您通常希望在其前面放置一个负载均衡器。负载均衡器本身不是 neo4j 的一部分,因为很多客户已经在他们的基础设施中安装了硬件负载均衡器。如果他们没有或想要一些易于使用的解决方案,推荐的工具是 haproxy 。
在 Neo4j 集群中,它完全有可能在任何集群成员上运行任何操作(读取和写入)。然而在性能方面,将写请求直接发送到主节点并将只读请求发送到所有集群成员或仅发送到从节点是一个好主意——这取决于你的主节点有多忙。
我们需要涵盖的内容
如今,大多数人通过 事务密码端点 发送密码命令来与 Neo4j 交互。此端点始终使用 http post,无论密码命令是否旨在执行读取或写入操作。这会给负载平衡带来一些痛苦,因为我们需要区分读取和写入。
一种方法是在客户端使用额外的自定义 HTTP 标头。假设任何应该执行写入的请求都会在请求中获得一个
x-write:1
标头。然后 haproxy 只需要在 acl 中检查此标头,并将此 acl 与仅包含主实例的后端相关联。
另一个想法是负载平衡器检查请求的有效负载(也就是您发送的密码命令)。如果该有效负载包含诸如创建、合并、设置、删除、删除或 foreach 之类的写子句,则很可能是将写入操作定向到主服务器。幸运的是 haproxy 有能力检查有效负载并应用正则表达式匹配。
第三种方法是将每个用例封装到一个 非托管扩展 中,并在内部使用密码、遍历 api 或核心 api 作为实现细节。通过适当地使用 rest 的语义,您可以使用 get 来实现无副作用的读取,使用 put、post 和 delete 来实现写入。这些也可以很容易地用在 haproxy 的 acl 中。
设置负载均衡器时我们必须处理的另一个问题是身份验证。从 neo4j 2.2 开始,数据库默认使用用户名和密码进行保护。当然我们可以关闭该功能,但如果安全是一个问题,那么启用它是明智的。结合 haproxy 我们需要知道 haproxy 需要使用身份验证以及它的状态检查来识别谁可用和谁是主人。
推荐设置
我花了一些时间来解决上述所有问题的 haproxy 配置。让我们逐行浏览下面的相关部分——剩下的我委托给 haproxy 的优秀文档 。请注意,此配置需要最新版本的 haproxy:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
荣耀细节
定义访问控制列表:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
acl declare conditions 是后面要用到的。我们有 ACLS 来识别传入请求是否:
- 具有 post、put 或 delete 的 http 方法 (l.16)
-
有一个特定的请求头
x-write:1
- 包含有效负载中用于标识写入的魔术词之一:创建、合并、删除、删除、设置
- 针对交易密码端点
存储指示 tx 端点的变量:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
我们在这里做了一个卑鄙的把戏:我们存储一个内部变量,该变量包含一个布尔值,具体取决于请求是否针对 tx 端点。稍后我们会参考该变量。理由是 haproxy 无法访问配置文件其他部分中的 acls。
决定要使用的后端(也就是我们读还是写):
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
上面定义的 ACLS 决定请求应该指向哪个后端。我们有一个端点用于 neo4j 集群的主节点,另一个端点用于所有可用的 neo4j 实例(主节点和从节点)的池。如果出现以下情况,将向 master 发送请求:
-
x-write:1
标头已设置,或者 - 该请求到达 tx 密码端点 , 它包含一个神奇的词。
- 这是一个帖子,放置或删除。
检查 neo4j 实例的状态,最终通过身份验证:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
后端需要检查它的哪个成员可用。因此,neo4j 有一个 rest 端点来暴露单个实例的状态。如果我们使用 neo4j 身份验证,则需要对状态端点的请求进行身份验证。感谢 stackoverflow,我发现您可以为这些请求添加
authorization
。像往常一样,auth 标头的值是一个 base64 编码的字符串“<username>:<password>”。在 unix 命令行上使用:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
如果您在 neo4j 中禁用了身份验证,请改用注释行。
tx 端点的粘性:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
使用我们之前存储的变量,我们取回值并使用它在此后端声明一个 acl。为了确保对相同交易编号的任何请求都发送到相同的 neo4j 实例,我们创建了一个粘性表来存储交易 ID。您可能希望根据需要采用该表的大小(此处为 1k),并且过期时间应与 neo4j 的 org.neo4j.server.transaction.timeout 设置保持一致。它默认为 60 秒,因此如果我们在 70 秒后使粘性表条目过期,我们就安全了。如果来自 tx 端点的 http 响应具有
location
标头,我们将其第 6 个字(即交易编号)存储在粘性表中。如果我们命中 tx 端点,我们会检查是否在粘性表中找到请求路径的第 4 个字(事务 ID)。如果是这样,请求将再次发送到同一台服务器,否则我们使用默认策略(循环)。
定义 neo4j 集群实例:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
这些行包含所有集群实例的列表。请确保将 maxconn 值与 max_server_threads 的数量对齐。默认情况下,neo4j 每个 cpu 核心使用 10 个线程。所以 32 对于 4 芯盒来说是一个很好的价值。
haproxy的统计界面:
global
daemon
maxconn 256
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8090
acl write_method method post delete put
acl write_hdr hdr_val(x-write) eq 1
acl write_payload payload(0,0) -m reg -i create|merge|set|delete|remove
acl tx_cypher_endpoint path_beg /db/data/transaction
http-request set-var(txn.tx_cypher_endpoint) bool(true) if tx_cypher_endpoint
use_backend neo4j-master if write_hdr
use_backend neo4j-master if tx_cypher_endpoint write_payload
use_backend neo4j-all if tx_cypher_endpoint
use_backend neo4j-master if write_method
default_backend neo4j-all
backend neo4j-all
option httpchk get /db/manage/server/ha/available http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/available
acl tx_cypher_endpoint var(txn.tx_cypher_endpoint),bool
stick-table type integer size 1k expire 70s # slightly higher with org.neo4j.server.transaction.timeout
stick match path,word(4,/) if tx_cypher_endpoint
stick store-response hdr(location),word(6,/) if tx_cypher_endpoint
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
backend neo4j-master
option httpchk get /db/manage/server/ha/master http/1.0\r\nauthorization:\ basic\ bmvvngo6mtiz
#option httpchk get /db/manage/server/ha/master
server s1 127.0.0.1:7474 maxconn 32 check
server s2 127.0.0.1:7475 maxconn 32 check
server s3 127.0.0.1:7476 maxconn 32 check
listen admin
bind *:8080
stats enable
stats realm haproxy\ statistics
stats auth admin:123
haproxy 提供了一个很好的状态仪表板。在大多数情况下,您希望使用密码来保护它。
结论
我希望这篇文章能为您提供一些关于如何在 Neo4j 集群前有效使用 haproxy 的见解。当然我知道我对 haproxy 的经验和知识是有限的。所以我感谢任何反馈来改进这里描述的配置。