本篇安装软件版本
azkaban 3.72.0
azkaban 3.72.0 配置用户代理
clickhouse 25 集群+切片+副本方式部署
redis6.2
redis 7.4.9 集群模式
## azkaban
azkaban能够用来解决可视化例行调度工作流flow,一个flow由多个job组成,每个job就是程序要做的具体事情,同一个工作流下的job可以有依赖关系,一般情况下写job描述文件的时候,都是固化不变的东西,常常会发生改变的写在job要调度的脚本里,当然如果你想,在job描述文件中也可以直接写命令。azkaban3.x不再只是一个flow中多个job,还支持了flow之间的依赖。前面的2.x集群文档装了2.5的azkaban,不过随着版本的更新,3.x的azkaban和2.x的内部变换还是有明细差别的,主要是需要一些其他的插件,所以这里记录一下搭建方法,且是从源码编译开始
第一步:从官网的连接跳转,去git上拉取需要的版本,https://azkaban.github.io/

第二步:上传到你的编译环境后开始调整源码中的拉取地址以及对应服务,因为咱这是国内懂得都懂
首先编译环境最基础的git、maven要有,3.72我本地用的maven3.9.9,解压源码包后会得到azkaban-3.72.0,后续操作都在此路径下
随后更新你的gcc环境
yum install -y gcc-c++*
之后在源码主路径下,找到build.gradle文件,更改其中如下内容,把依赖的镜像改成国内的
buildscript {
repositories {
maven{ url 'https://linkedin.jfrog.io/artifactory/open-source/'}
}
....其他任何内容都别动
....如果编译中遇到compileClasspat 这种报错重试不行就换阿里源 http://maven.aliyun.com/nexus/content/groups/public
}
之后去下载一个nodejs,https://nodejs.org/dist/,下载的版本看源码主路径下的azkaban-web-server/build.gradle文件,注意!!不是主路径下了,下载的同时把该文件中的download改成false

至于你下载的node,放在你服务器的一个自定义路径下,配置环境变量,别下载错版本和所属系统就行
export NODE_HOME=/opt/node-v8.10.0
export PATH=$PATH:$NODE_HOME/bin
"/etc/profile" 99L, 2607C written
[root@node1 node-v8.10.0]# source /etc/profile
[root@node1 node-v8.10.0]# node -v
v8.10.0
[root@node1 node-v8.10.0]# npm -v
5.6.0
之后对于azkaban来讲,需要设置两个软件连接,到系统的bin下面
[root@node1 bin]# ln -s /opt/node-v8.10.0/lib/node_modules/npm/bin/npm-cli.js /usr/bin/npm
[root@node1 bin]# ln -s /opt/node-v8.10.0/bin/node /usr/bin/node
然后回到源码主路径,查看./gradle/wrapper/gradle-wrapper.properties文件,看里面的gradle,需要那个版本

随后在同网址中下载一份,但是现在不要做啥变动,如果你用的和我是一个版本可以去阿里镜像中下载https://mirrors.aliyun.com/github/releases/gradle/gradle-distributions/v4.6.0/,先下载一份,是为了预防编译时,这个插件下载不下来的话,在操作下面的内容
也就是将下载到的gradle.zip包,解压到一个自定义路径下,随后配置环境变量,它的HOME和PATH指定到bin
# GRADLE_USER_HOME
export GRADLE_USER_HOME=解压路径
export PATH=$PATH:$GRADLE_USER_HOME/bin
然后修改./gradle/wrapper/gradle-wrapper.properties文件,改下面两个东西
distributionUrl=你下载到的包名
zipStoreBase=GRADLE_USER_HOME
这样再次执行编译时,会把该插件包的最终路径爆出来,然后把zip包放到对应路径下就行,如果你和我是同版本,就在编译之前直接放进去就行
[root@node1 azkaban-3.72.0]# ./gradlew build installDist -x test
Downloading file:/opt/wy/azkaban-3.72.0/gradle/wrapper/gradle-4.6-all.zip
Exception in thread "main" java.io.FileNotFoundException: /opt/wy/azkaban-3.72.0/gradle/wrapper/gradle-4.6-all.zip (No such file or directory)
第三步:现在开始编译,在源码主路径下执行自带的编译脚本
./gradlew build installDist -x test
编译中,如果失败的话,先尝试重试一般是网络问题,尤其在执行npm install的时候特别容易遇到,你重试的时候编译会中继而不是从头开始,实在不行先执行./gradlew clean清理后重编译
出现绿色显眼的BUILD SUCCESSFUL就是成功了

Azkaban源文件编译成功后,会在源码路径下开始的,各自azkaban-*/build/distributions目录下生成基于Windows和Linux的安装包文件
第四步:这一步开始就很好说了,和之前的2.5一样去对应路径下,拿到下面三个包
数据库生成资源:azkaban-db/build/distributions/azkaban-db-0.1.0-SNAPSHOT.tar.gz
执行器:azkaban-exec-server/build/distributions/azkaban-exec-server-0.1.0-SNAPSHOT.tar.gz
solo模式用的包可以不用一般用来学习:azkaban-solo-server/build/distributions/azkaban-solo-server-0.1.0-SNAPSHOT.tar.gz
web模块:azkaban-web-server/build/distributions/azkaban-web-server-0.1.0-SNAPSHOT.tar.gz
在前面2.5搭建的叫two-server模式,web和执行器在不同进程中,而3.x我们用multiple-executor模式部署,这也是应用在生产环境下的常见方式,不同模式有啥区别可以看https://zhuanlan.zhihu.com/p/630413027这里就不做展开了
这里准备总计部署3个节点
| 节点名称 | 服务 |
|---|---|
| node1 | web、mysql |
| node2 | exec |
| node3 | exec |
第五步:无论那种模式,在mysql上都是一样的,将db.tar上传到mysql所在的服务器,准备一个mysql数据库,并加载初始化sql包,注意azkaban和ranger有个一样的臭毛病,只能使用旧版本的mysql-driver包,所以mysql库不要太高,5.x的就行,同时注意不要用udf8,如果
CREATE DATABASE IF NOT EXISTS azkaban CHARACTER SET Latin1 COLLATE latin1_swedish_ci;
use azkaban;
source /opt/wy/azkaban-db-0.1.0-SNAPSHOT/create-all-sql-0.1.0-SNAPSHOT.sql;
#如果是一个新库,不要忘了连接授权
GRANT ALL ON *.* TO 'root'@'%' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
加载好db-all资源之后,进数据库单独改下面的字段为utf8
alter table projects modify column description varchar(2048) character set utf8;
第六步:将web放到node1节点对应的位置上,解压,并重命名一个方便的路径
和two模式一样web需要在主目录下生成key文件
keytool -keystore keystore -alias jetty -genkey -keyalg RSA
解释:
keytool:这是Java密钥和证书管理工具的名称,用于管理和操作密钥库和密钥对。
-keystore keystore:这个参数指定了密钥库文件的名称。在这个例子中,密钥库文件被命名为keystore(没有指定路径,所以它会在当前目录下创建或查找)。密钥库文件可以存储密钥对(公钥和私钥)以及证书链。
-alias jetty:这个参数用于为生成的密钥对指定一个别名(alias)。在这个例子中,别名被设置为jetty。别名用于在密钥库中唯一标识密钥对。
-genkey:这个参数告诉keytool执行生成密钥对的操作。
-keyalg RSA:这个参数指定了要使用的密钥算法。在这个例子中,它指定了RSA算法。RSA是一种广泛使用的非对称加密算法,它使用一对密钥:一个公钥用于加密,一个私钥用于解密。
注意,我把英文翻译了,对着着看就行
#这个密码是keystoreSSL认证时的密码
1、输入 keystore 密码:
2、再次输入新密码:
#下面这几步回车跳过就可以
3、您的名字与姓氏是什么?
[Unknown]:
4、您的组织单位名称是什么?
[Unknown]:
5、您的组织名称是什么?
[Unknown]:
6、您所在的城市或区域名称是什么?
[Unknown]:
7、您所在的州或省份名称是什么?
[Unknown]:
#这一步开始再次输入
8、该单位的两字母国家代码是什么
[Unknown]: CN
CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CN 正确吗?
[否]: y
#这个是jetty工具调用这个文件时的认证密码,一般为例后期配置文件不搞混两个密码,配置时和前面的密码保持一致
9、输入jetty的密码
10、再次输入密码
进入conf路径,修改azkaban.properties文件,该配置的时候,一定一定要注意,不要直接复制我下面的全部内容覆盖脚本,因为配置文件单行配置不能有空格,这是properties的格式标准,要不然会有问题
azkaban.name=Test #服务器UI名称,用于服务器上方显示的名字
azkaban.label=My Local Azkaban #描述
azkaban.color=#FF3601 #UI颜色
azkaban.default.servlet.path=/index #进入首页的路径一般不改
web.resource.dir=/opt/azkaban-web372/web #要使用绝对路径指定到自带的资源上
default.timezone.id=Asia/Shanghai #默认时区,改为亚洲/上海
# Azkaban UserManager class
user.manager.class=azkaban.user.XmlUserManager #用户权限管理默认类不用变
user.manager.xml.file=/opt/azkaban-web372/conf/azkaban-users.xml #用户配置,用绝对路径指定自带的
# Loader for projects
executor.global.properties=/opt/azkaban-web372/conf/global.properties #globa配置文件所在位置 使用绝对路径
azkaban.project.dir=projects
# 这个不用动
velocity.dev.mode=false
#jetty服务器属性
jetty.use.ssl=false #新版本中的参数不要改就保持false,下面的正常配置就行
jetty.ssl.port=8443
jetty.maxThreads=25 #最大线程数
jetty.port=8443 #jetty端口,一定要手动添加
# KeyStore for SSL ssl相关配置 注意密码和证书路径 , 前提是开启了ssl
jetty.keystore=/opt/azkaban-web372/keystore #SSL文件,用绝对路径
jetty.password=123456 #SSL文件密码
jetty.keypassword=123456 #jetty主密码与keystore文件相同
jetty.truststore=/opt/azkaban-web372/keystore #SSL文件,用绝对路径
jetty.trustpassword=123456 #SSL文件密码
# mail settings #邮件配置,自带文件配置不够,没有的追加(现在先别配置,最后会说)
mail.sender= #azkaban的邮件账号
mail.user= #azkaban的邮件账号,一般情况sender和user是一样的除非邮件服务商有单独说明,例如网易163邮箱经过测试就是一样的
mail.host= #发送邮箱的服务它的smtp服务器地址
mail.port= #发送邮箱的服务它的smtp服务端口号
mail.smtp.ssl.enable=false #smtp服务是否用ssl端口协议,一般邮箱服务提供商都有两个端口选择需要的就行
mail.password= #邮箱服务的授权码
job.failure.email= #任务失败时发送邮件的地址,但是源码中没有实际使用所以不用管
job.success.email= #任务成功时发送邮件的地址,也是不用管
lockdown.create.projects=false
cache.directory=cache #缓存目录
database.type=mysql #数据库类型
mysql.port=3306 #数据库端口号
mysql.host=node1 #数据库连接地址
mysql.database=azkaban #数据库实例名
mysql.user=root #数据库用户名
mysql.password=123456 #数据库密码
mysql.numconnections=100 #数据库最大连接数
#exec的端口!!!!!这里必须在配置文件中写死,下面告诉你为什么
executor.port=12321
#下面的就是multiple模式的基础配置,保持默认先不要改
azkaban.use.multiple.executors=true
#一定!!!!一定!!!要注释掉这个执行调度的过滤条件,否则容易造成任务无法调度
#azkaban.executorselector.filters=StaticRemainingFlowSize,MinimumFreeMemory,CpuStatus
azkaban.executorselector.comparator.NumberOfAssignedFlowComparator=1
azkaban.executorselector.comparator.Memory=1
azkaban.executorselector.comparator.LastDispatched=1
azkaban.executorselector.comparator.CpuUsage=1
更改conf下的azkaban-users.xml文件,里面是用户的管理
<azkaban-users>
<user username="azkaban" password="azkaban" roles="admin" groups="azkaban" />
<user username="metrics" password="metrics" roles="metrics"/>
<!-- 这个文件一般不用大改,但是要注意第三行的这个是登录azkaban时候的账户和密码有需要可以添加 -->
<user username="admin" password="admin" roles="admin,metrics"/>
<role name="admin" permissions="ADMIN" />
<role name="metrics" permissions="METRICS"/>
</azkaban-users>
最后你要把web部署完的所有文件,交给一个linux的普通用户以及他的组,业内通常是azkaban:azkaban,linux建用户改密码、改文件用户所属的命令我这里就不展示了,你都能从操作源码编译开始部署,应该不可能不会这些基本命令
第七步:将exec放到node2服务器上,解压,并更改名字
[root@node2 azkaban-exe372]# pwd
/opt/azkaban-exe372
进入conf路径,改azkaban.properties文件,整体上和web的区别,在于exe没有web所以不用管web的配置,只需关注下面的配置
#Azkaban
#时区
default.timezone.id=Asia/Shanghai
# Loader for projects
executor.global.properties=/opt/azkaban-exe372/conf/global.properties #globa配置文件所在位置 使用绝对路径
azkaban.project.dir=projects
#web模块的地址
azkaban.webserver.url=http://node1:8443
# Azkaban JobTypes Plugins
#jobtype 插件所在位置,用绝对路径
azkaban.jobtype.plugin.dir=/opt/azkaban-exe372/plugins/jobtypes
database.type=mysql
mysql.port=3306
mysql.host=node1
mysql.database=azkaban
mysql.user=root
mysql.password=123456
mysql.numconnections=100
# Azkaban Executor settings
executor.maxThreads=50
executor.flow.threads=30
#exec的端口要和web中的一致,也是必须写死!!!!!
executor.port=12321
随后在exec的主路径下,新建如下内容
mkdir -p plugins/jobtypes
cd plugins/jobtypes/
vi commonprivate.properties
追加:
azkaban.native.lib=false
execute.as.user=false
memCheck.enabled=false
随后和web模块一样,把 exec 的所有东西都给一个普通用户, 用scp发送到node3上,当然node3上也要是属于同样的普通用户,scp发送的时候直接指定,或者有其他的方式也行
第八步:开始启动服务,一定要使用所属的普通用户来操作
必须先启动exec!!!!并且启动时,会在运行命令的路径下生成out日志文件,打开看一下没有除了日志框架之外的报错就行
#在exec的安装目录下启动exec-server
bin/start-exec.sh
随后3.x的azkaban,web服务启动前,首先需要手动激活exec,方法也很简单,用bash像exec运行节点的端口发送一个请求就行,这也是为什么我上面强调必须把端口写死的原因,如果不写死,后期运维,你只能去查询数据库得到随机启动的端口,并且每生成一个新的端口azkaban就会写一条新的数据到表里面,长久来看对运维很不友好
curl http://{exec运行节点}:{exec的端口}/executor?action=activate
会返回一个:{"status":"success"}
网上其他文献你可能会看到直接改数据库中exec表的active字段为1,我这里要说的是这种方法是错的!!!如果你这样操作了会导致exec无法完全激活,最终的结果就是web去调度exec发现exec并不能执行任务,任务直接死掉页面上还不会有任何日志一片空白,同时out日志文件也没有任何有用的信息。之后就可以启动web了
#在web的安装目录下启动web-server
bin/start-web.sh
访问web节点的8443端口/index接口就可以进入web页面,点击右上角的create projects,新建一个项目对象主体,后面的调度流程都会绑定到不同的项目主体下,我这里建立个一个测试用的default

随后把下面两个脚本发送到所有exec节点的路径上,由于azkaban的特性,所以你想要执行一个脚本就要所有执行节点都要有,至于同步工具截至这个版本还没有内置,只能自己搞
截止这里要特别说明一个很重要的!!!很重要的事情!!!!很重要的事情!!!!我要强调三次!!!这个事情真的相当重要
这个重要的事情就是:如果你现在配置azkaban是为了商用,就大概率要配置azkaban的用户代理,毕竟要做权限隔离,但它的代理能力配置后,默认不允许root代理其他用户,大家可以去官方仓库的lssues中搜Failed to dispatch queued execution task_b because reached azkaban.maxDispatchingErrors,你会看到里面话题分支会教你如果改动权限代理限制,总之涉及到源码改动就很麻烦,讲到这里大家就应该能明白,为什么上面把所有部署好的文件给了一个普通用户和用户组,并用这个用户的身份运维azkaban了。但如果你不商用化,或者说你商用化,但不需要用户代理,没那个需求,你就自己用,那么这种情况用root用户启动不受影响,但建议不要用root,防止出事,因为这种情况下azkaban执行任务的身份始终是服务启动用户。而上面的流程到现在要启动测试任务了,也没有配用户代理,是因为这玩意配置有很多细节,因此这里先走一个不用代理功能的流程,给普通使用者使用,而代理功能我会在下面单独补充。言归正传!这里我们开始准备脚本
test1.sh脚本:
#!/bin/bash
echo "脚本1 被调用" > /opt/jobs/test1.log
test2.sh脚本:
#!/bin/bash
echo "脚本2 被调用" > /opt/wy/test2.log
这里顺带说一点:使用后期大部分job都是调用脚本,写脚本的时候注意,如果涉及字符串的嵌套,一定不能图方便在字符串中写带转义符的双引号,会导致azkaban不识别内容
随后准备job包,创建两个job文件,分别为task_a.job、task_b.job
# task_a.job 调用test1脚本
type=command
command=/opt/jobs/test1.sh
# task_b.job 调用test2脚本,但它依赖于task_a
type=command
command=/opt/jobs/test2.sh
# 这是关键,定义了依赖关系
dependencies=task_a
把这两个文件,打一个zip压缩包,注意不要有父路径,也就是说包中解压出来就是所有文件

随后在azkaban的default项目主体右上角,点击upload,上传这个zip包,开始生成一个工作流,要明白的是,工作流不管有多少个,他们和项目主体对象都是,以一个完整的zip包达到一对一的关系,所以你想调整某个工作流节点,从新上传job.zip包就好,不存在单独上传某个job这种,最后每个工作流的名称是其下最后一个job的名称,这个有需要就注意控制一下,尤其是一个项目主体对象下有多个工作流的时候


鼠标移动到下属的job上,可以单独执行,或待着依赖关系一起执行

点击Execute Flow可以看到这个工作流的依赖关系

下面有两个按钮,左面的是配置例行调度规则,右面的是单次触发执行,执行的任务明细列表会展示在Job List中

Executions是所有执行次的记录,你可以在里面看到不同id编号的执行记录

进入一个执行记录能看到它的调度情况、日志等信息


如果你在尝试运行的时候,瞬间失败,这通常都是由于web模块出现了错误,去web模块启动时生成的out日志中按照Execution id找日志就可以。至于任务跑在那个exec上,可以在日志开头看到

至于调度,就更简单了azkaban用的是cron表达式来配置调度时间,总共七个时间控制单位,秒目前恒定是0不允许更改、分钟、小时、月中天、年中月、周中天、年一般是省略可以不写

后续使用中,对于中途失败的任务,修正完bug,只需要点击历史Execution右上角的执行就行,会自动掉过成功的job,这也就是为什么开头提到job描述文件中只写能固化的东西的原因,因为如果你job中直接写了要操作的细节,而不是一个脚本,要调整一个job后状态就没了

或者你也可以在job文件中写重试策略
retries=3
retry.backoff=300000
retries表示最大重试次数,retry.backoff表示每次重试的间隔时间(单位为毫秒)。例如,上述配置会在任务失败后尝试重试3次,每次间隔5分钟。
还有一种比较麻烦的办法, 在工作流界面中,右键点击已成功的任务,选择“Disable”,将其标记为跳过状态。然后点击“Execute Flow”,工作流将从未完成的任务开始执行。但是执行完不改回来那就废了
在后期使用中有一个非常!!!非常!!!重要的功能就是报警邮箱,在上面配置web的azkaban.properties文件时,我提到一个邮箱配置,azkaban的邮件功能需要你给它准备一个专门的邮箱,其实就和我们自己开发Java发送邮件是一样的,你要给它使用的账号和邮箱授权码,以网易163邮箱为例,如何申请这个账号可以看->https://blog.csdn.net/dudadudadd/article/details/132164365,然后把得到的账号相关信息配置到web模块的配置文件中,重启web之后就可以在调度界面的Notification设置成功或失败邮件接收方,如果web模块中没配置或者配置的有问题,页面上这个配置即使你写了也是不生效的


默认的邮件内容不太好看,而且它的部分内容确实有问题,你可以在源码的DefaultMailCreator中修改,它是写死在代码里面的

网上这种方面的代码案例还是有的,找一找就有了,比如下面就是一个较为直观的改造代码用例
@Override
public boolean createErrorEmail(ExecutableFlow flow, EmailMessage message, String azkabanName, String clientHostname, String clientPortNumber, String... vars) {
ExecutionOptions option = flow.getExecutionOptions();
//获取失败后收件人日志
List<String> emailList = option.getFailureEmails();
int execId = flow.getExecutionId();
//获取job内容列表
Map<String, Object> executableNodeInfo = getExecutableNodeInfo(flow);
HashMap<String, Object> newNode = new HashMap<>(executableNodeInfo);
List<Map<String, Object>> nodes = (ArrayList<Map<String, Object>>) newNode.get("nodes");
int succeededNum = 0;
List<Map<String, String>> failedList = new ArrayList<>();
//获取所有job节点
for (Map<String, Object> map : nodes) {
String status = map.get("status").toString();
if ("SUCCEEDED".equals(status)) {
succeededNum++;
} else if ("FAILED".equals(status) || "FAILED_FINISHING".equals(status)) {
Map<String, String> failedMap = new HashMap<>();
String id = map.get("id").toString();
//获取后续影响节点
String outStr = getOut(map);
failedMap.put("id", id);
failedMap.put("out", outStr);
Set<String> impactAll = new HashSet<>();
//节点失败后后续影响节点分析
impactAnalysis(nodes, outStr, impactAll);
String impactAllStr = impactAll.toString();
failedMap.put("impactAll", impactAllStr.substring(1, impactAllStr.length() - 1));
failedList.add(failedMap);
}
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String newDate = simpleDateFormat.format(new Date(flow.getStartTime()));
String endTime = simpleDateFormat.format(new Date(flow.getEndTime()));
if (emailList != null && !emailList.isEmpty()) {
message.addAllToAddress(emailList);
message.setMimeType("text/html;charset=UTF-8");
message.setSubject("项目名称:" + flow.getProjectName() + ",execution '" + execId + "' 今日调度作业运行:失败");
message.println("<h3>作业开始时间:" + newDate + "</h3>");
message.println("<h3>作业结束时间:" + endTime + "</h3>");
message.println("<table width=\"400\" style=\"border: 1pt solid rgb(232, 242, 249); border-image: none;\" border=\"1\" cellpadding=\"0\">");
message.println("<tr style=\"background: rgb(66, 139, 202); padding: 0.75pt;\">" +
"<td align=\"center\" style=\"color: white; font-size: 13.5pt;\">作业状态</td>" +
"<td align=\"center\" style=\"color: white; font-size: 13.5pt;\">记录数</td>" +
"<td align=\"center\" style=\"color: white; font-size: 13.5pt;\">作业数据日期</td>" +
"</tr>");
message.println("<tr>" +
"<td>调度作业总数</td>" +
"<td>" + nodes.size() + "</td>" +
"<td>" + LocalDate.now().minusDays(1) + "</td>" +
"</tr>");
message.println("<tr>" +
"<td>当前成功执行</td>" +
"<td>" + succeededNum + "</td>" +
"<td>" + LocalDate.now().minusDays(1) + "</td>" +
"</tr>");
message.println("<tr style=\"background: crimson; padding: 0.75pt;\">" +
"<td style=\"color: white;\">当前运行失败</td>" +
"<td style=\"color: white;\">" + failedList.size() + "</td>" +
"<td style=\"color: white;\">" + LocalDate.now().minusDays(1) + "</td>" +
"</tr>");
message.println("</table>");
message.println("<h3>当前失败作业明细</h3>");
message.println("<table width=\"550\" style=\"border: 1pt solid rgb(232, 242, 249); border-image: none;\" border=\"1\" cellpadding=\"0\">");
message.println("<tr style=\"background: rgb(66, 139, 202); padding: 0.75pt;\">" +
"<td align=\"center\" style=\"color: white; font-size: 13.5pt;\">失败作业名称</td>" +
"<td align=\"center\" style=\"color: white; font-size: 13.5pt;\">后续影响第二级作业</td>" +
"<td align=\"center\" style=\"color: white; font-size: 13.5pt;\">后续影响所有的作业</td>" +
"</tr>");
for (Map<String, String> failedMap : failedList) {
message.println("<tr>" +
"<td>" + failedMap.get("id") + "</td>" +
"<td>" + failedMap.get("out") + "</td>" +
"<td>" + failedMap.get("impactAll") + "</td>" +
"</tr>");
}
message.println("</table>");
return true;
}
return false;
}
/**
* 获取后续影响节点
*/
private static String getOut(Map<String, Object> map) {
Object out = map.get("out");
String outStr = null;
if (out != null) {
outStr = out.toString();
outStr = outStr.substring(1, outStr.length() - 1);
}
return outStr;
}
/**
* 节点失败后,所有后续影响节点分析
*/
private void impactAnalysis(List<Map<String, Object>> nodes, String outStr, Set<String> impactAll) {
if (outStr != null) {
String[] sonNodeIds = outStr.split(",");
Set<String> collect = nodes.stream().filter(node -> {
String sonId = node.get("id").toString();
for (String sonNodeId : sonNodeIds) {
if (sonId.equals(sonNodeId.trim()) && node.get("out") != null) {
return true;
}
}
return false;
}).map(DefaultMailCreator::getOut).collect(Collectors.toSet());
for (String set : collect) {
impactAnalysis(nodes, set, impactAll);
}
impactAll.addAll(collect);
}
}
/**
* 获取job内容列表
*/
private Map<String, Object> getExecutableNodeInfo(ExecutableNode node) {
HashMap<String, Object> nodeObj = new HashMap<String, Object>();
nodeObj.put("id", node.getId());
nodeObj.put("status", node.getStatus());
nodeObj.put("startTime", node.getStartTime());
nodeObj.put("endTime", node.getEndTime());
//前置节点
if (node.getInNodes() != null && !node.getInNodes().isEmpty()) {
nodeObj.put("in", node.getInNodes());
}
//后置节点
if (node.getInNodes() != null && !node.getOutNodes().isEmpty()) {
nodeObj.put("out", node.getOutNodes());
}
if (node instanceof ExecutableFlowBase) {
ExecutableFlowBase base = (ExecutableFlowBase) node;
ArrayList<Map<String, Object>> nodeList = new ArrayList<Map<String, Object>>();
for (ExecutableNode subNode : base.getExecutableNodes()) {
Map<String, Object> subNodeObj = getExecutableNodeInfo(subNode);
if (!subNodeObj.isEmpty()) {
nodeList.add(subNodeObj);
}
}
nodeObj.put("nodes", nodeList);
}
return nodeObj;
}
如果你想做一些其他的改造就需要自己研究了,比如监控任务的执行3.72是在FlowRunner类中
最后要说的是,开头有提到azkaban3.x之后可以一个项目主体下存在多个flow,也就是工作流,且这些工作流之间也可以相互依赖,一定要注意,只能同一个项目主体对象下的工作流可以相互依赖不能跨工作流,具体使用时只需要按如下格式书写job文件即可
例如,第一个工作流为 a,它的描述文件不需要有任何调整,该做什么就做什么即可,比如调度一个脚本
type=command
command=/opt/jobs/test.sh
第二个工作流,想要想要依赖这个工作流,则需要第一个job描述文件写如下内容,如 b.job
type=flow
flow.name=a
随后的才是自己工作流内部需要干的事情,比如存在一个 c.job
type=command
dependencies=b
command=echo "c 依赖 b,执行完成"
但是这样的配置有一个缺陷!!!!!!当你调度它的时候,会发现只需要给最下游的flow配置调度规则即可,执行时会自动触发依赖的上游,反之如果你给上游配置了调度规则,执行的时候也是独立的,他不会去影响下游,这就反应出一个问题,如果一个工作流不止被一个下游引用,且在这样每一个调度规则都是一个独立执行的单元只是会参考关系图全量执行的情况下,那作为上游的工作流就会被次幂执行,即便你多个下游工作流第一个job用的是同一个type=flow,这点非常最重要,一定要清楚明白,azkaban在这种需求场景确实上有缺陷,大家可以本地测试一下,我这边也是经过测试验证过的,白话将就是azkaban内部并没有上游状态共享这一说,直接就是硬性的重复执行,就导致你先只依赖azkaban自己达到一个flow多个下游flow,且作为上游的flow只执行一次,就只能把它写成顺序执行,也就是flow-c依赖flow-b,flow-b依赖flow-a这种,可是这样写的话也就没必要多个工作流了,一个就搞定了,因此这种type=flow使用方式非常少。通常使用azkaban,又有这种需求的,都是自己写上游状态检查程序,就比如写一个java程序阻塞当前工作流,内部循环每5秒判断一次上游条件,如果满足则System.exit(int status)退出,shell中通过$?去获取并返回做下游执行的依据。而在国内有很多自己做中台的公司,调度系统是绕不开,这种场景下实现方法就多了,比如用kafka等等的方式,实现某个单独资源,如表分区、集群文件、hdfs上的一个资源等等,或者是任务维度的上游,这些都看具体实现了
azkaban 配置用户代理
用户代理,需要手动编译一个核心执行文件,3.72版本用来编译的源码文件在主路径下的az-exec-util/src/main/c路径,下面有一个execute-as-user.c文件,执行如下的命令将它编译
[root@node1 logs]# cd /opt/wy/azkaban-3.72.0/az-exec-util/src/main/c/
[root@node1 c]# pwd
/opt/wy/azkaban-3.72.0/az-exec-util/src/main/c
[root@node1 c]# ll
total 4
-rw-rw-r--. 1 root root 3976 May 1 2019 execute-as-user.c
[root@node1 c]# gcc execute-as-user.c -o execute-as-user
[root@node1 c]# chown root execute-as-user
[root@node1 c]# ll
total 20
-rwxr-xr-x. 1 root root 13688 Aug 30 00:40 execute-as-user
-rw-rw-r--. 1 root root 3976 May 1 2019 execute-as-user.c
[root@node1 c]# chmod 6050 execute-as-user
[root@node1 c]# ll
total 20
---Sr-s---. 1 root root 13688 Aug 30 00:40 execute-as-user
-rw-rw-r--. 1 root root 3976 May 1 2019 execute-as-user.c
上面这一套命令的核心目的就是生成一个---Sr-s---权限、所属用户和用户组为root的execute-as-user文件,注意后期这个文件做除了scp之外的移动权限会恢复成普通文件,需要在执行一次6050,至于文件所属不用边就root就行
现在把得到的execute-as-user文件,scp发送的exec节点的一个路径下,一般是部署路径下的plugins/jobtypes下
然后更改plugins/jobtypes/commonprivate.properties文件,修改后所有的exec节点要同步这个配置文件
#不用带末尾的execute-as-user azkaban会自己凭借
azkaban.native.lib=/opt/azkaban-exec372/plugins/jobtypes
execute.as.user=true
memCheck.enabled=false
# 这个配置的作用是改动代理后的用户组,当你有需要给所有在azkaban跑任务的用户共享某些资源,但又不向在权限中给其他人所有权限,你就可以配上这个从而用用户组来限制,不过一般不需要,而且在特殊场景下没法配置,比如原来用户所属组也有非用户自身独属的资源
azkaban.group.name=azkaban
之后重启azkaban整个服务,这可以正常使用了。但要注意一下几点
- 配置代理后,azkaban登录用户名在最上面修改过的azkaban-users.xml中维护,所有可登录用户均需要是exec节点的一个linux用户,azkaban代理进行时,就是以同名用户身份执行任务
- 所有exec节点的的启动,你最好写一个脚本,使得每次启动在同一个路径下生成日志文件,因为日志中存在executions文件夹,这个是任务运行时的工作路径,默认是个特使权限,不需要做什么更改。至于在脚本上其实就是
ssh "cd exec安装路径/logs && 启动",我相信都会 - 一定要合理的控制脚本存放以及会用到的日志输出路径,不过正常用户自己的身份用自己的文件就行,但是前面也说了目前azkaban需要所有节点都有任务脚本,所以合理的控制存放是有必要的。不过你可以对于轻量任务直接写在job文件里面了
clickhouse
ClickHouse 是俄罗斯的Yandex于2016年开源的列式存储数据库(DBMS),主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告。
以下面的表为例:

采用行式存储时,数据在磁盘上的组织结构为:

好处是想查某个人所有的属性时,可以通过一次磁盘查找加顺序或者索引辅助读取就可以。但是当想查所有人的年龄时,需要不停的查找,或者全表扫描才行,遍历的很多数据不需要的数据。
而采用列式存储时,数据在磁盘上的组织结构为:

这时想查所有人的年龄只需把年龄那一列拿出来就可以了。
第一步:安装包的下载,去官网上即可,选择符合部署环境的即可,这里选择下载25版本的CentOS 安装包,官网:https://clickhouse.com/docs/install/redhat

进入下载站点后,你可以不下载rpm包,在站点路径选择用tgz包,但版本就更不上rpm包了,配置方式上也有点小差别,比rpm包麻烦一点,拿的对应版本如下的4个包

第二步:所有安装节点,修改linux的内核限制,这里顺带提一嘴正式部署的时候,最好给Clickhouse单独的机器,因为它在执行负载较大的时候,会使得服务器短暂的夯住,这也是现在为什么用的少,而且市场上有比他更优秀好用的,比如前面装的百度开源给Apache的doris,向外兼容mysql协议,向内兼容大数据存算一体,JDK21之后支持存算分离,而且和presto一样可以挂在catalog,比Clickhouse好用多了
修改/etc/security/limits.conf,存在则修改,如果已经很大了就不用动,否则追加如下内容
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072
修改/etc/security/limits.d/20-nproc.conf,同上变动如下内容
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072
关闭SELINUX ,修改/etc/selinux/config
SELINUX=disabled
关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
更新yum依赖
yum install -y libtool *unixODBC*
之后重复服务器,所有安装节点重复以上步骤
第三步:在所有要安装Clickhouse的节点上,上传安装包,并执行安装
rpm -ivh clickhouse-common-static-25.8.1.5101.x86_64.rpm
rpm -ivh clickhouse-common-static-dbg-25.8.1.5101.x86_64.rpm
rpm -ivh clickhouse-server-25.8.1.5101.x86_64.rpm
rpm -ivh clickhouse-client-25.8.1.5101.x86_64.rpm
注意,在安装clickhouse-server包的时候,会自动生成一个默认的default用户,并提示你输入密码,正常输入就行
四个包全部装好之后,单节点的服务就可以启动测试运行一下了
[root@node1 clickhouse]# clickhouse start
chown -R clickhouse: '/var/run/clickhouse-server/'
Will run sudo --preserve-env -u 'clickhouse' /usr/bin/clickhouse-server --config-file /etc/clickhouse-server/config.xml --pid-file /var/run/clickhouse-server/clickhouse-server.pid --daemon
Waiting for server to start
Waiting for server to start
Server started
[root@node1 clickhouse]# clickhouse-client
ClickHouse client version 25.8.1.5101 (official build).
Connecting to localhost:9000 as user default.
Password for user (default):
Connecting to localhost:9000 as user default.
Connected to ClickHouse server version 25.8.1.
node1 :)
客户端的常用参数有以下这些
--host, -h 服务端的 host 名称, 默认是 'localhost'
--port 连接的端口,默认值: 9000。
--user, -u 用户名。 默认值: default。
--password 密码。 默认值: 空字符串。
--query, -q 非交互模式下的查询语句.
--database, -d 默认当前操作的数据库。 默认值: default
--multiline, -m 允许多行语句查询
--format, -f 使用指定的默认格式输出结果。
--time, -t 非交互模式下会打印查询执行的时间到窗口。
--stacktrace 如果出现异常,会打印堆栈跟踪信息。
--config-file 配置文件的名称。
如果单台启动不报错,就可以停止了,我们下面的工作就是把这些单台机器联系起来成为集群
[root@node1 clickhouse]# clickhouse stop
/var/run/clickhouse-server/clickhouse-server.pid file exists and contains pid = 1257.
The process with pid = 1257 is running.
Sent terminate signal to process with pid 1257.
Waiting for server to stop
/var/run/clickhouse-server/clickhouse-server.pid file exists and contains pid = 1257.
The process with pid = 1257 is running.
Waiting for server to stop
/var/run/clickhouse-server/clickhouse-server.pid file exists and contains pid = 1257.
The process with pid = 1257 is running.
Waiting for server to stop
Now there is no clickhouse-server process.
Server stopped
第四步:所有节点,修改默认的/etc/clickhouse-server/config.xml配置文件,注意!!!这个配置文件不能用scp同步其他节点,这也是clickhouse的另一个麻烦点之一,如果你scp了会发现部分节点无法正常启动,即使重试能够小概率启动,服务也无法正常响应
改之前将配置文件改为 755
chmod 755 /etc/clickhouse-server/config.xml
vi /etc/clickhouse-server/config.xml
如果你是ipv4,则放开如下配置的注释
<listen_host>::</listen_host>
如果你是ipv6,则则放开如下配置的注释
<listen_host>0.0.0.0</listen_host>
做完这一步,就可以让其他的客户端连接了
之后更具需要更改端口号,由于我的测试集群9000端口给hadoop的namanode了,所以我这里改成9500
<tcp_port>9500</tcp_port>
改端口的时候注意,有个<http_port>8123</http_port>这个是给内置接口绑定的端口一般用默认的就行,除非你的环境也冲突了
随后修改,如下路径,它是Clickhouse存放数据的路径,该路径需要已存在,且属于clickhouse用户,至于clickhouse用户在rpm安装时会自己建
<path>/var/lib/clickhouse/</path>
我这里改为:
<path>/opt/clickhouseData/</path>
随后准备这个路径
mkdir /opt/clickhouseData
chown clickhouse:clickhouse -R /opt/clickhouseData
第五步:所有节点,在/etc/clickhouse-server/config.xml文件更改zk集群和切片、副本信息
这里我要说另一个clickhouse非常!!!!非常!!!讨厌的事情!让运维它的人巨难受,在config.xml文件中,你会看到有下面这句话

但是我要告诉你的是,这个外置集群描述文件的xml文件标签规范,官方没给,不止没给,不同的版本还会有不一样的情况发生,就非常让人恼火,所以如果你信了它文件里写的东西上来就去准备集群描述文件,那你就废了,而且就现在安装的25版本自带的config文件,它里面默认的集群描述标签是非注释状态,就导致同样的如果你不看这个文件直接新建默认路径下的描述文件,那你也废了,由于要处理这个非注释的集群描述标签,所以这里不要它了直接在核心配置文件中的模板标签中改,每个节点都是这样
在进入文件后,搜索zookeeper标签,释放注释,修改你的zk集群地址信息
<!-- ZooKeeper is used to store metadata about replicas, when using Replicated tables.
Optional. If you don't use replicated tables, you could omit that.
See https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/
Note: ClickHouse Cloud https://clickhouse.com/cloud has ClickHouse Keeper automatically configured for every service.
-->
<zookeeper>
<node>
<host>node1</host>
<port>2181</port>
</node>
<node>
<host>node2</host>
<port>2181</port>
</node>
<node>
<host>node3</host>
<port>2181</port>
</node>
</zookeeper>
随后搜索 remote_servers 标签,开始定义clickhouse集群信息,这个标签就是上面说的非注释集群描述标签,就导致上来用外置的集群描述配置,就不生效,巨难受
<!-- Configuration of clusters that could be used in Distributed tables.
https://clickhouse.com/docs/en/operations/table_engines/distributed/
Note: ClickHouse Cloud https://clickhouse.com/cloud has the cluster preconfigured and dynamically scalable.
-->
<remote_servers>
<!-- 默认的一级子标签是default,改成你自己的 -->
<mycluster>
<!-- 这个不用动,它是用来分离账号密码的,一般用不到 -->
<!-- <secret></secret> -->
<!-- 每一个 shard 就是一个切片,切片其实是个逻辑概念,下面我会解释 -->
<shard>
<!-- 一定要改成true,让这个切片下的副本内部同步 -->
<internal_replication>true</internal_replication>
<!-- 这个是当前切片在写入数据时的权重,权重越高后续使用中写数据越多,其实就是写数据优先考虑这个切片下的副本 -->
<!-- <weight>1</weight> -->
<!-- 每一个 replica 就是一个副本-->
<replica>
<host>node1</host>
<port>9500</port>
<!-- priority 是副本的读取权重 -->
<!-- <priority>1</priority> -->
<!-- 这个是标识当前副本是否使用加密链接读取,如果你设置为 1 ,则使用该服务时,链接端口使用的是tcp_port_secure配置的端口号,默认是9440 -->
<!-- <secure>0</secure> -->
<!-- 这个不要随便改,它是当前服务的绑定那个网络连接的监听,我们在最外面已经配置了,为了不冲突这个不要配置 -->
<!-- <bind_host>10.0.0.1</bind_host> -->
</replica>
<replica>
<host>node2</host>
<port>9500</port>
</replica>
</shard>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node3</host>
<port>9500</port>
</replica>
</shard>
</mycluster>
</remote_servers>
搜索macros标签,这个标签时节点宏,如果你要配置,不同节点不能一样,可以不配,这里只是为了让大家知道它是干嘛的,它的作用是后期使用可以通过 {} 在sql中站位,clickhouse运行时执行节点会自动替换自身配置文件中写的值,按照业内规范,切片的标号要和前面集群信息的副本关系相对应,副本名写的是节点自身的host名字
<!-- Substitutions for parameters of replicated tables.
Optional. If you don't use replicated tables, you could omit that.
See https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/#creating-replicated-tables
-->
<macros>
<shard>01</shard>
<replica>node1</replica>
</macros>
上面这个文件最关键的配置点,是切片和副本以及宏之间的关系,通俗的讲,可以理解为切片本身就是以不同副本存在,或者说是副本组成的一个逻辑单位,而不是Hadoop数据块那样的块自身+副本数的组合,每个节点在表定义时,则可以使用宏映射,最终效果就是在其上定义的表,能够清晰的分辨负责哪一个切片的哪一个副本。而这个切片的标识会被存放在zookeeper上,至于切片名称本质上最终写什么都行,在业内切片一般是从01开始的编号,副本名则使用节点名,这样就能直观的发现不同切片的副本分布在哪个节点上。为了集群的稳定如果分两个切片两个副本,那么就需要4台机器来部署,当然测试集群这么来都行。综上所述,我的三个节点在宏定义上要写成如下
node1:
<macros>
<shard>1</shard>
<replica>node1</replica>
</macros>
node2:
<macros>
<shard>1</shard>
<replica>node2</replica>
</macros>
node3:
<macros>
<shard>2</shard>
<replica>node3</replica>
</macros>
到这里,其实也不难发现clickhouse截止25版本就没办法不借助其他工具大规模部署,所以如果你有相应的需求,就要考虑自己写脚本维护自带的config文件,然后再考虑使用外置<include_from>/etc/clickhouse-server/metrika.xml</include_from>的方式改集群信息,不然取消自带配置中的集群描述标签都能累死你
第六步:启动,前置必要条件的zk启动这里不做演示了
所有节点运行服务启动命令
service clickhouse-server start
用浏览器访问任意一个节点的8123端口,查询集群信息,select * from system.clusters

现在就可以测试集群了,首先在node1和node3上新建一个本地表,这不得不是又是clickhouse的一个槽点,不同节点之间的副本只负责同步数据,不负责同步表结构
CREATE DATABASE IF NOT EXISTS test_db;
CREATE TABLE test_db.distributed_table_local
(
id UInt32,
event_time DateTime,
data String
)
ENGINE=ReplicatedMergeTree('/clickhouse/tables/{shard}/test_db/distributed_table_local', '{replica}')
PARTITION BY toYYYYMM(event_time)
ORDER BY (id, event_time);
sql中{shard} 和 {replica}会取运行节点上的宏,去zookeeper的客户端上就会看到如下的数据

随后在任意节点上建立一个分布式表,这个分布式表基于本地表
CREATE TABLE test_db.distributed_table_all AS test_db.distributed_table_local
ENGINE = Distributed(myclick, test_db, distributed_table_local, id);
--ENGINE = Distributed(集群名称, 库, 分布在节点上的表, rand());
最后那个rand意思是用随机数决定分发到不同切片,你也可以写表中的某个字段。
本质的核心是ReplicatedMergeTree不同节点执行时,参数不一样,第一个参数是zk内部的path,一般业内常用格式是/clickhouse/tables/{shard}/{table_name} ,shard位置用来区分不同的分片,这里直接取宏,第二个参数是副本名称,相同的分片副本名称不能相同,通常也是直接获取配置文件中的宏就行
现在你就可以在任意一个节点上向分布式表写数据
INSERT INTO test_db.distributed_table_all VALUES
(1, now(), 'Hello 1'),
(2, now(), 'Hello 2'),
(3, now(), 'Hello 3'),
(4, now(), 'Hello 4');
DROP TABLE IF EXISTS test_db.distributed_table_all ON CLUSTER myclick;
redis
redis 6.2 和之前老版本3.x左右,安装方式基本相同,区别在于,较新版本需要手动改 bind 地址和连接安全限制
首先是安装 gcc
yum install -y gcc-c++
上传安装包解压后,编译安装
tar -zxf redis-6.2.14.tar.gz
cd redis-6.2.14
make
make PREFIX=/opt/redis-6.2.14 install
修改配置
cp redis.conf redis.conf.bak
vi redis.conf
在配置文件中找到,并修改启动方式配置为后台启动
daemonize yes
修改连接域
bind 0.0.0.0 -::1
# 开发环境把redis安全保护模式关掉
protected-mode no
随后启动就好了
./bin/redis-server ./redis.conf
redis 集群模式
部署架构说明:Redis集群本质上是由多个相互独立的Redis服务进程共同组成。在生产环境中,通常会将节点分布在不同宿主机上以实现高可用。在测试阶段,也可以在一台宿主机上通过多目录、多端口的方式模拟集群环境。实际安装版本选择5.0以后的,因为5.0以前组集群用的是一个第三方插件。
本文以 三主三从 的集群为例,共6个节点,每个主节点对应一个从节点。节点规划如下:
| 宿主机 IP | 节点端口 | 角色 |
|---|---|---|
| 192.168.1.100 | 7000 | 主节点 |
| 192.168.1.100 | 7001 | 从节点 |
| 192.168.1.101 | 7002 | 主节点 |
| 192.168.1.101 | 7003 | 从节点 |
| 192.168.1.102 | 7004 | 主节点 |
| 192.168.1.102 | 7005 | 从节点 |
第一步:在每台宿主机上为不同的Redis实例创建独立的工作目录。
# 在 192.168.1.100 上执行
mkdir -p /opt/redis-cluster/{7000,7001}
# 在 192.168.1.101 上执行
mkdir -p /opt/redis-cluster/{7002,7003}
# 在 192.168.1.102 上执行
mkdir -p /opt/redis-cluster/{7004,7005}
第二步:将Redis源码包中的默认配置文件 redis.conf 复制到每个实例目录中。
# 在 192.168.1.100 上执行
cp /opt/redis-7.4.9/redis.conf /opt/redis-cluster/7000/
cp /opt/redis-7.4.9/redis.conf /opt/redis-cluster/7001/
# 在 192.168.1.101 上执行
cp /opt/redis-7.4.9/redis.conf /opt/redis-cluster/7002/
cp /opt/redis-7.4.9/redis.conf /opt/redis-cluster/7003/
# 在 192.168.1.102 上执行
cp /opt/redis-7.4.9/redis.conf /opt/redis-cluster/7004/
cp /opt/redis-7.4.9/redis.conf /opt/redis-cluster/7005/
第三步:依次编辑每个实例目录下的 redis.conf 文件,根据实例端口号修改对应的配置项。
| 配置项 | 说明 | 示例值 |
|---|---|---|
port | Redis服务监听端口 | 7000(每个实例唯一) |
cluster-enabled | 开启集群模式 | yes |
cluster-config-file | 集群节点状态持久化文件 | nodes-7000.conf(每个实例唯一) |
cluster-node-timeout | 节点失联判定为故障的超时时间(毫秒) | 15000 |
appendonly | 开启AOF持久化 | yes |
appendfsync | AOF同步策略(可选值见下文) | everysec(默认,推荐) |
daemonize | 是否以后台守护进程方式运行 | yes |
bind | 监听的网络地址(IPv6回环地址 -::1 可选) | 0.0.0.0 -::1 |
dir | Redis工作目录(存储持久化文件) | /opt/redis-cluster/7000(每个实例唯一) |
protected-mode | 安全模式的开关 | 先设置 no 后面要设置密码 |
关于 AOF 同步策略 appendfsync 的说明:
| 策略 | 行为 | 性能 | 数据安全性 |
|---|---|---|---|
always | 每次写入命令后立即同步到磁盘 | 最低 | 最高(最多丢失一个命令) |
everysec | 每秒同步一次(官方推荐) | 中等 | 较高(最多丢失1秒数据) |
no | 由操作系统决定刷盘时机 | 最高 | 最差(丢失数据量不可控) |
第四步:依次启动每个Redis实例。
# 在 192.168.1.100 上执行
/opt/redis-7.4.9/bin/redis-server /opt/redis-cluster/7000/redis.conf
/opt/redis-7.4.9/bin/redis-server /opt/redis-cluster/7001/redis.conf
# 在 192.168.1.101 上执行
/opt/redis-7.4.9/bin/redis-server /opt/redis-cluster/7002/redis.conf
/opt/redis-7.4.9/bin/redis-server /opt/redis-cluster/7003/redis.conf
# 在 192.168.1.102 上执行
/opt/redis-7.4.9/bin/redis-server /opt/redis-cluster/7004/redis.conf
/opt/redis-7.4.9/bin/redis-server /opt/redis-cluster/7005/redis.conf
第五步:使用 redis-cli --cluster create 命令将所有节点组建为集群,参数 --cluster-replicas 1 表示每个主节点配备1个从节点。
注意:
--cluster-replicas的值,不是随便写的,它决定了集群的副本数。在6个节点的情况下,--cluster-replicas 1表示创建3个主节点和3个从节点。
/opt/redis-7.4.9/bin/redis-cli --cluster create \
192.168.1.100:7000 192.168.1.100:7001 \
192.168.1.101:7002 192.168.1.101:7003 \
192.168.1.102:7004 192.168.1.102:7005 \
--cluster-replicas 1
执行命令后,系统会自动分配哈希槽并规划主从关系:
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.1.101:7003 to 192.168.1.100:7000
Adding replica 192.168.1.102:7005 to 192.168.1.101:7002
Adding replica 192.168.1.100:7001 to 192.168.1.102:7004
...
Can I set the above configuration? (type 'yes' to accept):
在此处输入 yes 确认,Redis将正式生成并应用主从关系和槽位分配方案。
当看到以下输出时,表示集群已成功创建:
[OK] All 16384 slots covered.
连接任意集群节点,执行 cluster info 命令查看集群健康状态。
/opt/redis-7.4.9/bin/redis-cli -c -h 192.168.1.100 -p 7000 cluster info
关键指标说明:
| 指标 | 期望值 | 说明 |
|---|---|---|
cluster_state | ok | 集群状态正常 |
cluster_slots_assigned | 16384 | 所有槽位已分配 |
cluster_slots_ok | 16384 | 所有槽位均在线 |
cluster_known_nodes | 6 | 集群节点总数 |
第六步:安全加固配置
命令重命名(可选):在 redis.conf 中使用 rename-command 指令将危险命令重命名或置空(禁用)
# 示例:将 FLUSHALL 重命名为不易猜测的名称
rename-command FLUSHALL "MY_SECRET_FLUSHALL"
# 示例:将 CONFIG 命令置空(禁用)
rename-command CONFIG ""
用户认证与权限控制:Redis 6.0 及以上版本推荐使用 ACL(访问控制列表)实现精细化权限管理。
方式一:传统单密码模式(不推荐用于生产)
# redis.conf
requirepass your_global_password
# 从节点需同步配置
masterauth your_global_password
方式二:ACL 文件模式(推荐)
在 `redis.conf` 中指定 ACL 文件路径:
aclfile /etc/redis/users.acl
在 `/etc/redis/users.acl` 文件中定义用户:
# 管理员用户:拥有全部权限
user admin on >admin_password ~* +@all
# 应用用户:只能访问 app: 前缀的键,且仅允许 GET 和 SET 命令
user app_user on >app_password ~app:* +get +set
# 同步用户:供从节点连接使用(集群模式下必须配置)
user replica_user on >replica_password ~* +PSYNC +REPLCONF +PING +INFO +CLIENT SETNAME
当你通过上面两种方式配置了密码,从节点就需要配置认真密码
# 方式二,用户要配置为准备好的用户
masteruser replica_user
# 方式一,只需要配置密码即可
masterauth replica_password
重要提示:
requirepass与aclfile不能同时启用。若同时配置,Redis 会忽略requirepass,完全采用aclfile中的定义。且在集群模式下,无需配置replicaof,主从关系由集群自动管理。
第七步:常用运维命令
1、查看节点与槽位分布
# 查看集群节点详细信息
/opt/redis-7.4.9/bin/redis-cli -c -p 7000 cluster nodes
# 查看槽位分配情况
/opt/redis-7.4.9/bin/redis-cli -c -p 7000 cluster slots
2、新增节点
/opt/redis-7.4.9/bin/redis-cli --cluster add-node \
新节点IP:新节点端口 \
集群中任意现存节点IP:端口
3、删除节点
首先查看所有节点的ID:
/opt/redis-7.4.9/bin/redis-cli -c -p 7000 cluster nodes
删除指定节点(**注意:需先确保该节点上的槽位已迁移至其他节点**):
/opt/redis-7.4.9/bin/redis-cli --cluster del-node \
集群中任意现存节点IP:端口 \
待删除节点的ID
4、重新分配槽位(扩容/缩容)
当操作新增或移除节点时,需要通过 reshard 命令重新平衡槽位分布。
/opt/redis-7.4.9/bin/redis-cli --cluster reshard 集群中任意节点IP:端口
执行后会进入交互式引导,依次填写以下信息:
| 交互提示 | 说明 | 建议 |
|---|---|---|
How many slots do you want to move? | 要迁移的槽位总数 | 通常按 16384 / 主节点总数 计算 |
What is the receiving node ID? | 接收槽位的目标节点ID | 输入新节点或目标节点的ID |
Please enter all the source node IDs. | 槽位来源(迁出节点) | 输入 all 从所有主节点平均抽取,或逐个输入指定节点ID后以 done 结束 |
Do you want to proceed with the proposed reshard plan? | 确认执行 | 输入 yes |
5、手动主从切换
当需要主动触发故障转移(如计划内维护)时,可在从节点上执行:
/opt/redis-7.4.9/bin/redis-cli -c -h 从节点IP -p 从节点端口 cluster failover
最后,要明白一点,redis的插槽数是固定的,因此集群节点也不是说越多越好,它内部是用key哈希值来决定数据要存在哪个插槽,插槽又在哪个节点上,一个节点上能够存储的数据不受插槽限制,而是受于节点的内存,每个节点有多少插槽集群刚创建是默认是均摊,后续就看运维人员如何操作迁移了
第一篇:https://blog.csdn.net/dudadudadd/article/details/139886344
第二篇:https://blog.csdn.net/dudadudadd/article/details/145132245

586

被折叠的 条评论
为什么被折叠?



