www.ydisp.cn/oss/202207/13/825218f522d76123b21815523af85b73a7a5c0.jpg"alt="picture"title="picture"style="visibility:visible;宽度:676.997px;height:673px;"data-type="inline">根据不同的接口逻辑,代理接口的实现逻辑会有所不同,具体场景如下。2、单条数据查询针对单条数据,以及source可以通过数据源来判断。基于灰度和回滚的原理,目标类和代理类的路由规则如下:首先判断主控开关,如果主控开关打开,则表示已经迁移完成,验证完成,这时候就去代理界面,这样就可以关闭界面和数据,达到我们迁移的目的,如果旧数据中不存在数据table,那么不管这个数据在新表中是否存在,我们可以直接使用代理接口来采集新数据。此时上课(这可以在rollin时完成gback),并使用原来的接口方法,即旧的逻辑,不会影响系统功能。如果数据存在于旧数据表中,但在灰度列表中,则说明这条数据已经迁移,需要验证。这时候就可以使用代理类(灰度可以做到)来实现新的接口逻辑。3、多条数据查询不同于单条数据查询。我们需要查询新表和旧表中所有符合条件的数据。多次数据查询涉及到数据重复(即数据会同时存在于旧表和新表中。),因此需要对数据进行去重,然后合并返回结果。4、数据更新由于数据迁移到系统灰度的过程中有一个中间时间,所以我们在更新数据的时候要通过双写来保持数据新鲜。旧表数据的一致性。同时,要关闭接口和数据,首先要判断总控开关是否打开。如果总开关打开,数据更新只需要更新新表即可。5.数据插入关闭接口,需要切换增量数据,所以直接使用代理类,将数据插入新表,控制旧表的数据增量,只需要考虑存量数据迁移数据时。实践如在零售场景中,每个店铺都有一个唯一标识的storeid,所以我们的灰度列表可以存储storeid的列表,通过storedimension进行灰度来细化影响范围。1.代理分发逻辑分发逻辑是核心逻辑。重复数据删除规则和接口/存储层代理转发都是基于这套逻辑来控制的:首先确定主开关,当主开关打开时,迁移完成。这时候,都是通过代理类来获取新的接口逻辑和数据源。判断灰度切换,如果灰度存储包含在灰度进程中,则通过代理类使用新的接口;否则,使用原界面的旧逻辑实现界面切换。新的数据转发给代理类,关闭新的逻辑和数据,防止产生增量数据。批量查询接口需要转发给代理类,因为涉及到新旧数据去重合并的过程。/***是否启用代理**@paramctxcontext*@return是:启用代理,否:禁用代理*/publicBooleanenableProxy(ProxyEnableContextctx){if(ctx==null){returnfalse;}//判断主开关if(主开关打开){//表示数据迁移完成,所有接口都切换了returntrue;}if(单店操作){if(有旧数据源){//判断是否在灰名单中,如果是,则返回true;否则返回假;}else{//新数据返回true;}}else{//批量查询,需要通过代理合并新旧数据源returntrue;}}2。接口代理接口代理主要是通过aspects拦截,通过注解方法来实现。代理注解如下@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public@interfaceEnableProxy{//用于标识代理类Class>proxyClass();//用于标识转发代理类的方法,默认取目标类的方法名StringmethodName()default"";//对于单条数据的查询,可以指定key的参数索引位置,解析后转发intkeyIndex()default-1;}切面的实现核心逻辑是拦截注解,并根据代理分发逻辑判断是否使用代理类。如果使用代理类,需要解析代理类型、方法名、参数,然后转发。@Component@Aspect@Slf4jpublicclassProxyAspect{//核心代理类@ResourceprivateProxyManagerproxyManager;//注解拦截@Pointcut("@annotation(***)")privatevoidproxy(){}@Around("proxy()")@SuppressWarnings("rawtypes")publicObjectaround(ProceedingJoinPointjoinPoint)throwsThrowable{尝试{MethodSignaturemethodSignature=(MethodSignature)joinPoint.getSignature();类>clazz=joinPoint.getTarget().getClass();StringmethodName=methodSignature.getMethod().getName();Class[]parameterTypes=methodSignature.getParameterTypes();对象[]args=joinPoint.getArgs();//获取方法注解EnableProxyenableProxyAnnotation=ReflectUtils.getMethodAnnotation(clazz,EnableProxy.class,methodName,parameterTypes);if(enableProxyAnnotation==null){//没有找到注解,直接放手returnjoinPoint.proc需要();}//判断是否使用代理BooleanenableProxy=enableProxy(clazz,methodName,args,enableProxyAnnotation);if(!enableProxy){//不开启代理,直接放开returnjoinPoint.proceed();}//默认取目标类的方法名。methodName=StringUtils.isNotBlank(enableProxyAnnotation.methodName())?());方法proxyMethod=ReflectUtils.getMethod(enableProxyAnnotation.proxyClass(),methodName,parameterTypes);if(bean==null||proxyMethod==null){//没有代理类和代理方法,直接走原逻辑returnjoinPoint.proceed();}//通过反射,转发代理类方法returnReflectUtils.invoke(bean,proxyMethod,joinPoint.getArgs());}赶上(BizExceptiononbizException){//业务方法异常,直接抛出throwbizException;}catch(Throwablethrowable){//其他异常,做日志感知throwthrowable;}}}3。然后将逻辑转发给ProxyManager,由代理类管理器负责数据分发、去重、合并、更新、插入等操作。切面拦截它,切面判断是否使用代理接口。如果不需要代理接口(即数据源是旧的,没有灰度化),继续使用目标接口。如果需要代理接口(即数据源是新旧数据迁移后灰度列表),调用代理接口方法,存储层逻辑会在代理接口方法中进一步转发,ProxyManage会统一关闭界面。在单条数据的查询逻辑中,只需要调用代理存储层服务查询新的数据源即可,逻辑比较简单。比如单个店铺的信息查询,我们核心控制器ProxyManager的方法逻辑可以这样实现:public>oldSupplier=()->targetRepository.queryList(ids);//2.查询新数据Supplier
>newSupplier=()->proxyRepository.queryList(ids);//3.根据合并规则进行合并,依赖合并工具(合并逻辑抽象后的工具类)returnProxyHelper.mergeWithSupplier(oldSupplier,newSupplier,idMapping);}合并工具类实现如下:publicclassProxyHelper{/***核心去重逻辑,判断是否使用新表数据**@paramexistOldData是否有旧数据*@paramexistNewData是否有新数据*@paramidStoreid*@return是否使用新表数据*/publicstaticbooleanuseNewData(BooleanexistOldData,BooleanexistNewData,Longid){if(!existOldData&&!existNewData){//两个表都不返回true;}elseif(!existNewData){//新表没有returnfalse;}elseif(!existOldData){//旧表没有returntrue;}else{//新表和旧表都有,判断开关和灰度开关返回主开关打开或在灰度列表中}}/***合并新/旧表数据**@paramoldSupplier旧表数据*@paramnewSupplier新表数据*@return合并去重数据*/publicstatic
>oldSupplier,Supplier
>newSupplier,Function
