蓝绿和数据隔离

K8S支持蓝绿

Ingress-nginx的canary-*注解

配置

nginx.ingress.kubernetes.io/canary: 'true'
nginx.ingress.kubernetes.io/canary-by-header: version
nginx.ingress.kubernetes.io/canary-by-header-value: blue

注意

  • 配置中,除了string是支持的类型,不需要加引号,boolean值需要加引号
  • 要使配置生效,Nginx中必须要有不带canary配置或者 带有canary但是canary-by-header-value不一样的的另一套环境配置,单独的使用是无效的。

访问的时候只要在header中加上version = blue 。就会被Nginx代理到blue环境。

此方案的优点是可以在k8s中将所有路由配置在一个yaml中,然后可以直接切全套环境的蓝绿。

参考

Web获取蓝绿标签

HandlerInterceptor+ThreadLocal

提供2种实现方式:从cookie中获取蓝绿和从header中获取蓝绿。

从cookie中获取蓝绿


@Slf4j
public class RequestCookieHandler implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Cookie[] cookies = request.getCookies();
        if(null == cookies) {
            return true;
        }
        for (Cookie cookie : cookies) {
            String name = cookie.getName();
            if(BLUE_HEADER_KEY.equals(name)) {
                RequestHeaderThreadLocal.put(cookie.getValue());
                log.info("请求cookie的蓝绿属性值: {}", cookie.getValue());
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestHeaderThreadLocal.remove();
    }


}

从header中获取蓝绿


@Slf4j
public class RequestHeaderHandler implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String header = request.getHeader(BLUE_HEADER_KEY);
        RequestHeaderThreadLocal.put(header);
        log.info("请求header的蓝绿属性值: {}", header);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        RequestHeaderThreadLocal.remove();
    }
}

自动装配


@Slf4j
@Configuration
public class RequestAutoConfigure {

    @Value("${bg.type:header}")
    private String bgType;

    @Bean
    public HandlerInterceptor requestBgHandler() {
        log.info("启动自动装配获取requestHandlerType = {}", bgType);
        if(bgType.equals("cookie")) {
            return requestCookieHandler();
        }
        if(bgType.equals("header")) {
            return requestHeaderHandler();
        }
        throw new RuntimeException("未知bgHandler");
    }

    @Bean
    public RequestCookieHandler requestCookieHandler() {
        return new RequestCookieHandler();
    }

    @Bean
    public RequestHeaderHandler requestHeaderHandler() {
        return new RequestHeaderHandler();
    }

}

Feign下沉

如果将threadLocal中的蓝绿下沉到feign服务层中?

feign.RequestInterceptor

@Slf4j
public class FeignHeaderInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header(RequestHeaderThreadLocal.BLUE_HEADER_KEY, RequestHeaderThreadLocal.get());
        log.info("feign 调用增加header_version信息 : {}", RequestHeaderThreadLocal.get());
    }
}

通过实现RequestInterceptor 来自定义请求的header信息,将蓝绿参数传入feign调用的request.header中。

自动装配


@Configuration
@Slf4j
public class FeignRequestAutoConfigure {

    /*
       自动装配feign.requestInterceptor
    */
    @Bean(name = "feignHeaderInterceptor")
    public RequestInterceptor requestInterceptor() {
        log.info("自动装配feign调用请求头增加蓝绿拦截器");
        return new FeignHeaderInterceptor();
    }

}

Feign服务层使用上述的 <从header中获取蓝绿>的办法,获取到蓝绿标签。

数据隔离

org.apache.ibatis.plugin.Interceptor

要实现蓝绿数据隔离,在不影响业务逻辑的前提下,很自然的想到的就是mybatis-plugin。通过自定义plugin来完成数据的分离。

在前面说明中,我们在feign层中已经获取到了当前线程的蓝绿标签。后续拓展的思路是:

在需要支持蓝绿数据分离的表中增加字段bg,同时实体类型也需要加属性bg。

然后通过mybatis的拦截器去拦截当前实体对象是否有bg属性,如果有就获取当前线程的蓝绿标识根据sql的类型重新组装sql。

Insert语句处理


@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
@Slf4j
public class InsertInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
        SqlCommandType commandType = mappedStatement.getSqlCommandType();
        if(!SqlCommandType.INSERT.equals(commandType)) {
            return invocation.proceed();
        }
        Object param = invocation.getArgs()[1];

        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        boolean hasBg = InterceptorUtil.hasBgField(className);
        if(hasBg) {
            BeanUtils.setProperty(param, BG_FIELD, RequestHeaderThreadLocal.getBG());
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

}

此处贴出hasField代码:


public class InterceptorUtil {

    public static final String BG_FIELD = "bg";

    public static boolean hasBgField(String className) throws ClassNotFoundException {
        Class<?> classObj = Class.forName(className);
        Class baseEntity = null;
        for (Type type : classObj.getGenericInterfaces()) {
            if(type instanceof ParameterizedType) {
                ParameterizedType inter = (ParameterizedType) type;
                Type t = inter.getActualTypeArguments()[0];
                baseEntity = (Class) t;
            }
        }
        Field[] fields = baseEntity.getDeclaredFields();
        for (Field field : fields) {
            if(field.getName().equals(BG_FIELD)) {
                return true;
            }
        }
        return false;
    }

}

非insert语句处理


@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
public class NoInsertInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        StatementHandler realTarget = realTarget(target);
        MetaObject metaObject = SystemMetaObject.forObject(realTarget);
        processByField(realTarget, metaObject);
        return invocation.proceed();
    }

    private void processByField(StatementHandler handler, MetaObject metaObject) throws ClassNotFoundException {
        MappedStatement mappedStatement = (MappedStatement) metaObject
                .getValue("delegate.mappedStatement");
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if(SqlCommandType.INSERT.equals(sqlCommandType)) {
            return;
        }
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        boolean hasBg = InterceptorUtil.hasBgField(className);
        if(hasBg) {
            resetSql(handler, metaObject);
        }
    }

    private void resetSql(StatementHandler handler, MetaObject metaObject) {
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql().toLowerCase();
        if(sql.contains(" where")) {
            sql += " and `bg` = '" + RequestHeaderThreadLocal.getBG() + "'";
        } else {
            sql += " where `bg` = '" + RequestHeaderThreadLocal.getBG() + "'";
        }
        metaObject.setValue("delegate.boundSql.sql", sql);
    }

    public static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

自动装配


@Configuration
@Slf4j
@ConditionalOnBean(SqlSessionFactory.class)
public class MybatisConfig {

    @Resource
    private SqlSessionFactory sqlSessionFactory;
    @Bean
    public void addInterceptor() {
        log.info("mybatis 初始化添加bgInterceptor拦截器");
        org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
        configuration.addInterceptor(new NoInsertInterceptor());
        configuration.addInterceptor(new InsertInterceptor());
    }
}

到此,从蓝绿请求的分发到数据隔离就完成了。

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值