All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tencent.polaris.plugins.connector.grpc.SpecStreamClient Maven / Gradle / Ivy

/*
 * Tencent is pleased to support the open source community by making Polaris available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.tencent.polaris.plugins.connector.grpc;

import com.google.protobuf.StringValue;
import com.google.protobuf.TextFormat;
import com.tencent.polaris.annonation.JustForTest;
import com.tencent.polaris.api.exception.ErrorCode;
import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.api.exception.ServerCodes;
import com.tencent.polaris.api.exception.ServerErrorResponseException;
import com.tencent.polaris.api.plugin.server.ServerEvent;
import com.tencent.polaris.api.pojo.ServiceEventKey;
import com.tencent.polaris.api.pojo.ServiceEventKey.EventType;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.client.util.NamedThreadFactory;
import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl;
import com.tencent.polaris.logging.LoggerFactory;
import com.tencent.polaris.plugins.connector.common.ServiceUpdateTask;
import com.tencent.polaris.plugins.connector.common.constant.ServiceUpdateTaskConstant.Type;
import com.tencent.polaris.specification.api.v1.service.manage.PolarisGRPCGrpc;
import com.tencent.polaris.specification.api.v1.service.manage.RequestProto;
import com.tencent.polaris.specification.api.v1.service.manage.ResponseProto;
import com.tencent.polaris.specification.api.v1.service.manage.ServiceProto;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 用于cluster/healthcheck/heartbeat的服务发现
 *
 * @author Haotian Zhang
 */
public class SpecStreamClient implements StreamObserver {


    private static final Logger LOG = LoggerFactory.getLogger(SpecStreamClient.class);

    /**
     * 同步锁
     */
    private final Object clientLock = new Object();

    private ServerConnectorConfigImpl connectorConfig;

    /**
     * 在流中没有结束的任务
     */
    private final Map pendingTask = new HashMap<>();

    /**
     * 连接是否可用
     */
    private final AtomicBoolean endStream = new AtomicBoolean(false);

    /**
     * GRPC stream客户端
     */
    private final StreamObserver discoverClient;

    /**
     * 连接对象
     */
    private final Connection connection;

    /**
     * 请求ID
     */
    private final String reqId;

    /**
     * 最近更新时间
     */
    private final AtomicLong lastRecvTimeMs = new AtomicLong(0);

    /**
     * 创建时间
     */
    private final long createTimeMs;

    /**
     * 连接空闲时间
     */
    private final long connectionIdleTimeoutMs;

    /**
     * 清理长期处于 pending 状态的任务
     */
    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("clean-expire-pendingtask"));

    private static long PENDING_TASK_EXPIRE_MS = 30_000L;

    /**
     * 构造函数
     *
     * @param connection              连接
     * @param connectionIdleTimeoutMs 连接超时
     * @param serviceUpdateTask       初始更新任务
     */
    public SpecStreamClient(ServerConnectorConfigImpl connectorConfig, Connection connection, long connectionIdleTimeoutMs,
                            ServiceUpdateTask serviceUpdateTask) {
        this.connectorConfig = connectorConfig;
        this.connection = connection;
        this.connectionIdleTimeoutMs = connectionIdleTimeoutMs;
        createTimeMs = System.currentTimeMillis();
        reqId = GrpcUtil.nextGetInstanceReqId();
        PolarisGRPCGrpc.PolarisGRPCStub namingStub = PolarisGRPCGrpc.newStub(connection.getChannel());
        namingStub = GrpcUtil.attachRequestHeader(namingStub, reqId);
        namingStub = GrpcUtil.attachAccessToken(connectorConfig.getToken(), namingStub);
        discoverClient = namingStub.discover(this);
        putPendingTask(serviceUpdateTask);
        executor.schedule(new ExpirePendingTaskCleaner(this, PENDING_TASK_EXPIRE_MS), 5, TimeUnit.SECONDS);
    }

    @JustForTest
    public SpecStreamClient(long pendingTaskExpireMs) {
        this.connection = null;
        this.connectionIdleTimeoutMs = 0;
        this.createTimeMs = System.currentTimeMillis();
        this.discoverClient = null;
        this.reqId = UUID.randomUUID().toString();
        this.executor.schedule(new ExpirePendingTaskCleaner(this, pendingTaskExpireMs), 5, TimeUnit.SECONDS);
    }

    /**
     * 关闭流
     *
     * @param closeSend 是否发送EOF
     */
    public void closeStream(boolean closeSend) {
        executor.shutdownNow();
        boolean endStreamOK = endStream.compareAndSet(false, true);
        if (!endStreamOK) {
            return;
        }
        if (closeSend) {
            LOG.info("[ServerConnector]connection {} start to closeSend", connection.getConnID());
            discoverClient.onCompleted();
        }
        connection.release(GrpcUtil.OP_KEY_DISCOVER);
    }

    private boolean isEndStream() {
        return endStream.get();
    }


    public String getReqId() {
        return reqId;
    }

    /**
     * 发送请求
     *
     * @param serviceUpdateTask 服务更新任务
     */
    public void sendRequest(ServiceUpdateTask serviceUpdateTask) {
        ServiceEventKey serviceEventKey = serviceUpdateTask.getServiceEventKey();
        ServiceProto.Service.Builder builder = ServiceProto.Service.newBuilder();
        builder.setName(StringValue.newBuilder().setValue(serviceEventKey.getServiceKey().getService()).build());
        builder.setNamespace(StringValue.newBuilder().setValue(serviceEventKey.getServiceKey().getNamespace()).build());
        builder.setRevision(
                StringValue.newBuilder().setValue(serviceUpdateTask.getEventHandler().getRevision()).build());

        RequestProto.DiscoverRequest.Builder req = RequestProto.DiscoverRequest.newBuilder();
        req.setType(GrpcUtil.buildDiscoverRequestType(serviceEventKey.getEventType())); // switch
        req.setService(builder);
        if (serviceUpdateTask.getTaskType() == Type.FIRST) {
            LOG.info("[ServerConnector]send request(id={}) to {} for service {}", reqId, connection.getConnID(),
                    serviceEventKey);
        } else {
            LOG.debug("[ServerConnector]send request(id={}) to {} for service {}", reqId, connection.getConnID(),
                    serviceEventKey);
        }
        discoverClient.onNext(req.build());
    }

    /**
     * 检查该应答是否合法,不合法则走异常流程
     *
     * @param response 服务端应答
     * @return 合法返回true,否则返回false
     */
    private ValidResult validMessage(ResponseProto.DiscoverResponse response) {
        ErrorCode errorCode = ErrorCode.Success;
        if (response.hasCode()) {
            errorCode = ServerCodes.convertServerErrorToRpcError(response.getCode().getValue());
        }
        ServiceProto.Service service = response.getService();
        EventType eventType = GrpcUtil.buildEventType(response.getType());
        if (!eventType.equals(EventType.SERVICE) && (StringUtils.isEmpty(service.getNamespace().getValue())
                || StringUtils.isEmpty(service.getName().getValue()))) {
            return new ValidResult(null, ErrorCode.INVALID_SERVER_RESPONSE,
                    "service is empty, response text is " + response.toString());
        }
        if (eventType == EventType.UNKNOWN) {
            return new ValidResult(null, ErrorCode.INVALID_SERVER_RESPONSE,
                    "invalid event type " + response.getType());
        }
        ServiceEventKey serviceEventKey = new ServiceEventKey(
                new ServiceKey(service.getNamespace().getValue(), service.getName().getValue()), eventType);
        if (errorCode == ErrorCode.SERVER_ERROR) {
            //返回了500错误
            return new ValidResult(serviceEventKey, errorCode, "invalid event type " + response.getType());
        }

        return new ValidResult(serviceEventKey, ErrorCode.Success, "");
    }

    /**
     * 异常回调
     *
     * @param validResult 异常
     */
    public void exceptionCallback(ValidResult validResult) {
        this.closeStream(false);
        if (validResult.getMessage().contains("EOF")) {
            // print debug log when message contains "EOF" and it is not a normal exception.
            LOG.debug("[ServerConnector]exceptionCallback: errCode {}, info {}, serviceEventKey {}",
                    validResult.getErrorCode(), validResult.getMessage(), validResult.getServiceEventKey());
        } else {
            LOG.error("[ServerConnector]exceptionCallback: errCode {}, info {}, serviceEventKey {}",
                    validResult.getErrorCode(), validResult.getMessage(), validResult.getServiceEventKey());
        }

        //report down
        connection.reportFail(validResult.getErrorCode());
        List notifyTasks = new ArrayList<>();
        synchronized (clientLock) {
            ServiceEventKey serviceEventKey = validResult.getServiceEventKey();
            if (null == serviceEventKey) {
                if (CollectionUtils.isNotEmpty(pendingTask.values())) {
                    notifyTasks.addAll(listPendingTasks());

                    for (SpecTask value : pendingTask.values()) {
                        ServiceUpdateTask task = value.task;
                        PolarisException error = ServerErrorResponseException.build(ErrorCode.NETWORK_ERROR.getCode(),
                                String.format("[ServerConnector]code %s, fail to query service %s from server(%s): %s",
                                        validResult.getErrorCode(), task.getServiceEventKey(),
                                        connection.getConnID(), validResult.getMessage()));
                        task.notifyServerEvent(new ServerEvent(task.getServiceEventKey(), null, error));
                    }

                    pendingTask.clear();
                }
            } else {
                ServiceUpdateTask task = removePendingTask(serviceEventKey);
                if (null != task) {
                    notifyTasks.add(task);
                    PolarisException error = ServerErrorResponseException.build(ErrorCode.NETWORK_ERROR.getCode(),
                            String.format("[ServerConnector]code %s, fail to query service %s from server(%s): %s",
                                    validResult.getErrorCode(), task.getServiceEventKey(),
                                    connection.getConnID(), validResult.getMessage()));
                    task.notifyServerEvent(new ServerEvent(task.getServiceEventKey(), null, error));

                }
            }
        }
        for (ServiceUpdateTask notifyTask : notifyTasks) {
            notifyTask.retry();
        }
    }

    /**
     * 正常回调
     *
     * @param response 应答说
     */
    public void onNext(ResponseProto.DiscoverResponse response) {
        lastRecvTimeMs.set(System.currentTimeMillis());
        ValidResult validResult = validMessage(response);
        if (validResult.errorCode != ErrorCode.Success) {
            exceptionCallback(validResult);
            return;
        }
        ServiceProto.Service service = response.getService();
        ServiceKey serviceKey = new ServiceKey(service.getNamespace().getValue(), service.getName().getValue());
        EventType eventType = GrpcUtil.buildEventType(response.getType());
        ServiceEventKey serviceEventKey = new ServiceEventKey(serviceKey, eventType);
        ServiceUpdateTask updateTask;
        synchronized (clientLock) {
            updateTask = removePendingTask(serviceEventKey);
        }
        if (null == updateTask) {
            LOG.warn("[ServerConnector]callback not found for:{}", TextFormat.shortDebugString(service));
            return;
        }
        if (updateTask.getTaskType() == Type.FIRST) {
            LOG.info("[ServerConnector]request(id={}) receive response for {}", getReqId(), serviceEventKey);
        } else {
            LOG.debug("[ServerConnector]request(id={}) receive response for {}", getReqId(), serviceEventKey);
        }
        boolean svcDeleted = updateTask.notifyServerEvent(new ServerEvent(serviceEventKey, response, null));
        if (!svcDeleted) {
            updateTask.addUpdateTaskSet();
        }
    }

    @Override
    public void onError(Throwable throwable) {
        exceptionCallback(new ValidResult(null, ErrorCode.NETWORK_ERROR,
                String.format("stream %s received error from server(%s), error is %s", getReqId(),
                        connection.getConnID().toString(), throwable.getMessage())));
    }

    @Override
    public void onCompleted() {
        exceptionCallback(new ValidResult(null, ErrorCode.NETWORK_ERROR,
                String.format("stream %s EOF by server(%s)", getReqId(), connection.getConnID().toString())));
    }

    /**
     * 同步清理过期的流对象
     */
    public void syncCloseExpireStream() {
        synchronized (clientLock) {
            closeExpireStream();
        }
    }

    private boolean closeExpireStream() {
        //检查是否过期
        long lastRecvMs = lastRecvTimeMs.get();
        long nowMs = System.currentTimeMillis();
        long connIdleTime;
        if (lastRecvMs == 0) {
            //如果没有收到过消息,就通过创建时间来判断
            connIdleTime = nowMs - createTimeMs;
        } else {
            connIdleTime = nowMs - lastRecvMs;
        }
        if (connIdleTime >= connectionIdleTimeoutMs) {
            //连接已经过期
            closeStream(true);
            return true;
        }
        return false;
    }

    /**
     * checkAvailable
     *
     * @param serviceUpdateTask 更新任务
     * @return 是否可用
     */
    public boolean checkAvailable(ServiceUpdateTask serviceUpdateTask) {
        if (isEndStream()) {
            return false;
        }
        if (!Connection.isAvailableConnection(connection)) {
            return false;
        }
        synchronized (clientLock) {
            if (isEndStream()) {
                return false;
            }
            if (closeExpireStream()) {
                return false;
            }
            ServiceEventKey serviceEventKey = serviceUpdateTask.getServiceEventKey();
            SpecTask lastUpdateTask = pendingTask.get(serviceEventKey);
            if (null != lastUpdateTask) {
                LOG.warn("[ServerConnector]pending task {} has been overwritten", lastUpdateTask);
            }
            putPendingTask(serviceUpdateTask);
        }
        return true;
    }

    private List listPendingTasks() {
        return pendingTask.values().stream().map(specTask -> specTask.task).collect(Collectors.toList());
    }

    public void putPendingTask(ServiceUpdateTask serviceUpdateTask) {
        LOG.debug("Put " + serviceUpdateTask.getServiceEventKey() + "to pending task map. reqId: " + reqId);
        pendingTask.put(serviceUpdateTask.getServiceEventKey(), new SpecTask(serviceUpdateTask));
    }

    public ServiceUpdateTask removePendingTask(ServiceEventKey serviceEventKey) {
        LOG.debug("Remove " + serviceEventKey + "from pending task map. reqId: " + reqId);
        SpecTask task = pendingTask.remove(serviceEventKey);
        if (Objects.isNull(task)) {
            return null;
        }
        return task.task;
    }

    private static class ValidResult {

        final ServiceEventKey serviceEventKey;
        final ErrorCode errorCode;
        final String message;

        public ValidResult(ServiceEventKey serviceEventKey, ErrorCode errorCode, String message) {
            this.serviceEventKey = serviceEventKey;
            this.errorCode = errorCode;
            this.message = message;
        }

        public ServiceEventKey getServiceEventKey() {
            return serviceEventKey;
        }

        public ErrorCode getErrorCode() {
            return errorCode;
        }

        public String getMessage() {
            return message;
        }
    }

    private static class SpecTask {
        private final ServiceUpdateTask task;

        private final long submitTimeMs;

        private SpecTask(ServiceUpdateTask task) {
            this.task = task;
            this.submitTimeMs = System.currentTimeMillis();
        }
    }

    private static class ExpirePendingTaskCleaner implements Runnable {

        private final SpecStreamClient client;

        private final long pendingTaskExpireMs;

        private ExpirePendingTaskCleaner(SpecStreamClient client, long pendingTaskExpireMs) {
            this.client = client;
            this.pendingTaskExpireMs = pendingTaskExpireMs;
        }

        @Override
        public void run() {
            try {
                realCheck();
            } finally {
                client.executor.schedule(this, 5, TimeUnit.SECONDS);
            }
        }

        private void realCheck() {
            long current = System.currentTimeMillis();
            synchronized (client.clientLock) {
                Map waitRetry = new HashMap<>();
                for (Map.Entry entry : client.pendingTask.entrySet()) {
                    SpecTask specTask = entry.getValue();
                    if (current - specTask.submitTimeMs > pendingTaskExpireMs) {
                        waitRetry.put(entry.getKey(), specTask.task);
                    }
                }

                waitRetry.forEach((serviceEventKey, serviceUpdateTask) -> {
                    LOG.info("[ServerConnector] retry pending task {}, because it's long time to running", serviceEventKey);
                    client.removePendingTask(serviceEventKey);
                    serviceUpdateTask.retry();
                });
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy