Método de Sentinel para implementar el control de flujo de clúster configurado dinámicamente

Método de Sentinel para implementar el control de flujo de clúster configurado dinámicamente

introducir

06-clúster-integrado-8081

¿Por qué utilizar el control de flujo de clúster?

En comparación con el control de flujo de una sola máquina, establecemos un umbral de limitación de corriente de una sola máquina para cada máquina. En circunstancias ideales, el umbral de limitación de corriente de todo el grupo es el número de máquinas ✖️el umbral de una sola máquina. Sin embargo, en situaciones reales, el flujo a cada máquina puede ser desigual, lo que puede provocar que algunas máquinas comiencen a limitar el flujo cuando no se alcanza la cantidad total. Por lo tanto, será imposible limitar con precisión el tráfico general si está limitado únicamente por la dimensión única de la máquina. El control de flujo del clúster puede controlar con precisión el número total de llamadas en todo el clúster y, combinado con la limitación de corriente de una sola máquina, puede ejercer mejor el efecto del control de flujo .

Según el problema del tráfico desigual de una sola máquina y cómo configurar el QPS general del clúster, necesitamos crear un modo de limitación de corriente del clúster. En este momento, naturalmente pensamos que podemos encontrar un servidor para contar específicamente el número total. de llamadas Todas las instancias se comunican con este servidor para determinar si se puede llamar. Este es el método más básico de control de flujo de clústeres.

principio

El principio de limitación de corriente del clúster es muy simple. Al igual que la limitación de corriente independiente, se requieren estadísticas como qps. La diferencia es que la versión independiente realiza estadísticas en cada instancia, mientras que la versión del clúster tiene una instancia dedicada para las estadísticas. .

Este servidor de token especial se llama Sentinel para datos estadísticos. Otras instancias como clientes de token Sentinel solicitarán tokens del servidor de tokens. Si se puede obtener el token, significa que el qps actual no ha alcanzado el umbral total; de lo contrario, esto significa que el Se ha alcanzado el umbral total del clúster y es necesario bloquear la instancia actual, como se muestra en la siguiente figura:

imagen

En comparación con el control de flujo independiente, existen dos identidades en el control de flujo de clúster:

  • Cliente de token: cliente de control de flujo de clúster, utilizado para comunicarse con el servidor de token para solicitar tokens. El servidor de limitación de corriente del clúster devolverá los resultados al cliente para decidir si limita el flujo actual.
  • Servidor de token: el servidor de control de flujo del clúster maneja las solicitudes del cliente de token y determina si se debe emitir el token (si se debe permitir que pase) en función de las reglas del clúster configuradas.

Sólo hay una identidad en el control de flujo independiente y cada centinela es un servidor de token.

Tenga en cuenta que el servidor de token en la limitación de corriente del clúster es un solo punto. Una vez que el servidor de token muere, la limitación de corriente del clúster degenerará en un modo de limitación de corriente de una sola máquina.

El control de flujo del clúster Sentinel admite dos tipos de reglas: reglas de limitación actuales y reglas de puntos de acceso, y admite dos formas de métodos de cálculo de umbrales:

  • Modo general del clúster: es decir, limitar los qps generales de un recurso en todo el clúster para que no exceda este umbral.
  • Modo de amortización de una sola máquina: el umbral configurado en el modo de amortización de una sola máquina es igual al límite que puede soportar una sola máquina. El servidor de token calculará el umbral total en función del número de conexiones (por ejemplo, hay 3 clientes). conectado al servidor de token en modo independiente, y luego el umbral de amortización de una sola máquina configurado es 10, luego el número total calculado de clústeres es 30), y el límite se basa en el umbral total calculado. Este método calcula el umbral total en tiempo real en función del número actual de conexiones, lo cual es muy adecuado para entornos donde las máquinas cambian con frecuencia.

Método de implementación

Hay dos métodos de implementación para el servidor de tokens:

Una es la implementación independiente, que consiste en iniciar un servicio de servidor de token por separado para manejar las solicitudes de los clientes de token, como se muestra en la siguiente figura:

imagen

Si el servicio del servidor de tokens implementado de forma independiente falla, otros clientes de tokens se degradarán al modo de control de flujo local, es decir, la versión independiente del control de flujo. Por lo tanto, este método de limitación del flujo del clúster debe garantizar la alta disponibilidad del token. servidor.

Uno es la implementación integrada, que se inicia como un servidor de token integrado y un servicio en el mismo proceso. En este modo, cada instancia del clúster es igual y el servidor y el cliente del token se pueden cambiar en cualquier momento, como se muestra en la siguiente figura:

imagen

En el modo de implementación integrado, si el servicio del servidor de token se cuelga, podemos actualizar otro cliente de token al servidor de token. Por supuesto, si no queremos usar el servidor de token actual, también podemos elegir otro cliente de token para asumirlo. esta responsabilidad Responsabilidad y cambiar el servidor de token actual al cliente de token. Sentinel nos proporciona una API para cambiar entre servidor token y cliente token:

http://192.168.175.1:8721/setClusterMode?mode=1

Entre ellos, el modo 0representa al cliente, 1representa al servidor -1y representa el apagado.

Modo integrado

Importar dependencias
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-cluster-server-default</artifactId>
            <version>2.0.0-alpha</version>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-cluster-client-default</artifactId>
            <version>2.0.0-alpha</version>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>2.0.0-alpha</version>
        </dependency>
        
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10</version>
        </dependency>
aplicación.yml
server:
  port: 8081

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: gj001212
    type: com.alibaba.druid.pool.DruidDataSource
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.146.1:8839

    sentinel:
      transport:
        dashboard: localhost:7777
        port: 8719
      eager: true
      web-context-unify: false

  application:
    name: cloudalibaba-sentinel-clusterServer

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志

Modifique la configuración de opciones de VM e inicie tres instancias con diferentes puertos.

-Dserver.port=9091 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true

-Dserver.port=9092 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true

-Dserver.port=9093 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true
Configuración de la consola

Después de iniciar sesión en la consola Sentinel y obtener acceso, podemos ver el control de flujo del clúster en Sentinel:

Haga clic para agregar el servidor de tokens.

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-MNDP51sm-1683617959600) (C:\Users\86151\AppData\Roaming\Typora\ typora-user-images\ image-20230509103407298.png)]

Sentinel combina nacos para implementar la limitación de corriente del clúster
Clase de análisis ClusterGroupEntity
package com.liang.springcloud.alibaba.entity;

import java.util.Set;

/**
 * @author Eric Zhao
 * @since 1.4.1
 */
public class ClusterGroupEntity {
    
    

    private String machineId;
    private String ip;
    private Integer port;

    private Set<String> clientSet;

    public String getMachineId() {
    
    
        return machineId;
    }

    public ClusterGroupEntity setMachineId(String machineId) {
    
    
        this.machineId = machineId;
        return this;
    }

    public String getIp() {
    
    
        return ip;
    }

    public ClusterGroupEntity setIp(String ip) {
    
    
        this.ip = ip;
        return this;
    }

    public Integer getPort() {
    
    
        return port;
    }

    public ClusterGroupEntity setPort(Integer port) {
    
    
        this.port = port;
        return this;
    }

    public Set<String> getClientSet() {
    
    
        return clientSet;
    }

    public ClusterGroupEntity setClientSet(Set<String> clientSet) {
    
    
        this.clientSet = clientSet;
        return this;
    }

    @Override
    public String toString() {
    
    
        return "ClusterGroupEntity{" +
                "machineId='" + machineId + '\'' +
                ", ip='" + ip + '\'' +
                ", port=" + port +
                ", clientSet=" + clientSet +
                '}';
    }
}
Constantes
/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.liang.springcloud.alibaba;

/**
 * @author Eric Zhao
 */
public final class Constants {
    
    

    public static final String FLOW_POSTFIX = "-flow-rules";
    public static final String PARAM_FLOW_POSTFIX = "-param-rules";
    public static final String SERVER_NAMESPACE_SET_POSTFIX = "-cs-namespace-set";
    public static final String CLIENT_CONFIG_POSTFIX = "-cluster-client-config";
    public static final String CLUSTER_MAP_POSTFIX = "-cluster-map";

    private Constants() {
    
    }
}

Cree META-INF/service en la carpeta de recursos, luego cree un archivo llamado com.alibaba.csp.sentinel.init.InitFunc y nombre la ruta completa de la clase que implementa la interfaz InitFunc en el archivo. El contenido es el siguiente :

com.liang.springcloud.alibaba.init.ClusterInitFunc
FunciónInitClúster
package com.liang.springcloud.alibaba.init;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.liang.springcloud.alibaba.Constants;
import com.liang.springcloud.alibaba.entity.ClusterGroupEntity;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import com.alibaba.csp.sentinel.util.AppNameUtil;
import com.alibaba.csp.sentinel.util.HostNameUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;

/**
 * @author Eric Zhao
 */
public class ClusterInitFunc implements InitFunc {
    
    

    private static final String APP_NAME = AppNameUtil.getAppName();

    private final String remoteAddress = "localhost:8839";
    private final String groupId = "SENTINEL_GROUP";

    private final String flowDataId = APP_NAME + Constants.FLOW_POSTFIX;
    private final String paramDataId = APP_NAME + Constants.PARAM_FLOW_POSTFIX;
    private final String configDataId = APP_NAME + "-cluster-client-config";
    private final String clusterMapDataId = APP_NAME + Constants.CLUSTER_MAP_POSTFIX;

    @Override
    public void init() throws Exception {
    
    
        // Register client dynamic rule data source.
        initDynamicRuleProperty();

        // Register token client related data source.
        // Token client common config:
        initClientConfigProperty();
        // Token client assign config (e.g. target token server) retrieved from assign map:
        initClientServerAssignProperty();

        // Register token server related data source.
        // Register dynamic rule data source supplier for token server:
        registerClusterRuleSupplier();
        // Token server transport config extracted from assign map:
        initServerTransportConfigProperty();

        // Init cluster state property for extracting mode from cluster map data source.
        initStateProperty();
    }

    private void initDynamicRuleProperty() {
    
    
        ReadableDataSource<String, List<FlowRule>> ruleSource = new NacosDataSource<>(remoteAddress, groupId,
                flowDataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
    
    }));
        FlowRuleManager.register2Property(ruleSource.getProperty());

        ReadableDataSource<String, List<ParamFlowRule>> paramRuleSource = new NacosDataSource<>(remoteAddress, groupId,
                paramDataId, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {
    
    }));
        ParamFlowRuleManager.register2Property(paramRuleSource.getProperty());
    }

    private void initClientConfigProperty() {
    
    
        ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(remoteAddress, groupId,
                configDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientConfig>() {
    
    }));
        ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
    }

    private void initServerTransportConfigProperty() {
    
    
        ReadableDataSource<String, ServerTransportConfig> serverTransportDs = new NacosDataSource<>(remoteAddress, groupId,
                clusterMapDataId, source -> {
    
    
            List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {
    
    });
            return Optional.ofNullable(groupList)
                    .flatMap(this::extractServerTransportConfig)
                    .orElse(null);
        });
        ClusterServerConfigManager.registerServerTransportProperty(serverTransportDs.getProperty());
    }

    private void registerClusterRuleSupplier() {
    
    
        // Register cluster flow rule property supplier which creates data source by namespace.
        // Flow rule dataId format: ${namespace}-flow-rules
        ClusterFlowRuleManager.setPropertySupplier(namespace -> {
    
    
            ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
                    namespace + Constants.FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
    
    }));
            return ds.getProperty();
        });
        // Register cluster parameter flow rule property supplier which creates data source by namespace.
        ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
    
    
            ReadableDataSource<String, List<ParamFlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
                    namespace + Constants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {
    
    }));
            return ds.getProperty();
        });
    }

    private void initClientServerAssignProperty() {
    
    
        // Cluster map format:
        // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","machineId":"112.12.88.68@8728","port":11111}]
        // machineId: <ip@commandPort>, commandPort for port exposed to Sentinel dashboard (transport module)
        ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(remoteAddress, groupId,
                clusterMapDataId, source -> {
    
    
            List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {
    
    });
            return Optional.ofNullable(groupList)
                    .flatMap(this::extractClientAssignment)
                    .orElse(null);
        });
        ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());
    }

    private void initStateProperty() {
    
    
        // Cluster map format:
        // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","machineId":"112.12.88.68@8728","port":11111}]
        // machineId: <ip@commandPort>, commandPort for port exposed to Sentinel dashboard (transport module)
        ReadableDataSource<String, Integer> clusterModeDs = new NacosDataSource<>(remoteAddress, groupId,
                clusterMapDataId, source -> {
    
    
            List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {
    
    });
            return Optional.ofNullable(groupList)
                    .map(this::extractMode)
                    .orElse(ClusterStateManager.CLUSTER_NOT_STARTED);
        });
        ClusterStateManager.registerProperty(clusterModeDs.getProperty());
    }

    private int extractMode(List<ClusterGroupEntity> groupList) {
    
    
        // If any server group machineId matches current, then it's token server.
        if (groupList.stream().anyMatch(this::machineEqual)) {
    
    
            return ClusterStateManager.CLUSTER_SERVER;
        }
        // If current machine belongs to any of the token server group, then it's token client.
        // Otherwise it's unassigned, should be set to NOT_STARTED.
        boolean canBeClient = groupList.stream()
                .flatMap(e -> e.getClientSet().stream())
                .filter(Objects::nonNull)
                .anyMatch(e -> e.equals(getCurrentMachineId()));
        return canBeClient ? ClusterStateManager.CLUSTER_CLIENT : ClusterStateManager.CLUSTER_NOT_STARTED;
    }

    private Optional<ServerTransportConfig> extractServerTransportConfig(List<ClusterGroupEntity> groupList) {
    
    
        return groupList.stream()
                .filter(this::machineEqual)
                .findAny()
                .map(e -> new ServerTransportConfig().setPort(e.getPort()).setIdleSeconds(600));
    }

    private Optional<ClusterClientAssignConfig> extractClientAssignment(List<ClusterGroupEntity> groupList) {
    
    
        if (groupList.stream().anyMatch(this::machineEqual)) {
    
    
            return Optional.empty();
        }
        // Build client assign config from the client set of target server group.
        for (ClusterGroupEntity group : groupList) {
    
    
            if (group.getClientSet().contains(getCurrentMachineId())) {
    
    
                String ip = group.getIp();
                Integer port = group.getPort();
                return Optional.of(new ClusterClientAssignConfig(ip, port));
            }
        }
        return Optional.empty();
    }

    private boolean machineEqual(/*@Valid*/ ClusterGroupEntity group) {
    
    
        return getCurrentMachineId().equals(group.getMachineId());
    }

    private String getCurrentMachineId() {
    
    
        // Note: this may not work well for container-based env.
        return HostNameUtil.getIp() + SEPARATOR + TransportConfig.getRuntimePort();
    }

    private static final String SEPARATOR = "@";
}

Modo independiente

Supongo que te gusta

Origin blog.csdn.net/qq_52183856/article/details/130581568
Recomendado
Clasificación