fork

  1. 类Unix操作系统调用fork(),会创建父进程的一个完整副本,很耗时
  2. Linux调用fork(),创建子进程时并不会复制整个进程的地址空间,而是让父子进程共享同一个地址空间
    • 只有在父进程或者子进程需要写入时才会复制地址空间,从而使父子进程拥有各自独立的地址空间
  3. 本质上来说,父子进程的地址空间和数据都是要隔离的,使用Copy-on-Write更多体现的是一种延时策略
  4. Copy-on-Write还支持按需复制,因此在操作系统领域能够提升性能
  5. Java提供的Copy-on-Write容器,会复制整个容器,所以在提升读操作性能的同时,是以内存复制为代价的
    • CopyOnWriteArrayList / CopyOnWriteArraySet

RPC框架

  1. 服务提供方是多实例分布式部署的,服务的客户端在调用RPC时,会选定一个服务实例来调用
  2. 这个过程的本质是负载均衡,而做负载均衡的前提是客户端要有全部的路由信息
  3. 一个核心任务就是维护服务的路由关系,当服务提供方上线或者下线的时候,需要更新客户端的路由表信息
  4. RPC调用需要通过负载均衡器来计算目标服务的IP和端口号,负载均衡器通过路由表获取所有路由信息
    • 访问路由表这个操作对性能的要求很高,但路由表对数据一致性要求不高
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
// 采用Immutability模式,每次上线、下线都创建新的Router对象或删除对应的Router对象
@Data
@AllArgsConstructor
public class Router {
private final String ip;
private final Integer port;
private final String iFace;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Router router = (Router) o;
return Objects.equals(ip, router.ip) &&
Objects.equals(port, router.port) &&
Objects.equals(iFace, router.iFace);
}

@Override
public int hashCode() {
return Objects.hash(ip, port, iFace);
}
}

class RouterTable {
// <接口名 , 路由集合>
private ConcurrentHashMap<String, CopyOnWriteArraySet<Router>> routingTable = new ConcurrentHashMap<>();

// 获取路由
public Set<Router> get(String iFace) {
return routingTable.get(iFace);
}

// 增加路由
public void add(Router router) {
CopyOnWriteArraySet<Router> set = routingTable.computeIfAbsent(router.getIFace(),
iFace -> new CopyOnWriteArraySet<>());
set.add(router);
}

// 删除路由
public void remove(Router router) {
CopyOnWriteArraySet<Router> set = routingTable.get(router.getIFace());
if (set != null) {
set.remove(router);
}
}
}

参考资料

Java并发编程实战