摘要:在技术领域中,没有银弹。我们需要不断探索和研究新的技术,结合具体问题和需求,选择最适合的解决方案。

本文分享自华为云社区《知乎问题:如何说服技术老大用 Redis ?》,作者:勇哥java实战分享。

最近在某问答平台看到一个技术讨论:如何说服技术老大用Redis?

“他总觉得用Redis每次都要去请求,肯定是没有加载内存里快,项目一直是搞个map装下要缓存的数据,一个定时任务去刷新这个map……”

这个问题很微妙,可能这位同学内心深处,觉得 Redis 是所有应用缓存的标配。

缓存的世界很广阔,对于应用系统来讲,我们经常将缓存划分为本地缓存分布式缓存

本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无法共享缓存。

分布式缓存:和应用分离的缓存组件或服务,与本地应用隔离,多个应用可直接共享缓存。

1 缓存的本质

我们常常会讲:“加了缓存,我们的系统就会更快” 。

所谓的“更快”,本质上做到了如下两点:

  • 减小 CPU 消耗

    将原来需要实时计算的内容提前算好、把一些公用的数据进行复用,这可以减少 CPU 消耗,从而提升响应性能。

  • 减小 I/O 消耗

    将原来对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,从而提升响应性能。

假如可以通过增强 CPU、I/O 本身的性能来满足需求的话,升级硬件往往是更好的解决方案,即使需要一些额外的投入成本,也通常要优于引入缓存后可能带来的风险。

从开发角度来说,引入缓存会提高系统复杂度,因为你要考虑缓存的失效、更新、一致性等问题。

从运维角度来说,缓存会掩盖掉一些缺陷,让问题在更久的时间以后,出现在距离发生现场更远的位置上。

从安全角度来说,缓存可能泄漏某些保密数据,也是容易受到攻击的薄弱点。

因此,缓存是把双刃剑

2 本地缓存 JDK Map

JDK Map 经常用于缓存实现:

  • HashMap

    HashMap 是一种基于哈希表的集合类,它提供了快速的插入、查找和删除操作。可以将键值对作为缓存项的存储方式,将键作为缓存项的唯一标识符,值作为缓存项的内容。

  • ConcurrentHashMap

    ConcurrentHashMap 是线程安全的 HashMap,它在多线程环境下可以保证高效的并发读写操作。

  • LinkedHashMap

    LinkedHashMap 是一种有序的 HashMap ,它保留了元素插入的顺序,可以按照插入顺序或者访问顺序进行遍历。

  • TreeMap

    TreeMap 是一种基于红黑树的有序 Map,它可以按照键的顺序进行遍历。

笔者曾经负责艺龙红包系统,红包活动就是存储在 ConcurrentHashMap 中 ,通过定时任务刷新缓存 。

核心流程:

1、红包系统启动后,初始化一个 ConcurrentHashMap 作为红包活动缓存 ;

2、数据库查询所有的红包活动 , 并将活动信息存储在 Map 中 ;

3、定时任务每隔 30 秒 ,执行缓存加载方法,刷新缓存。

为什么红包系统会将红包活动信息存储在本地内存 ConcurrentHashMap 呢 ?

  • 红包系统是高并发应用,快速将请求结果响应给前端,大大提升用户体验;

  • 红包活动数量并不多,就算全部放入到 Map 里也不会产生内存溢出的问题;

  • 定时任务刷新缓存并不会影响红包系统的业务。

笔者见过很多单体应用都使用这种方案,该方案的特点是简洁易用,工程实现也容易 。

3 本地缓存框架

虽然使用 JDK Map 能快捷构建缓存,但缓存的功能还是比较孱弱的。

因为现实场景里,我们可能需要给缓存添加缓存统计过期失效淘汰策略等功能。

于是,本地缓存框架应运而生。

流行的 Java 缓存框架包括: Ehcache , Google Guava , Caffine Cache 。

下图展示了 Caffine 框架的使用示例。

虽然本地缓存框架的功能很强大,但是本地缓存的缺陷依然明显。

1、高并发的场景,应用重启之后,本地缓存就失效了,系统的负载就比较大,需要花较长的时间才能恢复;

2、每个应用节点都会维护自己的单独缓存,缓存同步比较头疼

4 分布式缓存

分布式缓存是指将缓存数据分布在多台机器上,以提高缓存容量和并发读写能力的缓存系统。分布式缓存通常由多台机器组成一个集群,每台机器上都运行着相同的缓存服务进程,缓存数据被均匀地分布在集群中的各个节点上。

Redis 是分布式缓存的首选,甚至我们一提到缓存,很多后端工程师首先想到的就它。

下图是神州专车订单的 Redis 集群架构 。将 Redis 集群拆分成四个分片,每个分片包含一主一从,主从可以切换。 应用 A 根据不同的缓存 key 访问不同的分片。

与本地缓存相比,分布式缓存具有以下优点:

1、容量和性能可扩展

通过增加集群中的机器数量,可以扩展缓存的容量和并发读写能力。同时,缓存数据对于应用来讲都是共享的。

2、高可用性

由于数据被分布在多台机器上,即使其中一台机器故障,缓存服务也能继续提供服务。

但是分布式缓存的缺点同样不容忽视。

1、网络延迟

分布式缓存通常需要通过网络通信来进行数据读写,可能会出现网络延迟等问题,相对于本地缓存而言,响应时间更长。

2、复杂性

分布式缓存需要考虑序列化、数据分片、缓存大小等问题,相对于本地缓存而言更加复杂。

笔者曾经也认为无脑上缓存 ,系统就一定更快,但直到一次事故,对于分布式缓存的观念才彻底改变。

2014年,同事开发了比分直播的系统,所有的请求都是从分布式缓存 Memcached 中获取后直接响应。常规情况下,从缓存中查询数据非常快,但在线用户稍微多一点,整个系统就会特别卡。

通过 jstat 命令发现 GC 频率极高,几次请求就将新生代占满了,而且 CPU 的消耗都在 GC 线程上。初步判断是缓存值过大导致的,果不其然,缓存大小在 300k 到 500k 左右。

解决过程还比较波折,分为两个步骤:

  1. 修改新生代大小,从原来的 2G 修改成 4G,并精简缓存数据大小 (从平均 300k 左右降为 80k 左右);
  2. 缓存拆成两个部分,第一部分是全量数据,第二部分是增量数据(数据量很小)。页面第一次请求拉取全量数据,当比分有变化的时候,通过 websocket 推送增量数据。

经过这次优化,笔者理解到:缓存虽然可以提升整体速度,但是在高并发场景下,缓存对象大小依然是需要关注的点,稍不留神就会产生事故。另外我们也需要合理地控制读取策略,最大程度减少 GC 的频率 , 从而提升整体性能。

5 多级缓存

开源中国网站最开始完全是用本地缓存框架 Ehcache 。

后来随着访问量的激增,出现了一个可怕的问题:“因为 Java 程序更新很频繁,每次更新的时候都要重启。一旦重启后,整个 Ehcache 缓存里的数据都被清掉。重启后若大量访问进来的话,开源中国的数据库基本上很快就会崩掉”。

于是,开源中国开发了多级缓存框架 J2Cache,使用了多级缓存 Ehcache + Redis 。

多级缓存有如下优势:

  1. 离用户越近,速度越快;
  2. 减少分布式缓存查询频率,降低序列化和反序列化的 CPU 消耗;
  3. 大幅度减少网络 IO 以及带宽消耗。

本地缓存做为一级缓存,分布式缓存做为二级缓存,首先从一级缓存中查询,若能查询到数据则直接返回,否则从二级缓存中查询,若二级缓存中可以查询到数据,则回填到一级缓存中,并返回数据。若二级缓存也查询不到,则从数据源中查询,将结果分别回填到一级缓存,二级缓存中。

2018年,笔者服务的一家电商公司需要进行 app 首页接口的性能优化。笔者花了大概两天的时间完成了整个方案,采取的是两级缓存模式,同时利用了 Guava 的惰性加载机制,整体架构如下图所示:

缓存读取流程如下:

1、业务网关刚启动时,本地缓存没有数据,读取 Redis 缓存,如果 Redis 缓存也没数据,则通过 RPC 调用导购服务读取数据,然后再将数据写入本地缓存和 Redis 中;若 Redis 缓存不为空,则将缓存数据写入本地缓存中。

2、由于步骤1已经对本地缓存预热,后续请求直接读取本地缓存,返回给用户端。

3、Guava 配置了 refresh 机制,每隔一段时间会调用自定义 LoadingCache 线程池(5个最大线程,5个核心线程)去导购服务同步数据到本地缓存和 Redis 中。

优化后,性能表现很好,平均耗时在 5ms 左右。最开始我以为出现问题的几率很小,可是有一天晚上,突然发现 app 端首页显示的数据时而相同,时而不同。

也就是说: 虽然 LoadingCache 线程一直在调用接口更新缓存信息,但是各个 服务器本地缓存中的数据并非完成一致。 说明了两个很重要的点:

1、惰性加载仍然可能造成多台机器的数据不一致

2、LoadingCache 线程池数量配置的不太合理, 导致了线程堆积

最终,我们的解决方案是:

1、惰性加载结合消息机制来更新缓存数据,也就是:当导购服务的配置发生变化时,通知业务网关重新拉取数据,更新缓存。

2、适当调大 LoadigCache 的线程池参数,并在线程池埋点,监控线程池的使用情况,当线程繁忙时能发出告警,然后动态修改线程池参数

6 没有银弹

没有银弹是 Fred Brooks 在 1987 年所发表的一篇关于软件工程的经典论文。

论文强调真正的银弹并不存在,而所谓的银弹则是指没有任何一项技术或方法可以能让软件工程的生产力在十年内提高十倍。

通俗来讲:在技术领域中没有一种通用的解决方案可以解决所有问题

技术本质上是为了解决问题而存在的,每个问题都有其独特的环境和限制条件,没有一种通用的技术或工具可以完美地解决所有问题。

虽然技术不断发展和进步,但是对于复杂的问题,仍需要结合多种技术和方法,进行系统性的思考和综合性的解决方案设计,才能得到最优解决方案。

回到文章开头的问题 ,如何说服技术老大用 Redis ?

假如应用就是一个单体应用,缓存可以不共享,通过定时任务刷新缓存对业务没有影响,而且本地内存可以 Hold 住缓存的对象大小,那么你的技术老大的方案没有问题。

假如应用业务比较复杂,需要使用缓存提升系统的性能,同时分布式缓存共享的特性对于研发来讲开发更加快捷,Redis 确实是个不错的选择,可以从研发成本、代码维护、人力模型等多个角度和技术老大提出自己的观点。

总而言之,在技术领域中,没有银弹。我们需要不断探索和研究新的技术,但同时也需要认识到技术的局限性,不盲目追求所谓的“银弹”,而是结合具体问题和需求,选择最适合的解决方案。

点击关注,第一时间了解华为云新鲜技术~

从缓存的本质说起,说服技术大佬用Redis的更多相关文章

  1. 分布式缓存集群方案特性使用场景(Memcache/Redis(Twemproxy/Codis/Redis-cluster))优缺点对比及选型

    分布式缓存集群方案特性使用场景(Memcache/Redis(Twemproxy/Codis/Redis-cluster))优缺点对比及选型   分布式缓存特性: 1) 高性能:当传统数据库面临大规模 ...

  2. bitmap技术解析:redis与roaringBitmap

    bitmap的表象意义是,使用一个01标识位来表示是否的状态,可以达到节省空间和高效判定的效果.在我们的实际工作中,也有着许多的应用场景,相信了解bitmap定会给你带来一些额外的收获. 1. bit ...

  3. 红眼技术博客 » redis连接池红眼技术博客 » redis连接池

    红眼技术博客 » redis连接池 redis连接池

  4. 【已转移】【缓存与性能优化】一篇文章搞掂:Redis

    本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新). 一.什么是Redis 全称: Remote Dictionary Server 远程字典服务器 实质: 一个缓存结构服务器或数据结 ...

  5. 二级缓存EhCache在几种应用技术的配置方法和步骤总结

    一:Spring和Ehcache缓存集成 业务问题:如果仓库不经常变动,大量进出库,总是需要查询仓库列表 (列表重复) ,使用缓存优化 ! 阅读spring规范29章节 第一步: 导入ehcache的 ...

  6. Unity-ECS(一)浅谈CPU缓存命中和Unity面向数据技术栈(DOTS)--笔记

    一,缓存类型 概念:局部性. 时间局部性:当前用到的一个存储器位置,不久的将来会被用到. 空间局部性:当前用到的一个存储器位置,附近的位置会被用到. 那么在CPU的层面,这两个局部性的特性就会被Cac ...

  7. Azure技术系列之Redis篇---第一章数据缓存

    嘈杂和忙碌的生活占据占据了生活的每一天,好久没有静下心来对自己喜欢的技术进行归纳总结了.痛定思痛,今天开始开荒,把之前研究的技术进行归纳总结,先从Azure的Redis的开发技术开始. Azure 的 ...

  8. 缓存技术比拼:Redis与Memcached的同与不同

    转至:http://developer.51cto.com/art/201603/507980.htm 在今天的文章中,我们将探讨Redis(REmote DIctionary Server).Red ...

  9. 数据库历险记(三) | 缓存框架的连环炮 数据库历险记(二) | Redis 和 Mecached 到底哪个好? 数据库历险记(一) | MySQL这么好,为什么还有人用Oracle? 面对海量请求,缓存设计还应该考虑哪些问题?

    数据库历险记(三) | 缓存框架的连环炮   文章首发于微信公众号「陈树义」,专注于 Java 技术分享的社区.点击链接扫描二维码,与500位小伙伴一起共同进步.微信公众号二维码 http://p3n ...

  10. python怎么自学?今日头条技术大佬的真实经历分享

    大家好,我是武州,27岁,目前在字节跳动担任Python后端工程师一职. (摆拍一下,假装是保安) 在开始今天的文章之前,不知道你们有没有遇到过这样的问题: 大学没学到什么实质技术,毕业后找不到高薪的 ...

随机推荐

  1. QT(3)-QTableView

    @ 目录 0 相关文章 1 说明 2 常用函数 2.1 clearSpans 2.2 setSpan 2.3 columnAt 2.4 rowAt 2.5 columnSpan 2.6 rowSpan ...

  2. BABYRE

    一道SMC,第一次做 主函数的伪代码,judge函数是关键函数,不过啥都没有 发现 judge 方法是判断的主要逻辑,在第 15 行时调用判断. 但是静态分析时不能生成 judge 的伪代码. 原因是 ...

  3. 从零开始学习Python

    从零开始学习Python是一个令人兴奋和有趣的过程.无论你是完全没有编程经验,还是已经熟悉其他编程语言,Python都可以成为你迈向程序员之路的理想起点. 首先,在开始学习之前,请确保在计算机上安装了 ...

  4. Ubuntu 20.04 挂载 NTFS 硬盘 / 格式化并挂载 EXT4 硬盘

    创建挂载目录 mkdir /mnt/hdd 此目录在最后一步中用得到. 确定要挂载的硬盘 fdisk -l 由于我们要挂载的是 NTFS 硬盘,根据上面的信息,可以确定 /dev/sda1 是我们要挂 ...

  5. Mockito - java单元测试

    原文地址 一.简介 Mockito是mocking框架,它让你用简洁的API做测试,简单易学,可读性强并且验证语法简洁. 官网: http://mockito.org 项目源码:https://git ...

  6. OpenAI宫斗,尘埃落定,微软成最大赢家

    周末被OpenAI董事会闹剧刷屏,ChatGPT之父Sam Altman前一天被踢出董事会,免职CEO,后一天重返OpenAI,目前结局未知. 很多同学想要围观,缺少背景知识,这里老章为大家简单介绍前 ...

  7. [python] 基于Tablib库处理表格数据

    Tablib是一个用于处理电子表格(如 Excel,CSV,JSON)的Python 库.它提供了一种简单而强大的方式来操作和处理数据.利用Tablib,我们可以轻松地读取.写入.过滤和转换各种类型的 ...

  8. Axure实战应用:Axure设计可视化大屏!

    Axure是一款功能强大的原型设计工具,可以用于设计可视化大屏.设计一个有效的可视化大屏需要考虑多个方面,包括布局规划.信息展示.交互设计等. 以下是一个详细的描述,希望对你有所帮助. 第一部分:可视 ...

  9. 性能测试Mysql之profiling参数

    一.查看profiling状态 mysql> select @@profiling; 0:表示为关闭 1:表示开启 二.开启 profiling mysql> SET profiling= ...

  10. MySQL笔记01: MySQL入门_1.2 MySQL下载安装与配置

    2.2 MySQL下载安装与配置 2.2.1 MySQL下载 MySQL中文官网:https://www.mysql.com/cn/ MySQL英文官网:https://www.mysql.com/ ...