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讲