架构师训练营 - 技术选型

发布于: 2020 年 07 月 07 日

分布式缓存

越早使用缓存,越能有效减少服务器访问压力。

缓存的关键指标: 缓存是否有效取决于有多少次重用同一个缓存响应业务请求,这个度量标准称为缓存命中率。

引入缓存带来新的复杂度

缓存雪崩,是指缓存失效,且新的缓存还没有创建的时候,有大量的请求访问并请求重新生成缓存,导致存储系统压力增大,最后影响整个系统,

解决措施由后台更新缓存,缓存失效的时候通知后台程序去更新

缓存穿透,是指大量请求的缓存数据不存在,直接访问的存储系统,

解决措施主要是把不存在的数据也缓存起来

本地对象缓存:

  • 对象直接缓存在应用服务器中,缓存服务器和应用服务器部署在一起。 

  • 缺点:需要更新同步到其他应用服务器,资源浪费,性能消耗大;

  • 本地对象缓存对象之间缓存在应用程序内存中。(HashMap)对象存储在共享内存中,同一个机器的多个进程可以访问它。缓存服务器作为独立应用和应用程序部署在同一机器上(localhost socket通信)。

远程分布式对象缓存:

  • Memcached 使用一致性hash算法,缓存服务器并不知道有其他缓存服务器存在;

  • Redis 支持复杂数据结构,缓存服务器知道在一个集群中,连接集群中任何一个可用节点都可以获取,节点互联。

  • 在对于分布式的支持上,memcached没有redis做的好,生产上也是推荐使用redis,集群或者哨兵模式。

  • redis目前的架构:单机, 分片, 集群, 集群分片组合

  • 集群是分布式概念中的子集,特指提供相同功能或相同角色的机器所构建的子系统。

一致性hash

  • 构建一致性hash环,范围0到2^32-1,把机器经过hash算法后等到的hash值放到hash环上,

  • 根据一个key查找对应的机器时,就是把key经过hash算法后得到的hash值顺时针查找到对应的机器上,那台机器就是key要访问的机器。

  • 增加或减少机器,只影响附近一小段的数据。

  • 但是以上的方案存在负载不均衡的问题比如:node2和node3挨得比较近,那么node3的负载就明显比较低;再比如,加机器的时候,只影响了附近的结点,其他机器的负载不受影响,可能违背了加机器分担负载的目标以及机器节点hash值冲突导致节点覆盖的问题。

  • 于是就引入了虚拟节点的概念。虚拟节点的个数经验值是150~200之间。

异步

  • 异步回调

  • 消息队列,基本角色:消息生产者,消息队列,消息消费者

  • 消息队列的优势在于:实现异步处理,提升处理性能;更好的伸缩性;削峰填谷;失败隔离和自我修复;解耦;

  • 主流产品:RabbitMQ、ActiveMQ、RocketMQ、Kafka

负载均衡

  • LB(Load Balance)的最终目的,是希望请求能均匀分发到每个服务器,以此提高性能、可用性、可靠性。

  • 负载均衡架构:HTTP重定向负载均衡、DNS负载均衡、反向代理负载均衡、IP负载均衡、数据链路层负载均衡

  • 负载均衡算法:轮询、加权轮询、随机、最少连接、原地址散列

分布式数据库

  • MySQL复制:主从复制、一主多从复制、主主复制

  • MySQL一主多从的优点分摊负载专机专用(应用读请求,数据分析等场景可以分开,避免互相影响)便于冷备和热备高可用

  • 主主复制两个数据库不能并发写入;复制只是增加读并发处理能力,没有增加写并发处理能力和存储能力;更新表结构会导致巨大的同步延迟;

HomeWork

  • 用你熟悉的编程语言实现一致性 hash 算法。

  • 编写测试用例测试这个算法,测试 100 万 KV 数据,10 个服务器节点的情况下,计算这些 KV 数据在服务器上分布数量的标准差,以评估算法的存储负载不均衡性。

public class Case4Application {
SortedMap<Integer, String> sortedCircle = new TreeMap<>();
int virtualNums = 200;
/**
* 初始化虚拟节点
*/
private void init(List<String> nodes) {
//服务器节点对应放大虚拟节点
Map<String, String> virtualNodes = new HashMap<>();
for (String node : nodes) {
for (int i = 0; i < virtualNums; i++) {
virtualNodes.put(node + "_" + i, node);
}
}
for (String key : virtualNodes.keySet()) {
sortedCircle.put(hash(key), virtualNodes.get(key));
}
}
/**
* 增加节点
* @param node
*/
private void addNode(String node) {
//服务器节点对应放大虚拟节点
Map<String, String> virtualNodes = new HashMap<>();
for (int i = 0; i < virtualNums; i++) {
virtualNodes.put(node + "_" + i, node);
}
for (String key : virtualNodes.keySet()) {
sortedCircle.put(hash(key), virtualNodes.get(key));
}
}
/**
* 找到离该key的hash值最新的一个节点
* 找不到取第一个节点
*
* @param key
* @return
*/
private String findNodeByHash(String key) {
int hash = hash(key);
SortedMap<Integer, String> subMap = sortedCircle.tailMap(hash);
return subMap.isEmpty()
? sortedCircle.get(sortedCircle.firstKey())
: subMap.get(subMap.firstKey());
}
/**
* hash值计算
* 32位FNV算法1
*
* @param data
* @return
*/
private static int hash(String data) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < data.length(); i++)
hash = (hash ^ data.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return hash;
}
/**
* 计算缓存key在节点上的分布和标准差
*
* @param keyValus
*/
private void compute(Map<String, Object> keyValus) {
//每个节点分配的缓存key个数
Map<String, Integer> result = new HashMap<>();
for (String key : keyValus.keySet()) {
String nodeName = findNodeByHash(key);
if (result.get(nodeName) == null) {
result.put(nodeName, 1);
} else {
Integer value = result.get(nodeName);
result.put(nodeName, value + 1);
}
}
System.out.println(result);
System.out.println("标准差:" + stdDev(result, keyValus.size()));
}
/**
* 标准差计算
*
* @param map
* @param totalNums
* @return
*/
private double stdDev(Map<String, Integer> map, long totalNums) {
int n = map.size();
long avgNums = totalNums / map.size();
double sum = 0;
for (String key : map.keySet()) {
int value = map.get(key);
sum += (value - avgNums) * (value - avgNums);
}
return Math.sqrt(sum / n);
}
public static void main(String[] args) {
//服务器节点
List<String> nodes = Arrays.asList("node1", "node2", "node3"
, "node4", "node5", "node6", "node7", "node8", "node9", "node10");
Case4Application case4Application = new Case4Application();
//初始化,默认虚拟节点为200
case4Application.init(nodes);
//需要环节的1000000个key
Map<String, Object> keyValus = new HashMap<>();
for (int i = 1; i <= 1000000; i++) {
keyValus.put("key" + i, "value" + i);
}
//输出缓存key在节点上的分布及标准差
case4Application.compute(keyValus);
//添加一个新节点
case4Application.addNode("node11");
case4Application.compute(keyValus);
}
}
// {node4=98661, node10=92694, node5=103651, node2=88519, node3=112497, node8=90940, node9=89461, node6=94505, node7=109283, node1=119789}
// 标准差:10284.048249595098
// {node4=89700, node5=92328, node10=83027, node11=92332, node2=78758, node3=105872, node8=85593, node9=82934, node6=90864, node7=101427, node1=97165}
// 标准差:7867.034967623964

用户头像

Pontus

关注

还未添加个人签名 2018.04.21 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 - 技术选型