前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一文讲透hdfs的delegation token

一文讲透hdfs的delegation token

作者头像
陈猿解码
发布于 2023-02-28 07:04:37
发布于 2023-02-28 07:04:37
2.1K00
代码可运行
举报
文章被收录于专栏:陈猿解码陈猿解码
运行总次数:0
代码可运行

【背景】


前一段时间总结了hadoop中的token认证、yarn任务运行中的token,其中也都提到了delegation token。而最近也遇到了一个问题,问题现象是:flink任务运行超过七天后,由于宿主机异常导致任务失败,继而触发任务的重试,但接连重试几次都是失败的,并且任务的日志也没有聚合,导致无法分析问题失败的原因。最后发现是和delegation token有关,本文就来总结下相关的原理。

【原理】


1. 什么是delegation token

先简单描述下为什么需要delegation token。在开启kerberos之后,服务之间交互前,都需要先向KDC认证获取对应的票据。而在一个yarn任务运行过程中可能会产生很多任务container,每个这样的任务container都可能会访问hdfs,由于访问前需要先获取票据来进行认证,那么这个时候KDC就很容易成为性能瓶颈。delegation token(委派token)就是为了减少不必要的认证工作而出现的。

2. delegation token在任务提交运行过程中的使用

任务提交运行过程中,delegation token相关的流程如下图所示:

1)首先,RM启动后,内部会创建一个服务线程专门用于处理token的更新

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ResourceManager.java
protected void serviceInit(Configuration configuration) throws Exception {
    ...
    if (UserGroupInformation.isSecurityEnabled()) {
        delegationTokenRenewer = createDelegationTokenRenewer();
        rmContext.setDelegationTokenRenewer(delegationTokenRenewer);
    }
    ....
}

protected DelegationTokenRenewer createDelegationTokenRenewer() {
    return new DelegationTokenRenewer();
}

2)客户端申请delegation token

客户端在提交任务前,通常需要先向hdfs上传资源文件(包括运行所需的jar包等),在此过程中会向nn申请一个delegation token,并放到任务启动上下文中,然后向rm发送提交任务请求(请求中包含任务的启动上下文)。

下面是flink on yarn提交任务时的代码片段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// flink YarnClusterDescriptor.java
private ApplicationReport startAppMaster(...){
    // 开启kerberos的情况下,获取token
    if (UserGroupInformation.isSecurityEnabled()) {
      // set HDFS delegation tokens when security is enabled
      LOG.info("Adding delegation token to the AM container.");
      List<Path> yarnAccessList =
        ConfigUtils.decodeListFromConfig(
          configuration, YarnConfigOptions.YARN_ACCESS, Path::new);
      Utils.setTokensFor(
        amContainer,
        ListUtils.union(yarnAccessList, fileUploader.getRemotePaths()),
        yarnConfiguration);
    }
}

public static void setTokensFor(
    ContainerLaunchContext amContainer, List<Path> paths, Configuration conf)
    throws IOException {
    Credentials credentials = new Credentials();
    // for HDFS
    TokenCache.obtainTokensForNamenodes(credentials, paths.toArray(new Path[0]), conf);
    // for HBase
    obtainTokenForHBase(credentials, conf);
    // for user
    UserGroupInformation currUsr = UserGroupInformation.getCurrentUser();
    // 获取到的token 放到启动上下文中
    Collection<Token<? extends TokenIdentifier>> usrTok = currUsr.getTokens();
    for (Token<? extends TokenIdentifier> token : usrTok) {
        final Text id = new Text(token.getIdentifier());
        LOG.info("Adding user token " + id + " with " + token);
        credentials.addToken(id, token);
    }
    try (DataOutputBuffer dob = new DataOutputBuffer()) {
        credentials.writeTokenStorageToStream(dob);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Wrote tokens. Credentials buffer length: " + dob.getLength());
        }

        ByteBuffer securityTokens = ByteBuffer.wrap(dob.getData(), 0, dob.getLength());
        amContainer.setTokens(securityTokens);
    }
}

// TokenCache.java
// 调用hadoop的接口 向nn请求token
public static void obtainTokensForNamenodes(
    Credentials credentials,
    Path[] ps, Configuration conf) 
    throws IOException {
    if (!UserGroupInformation.isSecurityEnabled()) {
        return;
    }
    obtainTokensForNamenodesInternal(credentials, ps, conf);
}

static void obtainTokensForNamenodesInternal(
    Credentials credentials,
    Path[] ps, 
    Configuration conf) 
    throws IOException {
    Set<FileSystem> fsSet = new HashSet<FileSystem>();
    for (Path p : ps) {
        fsSet.add(p.getFileSystem(conf));
    }
    String masterPrincipal = Master.getMasterPrincipal(conf);
    for (FileSystem fs : fsSet) {
        obtainTokensForNamenodesInternal(fs, credentials, conf, masterPrincipal);
    }
}

static void obtainTokensForNamenodesInternal(
    FileSystem fs,
    Credentials credentials, 
    Configuration conf, 
    String renewer)
    throws IOException {
    ...
    final Token<?> tokens[] = fs.addDelegationTokens(delegTokenRenewer, credentials);
    ...
}

// FileSystem.java
public Token<?>[] addDelegationTokens(
    final String renewer, Credentials credentials) 
    throws IOException {
    if (credentials == null) {
        credentials = new Credentials();
    }
    final List<Token<?>> tokens = new ArrayList<>();
    collectDelegationTokens(renewer, credentials, tokens);
    return tokens.toArray(new Token<?>[tokens.size()]);
}

private void collectDelegationTokens(
    final String renewer,
    final Credentials credentials,
    final List<Token<?>> tokens)
    throws IOException {
    final String serviceName = getCanonicalServiceName();
    // Collect token of the this filesystem and then of its embedded children
    if (serviceName != null) { // fs has token, grab it
        final Text service = new Text(serviceName);
        Token<?> token = credentials.getToken(service);
        if (token == null) {
            // 向NN 请求delegation token
            token = getDelegationToken(renewer);
            if (token != null) {
                tokens.add(token);
                credentials.addToken(service, token);
            }
        }
    }
    ...
}

3)RM将token添加到delegation token更新服务中

RM在处理客户端提交任务请求时,判断是否启用kerberos认证,如果启用则从任务启动上下文中解析出delegation token,并添加到delegation token更新服务中。在该服务中,会启动线程定时对delegation token进行更新。此后,继续向NM发送启动container的请求,delegation token则随启动上下文被带到NM中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// RMAppManager.java
protected void submitApplication(
    ApplicationSubmissionContext submissionContext, 
    long submitTime,
    String user)
    throws YarnException {
    ...
    if (UserGroupInformation.isSecurityEnabled()) {
        this.rmContext.getDelegationTokenRenewer().addApplicationAsync(
            applicationId,
            BuilderUtils.parseCredentials(submissionContext),
            submissionContext.getCancelTokensWhenComplete(),
            application.getUser(),
            BuilderUtils.parseTokensConf(submissionContext));
    }
    ...
}

4)NM使用delegation token

NM收到启动container的请求后,从请求(任务启动上下文)中解析出delegation token,并为该container构造一个对应的实例对象,同时将delegation token保存在该实例对象中,然后为该container进行资源本地化,即从hdfs中下载必须的资源文件,这里就会用到传递过来的delegation token。同时在任务结束时,如果需要进行任务日志聚合,仍旧会使用该delegation token将任务的日志上传到hdfs的指定路径。

另外,delegation token还会写入到持久化文件中,一方面用于NM的异常恢复,另一方面是将token传递给任务container进程以供使用。

3. delegation token的更新与生命周期

1)申请token时已经指定了token的最大生命周期

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// FSNamesystem.java
Token<DelegationTokenIdentifier> getDelegationToken(Text renewer) throws IOException {
    ...
    DelegationTokenIdentifier dtId = new DelegationTokenIdentifier(owner, renewer, realUser);
    token = new Token<DelegationTokenIdentifier>(dtId, dtSecretManager);
    ...
    return token;
}

// Token.java
public Token(T id, SecretManager<T> mgr) {
    password = mgr.createPassword(id);
    identifier = id.getBytes();
    kind = id.getKind();
    service = new Text();
}

// AbstractDelegationTokenSecretManager
protected synchronized byte[] createPassword(TokenIdent identifier) {
    long now = Time.now();
    identifier.setMaxDate(now + tokenMaxLifetime);
    ...
}

2)RM接收到任务提交请求后,先进行一次更新得到token的下次超时时间,然后再根据超时时间设置定时器时间触发进行更新。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void addApplicationSync(
    ApplicationId applicationId, 
    Credentials ts,
    boolean shouldCancelAtEnd, 
    String user) 
    throws IOException, InterruptedException {
    handleAppSubmitEvent(
        new DelegationTokenRenewerAppSubmitEvent(
            applicationId, ts, shouldCancelAtEnd, user, new Configuration()));
}

private void handleAppSubmitEvent(AbstractDelegationTokenRenewerAppEvent evt)
    throws IOException, InterruptedException {
    ...
    Credentials ts = evt.getCredentials();
    Collection<Token<?>> tokens = ts.getAllTokens();
    for (Token<?> token : tokens) {
        DelegationTokenToRenew dttr = allTokens.get(token);
        if (dttr == null) {
            dttr = new DelegationTokenToRenew(
                Arrays.asList(applicationId), 
                token, tokenConf, now, shouldCancelAtEnd, 
                evt.getUser());
            try {
                // 先进行一次更新
                renewToken(dttr)
            } catch (IOException ioe) {
                ...
            }
        }
        tokenList.add(dttr);
    }
    
    if (!tokenList.isEmpty()) {
        for (DelegationTokenToRenew dtr : tokenList) {
            DelegationTokenToRenew currentDtr = allTokens.putIfAbsent(dtr.token, dtr);
            if (currentDtr != null) {
                // another job beat us
                currentDtr.referringAppIds.add(applicationId);
                appTokens.get(applicationId).add(currentDtr);
            } else {
                appTokens.get(applicationId).add(dtr);
                setTimerForTokenRenewal(dtr);
            }
        }
    }
}

protected void renewToken(final DelegationTokenToRenew dttr)
    throws IOException {
    // need to use doAs so that http can find the kerberos tgt
    // NOTE: token renewers should be responsible for the correct UGI!
    try {
        // 更新delegation token 并得到下次超时时间
        dttr.expirationDate =
            UserGroupInformation.getLoginUser().doAs(
                new PrivilegedExceptionAction<Long>() {
                    @Override
                    public Long run() throws Exception {
                        return dttr.token.renew(dttr.conf);
                    }
                });
    } catch (InterruptedException e) {
        throw new IOException(e);
    }
    LOG.info("Renewed delegation-token= [" + dttr + "]");
}

protected void setTimerForTokenRenewal(DelegationTokenToRenew token)
    throws IOException {
    // calculate timer time
    long expiresIn = token.expirationDate - System.currentTimeMillis();
    if (expiresIn <= 0) {
        LOG.info("Will not renew token " + token);
        return;
    }
    long renewIn = token.expirationDate - expiresIn / 10; // little bit before the expiration
    // need to create new task every time
    RenewalTimerTask tTask = new RenewalTimerTask(token);
    token.setTimerTask(tTask); // keep reference to the timer

    renewalTimer.schedule(token.timerTask, new Date(renewIn));
    LOG.info(
        "Renew " + token + " in " + expiresIn + " ms, appId = " +
        token.referringAppIds);
}

再来看更新token的请求与处理细节:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 客户端发送更新请求
public long renew(Token<?> token, Configuration conf) throws IOException {
    Token<DelegationTokenIdentifier> delToken = (Token<DelegationTokenIdentifier>) token;
    ClientProtocol nn = getNNProxy(delToken, conf);
    try {
        return nn.renewDelegationToken(delToken);
    } catch (RemoteException re) {
        throw re.unwrapRemoteException(InvalidToken.class,
                AccessControlException.class);
    }
}

// 服务端的响应处理
long renewDelegationToken(Token<DelegationTokenIdentifier> token)
    throws InvalidToken, IOException {
    try {
        ...
        expiryTime = dtSecretManager.renewToken(token, renewer);
    } catch (AccessControlException ace) {
        ...
    }
    return expiryTime;
}

public synchronized long renewToken(
    Token<TokenIdent> token,
    String renewer) 
    throws InvalidToken, IOException {
    ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
    DataInputStream in = new DataInputStream(buf);
    TokenIdent id = createIdentifier();
    id.readFields(in);
    LOG.info(
        "Token renewal for identifier: " + formatTokenId(id) +
        "; total currentTokens " + currentTokens.size());

    long now = Time.now();
    if (id.getMaxDate() < now) {
        throw new InvalidToken(
            renewer + " tried to renew an expired token " +
            formatTokenId(id) + " max expiration date: " +
            Time.formatTime(id.getMaxDate()) +
            " currentTime: " + Time.formatTime(now));
    }
    if ((id.getRenewer() == null) || (id.getRenewer().toString().isEmpty())) {
        throw new AccessControlException(
            renewer +
            " tried to renew a token " + formatTokenId(id) +
            " without a renewer");
    }
    if (!id.getRenewer().toString().equals(renewer)) {
        throw new AccessControlException(
            renewer +
            " tries to renew a token " + formatTokenId(id) +
            " with non-matching renewer " + id.getRenewer());
    }
    DelegationKey key = getDelegationKey(id.getMasterKeyId());
    if (key == null) {
        throw new InvalidToken(
            "Unable to find master key for keyId=" +
            id.getMasterKeyId() +
            " from cache. Failed to renew an unexpired token " +
            formatTokenId(id) + " with sequenceNumber=" +
            id.getSequenceNumber());
    }
    byte[] password = createPassword(token.getIdentifier(), key.getKey());
    if (!MessageDigest.isEqual(password, token.getPassword())) {
        throw new AccessControlException(
            renewer +
            " is trying to renew a token " +
            formatTokenId(id) + " with wrong password");
    }
    long renewTime = Math.min(id.getMaxDate(), now + tokenRenewInterval);
    String trackingId = getTrackingIdIfEnabled(id);
    DelegationTokenInformation info = 
        new DelegationTokenInformation(renewTime, password, trackingId);

    if (getTokenInfo(id) == null) {
        throw new InvalidToken(
            "Renewal request for unknown token " + formatTokenId(id));
    }
    updateToken(id, info);
    return renewTime;
}

3)token达到最大生命周期的处理

在定时器中,会捕获更新抛出的异常,并直接移除失效的token。

但是注意:在每次更新之前,会按需重新申请新的delegation token(后面再展开讲解)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void run() {
    if (cancelled.get()) {
        return;
    }

    Token<?> token = dttr.token;

    try {
        // 先判断是否需要申请新的token
        requestNewHdfsDelegationTokenIfNeeded(dttr);
        // if the token is not replaced by a new token, renew the token
        if (!dttr.isTimerCancelled()) {
            renewToken(dttr);
            setTimerForTokenRenewal(dttr);// set the next one
        } else {
            LOG.info("The token was removed already. Token = [" + dttr + "]");
        }
    } catch (Exception e) {
        LOG.error("Exception renewing token" + token + ". Not rescheduled", e);
        removeFailedDelegationToken(dttr);
    }
}

【问题分析】


来看看前面问题失败的相关日志,复盘分析下。

首先从NM的日志中发现任务在重试时,因为无法下载资源(到本地)导致无法启动任务,而下载资源失败的原因则是因为无效的token。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2022-07-18 13:44:18,665 WARN org.apache.hadoop.ipc.Client: Exception encountered while connecting to the server : org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 4361 for hncscwc) can't be found in cache
2022-07-18 13:44:18,669 WARN org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.ResourceLocalizationService: { hdfs://hdfsHACluster/user/hncscwc/.flink/application_1637733238080_3800/application_1637733238080_38002636034628721129021.tmp, 1656925873322, FILE, null } failed: token (HDFS_DELEGATION_TOKEN token 4361 for hncscwc) can't be found in cache
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): token (HDFS_DELEGATION_TOKEN token 4361 for hncscwc) can't be found in cache
  at org.apache.hadoop.ipc.Client.getRpcResponse(Client.java:1486)
  at org.apache.hadoop.ipc.Client.call(Client.java:1432)
  at org.apache.hadoop.ipc.Client.call(Client.java:1342)
  at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:227)
  at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:116)
  at com.sun.proxy.$Proxy15.getFileInfo(Unknown Source)
  at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.getFileInfo(ClientNamenodeProtocolTranslatorPB.java:796)
  at sun.reflect.GeneratedMethodAccessor172.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.apache.hadoop.io.retry.RetryInvocationHandler.invokeMethod(RetryInvocationHandler.java:411)
  at org.apache.hadoop.io.retry.RetryInvocationHandler$Call.invokeMethod(RetryInvocationHandler.java:165)
  at org.apache.hadoop.io.retry.RetryInvocationHandler$Call.invoke(RetryInvocationHandler.java:157)
  at org.apache.hadoop.io.retry.RetryInvocationHandler$Call.invokeOnce(RetryInvocationHandler.java:95)
  at org.apache.hadoop.io.retry.RetryInvocationHandler.invoke(RetryInvocationHandler.java:348)
  at com.sun.proxy.$Proxy16.getFileInfo(Unknown Source)
  at org.apache.hadoop.hdfs.DFSClient.getFileInfo(DFSClient.java:1649)
  at org.apache.hadoop.hdfs.DistributedFileSystem$27.doCall(DistributedFileSystem.java:1440)
  at org.apache.hadoop.hdfs.DistributedFileSystem$27.doCall(DistributedFileSystem.java:1437)
  at org.apache.hadoop.fs.FileSystemLinkResolver.resolve(FileSystemLinkResolver.java:81)
  at org.apache.hadoop.hdfs.DistributedFileSystem.getFileStatus(DistributedFileSystem.java:1452)
  at org.apache.hadoop.yarn.util.FSDownload.copy(FSDownload.java:253)
  at org.apache.hadoop.yarn.util.FSDownload.access$000(FSDownload.java:63)
  at org.apache.hadoop.yarn.util.FSDownload$2.run(FSDownload.java:361)
  at org.apache.hadoop.yarn.util.FSDownload$2.run(FSDownload.java:359)
  at java.security.AccessController.doPrivileged(Native Method)
  at javax.security.auth.Subject.doAs(Subject.java:422)
  at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1922)
  at org.apache.hadoop.yarn.util.FSDownload.call(FSDownload.java:359)
  at org.apache.hadoop.yarn.util.FSDownload.call(FSDownload.java:62)
  at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
  at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  at java.lang.Thread.run(Thread.java:748)

为什么会出现无效的token,接着再看RM的日志。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2022-07-04 17:11:13,400 INFO org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler: Application 'application_1637733238080_3800' is submitted without priority hence considering default queue/cluster priority: 0
2022-07-04 17:11:13,424 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657012273422; apps=[application_1637733238080_3800]]
2022-07-05 14:47:13,462 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657090033446; apps=[application_1637733238080_3800]]
2022-07-06 12:23:13,467 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657167793465; apps=[application_1637733238080_3800]]
2022-07-07 09:59:13,487 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657245553484; apps=[application_1637733238080_3800]]
2022-07-08 07:35:13,532 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657323313511; apps=[application_1637733238080_3800]]
2022-07-09 05:11:13,551 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657401073532; apps=[application_1637733238080_3800]]
2022-07-10 02:47:13,564 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657478833547; apps=[application_1637733238080_3800]]
2022-07-11 00:23:13,591 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800]]
2022-07-11 17:11:07,361 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800]]
2022-07-11 17:11:07,361 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renew Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800] in 6032 ms, appId = [application_1637733238080_3800]
2022-07-11 17:11:12,793 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800]]
2022-07-11 17:11:12,793 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renew Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800] in 600 ms, appId = [application_1637733238080_3800]
2022-07-11 17:11:13,337 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800]]
2022-07-11 17:11:13,337 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renew Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800] in 56 ms, appId = [application_1637733238080_3800]
2022-07-11 17:11:13,391 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renewed delegation-token= [Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800]]
2022-07-11 17:11:13,391 INFO org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Renew Kind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc);exp=1657530673393; apps=[application_1637733238080_3800] in 2 ms, appId = [application_1637733238080_3800]
2022-07-11 17:11:13,398 ERROR org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: Exception renewing tokenKind: HDFS_DELEGATION_TOKEN, Service: ha-hdfs:hdfsHACluster, Ident: (HDFS_DELEGATION_TOKEN token 4361 for hncscwc). Not rescheduled
org.apache.hadoop.security.token.SecretManager$InvalidToken: hadoop tried to renew an expired token (HDFS_DELEGATION_TOKEN token 4361 for hncscwc) max expiration date: 2022-07-11 17:11:13,393+0800 currentTime: 2022-07-11 17:11:13,394+0800
  at org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager.renewToken(AbstractDelegationTokenSecretManager.java:499)
  at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.renewDelegationToken(FSNamesystem.java:5952)
  at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.renewDelegationToken(NameNodeRpcServer.java:675)
  at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.renewDelegationToken(ClientNamenodeProtocolServerSideTranslatorPB.java:1035)
  at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
  at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:447)
  at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:989)
  at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:850)
  at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:793)
  at java.security.AccessController.doPrivileged(Native Method)
  at javax.security.auth.Subject.doAs(Subject.java:422)
  at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1922)
  at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2489)

  at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  at org.apache.hadoop.ipc.RemoteException.instantiateException(RemoteException.java:121)
  at org.apache.hadoop.ipc.RemoteException.unwrapRemoteException(RemoteException.java:88)
  at org.apache.hadoop.hdfs.DFSClient$Renewer.renew(DFSClient.java:761)
  at org.apache.hadoop.security.token.Token.renew(Token.java:458)
  at org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer$1.run(DelegationTokenRenewer.java:601)
  at org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer$1.run(DelegationTokenRenewer.java:598)
  at java.security.AccessController.doPrivileged(Native Method)
  at javax.security.auth.Subject.doAs(Subject.java:422)
  at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1922)
  at org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer.renewToken(DelegationTokenRenewer.java:597)
  at org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer$RenewalTimerTask.run(DelegationTokenRenewer.java:531)
  at java.util.TimerThread.mainLoop(Timer.java:555)
  at java.util.TimerThread.run(Timer.java:505)
Caused by: org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.token.SecretManager$InvalidToken): hadoop tried to renew an expired token (HDFS_DELEGATION_TOKEN token 4361 for hncscwc) max expiration date: 2022-07-11 17:11:13,393+0800 currentTime: 2022-07-11 17:11:13,394+0800
  at org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager.renewToken(AbstractDelegationTokenSecretManager.java:499)
  at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.renewDelegationToken(FSNamesystem.java:5952)
  at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.renewDelegationToken(NameNodeRpcServer.java:675)
  at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.renewDelegationToken(ClientNamenodeProtocolServerSideTranslatorPB.java:1035)
  at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
  at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:447)
  at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:989)
  at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:850)
  at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:793)
  at java.security.AccessController.doPrivileged(Native Method)
  at javax.security.auth.Subject.doAs(Subject.java:422)
  at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1922)
  at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2489)

  at org.apache.hadoop.ipc.Client.getRpcResponse(Client.java:1486)
  at org.apache.hadoop.ipc.Client.call(Client.java:1432)
  at org.apache.hadoop.ipc.Client.call(Client.java:1342)
  at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:227)
  at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:116)
  at com.sun.proxy.$Proxy94.renewDelegationToken(Unknown Source)
  at 
org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB.renewDelegationToken(ClientNamenodeProtocolTranslatorPB.java:964)
  at sun.reflect.GeneratedMethodAccessor277.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.apache.hadoop.io.retry.RetryInvocationHandler.invokeMethod(RetryInvocationHandler.java:411)
  at org.apache.hadoop.io.retry.RetryInvocationHandler$Call.invokeMethod(RetryInvocationHandler.java:165)
  at org.apache.hadoop.io.retry.RetryInvocationHandler$Call.invoke(RetryInvocationHandler.java:157)
  at org.apache.hadoop.io.retry.RetryInvocationHandler$Call.invokeOnce(RetryInvocationHandler.java:95)
  at org.apache.hadoop.io.retry.RetryInvocationHandler.invoke(RetryInvocationHandler.java:348)
  at com.sun.proxy.$Proxy95.renewDelegationToken(Unknown Source)
  at org.apache.hadoop.hdfs.DFSClient$Renewer.renew(DFSClient.java:759)
  ... 10 more
2022-07-11 17:11:13,399 ERROR org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer: removing failed delegation token for appid=[application_1637733238080_3800];t=ha-hdfs:hdfsHACluster

从上面的日志可以看到,任务从提交后,delegation token每天都有在更新,然而运行到第7天后,更新失败而失效。失效后,NN内部会删除无效的token,此时如果任务失败需要重试,或者任务结束需要进行日志聚合,都会继续使用该无效的token来操作hdfs,最终结果就是在NN中找不到对应的token而抛异常导致失败。

【问题解决】


要解决该问题,一种最简单直接的办法就是加大delegation token的最大生命周期时间。

但一开始觉得该办法略有些low,尤其对于flink长周期运行的实时任务的场景,是无法确定任务的运行时长的,因此也就无法确定设置token的最大生命周期。

因此,再次分析了源码,发现RM中对于将要过期(超过最大生命周期)的delegation token,会按需重新申请一个新的token,也就是定时器线程中token更新之前的requestNewHdfsDelegationTokenIfNeeded方法。

来看看具体的实现逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void requestNewHdfsDelegationTokenIfNeeded(
    final DelegationTokenToRenew dttr) 
    throws IOException, InterruptedException {

    // 拥有特权 并且 token类型为委派token 并且 快到最大生命周期
    if (hasProxyUserPrivileges &&
        dttr.maxDate - dttr.expirationDate < credentialsValidTimeRemaining &&
        dttr.token.getKind().equals(HDFS_DELEGATION_KIND)) {

        final Collection<ApplicationId> applicationIds;
        synchronized (dttr.referringAppIds) {
            applicationIds = new HashSet<>(dttr.referringAppIds);
            dttr.referringAppIds.clear();
        }
        // remove all old expiring hdfs tokens for this application.
        for (ApplicationId appId : applicationIds) {
            Set<DelegationTokenToRenew> tokenSet = appTokens.get(appId);
            if (tokenSet == null || tokenSet.isEmpty()) {
                continue;
            }
            Iterator<DelegationTokenToRenew> iter = tokenSet.iterator();
            synchronized (tokenSet) {
                while (iter.hasNext()) {
                    DelegationTokenToRenew t = iter.next();
                    if (t.token.getKind().equals(HDFS_DELEGATION_KIND)) {
                        iter.remove();
                        allTokens.remove(t.token);
                        t.cancelTimer();
                        LOG.info("Removed expiring token " + t);
                    }
                }
            }
        }
        LOG.info("Token= (" + dttr + ") is expiring, request new token.");
        requestNewHdfsDelegationTokenAsProxyUser(
            applicationIds, dttr.user,
            dttr.shouldCancelAtEnd);
    }
}

申请到新的token之后,会在RM内部进行更新,然后通过NM的心跳响应同步给NM。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void requestNewHdfsDelegationTokenAsProxyUser(
    ...
    // Get new hdfs tokens for this user
    Credentials credentials = new Credentials();
    Token<?>[] newTokens = obtainSystemTokensForUser(user, credentials);
    DataOutputBuffer dob = new DataOutputBuffer();
    credentials.writeTokenStorageToStream(dob);
    ByteBuffer byteBuffer = ByteBuffer.wrap(dob.getData(), 0, dob.getLength());
    for (ApplicationId applicationId : referringAppIds) {
        // 更新app的delegation token
        // 在NM心跳时进行同步
        rmContext.getSystemCredentialsForApps().put(applicationId, byteBuffer);
    }
} 

public NodeHeartbeatResponse nodeHeartbeat(NodeHeartbeatRequest request)
    throws YarnException, IOException {
    ...
    ConcurrentMap<ApplicationId, ByteBuffer> systemCredentials =
        rmContext.getSystemCredentialsForApps();
    if (!systemCredentials.isEmpty()) {
        nodeHeartBeatResponse.setSystemCredentialsForApps(systemCredentials);
    }
    ...
}

NM在心跳响应中解析出token并在内存中更新保存,后续任务重试启动资源本地化和任务结束触发日志聚合时会使用到。

注意:这里只提到了资源本地化和日志聚合时会使用到更新后的token,那么正在运行的任务会用到更新后的token吗?

答案是不会(至少是2.X版本不会)。主要是因为:token已经写入到持久化文件中,任务启动时读取该文件获取token并使用;delegation token在更新后没有写入到持久化文件中,即使可以写入(更新)到该文件,也需要有机制通知任务进程更新读取该文件才行。因此正在运行中的任务在token过期后继续操作hdfs仍旧会抛出异常。

另外,在3.X的最新版本中,注意到有相关代码的改动,应该是通知正在运行的container,但具体细节还未深入研究,后面有时间再调研。

【相关配置】


与delegation token相关的配置包括:

配置项名称

默认值

说明

dfs.namenode.delegation.key.update-interval

1天

token更新密钥的时间间隔

dfs.namenode.delegation.token.renew-interval

1天

token更新的时间间隔

dfs.namenode.delegation.token.max-lifetime

7天

token的最大生命周期

yarn.resourcemanager.delegation-token.alwys-cancel

false

RM结束时是否需要移除token

yarn.resourcemanager.proxy-user-privileges.enabled

false

是否开启特权在delegation token快过期时重新申请新的token

yarn.resourcemanager.system-credentials.valid-time-remaining

10800000

距离最大生命周期之前多长时间进行重新申请token的操作,单位毫秒

yarn.resourcemanager.delegation-token-renewer.thread-count

50

RM中delegation token更新线程的线程数

【总结】


本文通过一个实际的问题,并结合源码讲解了hadoop的delegation token的相关原理。

文中如有不对的地方,欢迎拍砖指正。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陈猿解码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Spring高手之路17——动态代理的艺术与实践
动态代理是一种强大的设计模式,它允许开发者在运行时创建代理对象,用于拦截对真实对象的方法调用。这种技术在实现面向切面编程(AOP)、事务管理、权限控制等功能时特别有用,因为它可以在不修改原有代码结构的前提下,为程序动态地注入额外的逻辑。
砖业洋__
2024/04/13
4610
Spring高手之路17——动态代理的艺术与实践
SSM框架中十分常用的设计模式:动态代理
👆点击“博文视点Broadview”,获取更多书讯 本文介绍的是SSM框架中十分常用的设计模式,所以开发者掌握它十分必要。 动态代理和责任链无论在Spring还是MyBatis中都有重要的应用,只要随着本书的例子多写代码,反复体验,就能掌握。在分析Spring AOP和MyBatis技术原理时,我们还会不断提及它们,它们适用范围广,值得读者认真研究。 代理模式的意义在于生成一个占位(又称代理对象),来代理真实对象(又称目标对象),从而控制真实对象的访问。 先来谈谈什么是代理模式。 假设这样一个场景,你的公
博文视点Broadview
2023/05/06
2220
SSM框架中十分常用的设计模式:动态代理
【小家Java】JDK动态代理技术,你真学会了吗?(Proxy、ProxyClassFactory)
动态代理技术,相信我们都并不陌生。特别是在Spring框架内,大量的使用到了反射以及动态代理技术。但是如果我们只是停留在平时的运用阶段,此篇文章你其实是可以跳过的,因为反射、代理技术一般都只有在框架设计中才会使用到,业务开发是不用接触的。
YourBatman
2019/09/03
4400
【小家Java】JDK动态代理技术,你真学会了吗?(Proxy、ProxyClassFactory)
深入解析:Cglib与JDK动态代理的实现原理、区别及性能对比
在Java开发中,动态代理是一种强大的技术,它允许在运行时创建代理对象以添加行为,而无需修改原始类的代码。JDK动态代理和Cglib是两种主要的动态代理实现方式。本文将深入探讨它们的实现原理、区别、劣势以及性能对比。
AI码师
2024/05/29
3.2K0
深入解析:Cglib与JDK动态代理的实现原理、区别及性能对比
jdk动态代理和cglib动态代理详解
如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spring框架、hibernate框架中都采用了JDK动态代理,下面将结合代码阐述两种代理模式的使用与区别。
全栈程序员站长
2022/08/29
7200
jdk动态代理和cglib动态代理详解
Spring的两种动态代理Jdk与Cglib
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调InvokeHandler来处理。 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
全栈程序员站长
2022/08/04
7430
Spring的两种动态代理Jdk与Cglib
JDK之动态代理(JDK与CGLIB)与静态代理
代理类在程序运行时创建的代理方式被称为动态代理 静态代理中,代理类是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是运行时根据我们在Java代码中的指示动态生成的。相比较静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法,比如想要在每个代理方法前都加上一个处理方法,静态代理就需要在每个类内部加上这个方法
才疏学浅的木子
2022/11/28
2220
JDK之动态代理(JDK与CGLIB)与静态代理
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战
这里推荐一篇实用的文章:《Java 读取寄存器数据的实现与应用》,作者:【喵手】。
小马哥学JAVA
2024/11/21
2260
JDK动态代理
这里是最简单的Java接口和实现类的关系,此时可以开始动态代理了,一般会分为两个步骤:第一是建立代理对象和真实服务对象的代理和被代理关系,第二步是实现代理对象具体方法的逻辑。
itlemon
2020/04/03
5320
jdk动态代理和cglb动态代理
静态代理是在编译时就确定了代理类的代码,在程序运行前就已经存在了代理类的class文件。代理类与委托类的关系在编译时就已经确定,因此被称为静态代理。在静态代理中,代理类需要实现与委托类相同的接口或者继承委托类的父类,以便能够对委托类进行代理操作。
一个风轻云淡
2023/10/15
2310
jdk动态代理和cglb动态代理
JDK动态代理和CGLIB动态代理
Java动态代理是一种在运行时创建代理对象的技术,它允许开发者在不修改目标类代码的情况下,通过代理类对目标类的实例方法进行增强或拦截。动态代理的核心价值在于能够在程序运行阶段动态地生成一个实现了预定义接口的新类,这个新类就是所谓的“代理类”。
程序猿川子
2025/02/27
1750
JDK动态代理和CGLIB动态代理
Java代理相关:JDK动态代理、CGLIB动态代理
代理(Proxy)是一种设计模式,提供了对目标对象另外的一种访问方式。可以在目标对象实现的基础上,增加额外的功能操作,即扩展目标对象的功能。
lpe234
2020/07/27
4320
【面试系列】JDK动态代理和CGLIB静态代理 - Java技术债务
是否在面试过程中经常被问到Spring的代理的问题:比如说几种代理方式?两种代理方式的区别?或者问为什么JDK动态代理只能代理接口?
Java技术债务
2024/06/21
1220
【面试系列】JDK动态代理和CGLIB静态代理 - Java技术债务
Java 动态代理详解
动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。
小旋锋
2019/01/21
1.1K0
Java两种动态代理JDK动态代理和CGLIB动态代理[通俗易懂]
代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。
全栈程序员站长
2022/07/02
3660
Java两种动态代理JDK动态代理和CGLIB动态代理[通俗易懂]
java 代理模式-静态代理与动态代理
    举个例子吧:我们生活中的租房问题。假如我们去租个房子,我们大多数情况下是不会知道房主(就是真正租房,一手货源)的,我们是不是都是先去某些租房平台,或者去找当地的中介去询问何时的房子。我们通过九牛二虎之力在中介那里找到了个物美价廉的房子后,你的租金是不是交给了中介,中介还会收取一些额外的推荐费啦,押金啦、手续费等之类的,那么好,这样的一小段,就已经出来了其中两大核心对象了。
Arebirth
2019/09/24
4330
java 代理模式-静态代理与动态代理
基于JDK动态代理的自定义拦截器
接口中定义了三个方法,分别是:前置方法、后置方法和环绕方法(此叫法类似Spring AOP中的前置通知、后置通知以及环绕通知)。三个方法拥有相同的参数:第一个参数是动态代理对象,第二个是真实服务对象,第三个是方法对象,第四个是方法需要的参数集合。
itlemon
2020/04/03
1.2K0
jdk静态代理,jdk动态代理,cglib动态代理
代理是什么呢?举个例子,一个公司是卖摄像头的,但公司不直接跟用户打交道,而是通过代理商跟用户打交道。如果:公司接口中有一个卖产品的方法,那么公司需要实现这个方法,而代理商也必须实现这个方法。如果公司卖多少钱,代理商也卖多少钱,那么代理商就赚不了钱。所以代理商在调用公司的卖方法后,加上自己的利润然后再把产品卖给客户。而客户部直接跟公司打交道,或者客户根本不知道公司的存在,然而客户最终却买到了产品。
互扯程序
2019/07/01
5120
jdk静态代理,jdk动态代理,cglib动态代理
动态代理技术的运用
“ 在前一篇文章我们了解了Spring AOP的简单运用,我们发现面向切面编程的核心是动态代理,我们这篇文章主要就是看一下:JDK自带的动态代理和CGLIB的动态代理”
每天学Java
2020/06/02
2980
通俗易懂讲解一下代理模式
维基百科所说的优点抽象,这里我们简单来讲就是通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。
Dream城堡
2019/10/28
5020
通俗易懂讲解一下代理模式
推荐阅读
相关推荐
Spring高手之路17——动态代理的艺术与实践
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档