sentinel限流集成redis
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-码上中国博客
更多推荐
所有评论(0)