Cache

介于数据访问者和数据源之间的一种高速存储,当数据需要多次读取时,用于加快读取速度

  • 和Buffer的区别
    • Buffer是当数据源和数据访问者之间速度不匹配时,充当缓冲作用,用于加快访问速度
    • Buffer不止只有读操作,写操作也可以
    • 比如硬盘、网络的读写
  • 无处不在的Cache
    • CPU、操作系统、数据库、JVM
    • CDN、代理、前端、应用程序、分布式对象
  • 缓存实现的数据结构(Hash表)

    • 怎样快速定位
    • 怎样管理数据
    • Hash表的实现
      • 复杂度是O(1)
      • 本质是数组,通过下标方法
      • 用对象的hashcode取模,得到下标
        • hashcode存储的是对象的内存地址
        • 数组中每个对象的长度是相同的
        • 数组是内存中一段连续的空间,所以内存地址是呈倍数递增的
    • 缓存的关键指标
      • 命中率
        • 缓存的键集合大小
          • 键的数量越小,命中率就越高
        • 缓存可使用的内存空间
          • 物理上能缓存的对象越多,命中率越高
        • 缓存对象的生存时间
          • TTL(Time to live)
          • 原始数据被修改、缓存数据失效等导致缓存命中率降低
    • 代理缓存
      • 正向代理
        • 代理用户上网的服务器,通过代理服务器去访问用户想要访问的资源
        • 用户不知道
      • 反向代理
        • 缓存目标服务器的输出,当用户访问过来时,先看缓存中有没有需要的内容,有的话直接返回,没有再去访问目标资源
        • restful 通过url访问,反向代理服务器更容易以url作为key缓存内容。所以说,对restful的支持更友好
    • CDN
      • 图片或视频网站重度依赖CDN
      • 两种方式
        • 可以在服务端对静态和动态内容指定不同的域名
        • 也可以在CDN服务器中配置,区分动静资源,再跳转到不同服务器

          缓存的类型

  • 通读缓存(read-through)

    • 代理缓存,反向代理缓存,CDN缓存都是通读缓存
    • 客户端直接连接的是通读缓存而不是目标服务器
    • 通读缓存给客户端返回缓存资源,并在请求未命中缓存时获取实际数据
  • 旁路缓存(cache-aside)
    • 对象缓存是一种旁路缓存,通常是一个独立的键值对存储
    • 应用程序通常会询问对象缓存需要的对象是否存在,如果存在,它会获取并使用缓存对象,如果不存在或已过期,应用程序会主动连接主数据源来获取对象,并同时将其保存到对象缓存中,以供将来使用。
    • 旁路缓存中的对象,一般是不更新的,而是直接删除,再用新的对象替换

本地对象缓存

  • 直接存储在应用程序内存中
  • 存储在共享内存中,同一台服务器的多个进程可以访问它们
  • 缓存服务器作为独立应用和应用程序一起部署在同一个服务器上

分布式缓存的关键技术点

  • 缓存读写的路由选择
    • 余数哈希
    • 一致性哈希
  • 扩展性,新增或删减服务器时的适应性

技术栈各个层次的缓存

image.png

缓存为什么能显著提升性能

  • 缓存数据通常来自内存,比硬盘要快
  • 缓存存储的是数据是最终结果形态,不需要中间计算,减少CPU资源的消耗。
    • 要么过期删除,要么新增,不要修改
  • 缓存降低了数据库、磁盘、网络的负载压力,使这些设备能更好的响应

不正常的使用缓存

  • 频繁修改的数据不适合使用缓存
    • 应用来不及读取就已经失效或更新。一般来说,数据的读写在2:1以上,缓存才有意义。
  • 简单应用缓存,用于多次读取的情况,不要更新操作,会导致缓存效率降低
  • 没有热点的访问
    • 缓存使用的是内存,内存资源宝贵而有限,所以不能把所有数据都缓存起来,应该只缓存热点数据
    • 根据二八定律,大部分的数据访问应该集中的小部分的数据上,否则,缓存就没有意义
    • 怎样知道哪些是热点数据
      • LRU算法,总是把热点数据放到头部
    • 数据不一致与脏读,最简单的是,应用需要容忍一段时间内的数据不一致问题。
  • 缓存雪崩
    • 缓存数据丢失或缓存不可用会导致数据直接从数据库获取数据
    • 数据库可能因为不能完全承受缓存数据的读取压力而宕机
    • 数据库宕机就会导致整个系统瘫痪
  • 缓存预热
    • 在缓存系统启动的时候就先把热点数据加载好。
    • 对于一些源数据如城市地名列表等,可以在启动时加载数据库中所有数据到缓存中进行预热
    • 目的是为了访问一进来就能访问到缓存数据
  • 缓存穿透
    • 如果持续高并发的请求某个不存在的数据,因为缓存中没有数据,请求就会落在数据库中,会给数据库带来很大压力,甚至崩溃。
    • 一个简单的对策是,将不存在的数据也缓存起来,并设定一个较短的失效时间。

Redis VS Memcached

image.png
Redis 功能众多,更像是一个NoSql数据库。
Redis 使用的不是一致性hash

消息队列

同步调用 VS 异步调用

同步:
image.png

image.png

异步:

image.png

image.png

消息队列构建异步调用框架

  • 生产者
  • 消费者
  • 消息队列

image.png

两种架构模型

  • 点对点模型:
    • 生产者有多个,而且是独立的;只要求有消费者能处理生产者的消息。

image.png

  • 发布订阅
    • 生产者的消息有几个消费者订阅,就会有几个消息队列
    • 多个消费者之间是独立的,一个消费消息不会影响另外一个消费者

image.png

消息队列的好处

  • 实现异步处理,提升处理性能
  • 相比缓存提升读的效率,消息队列可以提升写的效率。
  • 更好的伸缩性
  • 削峰填谷
  • 失败隔离和自我修复
  • 解耦

    image.png

    事件驱动EDA

通过事件驱动下一步的动作
image.png

一个对比实例
image.png

主要的MQ产品

  • RabbitMQ
  • ActiveMQ
  • RocketMQ
  • Kafka

负载均衡

负载均衡的两个关注点

如何把请求发送给应用服务器

  • http重定向负载均衡

通过http协议中的重定向功能,把应用服务器地址写到http header中
缺点:

  • 需要两次重定向
  • 客户端流量较大
  • 将应用服务器直接暴露出来

image.png

  • DNS负载均衡
    • DNS服务器进行域名解析时,可以返回多个IP
    • DNS解析的结果会缓存在本地,所以如果直接返回应用服务器的IP,将会不利于即时生效

image.png

  • 反向代理负载均衡

基于http协议的转发
适用于小规模服务器,服务器集群不能太多。
反向代理服务器效率就会比较低

  • http协议比较重,包比较大,要拿到完整的http包才能继续,所以解析压力就大
    • IP负载均衡

image.png
负载进化服务器不再需要拿到完整的http包,而只要拿到ip层的请求;
转发请求时,将源地址改为负载均衡服务器IP,目标地址改为应用服务器IP

  • 数据链路层负载均衡

image.png

负载均衡的算法:请求应用服务器的路由选择

  • 轮询
  • 加权轮询
  • 随机
  • 加权随机
  • 最少连接
  • 原地址散列

扩展

Redis分桶原理

关于hashCode

hashCode()方法的存在是为了减少equals()的调用;当调用equals()时,先判断hashCode是否相等,如果不等,那两个对象肯定不相等,equals() 直接返回false;只有当hashCode()返回为true时,才需要再通过equals() 判断。

String类在重写equals()时,也重写了hashCode()。

  1. public int hashCode() {
  2. int h = hash;
  3. if (h == 0 && value.length > 0) {
  4. char val[] = value;
  5. for (int i = 0; i < value.length; i++) {
  6. h = 31 * h + val[i];
  7. }
  8. hash = h;
  9. }
  10. return h;
  11. }

和equals()的逻辑是一致的。在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值。

String的 equals 判断的逻辑是判断字符串中每个字符是否相等,hashCode的逻辑则是按照某种计算方法得出一个int,也就是说都是和字符串中每个字符有关的

  1. public boolean equals(Object anObject) {
  2. if (this == anObject) {
  3. return true;
  4. }
  5. if (anObject instanceof String) {
  6. String anotherString = (String)anObject;
  7. int n = value.length;
  8. if (n == anotherString.value.length) {
  9. char v1[] = value;
  10. char v2[] = anotherString.value;
  11. int i = 0;
  12. while (n-- != 0) {
  13. if (v1[i] != v2[i])
  14. return false;
  15. i++;
  16. }
  17. return true;
  18. }
  19. }
  20. return false;
  21. }

参考

一致性 Hash 算法
MemCache详细解读
一致性hash算法及java实现