Redis入门

Redis

简介

Redis是一个开源的key-value存储系统。存储在内存中。

相比于Memcached,value类型相对更多,string、list、set、zset、hash。

Redis操作都是原子性的,单线程多路IO复用

Redis支持各种不同方式的排序。

 

keys * 查看当前库所有key

exists key 判断某个key是否存在

type key 查看你的key是什么类型

del key 删除指定的key

unlink key 根据value选择非阻塞删除

expire key 10 为key设定过期时间

ttl key 查看还有多少秒过期 -1永不过期、-2已过期

 

string

String 类型是二进制安全的。意味着 Redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。

操作

set   <key><value>添加键值对 

*NX:当数据库中 key 不存在时,可以将 key-value 添加数据库
*XX:当数据库中 key 存在时,可以将 key-value 添加数据库,与 NX 参数互斥
*EX:key 的超时秒数
*PX:key 的超时毫秒数,与 EX 互斥

get <key>查询对应键值
append <key><value>将给定的<value> 追加到原值的末尾
strlen <key>获得值的长度
setnx <key><value>只有在 key 不存在时 设置 key 的值

incr <key>
将 key 中储存的数字值增 1
只能对数字值操作,如果为空,新增值为 1
decr <key>
将 key 中储存的数字值减 1
只能对数字值操作,如果为空,新增值为-1
incrby / decrby <key><步长>将 key 中储存的数字值增减。自定义步长。

mset <key1><value1><key2><value2> .....
同时设置一个或多个 key-value 对
mget <key1><key2><key3> .....
同时获取一个或多个 value
msetnx <key1><value1><key2><value2> .....
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

getrange <key><起始位置><结束位置>
获得值的范围,类似 java 中的 substring,前包,后包
setrange <key><起始位置><value>
用 <value> 覆写<key>所储存的字符串值,从<起始位置>开始(索引从 0 开始)。

setex <key><过期时间><value>
设置键值的同时,设置过期时间,单位秒。
getset <key><value>
以新换旧,设置了新值同时获得旧值。

数据结构

String 的数据结构为简单动态字符串(Simple Dynamic String,缩写 SDS)。是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.

List

 

单键多值
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

操作

lpush/rpush <key><value1><value2><value3> .... 从左边/右边插入一个或多个值。
lpop/rpop <key>从左边/右边吐出一个值。值在键在,值光键亡。

rpoplpush <key1><key2>从<key1>列表右边吐出一个值,插到<key2>列表左边。

lrange <key><start><stop>  按照索引下标获得元素(从左到右)
lrange mylist 0 -1 0 左边第一个,-1 右边第一个,(0-1 表示获取所有)
lindex <key><index>按照索引下标获得元素(从左到右)
llen <key>获得列表长度

linsert <key> before <value><newvalue>在<value>的后面插入<newvalue>插入值
lrem <key><n><value>从左边删除 n 个 value(从左到右)
lset<key><index><value>将列表 key 下标为 index 的值替换成 value

数据结构

List 的数据结构为快速链表 quickList。

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。

它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist。

因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的指针 prev 和 next。

Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个 ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

 

Set

Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,

这个也是 list 所不能提供的。Redis 的 Set 是 string 类型的无序集合。它底层其实是一个 value 为 null 的 hash 表,所以添加,删除,查找的复杂度都是 O(1)。

一个算法,随着数据的增加,执行时间的长短,如果是 O(1),数据增加,查找数据的时间不变

操作

sadd <key><value1><value2> .....
将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers <key>取出该集合的所有值。
sismember <key><value>判断集合<key>是否为含有该<value>值,有 1,没有 0
scard<key>返回该集合的元素个数。

srem <key><value1><value2> .... 删除集合中的某个元素。
spop <key>随机从该集合中吐出一个值。
srandmember <key><n>随机从该集合中取出 n 个值。不会从集合中删除 。
smove <source><destination>value 把集合中一个值从一个集合移动到另一个集合
sinter <key1><key2>返回两个集合的交集元素。
sunion <key1><key2>返回两个集合的并集元素。
sdiff <key1><key2>返回两个集合的差集元素(key1 中的,不包含 key2 中的)

数据结构

Set 数据结构是 dict 字典,字典是用哈希表实现的。Java 中 HashSet 的内部实现使用的是 HashMap,只不过所有的 value 都指向同一个对象。Redis 的 set 结构也是一样,它的内部也使用 hash 结构,所有的 value 都指向同一个内部值。

Hash

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

用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息,如果用普通的 key/value 结构来存储

操作

hset <key><field><value>给<key>集合中的 <field>键赋值<value>
hget <key1><field>从<key1>集合<field>取出 value
hmset <key1><field1><value1><field2><value2>... 批量设置 hash 的值
hexists<key1><field>查看哈希表 key 中,给定域 field 是否存在。
hkeys <key>列出该 hash 集合的所有 field
hvals <key>列出该 hash 集合的所有 value
hincrby <key><field><increment>为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx <key><field><value>将哈希表 key 中的域 field 的值设置为 value ,当且仅当域field 不存在 .

数据结构

Hash 类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value 长度较短且个数较少时,使用ziplist,否则使用hashtable。

Zset

Redis 有序集合 zset 与普通集合 set 非常相似,是一个没有重复元素的字符串集合。

不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。

因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

操作

zadd <key><score1><value1><score2><value2>…     将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

zrange <key><start><stop> [WITHSCORES]   返回有序集 key 中,下标在<start><stop>之间的元素

带 WITHSCORES,可以让分数一起和值返回到结果集。 zrangebyscore key minmax [withscores] [limit offset count]

返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。

zrevrangebyscore key maxmin [withscores] [limit offset count]   同上,改为从大到小排列。

zincrby <key><increment><value> 为元素的 score 加上增量

zrem <key><value>删除该集合下,指定值的元素
zcount <key><min><max>统计该集合,分数区间内的元素个数
zrank <key><value>返回该值在集合中的排名,从 0 开始。

数据结构

SortedSet(zset)是 Redis 提供的一个非常特别的数据结构,一方面它等价于 Java的数据结构 Map<String, Double>,可以给每一个元素 value 赋予一个权重 score,另一方面它又类似于 TreeSet,内部的元素会按照权重 score 进行排序,可以得到每个元素的名次,还可以通过 score 的范围来获取元素的列表。

zset 底层使用了两个数据结构
(1)hash,hash 的作用就是关联元素 value 和权重 score,保障元素 value 的唯
一性,可以通过元素 value 找到相应的 score 值。

(2)跳跃表,跳跃表的目的在于给元素 value 排序,根据 score 的范围获取元素列表。

 

Bitmaps

(1) Bitmaps 本身不是一种数据类型, 实际上它就是字符串(key-value) ,但是它可以对字符串的位进行操作。

(2) Bitmaps 单独提供了一套命令, 所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。 可以把 Bitmaps 想象成一个以位为单位的数组,数组的每个单元只能存储 0 和 1, 数组的下标在 Bitmaps 中叫做偏移量。

 

 

 操作

setbit<key><offset><value>设置 Bitmaps 中某个偏移量的值(0 或 1)   偏移从0开始

getbit<key><offset>获取 Bitmaps 中某个偏移量的值   offset 位的值,从 0 开始算

  bitcount
统计字符串被设置为 1 的 bit 数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指 bit 组的字节的下标数,二者皆包含。

举例: K1 【01000001 01000000  00000000 00100001】,对应【0,1,2,3】

bitcount<key>[start end] 统计字符串从 start 字节到 end 字节比特值为 1 的数量

  bitop 

bitop  and(or/not/xor) <destkey> [key…] 

bitop 是一个复合操作, 它可以做多个 Bitmaps 的 and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在 destkey 中。

 

 

bitfield key1 GET u3 0 从下标0处开始读3位,结果无符号位(二进制首位为符号位)

 

GEO

 

 

 

 Redis 的发布和订阅 

1、 打开一个客户端订阅 channel1
    SUBSCRIBE channel1

2、打开另一个客户端,给 channel1 发布消息 hello

    publish channel1 hello

3、打开第一个客户端可以看到发送的消息

注:发布的消息没有持久化,如果在订阅的客户端收不到 hello,只能收到订阅后发布的消息

Redis 的事务

Redis 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事
务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis 事务的主要作用就是串联多个命令防止别的命令插队

 

Multi  Exec  discard 

从输入 Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入
Exec 后,Redis 会将之前的命令队列中的命令依次执行。
组队的过程中可以通过 discard 来放弃组队。

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

WATCH key [key ...]

在执行 multi 之前,先执行 watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

unwatch
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。

 

Redis 事务三特性

单独的隔离操作
  事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

没有隔离级别的概念
  队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都
不会被实际执行

不保证原子性
  事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

 

Redis持久化

Redis 提供两种持久化机制 RDB 和 AOF 机制:

1、 RDB(Redis DataBase)持久化方式:

是指用数据集快照的方式半持久化模式 )记录 redis数据库的所有键值对 ,在某个时 间点将数据写入一 个临时文件,持久化结束 后,用这个临时文件替换上次持久化的文件,达到数据恢复 。

redis-cli中使用save命令保存dump.rdb文件,或者他会定期保存,这是在主线程中执行的,执行此命令,其他命令就会阻塞。也可以使用bgsave来后台保存,不会阻塞。 redis停机时,会自动RDB一次。

  优点:

  1、只 有一 个文 件 dump.rdb,方 便持 久化 。

  2、容 灾性 好,一个文件 可以 保存 到安 全的 磁盘 。

  3、性能最大化 ,fork 子进程来完成写操作, 让主进程继续 处理 命令 ,所 以是 IO最大化。使用 单独子进 程来进行持久化 ,主进程不会进 行任 何 IO 操作 。保证了 redis的高 性能 ) 4.相对于数据集大时 ,比 AOF的启动效 率更高。

  缺点:

  数据 安全 性低 。RDB 是间 隔一 段时 间进 行持 久化 ,如果 持久 化之 间 redis 发生故障 ,会 发生 数据 丢失 。所以这种方式更适合数据要求不严谨的时候 )

bgsave虽然是异步的,但是从主线程中fork页表是串行的。同时,写操作会copy-on-write,这要求我们执行RDB要给redis预留内存空间。

 

2、 AOFAppend-only file)持久化方式:

是指所有的命令行记录以 redis 命令请求协议的格式完全持久化存储)保存为 aof 文件 。

  优点:

  1、数 据安 全, aof 持久 化可 以配 置 appendfsync 属性 ,有 always,每 进行 一次命令 操作 就记 录到 aof 文件 中一 次。

  2、通 过 append 模式 写文 件, 即使 中途 服务 器宕 机, 可以 通过 redis-check-aof工具 解决 数据 一致 性问 题。

  3、AOF 机制 的 rewrite 模式 。AOF文件没被 rewrite 之前( 文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如 误操作的 flushall))

  缺点:

  1、 AOF文件比RDB文件大,且恢复速度慢 。
  2、数据集大的时候 ,比rdb启动效率低。

 

 

短信登陆功能

 

商品查询缓存功能

整体流程

缓存更新策略

 

 尽管方案一要自己硬编码,但是可控性高,优先使用。

 

两种方案都有可能发生不一致,但是第二种发生概率比较低,同时加上过期时间,优先选择。

 

  缓存穿透、击穿、雪崩

缓存穿透:

客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库并查出空来。

  解决方案,1、发生穿透时,缓存空对象加ttl。 

        优点:实现方便、维护方便

        缺点:额外的内存开销、有不一致风险(ttl设置小一点)。

          优先选择。

       2、布隆过滤,在查redis之前先走布隆过滤器。

        优点:内存占用少、没有多余的key

        缺点:实现复杂、存在误判可能(还是会穿透)

缓存击穿:

少数key过期,被高并发访问并且重建这部分缓存比较复杂。

1、利用互斥锁,不让多个线程都去创建。

 2、逻辑过期(不设ttl)

 

 

 

缓存雪崩:

同一时段内,大量热点key过期或者redis宕机,导致大量请求到达数据库。

  给不同的key添加随机的ttl

  利用redis集群,提高服务可用性

  给缓存业务添加降级限流策略

  给业务添加多级缓存

 

 

秒杀、redis计数器、分布式锁、lua脚本、三种消息队列

秒杀-全局唯一id

String类型自增长拼接其他信息。

实现秒杀

  

超卖问题

  乐观锁防并发。版本号或者where 库存 > 0。

一人一单问题

  单机:

    

     查询当前用户是否有多个订单->新增订单这段逻辑要加锁。不要直接锁方法,而是锁当前用户。

  集群:

    分布式锁。

分布式锁

  setnx存在问题:

    1、不可重入

    2、不可重试

    3、超时释放

    4、主从一致问题。

  Redisson分布式锁。

 可重入:

  

   标识线程和重入的次数。每次重入+1,释放锁就-1而不是直接删除锁。

  可重试与watckdog:

  主从一致性:

    简单粗暴,不要主从,集群全部操作正确才算正确。宕机一个,则其他两个中获取就行。multiLock 都是可重入的。

 

 

优化秒杀与消息队列异步秒杀

 黑马redis秒杀异步

基于set点赞列表和zset的点赞排行榜功能

set记录点赞过的用户,让一个用户只点赞一次,在点就是取消点赞。

展示前五个最早点赞的用户。

基于set的好友关注、取关、共同关注、消息推送

共同关注用set保存用户关注的人,sinter求交集。

feed流(投喂),给用户推感心趣的内容(无限下拉刷新)。

三种方案:拉、推、推拉结合

一开始只有一份,只有赵六要读的时候才会开始将关注的人的发件箱copy到收件箱,且用完就丢弃,节省空间。缺点是慢,读的时候才copy并排序。

直接发到每一个粉丝的收件箱,且排序好。

粉丝数少就推,粉丝数多的给活跃粉丝推,普通粉丝拉。

 收件箱用zset实现,因为zset用时间戳当sorce排序后,既能用下标找,又能像map用value找。

 

bitmap用户签到统计

GeoHash地理位置附近商店

UV统计

 

分布式

单机问题:

数据丢失问题:持久化策略

并发问题:主从集群、读写分离

故障恢复:哨兵机制

海量数据存储能力小:分片集群、利用插槽机制动态扩容

搭建一主两从

 从节点 配置主节点密码 masterauth <master-password>

从节点配置主机ip端口,主机不需要配置任何东西。

SLAVEOF NO ONE

info replication 查看主从信息。

注意保护模式开启否,设置可访问的ip,固定设置好ip

replica-announce-ip "192.168.21.234"

主从同步原理

全量同步:

 

增量同步:

 

同步优化:

1、减少全量同步  2、加快全量同步速度

 哨兵机制

哨兵的作用

 slave宕机重启找master恢复数据,master宕机的情况下呢?主从切换——哨兵机制

 

 搭建哨兵集群

redis-cli -h 127.0.0.1 -p 26379 登陆哨兵

sentinel auth-pass mymaster admin

所有哨兵都要配置主redis

多ip要直接指定ip。有密码要指定密码。

 

Spring和redis

 redis分片集群

https://www.bilibili.com/video/BV1cr4y1671t?p=108&vd_source=6f7e1b98cd04834fea7f81d76fbe0ad7

 搭建分片集群

散列插槽

集群伸缩

故障转移

redisTemplate

多级缓存

浏览器缓存->nginx本地缓存->redis缓存->JVM缓存->数据库

Lua

BigKey

bigkey

Bigkey删除都会很慢,要异步的删除unlink

选择合适的数据类型

 

posted @ 2023-04-09 22:49  长寿奉孝  阅读(24)  评论(0编辑  收藏  举报