diamond主要包含四个包:diamond-client、diamond-sdk、diamond-server和diamond-util

client就非常简单的进行http的调用server拿数据

server查数据库返回给client

其中server是集群

当一台server服务接收到写请求的时候:

1、先写数据库;

2、更新缓存(hashmap)

3、再写磁盘;

4、通知其他节点的服务器http

diamond的消息传输是通过http长链接来传输的。

diamond-client读取配置的context的时候是通过 DataId和GroupId来查询,本地Context是存在本地服务器文件的。文件路径根据一定规则来配置。

private Map<String, DiamondManager> dmMap = new HashMap<>();

	private DiamondManager getDiamondManager(String dataId, String group) {
String key = dataId + "#_#" + group;
DiamondManager dm = dmMap.get(key);
if (dm == null) {
dm = new DefaultDiamondManager(group, dataId, new ArrayList<ManagerListener>());
dmMap.put(key, dm);
}
return dm;
}

获取远程服务器列表并保持到本地

 protected void synAcquireServerAddress() {
if (!isRun) {
throw new RuntimeException("ServerAddressProcessor不在运行状态,无法同步获取服务器地址列表");
}
if (MockServer.isTestMode()) {
diamondConfigure.addDomainName("测试模式,没有使用的真实服务器");
return;
} int acquireCount = 0;
if (diamondConfigure.getDomainNameList().size() == 0) {
if (!acquireServerAddressOnce(acquireCount)) {
acquireCount++;
if (acquireServerAddressOnce(acquireCount)) {
// 存入本地文件
storeServerAddressesToLocal();
log.info("在同步获取服务器列表时,向日常ConfigServer服务器获取到了服务器列表");
}
else {
log.info("从本地获取Diamond地址列表");
reloadServerAddresses();
if (diamondConfigure.getDomainNameList().size() == 0)
throw new RuntimeException("当前没有可用的服务器列表,请检查~/diamond/ServerAddress文件");
}
}
else {
log.info("在同步获取服务器列表时,向线上ConfigServer服务器获取到了服务器列表");
// 存入本地文件
storeServerAddressesToLocal();
}
}
}

如果在本地缓存map找不到就执行远程查找,这个代码是有一定问题的,应该是先从本地服务器找,新版已经解决了这个问题

/**
* 使用指定的集群类型clusterType
*
* @param group
* @param dataId
* @param managerListenerList
* @param clusterType
*/
public DefaultDiamondManager(String group, String dataId, List<ManagerListener> managerListenerList) {
this.dataId = dataId;
this.group = group; diamondSubscriber = DiamondClientFactory.getSingletonDiamondSubscriber(); this.managerListeners.addAll(managerListenerList);
((DefaultSubscriberListener) diamondSubscriber.getSubscriberListener()).addManagerListeners(this.dataId,
this.group, this.managerListeners);
diamondSubscriber.addDataId(this.dataId, this.group);
diamondSubscriber.start();
}

接下来看这个订阅者start是如何执行的

 /**
* 启动DiamondSubscriber:<br>
* 1.阻塞主动获取所有的DataId配置信息<br>
* 2.启动定时线程定时获取所有的DataId配置信息<br>
*/
public synchronized void start() {
if (isRun) {
return;
} if (null == scheduledExecutor || scheduledExecutor.isTerminated()) {
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
} localConfigInfoProcessor.start(this.diamondConfigure.getFilePath() + "/" + DATA_DIR);
serverAddressProcessor = new ServerAddressProcessor(this.diamondConfigure, this.scheduledExecutor);
serverAddressProcessor.start(); this.snapshotConfigInfoProcessor =
new SnapshotConfigInfoProcessor(this.diamondConfigure.getFilePath() + "/" + SNAPSHOT_DIR);
// 设置domainNamePos值
randomDomainNamePos();
initHttpClient(); // 初始化完毕
isRun = true; if (log.isInfoEnabled()) {
log.info("当前使用的域名有:" + this.diamondConfigure.getDomainNameList());
} if (MockServer.isTestMode()) {
bFirstCheck = false;
}
else {
// 设置轮询间隔时间
this.diamondConfigure.setPollingIntervalTime(Constants.POLLING_INTERVAL_TIME);
}
// 轮询
rotateCheckConfigInfo(); addShutdownHook();
}

我们重点看下 serverAddressProcessor.start();这个有点像继承runable,然后执行run,我们看下具体逻辑

 public synchronized void start() {
if (isRun) {
return;
}
isRun = true;
initHttpClient();
if (this.diamondConfigure.isLocalFirst()) {
acquireServerAddressFromLocal();
}
else {
synAcquireServerAddress();
asynAcquireServerAddress();
} }

我们可以看到判断是否先从本地服务器获取,这个我上面说过了,新版的逻辑已经改掉了,信保就不适用hashMap作为缓存,的确hashMap容易被jvm回收,也占用内存空间。那么我们看下synACquireServerAddress方法


protected void synAcquireServerAddress() {
if (!isRun) {
throw new RuntimeException("ServerAddressProcessor不在运行状态,无法同步获取服务器地址列表");
}
if (MockServer.isTestMode()) {
diamondConfigure.addDomainName("测试模式,没有使用的真实服务器");
return;
} int acquireCount = 0;
if (diamondConfigure.getDomainNameList().size() == 0) {
if (!acquireServerAddressOnce(acquireCount)) {
acquireCount++;
if (acquireServerAddressOnce(acquireCount)) {
// 存入本地文件
storeServerAddressesToLocal();
log.info("在同步获取服务器列表时,向日常ConfigServer服务器获取到了服务器列表");
}
else {
log.info("从本地获取Diamond地址列表");
reloadServerAddresses();
if (diamondConfigure.getDomainNameList().size() == 0)
throw new RuntimeException("当前没有可用的服务器列表,请检查~/diamond/ServerAddress文件");
}
}
else {
log.info("在同步获取服务器列表时,向线上ConfigServer服务器获取到了服务器列表");
// 存入本地文件
storeServerAddressesToLocal();
}
}
}

我们看diamand是怎么获取服务器列表的

/**
* 获取diamond服务器地址列表
*
* @param acquireCount
* 根据0或1决定从日常或线上获取
* @return
*/
private boolean acquireServerAddressOnce(int acquireCount) {
HostConfiguration hostConfiguration = configHttpClient.getHostConfiguration();
String configServerAddress;
int port;
if (null != diamondConfigure.getConfigServerAddress()) {
configServerAddress = diamondConfigure.getConfigServerAddress();
port = diamondConfigure.getConfigServerPort();
}
else {
if (acquireCount == 0) {
configServerAddress = Constants.DEFAULT_DOMAINNAME;
port = Constants.DEFAULT_PORT;
}
else {
configServerAddress = Constants.DAILY_DOMAINNAME;
port = Constants.DEFAULT_PORT;
}
}
hostConfiguration.setHost(configServerAddress, port); String serverAddressUrl = Constants.CONFIG_HTTP_URI_FILE; HttpMethod httpMethod = new GetMethod(serverAddressUrl);
// 设置HttpMethod的参数
HttpMethodParams params = new HttpMethodParams();
params.setSoTimeout(diamondConfigure.getOnceTimeout());
// ///////////////////////
httpMethod.setParams(params); try {
if (SC_OK == configHttpClient.executeMethod(httpMethod)) {
InputStreamReader reader = new InputStreamReader(httpMethod.getResponseBodyAsStream());
BufferedReader bufferedReader = new BufferedReader(reader);
String address = null;
List<String> newDomainNameList = new LinkedList<String>();
while ((address = bufferedReader.readLine()) != null) {
address = address.trim();
if (StringUtils.isNotBlank(address)) {
newDomainNameList.add(address);
}
}
if (newDomainNameList.size() > 0) {
log.debug("更新使用的服务器列表");
this.diamondConfigure.setDomainNameList(newDomainNameList);
return true;
}
}
else {
log.warn("没有可用的新服务器列表");
}
}
catch (HttpException e) {
log.error(getErrorMessage(configServerAddress) + ", " + e);
}
catch (IOException e) {
log.error(getErrorMessage(configServerAddress) + ", " + e);
}
catch (Exception e) {
log.error(getErrorMessage(configServerAddress) + ", " + e);
}
finally {
httpMethod.releaseConnection();
}
return false;
}

思路简单清晰,但是这边有一个设计思想就是订阅者DiamondSubscriber和监听SubscriberListener的思想是值得研究下,我们研究下他们是怎么关联起来的。

/**
* Diamond订阅者的配置信息监听器
*
* @author aoqiong
*
*/
public interface SubscriberListener { public Executor getExecutor(); /**
* 接收到一次配置信息
*
* @param configureInfomation
*/
public void receiveConfigInfo(final ConfigureInfomation configureInfomation);
}

这个监听器只有一个默认的实现DefaultSubscriberListener

public class DefaultSubscriberListener implements SubscriberListener {

    // 回调日志单独记录
private static final Log dataLog = LogFactory.getLog(LoggerInit.LOG_NAME_CONFIG_DATA); private final ConcurrentMap<String/* dataId + group */, CopyOnWriteArrayList<ManagerListener>/* listeners */> allListeners =
new ConcurrentHashMap<String, CopyOnWriteArrayList<ManagerListener>>(); public Executor getExecutor() {
return null;
}

那么这个DefaultSubscriberListener和DefaultDiamondSubscriber有什么关系呢?如我们上面的代码,为了方便我这边重写贴下:

 /**
* 使用指定的集群类型clusterType
*
* @param group
* @param dataId
* @param managerListenerList
* @param clusterType
*/
public DefaultDiamondManager(String group, String dataId, List<ManagerListener> managerListenerList) {
this.dataId = dataId;
this.group = group; diamondSubscriber = DiamondClientFactory.getSingletonDiamondSubscriber(); this.managerListeners.addAll(managerListenerList);
((DefaultSubscriberListener) diamondSubscriber.getSubscriberListener()).addManagerListeners(this.dataId,
this.group, this.managerListeners);
diamondSubscriber.addDataId(this.dataId, this.group);
diamondSubscriber.start();
}

我们是通过diamondSubscriber的start去获取服务器列表的数据的,但是在start之前的addManagerListeners是干嘛的呢?原因很简单,那就是跟这个diamondSubscriber增加一个listeners的监听。

this.managerListeners.addAll(managerListenerList);
((DefaultSubscriberListener) diamondSubscriber.getSubscriberListener()).addManagerListeners(this.dataId,
this.group, this.managerListeners);

diamondSubscriber 是通过DiamondClientFactory.getSingletonDiamondSubscriber()获取的,其实他就是一个DefaultDiamondSubscriber类,那么就是这个监听就是加到这个DefaultDiamondSubscriber类上的,这个监听其实就是DefaultSubscriberListener。

public class DiamondClientFactory {

    private static DiamondSubscriber diamondSubscriber = new DefaultDiamondSubscriber(new DefaultSubscriberListener());

    public static DiamondSubscriber getSingletonDiamondSubscriber() {
return diamondSubscriber;
} }

以上我们是从一个client的角度解析diamand的设计思想和逻辑处理,思想其实不是很复杂,但是订阅监听的设计思想是亮点,本文也重点讲了订阅和监听,已经如何获取服务器列表的方法。

diamond的设计思路的更多相关文章

  1. TYPESDK手游聚合SDK服务端设计思路与架构之一:应用场景分析

    TYPESDK 服务端设计思路与架构之一:应用场景分析 作为一个渠道SDK统一接入框架,TYPESDK从一开始,所面对的需求场景就是多款游戏,通过一个统一的SDK服务端,能够同时接入几十个甚至几百个各 ...

  2. 分享一个CQRS/ES架构中基于写文件的EventStore的设计思路

    最近打算用C#实现一个基于文件的EventStore. 什么是EventStore 关于什么是EventStore,如果还不清楚的朋友可以去了解下CQRS/Event Sourcing这种架构,我博客 ...

  3. ENode框架单台机器在处理Command时的设计思路

    设计目标 尽量快的处理命令和事件,保证吞吐量: 处理完一个命令后不需要等待命令产生的事件持久化完成就能处理下一个命令,从而保证领域内的业务逻辑处理不依赖于持久化IO,实现真正的in-memory: 保 ...

  4. WebGIS中快速整合管理多源矢量服务以及服务权限控制的一种设计思路

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 在真实项目中,往往GIS服务数据源被其他多个信息中心或者第三方 ...

  5. OpenStack 通用设计思路 - 每天5分钟玩转 OpenStack(25)

    API 前端服务 每个 OpenStack 组件可能包含若干子服务,其中必定有一个 API 服务负责接收客户请求. 以 Nova 为例,nova-api 作为 Nova 组件对外的唯一窗口,向客户暴露 ...

  6. Redis入门指南(第2版) Redis设计思路学习与总结

    https://www.qcloud.com/community/article/222 宋增宽,腾讯工程师,16年毕业加入腾讯,从事海量服务后台设计与研发工作,现在负责QQ群后台等项目,喜欢研究技术 ...

  7. MVC3 数据验证用法之密码验证设计思路

    描述:MVC数据验证使用小结 内容:display,Required,stringLength,Remote,compare,RegularExpression 本人最近在公司用mvc做了一个修改密码 ...

  8. YYCache设计思路及源码学习

    设计思路 利用YYCache来进行操作,实质操作分为了内存缓存操作(YYMemoryCache)和硬盘缓存操作(YYDiskCache).内存缓存设计一般是在内存中开辟一个空间用以保存请求的数据(一般 ...

  9. 第三篇:Retrofit SDK的设计思路

    2016-05-08 15:24:03 Retreofit毫无疑问是一个优美的开源框架,有轻量级.耦合性低.扩展性好.灵活性高的特点,那么Retrofit的设计者们到底是怎么样做到这些的呢?我希望能够 ...

随机推荐

  1. python中从内部循环直接跳出多层循环

    学习循环的时候碰到一道题,需要从内部循环中直接跳出所有循环,想了很久终于想到一种好办法(小白认知) 题目为:使用while循环输出100-50,从大到小,到50时,再循环输出0-50,从小到大. ex ...

  2. h5常用标签语义

    <article>定义页面独立的内容区域.例如外部来的文章. <aside>定义页面的侧边栏内容.<aside> 标签定义 <article> 标签外的 ...

  3. css固宽截取字符串

    width:200px; white-space:nowrap; word-break:keep-all; overflow:hidden; text-overflow:ellipsis; 移动端支持 ...

  4. tms web core 通过URL 传递参数

    一般我们都会通过URL 给服务器传递很多参数,通过参数来决定对应的处理,今天就大概讲一下 如果通过URL 参数实现一些功能. 1.通过参数跳入不同的界面 首先我们先建立一个tms web core 工 ...

  5. 前端之css操作2

    一 伪类选择器 伪类选择器就是在查找的后面加上冒号和状态 hover:悬浮到上面就会变化一种状态 link:没有接触的状态 active:点击是触发的状态 visited:点击后的状态 <!DO ...

  6. Lecture2

    共生矩阵:

  7. location位置操作

    使用location对象可以通过很多方式来改变浏览器的位置. location.assign('http://www.klkx.com') 传入一个URL地址 这样可以立即打开一个新的URL并在浏览器 ...

  8. css3 box-sizing详解。

    人们慢慢的意识到传统的盒子模型不直接,所以他们新增了一个叫做 box-sizing 的CSS属性. box-sizing: 盒大小,盒模型. 我们经常遇到左右模块宽度为50%,加个边框会掉下去,加一个 ...

  9. AngularJS实战之ng-repeat的详细用法

    一.基本语法 {{$index}}:获取元素的下标. {{$first}}:判断当前元素是否是第一个元素,是则为true,否则:false: {{$last}}:判断当前元素是否是最后一个元素,是则为 ...

  10. HTML中JavaScript调用方法

    我在写web页面的时候,经常用js实现某些功能,我用的方法有两种: 1.点击调用JavaScript: <button onclick="loadXMLDoc()">b ...