Redis从基础到实战

1 Redis数据类型

CLI命令:
object encoding xxx - 查看保存当前Key值的数据结构

Redis命令参考:
http://redisdoc.com/index

1.1 String类型

最常用的数据类型,底层编码:int、enbstr、row

CLI命令:set、get、incr、incrby、mset、mget

1.2 hash类型 数组+链表

CLI命令:hget、hmget、hset、hmset、hkeys、hvals、hgetall、hdel、hlen

1.2.1 存储结构:ziplist

ziplist是使用连续的内存空间存储数据,读写效率更高。
使用条件:
一个hash对象保存的filed数量<512个
一个hash对象中所有的field和value的字符串长度都<64byte

超过ziplist使用条件的hash类型自动使用hashtable存储结构存储数据。

1.2.2 存储结构:hashtable

dict-dictht-dictEntry[]-dictEntry
dictht下的dictEntry中的链表长度平均大于5,则扩容dictht

Hash可以存储 String、对象 类型

1.3 list列表(有序字符串)

存储有序的字符串,元素可以重复
lpush、rpush、lpop、rpop、lindex、lrange、blpop、brpop(等待)

1.3.1 ziplist存储结构(早期3.2版本之前)

1.3.2 linkedlist存储结构(早期3.2版本之前)

1.3.3 quicklist(新版结构)

本质是一个数组+链表的数据结构

应用场景:
可以用作队列、栈
可以用作一些有序的内容,比如消息列表、文章列表、评论列表、公告列表、活动列表

1.4 set集合(无序的不重复)

最大存储数量2^32-1(40亿左右)

CLI命令:
sadd、smembers(查询)、scard(统计元素长度)、srandmember(随机拿一个元素)、spop(弹出来一个元素)、srem(删除某个元素)、sismember(是否存在某个元素)
取差集sdiff、取交集sinter、取并集sunion

1.4.1 存储结构:intset

存储整形的数组

1.4.2 存储结构:hashtable

默认超过512(redis.conf配置)个元素的时候使用,存储非纯数字的字符串

应用场景:
抽奖spop xxx、去重、 商品文章评论点赞、商品贴标签
商品条件的筛选(取交集)、用户关注和推荐的模型、两个账单对账(可以查出你有我没有的数据、差集)

1.5 zset有序链表(有序不重复)

CLI命令:
zadd、zrange(有序输出)、zrevrange(倒序排序)、zrangebyscore(范围输出)、zrem、zcard、zincrby(步增)、zcount(统计元素个数)、zrank(元素排名)、zscore(根据分数获取元素)

1.5.1 存储结构:ziplist

使用条件:元素数量<128,所有元素长度小于64bytes
根据分值排序决定元素放在哪个位置

1.5.2 存储结构:skiplist+dict

分层(跳表):增加指针,level是随机的

应用场景:存在动态变化的列表、学生成绩、热搜

1.6 其他数据结构

1.6.1 Bitmap(String)

CLI命令:
getbit、setbit、get

1.6.2 GeoSpatial

经纬度位置 geoadd(添加位置)、geopos(获取位置)、计算距离、获取范围内地点集合

1.6.3 Hyperloglogs:

基数统计,非精准计数(节省内存)

1.6.4 Streams

流数据处理

2 Redis高级特性

2.1 发布订阅

模式:publisher - Redis Channel - subscriber

订阅频道:可以一次订阅多个(channel不需要提前创建):

CLI命令:
subscribe channel-1 channel-2 channel-3

向指定频道发布消息:

CLI命令:
publish channel-1 hello

取消订阅:

CLI命令:
unsubscribe channel-1

按规则(pattern)订阅频道:

CLI命令:
psubscribe *sport

缺点:非专业MQ服务器,并非性能,延迟,持久化不好
Redis消息发布订阅主要是redis内部功能使用,或非常简单的消息服务

2.2 事务功能

定义:一组命令一起执行。先入队先执行,执行命令时不会存在其他客户端干扰,不存在事务的嵌套。

CLI命令:
multi //开启事务,将下面的命令放到待执行的队列中
—xxxx— // Redis命令
exec //执行事务命令

其他命令:
discard //取消事务的命令
watch //监视key是否被修改,如果修改了,就不能提交成功了。没有修改才可以exec成功

2.3 Lua脚本

在Redis中执行Lua脚本:

CLI命令:
eval lua-script key-num key[] val[]

在Lua脚本中执行Redis命令:

Lua脚本命令:
redis.call(command, key[],val[])

用执行lua脚本的语法,执行lua脚本来调用redis的命令:

Linux命令:
eval “return redis.call(‘set’,KEY[1],ARGV[1])” 1 hello world

调用Lua脚本文件:

Linux命令:
redis-cli --eval 脚本名称 参数个数 参数1 参数2 参数3…

3 Redis原理

3.1 Redis为什么这么快?

1、纯内存KV 2、请求单线程 3、同步非阻塞I/O–多路复用

Redis QPS性能测试:

Linux命令:
redis-benchmark -t set,lpush -n 100000 -q

为什么使用单线程?
1、多线程创建销毁的性能消耗
2、多线程的上下文切换
3、多线程竞争问题、锁的问题

redis.conf中有参数maxmemory设置Redis的最大内存:
32位系统:2^32 = 4GB,应用程序通过虚拟地址访问内存,内核空间1GB,实际用户空间大小3GB
64位系统:2^64 = 1024*1024TB,应用程序通过虚拟地址访问内存,内核空间256TB,实际用户空间大小256TB,其他空间Redis未定义

6.0的多线程 指的是 I/O多线程。说的是多线程处理I/O,实际4.0之后就已经有其他线程了。
我们一直说的单线程是处理请求是单线程的,多线程指的是从内核空间复制到用户空间是多线程。

虚拟内存 - 多路复用

3.2 过期策略

  1. 立即过期-占用大量cpu资源
  2. 惰性过期-访问的时候判断是否过期,过期的话就删掉,不占用大量cpu资源,但占用内存
  3. 定时过期

3.3 Redis持久化策略

3.3.1 RDB: Redis DataBase, 记录快照

redis.conf中的配置:
save 900 1
save 300 10
save 60 10000

rdbcompression yes # 是否开启压缩算法,LZF算法
rdbchecksum yes # 校验RDB完整性
rdbfilename dump.rdb # 指定的rdb的名字
rdb-del-sync-files no
dir /usr/local/soft/redis-6.0.9 # 指定rdb的路径

RDB手动触发刷盘:

  1. save:前台保存快照,会注册redis服务
  2. bgsave:后台保存快照,会folk一个子进程生成快照,不会长时间阻塞redis提供服务

另外Redis客户端的命令:shutdown(正常关机)和flushall(删库)也会刷rdb文件

优势:

  1. 内存紧凑,适合备份和灾难回复
  2. 生成文件过程不影响主进程
  3. 大数据集恢复速度快

不足:

  1. 不能实时持久化,可能丢失数据,可靠性没有那么强

3.3.2 AOF: Append Only File, 记录命令日志

(如果两个策略都开启的话,优先AOF)

redis.conf中的配置:
appendonly no # 开关 yes开启AOF
appendfilename “appendonly.aof” # 文件名
appendfsync everysec # no表示不执行fsync,由操作系统保证数据同步到磁盘;always表示每次从写入都执行fsync,以保证数据同步到磁盘;everysec(默认)表示每秒执行一次fsync,可能会丢失这1s的数据
auto-aof-rewrite-percentage 100 # 文件大小生长的比例达到,100表示100%,也就是翻倍的时候自动触发重写AOF文件
auto-aof-rewrite-min-size 64mb # 触发重写最小文件的大小,64mb表示如果文件小于64mb,则不触发重写

bgrewriterafo:重写AOF文件,从内存中(不是硬盘中的aof文件)的数据重新生成AOF,多其中的命令进行压缩

选哪个:
可以同时开启,rdb比较节省空间,aof可靠性更好。

4 Redis分布式

4.1 主从复制

主从复制需要协调维护master节点、slave节点 的关系。Redis主从节点架构中,只允许主节点写入数据,从节点自动同步

redis.conf中的配置:
replicaof [masterip] [masterport] # 指定当前节点的master的ip和port,则当前节点会成为master的从节点

也可以不通过redis配置文件,通过执行CLI命令的方式加入主从架构:

Linux命令:
–slaveof IP port # 启动redis时变成某个主节点的从节点

CLI命令:
slaveof host port # 在redis客户端中将当前已经启动的节点变成某个主节点的从节点

CLI命令:
info replication # 查看当前节点的主从关系
slaveof no one # 脱离主从架构

主从复制的实现:从节点后台定时任务线程每隔1s钟跑一次
全量复制: 读取RDB文件,生成新RDB文件
增量复制: 命令同步阶段,跟上主节点的步伐,从节点记录上次同步到了哪个位置offset。

6.0中主从复制的无盘复制:repl-diskless-sync no ; 不借助磁盘,直接通过网络发送RDB文件。2.8版本中这个功能已经出现

不足:
没有解决高可用问题,master节点挂掉的时候,存在单点故障。需要手动切换主节点。

4.2 可用性保证

4.2.1 Sentinel哨兵机制

目标:

  1. 服务端:自动切换主从
  2. 客户端:自动发现路由

Sentinel是一个特殊状态的Redis服务,主要用来监控master/slave,并且互相监控
Sentinel启动方式:

Linux命令:进入src目录
1、 ./redis-sentinel …/sentinel.conf
2、 ./redis-server …/sentinel.conf --sentinel

哨兵的选举基于RAFT算法:
先到先得,少数服从多数。(Raft算法在Consul、RocketMQ里面都有应用)

Redis Master选主规则:

  1. 如果与哨兵链接断开的比较久,超过了某个阈值,就直接失去了选举权。
  2. 如果拥有选举权,那就看谁的优先级高,这个在配置文件里可以设置(replica-priority 100),数值越小优先级越高。
  3. 如果优先级相同,就看谁从master中复制的数据最多(offset复制偏移量最大),选最多的那个。
  4. 如果复制数量也相同,就选择进程id最小的那个。

4.3 数据分片方案

4.3.1 客户端实现路由选择

Jedis自带分片方案:Jedis提供普通连接池、哨兵连接池、分片连接池

4.3.2 路由选择的逻辑抽取出来,做成一个服务

Twemproxy、Codis、Redis Cluster

Redis Cluster
创建一个Cluster就完成了集群的搭建:

Linux命令:
redis-cli --cluster create host[]:port[]

Redis Cluster特点:

  1. 无中心的集群架构
  2. 数据分布是根据slot来动态分布。slot可以用命令调整,可以将性能好的机器的槽分配的多一点
  3. 可扩展到1000个Redis Group(官方不推荐超过1000个),节点可以动态的添加或者删除,数据会重新分布。
  4. 高可用,部分节点不可用,集群仍然可用,可以实现自动的故障转移。master挂掉后,slave可以通过投票机制自动完成选举成为Master
  5. 降低了运维成本。提高系统的扩展性和可用性。
  6. 包括了sentinel的功能

Redis Cluster解决以下问题:

  1. 怎么均匀分片
    将内存分成16384个slot槽,再将槽分配到对应的节点上,通过CRC16 计算Hash的方式将key模以16384得到slot槽,从而得到对应的节点。

  2. 怎么访问到相应的节点

CLI命令:
cluster keyslot key # 可以用这个命令得到这个key分布在哪个slot槽里面

当需要将一批有业务关联的数据放在一个节点上时,可以通过{}来指定只对key中相同标识的一部分key做CRC16计算,从而让其保存在同一个节点。

CLI命令:
set a{key}1 1 # 只用大括号包起来的key取模,通过{}中相同的key控制他落到的节点

SmartJedis,在客户端本地计算key对应的槽,并从本地客户端中group和slot的对应表,选择对应的redis服务端进行选择。

  1. 重写分片,怎么正常服务
    节点的加入分两步:
    ① 通过命令将节点加入Redis Cluster
    新增节点时会马上生成一个reshard计划,进行新节点对应slot的数据迁移。
    新增节点命令:redis-cli --cluster reshard 目标节点(IP端口号)
    ② 通过命令分配slot给新加入的节点(slot可以动态的调整,旧数据还能自动迁移)

Redis Cluster故障转移

  1. 当Slave发现自己的Master变成FAIL了
  2. Slave将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
  3. 其他节点收到该信息,只有Master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
  4. 尝试failover的slave收集FAILOVER_AUTH_ACK
  5. 当投票超过半数后变成新的Master
  6. 广播Pong通知其他集群节点

4.3.3 服务端自带分片存储,路由选择。

5 Redis实战

用抓包工具抓包可以看到redis客户端的请求信息是怎样的。
抓包命令:ip.dst==192.168.125.1 and tcp.port in {6379}

5.1 客户端介绍

RedisTemplate、Jedis、Lettuce、Redisson

5.1.1 Jedis

Jedis实现了包含了几乎所有的Redis命令
Pipeline:批量打包发送给Redis

5.1.2 Lettuce

Lettuce时线程安全的Redis客户端,不存在Jedis那种多个客户端共享一个实例的时候的线程安全的问题。

  1. 提供了同步的API: RedisClient(创建客户端)、StatefulRedisConnection(线程安全的长连接)、RedisCommands(get、set方法)

  2. 提供了异步的API: RedisClient、StatefulRedisConnection、RedisAsyncCommands(异步执行命令set、get)、RedisFuture(异步get)

通常SpringBoot 2.x以后只需要用RedisTemplate,就是默认使用上述API实现的。

5.1.2 Redisson

Redisson诞生的定位是:Java在分布式环境中可扩展的数据结构,分布式的缓存。它不支持事务处理,但可以通过Lua脚本来实现事务。
Map、SortedSet

支持分布式锁:
加锁:RLock.tryLock
解锁:RLock.unlock

锁到期后,redisson内部提供了一个看门狗watchDog,不断监视这个锁,如果当前任务没执行完,自动进行续命

5.2 数据一致性

可以使用Redis作为缓存来进行数据一致性处理。

缓存的使用场景:

  1. 命中缓存-缓存返回
  2. 缓存不存在-数据库存在(加锁)-写入缓存-缓存返回

缓存一致性的原则:

  1. 以数据库的数据为准。
  2. 考虑实时一致性和最终一致性。

1、当缓存中不存在
2、数据库中的值发生变化的时候

一致性是在性能和准确性的权衡
先更新数据库再更新缓存失败: MQ保证缓存更新成功
先删除缓存再更新数据库: 双删保证缓存最新

6 Redis高并发

6.1 热点数据发现

怎么操作记录热点数据
1、客户端
2、代理层Proxy
3、服务端

客户端通过:Jedis.monitor(new JedisMonitor(){}),进行key的监听。
会影响服务端的性能,且一个monitor只能监听一个节点。如果要监听多个redis节点的数据,则需要再客户端创建多个monitor。

redis-faina:专门用来监听monitor的工具

4、机器
ELK提供packetbeat,可以对TCP的协议进行抓包,可以分析网络流量,抓到redis命令的数据,就可以解析它,再出报表热点数据。

6.2 缓存雪崩:大量的热点数据同时过期

原因:数据是批量添加到redis,且过期时间是一样的。过期以后就都打到数据库了,数据库就扛不住了。

解决方案:
1、加锁
2、预更新 - 提前更新过期值
3、ttl加随机数 - expire属性加一个随机数的时间,
4、永不过期 - 超热点数据就不用过期了

6.3 缓存击穿

热点key过期,影响没有雪崩大

6.4 缓存穿透

不断的查询数据库中不存在的值,Redis失去了作用,没起到保护数据库的作用
解决方案:
1、即使key不存在,也set一个值为无效数据的key到Redis中,设置过期时间expire。有可能导致Redis内存不够。
2、增加布隆过滤器,实时快速的判断数据库中是否存在对应key。

如何在海量元素中(比如有十亿数据,不定长、不重复),快速判断一个元素是否存在?
①Java中的Map、Set,时间复杂度是O(1),但是这种方式,10亿的大数据,一般服务器扛不住。
②BitMap(位图) BitSet,来标识某一个元素在Long[]数组中对应下标是否存在。提前将元素和下表对应起来,提前将不固定的长度转换成相同长度的输出,提前利用hash散列算法将数据分布均匀,当出现哈希碰撞的时候就可以利用链表或者向后移1位。
③布隆过滤器的本质:
1)位数组(二进制向量)
2)一系列随机映射函数(哈希)
布隆过滤器能在查询数据库之前就判断数据库中是否存在数据,如果不存在就不查询,起到保护数据库的作用。
实现:Guava的BloomFilter(只支持put,不支持删除元素)、baqend的带计数器的CountingBloomFilter(支持add,支持remove)…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值