存储过程和函数的区别?
存储过程 是一段代码(过程),存储在数据库中的SQL组成。一个存储过程通常用于完成一段业务逻辑;
函数通常是数据库已定义的方法。
存储过程和函数执行不是由程序调用,也不是手动启动。而是由事件触发、激活从而实现执行。
一个函数通常专注与某个功能,视为其他程序服务的,需要在其他语句中调用函数才可以,而存储过程不能被其他调用,是自己执行通过call执行。
存储过程和函数都是属于某个数据库。
函数有一个返回值,而存储过程是通过参数返回的,可以有多个或者没有。
函数一般情况下是用来计算并返回一个计算结果,而存储过程一般是用来完成特定的数据操作。
oracle中存储过程和函数都可以返回值,但是函数必须要返回值并且一般返回一个值,而存储过程则没有这个限制。
创建存储过程?
MySQL创建调用:
create procedure 存储过程名称(in|out|inout 参数名称 参数类型,……)
begin
过程体;
end
1、查询
查询所有存储过程状态
show procedure status;
查看对应数据库下所有存储过程状态
show procedure status where db="数据库名";
查看名称包含Student的存储过程状态
show procedure status where name like "%Student%";
查询存储过程详细代码
show create procedure 过程名;
2、修改
alter procedure 过程名([过程参数[,…]])过程体;
3、删除
drop procedure 过程名;
注:不能在一个存储过程中删除另一个存储过程,只能调用另一个存储过程。
调用存储过程
mysql存储过程用call和过程名以及一个括号,括号里面根据需要,加入参数,参数包括输入参数、输出参数、输入输出参数调用。
call 存储过程名([过程参数[,...]])
Oracle创建调用:
create or replace procedure sample_proc
as --声明
msg varchar2(50);
begin --执行
msg:='Hello world';--为参数赋值
dbms_output.put_line('你好的英文为:'||msg);--输出参数
exception --存贮过程异常
;
end;
--有三种执行语法
--执行语法1
call sample_proc();
--执行结果
sample_proc ) 成功。
你好的英文为:Hello world
--执行语法2
exec sample_proc;
--执行结果
匿名块已完成
你好的英文为:Hello world
--执行语法3
set serveroutput on
begin
sample_proc;
end;
--执行结果
匿名块已完成
你好的英文为:Hello world
函数的创建调用?
MySQL创建:
DELIMITER $$ --定义结束符。MySQL默认的结束符是分号,但是函数体中可能用到分号。为 了避免冲突,需要另外定义结束符。
DROP FUNCTION IF EXISTS function_name$$ --如果函数genPerson已经存在了,就删除掉。
CREATE FUNCTION function_name(name varchar(20)) RETURNS varchar(50) --创建函数genPerson,函数的参数是name,返回值是varchar(50)。
BEGIN --函数体放在BEGIN 与 END之间。
DECLARE str VARCHAR(50) DEFAULT ''; --DECLARE声明变量,str类型是varchar(50),默认值是空。
SET @tableName=name;
SET str=CONCAT('create table ', @tableName,'(id int, name varchar(20));');
--CONCAT连接多个字符串。
return str; --RETURN 返回拼接后的字符串str。
END $$
DELIMITER ;
Oracle创建:
CREATE [OR REPLACE] FUNCTION function_name
[ (parameter [,parameter]) ]
RETURN return_datatype
IS | AS
[declaration_section]
BEGIN
executable_section
[EXCEPTION
exception_section]
END [function_name];
执行:
--调用
select function_name('参数');
--删除
DROP FUNCTION function_name;
定时器的创建调用?
MySQL创建:
CREATE EVENT IF NOT EXISTS 计划名
-- 计划频率和开启计划时间或者是计划执行的时间
-- 前一个可以实现持续的计划调度,后一个到指定时间进行调度,执行完结束,没有持续性
ON SCHEDULE [EVERY 10 SECOND STARTS TIMESTAMP 开启时间] [AT 开启时间]
-- 当计划执行完成时,是否删除
ON COMPLETION [NOT] PRESERVE
do call 存储过程
---查看数据库是否开启调度
-- 查看是否开启调度
show variables like '%event_scheduler%';
-- value为OFF,未开启;
-- 开启
SET GLOBAL event_scheduler = 1;
--关闭和开启指定定时器
ALTER EVENT 定时器名 ON COMPLETION PRESERVE [ENABLE][DISABLE];
-- 开启ENABLE,关闭DISABLE
--删除定时器
drop EVENT 定时器名;
Oracle创建:
DECLARE job_test number; -- DECLARE 用来定义unlockTest_timer 的定时器编号
BEGIN
SYS.DBMS_JOB.SUBMIT(
job => unlockTest_timer, --job 指的是定时器编号,在DECLARE 中已经声明
what => 'pro_test;', --what 指的是要执行的存储过程,也就是SQL语句
NEXT_DATE => sysdate, --next_date 指的是下次执行时间
INTERVAL => 'sysdate+1/(24*60)' --interval 指的是每次执行时间的间隔时间 这里是一分钟执行一次
);
Commit;
End;
--定时器创建好后,会自动执行。
--查看在执行的定时器,job-定时器编号
SELECT job, next_date, next_sec, failures, broken FROM user_jobs;
begin
触发器创建和调用
触发器组成:
1、触发事件---DML或DDL语句。
2、触发时间---是在触发事件发生之前(before) 还是之后(after) 触发
3、触发操作---使用PL/SQL块进行相应的数据库操作
4、触发对象---表、视图、模式、数据库
5、触发频率---触发器内定义的动作被执行的次数,包括语句级和行级。
MySQL
触发器是按照BEFORE触发器、行操作、AFTER触发器的顺序执行的,其中任何一步发生错误都不会继续执行剩下的操作。如果是对事务表进行的操作,那么会整个作为一个事务被回滚,但是如果是对非事务表进行的操作,那么已经更新的记录将无法回滚,
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name --trigger_name:触发器的名称,不能与已经存在的触发器重复;
trigger_time trigger_event --trigger_time:{ BEFORE | AFTER },表示在事件之前或之后触发;trigger_event::{ INSERT |UPDATE | DELETE },触发该触发器的具体事件;
ON tbl_name FOR EACH ROW
trigger_body --tbl_name:该触发器作用在tbl_name上;
SHOW TRIGGERS trigger_name;--命令查看触发器
DROP TRIGGER trigger_name;--删除
Oracle
触发器:类似于AOP(面向切面编程)中的拦截器;不能传递参数,输出参数,也不能显示调用,只有满足触发器条件时会由Oracle自动调用。
限制
1、触发器不接受参数
2、一个表上最多可有12个触发器,但同一时间、同一事件、同一类型的触发器只能有一个。并各触发器之间不能有矛盾。
3、一个表上的触发器越多,该表上的DM操作的性能影响就越大
4、触发器代码的大小不能超过32K。如需要大量的代码创建触发器,则首先创建过程,然后在触发器中使用CALL语句调用过程
5、触发器代码只能包含SELECT、INSERT、UPDATE和DELETE语句,
6、不能包含DDL语句(CREATE、ALTER和DROP) 和事务控制语句(COMMIT、ROLLBACK和SAVEPOINT)
语句触发器
1、语句触发器是指当执行DML语句时被隐含执行的触发器
2、如果在表上针对某种DML操作创建了语句触发器,则当执行DML操作时会自动地执行触发器的相应代码
3、为了审计DML操作,或者确保DML操作安全执行时,可以使用语句触发器
触发器用途很多,例如用户清算购物车后将会触发待收货的数据库
--创建触发器
create or replace trigger tri_test
before--触发之前
update or delete--更新或删除
on emp
for each row--对行进行操作
begin dbms_output.put_line(:old.sal);--old表示数据库旧值
insert into demo(id) values (:new.sal);--new新值
end;
update emp set sal=888 where empno=7788;
commit;
--代码解释:先执行创建触发器代码后,再执行最后的更新语句。当更新恩平、表后将会输出数据库中本来存放的值,并且触发添加语句在demo表中插入一条语句。
--查询
select * from 表 where object_type='TRIGGER';
--删除
drop trigger ...;
drop trigger ..,;
定时任务的创建和调用
MySQL:
自 MySQL5.1.6起,增加了一个非常有特色的功能–事件调度器(Event Scheduler),可以用做定时执行某些特定任务,来取代原先只能由操作系统的计划任务来执行的工作。事件调度器有时也可称为临时触发器(temporal triggers),因为事件调度器是基于特定时间周期触发来执行某些任务,而触发器(Triggers)是基于某个表所产生的事件触发的,区别也就在这里。
--在使用这个功能之前必须确保 event_scheduler 已开启,可执行 :
mysq> SET GLOBAL event_scheduler = 1;
# 或
mysql> SET GLOBAL event_scheduler = ON;
也可以在配置文件中添加设置 : event_scheduler=1
也可以直接在启动命令加上 : --event_scheduler=1
--查看当前是否已开启事件调度器 :
mysql> SHOW VARIABLES LIKE 'event_scheduler';
# 或
mysql> SELECT @@event_scheduler;
# 或
mysql> SHOW PROCESSLIST;
--创建事件(CREATE EVENT)
CREATE EVENT [IFNOT EXISTS] event_name
ON SCHEDULE schedule
[ON COMPLETION [NOT] PRESERVE] --设置这个事件是执行一次还是持久执行,默认为 NOT PRESERVE
[ENABLE | DISABLE] --可设置该事件创建后状态是否开启或关闭,默认为ENABLE
[COMMENT 'comment'] --可以给该事件加上注释
DO sql_statement;
--修改事件(ALTER EVENT)
ALTER EVENT event_name
[ON SCHEDULE schedule]
[RENAME TO new_event_name]
[ON COMPLETION [NOT] PRESERVE]
[COMMENT 'comment']
[ENABLE | DISABLE]
[DO sql_statement]
--临时关闭事件
mysql> ALTER EVENT e_test DISABLE;
# 开启事件
mysql> ALTER EVENT e_test ENABLE;
--删除事件(DROP EVENT)
DROP EVENT [IF EXISTS] event_name
Oracle:
--创建job
begin
sys.dbms_job.submit(job => 1, --代表的是号码,第几个定时任务
what => 'sys_mailing_list_job;', --这个是调用的你想使用的存储过程切记要打;不然会报错
next_date => to_date('20-08-2018 14:05:00', 'dd-mm-yyyy hh24:mi:ss'), --这个是下次调用的时间
interval => 'trunc(sysdate,''hh'')+(60+5)/(24*60)');
commit; --这个是间隔时间 。我这个代表的是每个小时的过5 比如 1:05,2:05,3:05...24小时的
end;
--删除
job: dbms_job.remove(jobno); -- jobno就是你得任务号
--修改要执行的操作:
job:dbms_job.what(jobno, what); --指定任务号以及存储过程
--修改下次执行时间:
dbms_job.next_date(jobno, next_date); --指定任务号的时间
--修改间隔时间:
dbms_job.interval(jobno, interval); --指定任务号的间隔时间
--启动job:
dbms_job.run(jobno); --指定任务号启动
--停止job:
dbms.broken(jobno, broken, nextdate); --broken为boolean值 N代表启动,Y代表没启动(STOP)
Linux常用命令
运行jar包:java -jar
查看日志全文:cat + 日志名称.log
实时查看日志:tail -f +日志名称.log
模糊查询日志:grep -r -200 "查询内容" 日志名称.log
查看进程:
ps命令
-a,查看所有
-u,以用户(user)的格式显示
-x, 显示后台进程运行参数
-ef,以全格式显示进程所有信息,包括父进程Pid,创建人,创建时间,进程号。等等
ps -l 列出与本次登录有关的进程信息;
ps -aux 查询内存中进程信息;
ps -aux | grep ... 查询...进程的详细信息;
top 查看内存中进程的动态信息;
kill -9 pid 杀死进程。
并行和并发有什么区别?
- 并行(Parallel):指两个或者多个事件在同一时刻发生,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
线程和进程的基本概念、线程的基本状态以及状态之间的关系?
- 一个线程是进程的一个顺序执行流程。一个进程中的全部线程共享同一个堆空间。线程本身有一个供程序执行时的栈,一个进程中可以包含多个线程。
- 线程的基本状态:新建、就绪、运行状态、阻塞状态、死亡状态。
- 新建状态:利用NEW运算创建了线程对象,此时线程状态为新建状态,调用了新建状态线程的start()方法,将线程提交给操作系统,准备执行,线程将进入到就绪状态。
- 就绪状态:由操作系统调度的一个线程,没有被系统分配到处理器上执行,一旦处理器有空闲,操作系统会将它放入处理器中执行,此时线程从就绪状态切换到运行时状态。
- 运行状态:线程正在运行的过程中,碰到调用Sleep()方法,或者等待IO完成,或等待其他同步方法完成时,线程将会从运行状态,进入到阻塞状态。
- 死亡状态:线程一旦脱离阻塞状态时,将重新回到就绪状态,重新向下执行,最终进入到死亡状态。一旦线程对象是死亡状态,就只能被GC回收,不能再被调用。
守护线程是什么?
- 守护线程又称为后台线程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
- 正常创建的线程都是普通线程,或称为前台线程,守护线程与普通线程在使用上没有什么区别,但是他们有一个最主要的区别是在于进程的结束中。当一个进程中所有普通线程都结束时,那么进程就会结束。如果进程结束时还有守护线程在运行,那么这些守护线程就会被强制结束
- 在 Java 中垃圾回收线程就是特殊的守护线程
通俗来说:守护线程是个服务线程,准确地来说就是服务其他的线程。
创建线程有哪几种方式?
- 继承Thread类(真正意义上的线程类),是Runnable接口的实现。
- 实现Runnable接口,并重写里面的run方法。
- 使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。
sleep() 和 wait() 有什么区别?
- 类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
线程的 run() 和 start() 有什么区别?
- start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
- run() 可以重复调用,而 start() 只能调用一次。
- 第二次调用start() 必然会抛出运行时异常
创建线程池有哪几种方式?
- newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
- newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
- newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
- newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
- newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
- newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
- ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。
在 Java 程序中怎么保证多线程的运行安全?
- 使用安全类,比如 Java. util. concurrent 下的类。
- 使用自动锁 synchronized。
- 使用手动锁 Lock。
9. 什么是死锁?怎么防止死锁?
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
防止死锁方法:
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
尽量使用 Java. util. concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
尽量减少同步的代码块。
synchronized 和 volatile 的区别是什么?
- volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
- volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
- volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
synchronized 和 Lock 有什么区别?
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
synchronized 和 ReentrantLock 区别是什么?
- ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
- ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
- ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。
为什么使用线程池?
由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题;可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存。
Activiti?
一般选择为Activiti作为工作流模块的引擎。
Activiti拥有更简洁健壮的接口;
Activiti拥有更友好的用户体验;
Activiti支持启动引擎后随时热部署;
Activiti拥有更友好易用的Eclipse编译插件和在线插件;
Activiti依赖更少的jar包。
rabbitmq 的消息是怎么发送的?
首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息,客户端和 rabbit server 之间会创建一个 tcp 连接,一旦 tcp 打开并通过了认证(认证就是你发送给 rabbit 服务器的用户名和密码),你的客户端和 RabbitMQ 就创建了一条 amqp 信道(channel),信道是创建在“真实” tcp 上的虚拟连接,amqp 命令都是通过信道发送出去的,每个信道都会有一个唯一的 id,不论是发布消息,订阅队列都是通过这个信道完成的。
rabbitmq怎么保证消息的稳定性?
提供了事务的功能。
通过将 channel 设置为 confirm(确认)模式。
rabbitmq怎么避免消息丢失?
消息持久化;
ACK确认机制;
设置集群镜像模式;
消息补偿机制。
rabbitmq 有哪些重要的角色?
RabbitMQ 中重要的角色有:生产者、消费者和代理:
- 生产者:消息的创建者,负责创建和推送数据到消息服务器;
- 消费者:消息的接收方,用于处理数据和确认消息;
- 代理:就是 RabbitMQ 本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。
rabbitmq 有哪些重要的组件?
- ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用。
- Channel(信道):消息推送使用的通道。
- Exchange(交换器):用于接受、分配消息。
- Queue(队列):用于存储生产者的消息。
- RoutingKey(路由键):用于把生成者的数据分配到交换器上。
- BindingKey(绑定键):用于把交换器的消息绑定到队列上。
rabbitmq 中 vhost 的作用是什么?
vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
要保证消息持久化成功的条件有哪些?
- 声明队列必须设置持久化 durable 设置为 true.
- 消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)。
- 消息已经到达持久化交换器。
- 消息已经到达持久化队列。
rabbitmq 持久化有什么缺点?
持久化的缺地就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题。
rabbitmq 有几种广播类型?
- fanout: 所有bind到此exchange的queue都可以接收消息(纯广播,绑定到RabbitMQ的接受者都能收到消息);
- direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息;
- topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息;
rabbitmq 怎么实现延迟消息队列?
- 通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;
- 使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。
rabbitmq 集群有什么用?
集群主要有以下两个用途:
- 高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用;
- 高容量:集群可以承载更多的消息量。
rabbitmq 节点的类型有哪些?
- 磁盘节点:消息会存储到磁盘。
- 内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型。
rabbitmq 集群搭建需要注意哪些问题?
- 各节点之间使用“--link”连接,此属性不能忽略。
- 各节点使用的 erlang cookie 值必须相同,此值相当于“秘钥”的功能,用于各节点的认证。
- 整个集群中必须包含一个磁盘节点。
rabbitmq 每个节点是其他节点的完整拷贝吗?为什么?
不是,原因有以下两个:
- 存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据;
- 性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。
rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况?
如果唯一磁盘的磁盘节点崩溃了,不能进行以下操作:
- 不能创建队列
- 不能创建交换器
- 不能创建绑定
- 不能添加用户
- 不能更改权限
- 不能添加和删除集群节点
唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西。
rabbitmq 对集群节点停止顺序有要求吗?
RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失。
kafka 可以脱离 zookeeper 单独使用吗?为什么?
kafka 不能脱离 zookeeper 单独使用,因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。
kafka 有几种数据保留的策略?
kafka 有两种数据保存策略:按照过期时间保留和按照存储的消息大小保留。
kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 kafka 将如何处理?
这个时候 kafka 会执行数据清除工作,时间和大小不论那个满足条件,都会清空数据。
什么情况会导致 kafka 运行变慢?
- cpu 性能瓶颈
- 磁盘读写瓶颈
- 网络瓶颈
使用 kafka 集群需要注意什么?
- 集群的数量不是越多越好,最好不要超过 7 个,因为节点越多,消息复制需要的时间就越长,整个群组的吞吐量就越低。
- 集群数量最好是单数,因为超过一半故障集群就不能用了,设置为单数容错率更高。