写点什么

架构师训练营 Week5 学习总结

用户头像
小高
关注
发布于: 2020 年 07 月 08 日

第一节课:分布式缓存


缓存:存储在计算机上的一个原始数据复制集,以便于访问。通常是 key-value 格式的,主要用来提高系统的响应性能的。

缓冲可以理解为一种提高读写的速度的通道,数据存储在通道中,提高存取速度。

种类:

  1. 硬件:CPU 缓存、操作系统缓存、数据库缓存、JVM 缓存

  2. 软件:CDN 缓存、代理与反向代理缓存、前端缓存、应用程序缓存、分布式对象缓存

常见的缓存算法(hash 表):

将键进行哈希处理,得到值在数组中的索引,后续可以利用这个索引进行访问,时间复杂度是 O(1)。

缓存的关键指标:

  1. 缓存命中率:缓存是否有效依赖于能多少次重用同一个缓存响应业务请求,这个度量之变被称作缓存命中。假设十次缓存响应中,有 9 次成功响应,那么缓存命中率就是 90%。

  2. 缓存键集合大小: 键的数量多少。键数量越少,缓存的命中效率越高

  3. 缓存可使用内存空间:缓存可使用的物理内存空间,缓存对象越多,缓存命中率越高。

  4. 缓存对象生存时间:缓存对象的有效时间,其越长,缓存对象被重用的可能性就越高。

常见缓存种类:

  • 代理缓存

  • 反向代理缓存

  • 多层反向代理缓存

  • 内容分发网络

  • CDN 同时配置静态文件和动态内容

  • 通读缓存(给客户端返回缓存资源,并在请求命中缓存时获取实际数据,代理缓存、反向代理缓存、CDN 缓存)


  • 旁路缓存

  • 浏览器对象缓存本地对象缓存

  • 本地对象缓存构建分布式集群

  • 远程分布式对象缓存

案例:Memcached 分布式对象缓存

Java 访问其代码:

import com.danga.MemCached.MemCachedClient; //通用分布式缓存访问public class CommonCache<T> implements Cache<T> {	private static MemCachedClient memCachedClient = null; 	private String base = null; 	CommonCache(Class<T> t, MemCachedClient client) {		memCachedClient = client;		base = t.getSimpleName() + "-";	} 	public T get(String key) {		return (T) memCachedClient.get(base + key);	} 	public boolean set(String key, T value) {		return memCachedClient.set(base + key, value);	} 	@Override	public boolean update(String key, T value) {		return memCachedClient.replace(base + key, value);	} 	@Override	public boolean delete(String key) {		return memCachedClient.delete(base + key);	} 	@Override	public boolean add(String key, T value) {		return memCachedClient.add(base + key, value);	}}//分布式集群访问import com.danga.MemCached.MemCachedClient;import com.schooner.MemCached.SchoonerSockIOPool;import com.yx.cache.util.HashCodeUtil;import com.yx.task.ThreadPoolManager;     public class ClusterCache<T> implements Cache<T> {   private static MemCachedClient memCachedClient = null;        private static ThreadPoolManager taskManager = ThreadPoolManager    	.getInstance("cache");        private String base = null;        private SchoonerSockIOPool pool = SchoonerSockIOPool.getInstance();        ClusterCache(Class<T> t, MemCachedClient client) {    memCachedClient = client;    base = "i-" + t.getSimpleName() + "-";    }         @Override    public T get(String key) {    	T value = null;    	if (key == null) {    		return null;    	}    		key = base + key;    		if (pool.getServers().length < 2) {    			value = (T) memCachedClient.get(key);    		} else {    			int hashCode = HashCodeUtil.getHash(key);    			value = (T) memCachedClient.get(key, hashCode);    			if (value == null) {    				hashCode = this.getRehashCode(key, hashCode);    				value = (T) memCachedClient.get(key, hashCode);    				if (value != null) {// 如果在另外一台服务器上取到了缓存,则恢复第一台服务器    					UpdateTask task = new UpdateTask(key, value);    					taskManager.submit(task);    				}    			}    		}    		return value;    	}    	@Override    	public boolean set(String key, T value) {    		if (key == null) {    			return false;    		}    		key = base + key;    		boolean result = false;    		if (pool.getServers().length < 2) {    			result = memCachedClient.set(key, value);    		} else {    			int hashCode = HashCodeUtil.getHash(key);    			result = memCachedClient.set(key, value, hashCode);    			// if (result) {    			hashCode = getRehashCode(key, hashCode);    			memCachedClient.set(key, value, hashCode);    			// }    		}    		return result;    	}    	private int getRehashCode(String key, int oldHashcode) {    		String host = pool.getHost(key, oldHashcode);    		int rehashTries = 0;    		// if (result) {    		int hashCode = HashCodeUtil.getHash(rehashTries + key);    		while (host.equals(pool.getHost(key, hashCode))) {    			rehashTries++;    			hashCode = HashCodeUtil.getHash(rehashTries + key);    		}    		return hashCode;    	}    	@Override    	public boolean update(String key, T value) {    		if (key == null) {    			return false;    		}    		key = base + key;    		boolean result = false;    		if (pool.getServers().length < 2) {    			result = memCachedClient.replace(key, value);    		} else {    			int hashCode = HashCodeUtil.getHash(key);    			result = memCachedClient.replace(key, value, hashCode);    			// if (result) {    			hashCode = getRehashCode(key, hashCode);    			memCachedClient.replace(key, value, hashCode);    			// }    		}    		return result;    	}    	@Override    	public boolean delete(String key) {    		if (key == null) {    			return false;    		}    		key = base + key;    		boolean result = false;    		if (pool.getServers().length < 2) {    			result = memCachedClient.delete(key);    		} else {    			int hashCode = HashCodeUtil.getHash(key);    			result = memCachedClient.delete(key, hashCode, null);    			// if (result) {    			hashCode = this.getRehashCode(key, hashCode);    			memCachedClient.delete(key, hashCode, null);    			// }    		}    		return result;    	}    	@Override    	public boolean add(String key, T value) {    		if (key == null) {    			return false;    		}    		key = base + key;    		boolean result = false;    		if (pool.getServers().length < 2) {    			result = memCachedClient.add(key, value);    		} else {    			int hashCode = HashCodeUtil.getHash(key);    			result = memCachedClient.add(key, value, hashCode);    			// if (result) {    			hashCode = getRehashCode(key, hashCode);    			memCachedClient.add(key, value, hashCode);    			// }    		}    		return result;    	}    	static class UpdateTask implements Runnable {    		private String key;    		private Object value;    		UpdateTask(String key, Object value) {    			this.key = key;    			this.value = value;    		}    		@Override    		public void run() {    			memCachedClient.set(key, value, HashCodeUtil.getHash(key));    		}    	}    }
复制代码

MemCache 分布式缓存访问模型

这里最难理解的是其路由算法,牵涉到分布式对象的一致性哈希算法

基本思路:将集群节点 IP 放入一个环中,环大小为 2^32,再将键值对根据路由算法放入离最近节点的位置。

一致性 hash 节点扩容

基于虚拟节点的一致性 hash 算法

技术栈各个层面的缓存:

缓存能提升性能的原因:

  1. 缓存数据通常直接去内存读取

  2. 缓存存储的数据不需要中间计算,减少 CPU 资源的消耗

  3. 缓存降低数据库、磁盘、网络的负载压力,使这些 IO 设备获得更好的响应特性。


缓存不是性能优化的银弹,使用缓存应该注意以下几点:

  1. 不要频繁修改数据

  2. 设置热点访问(LRU 算法)

  3. 注意数据不一致性和脏读(计算机科学中最困难的三件事:缓存失效,命名事物、计数错误)

  4. 防止缓存雪崩

  5. 防止缓存预热

  6. 防止缓存穿透


Redis 的优点:

  1. 支持复杂的数据结构

  2. 支持多路复用异步 I/O 高性能

  3. 支持主从复制高可用

  4. 原生集群和 share nothin 集群模式


第二节课、消息队列和异步架构


同步调用

异步调用(多个耗时操作同步进行)


有回调的异步调用

多次异步调用不阻塞应用线程

由异步演变出消息队列为了解耦

架构元素:消息生产者、消息队列、消息消费者

分类:

  1. 点对点模型

  1. 发布订阅模型


优点:实现异步处理,提升处理性能;更好的伸缩性;削峰填谷;失败隔离与自我修复


由消息队列引出了一种架构方案:事件驱动架构 EDA




负载均衡架构

种类:

  • HTTP 重定向负载均衡

  • DNS 负载均衡

  • 反向代理负载均衡

  • IP 负载均衡

  • 数据链路层负载均衡


关于负载均衡的几类算法

  • 轮询:所有请求被依次分发到每个应用服务器上,适合于所有的服务器硬件都相同的场景

  • 加权轮询:根据应用服务器硬件性能的情况,在轮询的基础上,按照配置的权重将请求分发到每个服务器,高性能的服务器分配更多请求。

  • 随机:请求被随机分配到各个应用服务器。

  • 最少连接:记录每个应用服务器正在处理的连接数,将新的到的请求分发到最少连接的服务器上。这是最符合负载均衡定义的算法

  • 源地址散列:根据请求来源的 IP 地址进行 Hash 计算,得到应用服务器,该算法可以保证同一个来源的请求总在一个服务器上处理,实现了会话粘滞。

分布式集群的 Session 管理

  • Session 复制

  • Session 绑定


  • 利用 cookie 记录 Session


  • Session 服务器


参考链接:基于memcached for java 实现分布式缓存


用户头像

小高

关注

代码,思考,架构,阅读,旅行。 2018.11.02 加入

一起来进步吧,持续学习的小白!

评论

发布
暂无评论
架构师训练营Week5学习总结