分布式-分布式缓存Redis

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hardworking0323/article/details/81503906

使用Redis代替数据库查询缓存的原因:

  • (1) 缓存架构:
    数据库查询缓存通常每个数据库只有一个实例,因此存储内容受数据库服务器可用内存限制,可缓存数据有限。而Redis可采用高速分布式缓存服务器结构,不受数据库服务器约束,可扩展性更好。

  • (2) 缓存有效性:
    数据库查询缓存只要在发生写操作时就会失效,即使更新的是数据库中的其他行。而Redis可通过键值将数据进行散列缓存,有效降低缓存的更新频率,从而提髙缓存的有效性。

  • (3) 缓存数据类型:
    数据库查询缓存只能缓存数据库行,对社交网站好友动态显示等典型业务所需要的组合数据缓存缺乏有效支持,而Redis理论上可缓存任何内容。因此可以将分散在数据库中的关系或者列表组合后进行缓存,以提高缓存数据的针对性和效率。

一、Redis常用五大数据类型

1.1 String(字符串)

  • string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
  • string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

1.2 Hash(哈希)

  • Redis hash 是一个键值对集合。
  • Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
  • 类似Java里面的Map<String,Object>

1.33 List(列表)

  • Redis 列表是简单的字符串列表,按照插入顺序排序。
    你可以添加一个元素导列表的头部(左边)或者尾部(右边)。它的底层实际是个链表

1.4 Set(集合)

  • Redis的Set是string类型的无序集合。它是通过HashTable实现实现的,

1.5 zset(sorted set:有序集合)

  • Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
  • 不同的是每个元素都会关联一个double类型的分数。
    redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

二、redis应用场景

2.1 缓存——热数据

热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大到冒泡的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。

结合具体应用需要注意一下:很多人用spring的AOP来构建redis缓存的自动生产和清除,过程可能如下:

  • Select 数据库前查询redis,有的话使用redis数据,放弃select 数据库,没有的话,select 数据库,然后将数据插入redis
  • update或者delete数据库钱,查询redis是否存在该数据,存在的话先删除redis中数据,然后再update或者delete数据库中的数据

上面这种操作,如果并发量很小的情况下基本没问题,但是高并发的情况请注意下面场景:

为了update先删掉了redis中的该数据,这时候另一个线程执行查询,发现redis中没有,瞬间执行了查询SQL,并且插入到redis中一条数据,回到刚才那个update语句,这个悲催的线程压根不知道刚才那个该死的select线程犯了一个弥天大错!于是这个redis中的错误数据就永远的存在了下去,直到下一个update或者delete。

2.2 计数器

诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能!爽。

命令:INCRBY

当然爽完了,别忘记持久化,毕竟是redis只是存了内存!

2.3 队列

相当于消息系统,ActiveMQ,RocketMQ等工具类似,但是个人觉得简单用一下还行,如果对于数据一致性要求高的话还是用RocketMQ等专业系统。

由于redis把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务

队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用

2.4 位操作(大数据处理)

用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。

想想一下腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,你能怎么做?千万别说给每个用户建立一个key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多,腾讯光这个得多花多少钱。。)好吧。这里要用到位操作——使用setbit、getbit、bitcount命令。

原理是:

redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示我们上面例子里面的用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,上面我说的几个场景也就能够实现。用到的命令是:setbit、getbit、bitcount

2.5 分布式锁与单线程机制

验证前端的重复请求(可以自由扩展类似情况),可以通过redis进行过滤:每次请求将request Ip、参数、接口等hash作为key存储redis(幂等性请求),设置多长时间有效期,然后下次请求过来的时候先在redis中检索有没有这个key,进而验证是不是一定时间内过来的重复提交

秒杀系统,基于redis是单线程特征,防止出现数据库“爆破”

单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
https://blog.csdn.net/bird73/article/details/79792548

全局增量ID生成,类似“秒杀”

redis分布锁缺陷

现有的实现在集群上表现不好

原因
官方推荐的实现有三个特性
一,互斥,任何时候只能有一个客户端获得锁
二,可用,无死锁,只要等待下去最终都能获得锁
三,容错

2.6 最新列表

例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List,一个个顺序都塞进去就可以啦。不过万一内存清掉了咋办?也简单,查询不到存储key的话,用mysql查询并且初始化一个List到redis中就好了。

2.7 排行榜

谁得分高谁排名往上。命令:ZADD(有续集,sorted set)

最近在研究股票,发现量化交易是个非常好的办法,通过臆想出来规律,用程序对历史数据进行验证,来判断这个臆想出来的规律是否有效,这玩意真牛!有没有哪位玩这个的给我留个言,交流一下呗。

三、redis使用规范

3.1 键值规范设计

3.1.1 key名设计

(1)【建议】: 可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:idugc:video:1

**(2)【建议】:简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视
例如:user:{uid}:friends:messages:{mid}简化为u:{uid}🇫🇷m:{mid}。

(3)【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符

3.1.2 value设计

【强制】:拒绝bigkey(防止网卡流量、慢查询)
string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。

反例:一个包含200万个元素的list。

非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法

【推荐】:选择适合的数据类型。

反例:

set user:1:name tom
set user:1:age 19
set user:1:favor football

正例:

hmset user:1 name tom age 19 favor football

3.2 设置超时时间[最重要]

控制key的生命周期,redis不是垃圾桶。
建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。

  • 目前有许多key没有设置超时时间,导致一直占用内存。
  • 需要增加操作步骤,设置超时时间。时间尽量短。
  • 某些业务要求key长期有效。

可以在每次写入时,都设置超时时间,让超时时间顺延。(比如token/session机制 授权通过每调用一次口,授权时间增加30m)

  • 短的超时时间,如 5分钟,10分钟,30分钟,1小时,3小时,1天等
  • 长的超时时间,如 7天,15天,1个月,3个月,6个月等
  • 示例代码如下:
    • 设置有效期初始设置有效期
    • 如果存在key,设置有效期

3.3 高频和低频分离

  • 高频数据存入Redis缓存,低频数据不要存入Redis缓存。
    高频数据是经常访问的数据,在这里做好压力缓冲就行了。对于大量数据和列表数据尤其适用。如,某商店的所有评价数据,总共有5000条之多,最近的30条(高频)可能是最常访问的,可以存入Redis缓存,其他的数据(低频)都不需要存缓存。

3.4 合理使用数据类型

  • 结合具体业务,设置合理的数据结构,找出更好的选择。
  • 集合结构还可以减少key的个数。

例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)

3.5 尽量使用字符串格式

  • 可视化,便于查看和管理。
  • 特别是在大批量数据的时候,效果明显。

3.5 合理设置key的格式

  • 多系统在共用缓存,需要key唯一。
  • 合适的key,便于查看,统计,排错。
  • key的格式,如:系统名+业务名+业务数据+其他

3.7 减少key的个数

  • 为了过期管理,合理减少key。
    比如,把key-value数据聚合,放到map、list里面。一个集合结构里面就可以包含很多个小数据。

3.8 精细化运营

之前的使用方法一直是粗放式。业务量小时,没问题;业务量大了,就各种问题。为了未来系统稳定,为了每个人的职业成长,需要学会精细化运营。深入分析业务流程,合理安排数据结构,合理使用公共资源,优化读写效率,提高系统抗风险能力。

四、redis和memcached的区别

观点一

  • 1、Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等;
  • 2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;
  • 3、虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘;
  • 4、过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10;
  • 5、分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从;
  • 6、存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);
  • 7、灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;
  • 8、Redis支持数据的备份,即master-slave模式的数据备份;

在这里插入图片描述

4.1 什么时候倾向于选择redis?

业务需求决定技术选型,当业务有这样一些特点的时候,选择redis会更加适合。

4.1.1 复杂数据结构

  • value是哈希,列表,集合,有序集合这类复杂的数据结构时,会选择redis,因为mc无法满足这些需求。
    最典型的场景,用户订单列表,用户消息,帖子评论列表等。

4.1.2 持久化

  • mc无法满足持久化的需求,只得选择redis。

但是,这里要提醒的是,真的使用对了redis的持久化功能么?

千万不要把redis当作数据库用:

  • (1)redis的定期快照不能保证数据不丢失

  • (2)redis的AOF会降低效率,并且不能支持太大的数据量

不要期望redis做固化存储会比mysql做得好,不同的工具做各自擅长的事情,把redis当作数据库用,这样的设计八成是错误的。

4.1.3 缓存场景,开启固化功能,有什么利弊?

如果只是缓存场景,数据存放在数据库,缓存在redis,此时如果开启固化功能:

优点是,redis挂了再重启,内存里能够快速恢复热数据,不会瞬时将压力压到数据库上,没有一个cache预热的过程。

缺点是,在redis挂了的过程中,如果数据库中有数据的修改,可能导致redis重启后,数据库与redis的数据不一致。

因此,只读场景,或者允许一些不一致的业务场景,可以尝试开启redis的固化功能。

4.1.4 天然高可用

redis天然支持集群功能,可以实现主动复制,读写分离。

redis官方也提供了sentinel集群管理工具,能够实现主从服务监控,故障自动转移,这一切,对于客户端都是透明的,无需程序改动,也无需人工介入。

而memcache,要想要实现高可用,需要进行二次开发,例如客户端的双读双写,或者服务端的集群同步。

但是,这里要提醒的是,大部分业务场景,缓存真的需要高可用么?

  • (1)缓存场景,很多时候,是允许cache miss

  • (2)缓存挂了,很多时候可以通过DB读取数据

所以,需要认真剖析业务场景,高可用,是否真的是对缓存的主要需求?

画外音:即时通讯业务中,用户的在线状态,就有高可用需求。

4.1.5 存储的内容比较大

memcache的value存储,最大为1M,如果存储的value很大,只能使用redis。

4.2 什么时候倾向于memcache?

4.2.1 纯KV,数据量非常大,并发量非常大的业务,使用memcache或许更适合。

这要从mc与redis的底层实现机制差异说起。

内存分配

memcache使用预分配内存池的方式管理内存,能够省去内存分配时间。

redis则是临时申请空间,可能导致碎片。

从这一点上,mc会更快一些。

虚拟内存使用

memcache把所有的数据存储在物理内存里。

redis有自己的VM机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发swap,把冷数据刷到磁盘上。

从这一点上,数据量大时,mc会更快一些。

网络模型

memcache使用非阻塞IO复用模型,redis也是使用非阻塞IO复用模型。

但由于redis还提供一些非KV存储之外的排序,聚合功能,在执行这些功能时,复杂的CPU计算,会阻塞整个IO调度。

从这一点上,由于redis提供的功能较多,mc会更快一些。

线程模型

memcache使用多线程,主线程监听,worker子线程接受请求,执行读写,这个过程中,可能存在锁冲突。

redis使用单线程,虽无锁冲突,但难以利用多核的特性提升整体吞吐量。

从这一点上,mc会快一些。

4.3 总结

4.3.1 代码可读性,代码质量对比

看过mc和redis的代码,从可读性上说,redis是我见过代码最清爽的软件,甚至没有之一,或许简单是redis设计的初衷,编译redis甚至不需要configure,不需要依赖第三方库,一个make就搞定了。

而memcache,可能是考虑了太多的扩展性,多系统的兼容性,代码不清爽,看起来费劲。

例如网络IO的部分,redis源码1-2个文件就搞定了,mc使用了libevent,一个fd传过来传过去,又pipe又线程传递的,特别容易把人绕晕。

画外音:理论上,mc只支持kv,而redis支持了这么多功能,mc性能应该高非常多非常多,但实际并非如此,真的可能和代码质量有关。

4.3.2 水平扩展的支持

不管是mc和redis,服务端集群没有天然支持水平扩展,需要在客户端进行分片,这其实对调用方并不友好。如果能服务端集群能够支持水平扩展,会更完美一些。

五 缓存与数据库不一致,咋办

引入Redis缓存后系统访问数据库的基本过程:

  • 系统需要读取后台数据时,先检查数据是否存在于(Redis缓存)中,
  • 若存在则直接从其中读取
  • 若不存在则从(数据库)中读取并保存在(Redis缓存)中;
  • 当(数据库)中数据发生更新时,需要将更新后的内容同步到(Redis缓存)实例中。
展开阅读全文

没有更多推荐了,返回首页