API架构解析二 项目的注册与发现

前言

  • 在API架构解析一(项目的启动和加载)中已经介绍了如何构造API的静态存储单元ApiModel和API的执行轨迹的接口Api以及其实现类ApiHandler,同时介绍了如何构造拦截器调用链并把ApiHandler封装在InterceptChain的最后一层,通过反射完成最后的业务逻辑调用。

注册项目到zk

  1. 为了使调用方发现自己,API项目在启动的时候需要将项目的基本信息,包括API名称,版本号,IP地址等信息注册到Zookeeper上,利用zk的注册与发现机制调用方可以很容易找到对应版本的API并访问。
  2. 首先需要搭建zk环境,可以参考介绍zk的技术文章,这不是本文的重点,搭建好集群后,可以拿到zk的ZkSessionManager,具体如下。
private static ZkSessionManager getZkMgr() {
        ZkSessionManager zkSessionManager = ZkUtils.getInstance(zkConf.getServer(), zkConf.getTimeout() + "");
        zkSessionManager.addConnectionListener(new ApiZkListener());
        return zkSessionManager;
    }
  • 其中zkConf是API项目中的配置文件,主要配置了zk集群的ip地址,连接超时时间以及通用的注册路径等。
  • ApiZkListener是ConnectionListener接口的实现类,主要用来监听与zk集群的连接情况,最主要的是重写ConnectionListener接口的expired()方法用来保活,具体如下。
public class ApiZkListener implements ConnectionListener {
    private static final Logger logger = LoggerFactory.getLogger(ApiZkListener.class);

    @Override
    public void expired() {
        logger.error("***ApiZkListener expired");
        reconnect();
    }

    @Override
    public void disconnected() {
        logger.error("***ApiZkListener disconnected");
        reconnect();
    }

    private void reconnect() {
        try {
            logger.error("让ZK服务端API机器列表刷新");
            Thread.sleep(10000l);
            if (!RegisterMgr.existsApi()) {
                RegisterMgr.regApiNode();
                logger.error("API机器重连完成");
            } else {
                logger.error("ZK服务端并未注销本机器,或者watcher已生效,结束本次尝试");
            }
        } catch (Throwable throwable) {
            logger.error("ZK reconnect失败", throwable);
        }
    }

    @Override
    public void syncConnected() {
        logger.error("***ApiZkListener syncConnected");
    }
}
  1. 把项目信息如API名称,版本号等(放在项目的配置文件中),以EPHEMERAL临时节点模式注册到zk上,核心代码如下。
private static final ZkSessionManager zkMgr = getZkMgr();
private static volatile ZooKeeper zooKeeper;
private static volatile boolean over = false;
private static final Lock lock = new ReentrantLock();

public static boolean regApiNode() {
        boolean flag = false;
        lock.lock();
        try {
            if (over) {
                logger.error("项目已手动结束 无需注册zk");
                return true;
            }
            ApiConfig config = ApiService.getApiConfig();
            if (config != null && config.isRegisterToZk()) {
                String path = getCurApiPath();
                ZooKeeper zk = getZooKeeper();
                try {
                    zk.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                    zk.getData(path, watcher, null);
                    flag = true;
                    logger.error("注册zk success path=" + path);
                } catch (KeeperException e) {
                    if (e instanceof KeeperException.NoNodeException) {
                        logger.error("注册zk error path:" + path, e);
                    } else if (e instanceof KeeperException.NodeExistsException) {
                        logger.error("注册zk 重复");
                        flag = true;
                    } else {
                        logger.error("注册zk error 重试 path:" + path, e);
                        reloadZooKeeper();
                        regApiNode();
                    }
                } catch (InterruptedException e) {
                    logger.error("注册zk error path:" + path, e);
                }
            } else {
                flag = true;
                logger.error("zk无需注册");
            }
        } finally {
            lock.unlock();
        }
        return flag;
    }
    
    private static final Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            regApiNode();
        }
    };
  • 加锁是为了确保一台服务器只注册一次。
  • zk.getData(path, watcher, null)添加watcher事件,监听节点的变化。
  1. 到此为止,每个API项目在启动的时候就可以把自己的项目信息注册到zk上了,假设注册路径为mist/apilist,注册结构如下mapi_100_192.168.100.100

利用代理发现API,保证服务对用户的透明

  • 真实系统中,可能会存在多套API并存同时对外提供服务的情况,比如给移动端提供的API,给公司内部其他业务线提供的API,开放出去给第三方合作公司提供的API等等,为了统一管理可以在API接口层的前面提供一个proxy代理。
  • 利用proxy代理可以给用户提供透明的API接口服务,用户按照约定请求即可,proxy负责将请求路由纷发到具体的API,为此需要给每个API关联一个标识用来区分,并且将这个标识映射成用户的key值,提供给用户,用户发起请求时会带着自己的userKey这样就可以区分了。
  • 有些API比如提供给移动端的API可能要兼容多个版本,因此proxy路由的粒度要到版本这个级别,因为同一个API不同版本的逻辑可能是不同的,对于有多版本需求的客户端请求的时候需要加上对应的版本号,同时配置一个默认的版本号处理固定版本的请求。

Proxy的路由和分发

  1. 项目启动的时候从zk上加载各API信息,核心代码如下
private static final ZkSessionManager zkMgr = ZkUtils.getInstance(config.getServer(), config.getTimeout() + "");
private static ZooKeeper zk = zkMgr.getZooKeeper();

private static void loadApiServer() {
        try {
            List<String> list = zk.getChildren(config.getZkPath(), watcher);
            doLoadApiServer(list);
        } catch (KeeperException e) {
            if (e instanceof KeeperException.NoNodeException) {
                logger.error("请检查目录 path=" + config.getZkPath(), e);
            } else {
                zk = ZkUtils.ZK_SESSION_MANAGER.getZooKeeper();
                logger.error("loadServer异常 休眠50ms", e);
                ThreadUtil.sleep(50);
                loadApiServer();
            }
        } catch (Exception e) {
            logger.error("加载zk失败", e);
        }
    }
    
 private static final Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            if (event.getType() == Event.EventType.None) {
                zk = zkMgr.getZooKeeper();
            }
            loadApiServer();
        }
    };
  • 其中config.getZkPath()从配置文件读取,即为API注册的路径mist/apilist,watcher用于注册监听事件。
  • doLoadApiServer(list)核心代码如下
private static volatile Map<String, ApiServerMap> apiServerListMap = new HashMap<String, ApiServerMap>();

private static void doLoadApiServer(List<String> list) {
        Map<String, ApiServerModel> apiMap = CACHE.getByTime().getApiServerModelMap();   //从DB加载配置数据
        Map<String, ApiServerMap> map = new HashMap<String, ApiServerMap>(apiMap.size());
        for (String apiNameDesc : list) {
            ApiVersionModel model = buildApiServerModel(apiNameDesc);  
            if (model != null) {
                ApiServerMap tempMap = map.get(model.getName());
                if (tempMap == null) {
                    tempMap = new ApiServerMap();
                    map.put(model.getName(), tempMap);
                }
                tempMap.putApiModel(model); //按版本聚集服务器,并维护最新版本
            }
        }
        apiServerListMap = map;
        logger.error("zk列表=" + list + ",apiServerListMap=" + JSONUtil.json(map));
    }
  • CACHE中主要维护API服务的信息和用户注册的信息这两个纬度,从数据库中读取。
public class ApiServerModel {
    private String name;                    //API名称
    private String invokeServiceId;         //执行服务,支持RPC调用
    private String infoServiceId;           //查询API提供接口集合的信息服务
    private Integer accessType;
}


public class ChannelModel {
    private String name;                //用户注册的名称
    private Integer valid;              //
    private String code;                //用户的userKey
    private String desc;                
    private String apiName;             //对应的具体api
    private String secretKey;           //加密的密钥
    private Integer encryptType;        //加密粒度,是否加密
    private Integer skipSign;           //跳过签名验证
}
  • ApiVersionModel中存储的就是API注册搭配zk上的基本信息mapi_100_192.168.100.100,结构如下,buildApiServerModel()将字符串分割转换成ApiVersionModel类
class ApiVersionModel {
    private String name;        //api名称
    private Integer version;    //api版本号
    private String ip;          //服务器ip地址
}
  • ApiServerMap这个数据结构中维护着按版本聚集的对应api服务机器的关系,因为同一API的同一版本一般也是由多台机器部署来对外提供服务。其结核心代码如下
public class ApiServerMap {
    private Integer newestVersion;
    private final Map<Integer, List<ApiVersionModel>> versionToApiModels = new HashMap<Integer, List<ApiVersionModel>>(32);

    public Integer getNewestVersion() {
        return newestVersion;
    }

    public List<ApiVersionModel> getApiModels(Integer version) {
        return versionToApiModels.get(version);
    }

    public List<ApiVersionModel> getNewestApiModels() {
        return versionToApiModels.get(newestVersion);
    }

    public void putApiModel(ApiVersionModel model) {
        List<ApiVersionModel> list = versionToApiModels.get(model.getVersion());
        if (list == null) {
            list = new ArrayList<ApiVersionModel>(32);
            versionToApiModels.put(model.getVersion(), list);
        }
        list.add(model);
        if (newestVersion == null || newestVersion < model.getVersion()) {
            newestVersion = model.getVersion();
        }
    }
}

小结

  • 至此项目的注册与发现的大体流程就介绍完了,API在启动的时候会将自身的基本信息注册到ZK集群上,proxy代理启动的时候会从zk上搂取各API的信息,完成转发和路由。
  • 下面将会介绍客户端发起一次请求的详细流转步骤。

转载于:https://my.oschina.net/wangyusheng/blog/1541713

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值