阿里sentinel监控使用redis持久化规则

规则配置

一、从 Sentinel 1.4.0 开始,sentinel抽取出了接口用于向远程配置中心推送规则以及拉取规则:

  • DynamicRuleProvider<T>: 拉取规则
  • DynamicRulePublisher<T>: 推送规则

    用户只需实现 DynamicRuleProvider 和 DynamicRulePublisher 接口,即可实现应用维度推送

改造前原理分析:

       客户端利用sentinel-transport-simple-http模块暴露一个特定的端口,sentinel-dashboard项目通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中,如下图

改造后原理解析:

     sentinel-dashboard项目控制台将配置信息推送到配置中心,如nacos、zookeeper、apollo中,由配置中心去进行配置推送,更新配置到客户端本地内存中,如下图

二、官方提供了 Nacos、Apollo 、zookeeper的推送和拉取规则实现示例(位于 test 目录下)

三、官方没有给出redis持久化规则demo,下面我们自己改造

       注意:由于redis没有控制台,所以当客户端挂掉重启后,内存中的限流、降级等配置信息丢失,sentinel-dashboard项目需要通过按钮,定时任务等重新推送配置信息给客户端,或者客户端自己拉取规则。

1、sentinel-dashboard项目引入redis依赖

<!-- 集成redis -->
		<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter-data-redis</artifactId>
		    <version>2.0.5.RELEASE</version>
		</dependency>

2、改造controller

核心代码:

    @Autowired
    @Qualifier("flowRuleRedisProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleRedisPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

完整代码如下:

package com.alibaba.csp.sentinel.dashboard.controller;

import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.AuthUser;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
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.util.StringUtil;
import com.alibaba.fastjson.JSON;

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

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

    @Autowired
    private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

    @Autowired
    @Qualifier("flowRuleRedisProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleRedisPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

    @Autowired
    private AuthService<HttpServletRequest> authService;
    @Autowired
    private SentinelApiClient sentinelApiClient;

    @GetMapping("/rules")
    public Result<List<FlowRuleEntity>> apiQueryMachineRules(HttpServletRequest request, @RequestParam String app) {
        AuthUser authUser = authService.getAuthUser(request);
        authUser.authTarget(app, PrivilegeType.READ_RULE);

        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        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);
        }
    }

    private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
        if (entity == null) {
            return Result.ofFail(-1, "invalid body");
        }
        if (StringUtil.isBlank(entity.getApp())) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        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")
    public Result<FlowRuleEntity> apiAddFlowRule(HttpServletRequest request, @RequestBody FlowRuleEntity entity) {
        AuthUser authUser = authService.getAuthUser(request);
        authUser.authTarget(entity.getApp(), PrivilegeType.WRITE_RULE);

        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);
        } catch (Throwable throwable) {
            logger.error("Failed to add flow rule", throwable);
            return Result.ofThrowable(-1, throwable);
        }
        if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
            logger.error("Publish flow rules failed after rule add");
        }
        return Result.ofSuccess(entity);
    }

    @PutMapping("/save.json")
    public Result<FlowRuleEntity> updateIfNotNull(HttpServletRequest request, Long id, String app,
                                                  String limitApp, String resource, Integer grade,
                                                  Double count, Integer strategy, String refResource,
                                                  Integer controlBehavior, Integer warmUpPeriodSec,
                                                  Integer maxQueueingTimeMs) {
        AuthUser authUser = authService.getAuthUser(request);
        authUser.authTarget(app, PrivilegeType.WRITE_RULE);

        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");
            }
        } catch (Throwable throwable) {
            logger.error("save error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }
        if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
            logger.info("publish flow rules fail after rule update");
        }
        return Result.ofSuccess(entity);
    }

    @DeleteMapping("/delete.json")
    public Result<Long> delete(HttpServletRequest request, Long id) {
        AuthUser authUser = authService.getAuthUser(request);
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }
        FlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }
        authUser.authTarget(oldEntity.getApp(), PrivilegeType.DELETE_RULE);
        try {
            repository.delete(id);
        } catch (Exception e) {
            return Result.ofFail(-1, e.getMessage());
        }
        if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
            logger.info("publish flow rules fail after rule delete");
        }
        return Result.ofSuccess(id);
    }

    private boolean publishRules(String app, String ip, Integer port) {
        List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
        try {
        	rulePublisher.publish(app, rules);
        	logger.info("添加限流规则成功{}",JSON.toJSONString(rules));
		} catch (Exception e) {
			e.printStackTrace();
			logger.warn("publishRules failed", e);
            return false;
		}
        //核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中
        return sentinelApiClient.setFlowRuleOfMachine(app, ip, port, rules);
    }

}

3、改造接口实现

完整代码如下:

FlowRuleRedisProvider代码

package com.alibaba.csp.sentinel.dashboard.rule.redis;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSONObject;

@Component("flowRuleRedisProvider")
public class FlowRuleRedisProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {

    @Autowired
    private RedisUtil redisConfigUtil;

    @Override
    public List<FlowRuleEntity> getRules(String appName) throws Exception {
        String rules = redisConfigUtil.getString(RuleConsts.RULE_FLOW + appName);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return JSONObject.parseArray(rules,FlowRuleEntity.class);
    }
}

FlowRuleRedisPublisher代码

package com.alibaba.csp.sentinel.dashboard.rule.redis;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.fastjson.JSON;

@Component("flowRuleRedisPublisher")
public class FlowRuleRedisPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
	
	@Autowired
	private RedisUtil redisConfigUtil;

    @Override
    public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
    	AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return;
        }
        String strs = JSON.toJSONString(rules);
        redisConfigUtil.setString(RuleConsts.RULE_FLOW + app, strs);
    }
}

RedisUtil代码

package com.alibaba.csp.sentinel.dashboard.rule.redis;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisUtil {
	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	public StringRedisTemplate getStringRedisTemplate() {
		return stringRedisTemplate;
	}

	/**
	 * 存放string类型
	 * @param key
	 * @param data
	 * @param timeout
	 */
	public void setString(String key, String data, Long timeout) {
		try {
			stringRedisTemplate.opsForValue().set(key, data);
			if (timeout != null) {
				stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
			}

		} catch (Exception e) {

		}

	}

	/**
	 * 存放string类型
	 * @param key
	 * @param data
	 */
	public void setString(String key, String data) {
		setString(key, data, null);
	}

	/**
	 * 根据key查询string类型
	 * @param key
	 * @return
	 */
	public String getString(String key) {
		String value = stringRedisTemplate.opsForValue().get(key);
		return value;
	}

	/**
	 * 根据对应的key删除key
	 * @param key
	 */
	public Boolean delKey(String key) {
		return stringRedisTemplate.delete(key);
	}
}

RuleConsts代码

package com.alibaba.csp.sentinel.dashboard.rule.redis;

public interface RuleConsts {
	//流控规则key前缀
	public final String RULE_FLOW = "sentinel_rule_flow_";
	//限流规则key前缀
	public final String RULE_DEGRADE = "sentinel_rule_degrade_";
	//系统规则key前缀
	public final String RULE_SYSTEM = "sentinel_rule_system_";
}

4、以上步骤完成后,只是sentinel-dashboard监控服务端改造完成,下面改造sentinel-client被监控的客户端;由于客户端重启项目之后内存中的限流、降级等规则丢失,所以项目启动的时候需要重新加载配置规则到内存中,增加配置类,代码如下:

package com.it.sentinel.demo.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

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.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 lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class RedisDataSourceConfig  implements ApplicationRunner{
	@Value("${spring.redis.host}")
    public String redisHost;
	
	@Value("${spring.redis.port}")
	public int redisPort;
	
	//限流规则key前缀
	public final String RULE_FLOW = "sentinel_rule_flow_";
	public final String RULE_FLOW_CHANNEL = "sentinel_rule_flow_channel";
	//降级规则key前缀
	public final String RULE_DEGRADE = "sentinel_rule_degrade_";
	public final String RULE_DEGRADE_CHANNEL = "sentinel_rule_degrade_channel";
	//系统规则key前缀
	public final String RULE_SYSTEM = "sentinel_rule_system_";
	public final String RULE_SYSTEM_CHANNEL = "sentinel_rule_system_channel";
	
	/**
	 * ApplicationRunner
	 * 该接口的方法会在服务启动之后被立即执行
	 * 主要用来做一些初始化的工作
	 * 但是该方法的运行是在SpringApplication.run(…​) 执行完毕之前执行
	 */
	@Override
	public void run(ApplicationArguments args) throws Exception {
		log.info(">>>>>>>>>执行sentinel规则初始化");
		RedisConnectionConfig config = RedisConnectionConfig.builder().withHost(redisHost).withPort(redisPort).build();
		
	    Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
	    ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<>(config, RULE_FLOW+SentinelConfig.getAppName(), RULE_FLOW_CHANNEL, parser);
	    FlowRuleManager.register2Property(redisDataSource.getProperty());
	    
	    Converter<String, List<DegradeRule>> parser1 = source -> JSON.parseObject(source,new TypeReference<List<DegradeRule>>() {});
	    ReadableDataSource<String, List<DegradeRule>> redisDataSource1 = new RedisDataSource<>(config, RULE_DEGRADE+SentinelConfig.getAppName(), RULE_DEGRADE_CHANNEL, parser1);
	    DegradeRuleManager.register2Property(redisDataSource1.getProperty());
	   
	    Converter<String, List<SystemRule>> parser2 = source -> JSON.parseObject(source,new TypeReference<List<SystemRule>>() {});
	    ReadableDataSource<String, List<SystemRule>> redisDataSource2 = new RedisDataSource<>(config, RULE_SYSTEM+SentinelConfig.getAppName(), RULE_SYSTEM_CHANNEL, parser2);
	    SystemRuleManager.register2Property(redisDataSource2.getProperty());
	}
	
}

5、使用redis持久化sentinel配置规则,改造完成。

 

 

 

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值