29. Copy-on-Write模式:不是延时策略的COW - 并发设计模式

Copy-on-Write,缩写为COW或者CoW,顾名思义就是写时复制

1. Copy-on-Write模式的应用领域

  1. 操作系统

Unix创建进程的API是fork(),会创建父进程的一个完整副本,如父进程占用1G内存,fork()子进程要复制父进程的整个内存,非常耗时;Linux系统fork()子进程,先不复制,让父子进程共享同一个地址空间;只用在父进程或者子进程需要写入的时候才会复制地址空间,从而使父子进程拥有各自的地址空间。

本质上来讲,父子进程的地址空间以及数据都是要隔离的,使用Copy-on-Write更多地体现的是一种延时策略,只有在真正需要复制的时候才复制,而不是提前复制好,同时Copy-on-Write还支持按需复制

此外文件系统、Docker容器镜像的设计,Git等。

  1. Copy-on-Write最大的应用领域还是在函数式编程领域
    函数式编程的基础是不可变性(Immutability),所以函数式编程里面所有的修改操作都需要Copy-on-Write来解决。

2. 一个真实案例

一个RPC框架,服务提供方是多实例分布式部署的,所以客户端在调用RPC的时候,会选定一个服务实例来调用,这个选定的过程本质上就是在做负载均衡,而做负载均衡的前提是客户端要有全部的路由信息。如下图A服务的提供方有3个实例。
在这里插入图片描述
RPC框架的一个核心任务就是维护服务的路由关系,我们可以把服务的路由关系简化成下图所示的路由表。当服务提供方上线或者下线的时候,就需要更新客户端的这张路由表。
在这里插入图片描述
每次RPC调用都需要通过负载均衡器来计算目标服务的IP和端口号,而负载均衡器需要通过路由表获取接口的所有路由信息。每次RPC调用都需要访问路由表,访问路由表的操作性能要求很高。路由表对数据的一致性要求并不高,典型的读多写少。CopyOnWriteArrayList和CopyOnWriteArraySet天生就适用这种场景。

RouteTable这个类内部我们通过ConcurrentHashMap<String, CopyOnWriteArraySet<Router>>这个数据结构来描述路由表,ConcurrentHashMap的Key是接口名,Value是路由集合。

Router的设计有两种选择:

  • 通过更新Router的一个状态位来标识,结果所有访问该状态位的地方都需要同步访问,这样很影响性能;
  • 采用Immutability模式,每次上线、下线都创建新的Router对象或者删除对应的Router对象。由于上线、下线的频率很低,所以后者是最好的选择。
//路由信息
public final class Router {
	private final String ip;
	private final Integer port;
	private final String iface;

	//构造函数
	public Router(String ip, Integer port, String iface) {
		this.ip = ip;
		this.port = port;
		this.iface = iface;
	}

	//重写equals方法
	public boolean equals(Object obj) {
		if (obj instanceof Router) {
			Router r = (Router) obj;
			return iface.equals(r.iface) && ip.equals(r.ip) && port.equals(r.port);
		}
		return false;
	}

	public int hashCode() {
		// 省略hashCode相关代码
	}
}
//路由表信息
public class RouterTable {
	//Key:接口名
	//Value:路由集合
	ConcurrentHashMap<String, CopyOnWriteArraySet<Router>> rt = new ConcurrentHashMap<>();

	//根据接口名获取路由表
	public Set<Router> get(String iface) {
		return rt.get(iface);
	}

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

	//增加路由
	public void add(Router router) {
		Set<Router> set = rt.computeIfAbsent(route.iface, r -> new CopyOnWriteArraySet<>());
		set.add(router);
	}
}

3. 总结

Copy-on-Write是一项非常通用的技术方案,缺点是比较耗内存,随着GC的成熟和硬件的发展,已经可以接受。

4. 课后思考

Java提供了CopyOnWriteArrayList,为什么没有提供CopyOnWriteLinkedList呢?

发布了97 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39530821/article/details/102772312
今日推荐