架构师第五周总结

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

缓存



  • 缓存不能写数据,只是用来读数据

  • cache 缓存 buffer 缓冲



  • 缓存关键指标- 缓存命中率

- 缓存键集合大小 数量越少 效率越高

- 缓存可用内存空间 缓存对象越多,效率越高

- 缓存对象生存时间 时间越长 可用性约高



缓存使用注意点

  • 读写比例2:1 缓存才有意义

  • 非热点数据不缓存 遵循二八定律

  • 缓存可用lru算法保证热点数据

  • 数据不一致

- 设置有效期

- 数据更新时删除数据

  • 注意缓存雪崩

- 通过缓存预热解决,启动缓存服务器时,同时把热点数据加载

缓存代理

  • 直接返回用户



反响代理缓存

  • 直接返回服务

  • 多层代理



cdn

  • 由网络提供商提供服务



旁路缓存 cache-aside

  • 对象缓存

  • 浏览器对象缓存

  • 本地对象缓存 hashmap来作为缓存 spirng 容器对象



memcached 分布式缓存

  • 关键技术点

- 如何做路由

- 如何做集群伸缩



  • 对key做hash计算 得到具体的服务节点

  • 扩容服务器以后一小部分数据找不到是可以接受的,但是大部分找不到接受不了,因此提出了一致性hash算法



一致性哈希算法

  • 首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。

  • 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。

  • 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。





从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但Consistent Hashing中,只有在园(continuum)上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响,如下图所示:





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

  • 解决一致性hash算法 增加节点以后负载不均衡的问题

  • 增加新节点以后,数据哈希更均匀,同时每个节点都会有一部分数据失效,但是由于数据量小,影响不大



package com.suke.hystrix;

import java.util.*;

/**
* 带虚拟节点的一致性Hash算法
*/
public class ConsistentHashingWithoutVirtualNode {
static String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static int KEY_LENGTH = 10;
static int KEY_COUNT = 1000000;
//待添加入Hash环的服务器列表
private static String[] servers = {
"192.168.0.0:111",
"192.168.0.1:111",
"192.168.0.2:111",
"192.168.0.3:111",
"192.168.0.4:111",
"192.168.0.5:111",
"192.168.0.6:111",
"192.168.0.7:111",
"192.168.0.8:111",
"192.168.0.9:111"};

//真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
private static List<String> realNodes = new LinkedList<String>();

//虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
private static SortedMap<Integer, String> virtualNodes = new TreeMap<Integer, String>();

//虚拟节点的数目,这里写死,为了演示需要,一个真实结点对应10个虚拟节点
private static final int VIRTUAL_NODES = 10;

static{
//先把原始的服务器添加到真实结点列表中
for(int i=0; i<servers.length; i++)
realNodes.add(servers[i]);

//再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高
for (String str : realNodes){
for(int i=0; i<VIRTUAL_NODES; i++){
String virtualNodeName = str + "&&VN" + String.valueOf(i);
int hash = getHash(virtualNodeName);
System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);
virtualNodes.put(hash, virtualNodeName);
}
}
System.out.println();
}

//使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
private static int getHash(String str){
final int p = 16777619;
int hash = (int)2166136261L;
for (int i = 0; i < str.length(); i++)
hash = (hash ^ str.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;

// 如果算出来的值为负数则取其绝对值
if (hash < 0)
hash = Math.abs(hash);
return hash;
}

//得到应当路由到的结点
private static String getServer(String key){
//得到该key的hash值
int hash = getHash(key);
// 得到大于该Hash值的所有Map
SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
String virtualNode;
if(subMap.isEmpty()){
//如果没有比该key的hash值大的,则从第一个node开始
Integer i = virtualNodes.firstKey();
//返回对应的服务器
virtualNode = virtualNodes.get(i);
}else{
//第一个Key就是顺时针过去离node最近的那个结点
Integer i = subMap.firstKey();
//返回对应的服务器
virtualNode = subMap.get(i);
}
//virtualNode虚拟节点名称要截取一下
if(virtualNode != null && !"".equals(virtualNode)){
return virtualNode.substring(0, virtualNode.indexOf("&&"));
}
return null;
}

public static void main(String[] args){
Map<String,Integer> db = new HashMap<>();
for(int i = 0;i < KEY_COUNT;i ++){
String key = getRandomString(KEY_LENGTH);
String server = getServer(key);
Integer count = db.getOrDefault(server,0);
db.put(server,count+1);
}
double[] valArr = db.values().stream().mapToDouble(value -> value.doubleValue()).toArray();
System.out.println("节点hash分布:");
db.forEach((key,val)->{
System.out.println(" server: " + key+" 数据量:"+val);
});

System.out.println("数据分布标准差:" + StandardDiviation(KEY_COUNT,valArr));
}
public static double StandardDiviation(double sum ,double[] x) {
int m=x.length;

double dAve=sum/m;//求平均值
double dVar=0;
for(int i=0;i<m;i++){//求方差
dVar+=(x[i]-dAve)*(x[i]-dAve);
}
//reture Math.sqrt(dVar/(m-1));
return Math.sqrt(dVar/m);
}
public static String getRandomString(int length){
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i<length;i++){
int number=random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
}


redis 缓存



消息队列与异步架构



点对点模型



发布订阅模型



优点



  • 更好的伸缩性

  • 削峰填谷



事件驱动架构EDA



消息队列产品对比



  • rabbit mq

  • acrive mq

  • kafka



  • 根据搜索结果选型

  • 根据团队熟悉程度选型



负载均衡架构



http 重定向负载均衡



  • 性能问题

  • 安全问题



dns 负载均衡



反响代理负载均衡

  • 适合小规模集群

  • http

- 协议相比tcp 比较重

- 沉淀等待的数据比较多,导致大并发处理不过来

- 本身就是app web处理服务器



ip 负载均衡



链路层负载均衡



session绑定

  • 与快速升级冲突,导致session丢失



mysql 主从复制



用户头像

suke

关注

还未添加个人签名 2017.10.19 加入

还未添加个人简介

评论

发布
暂无评论
架构师第五周总结