淘寶動態配置diamond-server 原始碼講解
1.diamond-server工程裡 使用了我們常用的一些技術 比如spring springmvc jsbctamplate 等等這些技術
jar包如下
2.首先我們先從啟動tomcat的時候 載入bean的時候 會有三個bean進行初始化資料的bean開始分析
2.1載入diamond-server其他例項的ip地址和埠號
com.taobao.diamond.server.service.NotifyService
@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); }
node配置檔案裡面的內容(在本地部署兩個tomcat例項)
A=http://10.144.35.250:8080/diamond-server/notify.do
B=http://10.144.35.250:9090/diamond-server1/notify.do
2.2載入資料庫資訊,建立BasicDataSource,JdbcTemplate物件
com.taobao.diamond.server.service.PersistService
@PostConstruct public void initDataSource() throws Exception { // 讀取jdbc.properties配置, 載入資料來源 Properties props = ResourceUtils.getResourceAsProperties("jdbc.properties"); BasicDataSource ds = new BasicDataSource(); ds.setDriverClassName(JDBC_DRIVER_NAME); ds.setUrl(ensurePropValueNotNull(props.getProperty("db.url"))); ds.setUsername(ensurePropValueNotNull(props.getProperty("db.user"))); ds.setPassword(ensurePropValueNotNull(props.getProperty("db.password"))); ds.setInitialSize(Integer.parseInt(ensurePropValueNotNull(props.getProperty("db.initialSize")))); ds.setMaxActive(Integer.parseInt(ensurePropValueNotNull(props.getProperty("db.maxActive")))); ds.setMaxIdle(Integer.parseInt(ensurePropValueNotNull(props.getProperty("db.maxIdle")))); ds.setMaxWait(Long.parseLong(ensurePropValueNotNull(props.getProperty("db.maxWait")))); ds.setPoolPreparedStatements(Boolean.parseBoolean(ensurePropValueNotNull(props .getProperty("db.poolPreparedStatements")))); this.jt = new JdbcTemplate(); this.jt.setDataSource(ds); // 設定最大記錄數,防止記憶體膨脹 this.jt.setMaxRows(MAX_ROWS); // 設定JDBC執行超時時間 this.jt.setQueryTimeout(QUERY_TIMEOUT); }
2.3 定時任務服務(每600s 從資料庫拉取資料到本地磁碟和本地快取)
com.taobao.diamond.server.service.TimerTaskService
@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); }
3.以新增一條資料走一遍diamond-server的流程
3.1 新增資料的頁面
3.2提交資料進入controller
com.taobao.diamond.server.controller.AdminController
@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();
<span style="color:#FF0000;">this.configService.addConfigInfo(dataId, group, content);</span>
modelMap.addAttribute("message", "提交成功!");
return listConfig(request, response, dataId, group, 1, 20, modelMap);
}
3.3進入新增資料的service層
com.taobao.diamond.server.service.ConfigService
public void addConfigInfo(String dataId, String group, String content) {
checkParameter(dataId, group, content);
ConfigInfo configInfo = new ConfigInfo(dataId, group, content);
// 儲存順序:先資料庫,再磁碟
try {
persistService.addConfigInfo(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);
}
}
到此為止,已經把資料新增到資料裡,並且其他diamond-server例項也已經更新到最新的資料。
4.我們現在看看diamond是如何通知到其他的diamond例項的,如果通知不到其他diamond例項的話,該如何同步
4.1 通知其他的diamond例項
我們是通過node配置檔案進行配置的
現在我們看一下程式碼
com.taobao.diamond.server.service.NotifyService
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);
}
}
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呼叫
*
* @param urlString
* @return
*/
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) {
e.printStackTrace();
log.error("http呼叫失敗,url=" + urlString, e);
}
finally {
if (conn != null) {
conn.disconnect();
}
}
return "error";
}
4.2 如果通過http請求並沒有通知到其他例項,那該如何同步資料。
diamond本身有個定時任務進行查詢資料庫
現在我們看一下程式碼
com.taobao.diamond.server.service.TimerTaskService
@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);
}
到這diamond-server基本的業務就差不多了,對於更改資料和刪除資料這些跟新增資料也是一樣業務,必須得同步資料。
儲存到磁碟的業務是一個組一個資料夾,組資料夾相應下面是儲存的不同的dataId,一個dataId是一個檔案。
希望關於diamond-server的內容 對大家有所幫助。