流量隔离:Redis缓存隔离

Redis 缓存隔离方案

数据偏移 影子缓存(多实例) 影子缓存(单实例) 影子key
优点 实现简单,无需改造中间件 1.物理隔离;
2.对业务代码无侵入性;
3.安全性高,对生产缓存无任何影响;
4.扩展性高;
5.数据清理简单
1.逻辑隔离;
2.对业务代码无侵入性;
3.安安全性一般,对生产缓存有一定影响;
4.扩展性强;
5.数据清理简单
1.逻辑隔离;
2.中间件容器支持;
3.安全性一般,对生产缓存有一定影响;
4.扩展性较强;
5.数据清理较简单
缺点 1.数据进入正式缓存;
2.对业务数据侵入性较大,会影响缓存数据设计;
3.数据清理复杂,需要根据每个key的标记单独制定清理规则;
4.扩展性差,新的key需要设计新的标记;
5.安全性能较差,设计逻辑有纰漏会影响生产缓存;
6.压力大的情况下,会挤占数据库的资源
数据库资源压力双倍;需要切换数据库连接,中间件改造有成本 压力大的情况下,会挤压数据库的资源 压力大的情况下,会挤压数据库的资源
使用场景 1.流量标记没打通;
2.技术体系不完整,改造成本和复杂度过高
1.全服务流量标记;
2.资源充裕;
3.中间件技术体系完整
1.全服务流量标记;
2.中间件技术体系完整
1.全服务流量标记;
2.中间件技术体系完整

影子缓存(多实例)

配置yml文件

spring:
  application:
    name: redis-isolate
  sleuth:
    sampler:
      probability: 1.0
      rate: 10000
    web:
      enabled: true
  zipkin:
    base-url: http://localhost:9411

  redis:
    host: localhost
    port: 6379
    database: 1
    timeout: 6000
    password:
    pool:
      max-active: 8
      max-idle: 8
      min-idle: 0
      max-wait: -1
  redis2:
    host: localhost
    port: 6378
    database: 2
    password:
    timeout: 6000
    pool:
      max-active: 8
      max-idle: 8
      min-idle: 0
      max-wait: -1

logging:
  level:
    root: debug

添加配置类:

package com.edu.link.redis.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.JedisPoolConfig;

@EnableCaching
@Configuration
public class RedisConfiguration {
    @Primary
    @Bean(name = "slaveDataSource")
    public StringRedisTemplate slaveDataSource(@Value("${spring.redis2.host}") String hostName, @Value("${spring.redis2.port}") int port,
                                               @Value("${spring.redis2.password}") String password, @Value("${spring.redis2.pool.max-idle}") int maxIdle,
                                               @Value("${spring.redis2.pool.max-active}") int maxActive, @Value("${spring.redis2.pool.max-wait}") int maxWait,
                                               @Value("${spring.redis2.pool.min-idle}") int minIdle, @Value("${spring.redis2.database}") int index) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(connectionFactory(hostName, port, password, maxIdle, maxActive, index, maxWait, minIdle));
        return template;
    }

    @Primary
    @Bean(name = "masterDataSource")
    public StringRedisTemplate masterDataSource(@Value("${spring.redis.host}") String hostName, @Value("${spring.redis.port}") int port,
                                                @Value("${spring.redis.password}") String password, @Value("${spring.redis.pool.max-idle}") int maxIdle,
                                                @Value("${spring.redis.pool.max-active}") int maxActive, @Value("${spring.redis.pool.max-wait}") int maxWait,
                                                @Value("${spring.redis.pool.min-idle}") int minIdle, @Value("${spring.redis.database}") int index) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(connectionFactory(hostName, port, password, maxIdle, maxActive, index, maxWait, minIdle));
        return template;
    }

    public RedisConnectionFactory connectionFactory(String hostName, int port, String password, int maxIdle, int maxTotal, int index, long maxWait, int minIdle) {
        JedisConnectionFactory jedis = new JedisConnectionFactory();
        jedis.setHostName(hostName);
        jedis.setPort(port);
        if (null != password) {
            jedis.setPassword(password);
        }
        if (0 != index) {
            jedis.setDatabase(index);
        }

        jedis.setPoolConfig(poolConfig(maxIdle, maxTotal, maxWait, minIdle));
        jedis.afterPropertiesSet();
        RedisConnectionFactory factory = jedis;
        return factory;
    }

    private JedisPoolConfig poolConfig(int maxIdle, int maxTotal, long maxWait, int minIdle) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxWaitMillis(maxWait);
        poolConfig.setMinIdle(minIdle);
        return poolConfig;
    }
}

测试一下:

package com.edu.link.redis.controller;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/7d")
public class DemoController {
    @Resource(name = "slaveDataSource")
    public StringRedisTemplate slaveTemplate;
    @Resource(name = "masterDataSource")
    public StringRedisTemplate masterTemplate;
    @GetMapping("/get")
    public void get(@RequestParam("id")Integer id){
        slaveTemplate.opsForValue().set("one",System.currentTimeMillis()+" slave db");
        System.out.println(slaveTemplate.opsForValue().get("one"));
        System.out.println("=============================");
        masterTemplate.opsForValue().set("one",System.currentTimeMillis()+" master db");
        System.out.println(masterTemplate.opsForValue().get("one"));
    }
}

数据源上下文实现

添加yml配置文件

server:
  port: 8000
spring:
  application:
    name: dunshan-order
  sleuth:
    sampler:
      probability: 1.0
      rate: 10000
    propagation-keys:
      - dunshan
    web:
      enabled: true
  zipkin:
    base-url: http://localhost:9411
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
  redis:
    database: 1
    host: localhost
    port: 6379
    timeout: 6000ms
    password:
    jedis:
      pool:
        max-active: 1000
        max-wait: -1ms
        max-idle: 10
        min-idle: 5
  cache:
    type: nonde
  resar:
    database: 5
logging:
  leve:
    root: info

Redis配置文件类:

package com.edu.dunshan.order.config;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
    private RedisProperties redisProperties;

    public RedisConfig(RedisProperties redisProperties) {
        this.redisProperties = redisProperties;
    }

    @Bean
    @Primary
    public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(redisProperties.getHost());
        config.setPort(redisProperties.getPort());
        config.setPassword(redisProperties.getPassword());
        config.setDatabase(redisProperties.getDatabase());
        return new JedisConnectionFactory(config, getJedisClientConfiguration());
    }

    private JedisClientConfiguration getJedisClientConfiguration() {
        JedisClientConfiguration.JedisClientConfigurationBuilder builder = JedisClientConfiguration.builder();
        if (redisProperties.isSsl()) {
            builder.useSsl();
        }
        if (null != redisProperties.getTimeout()) {
            Duration timeout = redisProperties.getTimeout();
            builder.readTimeout(timeout).connectTimeout(timeout);
        }
        RedisProperties.Pool pool = redisProperties.getJedis().getPool();
        if (null != pool) {
            builder.usePooling().poolConfig(jedisPoolConfig(pool));
        }
        return builder.build();
    }

    private GenericObjectPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(pool.getMaxActive());
        config.setMaxIdle(pool.getMaxIdle());
        config.setMinIdle(pool.getMinIdle());
        if (null != pool.getMaxWait()) {
            config.setMaxWaitMillis(pool.getMaxWait().toMillis());
        }
        return config;
    }

    @Bean(name = "redisTemplate")
    @Primary
    public SelectableRedisTemplate redisTemplate() {
        SelectableRedisTemplate redisTemplate = new SelectableRedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        return redisTemplate;
    }
}

创建数据源上下文:

package com.edu.dunshan.order.config;

import com.alibaba.ttl.TransmittableThreadLocal;

public class RedisSelectSupport {
    private static final TransmittableThreadLocal SELECT_CONTEXT = new TransmittableThreadLocal<>();
    public static void select(int db){
        SELECT_CONTEXT.set(db);
    }

    public static Integer getSelect(){
        return SELECT_CONTEXT.get();
    }
}

StringRedisTemplate继承类:

package com.edu.dunshan.order.config;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;

public class SelectableRedisTemplate extends StringRedisTemplate {
    @Override
    protected RedisConnection createRedisConnectionProxy(RedisConnection pm) {
        return super.createRedisConnectionProxy(pm);
    }

    @Override
    protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
        Integer db;
        if (null != (db = RedisSelectSupport.getSelect())){
            connection.select(db);
        }
        return super.preProcessConnection(connection,existingConnection);
    }
}

aop拦截类:

package com.edu.dunshan.order.aspect;

import com.edu.dunshan.order.config.AppContext;
import com.edu.dunshan.order.config.RedisSelectSupport;
import com.edu.dunshan.order.config.SelectableRedisTemplate;
import com.edu.dunshan.order.constant.DataSourceNames;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Slf4j
public class RedisDbAspect {
    @Value("${spring.redis.database}")
    public int defaultDatabase;
    @Value("${spring.resar.database}")
    public int slaveDatabase;

    @Pointcut("execution (public * com.edu.dunshan.order.controller..*.*(..))")
    public void pointCutAround() {

    }

    @Around(value = "pointCutAround()")
    @ConditionalOnBean(SelectableRedisTemplate.class)
    public Object configRedis(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        AppContext context = AppContext.getContext();
        String flag = context.getFlag();
        int db = defaultDatabase;
        try {
            if (null != flag && flag.equals(DataSourceNames.HEAD)) {
                db = slaveDatabase;
                log.info("test data:" + db);
            } else {
                db = defaultDatabase;
                log.info("data:" + db);
            }
            RedisSelectSupport.select(db);
            return proceedingJoinPoint.proceed();
        } finally {
            RedisSelectSupport.select(db);
            log.debug("redis switch {} to {}",defaultDatabase,db);
        }
    }
}

context 类:


import com.alibaba.ttl.TransmittableThreadLocal;

import java.io.Serializable;

public class AppContext  implements Serializable {
    private static final TransmittableThreadLocal CONTEXT_DUN_SHAN = new TransmittableThreadLocal<>();
    private String flag;

    public static AppContext getContext(){
        return CONTEXT_DUN_SHAN.get();
    }

    public static void setContext(AppContext appContext){
        CONTEXT_DUN_SHAN.set(appContext);
    }

    public static void removeContext(){
        CONTEXT_DUN_SHAN.remove();
    }

    public String getFlag() {
        return flag;
    }

    public void setFlag(String flag) {
        this.flag = flag;
    }
}

filter类:

package com.edu.dunshan.order.filter;

import brave.baggage.BaggageField;
import com.edu.dunshan.order.config.AppContext;
import com.edu.dunshan.order.constant.DataSourceNames;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

@Component
@Slf4j
public class ContextFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String value = BaggageField.getByName(DataSourceNames.DUNSHAN).getValue();
        AppContext appContext = new AppContext();
        log.info("contextfilter {}", value);
        if (StringUtils.isNotEmpty(value)) {
            appContext.setFlag(value);
        } else {
            appContext.setFlag("");
        }
        AppContext.setContext(appContext);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        AppContext.removeContext();
        Filter.super.destroy();
    }
}

参考文献

全链路压测实战30讲

你可能感兴趣的:(流量隔离:Redis缓存隔离)