首页 > 其他分享 >Orchestrator使用讲解

Orchestrator使用讲解

时间:2024-07-02 10:53:09浏览次数:1  
标签:false 讲解 orchestrator echo VIP orch 使用 Orchestrator true

1、提前部署Mysql主从(一主两从)

2、配置orch到所有机器的免密,配置好主机名/etc/hosts

3、安装orch

配置文件

{
  "Debug": true,
  "EnableSyslog": false,     //是否把日志输出到系统日志里
  "ListenAddress": ":3000",  
  "MySQLTopologyUser": "root",    //orch管理mysql的用户
  "MySQLTopologyPassword": "abc123",  //orch管理mysql的密码
  "MySQLTopologyCredentialsConfigFile": "",
  "MySQLTopologySSLPrivateKeyFile": "",
  "MySQLTopologySSLCertFile": "",
  "MySQLTopologySSLCAFile": "",
  "MySQLTopologySSLSkipVerify": true,
  "MySQLTopologyUseMutualTLS": false,
  "MySQLOrchestratorHost": "10.211.55.2",  //orch元数据信息存放数据库host
  "MySQLOrchestratorPort": 4000,          //orch元数据信息存放数据库port
  "MySQLOrchestratorDatabase": "orchestrator", //orch元数据信息存放数据库database
  "MySQLOrchestratorUser": "orchestrator",    //orch元数据信息存放数据库user
  "MySQLOrchestratorPassword": "orchestrator",  //orch元数据信息存放数据库password
  "MySQLOrchestratorCredentialsConfigFile": "",
  "MySQLOrchestratorSSLPrivateKeyFile": "",
  "MySQLOrchestratorSSLCertFile": "",
  "MySQLOrchestratorSSLCAFile": "",
  "MySQLOrchestratorSSLSkipVerify": true,
  "MySQLOrchestratorUseMutualTLS": false,
  "MySQLConnectTimeoutSeconds": 1,
  "DefaultInstancePort": 3306,
  "DiscoverByShowSlaveHosts": false,   //通过show slave hosts自动发现slave,需在mysql配置文件中设置正确的report_host和report_port
  "InstancePollSeconds": 5,   //每隔5s获取状态
  "DiscoveryIgnoreReplicaHostnameFilters": [
    "a_host_i_want_to_ignore[.]example[.]com",
    ".*[.]ignore_all_hosts_from_this_domain[.]example[.]com",
    "a_host_with_extra_port_i_want_to_ignore[.]example[.]com:3307"
  ],
  "UnseenInstanceForgetHours": 240, 
  "SnapshotTopologiesIntervalHours": 0,
  "InstanceBulkOperationsWaitTimeoutSeconds": 10, //在进行批量操作时, 等待单个实例的最大时间(秒)
  "HostnameResolveMethod": "default",  //如何解析主机名,none: 直接返回传入的hostname,default: 直接返回传入hostname,cname: resolves an IP or hostname into a normalized valid CNAME,ip: 返回ip
  "MySQLHostnameResolveMethod": "@@hostname",
  "SkipBinlogServerUnresolveCheck": true,
  "ExpiryHostnameResolvesMinutes": 60,
  "RejectHostnameResolvePattern": "",
  "ReasonableReplicationLagSeconds": 0,  //合理的复制滞后秒数. 高于这个值就是有问题的
  "ProblemIgnoreHostnameFilters": [],
  "VerifyReplicationFilters": false,
  "ReasonableMaintenanceReplicationLagSeconds": 20,
  "CandidateInstanceExpireMinutes": 60,
  "AuditLogFile": "",
  "AuditToSyslog": false,
  "RemoveTextFromHostnameDisplay": ".mydomain.com:3306",
  "ReadOnly": false,
  "AuthenticationMethod": "basic",
  "HTTPAuthUser": "admin",
  "HTTPAuthPassword": "admin",
  "AuthUserHeader": "",
  "PowerAuthUsers": [
    "*"
  ],
  "ClusterNameToAlias": {
    "127.0.0.1": "test suite"
  },
  "ReplicationLagQuery": "",   //提供ReplicationLagQuery 时, 计算出的复制延迟. 否则与SecondsBehindMaster 相同
  "DetectClusterAliasQuery": "SELECT SUBSTRING_INDEX(@@hostname, '.', 1)",
  "DetectClusterDomainQuery": "",
  "DetectInstanceAliasQuery": "",
  "DetectPromotionRuleQuery": "",
  "DataCenterPattern": "[.]([^.]+)[.][^.]+[.]mydomain[.]com",
  "PhysicalEnvironmentPattern": "[.]([^.]+[.][^.]+)[.]mydomain[.]com",
  "PromotionIgnoreHostnameFilters": [],
  "DetectSemiSyncEnforcedQuery": "",
  "ServeAgentsHttp": false,
  "AgentsServerPort": ":3001",
  "AgentsUseSSL": false,
  "AgentsUseMutualTLS": false,
  "AgentSSLSkipVerify": false,
  "AgentSSLPrivateKeyFile": "",
  "AgentSSLCertFile": "",
  "AgentSSLCAFile": "",
  "AgentSSLValidOUs": [],
  "UseSSL": false,
  "UseMutualTLS": false,
  "SSLSkipVerify": false,
  "SSLPrivateKeyFile": "",
  "SSLCertFile": "",
  "SSLCAFile": "",
  "SSLValidOUs": [],
  "URLPrefix": "",
  "StatusEndpoint": "/api/status",
  "StatusSimpleHealth": true,
  "StatusOUVerify": false,
  "AgentPollMinutes": 60,
  "UnseenAgentForgetHours": 6,
  "StaleSeedFailMinutes": 60,
  "SeedAcceptableBytesDiff": 8192,
  "PseudoGTIDPattern": "",
  "PseudoGTIDPatternIsFixedSubstring": false,
  "PseudoGTIDMonotonicHint": "asc:",
  "DetectPseudoGTIDQuery": "",
  "BinlogEventsChunkSize": 10000,
  "SkipBinlogEventsContaining": [],
  "ReduceReplicationAnalysisCount": true,
  "FailureDetectionPeriodBlockMinutes": 1,
  "FailMasterPromotionOnLagMinutes": 0,
  "RecoveryPeriodBlockSeconds": 10, //一旦集群经历了恢复,那么在这段时间内将阻止自动恢复,以避免抖动
  "FailMasterPromotionIfSQLThreadNotUpToDate": false,
  "DelayMasterPromotionIfSQLThreadNotUpToDate": true, //等slave relay log apply完成
  "RecoveryIgnoreHostnameFilters": [],
  "RecoverMasterClusterFilters": [   
    "*"
  ],  //只对列表中正则表达式匹配的集群做故障恢复操作
  "RecoverIntermediateMasterClusterFilters": [
    "*"
  ],  //只对列表中正则表达式匹配的IntermediateMaster做故障恢复操作
  "OnFailureDetectionProcesses": [
    "echo 'Detected {failureType} on {failureCluster}. Affected replicas: {countSlaves}' >> /tmp/recovery.log"
  ], //检测出故障时执行(在决定是否进行故障转移之前)
  "PreGracefulTakeoverProcesses": [
    "echo 'Planned takeover about to take place on {failureCluster}. Master will switch to read_only' >> /tmp/recovery.log" //在主变为只读之前立即执行
  ],
  "PreFailoverProcesses": [
    "echo 'Will recover from {failureType} on {failureCluster}' >> /tmp/recovery.log"
  ], //在执行恢复操作之前立即执行。任何这些进程的失败(非零退出代码)都会中止恢复。
  "PostFailoverProcesses": [
    "echo '(for all types) Recovered from {failureType} on {failureCluster}. Failed: {failedHost}:{failedPort}; Successor: {successorHost}:{successorPort}' >> /tmp/recovery.log",
    "sh -x /usr/local/orchestrator/orch_hook.sh {failureType} {failureClusterAlias} {failedHost} {successorHost} >> /tmp/orch.log"
  ],   //成功恢复结束后,执行的脚本,这里调用切换vip的脚本
  "PostUnsuccessfulFailoverProcesses": [],
  "PostMasterFailoverProcesses": [
    "echo 'Recovered from {failureType} on {failureCluster}. Failed: {failedHost}:{failedPort}; Promoted: {successorHost}:{successorPort}' >> /tmp/recovery.log"
  ],
  "PostIntermediateMasterFailoverProcesses": [
    "echo 'Recovered from {failureType} on {failureCluster}. Failed: {failedHost}:{failedPort}; Successor: {successorHost}:{successorPort}' >> /tmp/recovery.log"
  ],  //在intermediate master(新主)恢复成功后执行的脚本
  "PostGracefulTakeoverProcesses": [
    "echo 'Planned takeover complete' >> /tmp/recovery.log",
    "sh -x /usr/local/orchestrator/orch_hook.sh {failureType} {failureClusterAlias} {failedHost} {successorHost} >> /tmp/orch.log"
  ], //在手动主从切换,旧的主节点挂在新的主节点上之后执行
  "CoMasterRecoveryMustPromoteOtherCoMaster": true, //双主的场景. 如果一个主库故障, 当该参数为true时, 只会提升另一个"主库"为新主库(看起来怪怪的), 否则集群中所有节点都可能提升为新主库
  "DetachLostSlavesAfterMasterFailover": true, //某些从库再恢复过程中可能会丢失。当为true时,Orchestrator将通过detach-replica命令将其强行中断复制
  "ApplyMySQLPromotionAfterMasterFailover": true, //当为true时,Orchestrator将会在新的master上执行:reset slave all 和 set read_only=0两条命令
  "PreventCrossDataCenterMasterFailover": false, //默认false,当为true时,Orchestrator只会使用同一个DC下的服务器进行恢复。如果在同一DC中找不到,那么恢复就会失败
  "PreventCrossRegionMasterFailover": false,  //默认false,当为true时,Orchestrator将会在同一区域内的服务器上进行故障恢复,如果找不到,那么恢复就会失败
  "FailMasterPromotionOnLagMinutes": 0,        
  "MasterFailoverDetachReplicaMasterHost": false, //当是true,Orchestrator将发出一个detach-replica-master-host命令到新的master上(确保新的master将不会复制旧的master)。 orchestrator将发出一个detach-replica-master-host升级的母版(这可以确保新的母版不会重生旧的母版)。默认值:false。如果ApplyMySQLPromotionAfterMasterFailover是true,那么该参数是无意义的。MasterFailoverDetachSlaveMasterHost是别名
  "MasterFailoverLostInstancesDowntimeMinutes": 0, //故障转移之后,所有的服务器的停机分钟数。默认为0
  "PostponeReplicaRecoveryOnLagMinutes": 0, //在故障恢复过程中,复制的延迟超过给定时间的从库,只有在选出master并且执行了所有process流程之后,才会在恢复的后期进行恢复。0为禁用。PostponeSlaveRecoveryOnLagMinutes是别名
  "OSCIgnoreHostnameFilters": [],
  "GraphiteAddr": "",
  "GraphitePath": "",
  "GraphiteConvertHostnameDotsToUnderscores": true,
  "ConsulAddress": "",
  "ConsulAclToken": "",
  "ConsulKVStoreProvider": "consul"
}

切换逻辑如下

逻辑如下:
1、分析mysql实例的故障类型并得到DeadCoMaster的故障
2、获取DeadCoMaster对应的处理函数,为checkAndRecoverDeadCoMaster()
3、登记failure detection记录到后端数据库并执行OnFailureDetectionProcesses的hook。执行成功则执行下面的流程,否则不执行下面所有流程
   1、执行PreFailoverProcesses的hook,成功则执行下面的子流程
     1、选择新主
     2、无论选择新主成功与否,清理failure detection的登记,将记录active设为0。
   2、选择新主成功且开启ApplyMySQLPromotionAfterMasterFailover配置时,将新主的read_only设为false
   3、选择新主成功情况下,执行PostMasterFailoverProcesses的hook
   4、如果上述的恢复成功,执行PostFailoverProcesses的hook。上述恢复执行失败则执行PostUnsuccessfulFailoverProcesses的hook

4、准备脚本

起停脚本

start.sh

#!/bin/bash

nohup ./orchestrator --debug --config orchestrator.conf.json http > arch.log &

stop.sh

#!/bin/bash

ps -ef|grep orchestrator|grep http|awk '{print $2}'|xargs -i kill -9 {}

vip切换脚本

orch_hook.sh

#!/bin/bash
isitdead=$1
#cluster=$2
oldmaster=$3
newmaster=$4

ssh=$(which ssh)

logfile="/home/mysql/usr/local/orchestrator/orch.log"
#interface='enp0s3'
interface='eth0'
user=mysql
#VIP=$($ssh -tt ${user}@${oldmaster} "sudo ip address show dev enp0s3|grep -w 'inet'|tail -n 1|awk '{print \$2}'|awk -F/ '{print \$1}'")
VIP='10.37.129.10'
VIP_TEMP=$($ssh -tt ${user}@${oldmaster} "sudo ip address|sed -nr 's#^.*inet (.*)/32.*#\1#gp'")
#remove '\r' at the end $'192.168.56.200\r'
VIP_TEMP=$(echo $VIP_TEMP|awk -F"\\r" '{print $1}')

if [ ${#VIP_TEMP} -gt 0 ]; then
    VIP=$VIP_TEMP
fi
echo ${VIP}
echo ${interface}

if [[ $isitdead == "DeadMaster" ]]; then
        if [ !-z ${!VIP} ] ; then

                echo $(date)
                echo "Revocering from: $isitdead"
                echo "New master is: $newmaster"
                echo "/home/mysql/usr/local/orchestrator/orch_vip.sh -d 1 -n $newmaster -i ${interface} -I $VIP -u ${user} -o ${oldmaster}"
                /home/mysql/usr/local/orchestrator/orch_vip.sh -d 1 -n $newmaster -i ${interface} -I $VIP -u ${user} -o ${oldmaster}
        else

                echo "Cluster does not exist!" | tee $logfile

        fi
fi
[mysql@tidb-4 orchestrator]$ cat orch_hook.sh
#!/bin/bash
isitdead=$1
#cluster=$2
oldmaster=$3
newmaster=$4

ssh=$(which ssh)

logfile="/home/mysql/usr/local/orchestrator/orch.log"
interface='eth0'
user=mysql
VIP='10.37.129.10'
VIP_TEMP=$($ssh -tt ${user}@${oldmaster} "sudo ip address|sed -nr 's#^.*inet (.*)/32.*#\1#gp'")
#remove '\r' at the end $'192.168.56.200\r'
VIP_TEMP=$(echo $VIP_TEMP|awk -F"\\r" '{print $1}')

if [ ${#VIP_TEMP} -gt 0 ]; then
    VIP=$VIP_TEMP
fi
echo ${VIP}
echo ${interface}

if [[ $isitdead == "DeadMaster" ]]; then
        if [ !-z ${!VIP} ] ; then

                echo $(date)
                echo "Revocering from: $isitdead"
                echo "New master is: $newmaster"
                echo "/home/mysql/usr/local/orchestrator/orch_vip.sh -d 1 -n $newmaster -i ${interface} -I $VIP -u ${user} -o ${oldmaster}"
                /home/mysql/usr/local/orchestrator/orch_vip.sh -d 1 -n $newmaster -i ${interface} -I $VIP -u ${user} -o ${oldmaster}
        else

                echo "Cluster does not exist!" | tee $logfile

        fi
fi
[mysql@tidb-4 orchestrator]$ cat orch_vip.sh
#!/bin/bash

function usage {
  cat << EOF
 usage: $0 [-h] [-d master is dead] [-o old master ] [-s ssh options] [-n new master] [-i interface] [-I] [-u SSH user]

 OPTIONS:
    -h        Show this message
    -o string Old master hostname or IP address
    -d int    If master is dead should be 1 otherweise it is 0
    -s string SSH options
    -n string New master hostname or IP address
    -i string Interface exmple eth0:1
    -I string Virtual IP
    -u string SSH user
EOF

}

while getopts ho:d:s:n:i:I:u: flag; do
  case $flag in
    o)
      orig_master="$OPTARG";
      ;;
    d)
      isitdead="${OPTARG}";
      ;;
    s)
      ssh_options="${OPTARG}";
      ;;
    n)
      new_master="$OPTARG";
      ;;
    i)
      interface="$OPTARG";
      ;;
    I)
      vip="$OPTARG";
      ;;
    u)
      ssh_user="$OPTARG";
      ;;
    h)
      usage;
      exit 0;
      ;;
    *)
      usage;
      exit 1;
      ;;
  esac
done


if [ $OPTIND -eq 1 ]; then
    echo "No options were passed";
    usage;
fi

shift $(( OPTIND - 1 ));

# discover commands from our path
ssh=$(which ssh)
arping=$(which arping)
ip2util=$(which ip)
#ip2util='ip'

# command for adding our vip
cmd_vip_add="sudo -n $ip2util address add $vip dev $interface"
# command for deleting our vip
cmd_vip_del="sudo -n $ip2util address del $vip/32 dev $interface"
# command for discovering if our vip is enabled
cmd_vip_chk="sudo -n $ip2util address show dev $interface to ${vip%/*}/32"
# command for sending gratuitous arp to announce ip move
cmd_arp_fix="sudo -n $arping -c 1 -I ${interface} ${vip%/*}"
# command for sending gratuitous arp to announce ip move on current server
#cmd_local_arp_fix="sudo -n $arping -c 1 -I ${interface} ${vip%/*}"
cmd_local_arp_fix="$arping -c 1 -I ${interface} ${vip%/*}"

vip_stop() {
    rc=0
    echo $?

    echo "$ssh ${ssh_options} -tt ${ssh_user}@${orig_master} \
       \"[ -n \"\$(${cmd_vip_chk})\" ] && ${cmd_vip_del} && \
       sudo -n ${ip2util} route flush cache || [ -z \"\$(${cmd_vip_chk})\" ]\""

    # ensure the vip is removed
    $ssh ${ssh_options} -tt ${ssh_user}@${orig_master} \
       "[ -n \"\$(${cmd_vip_chk})\" ] && ${cmd_vip_del} && \
       sudo -n ${ip2util} route flush cache || [ -z \"\$(${cmd_vip_chk})\" ]"
    rc=$?
    return $rc
}

vip_start() {
    rc=0

    # ensure the vip is added
    # this command should exit with failure if we are unable to add the vip
    # if the vip already exists always exit 0 (whether or not we added it)
    echo "$ssh ${ssh_options} -tt ${ssh_user}@${new_master} \
     \"[ -z \"\$(${cmd_vip_chk})\" ] && ${cmd_vip_add} && ${cmd_arp_fix} || [ -n \"\$(${cmd_vip_chk})\" ]\""

    $ssh ${ssh_options} -tt ${ssh_user}@${new_master} \
     "[ -z \"\$(${cmd_vip_chk})\" ] && ${cmd_vip_add} && ${cmd_arp_fix} || [ -n \"\$(${cmd_vip_chk})\" ]"
    rc=$?
    echo "vip started"
    #$cmd_local_arp_fix
    return $rc
}

vip_status() {
    $arping -c 1 -I ${interface} ${vip%/*}
    echo "$arping -c 1 -I ${interface} ${vip%/*}"
    if ping -c 1 -W 1 "$vip"; then
        return 0
    else
        return 1
    fi
}
if [[ $isitdead == 0 ]]; then
    echo "Online failover"
    if vip_stop; then
        if vip_start; then
            echo "$vip is moved to $new_master."
        else
            echo "Can't add $vip on $new_master!"
            exit 1
        fi
    else
        echo $rc
        echo "Can't remove the $vip from orig_master!"
        exit 1
    fi
elif [[ $isitdead == 1 ]]; then
    echo "Master is dead, failover"
    # make sure the vip is not available
    if vip_status; then
        if vip_stop; then
            echo "$vip is removed from orig_master."
        else
            echo $rc
            echo "Couldn't remove $vip from orig_master."
            exit 1
        fi
    fi

    if vip_start; then
          echo "$vip is moved to $new_master."

    else
          echo "Can't add $vip on $new_master!"
          exit 1
    fi
else
    echo "Wrong argument, the master is dead or live?"

fi

5、启动orch并检查拓扑

./start.sh

$ orchestrator-client -c topology -i tidb-4.0-control:3306
 tidb-4-1:3306           [0s,ok,5.7.41-log,rw,ROW,>>,GTID,semi:master]
+ tidb-4-2:3306         [0s,ok,5.7.41-log,ro,ROW,>>,GTID,semi:master,semi:replica]
+ tidb-4.0-control:3306 [0s,ok,5.7.41-log,rw,ROW,>>,GTID,semi:master,semi:replica]

6、手动平滑切换

orchestrator-client -c graceful-master-takeover -a tidb-4.0-control:3306 -d tidb-4-1:3306

注: -a 为旧主 , -d为新主。切换后旧主需手动start slave。

7、强制关闭主库切换

确保旧主库已经提前配置vip,如没配置需执行:

/usr/sbin/ip address add 10.37.129.10 dev eth0

停止主库:

mysql > shutdown;

自动重新change master,并且飘移vip.

该场景下的问题:在主从延迟的情况下,从库会立即提升为主库,新主库有数据不一致的问题。

标签:false,讲解,orchestrator,echo,VIP,orch,使用,Orchestrator,true
From: https://www.cnblogs.com/longfeij/p/18279466

相关文章

  • ros - microros - 电机控制之使用开源库驱动多路电机
    前面了解了电机控制的原理并通过实验测试了对电机正反转以及转速的控制。本节我们采用开源库调用ESP32的外设MCPWM进行精细化的电机PWM控制。一、MCPWM简介MCPWM中文名是电机控制脉宽调制器(MotorControlPulseWidthModulator),是一款多功能PWM发生器,包含各种子模块,使其成为电......
  • TDengine使用taosdump工具进行数据导出导入
    数据备份(导出)可以使用命令导出sql相关文件,这些导出的相关文件可以导入时使用taosdump-o[导出文件存放路径,需要是已存在目录]-D[数据库名]导出所有数据库使用-A代替-D,后不跟数据库名,但是博主没成功,使用-D单独导出一个库是很稳定的,导出目录下包含一个sql文件和一个tdengin......
  • 自动化(爬虫)工具 DrissionPage SessionPage模式 API介绍 使用笔记(三)
    自动化(爬虫)工具DrissionPageSessionPage模式API介绍使用笔记(三)目录启动驱动启动配置常用方法(API)启动最简单的启动方式,更多启动方式见这里fromDrissionPageimportSessionPage#session_or_options:Session对象或SessionOptions对象#timeout:超时时间(秒)o......
  • ant-ui+vue3使用踩坑记录
    1、table组件使用Summary合计时,明明设置summary的fixed属性,设置固定还是没有生效!滚动的时候合计栏还是会滚动代码    通过查看官方文档,发现还要配合设置SummaryCell的index序号进行指定   解决方法如下,那个栏目需要固定就设置相应的index 效果 ......
  • 【crontab】使用cron每天定时签到掘金
    一、场景   每天自动掘金签到  二、crontab工具usage:crontab[-uuser]filecrontab[-uuser][-i]{-e|-l|-r}(defaultoperationisreplace,per1003.2)-e(edituser'scrontab)-l(listuser'scrontab)-r......
  • Java开发者LLM实战——使用LangChain4j构建本地RAG系统
    1、引言由于目前比较火的chatGPT是预训练模型,而训练一个大模型是需要较长时间(参数越多学习时间越长,保守估计一般是几个月,不差钱的可以多用点GPU缩短这个时间),这就导致了它所学习的知识不会是最新的,最新的chatGPT-4o只能基于2023年6月之前的数据进行回答,距离目前已经快一年的时间,如......
  • ASP.NET Core如何使用HttpClient调用WebService
    原文链接:https://www.yisu.com/jc/691937.html我们使用VS创建一个ASP.NETCoreWebAPI项目,由于是使用HttpClient,首先在ConfigureServices方法中进行注入。public void ConfigureServices(IServiceCollection services){    // 注入HttpClient    services.AddHt......
  • AI绘画领域有哪些副业?Stable Diffusion居然可以这样变现!最全副业讲解
    这几天有些小伙伴在聊AI关于绘画的一些事,问有没有相关的副业,今天我们分享几个,大家可以参考看看。1.AI绘画课程2.AI绘画定制服务3.AI绘画作品售卖4.参加绘画比赛5.公司外包服务6.绘画软件开发7.AI咨询服务8.运营自媒体账号AI当下还是个风口,刚开始,现在入局还算早的,......
  • net core 中如何使用session
    原文链接:https://zhuanlan.zhihu.com/p/6373955031,在ConfigureServices方法里加入services.AddSession()配置publicvoidConfigureServices(IServiceCollectionservices){services.AddSession();} 2,在Configure里介入app.UseSession()配置注意app.UseSession();这句要......
  • Kotlin作用域函数it和with的使用场景
    在Kotlin中,apply、run、with使用this,而let和also使用it,这背后的原因是为了提供灵活性和代码清晰度。不同的作用域函数有不同的设计目的,选择使用this或it是为了适应不同的使用场景。以下是详细解释:使用this的作用域函数apply设计目的:主要用于配置对象。使用th......