redis 笔记

redis 查找key

  • key 指令
127.0.0.1:6379> keys a*
1) "a1"
2) "a2"
  • 缺点
  1. 没有 offset、limit 参数,一次性吐出所有满足条件的 key。
  2. keys 算法是遍历算法,复杂度是 O(n),如果key千万级就会导致 Redis 服务卡顿。
  • scan 指令推荐
127.0.0.1:6379>  scan 0 match a* count 3
1) 	"30"
2) 	1) "a1"
	2) "a2"
	3) "a3"
  • 其中0 表示下标从0开始 count 3 表示取出3条
  • 返回值的第一行数字(30)可作为下一次遍历的下标

redis 为啥快

  • redis线程模型redis是Nio 非阻塞io redis 是单线程 避免了多线程上下文切换带来的性能开销。

  • 指令队列
    Redis 会将每个客户端套接字都关联一个指令队列。客户端的指令通过队列来排队进行顺序处理,先到先服务。

  • 响应队列
    Redis 同样也会为每个客户端套接字关联一个响应队列。Redis 服务器通过响应队列来将指令的返回结果回复给客户端。 如果队列为空,那么意味着连接暂时处于空闲状态,不需要去获取写事件,也就是可以将当前的客户端描述符从write_fds里面移出来。等到队列有数据了,再将描述符放进去。避免select系统调用立即返回写事件,结果发现没什么数据可以写。出这种情况的线程会飙高 CPU。

redis持久化

  • 持久化的两种方式:aof(日志) 和 rdb(快照)

  • aof 是指令记录文本数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长。所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身。

    1.redis 是先执行指令再存日志。
    2.AOF 重写
    Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。

    3.fsync
    如果机器突然宕机,AOF 日志内容可能还没有来得及完全刷到磁盘中,这个时候就会出现日志丢失。Linux 的glibc提供了fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。Redis 通常是每隔 1s 左右执行一次 fsync 操作,周期 1s 是可以配置的。这是在数据安全性和性能之间做了一个折中,在保持高性能的同时,尽可能使得数据少丢失。

  • rdb 持久化 时会fork 一个子进程 由这个子进程进行持久化从fork出这个进程开始 主线程如果修改数据都会复制出来一份进行修改不会影响子进程 这也是rdb 叫做快照的原因。

  • redis 混合持久化 先rdb然后再rdb后的时间点开始aof

  1. 开启混合持久化
    $ cat redis.conf
    appendonly yes
    aof-use-rdb-preamble yes
  2. 混合持久化命令 会生成 appendonly.aof 文件
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started

redis 管道

redis 管道可以一次连接发送多个命令 减少建立tcp连接的消耗。

	Jedis jedis = jedisPool.getResource();
	Pipeline pipelined = jedis.pipelined();
	String keyPrefix = "pipeline";
	long begin = System.currentTimeMillis();
	for (int i = 1; i < 10000; i++) {
		String key = keyPrefix + "_" + i;
		String value = String.valueOf(i);
		pipelined.set(key, value);
    }
    pipelined.sync();
    jedis.close();

redis 事务

redis的事务没有回滚的功能 redis的事务只具备隔离性不具备原子性
redis事务四个指令: MULTI、EXEC、DISCARD、WATCH。这四个指令构成了redis事务处理的基础。

1.MULTI用来组装一个事务;
2.EXEC用来执行一个事务;
3.DISCARD用来取消一个事务(在EXEC执行前取消);
4.WATCH用来监视一些key,一旦这些key在事务执行之前被改变,则取消事务的执行。
redis 事务执行中如果有命令出错会继续执行下一条命令所以不具备原子性。

redis主从同步

cap原理

  • C - Consistent ,一致性
  • A - Availability ,可用性
  • P - Partition tolerance ,分区容忍性
  1. redis 之前同步是异步 不能保证 一致性
  2. redis 保证最终一致性,从节点在某一时刻会和主节点保持一致。当网络断开又恢复后,从节点会采用多种策略同步数据,继续同步数据。
  3. redis 支持主从同步从从同步。主从复制不会阻塞 master,在同步数据时,master 可以继续处理 client 请求。
    1. Salve会发送sync命令到Master
      
    2. Master启动一个后台进程,将Redis中的数据快照保存到文件中
      
    3. 启动后台进程的同时,Master会将保存数据快照期间接收到的写命令缓存起来
      
    4. Master完成写文件操作后,将该文件发送给Salve
      
    5. Salve将文件保存到磁盘上,然后加载文件到内存恢复数据快照到Salve的Redis上
      
    6. 当Salve完成数据快照的恢复后,Master将这期间收集的写命令发送给Salve端
      
    7. 后续Master收集到的写命令都会通过之前建立的连接,增量发送给salve端
      
  4. 增量同步
    Redis 同步的是指令流,主节点会将对状态产生影响的指令记录在本地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了 (偏移量)。
    因为内存的 buffer 是有限的,所以 Redis 主库不能将所有的指令都记录在内存 buffer 中。Redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。
    主从复制有关的buffer包括repl-backlog-size、client-output-buffer-limit slave可以调成峰值流量的两倍甚至同普通客户端buffer一样不做限制。
  5. 快照同步是主节点bgsave 保存rdb文件发给客户端,在生成rdb文件时主节点会继续收集写操作指令保存到buffer 快照同步完后把指令流发给从节点。
  6. 注 意 : \color{HotPink}{注意:} 如果快照同步时间过长或者增量同步的buffer 设置的太小 会导致一直快照同步对性能影响很大。buffer设置太小再快照同步时buffer中的指令已经被新的指令覆盖掉就有需要快照同步。
  7. 新增从节点会先快照在增量同步。如果是断开后又连接的从节点在2.8版本之后支持PSYNC 从节点会发送一个 向Master发送PSYNC runid(主节点id) offset(从节点数据当前的偏移量) 来同步。2.8之前是全量快照。
  8. 无盘复制.
    主节点在进行快照同步时,会进行很重的文件 IO 操作,特别是对于非 SSD 磁盘存储时,快照会对系统的负载产生较大影响。特别是当系统正在进行 AOF 的 fsync 操作时如果发生快照,fsync 将会被推迟执行,这就会严重影响主节点的服务效率。
    2.8后开始有了无盘复制主节点不再是本地生成快照发给从节点而是直接把生成快照的数据发给从节点,从节点接收到的内容存储到磁盘文件中,再进行一次性加载。
  9. Wait 指令
    Redis 的复制是异步进行的,wait 指令可以让异步复制变身同步复制,确保系统的强一致性 (不严格)。wait 指令是 Redis3.0 版本以后才出现的。

set key value
OK
wait 1 0
(integer) 1

wait 提供两个参数,第一个参数是从库的数量 N,第二个参数是时间 t,以毫秒为单位。它表示等待 wait 指令之前的所有写操作同步到 N 个从库 (也就是确保 N 个从库的同步没有滞后),最多等待时间 t。如果时间 t=0,表示无限等待直到 N 个从库同步完成达成一致。

如果出现网络分区,wait 第二个参数时间 t=0,主从同步无法进行,wait 指令会永远阻塞,Redis 服务器将丧失可用性。

redis哨兵机制Sentinel

  • 消息丢失

Redis 主从采用异步复制,意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分未同步的消息就丢失了。如果主从延迟特别大,那么丢失的数据就可能会特别多。Sentinel 无法保证消息完全不丢失,但是也尽可能保证消息少丢失。它有两个选项可以限制主从延迟过大。

min-slaves-to-write 1
min-slaves-max-lag 10
第一个参数表示主节点必须至少有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性。

何为正常复制,何为异常复制?这个就是由第二个参数控制的,它的单位是秒,表示如果 10s 没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开了,要么一直没有给反馈。

Sentinel

哨兵默认端口是26379。
  1、配置端口
    在sentinel.conf 配置文件中, 我们可以找到port 属性,这里是用来设置sentinel 的端口,一般情况下,至少会需要三个哨兵对redis 进行监控。
在 sentinel.conf 配置文件中配置。

哨兵机制

redis 集群 cluster

redis 吧集群分为16384 个槽,redis cluster 是去中心化的,RedisCluster 的每个节点会将集群的配置信息持久化到配置文件中,所以必须确保配置文件是可写的,而且尽量不要依靠人工修改配置文件。

槽位定

Cluster 默认会对 key 值使用 crc16 算法得到hash 整数值,然后对 16384 进行取模获得槽位。
为什么redis的槽位是16384个? redis的作者给的回答是由于节点之间的心跳包都要带上槽位信息,用bitmap压缩后后16384个只占用2K空间,redis集群理论上不会超过1000个节点,不需要更大的槽位
哈 希 一 致 性 原 理 \color{HotPink}{哈希一致性原理}

错误跳转

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。

GET x
-MOVED 3999 127.0.0.1:6381

MOVED 指令的第一个参数 3999 是 key 对应的槽位编号,后面是目标节点地址。MOVED 指令前面有一个减号,表示该指令是一个错误消息。

客户端收到 MOVED 指令后,要立即纠正本地的槽位映射表。后续所有 key 将使用新的槽位映射表。

迁移

  • Redis Cluster 提供了工具 redis-trib
  • 迁移以槽为单位
  • 从源节点获取内容 => 存到目标节点 => 从源节点删除内容。
  • 迁移过程中如果key在老节点老节点正产处理,如果客户端来请求对应的key不在老节点 要目老节点不存在,要么在新节点,老节点会给客户端一个-ASK targetNodeAddr的重定向指令。让客户端去新节点点执行一个不带任何参数的asking指令,然后在目标节点再重新执行原先的操作指令。
    asking指令是让目标节点强制执行自己的指令(因为迁移未完成新节点槽位还是不归新节点管理的,如果这个时候向目标节点发送该槽位的指令,节点是不认的,它会向客户端返回一个-MOVED重定向指令告诉它去源节点去执行。如此就会形成 重定向循环。)

容错

  • cluster-require-full-coverage 设置可允许部分节点故障,其它节点还可以继续提供对外访问。
  • Redis Cluster 提供了一种选项cluster-node-timeout,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重新复制)。
  • 大多数节点任务一个节点失联集群才会任务其失联。

注意

  • Cluster 不支持事务。
  • Cluster 的 mget 方法相比 Redis 要慢很多,被拆分成了多个 get 指令,* * Cluster 的 rename 方法不再是原子的,它需要将数据从原节点转移到目标节点。

redis stream

https://blog.csdn.net/enmotech/article/details/81230531
写的很好
在这里插入图片描述https://blog.csdn.net/enmotech/article/details/81230531

  • redis stream 每个消息可以有多个消费组。
  • redis stream 的每个消费组是竞争关系一个读完后另一个不能在读。
  • redis stream 有ack 机制防止消息丢失
  • redis stream 如果消息丢失可以xpeding 命令获取PEL列表中没有ack的消息
  • 消息 ID 的形式是timestampInMillis-sequence,例如1527846880572-5,它表示当前的消息在毫秒时间戳1527846880572时产生,并且是该毫秒内产生的第 5 条消息。消息 ID 可以由服务器自动生成 不用担心id会重复及时服务器时间倒退也不会重复生成前会检查最新的消息id。

info指令

  • ops_per_sec: operations per second,每秒操作数

redis-cli info stats |grep ops
instantaneous_ops_per_sec:789

  • info clients 客户端连接数量

127.0.0.1:6379> info clients
connected_clients:4
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0

  • client list 列出当前所有连接

127.0.0.1:6379> client list
id=4 addr=127.0.0.1:41228 fd=9 name= age=13522 idle=13522 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=command
id=5 addr=127.0.0.1:41230 fd=10 name= age=13504 idle=13354 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=xread
id=6 addr=127.0.0.1:41232 fd=8 name= age=3080 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=7 addr=127.0.0.1:41234 fd=11 name= age=3070 idle=1714 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=xreadgroup

  • redis-cli info stats |grep reject 因为超过最大连接被拒绝的次数 如果很多要调整最大连接数maxclients
    rejected_connections:0

  • 查看内存信息 ./redis-cli info memory | grep used | grep human

[root@localhost src]# ./redis-cli info memory | grep used | grep human
used_memory_human:916.21K #从操作系统分配的内存总量
used_memory_rss_human:12.21M # 操作系统看到的内存占用 ,top 命令看到的内存
used_memory_peak_human:935.60K # Redis 内存消耗的峰值
used_memory_lua_human:37.00K # lua 脚本引擎占用的内存大小
used_memory_scripts_human:0B

  • 赋值积压缓存,可以通过 info replication 看到。(多个从库 共享一个主库的积压缓冲)

[root@localhost src]# redis-cli info replication |grep backlog
repl_backlog_active:0
repl_backlog_size:1048576 # 积压缓冲区大小
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

过期策略

  • redis 删除过期的key redis 把有过期时间的key放到一个独立的字典 。

  • 零散删除: 当客户端访问时看key是否过期过期删除。

  • 集中删除: 定时删除每秒集中删除10次 。 删除采用简单贪心策略随机找到20个key如果过期key 比率超过 1/4就再次查找。

  • 从库删除策略 主库删除会生成del 命令同步到从库删除

缓存淘汰策略

  • noeviction:当内存使用超过配置的时候写入会返回错误,不会驱逐任何键(默认)
  • allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键(最长用)
  • volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
  • allkeys-random:加入键的时候如果过限,从所有key随机删除
  • volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
  • volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
  • volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  • allkeys-lfu:从所有键中驱逐使用频率最少的键

redis 懒惰删除

  • del指令删除特别大的key会造成redis停顿redis 4.0 提供了unlink 指令异步删除

unlink key
OK

  • 清空整个库也可以异步 flushdb 和 flushall 加上async

flushall async
OK

修改特殊指令

把一些危险的指令如flushdb 等 修改为自定义指令 避免误触发

  • rename-command keys mykeys

安全

可以为redis增加密码 修改端口 增加ip访问权限等。


redis分布式锁

  • 命令:set lockKey true ex 5 nx
    1. lockKey 自定义的key名称
    2. true 自定义的key的value
    3. ex 5 表示过期时间为5秒
    4. nx 是setnx(set if not exists) 不存在就添加
  • redis2.8之后set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行(原子性).
  • redis2.8 之前setnx 和 expire是两条指令而不是原子指令。如果在 setnx 和 expire 之间出现故障导致 expire 得不到执行,会造成死锁。
  • 可重入锁
    ThreadLocal<Map<String, Integer>> lockNum = new ThreadLocal<>();
    其中 Map的key的锁名称 value 为进入了几次锁
    每次进入锁就给value+1出锁就减去1
import com.google.common.collect.Maps;

import java.util.Map;
import java.util.Optional;

public class RedisLock {


    private static ThreadLocal<Map<String, Integer>> lockNum = new ThreadLocal<>();

    static {
        lockNum.set(Maps.newHashMap());
    }

    public static boolean unLock(String key) {
        Integer num = lockNum.get().get(key);
        num = num - 1;
        if (num == 0) {
            if (!del(key)) {
                return false;
            }
            lockNum.get().remove(key);
        } else {
            lockNum.get().put(key, num);
        }

        return true;

    }

    public static boolean lock(String key) {

        Integer num = Optional.ofNullable(lockNum.get().get(key)).orElse(0);
        if (num == 0) {
            if (!set(key)) {
                return false;
            }
        }
        num = num + 1;
        lockNum.get().put(key, num);

        return true;
    }

    public static boolean set(String key) {
        // 设置redis锁
        // set lockKey true ex 5 nx ;
        return true;
    }

    public static boolean del(String key) {
        // 清除redis锁
        // DEL key ;
        return true;
    }
}

延时队列

  • 使用zset 的score做延时的时间 value做队列的值
  • 简单实现
public class RedisDelayingQueue {

    private  Jedis jedis=new Jedis();

    public void add(String setName,double score,String value){
        jedis.zadd(setName,score,value);
    }

    public String get(String setName){

        while (!Thread.interrupted()) {
            // 只取一条
            Set<String> values=jedis.zrangeByScore(setName, 0, System.currentTimeMillis(), 0, 1);
            if (values.isEmpty()) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    break;
                }
                continue;
            }
            String s = values.iterator().next();
            if (jedis.zrem(setName, s) > 0) {
               return s;
            }
        }
        return null;
    }


}

redis 位图

  • redis 可以利用位图做用户统计
  • 如,其中的7位用户登录状态(1代表登录0代表未登录 其中下标为每个用户的id)
    下标:012 3456
    周一:010 0010
    周二:110 0010
  • setbit monday 0 0
  • setbit tuesday 0 1
  • … 设置其他用户
  • 查询一周登录用户 bitop and result monday thurday
  • bitcount result
    http://www.redis.cn/commands/bitop.html
    由于1个用户只占1bit 一个亿才占10m所以占用空间很少。

HyperLogLog 统计每天UV

pfadd 可以统计uv 会自动去重 但是不太精确

redis> pfadd a.html user1
redis> pfadd b.html a b c d
redis> pfcount b.html
4
redis> pfmerge a.html b.html
5

pfadd 获取单个网页的值
pfcount 获取单个网页的值
pfmerge 获取多个网页合并后的值

布隆过滤器

布隆过滤器不是很精确布隆过滤器认为存在的不一定存在 但是认为不存在的一定不存在。

127.0.0.1:6379> bf.add key user1
(integer) 1
127.0.0.1:6379> bf.exists key user1
(integer) 1
127.0.0.1:6379> bf.exists key user4
(integer) 0
127.0.0.1:6379> bf.madd key user4 user5 
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> bf.mexists codehole user4 user5 user6 
1) (integer) 1
2) (integer) 1
4) (integer) 0

redis限流

redis-cell 模块

redis> cl.throttle test 15 14 60 1
1) (integer) 0   # 0 表示允许,1表示拒绝
2) (integer) 15  # 漏斗容量capacity
3) (integer) 14  # 漏斗剩余空间left_quota
4) (integer) -1  # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2   # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

  • test 是key
  • 100 是令牌桶初始大小
  • 14 和 60 是60秒内14个指令
  • 1 是取得一个令牌
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值