1. 拥有更长时间的思维,5-10年,放长视野,顺利的人生不多,最重要的能力是从失败中重新站起来。人生也是,长达50-70年,一小段时间的低迷,每个人都会经历,越早站起来,越能奔向下一个高峰。抗挫折能力很重要,地图很重要,看清周期,看清规律。

  2. 自我剖析是痛苦的,看见,承认自己的缺点是痛苦,人心里都有对自己的一个美好幻想。认清自己很困难,需要从第三视角去旁观。

  3. 要抬头看宏观世界的运行周期,人顺应时代更能快速发展,逆规律艰难求生,经济危机下人人都不好找工作

  4. 多领域参与很重要,单一行业总有周期

  5. 经营家庭是给人幸福感提升的方式,一个社会的小单位,共同度过难关,互相扶持

  6. 越帮助别人越快乐,能力越大,越付出帮助社会。

  7. 修身齐家平天下,顺序不可颠倒。

思维模式:长期思维,周期思维,抗挫折,地图,不变特性

题目

设计一个支持以下两种操作的数据结构:
void addWord(word)
bool search(word)
search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a-z 。 . 可以表示任何一个字母。
示例:
addWord(“bad”)
addWord(“dad”)
addWord(“mad”)
search(“pad”) -> false
search(“bad”) -> true
search(“.ad”) -> true
search(“b..”) -> true
说明:
你可以假设所有单词都是由小写字母 a-z 组成的。

思路

用字典树可以解决,关键问题是.如何解决
我第一时间想到递归,遇到。要遍历整个子字符数组,
有一个递归标志符,表示已经匹配,这个标志符成功则返回,写的很乱,自己都懵逼
如何是. 就深度遍历,如果不是就匹配
结束条件,不匹配或到结尾,最后匹配是不是有结束符号
分两个2情况,一种是。,深度遍历,一种是具体值,走字典树
递归结束时index到数组结尾,返回结束标示符,需要空数组也返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class WordDictionary {
private TreeNode root;
public static class TreeNode{
private char data;
private TreeNode[] child = new TreeNode[26];
private boolean isEndingChar = false;
public TreeNode(char data){
this.data = data;
}
}
/** Initialize your data structure here. */
public WordDictionary() {
root = new TreeNode('/');
}

/** Adds a word into the data structure. */
public void addWord(String word) {
TreeNode node = root;
char[] array = word.toCharArray();
for(int i = 0 ;i < array.length ; i++){
int index = array[i] - 'a';
if(node.child[index] == null){
node.child[index] = new TreeNode(array[i]);
}
node = node.child[index];
}
node.isEndingChar = true;
}

/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
public boolean search(String word) {
if(word == null || word.length() == 0){
return true;
}
TreeNode node = root;
char[] array = word.toCharArray();
return helper(array,0,node);
}
public boolean helper(char[] array,int index,TreeNode node){
if(index == array.length){
return node.isEndingChar;
}
char data = array[index];

if(data == '.'){
TreeNode[] child = node.child;
for(int i = 0 ; i< child.length ; i++){

if(child[i] != null && helper(array, index+1, child[i])){
return true;
}
}
return false;
}else{
int num = data - 'a';
if(node.child[num] == null){
return false;
}
return helper(array,index+1, node.child[num]);
}

}
}

/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary obj = new WordDictionary();
* obj.addWord(word);
* boolean param_2 = obj.search(word);
*/

基于链表的有界阻塞队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Linked list node class
*/
static class Node<E> {
E item;

/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;

Node(E x) { item = x; }
}

基于单链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;

/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();

/**
* Head of linked list.
* Invariant: head.item == null
*/
transient Node<E> head;

/**
* Tail of linked list.
* Invariant: last.next == null
*/
private transient Node<E> last;

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

capacity是用于记录容量 ,首尾两个指针,head和last。基于2个锁,一个takeLock锁,notEmpty条件变量,在空时候用。一个putLock锁,notFull条件变量,在满的时候用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}

/**
* Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity} is not greater
* than zero
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}

初始化可以赋值容量,新建哨兵头指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary for space to become available.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
/**
* Links node at end of queue.
*
* @param node the node
*/
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}

如果队列容量等于最大容量,线程要在notFull 条件变量下等待。
如果没满,就调用enqueue,进行入站。
如果添加队列后小于容量,就在notFull里唤醒一个线程。
这里面使用putLock进行加锁,不影响出站。
由于是2个锁,只有在容量空的时候,才会去唤醒takeLock的notEmpty等待队列。
使用的模型与一个锁的不同,2个锁的模型,需要通过原子操作的count来判断是不是唤醒,小于容量就可以唤醒notFull等待队列,最后如果容量等于0,就去takeLock的锁去唤醒notEmpty等待队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
/**
* Removes a node from head of queue.
*
* @return the node
*/
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}

出队列,与入队列思路相同,使用takeLock锁,如果count为0,就进入notEmpty队列等待。
dequeue就是将头删除,原来的first元素,变成哨兵指针。
如果count 大于1还可以继续take,唤醒notEmpty队列的线程。最后如果修改之前count等于容量,唤醒notFull队列,还可以put。
offer和poll和put和take原理相同,只是他们不是阻塞的,直接返回布尔值。

总结:基于单链表的阻塞单端队列,入队出队2个独立的锁,通过原子操作count,来处理状态。

题目

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
示例:
Trie trie = new Trie();
trie.insert(“apple”);
trie.search(“apple”); // 返回 true
trie.search(“app”); // 返回 false
trie.startsWith(“app”); // 返回 true
trie.insert(“app”);
trie.search(“app”); // 返回 true
说明:
你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。

解题思路

开始是root不存储数据
然后引入一个小写字母数组
每个数组里面的对象包含自己和数组还有是否是叶子结点
内部类 TreeNode,包含字符,TreeNode数组,叶子结点标示
每次一个字符,一次遍历查下去
abcde fghij klmno pqrst uvwxy z
字符数组的大小一直错
Trie树的实现
插入没有就新建,有的继续遍历
搜索跟新建相似,最后介绍不是叶子结点就返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class Trie {
private TreeNode root;
public static class TreeNode{
private char data;
private TreeNode[] child = new TreeNode[26];
private boolean isEndingChar = false;
public TreeNode(char data){
this.data = data;
}
}
/** Initialize your data structure here. */
public Trie() {
root = new TreeNode('/');
}

/** Inserts a word into the trie. */
public void insert(String word) {
TreeNode node = root;
char[] array = word.toCharArray();
for(int i = 0 ; i < array.length ; i++){
int index = array[i] - 'a';
if(node.child[index] == null){
node.child[index] = new TreeNode(array[i]);
}
node = node.child[index];
}
node.isEndingChar = true;
}

/** Returns if the word is in the trie. */
public boolean search(String word) {
TreeNode node = root;
char[] array = word.toCharArray();
for(int i = 0 ; i < array.length ; i++){
int index = array[i] - 'a';
if(node.child[index] == null){
return false;
}
node = node.child[index];
}
if(!node.isEndingChar){
return false;
}
return true;
}

/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TreeNode node = root;
char[] array = prefix.toCharArray();
for(int i = 0 ; i < array.length ; i++){
int index = array[i] - 'a';
if(node.child[index] == null){
return false;
}
node = node.child[index];
}
return true;
}
}

/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/

基于优先级队列的无界队列

1
2
3
4
/**
* Creates a new {@code DelayQueue} that is initially empty.
*/
public DelayQueue() {}

构造器为空

1
2
3
4
5
6
7
8
9
10
private final transient ReentrantLock lock = new ReentrantLock();
private final PriorityQueue<E> q = new PriorityQueue<E>();
private Thread leader = null;

/**
* Condition signalled when a newer element becomes available
* at the head of the queue or a new thread may need to
* become leader.
*/
private final Condition available = lock.newCondition();

一个内部的锁,一个存储用的优先级队列,存储对象E,要继承Delayed,一个leader用于当前优先级最高要执行的线程。available用于等待的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Inserts the specified element into this delay queue.
*
* @param e the element to add
* @return {@code true}
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}

主要的插入的方法是offer,使用lock锁住,存取使用同一个锁,将新元素添加之前的优先级队列中,如果添加的新元素根据优先级排列在第一位,就要清空原来要执行leader线程,不然你没法作为优先级最好的去执行,然后随便唤醒一个线程就可以执行优先级最高的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Retrieves and removes the head of this queue, waiting if necessary
* until an element with an expired delay is available on this queue.
*
* @return the head of this queue
* @throws InterruptedException {@inheritDoc}
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}

Take是获取到时间的元素,不断自旋,进来先取出优先级队列的第一个,如果优先级队列是空,就在available变量里等待。
如果队列里有元素,就计算纳秒的延迟时间。如果这个时间小于0,就弹出元素,然后返回,这个是推出自旋的出口。
如果还没到延迟时间,就看leader是不是有值,代表只不是有其他正要执行的线程,如果有就等待,如果没有,就将当前线程作为执行线程,同时等待延迟时间,到时间后唤醒,重新自旋,判断是否退出。
这个主要问题就是插入优先级高于正要执行的当前线程,就需要重置leader重新判断哪个实现执行。
最后没有leader线程再执行,就再唤醒下一个available的等待线程
总结:基于优先级队列,需要自己继承Delay,实现比较、计算延迟时间的方法。通过等待与唤醒来按时间执行。

回望每个人的一个个七年,提取出共性,每个人大致都会经历的事情,看人的一生的规律

人生的几个阶段

原生家庭(父母是否离婚,单亲,与父母相处的时间)
教育(学校(付出金钱的程度),自己学习的态度)
工作(获得金钱是否足够生活,是否努力向上)
婚姻(多大年龄结婚,如何维系婚姻,离婚,再婚)
子女(是否要孩子,几个孩子,如何处理关系)
父母(去世,癌症,关系)
健康(疾病,生活质量)


俯瞰人生,看重要阶段见元素的关联关系,分析人生的关键点

重要阶段分析

人从出生开始就各不相同,家庭情况对于每个新生儿来说是随机的,不能选择的。
富裕的家庭的孩子比贫困的更加自信。单亲或父母离婚的比正常家庭的孩子,更没有安全感,缺乏自信。
好的教育可以改变一个人,富裕家庭的孩子可以接受更好的教育,比如读私立学校,读预科学校。贫穷家庭的孩子可能没意识到教育的重要性,需要更早的进入社会,去竞争生存。也可能通过教育,提升自己的维度,让自己跨越不同的阶级。
出生情况不能选择,但后面的人生可以选择,选择的前提是自己的见识。
见识的高度才是整个人生的高度,先天不足可以后天弥补,但如果不知道自己不足,那就会原地踏步。听到之前林赛说,她们三姐妹反而比富人的家的孩子选择更多,他们的人生已经固定,从7岁就被选择好了,她们可以自由的决定。一听起来好像很有道理,但这个是带有偏见的,富人家的孩子也是通过不断努力才达到父母预期的高度,不能否认他们自己的努力,并非一切从出生就安排好了,而是他们努力达到的比较高的目标。反而是林赛她们没有什么目标,好像可以自由选择,但都是漫无目的的原地踏步。
婚姻对一个人十分重要,好的婚姻二个人互相扶持,共同面对困难,共同承担责任。很多人当时结婚的时候并没有考虑清楚,只是正好到了年龄,决定应该结婚了。一些自我牺牲,换取团体的利益最大化,在不断磨合下,既能做自己,又能融洽相处。婚姻是一个集体,家庭也是一个集体,这个集体与外面的不太一样,是通过血缘与爱结合在一起。
子女出生,又是一个轮回,到了教育子女的问题了,大部分会将自己的遗憾投射到子女身上,希望她们比自己更好,不经历自己的痛苦,希望自己的孩子接受更好的教育,但也要跟自己经济实力匹配,父母一般会支出一条自己熟悉的路。
健康是无价之宝,一切的前提,得癌症的概率很高,没有几个健康的身体,生活的质量会明显下降。
死亡是每个人都必须面对的,父母、亲人的离世,自己的寿命到期,尽量多帮助别人,可以在多留下自己的生活过的痕迹。

这周读的也不多,只读了一节

第二节讲了分布式的架构,在谈论架构时,一个关键的想法是架构风格。 一种风格反映了组织包括分布式系统的软件组件之间的交互所遵循的基本原则。 重要的样式包括分层,面向对象,面向事件和数据中心方向。

分布式有很多组织结构,一种重要的模式切分服务器为访问端和服务器端。

客户端 - 服务器架构通常高度集中。 在分散的架构中,我们经常看到构成分布式系统的过程所起的平等作用,也称为点对点系统。 在对等系统中,进程被组织成覆盖网络,该覆盖网络是逻辑网络,其中每个进程具有可与其通信的其他对等体的本地列表。 可以构造覆盖网络,在这种情况下,可以部署确定性方案以在进程之间路由消息。 在非结构化网络中,对等体列表或多或少是随机的,这意味着需要部署搜索算法来定位数据或其他进程。

作为替代方案,已经开发了自我管理的分布式系统。 这些系统在某种程度上融合了系统和软件架构的想法。 自我管理系统通常可以组织为反馈控制循环。 这样的循环包含由分布式系统的行为测量的监视组件,用于查看是否需要调整任何内容的分析组件,以及用于改变行为的各种仪器的集合。 反馈控制循环可以集成到许多地方的分布式系统中。 在共同理解如何开发和部署这样的循环之前,仍然需要进行大量研究。

总结的翻译,这周看的时间很少,还没有找到分布式的线索。

之前在得到上听了一遍,今天又重新看书。书中使用2条线索穿起来整个科技史,能量与信息,真的很绝妙。相信没有理科思维的人是不会有这两种视角去看历史。从能量的角度看人类是怎么一步步进化,是不断获取更多的能量。信息的处理又能优化能量的使用。
之前的历史都是使用重大事件以及它产生的意义作为分割点,但这里从能量的角度去看,既新奇又合理,之前为什么没有想到。物理学是研究事物规律的,那里面的一些定律是不是会反映出一些,客观规律,也可以作为一些基础概念,理解其他事物。
还是比较好奇,吴军老师是怎么研究这些历史的,现在听得都是结果,如果我想自己去做研究,要从哪里获取资料,怎么去研究,怎么得到结论。吴军老师经常能拿一些我们之前熟悉的例子,通过一些不为人知的历史,来证明原来的是谣传。如何查看的资料,我很想知道。
很多史料都很有趣,比如人发明衣服的时间是通过体虱的进化时间的来,人类走出非洲的时间跟太阳的热量有关。人类从史前到定居,农耕,创建文明。
分享一些基本方法论
回望历史,把握历史进化的来龙去脉,可以清楚我们今天所处的位置;
俯瞰历史,把握所有要素之间的联系,可以看清世界变化的规律。
经典结论
历史总在重演,科技永远向前。

内部类Sync继承AbstractQueuedSynchronizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;

Sync(int count) {
setState(count);
}

int getCount() {
return getState();
}

protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}

通过构造器传入state的数值。
tryAcquireShared 非独占加锁,实现如果state不是0,就返回-1,小于0。等于0,就返回1,大于0
tryReleaseShare 非独占解锁,自旋,如果状态等于0,就是解锁失败。每次解锁大小减1,如果等于0,则解锁成功,其他则失败

1
2
3
4
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

构造器传入count,赋值给state
添加await方法,用于加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

除非state减为0,其他情况,tryAcquireShared都小于0,要进doAcquireSharedInterruptibly方法
doAcquireSharedInterruptibly方法,先非独占的加入队列,然后判断前序节点是不是头节点,
如果是前序节点是头节点,判断tryAcquireShared结果,在state减为0前,这个都是不进入r》=0的条件里,后面就会wait等待 前面执行完,后唤醒。
不断自旋,只有在前序是头节点,同时state是0 时,才会继续执行。

1
2
3
4
5
6
7
8
9
10
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

countDown,每次减1,在每到0,之前返回都是false。

总结:基于模版AbstractQueuedSynchronizer,通过tryAcquireShared 加锁,countDown 不断减少state,直到等于0,再解锁,可以实现,单个线程等待其他多个线程的功能。

五一假期,学习时间比平时还要少,看得很少,先写一部分吧。

分布式系统的定义

A distributed system is a collection of independent computers that appears to its users as a single coherent system.
分布式系统是一堆独立的电脑的集合,使用者使用的像是单一系统一样。

分布式系统的目标

  1. Making Resources Accessible
  2. Distribution Transparency
  3. Openness
  4. Scalability
  5. Pitfalls

第一个是让分散在各地的资源可以简单被访问。
第二个是分配透明度,隐藏资源分布在各地。
第三个是开放式分布式系统是根据标准规则提供服务的系统,这些规则描述了这些服务的语法和语义。
第四个是可扩展性
第五个是一些陷阱,7大分布式谬论