目录

kd-微服务简介

功能介绍

微服务是一种开发软件的架构和组织方法。在苍穹中,通过明确定义的API 将平台业务依据应用进行微服务拆分。微服务调用kd.bos.servicehelper.DispatchServiceHelper为微服务上层封装,起到简化集群中不同微服务之间的调用操作的作用。

对于平台应用而言,平台的功能为通用型,jar包会在每一个节点上进行部署;而对于业务而言,其功能jar包只会部署在自身所在的应用节点上,因此需要调用到正确的节点才能的到正确的调用返回。因此微服务调用区分了以下类型:

  • 平台微服务调用,该类别的微服务调用会依据负载均衡算法调用到集群中任一节点;
  • 业务微服务调用,该类别的微服务调用会调用到指定的业务集群的节点上;
  • 二开微服务调用,该类别的微服务调用会调用到指定二开注册的微服务节点上。

说明:二开微服务必须由服务工厂注册定义才能使用,因此必须要有服务工厂类,服务工厂路由规则为:{isv标识|公司标识}.{云id}.{应用id}.ServiceFactory

例如:

  • 新建微服务接口示例:isv.ti.bo.mservice.xxxService
  • 新建服务工厂示例:isv.ti.bo.ServiceFactory

目前苍穹使用的负载均衡算法为健康度负载算法。

API概览

方法 说明
static T getBOSService (String appId, String serviceName, Class bizCls) 通过应用Id获取平台提供的微服务
static Object invokeBOSService (String appId, String serviceName, String methodName, Object… paras) 平台微服务调用
static T invokeBizService (String cloudId, String appId, String serviceName, String methodName,Object… paras) 业务微服务调用
static T invokeService (String factoryQualifiedPrefix, String appId, String serviceName, String methodName, Object… paras) 二开业务调用
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package kd.bos.servicehelper;
import kd.bos.dataentity.resource.ResManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import kd.bos.dataentity.trace.EntityTraceSpan;
import kd.bos.dataentity.trace.EntityTracer;
import kd.bos.service.DispatchService;
import kd.bos.service.lookup.ServiceLookup;
import kd.sdk.annotation.SdkService;

/**
 * 微服务调用入口:通过动态反射方式调用各种平台、业务封装的各种微服务 
 */
@SdkService(name = "微服务调用入口")
public class DispatchServiceHelper {
	@SuppressWarnings("fb-contrib:PMB_POSSIBLE_MEMORY_BLOAT")
	private static Map<String, Object> proxyMap = new ConcurrentHashMap<>();

	static {
		//增加安字母顺序排列
		ServiceLookup.addSystemAppId("bos", "bd", "qing", "sbd", "nocode");
		ServiceLookup.addSystemAppId("iscb");
		//ai ai服务云 
		ServiceLookup.addSystemAppId("cvp");
		//data 数据服务云 
		ServiceLookup.addSystemAppId("idi"); //数据洞察
		//scm 供应链(含lts-im)
		ServiceLookup.addSystemAppId("adm", "bid", "ent", "im", "mal", "pbd", "pur", "quo", "scp", "sou", "srm", "ten",     "bdm", "mm", "lbd");	//"bdm", "mm", "lbd"
		ServiceLookup.addSystemAppId("mppm", "mpur", "mscp", "pm", "pmm", "rebm", "repe", "resc", "resm");
		//pmc 装备制造
		ServiceLookup.addSystemAppId("pmbd", "pmpd", "pmps"); 
		//fi 财务
		ServiceLookup.addSystemAppId("aifs", "ai", "ap", "ar", "bcm", "cas", "er", "fa", "fibd", "gl", "iep", "pa", "ua",    "rpt", "cm", "em");	//"rpt", "cm", "em"微服务没有配置
		ServiceLookup.addSystemAppId("ssc", "task");
		//tmc 资金云
		ServiceLookup.addSystemAppId("am", "bei", "fbp", "fim");
		//esc 员工服务云
		ServiceLookup.addSystemAppId("exp", "kdem", "tra");
		// 企业绩效云
		ServiceLookup.addSystemAppId("eb");
		
		//hr
		ServiceLookup.addSystemAppId("haos", "hbom", "hbp", "hbss", "heo", "hers", "hjms", "hlcs");
		//hr薪酬
		ServiceLookup.addSystemAppId("hsas", "hsbp","hsbs","hscs","hspp","hsss");
		//drp
		ServiceLookup.addSystemAppId("bbc", "dmp", "dpa", "dpm", "drm", "gcm", "gmc", "mdr", "mem", "ocic", "ococ", "ocp", "orvc", "pos", "posm", "rsp", "rspm", "rss", "saa",       "dbd");  // "dbd" 微服务里没有配置 
		//tran
		ServiceLookup.addSystemAppId("tc");
		
		ServiceLookup.addSystemAppId("bcm_calcnode");	//虚拟计算节点
		ServiceLookup.addSystemAppId("ir");		//?
		//升级节点
		ServiceLookup.addSystemAppId("bos-up");
	}

	/**
	 * 获取苍穹平台发布的微服务接口
	 * @param appId 业务应用编码,如"bos", "gl",用以定位微服务执行节点
	 * @param serviceName 微服务名称,默认为微服务接口短类名
	 * @param bizCls 微服务接口定义
	 * @return 返回微服务接口实例
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getBOSService(String appId, String serviceName, Class<T> bizCls) {
		if (serviceName == null) {
			serviceName = bizCls.getSimpleName();
		}

		String key = (appId + "#" + serviceName + "#" + bizCls.getName()).toLowerCase();
		final String sn = serviceName;
		Object obj = proxyMap.computeIfAbsent(key,k->{
			return Proxy.newProxyInstance(
					bizCls.getClassLoader(),
					new Class[] { bizCls },
					new InvocationHandler() {
						@Override
						public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
							return invokeBOSService(appId, sn, method.getName(), args);
						}
					});
		});

		return (T) obj;
	}

	/**
	 * 调用苍穹平台发布的微服务
	 * @param appId 业务应用编码,如"bos", "gl",用以定位微服务执行节点
	 * @param serviceName 微服务接口名
	 * @param methodName 服务方法
	 * @param paras 服务方法的参数
	 * @return 返回微服务执行结果
	 */
	public static Object invokeBOSService(String appId, String serviceName, String methodName, Object... paras) {
		try (EntityTraceSpan span = EntityTracer.create(appId + "." + serviceName, methodName)) {
			DispatchService service = serviceLookup(appId);
			return service.invoke("kd.bos.service.ServiceFactory", serviceName, methodName, paras);
		}
	}

	/**
	 * 调用星瀚财务、供应链、制造、HR等业务领域发布的微服务
	 * @param cloudId 业务云编码,如"fi"
	 * @param appId 业务应用编码,如"gl",用以定位微服务执行节点
	 * @param serviceName 微服务接口名
	 * @param methodName 服务方法
	 * @param paras 服务方法的参数
	 * @return 返回微服务执行结果
	 */
	@SuppressWarnings("unchecked")
	public static <T> T invokeBizService(String cloudId, String appId, String serviceName, String methodName,
			Object... paras) {
		try (EntityTraceSpan span = EntityTracer.create(appId + "." + serviceName, methodName)) {
			DispatchService service = serviceLookup(appId);
			String factory = String.format("kd.%s.%s.servicehelper.ServiceFactory", cloudId, appId);
			return (T) service.invoke(factory, serviceName, methodName, paras);
		}
	}

	/**
	 * 调用伙伴、二次开发发布的微服务
	 * @param factoryQualifiedPrefix Factory类限定前缀,如:服务工厂"isv.ti.bo.ServiceFactory" 的限定前缀为"isv.ti.bo"
	 * @param appId 二开业务应用编码,如"bo",用以定位微服务执行节点
	 * @param serviceName 微服务接口名
	 * @param methodName 服务方法
	 * @param paras 服务方法的参数
	 * @return 返回微服务执行结果
	 */
	@SuppressWarnings("unchecked")
	public static <T> T invokeService(String factoryQualifiedPrefix, String appId, String serviceName, String methodName,
			Object... paras) {
		try (EntityTraceSpan span = EntityTracer.create(appId + "." + serviceName, methodName)) {
			DispatchService service = serviceLookup(appId);
			String factory = String.format("%s.ServiceFactory", factoryQualifiedPrefix);
			return (T) service.invoke(factory, serviceName, methodName, paras);
		}
	}

	
    /**
     * 调用外部第三方接口服务(通过集成云服务转发,默认先路由到二级节点cic)
     * @param callerAppId 调用者发起者所属应用,如:bo, scm,fi
     * @param apiNumber 集成API注册的编码
     * @param params api参数,map格式
     * @return 返回第三方服务返回结果
     */
    public static <T> T invokeExternalService(String callerAppId, String apiNumber, Map<String, Object> params) {
        return invokeBizService("isc", "iscb.cic", "IscApicService", "invokeBOSExternalService",
                new Object[] { callerAppId, apiNumber, params });
    }
    
	private static DispatchService serviceLookup(String appId) {
		DispatchService service = null;
		String serviceAppId = ServiceLookup.getServiceAppId(appId);
		service = ServiceLookup.lookup(DispatchService.class, serviceAppId);
		return service;
	}


}

传入参数:

参数名称 参数 参数类型 是否必传
云Id cloudId String
应用Id appId String
注册的服务名称 serviceName String
调用服务方法 methodName String
方法入参 paras Object[]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
     * 根据用户id微服务获取用户有权限的立项id
     * @return 返回bean属性code为true data没有值说明是全功能用户,
     *         code为true data有值即为立项id
     *         code为false 失败打日志
     *
     * */
public static ResultBean getHasPermBidProjectIdsByUserId(Object userId){
    ResultBean resultBean = new ResultBean();
    Object result = null;
    try {
        result = DispatchServiceHelper.invokeBizService(CrlbdlTenListBillConstant.REPC,
                                                        CrlbdlTenListBillConstant.REPC_REBM_APPID,
                                                        CrlbdlTenListBillConstant.GET_BIDPROJRCTIDS_INTERFACE,
                                                        CrlbdlTenListBillConstant.GET_BIDPROJRCTIDS_METHOD,
                                                        userId);
    }catch (Exception e){
        BizLog.log(e.getMessage());
        BizLog.log(Arrays.toString(e.getStackTrace()));
        BizLog.log(ResManager.loadKDString("微服务调用失败,请稍后重试。","CrlTenlistUtil_0","crlc-crlbdl-common"));
        resultBean.setSuccess(false);
        return resultBean;
    }
    if(result == null){
        //这是没有返回值 打日志
        BizLog.log("No Return Data From Rebm!!!!!!!!!!");
        resultBean.setSuccess(false);
        return resultBean;
    }
    if(result instanceof Boolean && (Boolean)result){
        //全功能用户
        resultBean.setSuccess(true);
    }else if(result instanceof Set){
        //返回立项id集合
        resultBean.setSuccess(true);
        resultBean.setData(result);
    }
    return resultBean;
}

框架解析

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package kd.bos.service.lookup;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import kd.bos.config.client.RuntimeGlobalProperties;
import kd.bos.context.RequestContext;
import kd.bos.instance.Cluster;
import kd.bos.mservice.spi.rpc.MServiceLookup;
import kd.bos.thread.ThreadTruck;
import kd.bos.util.StringUtils;

public final class ServiceLookup {
    private static MServiceLookup impl;
    private static long startTimesnap = -1L;
    private static boolean isRpcServiceStarting = false;
    private static Map<String, Set<String>> clusterAppIds = new ConcurrentHashMap(1);
    private static Set<String> systemAppIds = new HashSet();

    public ServiceLookup() {
    }

    public static void setImpl(MServiceLookup lookup) {
        impl = lookup;
    }

    public static void endStartRpcService() {
        isRpcServiceStarting = false;
    }

    public static void beginStartRpcService(long timesnap) {
        startTimesnap = timesnap;
        isRpcServiceStarting = true;
    }

    /** @deprecated */
    public static <T> T lookup(String id) {
        throw new UnsupportedOperationException("Not supported. use lookup(class,appId) instead.");
    }

    /** @deprecated */
    public static <T> T lookup(Class<T> clazz) {
        return lookup(clazz, "bos");
    }

    public static <T> T lookup(Class<T> clazz, String appId) {
        if (impl == null) {
            if (isRpcServiceStarting) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                throw new Error("RpcService service is starting , start at: " + sdf.format(new Date(startTimesnap)));
            } else {
                throw new Error("ServiceLookup not implemented .");
            }
        } else {
            T t = impl.lookup(clazz, appId);
            if (t == null) {
                throw new RuntimeException("Mservice consumer by class '" + clazz + "' and appId '" + appId + "' not found.");
            } else {
                return t;
            }
        }
    }

    public static <T> T lookupHttp(Class<T> clazz, String appId, String dataCodecType) {
        if (impl == null) {
            if (isRpcServiceStarting) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                throw new Error("RpcService service is starting , start at: " + sdf.format(new Date(startTimesnap)));
            } else {
                throw new Error("ServiceLookup not implemented .");
            }
        } else {
            T t = impl.lookupHttp(clazz, appId, dataCodecType);
            if (t == null) {
                throw new RuntimeException("Mservice consumer by class '" + clazz + "' and appId '" + appId + "' not found.");
            } else {
                return t;
            }
        }
    }

    public static <T> T lookupEndpoint(Class<T> clazz, String ip, String port) {
        if (impl == null) {
            if (isRpcServiceStarting) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                throw new Error("RpcService service is starting , start at: " + sdf.format(new Date(startTimesnap)));
            } else {
                throw new Error("lookupEndpoint not implemented .");
            }
        } else if (StringUtils.isEmpty(ip)) {
            throw new RuntimeException("lookupEndpoint ip can not be empty");
        } else {
            T t = impl.lookupEndpoint(clazz, ip, port);
            if (t == null) {
                throw new RuntimeException("Mservice consumer by class '" + clazz + "' and ip '" + ip + "' not found.");
            } else {
                return t;
            }
        }
    }

    public static void addSystemAppId(String... appIds) {
        String[] var1 = appIds;
        int var2 = appIds.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String id = var1[var3];
            systemAppIds.add(id);
        }

    }

    public static Set<String> getSystemAppIds() {
        return Collections.unmodifiableSet(systemAppIds);
    }

    public static String getServiceAppId(String appId) {
        String[] leafAppIds = appId.split("\\.");

        for(int i = leafAppIds.length - 1; i >= 0; --i) {
            String leafAppId = _getServiceAppId(leafAppIds[i]);
            if (leafAppId != null) {
                return leafAppId;
            }
        }

        ThreadTruck.put("customForLastestAppid", appId);
        String customAppid = _getServiceAppId("custom");
        return customAppid == null ? appId : "custom";
    }

    public static boolean hasDeployedAppId(String appId) {
        String strAppIds = System.getProperty("registedAppIds");
        if (strAppIds != null) {
            Set<String> appIds = (Set)clusterAppIds.get(strAppIds);
            if (appIds == null) {
                String[] arrayAppids = strAppIds.split(",");
                appIds = new HashSet(arrayAppids.length);
                String[] var4 = arrayAppids;
                int var5 = arrayAppids.length;

                for(int var6 = 0; var6 < var5; ++var6) {
                    String id = var4[var6];
                    ((Set)appIds).add(id);
                }

                clusterAppIds.put(strAppIds, appIds);
            }

            boolean b = ((Set)appIds).contains(appId);
            if (b) {
                return true;
            }

            RequestContext rc = RequestContext.get();
            String deployCloud = Cluster.getCloudByApp(appId, rc == null ? null : rc.getAccountId());
            if (deployCloud != null) {
                String cloudtag = "cloud--" + deployCloud;
                if (((Set)appIds).contains(cloudtag)) {
                    return true;
                }
            }
        }

        return false;
    }

    private static String _getServiceAppId(String appId) {
        String strAppIds = System.getProperty("registedAppIds");
        Object appIds;
        String[] arrayAppids;
        String[] var4;
        int var5;
        int var6;
        String id;
        if (strAppIds != null) {
            appIds = (Set)clusterAppIds.get(strAppIds);
            if (appIds == null) {
                arrayAppids = strAppIds.split(",");
                appIds = new HashSet(arrayAppids.length);
                var4 = arrayAppids;
                var5 = arrayAppids.length;

                for(var6 = 0; var6 < var5; ++var6) {
                    id = var4[var6];
                    ((Set)appIds).add(id);
                }

                clusterAppIds.put(strAppIds, appIds);
            }

            if (((Set)appIds).contains(appId)) {
                return appId;
            }

            RequestContext rc = RequestContext.get();
            String deployCloud = Cluster.getCloudByApp(appId, rc == null ? null : rc.getAccountId());
            if (deployCloud != null) {
                String cloudtag = "cloud--" + deployCloud;
                if (((Set)appIds).contains(cloudtag)) {
                    return cloudtag;
                }
            }
        }

        if (systemAppIds.contains(appId)) {
            return appId;
        } else {
            if (Boolean.getBoolean("customAppIds.lookup")) {
                strAppIds = System.getProperty("customAppIds");
                if (strAppIds == null) {
                    strAppIds = RuntimeGlobalProperties.get("customAppIds");
                }

                if (strAppIds != null) {
                    appIds = (Set)clusterAppIds.get(strAppIds);
                    if (appIds == null) {
                        arrayAppids = strAppIds.split(",");
                        appIds = new HashSet(arrayAppids.length);
                        var4 = arrayAppids;
                        var5 = arrayAppids.length;

                        for(var6 = 0; var6 < var5; ++var6) {
                            id = var4[var6];
                            ((Set)appIds).add(id);
                        }

                        clusterAppIds.put(strAppIds, appIds);
                    }

                    if (((Set)appIds).contains(appId)) {
                        return appId;
                    }
                }
            }

            return null;
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package kd.bos.mservice.spi.rpc;

public interface MServiceLookup {
    <T> T lookup(Class<T> var1, String var2);

    default <T> T lookupHttp(Class<T> arg0, String arg1, String dataCodecType) {
        return this.lookup(arg0, arg1);
    }

    <T> T lookupEndpoint(Class<T> var1, String var2, String var3);
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package kd.bos.mservice.rpc.dubbo;

import com.alibaba.dubbo.config.ReferenceConfig;
import java.lang.management.ManagementFactory;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import kd.bos.exception.ErrorCode;
import kd.bos.instance.Instance;
import kd.bos.logging.Log;
import kd.bos.logging.LogFactory;
import kd.bos.mservice.debug.DebugUtil;
import kd.bos.mservice.rpc.dubbo.debug.LocalDebugProxy;
import kd.bos.mservice.spi.rpc.MServiceLookup;
import kd.bos.util.AppUtils;

public class DubboServiceLookup implements MServiceLookup {
    private static final Log log = LogFactory.getLog(DubboServiceLookup.class);
    private static ErrorCode errorCode = new ErrorCode("DubboServiceLookup", "%s");
    private static ConcurrentHashMap<String, Object> appIdInstanceMap = new ConcurrentHashMap();
    private static ConcurrentHashMap<String, Object> hostInstanceMap = new ConcurrentHashMap();
    private static boolean debugInstance = false;
    private static boolean canlooluplocal = true;

    public DubboServiceLookup() {
    }

    public static boolean getCanlooluplocal() {
        return canlooluplocal;
    }

    public static ErrorCode getErrorCode() {
        return errorCode;
    }

    private <T> T lookupNotAppSplit(Class<T> clazz) {
        if (canlooluplocal) {
            T bean = DubboBeanManager.getLocalServiceInstance(clazz);
            if (bean != null) {
                return bean;
            }
        }

        ReferenceConfig config = DubboBeanManager.getReferenceConfig(clazz.getName());
        if (config == null) {
            return null;
        } else {
            String key = clazz.getName() + "_";
            return appIdInstanceMap.computeIfAbsent(key, (k) -> {
                return ProxyFactory.createProxyNoAppId(clazz);
            });
        }
    }

    public <T> T lookup(Class<T> clazz, String appId) {
        boolean debugMode = DebugUtil.isDebugMode();
        if (debugMode || debugInstance) {
            appId = "debug";
        }

        T obj = this.doLookupImpl(clazz, appId, "rpc", "javaobj");
        if (canlooluplocal) {
            obj = LocalDebugProxy.debugProxy(clazz, obj);
        }

        return obj;
    }

    public <T> T lookupHttp(Class<T> clazz, String appId, String dataCodecType) {
        boolean debugMode = DebugUtil.isDebugMode();
        if (debugMode || debugInstance) {
            appId = "debug";
        }

        T obj = this.doLookupImpl(clazz, appId, "http", dataCodecType);
        if (canlooluplocal) {
            obj = LocalDebugProxy.debugProxy(clazz, obj);
        }

        return obj;
    }

    private <T> T doLookupImpl(Class<T> clazz, String appId, String prototol, String dataCodecType) {
        if (!Instance.isAppSplit() && !AppUtils.isDeployAloneApp(appId)) {
            return this.lookupNotAppSplit(clazz);
        } else {
            if (canlooluplocal) {
                Object bean = DubboBeanManager.getLocalServiceInstance(clazz, appId);
                if (bean != null) {
                    return bean;
                }
            }

            String key = clazz.getName() + ":" + appId + ":" + prototol + ":" + dataCodecType;
            return appIdInstanceMap.computeIfAbsent(key, (k) -> {
                return ProxyFactory.createProxy(clazz, appId, prototol, dataCodecType);
            });
        }
    }

    public <T> T lookupEndpoint(Class<T> clazz, String ip, String port) {
        String key = clazz.getName() + "_" + ip + "_" + port;
        return hostInstanceMap.computeIfAbsent(key, (k) -> {
            return ProxyFactory.createProxyEndpoint(clazz, ip, port);
        });
    }

    static {
        debugInstance = Instance.isDebugInstance();
        if (debugInstance) {
            canlooluplocal = true;
        } else {
            try {
                String str = System.getProperty("dubbo.service.lookup.local");
                if (str != null) {
                    canlooluplocal = Boolean.parseBoolean(str);
                } else {
                    List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
                    Iterator var2 = args.iterator();

                    while(var2.hasNext()) {
                        String arg = (String)var2.next();
                        if (arg.startsWith("-agentlib:jdwp")) {
                            canlooluplocal = false;
                            break;
                        }
                    }
                }
            } catch (Error | Exception var4) {
                log.warn(var4);
            }
        }

    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package kd.bos.mservice.rpc.feign;

import java.lang.management.ManagementFactory;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import kd.bos.instance.Instance;
import kd.bos.mservice.debug.DebugUtil;
import kd.bos.mservice.rpc.feign.debug.FeignLocalDebugProxy;
import kd.bos.mservice.spi.rpc.MServiceLookup;
import kd.bos.util.AppUtils;

public class FeignServiceLookup implements MServiceLookup {
    private static ConcurrentHashMap<String, Object> appIdInstanceMap = new ConcurrentHashMap();
    private static boolean debugInstance = false;
    private static boolean canlooluplocal = true;

    public FeignServiceLookup() {
    }

    public <T> T lookup(Class<T> clazz, String appId) {
        boolean debugMode = DebugUtil.isDebugMode();
        if (debugMode || debugInstance) {
            appId = "debug";
        }

        T obj = this.doLookupImpl(clazz, appId, "javaobj");
        if (!(obj instanceof Proxy) && (debugMode || debugInstance)) {
            obj = FeignLocalDebugProxy.debugProxy(clazz, obj);
        }

        return obj;
    }

    public <T> T lookupHttp(Class<T> clazz, String appId, String dataCodecType) {
        boolean debugMode = DebugUtil.isDebugMode();
        if (debugMode || debugInstance) {
            appId = "debug";
        }

        T obj = this.doLookupImpl(clazz, appId, dataCodecType);
        if (!(obj instanceof Proxy) && (debugMode || debugInstance)) {
            obj = FeignLocalDebugProxy.debugProxy(clazz, obj);
        }

        return obj;
    }

    private <T> T doLookupImpl(Class<T> clazz, String appId, String dataCodecType) {
        if (!Instance.isAppSplit() && !AppUtils.isDeployAloneApp(appId)) {
            return this.lookupNotAppSplit(clazz, dataCodecType);
        } else {
            if (canlooluplocal) {
                Object o = FeignServiceRegister.getServiceInstance(appId, clazz.getName());
                if (o != null) {
                    return o;
                }
            }

            String key = clazz.getName() + ":" + appId + ":" + dataCodecType;
            return appIdInstanceMap.computeIfAbsent(key, (k) -> {
                return FeignProxyFactory.createProxy(clazz, appId, dataCodecType);
            });
        }
    }

    private <T> T lookupNotAppSplit(Class<T> clazz, String dataCodecType) {
        Object o = FeignServiceRegister.getServiceInstance(clazz.getName());
        if (canlooluplocal && o != null) {
            return o;
        } else {
            String key = clazz.getName() + ":" + dataCodecType;
            return appIdInstanceMap.computeIfAbsent(key, (k) -> {
                return FeignProxyFactory.createProxy(clazz, (String)null, dataCodecType);
            });
        }
    }

    public <T> T lookupEndpoint(Class<T> type, String ip, String port) {
        String key = type.getName() + "_" + ip + "_" + port;
        return appIdInstanceMap.computeIfAbsent(key, (k) -> {
            return FeignProxyFactory.createHostProxy(type, ip, port);
        });
    }

    static {
        debugInstance = Instance.isDebugInstance();
        if (debugInstance) {
            canlooluplocal = true;
        } else {
            try {
                String str = System.getProperty("feign.lookup.local");
                if (str != null) {
                    canlooluplocal = Boolean.parseBoolean(str);
                } else {
                    List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
                    Iterator var2 = args.iterator();

                    while(var2.hasNext()) {
                        String arg = (String)var2.next();
                        if (arg.startsWith("-agentlib:jdwp")) {
                            canlooluplocal = false;
                            break;
                        }
                    }
                }
            } catch (Throwable var4) {
            }
        }

    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package kd.bos.mservice.rpc.assembly;

import kd.bos.mservice.spi.rpc.MServiceLookup;

public class AssemblyServiceLookup implements MServiceLookup {
    public AssemblyServiceLookup() {
    }

    public <T> T lookup(Class<T> type, String appId) {
        return AssemblyServiceManager.getInsideServiceLookup().lookup(type, appId);
    }

    public <T> T lookupEndpoint(Class<T> type, String ip, String port) {
        return AssemblyServiceManager.getInsideServiceLookup().lookupEndpoint(type, ip, port);
    }

    public <T> T lookupHttp(Class<T> type, String str, String dataCodecType) {
        return AssemblyServiceManager.getOutsideServiceLookup().lookupHttp(type, str, dataCodecType);
    }
}



package kd.bos.mservice.rpc.assembly;

import java.util.ArrayList;
import java.util.List;
import kd.bos.extension.ExtensionFactory;
import kd.bos.mservice.spi.rpc.MServiceLookup;
import kd.bos.mservice.spi.rpc.MServiceRegister;
import kd.bos.mservice.spi.rpc.MServiceStarter;

public class AssemblyServiceManager {
    private static String[] assemRpcTypes;
    private static final int INSIDEINDEX = 0;
    private static final int OUTSIDEINDEX = 1;
    private static ExtensionFactory<MServiceLookup> lookupFactory = ExtensionFactory.getExtensionFacotry(MServiceLookup.class);
    private static ExtensionFactory<MServiceRegister> registerFactory = ExtensionFactory.getExtensionFacotry(MServiceRegister.class);
    private static ExtensionFactory<MServiceStarter> servicestarterFactory = ExtensionFactory.getExtensionFacotry(MServiceStarter.class);

    public AssemblyServiceManager() {
    }

    static List<MServiceStarter> getServiceStarters() {
        List<MServiceStarter> serviceStarters = new ArrayList(2);
        String[] var1 = assemRpcTypes;
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String rpcType = var1[var3];
            serviceStarters.add(servicestarterFactory.getExtension(rpcType));
        }

        return serviceStarters;
    }

    static List<MServiceRegister> getServiceRegisters() {
        List<MServiceRegister> serviceRegisters = new ArrayList(2);
        String[] var1 = assemRpcTypes;
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String rpcType = var1[var3];
            serviceRegisters.add(registerFactory.getExtension(rpcType));
        }

        return serviceRegisters;
    }

    static MServiceRegister getInsideServiceRegister() {
        String insideRegister = System.getProperty("assemble.rpc.inside", assemRpcTypes[0]);
        return (MServiceRegister)registerFactory.getExtension(insideRegister);
    }

    static MServiceRegister getOutsideServiceRegister() {
        String outsideRegister = System.getProperty("assemble.rpc.outside", assemRpcTypes[1]);
        return (MServiceRegister)registerFactory.getExtension(outsideRegister);
    }

    static MServiceLookup getInsideServiceLookup() {
        String insideLookup = System.getProperty("assemble.rpc.inside", assemRpcTypes[0]);
        return (MServiceLookup)lookupFactory.getExtension(insideLookup);
    }

    static MServiceLookup getOutsideServiceLookup() {
        String outsideLookup = System.getProperty("assemble.rpc.outside", assemRpcTypes[1]);
        return (MServiceLookup)lookupFactory.getExtension(outsideLookup);
    }

    private static void initAssemRpcTypes() {
        String assemRpcType = System.getProperty("assemble.rpc.types", "dubbo,feign");
        assemRpcTypes = assemRpcType.split(",");
    }

    static {
        initAssemRpcTypes();
    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package kd.bos.mservice.rpc.dubbo;

import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.remoting.RemotingException;
import com.alibaba.dubbo.rpc.RpcException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import kd.bos.context.RequestTimeoutContext;
import kd.bos.exception.DubboErrorCode;
import kd.bos.exception.KDException;
import kd.bos.framework.gray.GrayStrategy;
import kd.bos.govern.eventdata.reporter.EventDataReporter;
import kd.bos.govern.eventdata.types.TerminationEvent.RpcTimeoutEvent;
import kd.bos.govern.eventdata.types.TerminationEvent.WebAutoTimeoutEvent;
import kd.bos.mservice.sdk.thread.InnerThreadTruck;
import kd.bos.thread.ThreadTruck;
import kd.bos.trace.tracer.MemSpanTrace;
import kd.bos.trace.util.TraceIdUtil;
import kd.bos.util.PropertiesUtil;
import kd.bos.util.StringUtils;

public class ProxyFactory {
    public ProxyFactory() {
    }

    public static <T> T createProxy(Class<T> model, String appId) {
        Handler handler = new Handler(model.getName(), appId);
        T proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{model}, handler);
        return proxy;
    }

    public static <T> T createProxy(Class<T> model, String appId, String prototol, String dataCodecType) {
        Handler handler = new Handler(model.getName(), appId);
        handler.setPrototol(prototol);
        handler.setDataCodecType(dataCodecType);
        T proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{model}, handler);
        return proxy;
    }

    protected static <T> T createProxyNoAppId(Class<T> model) {
        Handler handler = new Handler(model.getName(), (String)null);
        T proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{model}, handler);
        return proxy;
    }

    public static <T> T createProxyEndpoint(Class<T> model, String ip, String port) {
        if (StringUtils.isEmpty(port)) {
            port = System.getProperty("dubbo.protocol.port", "20880");
        }

        Handler handler = new Handler(model.getName(), (String)null, ip, port);
        T proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{model}, handler);
        return proxy;
    }

    private static class Handler implements InvocationHandler {
        private final String interfaze;
        private final String appId;
        private String host;
        private String ip;
        private String port;
        private String prototol = "rpc";
        private String dataCodecType = "javaobj";

        public Handler(String interfaze, String appId) {
            this.interfaze = interfaze;
            this.appId = appId;
        }

        public Handler(String interfaze, String appId, String ip, String port) {
            this.interfaze = interfaze;
            this.appId = appId;
            this.ip = ip;
            this.port = port;
            this.host = ip + ":" + port;
        }

        public void setDataCodecType(String dataCodecType) {
            this.dataCodecType = dataCodecType;
        }

        protected void setPrototol(String prototol) {
            this.prototol = prototol;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object bean = null;
            ReferenceConfig config = null;
            Throwable error = null;
            if (this.appId == null) {
                if (this.host != null) {
                    config = DubboBeanManager.getReferenceConfigEndpoint(this.interfaze, this.ip, this.port);
                } else {
                    config = DubboBeanManager.getReferenceConfig(this.interfaze);
                }
            } else {
                config = DubboBeanManager.getReferenceConfigWithPrototol(this.interfaze, this.appId, this.prototol);
            }

            error = null;
            if (config != null) {
                try {
                    bean = config.get();
                } catch (Error | Exception var30) {
                    error = var30;
                }
            }

            if (bean == null) {
                String partReason = this.appId != null ? " by appId " + this.appId : (this.host != null ? " by endpoint " + this.host : "");
                if (error != null) {
                    if (error instanceof KDException) {
                        throw error;
                    } else {
                        throw new KDException(error, DubboServiceLookup.getErrorCode(), new Object[]{"Can't lookup mservice:" + this.interfaze + partReason});
                    }
                } else {
                    throw new KDException(error, DubboServiceLookup.getErrorCode(), new Object[]{"Can't lookup mservice:" + this.interfaze + partReason + ", no consumer configured."});
                }
            } else {
                try {
                    MemSpanTrace span = MemSpanTrace.create(method.getDeclaringClass().getName(), method.getName() + this.toArrayString(args));
                    Throwable var34 = null;

                    Object var10;
                    try {
                        if (this.appId != null) {
                            span.addTag("appid", this.appId);
                        }

                        try {
                            ThreadTruck.put("DATACODEC_TYPE_KEY", this.dataCodecType);
                            Object result = method.invoke(bean, args);
                            var10 = result;
                        } finally {
                            ThreadTruck.remove("DATACODEC_TYPE_KEY");
                        }
                    } catch (Throwable var31) {
                        var34 = var31;
                        throw var31;
                    } finally {
                        if (span != null) {
                            if (var34 != null) {
                                try {
                                    span.close();
                                } catch (Throwable var28) {
                                    var34.addSuppressed(var28);
                                }
                            } else {
                                span.close();
                            }
                        }

                    }

                    return var10;
                } catch (Throwable var33) {
                    Throwable e = var33;
                    if (var33 instanceof InvocationTargetException) {
                        e = ((InvocationTargetException)var33).getTargetException();
                        if (e instanceof RpcException) {
                            String errorMsg = this.getDubboErrorMessage(this.appId, method, args, e);
                            if (e.getCause() != null && e.getCause() instanceof RemotingException) {
                                e = e.getCause();
                            }

                            if (e instanceof KDException) {
                                throw e;
                            }

                            String msg = StringUtils.isNotEmpty(errorMsg) ? errorMsg : "Exception in invoke " + this.interfaze + "(appId=" + this.getShowAppIdInException(this.appId) + "), error is: " + e.getMessage();
                            throw new KDException(e, DubboServiceLookup.getErrorCode(), new Object[]{msg});
                        }
                    }

                    if (e instanceof KDException) {
                        throw e;
                    } else {
                        throw new KDException(e, DubboServiceLookup.getErrorCode(), new Object[]{"Exception in invoke " + this.interfaze + "(appId=" + this.getShowAppIdInException(this.appId) + "), error is: " + e.getMessage()});
                    }
                }
            }
        }

        private String getShowAppIdInException(String appId) {
            if (appId == null) {
                return "";
            } else {
                return "custom".equals(appId) ? "custom[" + ThreadTruck.get("customForLastestAppid") + "]" : appId;
            }
        }

        private String toArrayString(Object[] args) {
            try {
                StringBuilder sb = new StringBuilder("[");
                Object[] var3 = args;
                int var4 = args.length;

                for(int var5 = 0; var5 < var4; ++var5) {
                    Object o = var3[var5];
                    if (o != null) {
                        if (o instanceof Object[]) {
                            sb.append(Arrays.toString((Object[])((Object[])o)));
                        } else {
                            sb.append(o);
                        }

                        sb.append(" , ");
                    }
                }

                sb.setLength(sb.length() - 2);
                sb.append("]");
                if (sb.length() > 3000) {
                    sb.setLength(3000);
                    sb.append(".......");
                }

                return sb.toString();
            } catch (Error | Exception var7) {
                return Arrays.toString(args);
            }
        }

        private String getDubboErrorMessage(String appId, Method method, Object[] args, Throwable e) {
            String logId = TraceIdUtil.idToHex(TraceIdUtil.createId());
            String fullApiName = this.getApiName(method.getDeclaringClass().getName() + "." + method.getName(), args, appId);
            String appIdName = PropertiesUtil.getAppIdName(appId);
            String appgroup = GrayStrategy.getAppGroup(appId);
            if ("defaultGroup".equals(appgroup)) {
                appgroup = "";
            } else {
                appgroup = appgroup + ".";
            }

            appIdName = (appIdName == null ? "" : appIdName) + (appId == null ? "" : "(" + appgroup + appId + ")");
            int errorCode = ((RpcException)e).getCode();
            String errorMsg;
            if (DubboErrorCode.providerTimeOut.getCode() == errorCode) {
                RequestTimeoutContext requestTimeoutContext = RequestTimeoutContext.get();
                if (requestTimeoutContext != null && requestTimeoutContext.isAutomaticTimeout()) {
                    errorMsg = DubboErrorCode.getMsgByCode(errorCode, fullApiName, appIdName, logId, String.valueOf(requestTimeoutContext.getTimeout() / 1000L), e);
                    EventDataReporter.report(WebAutoTimeoutEvent.getInstance(), errorMsg, e);
                } else {
                    errorMsg = DubboErrorCode.getMsgByCode(errorCode, fullApiName, appIdName, logId, e);
                    EventDataReporter.report(RpcTimeoutEvent.getInstance(), errorMsg, e);
                }
            } else {
                errorMsg = DubboErrorCode.getMsgByCode(errorCode, fullApiName, appIdName, logId, e);
            }

            return errorMsg;
        }

        private String getApiName(String classMethodName, Object[] args, String appId) {
            if ("kd.bos.service.DispatchService.invoke".equals(classMethodName) && args.length > 2) {
                StringBuilder apiNameBuild = new StringBuilder();
                String serviceName = String.valueOf(args[1]);
                String methodName = String.valueOf(args[2]);
                String urlMethodName;
                if ("batchInvokeAction".equals(methodName)) {
                    urlMethodName = (String)InnerThreadTruck.get("batchInvokeAction");
                    if (StringUtils.isNotEmpty(urlMethodName)) {
                        methodName = "(" + urlMethodName.replace(appId + ".", "") + ")";
                    }
                }

                urlMethodName = apiNameBuild.append(serviceName).append(":").append(methodName).toString();
                return urlMethodName;
            } else {
                return classMethodName;
            }
        }
    }
}

微服务框架分析


1. 核心架构与组件

该框架是一个多协议支持、可扩展的微服务调用框架,核心组件如下:

组件 作用
ServiceLookup 服务查找接口,抽象了服务实例的获取逻辑,支持多协议(Dubbo、Feign、Assembly)。
DubboServiceLookup Dubbo 协议的具体实现,通过动态代理和缓存管理服务实例。
FeignServiceLookup Feign 协议的具体实现,支持 HTTP 调用和 RESTful 接口。
AssemblyServiceLookup 组合模式实现,集成多个服务查找策略(如内部服务、外部服务)。
ProxyFactory 代理工厂,生成动态代理对象,封装远程调用逻辑(如协议选择、编解码)。
DispatchServiceHelper 微服务调用入口,提供统一接口(invokeBizService/invokeService)调用服务。
AssemblyServiceManager 管理服务注册、查找、启动的组件,负责组合多个服务实现。

2. 核心流程与协作

(1) 服务调用流程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
DispatchServiceHelper.invokeBizService(...)
ServiceLookup.lookup(...) → 获取服务实例
DubboServiceLookup.doLookupImpl(...) → 根据 appId 和协议生成代理对象
ProxyFactory.createProxy(...) → 创建动态代理
ProxyHandler.invoke(...) → 执行远程调用(Dubbo 或 Feign)
ReferenceConfig(Dubbo)或 FeignClient(Feign) → 实际调用远程服务
(2) 关键步骤分析
  • 服务查找

    • ServiceLookup 通过 MServiceLookup 接口抽象,支持多种实现(Dubbo、Feign、Assembly)。
    • DubboServiceLookup 使用 ConcurrentHashMap 缓存服务实例(appIdInstanceMap/hostInstanceMap),避免重复创建。
    • AssemblyServiceLookup 通过组合模式调用内部/外部服务查找实现(getInsideServiceLookup()/getOutsideServiceLookup())。
  • 动态代理

    • ProxyFactory.createProxy(...) 使用 Java 动态代理生成服务接口的代理对象。
    • ProxyHandlerinvoke(...) 方法中封装远程调用逻辑,支持协议选择(prototol)和数据编解码(dataCodecType)。
  • 协议支持

    • Dubbo:通过 ReferenceConfig 配置服务引用,支持 RPC 协议。
    • Feign:通过 FeignProxyFactory 生成 HTTP 客户端,支持 RESTful 接口。
    • Assembly:组合多个服务查找策略,适配不同场景(如内部服务、外部服务)。

3. 设计模式与实现

设计模式 应用位置 作用与优势
代理模式 ProxyFactory.createProxy(...) 封装远程调用细节,对调用方透明。支持统一处理超时、重试、链路追踪等横切关注点。
工厂模式 ProxyFactoryAssemblyServiceManager 隐藏代理对象的创建逻辑,提供统一接口生成不同类型的代理实例(如 Dubbo、Feign),提高扩展性。
策略模式 ServiceLookup.setImpl(MServiceLookup lookup) 允许运行时切换不同的服务查找实现(如 Dubbo、Feign),实现框架与具体 RPC 实现的解耦,提升灵活性。
单例模式 ConcurrentHashMap 缓存服务实例(appIdInstanceMap/hostInstanceMap 通过缓存避免重复创建代理对象,减少资源消耗,提升性能。
模板方法模式 DubboServiceLookup.doLookupImpl(...) 抽象通用逻辑(如本地调试判断、缓存 key 生成),子类按需覆盖具体实现(如 lookupNotAppSplit(...))。
组合模式 AssemblyServiceLookup 调用 getInsideServiceLookup()/getOutsideServiceLookup() 集成多个服务查找策略,支持复杂场景(如内部服务 + 外部服务组合)。

4. 关键功能与特性

  1. 多协议支持

    • Dubbo:通过 ReferenceConfig 配置服务引用,支持 RPC 协议和本地调试(LocalDebugProxy)。
    • Feign:通过 FeignProxyFactory 生成 HTTP 客户端,适配 RESTful 接口。
    • Assembly:组合多个服务查找策略,适配不同业务场景。
  2. 服务缓存与复用

    • 使用 ConcurrentHashMap 缓存服务实例,避免重复创建代理对象,提升性能。
    • 根据 appId、协议(prototol)、编解码方式(dataCodecType)生成唯一 key,确保缓存精确。
  3. 链路追踪与可观测性

    • EntityTracer.create(...) 创建调用链路上下文,支持分布式追踪。
    • ProxyHandler 中处理超时和异常(如 RpcTimeoutEvent),增强系统可观测性。
  4. 本地调试与热部署

    • LocalDebugProxy.debugProxy(...) 支持本地调试,直接调用本地服务实例。
    • DubboServiceLookup 中的 canlooluplocal 标志控制是否启用本地调试。
  5. 应用拆分与多租户支持

    • Instance.isAppSplit() 判断是否启用应用拆分,适配多租户场景。
    • AppUtils.isDeployAloneApp(appId) 判断服务是否独立部署,动态调整查找逻辑。

5. 优势与适用场景

优势 说明
解耦性 业务代码无需感知 Dubbo/Feign 原生 API,通过统一接口调用服务。
扩展性 支持多协议(Dubbo/Feign/Assembly),可扩展新的服务查找策略。
性能优化 通过缓存和复用代理实例减少资源消耗,适配高并发场景。
可观测性 集成链路追踪(EntityTracer)和调试代理(LocalDebugProxy),便于问题定位。
灵活性 支持应用拆分、多租户、本地调试,适配复杂业务场景。
适用场景
  • 企业级微服务架构:支持 Dubbo/Feign 协议,适配传统和云原生场景。
  • 多租户系统:通过 appId 和应用拆分策略管理不同租户的服务。
  • 混合部署环境:组合内部服务(Dubbo)和外部服务(Feign)调用。
  • 高并发系统:通过缓存和动态代理优化性能,减少资源消耗。

6. 代码结构与依赖关系

(1) 包结构
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
├── DispatchServiceHelper.java          # 微服务调用入口
├── ServiceLookup.java                  # 服务查找接口
├── DubboServiceLookup.java             # Dubbo 协议实现
├── FeignServiceLookup.java             # Feign 协议实现
├── AssemblyServiceLookup.java          # 组合服务查找策略
├── ProxyFactory.java                   # 代理工厂
├── ProxyHandler.java                   # 代理调用逻辑
├── AssemblyServiceManager.java         # 服务管理组件
├── AssemblyServiceStarter.java         # 服务启动组件
├── AssemblyServiceRegister.java        # 服务注册组件
└── ServiceHelperModule.java            # 模块注册
(2) 依赖关系
  • ServiceLookup 依赖 MServiceLookup 接口,实现多协议支持。
  • DubboServiceLookup 依赖 ReferenceConfig(Dubbo 原生 API)。
  • FeignServiceLookup 依赖 FeignProxyFactory 和 HTTP 客户端。
  • ProxyFactory 依赖 Java 动态代理和 InvocationHandler
  • AssemblyServiceManager 依赖 ExtensionFactory 和 SPI 扩展机制。

7. 潜在优化点

  1. 协议自动选择
    可根据服务注册信息自动选择最优协议(如优先使用 Dubbo,降级到 Feign)。

  2. 负载均衡策略
    ProxyHandler 中集成负载均衡算法(如轮询、权重),提升服务调用的可用性。

  3. 服务降级与熔断
    在代理调用中集成 Hystrix 或 Resilience4j,实现服务降级和熔断机制。

  4. 配置中心集成
    ReferenceConfig 和 Feign 配置迁移到配置中心(如 Nacos),支持动态配置更新。

  5. 服务注册与发现
    集成服务注册中心(如 Nacos/Eureka),实现服务的自动注册与发现。


8. 总结

该微服务框架通过 抽象层设计(如 ServiceLookup 接口)和 动态代理模式 实现了多协议支持(Dubbo/Feign)和灵活扩展。其核心优势在于:

  • 解耦业务与 RPC 实现,提升代码可维护性。
  • 通过缓存和复用优化性能,适配高并发场景。
  • 支持本地调试与链路追踪,增强开发效率和系统可观测性。

适用于企业级微服务架构、多租户系统以及混合部署环境,具备良好的扩展性和灵活性。

调用链路解析:以dubbo为例

微服务调用分析

  1. 调用入口
    DispatchServiceHelper.invokeBizService(...) 是微服务调用的入口方法,其核心流程如下:

    • 通过 EntityTracer.create(...) 创建调用链路追踪上下文(用于分布式追踪)。
    • 调用 serviceLookup(appId) 获取 DispatchService 实例。
    • 通过 service.invoke(...) 执行远程调用,参数包含服务工厂类名、服务名、方法名和参数。
  2. Dubbo 封装实现

    • 动态代理
      ProxyFactory.createProxy(...) 使用 Java 动态代理生成服务接口的代理对象。代理对象在 invoke 方法中封装了 Dubbo 的 ReferenceConfig 配置和远程调用逻辑。
    • 协议与编解码
      ProxyFactory.Handler 中通过 prototol(如 rpc/http)和 dataCodecType(如 javaobj)指定通信协议和数据编解码方式,实现多协议支持。
    • 本地调试支持
      DubboServiceLookup.lookup(...) 中通过 LocalDebugProxy.debugProxy(...) 实现代理对象的本地调试能力,支持开发环境直接调用本地服务。
  3. 服务查找与缓存

    • DubboServiceLookup 使用 ConcurrentHashMap 缓存服务实例(appIdInstanceMap/hostInstanceMap),避免重复创建代理对象。
    • 根据 appId 和应用拆分策略(Instance.isAppSplit())决定服务查找逻辑,支持多租户和微服务拆分场景。

使用的设计模式及好处

设计模式 实现位置 作用与好处
代理模式 ProxyFactory.createProxy(...) 通过动态代理封装远程调用细节,对调用方透明,实现解耦。支持统一处理超时、重试、链路追踪等横切关注点。
工厂模式 ProxyFactory 隐藏代理对象的创建逻辑(如协议选择、编解码配置),提供统一接口生成不同类型的代理实例,提高扩展性。
策略模式 ServiceLookup.setImpl(MServiceLookup lookup) 允许运行时切换不同的服务查找实现(如 Dubbo、gRPC),实现框架与具体 RPC 实现的解耦,提升灵活性。
单例模式 ConcurrentHashMap 缓存服务实例 通过缓存避免重复创建代理对象,减少资源消耗,提升性能。
模板方法模式 DubboServiceLookup.doLookupImpl(...) 抽象通用逻辑(如本地调试判断、缓存 key 生成),子类按需覆盖具体实现(如 lookupNotAppSplit(...))。

典型调用链路图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
DispatchServiceHelper.invokeBizService(...)
ServiceLookup.serviceLookup(appId) → 获取 DubboServiceLookup 实例
DubboServiceLookup.lookup(...) → 调用 doLookupImpl(...)
ProxyFactory.createProxy(...) → 生成动态代理对象
ProxyFactory.Handler.invoke(...) → 执行 Dubbo 远程调用
Dubbo ReferenceConfig 调用远程服务

优势总结

  1. 解耦性:业务代码无需感知 Dubbo 原生 API,通过统一服务查找接口抽象实现解耦。
  2. 扩展性:支持多协议(RPC/HTTP)、多编解码方式,且可扩展新的服务查找策略。
  3. 可观测性:集成链路追踪(EntityTracer)和调试代理(LocalDebugProxy),便于问题定位。
  4. 性能优化:通过缓存和复用代理实例减少重复创建开销,适应高并发场景。

Feign VS Dubbo

技术定位与通信协议

Feign

  • 定位:HTTP客户端工具,本质是对HTTP调用的封装,简化RESTful API的调用。

  • 协议:基于HTTP协议,支持JSON/XML等通用数据格式,天然跨语言(需API规范一致)。

  • 场景:适用于基于HTTP的微服务架构,尤其是云原生场景(如Kubernetes原生支持HTTP)。

Dubbo

  • 定位:高性能RPC框架,提供服务发现、负载均衡、集群容错等完整的微服务解决方案。

  • 协议:默认使用Dubbo协议(二进制序列化),也支持HTTP、gRPC等协议,强调高性能和低延迟。

  • 场景:适用于高并发、低延迟的内部服务调用,尤其是Java技术栈主导的大型分布式系统。

服务发现与注册中心

Feign

依赖外部服务发现组件(如Spring Cloud Eureka、Consul、Nacos),需手动集成。

服务调用通过服务名解析为具体URL,需配合负载均衡器(如Ribbon)。

Dubbo

内置多种注册中心(ZooKeeper、Nacos、Etcd等),支持服务自动注册与发现。

服务发现与负载均衡由框架自动处理,提供更细粒度的流量控制(如权重、标签路由)。

负载均衡与集群容错

Feign

依赖Spring Cloud LoadBalancer或Ribbon实现负载均衡,支持轮询、随机等策略。

容错需集成Hystrix或Resilience4j实现熔断、降级。

Dubbo

内置丰富的负载均衡策略(随机、加权轮询、最少活跃调用等)和集群容错模式(failover、failfast等)。

提供更细粒度的服务治理能力,如动态权重调整、流量隔离、自适应负载均衡。

序列化与性能

Feign

  • 基于HTTP协议,默认使用JSON序列化,性能受限于文本格式的开销。

  • 适合对吞吐量要求不极端、API兼容性要求高的场景。

Dubbo

  • 默认使用Hessian2二进制序列化,性能显著优于JSON(尤其对于复杂对象)。

  • 支持多种序列化协议(如Kryo、Protobuf),并可通过TCP长连接复用提升效率。

服务治理与扩展性

Feign

  • 服务治理能力较弱,需依赖Spring Cloud生态组件(如Gateway、Sentinel)。

  • 扩展性主要通过拦截器(RequestInterceptor)和解码器(ErrorDecoder)实现。

Dubbo

提供全方位的服务治理能力:

  • 动态配置(如参数路由、流量控制)。

  • 服务降级与熔断(支持自适应熔断)。

  • 服务监控与链路追踪(集成Zipkin、Skywalking等)。

  • 支持SPI扩展机制,可自定义协议、序列化、负载均衡策略等。

多语言支持

Feign

  • 基于标准HTTP协议,天然支持跨语言调用(只要API规范一致)。

  • 其他语言需自行实现HTTP客户端。

Dubbo

  • 原生支持Java,对其他语言(如Go、Python)的支持需依赖Dubbo3的Triple协议或REST扩展。

  • 跨语言场景需额外开发适配层。

开发体验与生态

Feign

简单易用,通过接口+注解即可定义服务调用(如@FeignClient)。

强依赖Spring Cloud生态,适合Spring Boot项目快速集成。

Dubbo

配置相对复杂,需理解服务提供者/消费者、注册中心等概念。

提供丰富的可视化管理工具(如Dubbo Admin),生态逐渐完善(尤其在阿里系技术栈中)。

适用场景对比

/posts/kd-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%AE%80%E4%BB%8B/images/image-20250908142306922.png

总结

选Feign:若项目基于Spring Cloud,追求简单集成HTTP接口,且对性能要求不是极端苛刻。

选Dubbo:若项目需要高性能RPC、丰富的服务治理能力,且以Java技术栈为主,尤其适合大型分布式系统。

两者并非完全互斥,在混合架构中也可结合使用(如外部API用Feign,内部服务用Dubbo)。