文章转自:http://www.oracle.com/technetwork/cn/articles/database-performance/oracle-rac-connection-mgmt-1650424-zhs.html

Oracle RAC 环境下的连接管理

作者:崔华

这篇文章详细介绍了Oracle RAC环境下的连接管理,分别介绍了什么是 Connect Time Load Balancing、Runtime Connection Load Balancing、Connect Time Connection Failover 和 Runtime Connection Failover,以及里面所涉及到的 TAF、ONS、FCF、FAN、LBA 等诸多知识点。本文主要是针对 Oracle RAC 11gR2 环境下的连接管理,但同时也会对比说明一下 Oracle RAC 10gR2/9iR2,以体现他们之间在连接管理上的差异。

所谓“连接管理”,主要体现在 Load Balancing 和 Failover 两方面。Oracle RAC 11gR2 下的 Load Balancing 和 Failover,根据是否使用了事先已经存在的连接(如连接池中的连接)又分为 Connect Time Load Balancing、Runtime Connection Load Balancing、Connect Time Connection Failover和Runtime Connection Failover 这 4 种类型,凡是带上了“Runtime”前缀的,就是指连接已经存在的情况,比如使用了连接池。

一、首先来介绍 Connect Time Connection Failover

Connect Time Connection Failover 是指不从连接池中取得已有连接,而是直接连接 Oracle 数据库时的 Failover。在 Oracle RAC 11gR2 之前,Connect Time Connection Failover 是非常容易实现的,只需要在相关的 tnsnames.ora 中指定多个 vip,同时指定 FAILOVER=ON 就好了。如下所示:

(DESCRIPTION=
(FAILOVER=ON)
(ADDRESS_LIST=
(LOAD_BALANCE=OFF)
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC1-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC2-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC3-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC4-vip)(PORT=1521))
)
(CONNECT_DATA=(SERVICE_NAME=RAC10g))
)

这里客户端进程首先会尝试连接 RAC1-vip,如果连不上,则会尝试RAC2-vip,再连不上,则会继续往下尝试,直到所有出现在 ADDRESS_LIST 中的 vip 地址全部顺序尝试完为止。这种客户端在连接 Oracle 数据库时的 Failover,不仅适用于 RAC 环境,也适用于 Data Guard 环境。如下所示:

DESCRIPTION=
(FAILOVER=ON)
(ADDRESS_LIST=
(LOAD_BALANCE=OFF)
(ADDRESS=(PROTOCOL=TCP)(HOST=primary-ip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=standby-ip)(PORT=1521))
)
(CONNECT_DATA=(SERVICE_NAME=service10g))
)

Oracle RAC 11gR2 引入了 SCAN(Single Client Access Name),并且客户端缺省是通过 SCAN 来连接整个 RAC 环境的,如下是 SCAN 的架构图:

如上图所示,如果使用了 DNS 或者 GNS (Grid Naming Service),那么最多可以有 3 个 SCAN VIP 和 3 个 SCAN Listener;如果没有使用 DNS 或者 GNS,而是选择使用 hosts 文件,则只会有 1 个 SCAN VIP 和 1 个 SCAN Listener。

这里假设在 tnsnames.ora 中这样配置:

  (DESCRIPTION =
(FAILOVER=ON)
(ADDRESS = (PROTOCOL = TCP)(HOST = MySCAN)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME =RAC11g)))

严格意义上说,只有在 RAC 环境有 1 个以上 SCAN VIP 的时候,上述 FAILOVER=ON 才有意义——它表示的是客户端在连接 SCAN VIP 的时候,如果其中的一个 SCAN VIP 连不上,则马上会尝试另外一个 SCAN VIP。

当使用了 hosts 文件来指定 SCAN VIP 的时候,即在整个 RAC 环境只有 1 个 SCAN VIP 的情况下,Failover 其实也存在,只不过这种情况下 Failover 的速度会慢一些。因为当 SCAN VIP 所在的节点宕掉后,SCAN VIP 会和相关的 SCAN Listener 一起整体 Failover 到其他节点,只不过这个 Failover 需要时间,而客户端需要等待这个 Failover 过程完毕后才能重新连上 RAC。

二、接下来介绍 Runtime Connection Failover

Runtime Connection Failover 是指连接已经存在的情况下的 Failover。这个已存在的连接,可能是连接池中正在用的连接,也可能是不通过连接池、直接通过 OCI 客户端(如 sqlplus)连上 Oracle 数据库后的连接。

这种 Runtime Connection Failover,就是指在连接已经存在的情况下,如果 Oracle 数据库端出现了异常的情况(比如 Service 宕了、Instance 崩溃了、Session 断了)而导致已有连接中断,怎样 Failover 的问题。

有两种手段来实现 Runtime Connection Failover,分别为 TAF(Transparent Application Failover)和 FCF(Fast Connection Failover)。

首先来介绍 TAF。TAF 有如下一些知识点需要我们注意:

1、它可以在 client 端的 tnsnames.ora 中的连接串里定义,也可以在 server 端的 service 中定义,只不过 service 端的设置会取代(override)客户端 tnsnames.ora 中的设置:

客户端可以这样设置 TAF:

  (DESCRIPTION =
(FAILOVER=ON)
(ADDRESS = (PROTOCOL = TCP)(HOST = MySCAN)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = Email)
(FAILOVER_MODE= (TYPE=select)(METHOD=basic)(RETRIES=180)(DELAY=5)))

Server 端可以这样设置 TAF:

srvctl modify service -d RAC11g -s Email -q TRUE -P BASIC -e SELECT -z 180 -w 5 -j LONG 具体各个参数的含义可参见如下注释:

Usage: srvctl modify service -d <db_unique_name> -s <service_name> [-c {UNIFORM |
SINGLETON}] [-P {BASIC|PRECONNECT|NONE}] [-l
[PRIMARY][,PHYSICAL_STANDBY][,LOGICAL_STANDBY][,SNAPSHOT_STANDBY]] [-y
{AUTOMATIC | MANUAL}][-q {true|false}] [-x {true|false}] [-j {SHORT|LONG}] [-B
{NONE|SERVICE_TIME|THROUGHPUT}] [-e {NONE|SESSION|SELECT}] [-m
{NONE|BASIC}] [-z <integer>] [-w <integer>]
-d <db_unique_name> Unique name for the database
-s <service> Service name
-c {UNIFORM | SINGLETON} Service runs on every active server in the server
pool hosting this service (UNIFORM) or just one server (SINGLETON)
-P {NONE | BASIC | PRECONNECT} TAF policy specification
-l <role> Role of the service (primary, physical_standby,
logical_standby, snapshot_standby)
-y <policy> Management policy for the service (AUTOMATIC or MANUAL)
-e <Failover type> Failover type (NONE, SESSION, or SELECT)
-m <Failover method> Failover method (NONE or BASIC)
-w <integer> Failover delay
-z <integer> Failover retries
-j <clb_goal> Connection Load Balancing Goal (SHORT or LONG). Default is LONG.
-B <Runtime Load Balancing Goal> Runtime Load Balancing Goal (SERVICE_TIME,
THROUGHPUT, or NONE)
-x <Distributed Transaction Processing> Distributed Transaction Processing (TRUE or FALSE)
-q <AQ HA notifications> AQ HA notifications
(TRUE or FALSE)
-h Print usage

2、当 TAF 的TYPE 设置为 select 的时候,单纯 select 操作(不包括 select … for update)可以做到“断点续传”,即单纯的 select 操作在利用 TAF 实现 Failover 后是可以从中断的地方继续往下执行的;
3、TAF 对 DML 操作不能做到“断点续传”,即如果一个 transaction 在使用 TAF 实现 Failover 后,该 transaction 不能从中断的地方继续执行,需要再次从头开始执行;
4、TAF 仅对使用 OCI 连接的客户端和连接池有效,这里的 OCI 连接可以是在 OCI 连接上的封装,比如 JDBC-OCI driver 就支持 TAF,但 JDBC thin driver 就不支持 TAF(因为 JDBC thin driver 不是基于 OCI 的)。

接下来,在介绍 FCF(Fast Connection Failover)之前,我们必须要先介绍 FAN(Fast Application Notification)。

FAN 是 Oracle RAC 里的一种消息主动通知机制。当 RAC 里出现 service down/up,instance down/up,节点负载变化时,Oracle 数据库都能通过 FAN events 将这些信息发布出去,订阅这些 FAN events 的客户端在第一时间收到这些 FAN events 后就能做出相应的动作来响应这些 FAN events。

FAN events 分为两种,第一种是 FAN HA events,第二种是 LBA events,这里的 LBA 是指 Load Balancing Advisory。

当 RAC 里出现 service down/up、instance down/up 时就会触发 FAN HA events。FAN HA events 的示例如下所示:

Event 1: FAN event type: instance
Properties: version=1.0 service=PROD database=PROD instance=PROD1 host=node1 status=down Event 2: FAN event type: service_member
Properties: version=1.0 service=ERP database=PROD instance=PROD1 host=node1 status=down Event 3: FAN event type: service_member
Properties: version=1.0 service=ERP database=PROD instance=PROD3 host=node3 status=up

RAC 里节点的负载变化后也会产生 LBA events,LBA events 的示例如下所示:

Event 4: FAN-event type: service_metrics
Properties: version=2.0 service=ERP database=PROD instance=PROD1 percent=70
service_quality=GOOD instance=PROD2 percent=30 service_quality=GOOD Event 5 :FAN-event type: service_metrics
Properties: version=2.0 service=CRM database=PROD instance=PROD2 percent=30
service_quality=GOOD instance=PROD3 percent=70 service_quality=GOOD

上述 FAN events 可能会通过多种渠道传播出去,这些渠道包括 ONS(Oracle Notification Service),AQ(Advanced Queue),PMON 等。下面是关于 FAN events 架构和传播途径的两张图,它们就直观的说明了 FAN events 的传播途径:

订阅 FAN HA events 的客户端包括:JDBC Implicit Connection Cache, OCI, ODP.NET Connection Pools, Listener, Server Side Callouts 等;
订阅 LBA events 的客户端包括:JDBC Implicit Connection Cache, ODP.NET Connection Pools, Listener,OCI Session Pools 等;

介绍完 FAN,现在可以开始介绍 FCF:FCF 的意思是 Fast Connection Failover,它实际上是客户端通过订阅 FAN HA events 来实现的。如下是两个客户端通过订阅 FAN HA events 来实现 FCF 的例子:

例一:JDBC Fast Connection Failover (FCF)
这里的 JDBC 连接是指 JDBC thin 连接。因为 JDBC thin 连接不是基于 OCI 的,所以这种情况下的 Runtime Connection Failover 不能使用 TAF,只能用 FCF。并且要做如下几件事情后才可以正常使用 FCF:
1、把 implicit connection cache 打开;
2、把 FastConnectionFailoverEnabled 打开;
3、最好是直接订阅远程的 ONS(在Oracle 10gR2 之前的版本不能直接订阅远程的 ONS,只能通过在本地安装 ONS 后来实现 FAN events 的中转);
4、最好是在 Java 程序里设置一下 TCP timeout(后面专门会讲到在 Oracle 数据库里如何调整 TCP timeout);

演示代码如下:

OracleDataSource ods = new OracleDataSource()
...
ods.setUser(“Scott”)
ods.setPassword(“tiger”)
ods.setConnectionCachingEnabled(true);
ods.setFastConnectionFailoverEnabled(true);
ods.setConnectionCacheName(“MyCache”)
ods.setConnectionCacheProperties(cp);
ods.setONSConfiguration("nodes=racnode1:6201,racnode2.:6201");
ods.setURL("jdbc:oracle:thin:@sales1-scan:1521/oltp"); //TCP connect timeout
Properties prop = new Properties();
prop.setProperty("MinLimit", MIN_CONN);
prop.setProperty("MaxLimit", MAX_CONN);
prop.setProperty("InitialLimit", INIT_CONN);
prop.put (oracle.net.ns.SQLnetDef.TCP_CONNTIMEOUT_STR, "1000")); //
这里是表示把TCP timeout设为1000毫秒,即1秒
ods.setConnectionCacheProperties(prop);

例二:ODP.NET Fast Connection Failover (FCF)
对于 ODP.NET 而言,通常做了如下几件事情后就可以使用 FCF 了:
1、把对应 service 的 AQ Notification 打开:
srvctl modify service -d RAC11g -s Email -q TRUE
2、把 aq_tm_processes 的值设为 1;
3、赋予指定用户 de-queue 的权限:
exec dbms_aqadm.grant_queue_privilege('DEQUEUE','SYS.SYS$SERVICE_METRICS', <your username=>);
4、在 .NET 连接串里设置 HA events=true;

演示代码如下:

// C#
using System;
using Oracle.DataAccess.Client;
class ConnectionPoolingSample
{
static void Main()
{
OracleConnection con = new OracleConnection();
//Open a connection using ConnectionString attributes
//related to connection pooling.
con.ConnectionString =
"User Id=scott;Password=tiger;Data Source=crm;" +
"Min Pool Size=10;Connection Lifetime=120;Connection Timeout=60;" +
"HA events=true", "Incr Pool Size=5; Decr Pool Si=2";
con.Open();
Console.WriteLine("Connection pool successfully created");
// Close and Dispose OracleConnection object
con.Close();
con.Dispose();
Console.WriteLine("Connection is placed back into the pool.");
}
}

FCF 跟 TAF 有一个很大的不同就是即便是单纯 select 操作,FCF 也不能像 TAF 那样做到“断点续传”。对于配置好了 FCF 的连接池而言,当它接收到包含 instance/service 宕掉的 FAN HA events 后,原先 cache 在连接池里的跟这个 instance/service 相关的连接马上会被标记为失效(invalid)同时这些连接会被清除,使用这些连接的 transaction 也会马上中止并回滚。当应用捕捉到这个中止的 transaction 所产生的错误信息后,要么直接把相关错误返回给最终用户,要么从连接池中重新取得一个有效连接并重新执行这个被中止的 transaction。

在启用了 FCF 的情况下,如果连接错误被返回给了最终用户,那么应该如何判断错误信息的来源呢(即是否是 FCF 返回的错误)?很简单,用 isFatalConnectionError(SQLException e)来判断一下就好了,演示代码如下:

try {
conn = getConnection();
//这里取得连接后做相关的工作
} catch (SQLException e) {
handleSQLException(e)
}
...
void handleSQLException (SQLException e)
{
if
(OracleConnectionCacheManager.isFatalConnectionError(e))
ConnRetry = true; //这里表示捕捉到FCF返回的错误

}

三、接着介绍 Connect Time Load Balancing

Connect Time Load Balancing 就是指不从连接池中取得已有连接,而是直接连接 Oracle 数据库时的 Load Balance。Connect Time Load Balancing 又细分为两种,分别是客户端的 Connect Time Load Balancing 和 Server 端的 Connect Time Load Balancing。

在 Oracle RAC 11gR2 之前,客户端的 Connect Time Load Balancing 非常容易实现,只需要在相关的 tnsnames.ora 中指定多个 vip,同时指定 LOAD_BALANCE=ON 就好了。如下所示:

(DESCRIPTION=
(ADDRESS_LIST=
(LOAD_BALANCE=ON)
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC1-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC2-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC3-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC4-vip)(PORT=1521))
)
(CONNECT_DATA=(SERVICE_NAME=RAC10g))
)

这样客户端在连接的时候,会随机地从上述 4 个 VIP 地址中选一个来连接 Oracle 数据库以达到 Load Balance 的目的。

之前已经提到,Oracle RAC 11gR2 中引入了 SCAN(Single Client Access Name),并且客户端缺省是通过 SCAN 来连接整个 RAC 环境的,如果使用了 DNS 或者 GNS (Grid Naming Service),那么最多可以有 3 个 SCAN VIP 和 3 个 SCAN Listener;如果没有使用 DNS 或者 GNS,而是选择使用 hosts 文件,则只会有 1 个 SCAN VIP 和 1 个 SCAN Listener。

这里假设你在 tnsnames.ora 中这样配置:

  (DESCRIPTION =
(LOAD_BALANCE=ON)
(ADDRESS = (PROTOCOL = TCP)(HOST = MySCAN)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME =RAC11g)))

严格意义上说,只有在 RAC 环境有 1 个以上 SCAN VIP 的时候,上述 LOAD_BALANCE=ON 才有意义——它表示的是客户端在连接 Oracle 11gR2 RAC 的时候,会随机的选择三个 SCAN VIP 中的一个来连接,所以 Oracle 11gR2 RAC 的客户端 Connect Time Load Balancing 实际上是针对 SCAN VIP 而言的,而不是像 Oracle RAC 10gR2 /9iR2 那样直接针对 RAC 节点的 VIP(Oracle RAC 9iR2 里没有 VIP,此时 Connect Time Load Balancing 是针对 public ip)。

当使用了 hosts 文件来指定 SCAN VIP 的时候,客户端 Connect Time Load Balancing 实际上是不存在的,因为现在整个 RAC 环境只有 1 个 SCAN VIP。

现在再来介绍一下 Server 端的 Connect Time Load Balancing。Server 端的 Connect Time Load Balancing 相对来说要复杂一些,下面针对 Oracle 数据库不同的版本来分别加以说明。

首先要说明的是:无论是 Oracle RAC 9iR2/10gR2,还是 Oracle RAC 11gR2,它们的 Server 端 Connect Time Load Balancing 都是通过联合使用 local_listener 和 remote_listener 来实现的。

先来介绍 Oracle RAC 9iR2 下的 Server 端 Connect Time Load Balancing:
这里假设是一个 4 节点的 Oracle RAC 9iR2,tnsnames.ora 中的连接串是如下这样:

(DESCRIPTION=
(FAILOVER=ON)
(ADDRESS_LIST=
(LOAD_BALANCE=ON)
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC1-ip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC2-ip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC3-ip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC4-ip)(PORT=1521))
)
(CONNECT_DATA=(SERVICE_NAME=RAC9i))
)

再在各个节点的 tnsnames.ora 中加入如下设置:

LISTENER_RAC1 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC1-ip)(PORT = 1521)) LISTENER_RAC2 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC2-ip)(PORT = 1521)) LISTENER_RAC3 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC3-ip)(PORT = 1521)) LISTENER_RAC4 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC4-ip)(PORT = 1521)) LISTENERS_RAC =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC1-ip)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC2-ip)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC3-ip)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC4-ip)(PORT = 1521))
)

然后只需要在初始化参数文件里加入如下设置就能实现 Server 端 Connect Time Load Balancing 了:

RAC1.local_listener=LISTENER_RAC1
RAC2.local_listener=LISTENER_RAC2
RAC3.local_listener=LISTENER_RAC3
RAC4.local_listener=LISTENER_RAC4
*.remote_listener=LISTENERS_RAC

当做了上述设置后,上述 4 个节点的 listener 实际上除了知道本节点负载的情况之外,同时也知道了其余节点的负载情况。所以当先经过一层客户端的 Connect Time Load Balancing,比如这里随机地连到了第二个节点上的 listener(即 LISTENER_RAC2)上,当 LISTENER_RAC2 发现自身的负载较高,是有可能把你的连接请求转移(redirect)到其余负载较低的节点的 listener 上的——这就是所谓的第二层 Load Balancing,也就是 server 端的 Connect Time Load Balancing。

Oracle RAC 9iR2 里 server 端的 Connect Time Load Balancing 的依据是各节点 CPU 的负载(CPU runqueue-based load)或各节点所连接的 session 的数量。我们可以在相应节点的 listener.ora 中通过参数 prefer_least_loaded_node_<LISTENER_NAME>来控制 Oracle RAC 9iR2 数据库到底采用哪种判断依据。prefer_least_loaded_node_ <LISTENER_NAME>的默认值是 on,意味着 Listener 会把连接转移(redirect)到 CPU 负载较低的节点,即这种情况下判断负载的依据是各节点 CPU 的负载情况;如果把它设为 off,则意味着 Listener 在转移(redirect)连接的时候会考虑各个节点已连接 session 的数量并且会尽量保证各个节点所连接 session 数量的均衡,即这种情况下判断负载的依据是各节点已连接 session 的数量。

接着再来看 Oracle RAC 10gR2 下的 Server 端 Connect Time Load Balancing。Oracle RAC 10gR2 里 Server 端 Connect Time Load Balancing 也是通过联合使用 local_listener 和 remote_listener 来实现的,只不过 Oracle RAC 10gR2 里引入了 VIP,所以这里 local_listener 和 remote_listener 一定是要监听 VIP,而不是像 Oracle RAC 9iR2 那样监听 public ip 了。

这里假设是一个 4 节点的 Oracle RAC 10gR2,tnsnames.ora 中的连接串是如下所示(注意这里的连接地址已经是 VIP 了):

(DESCRIPTION=
(FAILOVER=ON)
(ADDRESS_LIST=
(LOAD_BALANCE=ON)
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC1-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC2-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC3-vip)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=RAC4-vip)(PORT=1521))
)
(CONNECT_DATA=(SERVICE_NAME=RAC10g))
)

再在各个节点的 tnsnames.ora 中加入如下设置(注意这里监听的已经是 VIP了):

LISTENER_RAC1 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC1-vip)(PORT = 1521)) LISTENER_RAC2 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC2-vip)(PORT = 1521)) LISTENER_RAC3 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC3-vip)(PORT = 1521)) LISTENER_RAC4 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC4-vip)(PORT = 1521)) LISTENERS_RAC =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC1-vip)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC2-vip)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC3-vip)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC4-vip)(PORT = 1521))
)

然后只需要在初始化参数文件里加入如下设置就能实现 Server 端 Connect Time Load Balancing 了:

RAC1.local_listener=LISTENER_RAC1
RAC2.local_listener=LISTENER_RAC2
RAC3.local_listener=LISTENER_RAC3
RAC4.local_listener=LISTENER_RAC4
*.remote_listener=LISTENERS_RAC

当做了上述设置后,与 Oracle RAC 9iR2 一样,也可以实现 Server 端 Connect Time Load Balancing。只不过这里判断负载的依据有了变化。(注:如果客户端不能解析 RAC1-vip 这样的主机名,则在连接时很可能报 ORA-12545 错误,local_listener 对应的 TNS 配置中应该使用 VIP 地址,而不要用主机名,这里写主机名只是出于演示的目的)

Oracle 10g 引入了 Service,所以在 Oracle 10g 里,判断负载的依据就跟 Service 绑定在了一起。Oracle RAC 10gR2 里 Server 端 Connect Time Load Balancing 判断负载的依据是由相关 service 的参数 CLB_GOAL 和 GOAL 联合来决定的。

Oracle RAC 10gR2 里的负载可以通过 v$servicemetric 来查看:

SQL> desc v$servicemetric;
Name Type Nullable Default Comments
----------------- ------------ -------- ------- --------
BEGIN_TIME DATE Y
END_TIME DATE Y
INTSIZE_CSEC NUMBER Y
GROUP_ID NUMBER Y
SERVICE_NAME_HASH NUMBER Y
SERVICE_NAME VARCHAR2(64) Y
CTMHASH NUMBER Y
ELAPSEDPERCALL NUMBER Y
CPUPERCALL NUMBER Y
DBTIMEPERCALL NUMBER Y
CALLSPERSEC NUMBER Y
DBTIMEPERSEC NUMBER Y
GOODNESS NUMBER Y
DELTA NUMBER Y
FLAGS NUMBER Y

其中每个 service 在 v$servicemetric 里会对应两条记录,一条记录每5秒采样一次,另外一条记录每 60 秒采样一次。

这里衡量每个 service 的负载情况,主要是通过 GOODNESS、DELTA和FLAGS 这三列来说明的,如下是它们各自的含义:
GOODNESS 表示这个节点成为 Server 端 Connect Time Load Balancing 的目标节点的可能性,这个值越高,可能性就越低。即这个 service 在某个节点上的 GOODNESS 的值越大,则表明这个节点的负载越重,这个节点成为 Server 端 Connect Time Load Balancing 的目标节点的可能性就越低。

DELTA 表示当节点增加了一个额外的 session 后对负载增加情况的估算。

FLAGS 是一个标志位,它的各个值的含义如下:

 0 – all good
1 – blocked
2 – crossed threshold
4 – goodness unknown (usually when no sessions connected)

每个 service 所对应的 CLB_GOAL 实际上表示 Client Load Balance Goal,它的值要么为 LONG,要么为 SHORT,默认值是 LONG。

LONG 和 SHORT 的区别是:LONG 是 CLB_GOAL 的缺省值,通常用于那些需要长时间保持的连接,比如一些第三方的连接池或者 SQL*Form 应用;而 SHORT 则通常用于那些连接持续时间较短的应用,如果使用了支持订阅 LBA(Load Balancing Advisory)的连接池,则应该把 CLB_GOAL 的值设为 SHORT。

如果一个 service 的 CLB_GOAL 被设为 LONG,则意味着衡量这个 service 所在节点的负载情况是依据连接到这个节点的 session 的数量,此时与 CLB_GOAL 相对应的另外一个参数 GOAL 的设置将不再生效。

如果你把一个 service 的 CLB_GOAL 设为 SHORT,则意味着衡量这个 service 的负载情况是依据 LBA,在根据 LBA 判断负载情况时根据对应 service 的 GOAL 的设置的值的不同,又可以细分为是依据 SERVICE_TIME 还是依据 THROUGHPUT。也就是说,每个 service 所对应的 GOAL 实际上表示 LBA GOAL,它的值要么为 THROUGHPUT,要么为 SERVICE_TIME,要么是 NONE,GOAL 的默认值是 NONE。即当你把 CLB_GOAL 设为 SHORT 后,这种情况下 Server 端 Connect Time Load Balancing 判断负载的依据就是由 GOAL 的设置来决定了。

GOAL 所对应的三个值 THROUGHPUT、SERVICE_TIME 和 NONE 的区别是:
THROUGHPUT:表示判断负载的依据是吞吐量(THROUGHPUT),这通常用于那些并发的 transaction 具有相似的完成时间、相似的完成速率的系统,比如在线交易系统;
SERVICE_TIME:表示判断负载的依据是响应时间(response time),这通常用于那些并发的 transaction 具有不同的完成时间、不同的完成速率的系统,比如在线购物系统,不同的人完成一次在线购物,所购买的产品、所耗费的时间可能有很大差异;
NONE:表示不启用 LBA。

如果再结合 service 的 CLB_GOAL 和 GOAL,以及 v$servicemetric,就可以归纳出 Oracle RAC 10gR2 里 Server 端 Connect Time Load Balancing 判断负载的依据:

1、Oracle RAC 10gR2 里 Server 端 Connect Time Load Balancing 默认情况下判断负载的依据是连接到每个节点的 session 的数量,即当 CLB_GOAL 为默认值 LONG 的时候,v$servicemetric 的对应 service 的 GOODNESS=number of connected sessions,DELTA=1,注意此时 LBA 并没有启用;
2、Oracle RAC 10gR2 里如果把 service 的 CLB_GOAL 设为 SHORT,同时把 GOAL 设为 THROUGHPUT 或 SERVICE_TIME,则意味着 Server 端 Connect Time Load Balancing 判断节点负载的依据是 LBA。此时如果 GOAL 设为 THROUGHPUT,则 v$servicemetric 的对应 service 的 GOODNESS 值是根据 CPUPERCALL 和 DBTIMEPERCALL 来计算;如果 GOAL 设为 SERVICE_TIME,则 v$servicemetric 的对应 service 的 GOODNESS 值是根据 CALLSPERSEC 和 DBTIMEPERSEC 来计算。

接下来再看一下 Oracle RAC 11gR2 下的 Server 端 Connect Time Load Balancing:
Oracle RAC 11gR2 下的 Server 端 Connect Time Load Balancing 和 Oracle RAC 10gR2 下的 Server 端 Connect Time Load Balancing 类似,只不过因为 Oracle RAC 11gR2 里引入了 SCAN,所以 Oracle RAC 11gR2 环境下 remote_listener 应设置为 SCAN:port。

这里假设是一个 4 节点的 Oracle RAC 11gR2 环境,tnsnames.ora 中的连接串是如下这样:

(DESCRIPTION =
(FAILOVER=ON)(LOAD_BALANCE=ON)
(ADDRESS = (PROTOCOL = TCP)(HOST = MySCAN)(PORT = 1521))
(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME =RAC11g)))

再在各个节点的 tnsnames.ora 中加入如下设置(注意这里监听的是各个节点的 VIP):

LISTENER_RAC1 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC1-vip)(PORT = 1521)) LISTENER_RAC2 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC2-vip)(PORT = 1521)) LISTENER_RAC3 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC3-vip)(PORT = 1521)) LISTENER_RAC4 =
(ADDRESS = (PROTOCOL = TCP)(HOST = RAC4-vip)(PORT = 1521))

然后只需要在初始化参数文件里加入如下设置就能实现 Server 端 Connect Time Load Balancing 了:

RAC1.local_listener=LISTENER_RAC1
RAC2.local_listener=LISTENER_RAC2
RAC3.local_listener=LISTENER_RAC3
RAC4.local_listener=LISTENER_RAC4
*.remote_listener= MySCAN:1521

当做了上述设置后,Oracle 11gR2 RAC 的 Server 端 Connect Time Load Balancing 也就配好了。此时所有的 SCAN Listener 实际上是都知道所有RAC节点的负载情况的。当先经过一层客户端的 Connect Time Load Balancing,比如这里随机的连到了第二个 SCAN VIP 所对应的 SCAN Listener 上后,这时候这个 SCAN Listener 会选择一个实际负载较低的 RAC 节点,然后把连接请求转移(redirect)到这个负载较低的 RAC 节点的 Local Listener 上——这就是 Oracle RAC 11gR2 的 server 端的 Connect Time Load Balancing。

实际上,local_listener 和 remote_listener 支持复杂的连接串的写法。所以,可以在初始化参数里面直接设置 local_listener 和 remote_listener,而不需要在 $ORACLE_HOME/network/admin 下的 tnsnames.ora 中做上述设置。

来看一个两节点的 Oracle 11gR2 RAC 的实例。这个环境中用了 hosts 文件,hosts 文件内容如下所示:

10.1.15.64  P550-05-LA
10.1.15.84 P550-05-LA-vip
9.2.1.64 P550-05-LA-priv
10.1.15.65 P550-05-LB
10.1.15.85 P550-05-LB-vip
9.2.1.65 P550-05-LB-priv
10.1.15.86 nbsdev-scan

从上述内容可以看到,现在节点 1 的 vip 是 10.1.15.84,节点 2 的 vip 是 10.1.15.85,整个 RAC 环境的 SCAN vip 是 10.1.15.86。

先登陆节点 1,看一下节点 1 上的 local_listener 和 remote_listener 的设置: SQL> show parameter instance_name;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
instance_name string NBSDEV1

SQL> show parameter local_listener;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
local_listener string (DESCRIPTION=(ADDRESS_LIST=
(ADDRESS=(PROTOCOL=TCP)(HOST=10.1.15.84)(PORT=1522))))

SQL> show parameter remote_listener;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
remote_listener string nbsdev-scan:1522

再登陆节点 2,看一下节点 2 上的 local_listener 和 remote_listener 的设置: SQL> show parameter instance_name;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
instance_name string NBSDEV2

SQL> show parameter local_listener;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
local_listener string (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=
(PROTOCOL=TCP)(HOST=10.1.15.85)(PORT=1522))))

SQL> show parameter remote_listener;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
remote_listener string nbsdev-scan:1522

上述环境 server 端的 Connect Time Load Balancing 实际上已经配置好了,但从如下内容可以看到,我们并没有在 $ORACLE_HOME/network/admin 下的 tnsnames.ora 中配置相关的 local_listener 和 remote_listener:
ora11g:/nbsdu01/app/oracle/product/11.2/network/admin>cat tnsnames.ora

# tnsnames.ora Network Configuration File: /nbsdu01/app/oracle/product/11.2/network/
admin/tnsnames.ora
# Generated by Oracle configuration tools. NBSDEV =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = nbsdev-scan)(PORT = 1522))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = NBSDEV)
)
)

四、最后来介绍 Runtime Connection Load Balancing

Runtime Connection Load Balancing 是指从连接池中取得已有连接时的 Connection Load Balancing。

之前无论是 Oracle RAC 9iR2/10gR2,还是 Oracle RAC 11gR2,在存在连接池的情况下,单纯的 server 端的 Connect Time Load Balancing 并不能保证当应用需要从连接池里取得一个已有连接的时候,这个连接就指向了节点负载较低的那个节点。因为这个时候应用从连接池里取得的连接很可能就是连接池初始化的时候形成的连接,只是反映了连接池初始化那个时间点的各个节点的负载情况,而随着时间的推移,各个节点的负载情况可能发生了很大的变化,所以这种情况下连接池的连接很可能并不是真正的 Load Balance。

实际上 FAN 就是为了解决上述问题而设计的。能支持 FAN events 的连接池通过订阅 FAN HA events,就可以保证当应用需要从连接池里取得一个已有连接的时候,这个连接肯定是有效的连接,不会指向那些 service 宕掉或者 instance 崩溃的节点(之前已经提到过,这是通过 FCF 来实现的:当支持 FAN events 的连接池接收到包含 instance/service 宕掉的 FAN HA events 后,原先 cache 在连接池里的跟这个 instance/service 相关的连接马上会被标记为失效,同时这些连接会被清除);另外一个方面,能支持 FAN events 的连接池通过订阅 LBA events,就能近乎实时地知道各个 RAC 节点实际的负载情况,所以当应用需要从连接池里取得一个已有连接的时候,连接池就能提供给用户一个真正的负载较低的 RAC 节点,这样就实现了真正的 Runtime Connection Load Balancing。

现在介绍两个通过订阅 LBA events 实现 Runtime Connection Load Balancing 的例子。

例三:JDBC Runtime Connection Load Balancing
这里的 JDBC 连接是指 JDBC thin 连接,要实现 JDBC Runtime Connection Load Balancing,只需要做如下两步即可:
1、首先要按照“例一:JDBC Fast Connection Failover (FCF)”里那样把 JDBC FCF 设置好;
2、启用 LBA events:
srvctl modify service -d RAC11g -s Email -B SERVICE_TIME -j SHORT
这里首先把 CLB_GOAL 设置成了 SHORT,接着把 GOAL 设置成了 SERVICE_TIME,这两者缺一不可,CLB_GOAL 和 GOAL 的各个值的详细含义已经在 Connect Time Load Balancing 里详细解释过,这里不再赘述。

例四:ODP.NET Runtime Connection Load Balancing
ODP.NET的Runtime Connection Load Balancing的 启用跟“例二:ODP.NET Fast Connection Failover (FCF)”里的步骤类似,只需要做如下 4 步就好,注意这里第 1 步和第 4 步跟“例二:ODP.NET Fast Connection Failover (FCF)”里的相应步骤是不一样的:
1、把对应 service的AQ Notification 打开,同时设置 CLB_GOAL和GOAL:
srvctl modify service -d RAC11g -s Email -q TRUE -B SERVICE_TIME -j SHORT
2、把 aq_tm_processes 的值设为 1;
3、赋予指定用户 de-queue 的权限:
exec dbms_aqadm.grant_queue_privilege('DEQUEUE','SYS.SYS$SERVICE_METRICS', <your username=>);
4、在.NET连接串里设置 Load Balancing=true,如下所示:

con.ConnectionString =
"User Id=user_name;Password=password;Data Source=odpapp;" +
"Min Pool Size=10;Connection Lifetime=120;Connection Timeout=60;" +
"Load Balancing=true;Incr Pool Size=5;Decr Pool Size=2";

至此我们已经详细的描述了 RAC 环境下的连接管理。

作为这篇文章的结束,最后我们来阐述一下如何在 Oracle 数据库里设置 TCP timeout。

1、32 位 Windows上Oracle 数据库 11.2.0.1 默认的操作系统TNS连接 timeout 的时间大概是 20 秒:

16:27:26 SQL> conn scott/tiger@cuihua112;
ERROR:
ORA-12170: TNS: 连接超时 16:27:49 SQL>

这里可以看到,从开始连接到连接超时的间隔时间是 23 秒,去掉输入上述连接串“conn scott/tiger@cuihua112”所耗费的时间,可以知道在 32 位 Windows上Oracle 数据库 11.2.0.1 默认的操作系统TNS连接 timeout 的时间大概是 20 秒。

2、修改一下 client 端 sqlnet.ora 文件,将 TNS 连接 timeout 时间修改为 5 秒(这是通过设置 SQLNET.OUTBOUND_CONNECT_TIMEOUT 来实现的):

# sqlnet.ora Network Configuration File:
C:\app\cuihua\product\11.2.0\dbhome_1\network\admin\sqlnet.ora
# Generated by Oracle configuration tools. # This file is actually generated by netca. But if customers choose to
# install "Software Only", this file wont exist and without the native
# authentication, they will not be able to connect to the database on NT. SQLNET.AUTHENTICATION_SERVICES= (NTS)
SQLNET.OUTBOUND_CONNECT_TIMEOUT = 5
NAMES.DIRECTORY_PATH= (TNSNAMES, EZCONNECT)

从如下结果可以看到,从开始连接到连接超时的间隔时间是 7 秒,去掉输入上述连接串“conn scott/tiger@cuihua112”所耗费的时间,可以知道上述 5 秒超时的设置确实生效了。

16:28:34 SQL> conn scott/tiger@cuihua112;

ERROR:
ORA-12170: TNS: 连接超时 16:28:41 SQL>

3、注释掉上述 SQLNET.OUTBOUND_CONNECT_TIMEOUT = 5,在 tnsnames.ora 的 cuihua112 的连接串中将 TNS 连接 timeout 时间设置为 15 秒:

CUIHUA112 =
(DESCRIPTION =
(CONNECT_TIMEOUT=5)(RETRY_COUNT=2)
(ADDRESS = (PROTOCOL = TCP)(HOST = 172.20.190.11)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = cuihua112)
)
)

从如下结果可以看到,从开始连接到连接超时的间隔时间是 17 秒,去掉输入上述连接串“conn scott/tiger@cuihua112”所耗费的时间,可以知道上述 15 秒超时的设置确实生效了。

16:31:08 SQL> conn scott/tiger@cuihua112;

ERROR:
ORA-12170: TNS: 连接超时 16:31:25 SQL>

4、同时启用 SQLNET.OUTBOUND_CONNECT_TIMEOUT = 5 和上述 cuihua112 的连接串,从结果里可以看到,tnsnames.ora 中的设置取代了(override)了 sqlnet.ora 中的 TNS 连接 timeout 的设置。即在同时启用的情况下,现在的 TNS 连接 timeout 设置还是为 15 秒。

16:33:12 SQL> conn scott/tiger@cuihua112;

ERROR:
ORA-12170: TNS: 连接超时 16:33:29 SQL>

这里可以看到,从开始连接到连接超时的间隔时间是 17 秒,去掉输入上述连接串“conn scott/tiger@cuihua112”所耗费的时间,可以知道是 15 秒超时的设置生效了。

5、在 tnsnames.ora 的 cuihua112 的连接串中将 TNS 连接 timeout 时间设置为 40 秒,这已经超过了 TNS 连接默认的 timeout 值,从如下测试里可以看到,Oracle 数据库会以 tnsnames.ora 中的设置为准(当然,这里前提条件是单次连接的 CONNECT_TIMEOUT 设置不要超过操作系统 TNS 连接默认的 timeout 值,如果超过了则 CONNECT_TIMEOUT 设置失效,但 RETRY_COUNT 的设置依然有效)。

CUIHUA112 =
(DESCRIPTION =
(CONNECT_TIMEOUT=10)(RETRY_COUNT=3)
(ADDRESS = (PROTOCOL = TCP)(HOST = 172.20.190.11)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = cuihua112)
)
)

这里 CONNECT_TIMEOUT 设置为 10 秒,RETRY_COUNT 设置为 3,实际上就将 TNS 连接 timeout 时间设置成了40秒

16:52:52 SQL> conn scott/tiger@cuihua112;

ERROR:
ORA-12170: TNS: 连接超时 16:53:33 SQL>

这里可以看到,从开始连接到连接超时的间隔时间是 41 秒,去掉输入上述连接串“conn scott/tiger@cuihua112”所耗费的时间,可以知道是 40 秒超时的设置生效了。

五、总结

这篇文章详细介绍了 RAC 环境下的连接管理及其相关内容,主要针对 RAC 环境下连接管理所涉及到的 Connect Time Load Balancing、Runtime Connection Load Balancing、Connect Time Connection Failover 和 Runtime Connection Failover 等内容,同时也描述了包括 TAF、ONS、FCF、FAN 和 LBA 等其它一些相关内容。

【转】Oracle RAC 环境下的连接管理的更多相关文章

  1. Oracle RAC 环境下的连接管理(转) --- 防止原文连接失效

    崔华老师的文章!!! 这篇文章详细介绍了Oracle RAC环境下的连接管理,分别介绍了什么是 Connect Time Load Balancing.Runtime Connection Load ...

  2. Oracle RAC 环境下的连接管理

    http://blog.csdn.net/cyxlxp8411/article/details/7634003

  3. Oracle RAC环境下定位并杀掉最终阻塞的会话-续

    之前在<Oracle RAC环境下定位并杀掉最终阻塞的会话>中,最终使用一个SQL查询出RAC实例之间的所有阻塞关系.但是实际在某些极端的生产环境,是不允许执行复杂的SQL语句,即使允许执 ...

  4. bay——Oracle RAC环境下ASM磁盘组扩容.docx

    https://www.cnblogs.com/polestar/p/10115263.html Oracle RAC环境下ASM磁盘组扩容 生产环境注意调整以下参数: +++++++++++++++ ...

  5. Oracle RAC 环境下的 v$log v$logfile

    通常情况下,在Oracle RAC 环境中,v$视图可查询到你所连接实例的相关信息,而gv$视图则包含所有实例的信息.然而在RAC环境中,当我们查询v$log视图时说按照常理的话,v$log视图应当看 ...

  6. Oracle RAC环境下如何更新patch(Rolling Patch)

    Oracle RAC数据库环境与单实例数据库环境有很多共性,也有很多异性.对于数据库补丁的更新同样如此,都可以通过opatch来完成.但RAC环境的补丁更新有几种不同的更新方式,甚至于可以在零停机的情 ...

  7. Oracle RAC环境下怎样更新patch(Rolling Patch)

        Oracle RAC数据库环境与单实例数据库环境有非常多共性,也有非常多异性.对于数据库补丁的更新相同如此.都能够通过opatch来完毕.但RAC环境的补丁更新有几种不同的更新方式,甚至于能够 ...

  8. Oracle RAC环境下定位并杀掉最终阻塞的会话

    实验环境:Oracle RAC 11.2.0.4 (2节点) 1.模拟故障:会话被级联阻塞 2.常规方法:梳理找出最终阻塞会话 3.改进方法:立即找出最终阻塞会话 之前其实也写过一篇相关文章: 如何定 ...

  9. Oracle RAC环境下ASM磁盘组扩容

    生产环境注意调整以下参数: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ...

随机推荐

  1. [Python爬虫] scrapy爬虫系列 <一>.安装及入门介绍

    前面介绍了很多Selenium基于自动测试的Python爬虫程序,主要利用它的xpath语句,通过分析网页DOM树结构进行爬取内容,同时可以结合Phantomjs模拟浏览器进行鼠标或键盘操作.但是,更 ...

  2. Oracle 函数中动态执行语句

    函数: 1 create or replace function fn_test(tablename in varchar2) return number is sqls ); rtn ):; beg ...

  3. bind() live()和delegate 区别

    Event bubbling (aka event propagation)冒泡 我们的页面可以理解为一棵DOM树,当我们在叶子结点上做什么事情的时候(如click一个a元素),如果我们不人为的设置s ...

  4. 使用jquery修改css中带有!important的样式属性

    当CSS中含有!important的样式属性时,普通的修改方式是会出现失败的.如下: <div class="test">使用jquery修改css中带有!import ...

  5. 解决12306.cn网站验证码获取提示“基础连接已经关闭: 未能为 SSL/TLS 安全通道建立信任关系“的问题

    https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=sjrand&0.8967564508222368 这是图片的访问网址 大家看清楚 ...

  6. 那些在学习iOS开发前就应该知道的事(part 1)

    英文原文:Things I wish I had known before starting iOS development—Part 1 http://www.cocoachina.com/ios/ ...

  7. 【原】MyEclipse8.5集成Tomcat7时启动错误:Exception in thread “main” java.lang.NoClassDefFoundError

    解决方法: MyEclipse->Window->Preferences->MyEclipse->Servers->Tomcat->Tomcat 6.x->L ...

  8. MiniDao普通项目集成方案

    1.导入必要的jar包: 2.spring配置文件增加如下配置: <!-- Hibernate工具栏配置--> <bean id="miniDaoHiberCommonDa ...

  9. liunx 套接字编程(Linux_C++)

    网络中的进程是如何通信的? 在网络中进程之间进行通信的时候,那么每个通信的进程必须知道它要和哪个计算机上的哪个进程通信.否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行 ...

  10. svg―Raphael.js Library(一)

    Raphael是一个用于在网页中绘制矢量图形的Javascript库,它使用SVG W3C推荐标准和VML作为创建图形的基础,可以通过JavaScript操作DOM来轻松创建出各种复杂的柱状图.饼图. ...