diamond-server 源码

一、diamond-server功能介绍:

161433_FALM_725440.png

    1、提供配置信息的可视化管理(新增,修改,删除)

    2、保证配置服务的可用性:

            a.  提供多节点无中心的集群部署,并使用通知机制保证节点数据一致性;

            b.  借助于配置服务是读多写少的特性,内部使用读内存+本地磁盘文件,写DB的实现方式;

    3、提供diamond-client配置获取和轮训更新的接口

二、diamond-server启动时自动加载运行的类

    1、com.taobao.diamond.server.service.TimerTaskService-定时任务,主要职责是同步DB中的配置信息到本地磁盘文件中。默认是每隔10分钟同步一次。这样可以保证在diamond-client获取配置的时候,可以直接从diamond-server本地文件中取内容。

// 开启定时任务,并通过DumpConfigInfoTask实现DB->缓存同步
@PostConstruct
public void init() {
    this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName(THREAD_NAME);
            t.setDaemon(true);
            return t;
        }
    });
    DumpConfigInfoTask dumpTask = new DumpConfigInfoTask(this);
    dumpTask.run();
    this.scheduledExecutorService.scheduleWithFixedDelay(dumpTask, SystemConfig.getDumpConfigInterval(),
        SystemConfig.getDumpConfigInterval(), TimeUnit.SECONDS);
}

TimerTaskService借助com.taobao.diamond.server.service.DumpConfigInfoTask -> run -> updateConfigInfo来实现同步任务。

// 依次遍历数据库中所有的数据
public void run() {
    try {
        Page<ConfigInfo> page = this.timerTaskService.getPersistService().findAllConfigInfo(1, PAGE_SIZE);
        if (page != null) {
            // 总页数
            int totalPages = page.getPagesAvailable();
            updateConfigInfo(page);
            if (totalPages > 1) {
                for (int pageNo = 2; pageNo <= totalPages; pageNo++) {
                    page = this.timerTaskService.getPersistService().findAllConfigInfo(pageNo, PAGE_SIZE);
                    if (page != null) {
                        updateConfigInfo(page);
                    }
                }
            }
        }
    }
    catch (Throwable t) {
        log.error("dump task run error", t);
    }
}

// 把从DB中查询出的配置信息更新到缓存和磁盘中
private void updateConfigInfo(Page<ConfigInfo> page) throws IOException {
    for (ConfigInfo configInfo : page.getPageItems()) {
        if (configInfo == null) {
            continue;
        }
        try {
            // 更新缓存
            this.timerTaskService.getConfigService().updateMD5Cache(configInfo);
            // 存入磁盘
            this.timerTaskService.getDiskService().saveToDisk(configInfo);
        }
        catch (Throwable t) {
            log.error(
                "dump config info error, dataId=" + configInfo.getDataId() + ", group=" + configInfo.getGroup(), t);
        }
    }
}

    2、com.taobao.diamond.server.service.PersistService-初始化DB链接,提供config在DB的存取功能。
    3、com.taobao.diamond.server.service.NotifyService-通知服务,diamond-server在有数据更改的时候通知其他节点做数据同步操作。首先加载所有的diamond-server节点信息。

// 加载node.properties配置好的diamond-server节点
@PostConstruct
public void loadNodes() {
    InputStream in = null;
    try {
        in = ResourceUtils.getResourceAsStream("node.properties");
        nodeProperties.load(in);
    }
    catch (IOException e) {
        log.error("加载节点配置文件失败");
    }
    finally {
        try {
            if (in != null)
                in.close();
        }
        catch (IOException e) {
            log.error("关闭node.properties失败", e);
        }
    }
    log.info("节点列表:" + nodeProperties);
}

然后diamond-server在配置修改的时候通过调用notifyConfigInfoChange来进行配置信息更改通知。

// 通知配置信息改变
public void notifyConfigInfoChange(String dataId, String group) {
    Enumeration<?> enu = nodeProperties.propertyNames();
    while (enu.hasMoreElements()) {
        String address = (String) enu.nextElement();
        if (address.contains(SystemConfig.LOCAL_IP)) {
            continue;
        }
        String urlString = generateNotifyConfigInfoPath(dataId, group, address);
        final String result = invokeURL(urlString);
        log.info("通知节点" + address + "分组信息改变:" + result);
    }
}
// 生成通知url, 默认是:http://address:port/diamond-server/notify.do?method=notifyConfigInfo
String generateNotifyConfigInfoPath(String dataId, String group, String address) {
    String specialUrl = this.nodeProperties.getProperty(address);
    String urlString = PROTOCOL + address + URL_PREFIX;
    // 如果有指定url,使用指定的url
    if (specialUrl != null && StringUtils.hasLength(specialUrl.trim())) {
        urlString = specialUrl;
    }
    urlString += "?method=notifyConfigInfo&dataId=" + dataId + "&group=" + group;
    return urlString;
}

// http get调用
private String invokeURL(String urlString) {
    HttpURLConnection conn = null;
    URL url = null;
    try {
        url = new URL(urlString);
        conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(TIMEOUT);
        conn.setReadTimeout(TIMEOUT);
        conn.setRequestMethod("GET");
        conn.connect();
        InputStream urlStream = conn.getInputStream();
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(urlStream));
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        }
        finally {
            if (reader != null)
                reader.close();
        }
        return sb.toString();
    }
    catch (Exception e) {
        log.error("http调用失败,url=" + urlString, e);
    }
    finally {
        if (conn != null) {
            conn.disconnect();
        }
    }
    return "error";
}

三、diamond-server新增配置流程(修改删除流程类似)

    1. 提交数据进入com.taobao.diamond.server.controller.AdminController.postConfig

// 新增配置信息,通过configService.addConfigInfo来操作
@RequestMapping(params = "method=postConfig", method = RequestMethod.POST)
public String postConfig(HttpServletRequest request, HttpServletResponse response,
        @RequestParam("dataId") String dataId, @RequestParam("group") String group,
        @RequestParam("content") String content, ModelMap modelMap) {
    response.setCharacterEncoding("GBK");
    boolean checkSuccess = true;
    String errorMessage = "参数错误";
    if (StringUtils.isBlank(dataId) || DiamondUtils.hasInvalidChar(dataId.trim())) {
        checkSuccess = false;
        errorMessage = "无效的DataId";
    }
    if (StringUtils.isBlank(group) || DiamondUtils.hasInvalidChar(group.trim())) {
        checkSuccess = false;
        errorMessage = "无效的分组";
    }
    if (StringUtils.isBlank(content)) {
        checkSuccess = false;
        errorMessage = "无效的内容";
    }
    if (!checkSuccess) {
        modelMap.addAttribute("message", errorMessage);
        return "/admin/config/new";
    }
    dataId = dataId.trim();
    group = group.trim();
    this.configService.addConfigInfo(dataId, group, content);
    modelMap.addAttribute("message", "提交成功!");
    return listConfig(request, response, dataId, group, 1, 20, modelMap);
}

    2. 进入com.taobao.diamond.server.service.ConfigService,新增顺序是:存DB -> 更新缓存 -> 存磁盘文件 -> 通知其他diamond-server节点

// 更新配置信息
public void updateConfigInfo(String dataId, String group, String content) {
    checkParameter(dataId, group, content);
    ConfigInfo configInfo = new ConfigInfo(dataId, group, content);
    // 先更新数据库,再更新磁盘
    try {
        persistService.updateConfigInfo(configInfo);
        // 切记更新缓存
        this.contentMD5Cache.put(generateMD5CacheKey(dataId, group), configInfo.getMd5());
        // 存磁盘文件
        diskService.saveToDisk(configInfo);
        // 通知其他节点
        this.notifyOtherNodes(dataId, group);
    }
    catch (Exception e) {
        log.error("保存ConfigInfo失败", e);
        throw new ConfigServiceException(e);
    }
}

     3. 通知其他diamond-server节点配置信息有变更是通过http调用NotifyController.notifyConfigInfo方法实现的。

// 通知配置信息改变
@RequestMapping(method = RequestMethod.GET, params = "method=notifyConfigInfo")
public String notifyConfigInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group) {
    dataId = dataId.trim();
    group = group.trim();
    this.configService.loadConfigInfoToDisk(dataId, group);
    return "200";
}

四、给diamond-client提供配置信息接口

    1、获取配置接口:com.taobao.diamond.server.controller.ConfigController.getConfig

// diamond-client请求该方法获取配置信息
public String getConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group) {
    response.setHeader("Content-Type", "text/html;charset=GBK");
    final String address = getRemortIP(request);
    if (address == null) {
        // 未找到远端地址,返回400错误
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        return "400";
    }
    if (GlobalCounter.getCounter().decrementAndGet() >= 0) {
        response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
        return "503";
    }
    String md5 = this.configService.getContentMD5(dataId, group);
    if (md5 == null) {
        return "404";
    }
    response.setHeader(Constants.CONTENT_MD5, md5);
    // 正在被修改,返回304,这里的检查并没有办法保证一致性,因此做double-check尽力保证
    if (diskService.isModified(dataId, group)) {
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        return "304";
    }
    // 从缓存中查询配置信息
    String path = configService.getConfigInfoPath(dataId, group);
    // 再次检查
    if (diskService.isModified(dataId, group)) {
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        return "304";
    }
    // 禁用缓存
    response.setHeader("Pragma", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setHeader("Cache-Control", "no-cache,no-store");
    return "forward:" + path;
}

    2、轮训配置是否更新:com.taobao.diamond.server.controller.ConfigController. getProbeModifyResult 

// 接收 dataId+groupId+contentMD5 集合,判断返回配置信息是否更改
public String getProbeModifyResult(HttpServletRequest request, HttpServletResponse response, String probeModify) {
    response.setHeader("Content-Type", "text/html;charset=GBK");
    final String address = getRemortIP(request);
    if (address == null) {
        // 未找到远端地址,返回400错误
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        return "400";
    }
    if (GlobalCounter.getCounter().decrementAndGet() >= 0) {
        response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
        return "503";
    }
    final List<ConfigKey> configKeyList = getConfigKeyList(probeModify);
    StringBuilder resultBuilder = new StringBuilder();
    for (ConfigKey key : configKeyList) {
        String md5 = this.configService.getContentMD5(key.getDataId(), key.getGroup());
        if (!StringUtils.equals(md5, key.getMd5())) {
            resultBuilder.append(key.getDataId()).append(WORD_SEPARATOR).append(key.getGroup())
                .append(LINE_SEPARATOR);
        }
    }
    String returnHeader = resultBuilder.toString();
    try {
        returnHeader = URLEncoder.encode(resultBuilder.toString(), "UTF-8");
    }
    catch (Exception e) {
        // ignore
    }
    request.setAttribute("content", returnHeader);
    // 禁用缓存
    response.setHeader("Pragma", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setHeader("Cache-Control", "no-cache,no-store");
    return "200";
}

 

转载于:https://my.oschina.net/yunna/blog/1637895

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值