短视频app制作,通过数据隔离实现全链路压测
一、JDBC数据源隔离
在短视频app制作中,数据隔离需要对DB,Redis,RabbitMQ进行数据隔离
通过实现Spring动态数据源AbstractRoutingDataSource,通过ThreadLocal识别出来压测数据,如果是压测数据就路由到影子库,如果是正常流量则路由到主库,通过流量识别的改造,各个服务都已经能够识别出压测的请求流量了。
代码实现
数据源路由Key持有对象
根据路由Key将选择将操作路由给那个数据源
/** * 动态数据源上下文 */ public class DynamicDataSourceContextHolder { public static final String PRIMARY_DB = "primary"; public static final String SHADOW_DB = "shadow"; private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() { /** * 将 master 数据源的 key作为默认数据源的 key */ @Override protected String initialValue() { return PRIMARY_DB; } }; /** * 数据源的 key集合,用于切换时判断数据源是否存在 */ public static List<Object> dataSourceKeys = new ArrayList<>(); /** * 切换数据源 * * @param key */ public static void setDataSourceKey(String key) { contextHolder.set(key); } /** * 获取数据源 * * @return */ public static String getDataSourceKey() { return contextHolder.get(); } /** * 重置数据源 */ public static void clearDataSourceKey() { contextHolder.remove(); } /** * 判断是否包含数据源 * * @param key 数据源key * @return */ public static boolean containDataSourceKey(String key) { return dataSourceKeys.contains(key); } /** * 添加数据源keys * * @param keys * @return */ public static boolean addDataSourceKeys(Collection<? extends Object> keys) { return dataSourceKeys.addAll(keys); } }
动态数据源实现类
根据路由Key实现数据源的切换
/** * 动态数据源实现类 */ public class DynamicDataSource extends AbstractRoutingDataSource { /** * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源 * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可 */ @Override protected DataSource determineTargetDataSource() { //获取当前的上下文 WormholeContext wormholeContext = WormholeContextHolder.getContext(); //如果不为空使用影子库 if (null != wormholeContext) { DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.SHADOW_DB); } else { //为空则使用主数据源 DynamicDataSourceContextHolder.setDataSourceKey(DynamicDataSourceContextHolder.PRIMARY_DB); } return super.determineTargetDataSource(); } /** * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法 */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceKey(); } }
二、Redis 数据源隔离
同时通过ThreadLocal识别出来压测数据,自定义Redis的主键的序列化方式,如果是压测数据则在主键后面加上后缀,这样就可以通过不同主键将Redis数据进行隔离。
实现key序列化
public class KeyStringRedisSerializer extends StringRedisSerializer { @Resource private WormholeIsolationConfiguration isolationConfiguration; public byte[] serialize(@Nullable String redisKey) { WormholeContext wormholeContext = WormholeContextHolder.getContext(); if (null != wormholeContext) { redisKey = isolationConfiguration.generateIsolationKey(redisKey); } return super.serialize(redisKey); } }
配置序列化器
/** * Redis 配置类 */ @Configuration @ConditionalOnClass({RedisTemplate.class, RedisOperations.class, RedisConnectionFactory.class}) public class WormholeRedisAutoConfiguration { @Bean public KeyStringRedisSerializer keyStringRedisSerializer() { return new KeyStringRedisSerializer(); } @Bean("redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate(); //使用fastjson序列化 FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class); // value值的序列化采用fastJsonRedisSerializer template.setValueSerializer(fastJsonRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer); // key的序列化采用StringRedisSerializer template.setKeySerializer(keyStringRedisSerializer()); template.setHashKeySerializer(keyStringRedisSerializer()); template.setConnectionFactory(factory); return template; } @Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setKeySerializer(keyStringRedisSerializer()); template.setHashKeySerializer(keyStringRedisSerializer()); template.setConnectionFactory(factory); return template; } }
三、RabbitMQ 数据隔离
自动创建影子队列
因为SpringAMQP中的
中的关键方法是私有的,无法通过继承的方式进行实现对以配置好的队列进行扩展,所以需要自定义该类,来实现对自动创建影子队列,并和交换器进行绑定
代码实现
改造RabbitListenerAnnotationBeanPostProcessor类来实现创建MQ影子队列以及将影子Key绑定到影子队列。
public class WormholeRabbitListenerAnnotationBeanPostProcessor extends RabbitListenerAnnotationBeanPostProcessor { @Resource private WormholeIsolationConfiguration wormholeIsolationConfiguration; /** * routingKey 前置处理器 * * @param queueName * @param routingKey * @return */ @Override public String preProcessingRoutingKey(String queueName, String routingKey) { //如果是影子队列就将routingKey转换为 影子routingKey if (wormholeIsolationConfiguration.checkIsolation(queueName) && !wormholeIsolationConfiguration.checkIsolation(routingKey)) { return wormholeIsolationConfiguration.generateIsolationKey(routingKey); } return routingKey; } /** * 处理队列问题,如果来了一个队列就生成一个shadow的队列 * * @param queues * @return */ @Override public List<String> handelQueues(List<String> queues) { List<String> isolationQueues = new ArrayList<>(); if (null != queues && !queues.isEmpty()) { for (String queue : queues) { //添加shadow队列 isolationQueues.add(wormholeIsolationConfiguration.generateIsolationKey(queue)); } queues.addAll(isolationQueues); } return queues; } }
传递染色标识
因为在短视频app制作中MQ是异步通讯,为了传递染色标识,会在发送MQ的时候将染色标识传递过来,MQ接收到之后放进当前线程的ThreadLocal里面,这个需要扩展Spring的SimpleRabbitListenerContainerFactory来实现
代码实现
public class WormholeSimpleRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory { @Override protected SimpleMessageListenerContainer createContainerInstance() { SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(); simpleMessageListenerContainer.setAfterReceivePostProcessors(message -> { //防止线程复用 销毁ThreadLocal WormholeContextHolder.invalidContext(); //获取消息属性标识 String wormholeRequestContext = message.getMessageProperties().getHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK); if (StringUtils.isNotEmpty(wormholeRequestContext)) { WormholeContextHolder.setContext(wormholeRequestContext); } return message; }); return simpleMessageListenerContainer; } }
发送MQ消息处理
同上,需要传递染色标识,就通过继承RabbitTemplate重写convertAndSend方法来实现传递染色标识。
public class ShadowRabbitTemplate extends RabbitTemplate { public ShadowRabbitTemplate(ConnectionFactory connectionFactory) { super(connectionFactory); } @Autowired private WormholeIsolationConfiguration isolationConfiguration; @Override public void send(final String exchange, final String routingKey, final Message message, @Nullable final CorrelationData correlationData) throws AmqpException { WormholeContext wormholeContext = WormholeContextHolder.getContext(); if (null == wormholeContext) { super.send(exchange, routingKey, message, correlationData); } else { message.getMessageProperties().setHeader(WormholeContextHolder.WORMHOLE_REQUEST_MARK, wormholeContext.toString()); //生成Rabbit 隔离Key String wormholeRoutingKey = isolationConfiguration.generateIsolationKey(routingKey); //调用父类进行发送 super.send(exchange, wormholeRoutingKey, message, correlationData); } } }
以上就是短视频app制作,实现数据隔离可选方案有很多, 更多内容欢迎关注之后的文章