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实现异步调用。

参考资料:

一文带你读懂API网关

浅析如何设计一个亿级网关

--------EOF---------
微信分享/微信扫码阅读