Redis


基础

配置

数据结构

1.String

字符串是Redis最简单的数据结构,字符串值内容是二进制安全的,这意味着程序可以把数字、文字、图片、视频等都赋给这个值。

2.List

列表是由若干插入顺序排序的字符串元素组成的集合。可以理解为多个字符串组成一个集合对象,并按照链表的插入顺序排序,在读写操作时只能从其两端开始(由链表的寻址方式决定)。有序可重复

可用于聊天记录、评论等无需调整字符串顺序,而又需要快速响应的场景。

3.Set

集合由不重复且无序的字符串元素构成的一个整体。

4.Hash

散列表可以存储多个键值对的映射,是无序的数据集合。键的内容必须是唯一的,键内容之间可以采用 “:” 隔离 。例如 Book:name。

键内容必须是字符串型,但值可以是字符串型也可以是数字型。

5.zset

有序集合(sorted set)和Hash一样都是由键值对构成数据集合,主要区别是有序集合根据值进行自动排序,而Hash不排序;有序集合可以对值直接进行操作,而Hash通过键查找获取值。键字符串有序且必须唯一,值可以重复

常用命令

优点

  • 基于内存,读写速度快
  • 支持数据的持久化
  • 支持master-slave模式的数据备份
  • 丰富的数据类型
  • 原子性操作
  • 丰富的特性

提高

管道

Redis引入管道,为了提高客户端与服务端之间的多命令的执行效率

原理

Redis数据库从客户端到服务端传输命令,采用 请求-响应 的TCP通信协议。一个命令从客户端发出查询请求,往往采用阻塞方式监听socket接口,直到服务器端返回执行结果信号,一个命令的执行时间周期才结束。这个时间周期叫做往返时间(Round Trip Time,RTT)

如果一条命令的RTT延迟100ms,那么10条连续的命令就会消耗1000ms的时间。于是采用了管道技术:批量发送命令,在服务器端一起执行,最后把结果一次性发送会客户端。可以减少命令的返回次数、减少阻塞时间。

使用建议

(1)管道技术会占用服务器端内存资源,一般建议一次管道最大发送命令数量限制在10000条以内

(2)Lua脚本技术无论是内存的消耗,还是速度都比管道技术占优势

分布式集群

集群安装

模拟节点故障

加减节点

Lua脚本应用

Lua本身是一种独立的脚本编程语言,优点是能很方便的被嵌入到其他语言中,然后被调用

为什么

管道技术提高了Redis多命令的执行效率,那么我们是否可以把客户端应用系统的部分代码直接在服务器端运行,进一步提高代码的执行效率,同时可以实现在服务器端更换代码的目标?

随着大型电子商务平台对客户操作行为研究的深入,技术人员希望在广告行为分析重定向时,可以快速替换该部分代码,而不需要去应用系统服务器上更新业务系统的执行代码。这样的替换过程,几乎不影响用户的操作使用。(在下面会讲到具体实现)

优点

Redis本身没有类似关系数据库的存储过程的功能,但Redis设计者很聪明,直接把世界上最优秀的嵌入式脚本语言Lua内嵌到Redis系统之中,使Redis具备了在数据库服务器端运行逻辑运算代码的功能。有以下优势:

  • 减少网络开销

    如刚才所说,将部分特殊代码直接放到服务器端执行,减少交互产生的额外的网络开销问题,大幅度提高应用系统的响应性能。

  • 原子性操作

    Lua脚本在服务器端执行时,采用排他性行为,也就是脚本代码执行时,其他命令或脚本无法在同一个服务器端执行(除了极个别命令外)。同时命令要么都被执行,要么都被放弃,具有完整的执行原子性

  • 服务器端快速代码替换

    经常需要变化业务规则或算法的代码,可以考虑放到服务器端交给Lua脚本统一执行。因为Lua脚本第一次被执行后,将一直保存在服务器端的脚本缓存中,可以供其他客户端持续调用,效率高、占用内存少。当需要修改Lua脚本代码时,只需要更新内存中的执行脚本内容即可,无需修改业务系统的原始代码,几乎不对客户端产生影响。

使用建议

  • 不要把执行速度慢的代码纳入Lua脚本中,否则将严重影响客户端的使用性能
  • 确保Lua脚本编写正确,尤其是不能出现无限循环这样的低价错误
  • 不能滥用Lua脚本,把最需要、最重要的任务交给它处理。如利用它的原子性特点处理高价值的数据修改一致性
  • Lua脚本代码条数不应太多,应该是非常简练的、轻量级的

优化

读写分离

内存配置优化

  • 压缩存储
  • 合理选择存储结构
  • 分片
  • 字符串优化
  • 内存使用管理

数据持久化

AOF持久

  • 实时备份

RDB持久(默认)

  • 快照持久化

缓存

缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。每次都要去数据库再查询一遍,然后返回空,这样请求就绕过缓存直接查数据库,如果恶意请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

解决方案
  • 1. 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。
  • 2. 也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

缓存击穿

缓存中的一个 Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。

解决方案

对缓存查询加锁,如果 KEY 不存在,就加锁,然后查 DB 入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入 DB 查询。

缓存雪崩

缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(大面积缓存击穿
(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

解决方案

大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就是将缓存失效时间分散开。

如何避免

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1 为原始缓存,A2 为拷贝缓存,A1 失效时,可以访问 A2,A1 缓存失效时间设置为短期,A2 设置为长期
3:不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

缓存预热

缓存预热这个应该是一个比较常见的概念,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决方案

1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存;

缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的 key 是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在 95~100% 之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于 90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止 Redis 服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis 出现问题,不去数据库查询,而是直接返回默认值给用户。

缓存失效策略

redis 采用的是定期删除 + 惰性删除策略。

  • 1、定时删除: 在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键 的过期时间来临时,立即执行对键的删除操作。
  • 2、惰性删除: 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是 否过期,如果过期的话,就删除该键; 如果没有过期,就返回该键。
  • 3、定期删除: 每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至 于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
为什么不用定时删除策略

定时删除, 用一个定时器来负责监视 key, 过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 key, 因此没有采用这一策略.

如何工作

定期删除,redis 默认每个 100ms 检查,是否有过期的 key, 有过期 key 则删除。需要说明的是,redis 不是每个 100ms 将所有的 key 检查一次,而是随机抽取进行检查 (如果每隔 100ms, 全部 key 进行检查,redis 岂不是卡死)。因此,如果只采用定期删除策略,会导致很多 key 到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个 key 的时候,redis 会检查一下,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

如果定期删除没删除 key。然后你也没即时去请求 key,也就是说惰性删除也没生效。这样,redis 的内存会越来越高。那么就应该采用内存淘汰机制。
在 redis.conf 中有一行配置
maxmemory-policy volatile-lru

该配置就是配内存淘汰策略的

回收策略(淘汰策略)
  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最 少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过 期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意 选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 .
  • no-enviction(驱逐):禁止驱逐数据
    注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘 汰数据还是从全部数据集淘汰数据,后面的 lru、ttl 以及 random 是三种不同的 淘汰策略,再加上一种 no-enviction 永不回收的策略。
使用策略规则
  • 1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率 低,则使用 allkeys-lru
  • 2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random

应用

广告访问

电子广告特点

  • 要求客户界面响应快速

    如果一个带广告界面被浏览时,响应动作不流畅,甚至产生明显的广告图片托屏现象,用户印象一般不会太好

  • 广告投放具有临时性

    广告投放一般是短期行为,频繁更换。通过Redis+Java技术,进行相对独立管理比较合理

  • 广告内容都通过代码生成,并缓存到Redis服务端

代码

商品推荐

为了快速推荐商品,需要事先把推荐商品信息通过Redis缓存到内存上。这样的预存动作一般发生在用户浏览某类商品之前的几秒钟。如根据用户的浏览喜好、检索字,生成对应的推荐商品列表。

商品推荐属于临时行为,可以设置n分钟后过期,一般保证被业务系统短时间内调用一两次后将自动删除。淘宝、京东一般也不会将同一件商品推荐多次.

购物车

购物车的核心是:商品ID、商品购买数量、销售价格等信息。

行为记录

用户从登录开始,就会产生很多相关的操作信息,如客户端访问连接信息:IP地址、端口号、访问时间、经纬度等;又比如:用户在网站上的点击行为记录:访问网页ID号、点击时间、鼠标的坐标点、停留时间等。

替代session

session记录了一个终端用户从登录到退出的刚才,临时存储于服务端内存上,但是当同时在线用户过多时,如几十万、数百万,对服务器内存压力将很多。

Redis常驻内存、快速处理、相对低内存占用、对Key对象时限的灵活控制、分布式处理等优点,可以很好的处理session所承担的临时数据的存取任务。

分页缓存

当商品数量很大时,必须分页处理,否则,用户是无法忍受一个非常长的、呆板的浏览界面。

为此,需要把商品信息分页显示,然后为其提供下翻到第二个页面的功能,这样的分页浏览功能在传统Web技术下已经实现,只不过响应速度不够快,不能让用户体验到如丝般的顺滑,因此利用Redis的高效缓存处理。

参考自:《NoSQL 数据库入门与时间(基于MongoDB、Redis)》

涉及的源代码部分:https://github.com/lxlxlx1020/nosql_bookdemo


  TOC