sentinel限流集成redis的改造主要包括两大模块:

1、sentinel的dashboard的改造。

2、 应用端服务的改造。

首先需要下载sentinel的源码工程。sentinel改造版本:1.8.0 。

本文主要对sentinel的流控进行改造,至于其它限流规则(降级、热点、授权)的改造和流控改造类同,需要的话按照流控改造逻辑进行改造即可。

1. sentinel的dashboard的改造

1.1 需要添加的类:
com.alibaba.csp.sentinel.dashboard.rule目录下添加redis目录,添加文件:

FlowRuleRedisProvider.java
FlowRuleRedisPublisher.java
RedisConfig.java
RuleConstants.java
package com.alibaba.csp.sentinel.dashboard.rule.redis;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description 自定义实现基于redis的拉取规则
 * @Author: jinglonglong
 * @Datetime:2020-9-2
 **/
@Component("flowRuleRedisProvider")
public class FlowRuleRedisProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public List<FlowRuleEntity> getRules(String appName) throws Exception {
        System.out.println("Sentinel 从Redis拉取规则 begin >>>>>>>>>>>>>>>>>>>>");
        String value = JSON.toJSONString(redisTemplate.opsForValue().get(RuleConstants.ruleFlow + appName)) ;
        if (StringUtils.isEmpty(value)){
            return new ArrayList<>();
        }
        System.out.println("Sentinel 从Redis拉取规则 end >>>>>>>>>>>>>>>>>>>>");
        return JSONObject.parseArray(value,FlowRuleEntity.class);
    }
}
package com.alibaba.csp.sentinel.dashboard.rule.redis;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @Description 自定义实现限流配置推送规则
 * @Author: jinglonglong
 * @Datetime:2020-9-2
 **/
@Component("flowRuleRedisPublisher")
public class FlowRuleRedisPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
        System.out.println("Sentinel 向Redis推送规则 begin >>>>>>>>>>>>>>>>>>>>");
        if (rules == null) {
            return;
        }
        redisTemplate.multi();
        redisTemplate.opsForValue().set(RuleConstants.ruleFlow + app, rules);
        redisTemplate.convertAndSend(RuleConstants.ruleFlowChannel + app, rules);
        redisTemplate.exec();
        System.out.println("Sentinel 向Redis推送规则 end >>>>>>>>>>>>>>>>>>>>");
    }
}
package com.alibaba.csp.sentinel.dashboard.rule.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @Description
 * @Author: jinglonglong
 * @Datetime:2020-9-2
 **/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
    {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(factory);
        // 打开事务
        redisTemplate.setEnableTransactionSupport(true);
        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        serializer.setObjectMapper(mapper);

        redisTemplate.setValueSerializer(serializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
package com.alibaba.csp.sentinel.dashboard.rule.redis;

import org.springframework.stereotype.Component;

/**
 * @Description
 * @Author: jinglonglong
 * @Datetime:2020-9-2
 **/
@Component
public class RuleConstants {
    /**
     * 流控规则key前缀
     */
    public static final String ruleFlow = "sentinel_rule_flow_";
    public static final String ruleFlowChannel = "sentinel_rule_flow_channel_";
}

1.2 pom.xml文件中需要添加的依赖:

<!-- 集成redis -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-redis</artifactId>
    <version>1.8.0</version>
</dependency>
<!--redis依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
   <version>${spring.boot.version}</version>
</dependency>

1.3 application.properties需要添加redis连接信息:

# redis配置
spring.redis.database=0
spring.redis.host=10.4.11.128
spring.redis.port=6379
spring.redis.timeout=3000

1.4 需要修改的类

FlowControllerV1.java
/*
 * 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.alibaba.csp.sentinel.dashboard.controller;

import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.client.CommandFailedException;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.dashboard.util.AsyncUtils;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * Flow rule controller.
 *
 * @author leyou
 * @author Eric Zhao
 */
@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {

    private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);

    @Autowired
    private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

    @Autowired
    private SentinelApiClient sentinelApiClient;
    //-----------新增逻辑,服务于redis持久化---------------
    @Autowired
    @Qualifier("flowRuleRedisProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleRedisPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
    //-----------新增逻辑,服务于redis持久化---------------

    //修改apiQueryMachineRules,原版本见 apiQueryMachineRules_back
    @GetMapping("/rules")
    @AuthAction(PrivilegeType.READ_RULE)
    public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,
                                                             @RequestParam String ip,
                                                             @RequestParam Integer port) {

        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        try {
            List<FlowRuleEntity> rules = ruleProvider.getRules(app);
            if (rules != null && !rules.isEmpty()) {
                for (FlowRuleEntity entity : rules) {
                    entity.setApp(app);
                    if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
                        entity.setId(entity.getClusterConfig().getFlowId());
                    }
                }
            }
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (Throwable throwable) {
            logger.error("Error when querying flow rules", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

    //old apiQueryMachineRules
    @GetMapping("/rules0")
    @AuthAction(PrivilegeType.READ_RULE)
    public Result<List<FlowRuleEntity>> apiQueryMachineRules_back(@RequestParam String app,
                                                             @RequestParam String ip,
                                                             @RequestParam Integer port) {

        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        try {
            List<FlowRuleEntity> rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port);
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (Throwable throwable) {
            logger.error("Error when querying flow rules", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

    private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
        if (StringUtil.isBlank(entity.getApp())) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getIp())) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (entity.getPort() == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        if (StringUtil.isBlank(entity.getLimitApp())) {
            return Result.ofFail(-1, "limitApp can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getResource())) {
            return Result.ofFail(-1, "resource can't be null or empty");
        }
        if (entity.getGrade() == null) {
            return Result.ofFail(-1, "grade can't be null");
        }
        if (entity.getGrade() != 0 && entity.getGrade() != 1) {
            return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
        }
        if (entity.getCount() == null || entity.getCount() < 0) {
            return Result.ofFail(-1, "count should be at lease zero");
        }
        if (entity.getStrategy() == null) {
            return Result.ofFail(-1, "strategy can't be null");
        }
        if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
            return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
        }
        if (entity.getControlBehavior() == null) {
            return Result.ofFail(-1, "controlBehavior can't be null");
        }
        int controlBehavior = entity.getControlBehavior();
        if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
            return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
        }
        if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
            return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
        }
        if (entity.isClusterMode() && entity.getClusterConfig() == null) {
            return Result.ofFail(-1, "cluster config should be valid");
        }
        return null;
    }

    @PostMapping("/rule")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
        Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        entity.setId(null);
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        entity.setLimitApp(entity.getLimitApp().trim());
        entity.setResource(entity.getResource().trim());
        try {
                entity = repository.save(entity);

            publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
            return Result.ofSuccess(entity);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Failed to add new flow rule, app={}, ip={}", entity.getApp(), entity.getIp(), e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

    @PutMapping("/save.json")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<FlowRuleEntity> apiUpdateFlowRule(Long id, String app,
                                                  String limitApp, String resource, Integer grade,
                                                  Double count, Integer strategy, String refResource,
                                                  Integer controlBehavior, Integer warmUpPeriodSec,
                                                  Integer maxQueueingTimeMs) {
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }
        FlowRuleEntity entity = repository.findById(id);
        if (entity == null) {
            return Result.ofFail(-1, "id " + id + " dose not exist");
        }
        if (StringUtil.isNotBlank(app)) {
            entity.setApp(app.trim());
        }
        if (StringUtil.isNotBlank(limitApp)) {
            entity.setLimitApp(limitApp.trim());
        }
        if (StringUtil.isNotBlank(resource)) {
            entity.setResource(resource.trim());
        }
        if (grade != null) {
            if (grade != 0 && grade != 1) {
                return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");
            }
            entity.setGrade(grade);
        }
        if (count != null) {
            entity.setCount(count);
        }
        if (strategy != null) {
            if (strategy != 0 && strategy != 1 && strategy != 2) {
                return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got");
            }
            entity.setStrategy(strategy);
            if (strategy != 0) {
                if (StringUtil.isBlank(refResource)) {
                    return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
                }
                entity.setRefResource(refResource.trim());
            }
        }
        if (controlBehavior != null) {
            if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) {
                return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got");
            }
            if (controlBehavior == 1 && warmUpPeriodSec == null) {
                return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
            }
            if (controlBehavior == 2 && maxQueueingTimeMs == null) {
                return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
            }
            entity.setControlBehavior(controlBehavior);
            if (warmUpPeriodSec != null) {
                entity.setWarmUpPeriodSec(warmUpPeriodSec);
            }
            if (maxQueueingTimeMs != null) {
                entity.setMaxQueueingTimeMs(maxQueueingTimeMs);
            }
        }
        Date date = new Date();
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            if (entity == null) {
                return Result.ofFail(-1, "save entity fail: null");
            }

            publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(15000, TimeUnit.MILLISECONDS);
            return Result.ofSuccess(entity);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Error when updating flow rules, app={}, ip={}, ruleId={}", entity.getApp(),
                entity.getIp(), id, e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

    @DeleteMapping("/delete.json")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<Long> apiDeleteFlowRule(Long id) {

        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }
        FlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }

        try {
            repository.delete(id);
        } catch (Exception e) {
            return Result.ofFail(-1, e.getMessage());
        }
        try {
            publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get(5000, TimeUnit.MILLISECONDS);
            return Result.ofSuccess(id);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Error when deleting flow rules, app={}, ip={}, id={}", oldEntity.getApp(),
                oldEntity.getIp(), id, e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

    // old publishRules
    private CompletableFuture<Void> publishRules_back(String app, String ip, Integer port) {
        List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
        return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
    }
    //修改publishRules
    private CompletableFuture<Void> publishRules(String app, String ip, Integer port)
    {
        List<FlowRuleEntity> rules = repository.findAllByApp(app);
        try {
            //修改了redis中的存储及发布修改的规则,客户端app订阅channel后将规则保存在本地内存中
            rulePublisher.publish(app, rules);
            logger.info("添加限流规则成功{}");
        } catch (Exception e) {
            e.printStackTrace();
            logger.warn("publishRules failed", e);
            return AsyncUtils.newFailedFuture(new CommandFailedException("Sentinel 推送规则到Redis失败>>>>>>>>>>>>>>>>>>>>>>>>"));
        }
        //核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中
        return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
    }
}

1.4中的 FlowControllerV1.java 具体修改为添加了两个redis相关的@Autowired。且修改了apiQueryMachineRules 和 publishRules 两个方法。具体代码看上述 FlowControllerV1.java 修改后的代码与源码中这几个地方的对比即可看出。

//-----------新增逻辑,服务于redis持久化---------------
    @Autowired
    @Qualifier("flowRuleRedisProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleRedisPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

2. 应用端服务的改造

本文单把应用端需要集成sentinel的相关代码单独放在一个jar包中,以后哪个项目需要对接口做限流只需要添加这个maven依赖即可。

jar包项目结构:

 具体代码:

CommonConstant.java

package com.jll.sentinel.common;

/**
 * @Description
 * @Author: jinglonglong
 * @Date:2021-5-10
 **/
public class CommonConstant {
    //英文格式逗号
    public static final String COMMA_EN = ",";
    //英文格式冒号
    public static final String COLON_EN = ":";


    //redis单点模式
    public static final String REDIS_SINGLETEN = "singleton";
    //redis哨兵模式
    public static final String REDIS_SENTINEL = "sentinel";

    //redis默认连接地址
    public static final String DEFAULT_REDIS_HOST_INFO = "127.0.0.1:6379";

    //流控规则key前缀
    public static final String RULE_FLOW = "sentinel_rule_flow_";
    //降级规则key前缀
    public static final String RULE_DEGRADE = "sentinel_rule_degrade_";
    //系统规则key前缀
    public static final String RULE_SYSTEM = "sentinel_rule_system_";
    //热点参数限流
    public static final String RULE_FLOW_PARAM = "sentinel_rule_param_";
    //授权规则限流
    public static final String RULE_AUTHORITY = "sentinel_rule_authority_";

    //限流规则channel
    public static final String RULE_FLOW_CHANNEL = "sentinel_rule_flow_channel";
    //降级规则channel
    public static final String RULE_DEGRADE_CHANNEL = "sentinel_rule_degrade_channel";
    //系统规则channel
    public static final String RULE_SYSTEM_CHANNEL = "sentinel_rule_system_channel";
    //热点参数规则channel
    public static final String RULE_FLOW_PARAM_CHANNEL = "sentinel_rule_param_channel";
    //授权规则channel
    public static final String RULE_AUTHORITY_CHANNEL = "sentinel_rule_authority_channel";
}

CommonConfig.java
package com.jll.sentinel.config;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description @SentinelResource 的切面bean注册到spring容器
 * @Author: jinglonglong
 * @Date:2021-5-10
 **/
@Configuration
public class CommonConfig {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

RedisConfig.java

package com.jll.sentinel.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;

/**
 * @Description
 * @Author: jinglonglong
 * @Date:2021-5-10
 **/
@Configuration
@PropertySources({
        @PropertySource("classpath:application.properties"),
        @PropertySource(value = "classpath:sentinel.properties", ignoreResourceNotFound = true)
})
public class RedisConfig {
    @Value("${jll.sentinel.redis.url}")
    private String hostInfo;
    @Value("${jll.sentinel.redis.masterid}")
    private String masterId;
    @Value("${jll.sentinel.redis.mode}")
    private String redisMode;

    public String getHostInfo() {
        return hostInfo;
    }

    public void setHostInfo(String hostInfo) {
        this.hostInfo = hostInfo;
    }

    public String getMasterId() {
        return masterId;
    }

    public void setMasterId(String masterId) {
        this.masterId = masterId;
    }

    public String getRedisMode() {
        return redisMode;
    }

    public void setRedisMode(String redisMode) {
        this.redisMode = redisMode;
    }
}

RedisConnection.java

package com.jll.sentinel.config;

import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig;
import com.jll.sentinel.common.CommonConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Description
 * @Author: jinglonglong
 * @Date:2021-5-10
 **/
@Component
public class RedisConnection {
    //redis配置对象
    private static RedisConfig redisConfig;
    //redis机器信息
    private static String hostInfo;
    //redis配置模式 singleten  or  sentinel
    private static String redisMode;
    //redis哨兵模式的masterid
    private static String masterId;

    //通过setting模式注入redis配置
    @Autowired
    public void setRedisConfig(RedisConfig redisConfig) {
        RedisConnection.redisConfig = redisConfig;
        hostInfo = redisConfig.getHostInfo();
        redisMode = redisConfig.getRedisMode();
        masterId = redisConfig.getMasterId();
    }

    public static RedisConnectionConfig getConfig() {
        /**
         * 判断是否有设置redis配置信息
         */
        if (hostInfo == null || hostInfo.length() == 0) {
            hostInfo = CommonConstant.DEFAULT_REDIS_HOST_INFO;
        }
        String[] hosts = hostInfo.split(CommonConstant.COMMA_EN);

        /**
         * 判断是获取单节点还是哨兵模式
         * 根据mode是否等于sentinel
         * singleten : 单点模式
         * sentinel : 哨兵模式
         * 暂不支持集群模式
         */
        RedisConnectionConfig config;
        if (redisMode != null && redisMode.trim().toLowerCase().equals(CommonConstant.REDIS_SENTINEL)) {
            config = getSentinel(hosts);
        } else {
            config = getSingleton(hostInfo.split(CommonConstant.COMMA_EN)[0].split(CommonConstant.COLON_EN)[0], Integer.valueOf(hostInfo.split(CommonConstant.COLON_EN)[1]));
        }

        return config;
    }

    /**
     * 获取单点模式redis连接
     *
     * @param host
     * @param port
     * @return
     */
    private static RedisConnectionConfig getSingleton(String host, Integer port) {
        return RedisConnectionConfig.builder().withHost(host).withPort(port).build();
    }

    /**
     * 获取哨兵模式redis连接
     *
     * @param hosts
     * @return
     */
    private static RedisConnectionConfig getSentinel(String[] hosts) {
        RedisConnectionConfig.Builder builder = RedisConnectionConfig.builder();
        for (String item : hosts) {
            String host = item.split(CommonConstant.COLON_EN)[0];
            Integer port = Integer.valueOf(item.split(CommonConstant.COLON_EN)[1]);
            builder.withRedisSentinel(host, port);
        }
        builder.withSentinelMasterId(masterId);

        return builder.build();
    }
}

RedisDataRuleLoadConfig.java

package com.jll.sentinel.config;

import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.redis.RedisDataSource;
import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.jll.sentinel.common.CommonConstant;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @Description 规则初始化
 * @Author: jinglonglong
 * @Date:2021-5-10
 **/
@Component
@Order(value = 1)
public class RedisDataRuleLoadConfig implements ApplicationRunner {
    private final org.slf4j.Logger logger = LoggerFactory.getLogger(RedisDataRuleLoadConfig.class);

    @Autowired
    private RedisConnection redisConnection;

    @Override
    public void run(ApplicationArguments args) {
        try {
            logger.info(">>>>>>>>>执行sentinel规则初始化 start。。。");

            RedisConnectionConfig config = redisConnection.getConfig();
            //流控规则
            Converter<String, List<FlowRule>> parserFlow = source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
            });
            ReadableDataSource<String, List<FlowRule>> redisDataSourceFlow = new RedisDataSource<>(config, CommonConstant.RULE_FLOW + SentinelConfig.getAppName(), CommonConstant.RULE_FLOW_CHANNEL, parserFlow);
            FlowRuleManager.register2Property(redisDataSourceFlow.getProperty());
            //降级规则
            Converter<String, List<DegradeRule>> parserDegrade = source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
            });
            ReadableDataSource<String, List<DegradeRule>> redisDataSourceDegrade = new RedisDataSource<>(config, CommonConstant.RULE_DEGRADE + SentinelConfig.getAppName(), CommonConstant.RULE_DEGRADE_CHANNEL, parserDegrade);
            DegradeRuleManager.register2Property(redisDataSourceDegrade.getProperty());
            //系统规则
            Converter<String, List<SystemRule>> parserSystem = source -> JSON.parseObject(source, new TypeReference<List<SystemRule>>() {
            });
            ReadableDataSource<String, List<SystemRule>> redisDataSourceSystem = new RedisDataSource<>(config, CommonConstant.RULE_SYSTEM + SentinelConfig.getAppName(), CommonConstant.RULE_SYSTEM_CHANNEL, parserSystem);
            SystemRuleManager.register2Property(redisDataSourceSystem.getProperty());
            //授权规则
            Converter<String, List<AuthorityRule>> parserAuthority = source -> JSON.parseObject(source, new TypeReference<List<AuthorityRule>>() {
            });
            ReadableDataSource<String, List<AuthorityRule>> redisDataSourceAuthority = new RedisDataSource<>(config, CommonConstant.RULE_AUTHORITY + SentinelConfig.getAppName(), CommonConstant.RULE_AUTHORITY_CHANNEL, parserAuthority);
            AuthorityRuleManager.register2Property(redisDataSourceAuthority.getProperty());

            logger.info(">>>>>>>>>执行sentinel规则初始化 end。。。");
        } catch (Exception e) {
            logger.error("应用初始化加载sentinel规则异常", e);
        }
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jll.sentinel</groupId>
    <artifactId>mine-jll-sentinel</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>mine-jll-sentinel</name>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <spring-version>4.3.13.RELEASE</spring-version>
        <spring-boot-version>1.5.9.RELEASE</spring-boot-version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
            <version>1.7.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.7.2.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>fastjson</artifactId>
                    <groupId>com.alibaba</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-annotation-aspectj</artifactId>
            <version>1.7.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-redis</artifactId>
            <version>1.7.2.1</version>
            <scope>compile</scope>
            <!--<exclusions>
                <exclusion>
                    <artifactId>netty-common</artifactId>
                    <groupId>io.netty</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>netty-handler</artifactId>
                    <groupId>io.netty</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>netty-transport</artifactId>
                    <groupId>io.netty</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>reactor-core</artifactId>
                    <groupId>io.projectreactor</groupId>
                </exclusion>
            </exclusions>-->
        </dependency>
        <!--自封装所需要的依赖-->
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-version}</version>
            <optional>true</optional>
        </dependency>
        <!-- spring end -->

        <!-- spring boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <version>${spring-boot-version}</version>
            <optional>true</optional>
        </dependency>
        <!-- spring boot end -->

        <!-- redis 相关 -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>reactor-core</artifactId>
                    <groupId>io.projectreactor</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- sentinel -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-extension</artifactId>
            <version>1.7.2.1</version>
        </dependency>
        <!-- sentinel end -->
        <!-- tools -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
        <!-- tools end -->
        <!-- log -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- log end -->
        <dependency>
            <artifactId>reactor-core</artifactId>
            <groupId>io.projectreactor</groupId>
            <version>3.1.2.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.0.1</version>
                <configuration>
                    <attach>true</attach>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3. 应用端服务添加配置

1.1 @SpringBootApplication(scanBasePackages = {"com.jll.sentinel"})

通过scanBasePackages扫描jar需要注册到spring容器的bean。

2.2 接口需要限流的依赖2中的jar包,添加以下sentinel属性配置文件:sentinel.properties

#应用监控名称。下面配置复制到配置文件
sentinel.app.name=MySampleServer
#sentinel日志配置
csp.sentinel.log.dir=D:/data/dubbo/logs/
csp.sentinel.log.use.pid=true
#日志输出到文件还是控制台 file表示文件,console表示控制台
csp.sentinel.log.output.type=console
#50M
csp.sentinel.metric.file.single.size=52428800
#日志文件个数
csp.sentinel.metric.file.total.count=100
#心跳配置,dashboard服务连接地址
csp.sentinel.dashboard.server=localhost:8080
#心跳包周期 单位毫秒
csp.sentinel.heartbeat.interval.ms=1000
##客户应用端服务接口
csp.sentinel.api.port=8083

# 服务限流配置
jll.sentinel.redis.url=10.4.11.128:6379
jll.sentinel.redis.mode=singleten
jll.sentinel.redis.masterid=qsmaster

jar包的maven依赖(2步骤中构建的jar包):

<!--sentinel 依赖-->
		<dependency>
			<groupId>com.jll.sentinel</groupId>
			<artifactId>mine-jll-sentinel</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>

3.3 接口限流注解

@SentinelResource(value = "/mySampleServer/t2", fallback = "fallbackHandle", fallbackClass = {TestFallback.class})
    @GetMapping("/t2")
    public String t2() {

        return "t2";
    }

回调方法:

public class TestFallback {
    public static String fallbackHandle() {
        return "服务降级测试-controller";
    }
}

回调方法规则:

测试-fallBack
1 返回类型必须与业务方法返回类型一致
2 参数与业务方法参数一致
3 必须是静态方法
4 返回方法注释最好与业务方法相关,方便以后维护。
5 返回码最好统一,如果出现服务降级的情况,可以定义一个统一的返回码及返回提示。
6 如果服务降级返回信息相同,则多个业务方法的fallback可以共用同一个fallback方法。

以上sentinel集成redis的流程参考了这一博文:Spring cloud 整合Sentinel 实现redis缓存限流规则(最新一)|Redis 持久化 SentinelCDCN-码上中国博客

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐