API网关学习
API网关作用:
- 后端多服务的整合、服务编排;
- 解决跨域问题;
- 统一鉴权;
- 流控;
- 降级熔断;
- 监控告警
这里首先要说,如果业务量不是特别大的情况,是完全没必要使用API网关的,一般的功能nginx都能实现,比如负载均衡、限流、解决跨域等问题。只是当公司系统规模较大,比如微服务较多,后端接口多且复杂,流量较大的情况,才会考虑使用API网关。目前较大的公司都会采用,我们有品最初也没用,只是后来规模不断变大,才开始使用了API网关。
目前常见的开源网关大致上按照语言分类有如下几类。
-
Nginx+Lua: Open Resty 、 Kong 、Orange、Abtesting Gateway、 Tengine 等;
-
Java: Zuul/Zuul 2 、 Spring Cloud Gateway 、Kaazing KWG、gravitee、 Dromara soul 等;
-
Go:Janus、fagongzi、Grpc-Gateway;
我就是本着一个学习的目的,可以实现简单的网关。
一个基本的API网关要具备下面的功能(来自网络):
要完成的工作:
1、实现WebSocket服务器;
2、协议转换;一般是http转换为dubbo,grpc等等。
3、路由发现和下线;如何自动发现服务。比如nacos上有dubbo,怎么才能转换为http. ip
4、统一鉴权;
5、风控;
6、缓存;
Step:
1.完成统一鉴权;
2.http,dubbo协议的调用;
3.路由动态配置
----2020-09-04----
今天实现了Dubbo的泛化调用,其实真的很简单,在Dubbo官网中已经有例子,直接用Dubbo提供的GenericSerivce即可。Dubbo官网的例子: http://dubbo.apache.org/zh-cn/blog/dubbo-generic-invoke.html
先拿一个简单的例子:
@Component
public class GwDubboConfigCache {
@Autowired
private RegistryConfig registryConfig;
@Autowired
private ApplicationConfig applicationConfig;
private ConcurrentHashMap<String,ReferenceConfig<GenericService>> referenceConfigConcurrentHashMap = new ConcurrentHashMap<>();
//TODO dubbo缓存
private static final KeyGenerator DEFAULT_KEY_GENERATOR = new KeyGenerator() {
@Override
public String generateKey(String iName, String group, String version) {
StringBuilder ret = new StringBuilder();
if (!StringUtils.isBlank(group)) {
ret.append(group).append("/");
}
ret.append(iName);
if (!StringUtils.isBlank(version)) {
ret.append(":").append(version);
}
return ret.toString();
}
};
/**
* 获取 Dubbo GenericService
* @param apiInfo
* @return GenericService
*/
public GenericService get(ApiInfo apiInfo) {
// return null;
String key = DEFAULT_KEY_GENERATOR.generateKey(
apiInfo.getDubboInfo().getServiceName(),
apiInfo.getDubboInfo().getGroup(),
apiInfo.getDubboInfo().getVerison()
);
ReferenceConfig<GenericService> genericServiceReferenceConfig = new ReferenceConfig<GenericService>();
genericServiceReferenceConfig = referenceConfigConcurrentHashMap.get(key);
if(Optional.ofNullable(genericServiceReferenceConfig).isPresent()){
return genericServiceReferenceConfig.get();
}
//先尝试从缓存中获取。
genericServiceReferenceConfig = this.genericServiceReferenceConfig(apiInfo);
//存储到缓存中
referenceConfigConcurrentHashMap.putIfAbsent(key,genericServiceReferenceConfig);
return genericServiceReferenceConfig.get();
}
private ReferenceConfig<GenericService> genericServiceReferenceConfig(ApiInfo apiInfo){
log.info("start to create new reference .apiinf:{}",apiInfo);
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setApplication(applicationConfig);
referenceConfig.setRegistry(registryConfig);
referenceConfig.setInterface(apiInfo.getDubboInfo().getServiceName());
referenceConfig.setProtocol(apiInfo.getDubboInfo().getProtocol());
referenceConfig.setGeneric(true);
referenceConfig.setVersion(apiInfo.getDubboInfo().getVerison());
referenceConfig.setLoadbalance("roundrobin");
log.info("co:{},e:{},{}",referenceConfig,referenceConfig.get(),referenceConfig.getProtocol());
return referenceConfig;
}
public static String generateCachekey(String iName,String group,String version){
StringBuilder ret = new StringBuilder();
if (!StringUtils.isBlank(group)) {
ret.append(group).append("/");
}
ret.append(iName);
if (!StringUtils.isBlank(version)) {
ret.append(":").append(version);
}
return ret.toString();
}
public interface KeyGenerator {
String generateKey(String iName,String group,String version);
}
}
调用:
@Component
public class DubboHandler {
@Autowired
private GwDubboConfigCache gwDubboConfigCache;
public Object call(ApiInfo apiInfo, GatewayContext gatewayContext){
String requestData = gatewayContext.getReqBody();
String methodName = apiInfo.getDubboInfo().getMethodName();
GenericService genericService = gwDubboConfigCache.get(apiInfo);
Pair<String[],Object[]> paramPair;
if(apiInfo.getParameterTypes() == null || apiInfo.getParameterTypes().isEmpty()){
paramPair = new ImmutablePair<>(new String[]{},new Object[]{});
}else {
if(requestData == null || requestData.isEmpty()){
throw new BussinessException(101,"请求参数异常");
}
paramPair = this.buildDubboParam(requestData,apiInfo.getParameterTypes());
if(! Objects.isNull(apiInfo.getNeedAuth()) && apiInfo.getNeedAuth()){
Map m = (Map) paramPair.getValue()[0];
m.put("uid",gatewayContext.getUid());
}
}
try {
//一个同步,一个异步。后续必须要换成异步
Object response = genericService.$invoke(methodName,paramPair.getLeft(),paramPair.getRight());
// Object response = genericService.$invokeAsync(methodName,paramterTypes,args);
log.info("get original dubbo response from dubbo service:{},ret:{}",methodName,response);
return response;
}catch (GenericException exception){
log.error("get generic exception,apiinfo:{},method:{},exception:{}",apiInfo,methodName,exception);
throw new BussinessException(1001,exception.getExceptionMessage());
}catch (Throwable throwable){
log.error("get exception,apiinfo:{},method:{},exception:{}",apiInfo,methodName,throwable);
throw throwable;
}
}
private Pair<String[],Object[]> buildDubboParam(String requestData,String paramTypes){
log.info("start to build dubbo param,request:{},paramtype:{}",requestData,paramTypes);
String[] paramTypeList = StringUtils.split(paramTypes,",");
Map<String,Object> valueMap = GsonUtils.getInstance().toObjectMap(requestData);
log.info("start to build dubbo param,request:{},paramtype:{},map:{}",requestData,paramTypes,valueMap);
//就算是一个参数,也要用map的类型传参,否则报错
if (paramTypeList.length == 1 && !isBaseType(paramTypeList[0])) {
for (String key : valueMap.keySet()) {
Object obj = valueMap.get(key);
if (obj instanceof JsonObject) {
valueMap.put(key, GsonUtils.getInstance().convertToMap(obj.toString()));
} else if (obj instanceof JsonArray) {
valueMap.put(key, GsonUtils.getInstance().fromList(obj.toString(), Object.class));
} else {
valueMap.put(key, obj);
}
}
return new ImmutablePair<>(paramTypeList, new Object[]{valueMap});
}
List<Object> objectList = new ArrayList<>();
for (String key:valueMap.keySet()){
Object cuObj = valueMap.get(key);
if(cuObj instanceof JsonObject){
objectList.add(GsonUtils.getInstance().toObjectMap(cuObj.toString()));
}else if (cuObj instanceof JsonArray){
objectList.add(GsonUtils.getInstance().fromList(cuObj.toString(),Object.class));
}else {
objectList.add(cuObj);
}
}
log.info("build param complete,request:{},paramType:{},buildParam:{}",requestData,paramTypes,objectList);
return new ImmutablePair<>(paramTypeList,objectList.toArray());
}
private boolean isBaseType(final String paramType) {
return paramType.startsWith("java") || paramType.startsWith("[Ljava");
}
So easy!主要还是设计好前端的http到Dubbo的转换。最初API网关先实现同步调用,后期还要使用异步调用。
目前我还是使用springboot的filter来实现一些前置功能。响应也是同步处理的。
下一步是使用Netty实现异步调用。
参考资料:
微信分享/微信扫码阅读