Redis的简单使用

目录

概述

  • Redis是一个开源的key-value存储系统;
  • 支持存储的value类型多,包括String(字符串)、list(链表)、set(集合)、zset(sorted set – 有序集合)、hash(哈希类型);
  • 数据类型都支持push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且操作都是原子性的;
  • Redis支持各种不同方式的排序;
  • 为了保证效率,数据都是缓存在内存中;
  • Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件;
  • 实现了master-slave(主从)同步;

相关

Reids默认16个数据库,默认使用0号库;
使用select 来切换数据库,例如:

select 1

所有库密码相同;
dbsize:查看当前数据库的key数量
flushdb:清空当前库
flushall:清空全部库

Redis是单线程+多路IO复用技术:
多路复用:

一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件扫描符,如果有一个文件扫描符就绪则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。

原子性:
操作不会被线程调度机制打断的操作;
这种操作一旦开始,就一直到结束,中间不会有任何context switch(线程切换);

  • 在单线程中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只能发生于指令间;
  • 在多线程中,不能被其他线程打断的操作就叫原子操作,Redis单命令的原子性主要得益于Redis的单线程;

常用数据操作

key–键操作

  • keys * :查看当前所有key(匹配:key *1);
    127.0.0.1:6379> keys *
    1) "k1"
    2) "k3"
    3) "k2"
    
  • exists key:判断某个key是否存在,返回1表示存在,返回0表示不存在;
    127.0.0.1:6379> exists k1
    (integer) 1
    127.0.0.1:6379> exists k4
    (integer) 0
    
  • type key:查看你的key是什么类型
    127.0.0.1:6379> type k1
    string
    
  • del key:删除指定的key数据
  • unlink key:根据key选择分阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作
    127.0.0.1:6379> del k3
    (integer) 1
    127.0.0.1:6379> exists k3
    (integer) 0
    127.0.0.1:6379> unlink k3
    (integer) 0
    
  • expire key n:为给定的key设置过期时间n秒
  • ttl key:查看还有多少秒过期,-1表示永不过期,-2表示已过期
    127.0.0.1:6379> expire k1 10
    (integer) 1
    127.0.0.1:6379> ttl k1
    (integer) 8
    127.0.0.1:6379> ttl k1
    (integer) 5
    127.0.0.1:6379> ttl k1
    (integer) 2
    127.0.0.1:6379> ttl k1
    (integer) -2
    
  • select n:命令切换到n号数据库
  • dbsize:查看当前数据库的key的数量
    127.0.0.1:6379> dbsize
    (integer) 2
    
  • flushdb:清空当前数据库
  • flushall:清空全部数据库

String字符串

String是Redis最基本的类型,是二进制安全的,意味着Redis的String可以包含任何数据,比如JPG图片或者序列化对象,一个字符串value最多可以是512M

数据结构

String的数据结构为简单动态字符串,是可以修改的字符串,内部结构实现上类似于java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。当字符串小于1M时,扩容都是加倍现有的空间没如果超过1M,扩容时一次只会多扩1M的空间;

常用命令

  • set :添加键值对
    set key value [EX seconds | PX milliseconds | KEEPTTL] [NX|XX]
    
    • NX:当数据库中key不存在时,可以将key-value添加数据库
    • XX:当数据库中key存在时,可以将key-value添加 数据库,与NX参数互斥
    • EX:key的超时秒数
    • PS:key的超时毫秒数,与EX互斥
    127.0.0.1:6379> set k1 v1
    OK
    127.0.0.1:6379> set k2 v2
    OK
    
    设置相同的key,value会覆盖之前的内容
  • get :查询对应键值
    127.0.0.1:6379> get k1
    "v1"
    
  • append :将给定的value追加到原值得末尾,返回value总长度
    127.0.0.1:6379> append k1 v1
    (integer) 4
    127.0.0.1:6379> get k1
    "v1v1"
    
  • strlen :获取值的长度
    127.0.0.1:6379> strlen k1
    (integer) 4
    
  • setnx :只有在key不存在时,设置key的值
    127.0.0.1:6379> setnx k1 l1
    (integer) 0
    127.0.0.1:6379> setnx k3 l3
    (integer) 1
    
  • incr :将key中存储的数字值增1,只能对数字值操作,如果为空,新增值为1
  • dect :将key中存储的数字值减1
    127.0.0.1:6379> set k4 100
    OK
    127.0.0.1:6379> get k4
    "100"
    127.0.0.1:6379> type k4
    string
    127.0.0.1:6379> incr k4
    (integer) 101
    127.0.0.1:6379> decr k4
    (integer) 100
    127.0.0.1:6379> incr k5
    (integer) 1
    127.0.0.1:6379> decr k6
    (integer) -1
    127.0.0.1:6379> 
    127.0.0.1:6379> incr k1
    (error) ERR value is not an integer or out of range
    
  • incrby/decrby <步长>:将key中存储的数字值增减自定义步长
    127.0.0.1:6379> incrby k4 100
    (integer) 200
    127.0.0.1:6379> decrby k4 100000
    (integer) -99800
    127.0.0.1:6379> incrby k6 500
    (integer) 499
    127.0.0.1:6379> incrby k7 500
    (integer) 500
    127.0.0.1:6379> decrby k8 500
    (integer) -500
    127.0.0.1:6379> 
    
  • mset …:同时设置一个活多个key-value对
  • mget …:同时获取一个或多个value
    127.0.0.1:6379> mset k1 v1 k2 v2
    OK
    127.0.0.1:6379> keys *
    1) "k1"
    2) "k2"
    127.0.0.1:6379> mset k1 l1
    OK
    127.0.0.1:6379> keys *
    1) "k1"
    2) "k2"
    127.0.0.1:6379> mget k1 k2
    1) "l1"
    2) "v2"
    
  • msetnx …:同时设置一个活多个key-value对,当且仅当所有给定key都不存在。
    127.0.0.1:6379> msetnx k1 l1 k2 l2
    (integer) 0
    127.0.0.1:6379> keys * 
    1) "k1"
    2) "k2"
    127.0.0.1:6379> msetnx k3 l3 k4 l4
    (integer) 1
    127.0.0.1:6379> keys *
    1) "k4"
    2) "k1"
    3) "k3"
    4) "k2"
    
  • getrange <起始位置><结束位置>:获得值得范围,类似java中的substring,前包后包;
  • setrange <起始位置>:用value覆盖所存储的字符串值,从起始位置开始,索引从0开始;
    127.0.0.1:6379> set k5 0123456789
    OK
    127.0.0.1:6379> getrange k5 2 5
    "2345"
    127.0.0.1:6379> setrange k5 2 nnnnnnn
    (integer) 10
    127.0.0.1:6379> get k5
    "01nnnnnnn9"
    
  • setex <过期时间:秒>:设置键值的同时设置过期时间,单位秒
    127.0.0.1:6379> setex k6 20 l6
    OK
    127.0.0.1:6379> ttl k6
    (integer) 18
    127.0.0.1:6379> ttl k6
    (integer) 11
    127.0.0.1:6379> ttl k6
    (integer) -2
    
  • getset:以旧换新,设置新值同时获得旧值
    127.0.0.1:6379> getset k6 v1
    (nil)
    127.0.0.1:6379> set k6 l6
    OK
    127.0.0.1:6379> getset k6 v1
    "l6"
    127.0.0.1:6379> 
    

List列表

简介

Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部或者尾部。
底层实际是一个双向链表,对两端的操作性能很高,通过索引下表的操作中间的节点性能会较差;

数据结构

List的数据结构为快速链表quickList;
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表,它将所有元素紧挨着一起存储,分配的是一块连续的内存;
当数据量比较大的时候才会改为quicklist;
因为普通的链表需要的附加指针空间太大,会比较浪费空间,需要两个额外的指针prev和next;
Redis将链表和ziplist结合起来组成了quicklist,也就是将多个ziplist使用双向指针串起来使用,这样既满足了快速的插入删除性能,又不会出现太大的空间冗余;

常用命令

  • lpush/rpush …:从左边/右边插入一个或多个值
    127.0.0.1:6379> lpush k1 v1 v2 v3 v4
    (integer) 4
    127.0.0.1:6379> lpush k1 v1 v2 v3 v4
    (integer) 8
    127.0.0.1:6379> lrange k1 0 -1
    1) "v4"
    2) "v3"
    3) "v2"
    4) "v1"
    5) "v4"
    6) "v3"
    7) "v2"
    8) "v1"
    127.0.0.1:6379> rpush k2 v1 v2 v3 
    (integer) 3
    127.0.0.1:6379> lrange k2 0 -1
    1) "v1"
    2) "v2"
    3) "v3"
    
  • lpop/rpop :从左边/右边吐出一个值。值在健在,值光健亡
    127.0.0.1:6379> lpop k1
    "v4"
    127.0.0.1:6379> rpop k2
    "v3"
    127.0.0.1:6379> rpop k2
    "v2"
    127.0.0.1:6379> rpop k2
    "v1"
    127.0.0.1:6379> rpop k2
    (nil)
    127.0.0.1:6379> 
    
  • rpoplpush :从key1列表右边吐出一个值,插到key2列表坐后边
    127.0.0.1:6379> rpush k2 v1 v2 v3
    (integer) 3
    127.0.0.1:6379> rpoplpush k1 k2
    "v1"
    127.0.0.1:6379> lrange k1 0 -1
    1) "v3"
    2) "v2"
    3) "v1"
    4) "v4"
    5) "v3"
    6) "v2"
    127.0.0.1:6379> lrange k2 0 -1
    1) "v1"
    2) "v1"
    3) "v2"
    4) "v3"
    127.0.0.1:6379> 
    
  • lrange:按照索引下表获得元素(从左到右),-1表示取所有的值
  • index :按照索引下表获得元素(从左到右)
    127.0.0.1:6379> lrange k2 0 -1
    1) "v1"
    2) "v1"
    3) "v2"
    4) "v3"
    127.0.0.1:6379> lindex k2 1
    "v1"
    
  • llen :获得列表长度
    127.0.0.1:6379> llen k2
    (integer) 4
    
  • linsert before/after :在第一个指定value的前/后面插入newvalue
    127.0.0.1:6379> lrange k2 0 -1
    1) "v5"
    2) "v3"
    3) "v1"
    4) "v5"
    5) "v3"
    6) "v1"
    127.0.0.1:6379> linsert k2 before v5 v10
    (integer) 7
    127.0.0.1:6379> linsert k2 after v1 v10
    (integer) 8
    127.0.0.1:6379> lrange k2 0 -1
    1) "v10"
    2) "v5"
    3) "v3"
    4) "v1"
    5) "v10"
    6) "v5"
    7) "v3"
    8) "v1"
    
  • lrem :从左边删除n个value(从左到右)
    127.0.0.1:6379> lrange k2 0 -1
    1) "v10"
    2) "v5"
    3) "v3"
    4) "v1"
    5) "v10"
    6) "v5"
    7) "v3"
    8) "v1"
    127.0.0.1:6379> lrem k2 2 v10
    (integer) 2
    127.0.0.1:6379> lrange k2 0 -1
    1) "v5"
    2) "v3"
    3) "v1"
    4) "v5"
    5) "v3"
    6) "v1"
    
  • lset :将列表key下表为index的值替换成value,超出下标报错
    127.0.0.1:6379> lrange k2 0 -1
    1) "v5"
    2) "v3"
    3) "v1"
    4) "v5"
    5) "v3"
    6) "v1"
    127.0.0.1:6379> lset k2 1 v100
    OK
    127.0.0.1:6379> lrange k2 0 -1
    1) "v5"
    2) "v100"
    3) "v1"
    4) "v5"
    5) "v3"
    6) "v1"
    127.0.0.1:6379> lset k2 100 v100
    (error) ERR index out of range
    

Set集合

简介

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当需要存储一个列表数据又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;
Redis的Set是string类型的无序集合,底层其实是一个value为null的hash表,所以添加删除,查找的复杂度都是o(1)。

数据结构

set数据结构是dict字典,字典是用hash表实现的;
java中的hashset的内部实现使用的是hashMap,只不过所有的value都指向同一个对象,Redis的set结构也是一样的,它的内部也使用hash结构,所有的value都指向同一个内部值;

常用命令

  • sadd …:将一个或多个元素添加到集合key中,已经存在的元素将被忽略
  • smembers :取出该集合的所有值
    127.0.0.1:6379> sadd k1 v1 v2 v3 v4
    (integer) 4
    127.0.0.1:6379> sadd k1 v2 v3 v4 v5
    (integer) 1
    127.0.0.1:6379> smembers k1
    1) "v5"
    2) "v3"
    3) "v1"
    4) "v4"
    5) "v2"
    
  • sismember 判断集合key中是否包含元素value,有1,没有0
    127.0.0.1:6379> sismember k1 v6
    (integer) 0
    127.0.0.1:6379> sismember k1 v5
    (integer) 1
    
  • scard :返回该集合的元素个数
    127.0.0.1:6379> scard k1
    (integer) 5
    
  • srem …:删除集合中的某个元素
    127.0.0.1:6379> srem k1 v6 v5
    (integer) 1
    127.0.0.1:6379> smembers k1
    1) "v3"
    2) "v1"
    3) "v4"
    4) "v2"
    
  • spop :随机从该集合中吐出一个值,值取完了key就不存在了
    127.0.0.1:6379> spop k1
    "v1"
    127.0.0.1:6379> smembers k1
    1) "v3"
    2) "v4"
    3) "v2"
    
  • srandmember :随机从该集合中取出n个值,不会从集合中删除
    127.0.0.1:6379> srandmember k1 5
    1) "v3"
    2) "v4"
    3) "v2"
    127.0.0.1:6379> srandmember k1 2
    1) "v4"
    2) "v2"
    127.0.0.1:6379> srandmember k1 2
    1) "v4"
    2) "v2"
    127.0.0.1:6379> srandmember k1 2
    1) "v4"
    2) "v2"
    127.0.0.1:6379> srandmember k1 2
    1) "v3"
    2) "v2"
    127.0.0.1:6379> srandmember k1 2
    1) "v4"
    2) "v2"
    
  • smove value:把集合中的一个值移动到另一个集合
    127.0.0.1:6379> sadd k1 l1 l2 l3
    (integer) 3
    127.0.0.1:6379> sadd k2 l3 l4 l5
    (integer) 3
    127.0.0.1:6379> smove k1 k2 l1
    (integer) 1
    127.0.0.1:6379> smembers k1
    1) "l3"
    2) "l2"
    127.0.0.1:6379> smembers k2
    1) "l3"
    2) "l5"
    3) "l4"
    4) "l1"
    127.0.0.1:6379> smove k1 k2 l00
    (integer) 0
    
  • sinter :返回两个集合的交集
    127.0.0.1:6379> smembers k1
    1) "l3"
    2) "l2"
    127.0.0.1:6379> smembers k2
    1) "l3"
    2) "l5"
    3) "l4"
    4) "l1"
    127.0.0.1:6379> sinter k1 k2
    1) "l3"
    
  • sunion :返回两个集合的并集
    127.0.0.1:6379> smembers k1
    1) "l3"
    2) "l2"
    127.0.0.1:6379> smembers k2
    1) "l3"
    2) "l5"
    3) "l4"
    4) "l1"
    127.0.0.1:6379> sunion k1 k2
    1) "l3"
    2) "l4"
    3) "l5"
    4) "l2"
    5) "l1"
    
  • sdiff :返回两个集合中的差集(key1中不包含key2中的)
    127.0.0.1:6379> smembers k2
    1) "l3"
    2) "l5"
    3) "l4"
    4) "l1"
    127.0.0.1:6379> smembers k1
    1) "l3"
    2) "l2"
    127.0.0.1:6379> sdiff k1 k2
    1) "l2"
    127.0.0.1:6379> sdiff k2 k1
    1) "l4"
    2) "l5"
    3) "l1"
    

Hash哈希

简介

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

数据结构

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

常用命令

  • hset :给key集合中的field健赋值value,也可以同时设置多个field
  • hget :从key集合field取出value
    127.0.0.1:6379> hset user:1 id 1 name xgt
    (integer) 2
    127.0.0.1:6379> hget user:1 id
    "1"
    127.0.0.1:6379> hget user:1 name
    "xgt"
    
  • hmset :批量设置hash的值,redis4.0已经弃用了,使用hset
    127.0.0.1:6379> hmset user:1 age 10 gender 1
    OK
    
  • hexists :查看哈希表key中,给定field是否存在
    127.0.0.1:6379> hexists user:1 id
    (integer) 1
    127.0.0.1:6379> hexists user:1 haha
    (integer) 0
    
  • hkeys :列出hash集合的所有field
    127.0.0.1:6379> hkeys user:1
    1) "id"
    2) "name"
    3) "age"
    4) "gender"
    
  • hvals :列出哈市集合的所有value
    127.0.0.1:6379> hvals user:1
    1) "1"
    2) "xgt"
    3) "10"
    4) "1"
    127.0.0.1:6379> 
    
  • hincrby :为hash中的域field的值加上增量
    127.0.0.1:6379> hincrby user:1 id 2
    (integer) 3
    127.0.0.1:6379> hincrby user:1 name 5
    (error) ERR hash value is not an integer
    127.0.0.1:6379> hincrby user:1 hash 5
    (integer) 5
    127.0.0.1:6379> hkeys user:1
    1) "id"
    2) "name"
    3) "age"
    4) "gender"
    5) "hash"
    127.0.0.1:6379> hvals user:1
    1) "3"
    2) "xgt"
    3) "10"
    4) "1"
    5) "5"
    
  • hsetnx :将hash表中的field的值设置为value,当且仅当field不存在的时候
    127.0.0.1:6379> hsetnx user:1 id 100
    (integer) 0
    127.0.0.1:6379> hsetnx user:2 id 2
    (integer) 1
    127.0.0.1:6379> hsetnx user:1 xixi 5
    (integer) 1
    127.0.0.1:6379> hkeys user:1
    1) "id"
    2) "name"
    3) "age"
    4) "gender"
    5) "hash"
    6) "xixi"
    127.0.0.1:6379> hvals user:1
    1) "3"
    2) "xgt"
    3) "10"
    4) "1"
    5) "5"
    6) "5"
    

Zset有序集合

简介

Redis有序集合zset与普通set非常相似,是一个没有重复元素的字符串集合;
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分被用来按照最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的;
因为元素是有序的,所以可以很快根据评分或者次序来获取一个范围的元素;
访问有序集合的中间元素也是非常快的,因此能够使用有序集合作为一个没有重复成员的只能列表;

数据结构

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

Zset底层使用了两种数据结构:

  • hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到响应的score值;
  • 跳跃表,目的在于给元素value排序,根据score的范围获取元素列表

常用命令

  • zadd …:讲一个或多个元素及其score值加入到有序key当中
    127.0.0.1:6379> zadd top 200 java 300 c++ 400 c 100 sql
    (integer) 4
    
  • zrange [WITHSCORES]:返回有序集合key中,下表在start和stop之间的元素stop为-1表示余下所有的值,带WITHSCORES,可以让分数一起和值返回到结果集
    127.0.0.1:6379> zrange top 0 -1
    1) "sql"
    2) "java"
    3) "c++"
    4) "c"
    127.0.0.1:6379> zrange top 2 -1
    1) "c++"
    2) "c"
    127.0.0.1:6379> zrange top 2 2
    1) "c++"
    127.0.0.1:6379> zrange top 0 -1 withscores
    1) "sql"
    2) "100"
    3) "java"
    4) "200"
    5) "c++"
    6) "300"
    7) "c"
    8) "400"
    
  • zrangebyscore key minmax [withscores][limit offset count]:返回有序集合key中,所有score值介于min和max之间(包括min或max)的成员,有序集合成员按score值递增次序排序;
    127.0.0.1:6379> zrangebyscore top 100 300 withscores limit 0 2
    1) "sql"
    2) "100"
    3) "java"
    4) "200"
    127.0.0.1:6379> zrangebyscore top 100 300 withscores limit 0 5
    1) "sql"
    2) "100"
    3) "java"
    4) "200"
    5) "c++"
    6) "300"
    
  • zrevangebyscore key maxmin [withscores][limit offset count]:同上,排序规则为从大到小
    127.0.0.1:6379> zrevrangebyscore top 500 300 withscores limit 0 5
    1) "c"
    2) "400"
    3) "c++"
    4) "300"
    
  • zincrby :为元素的score加上增量
    127.0.0.1:6379> zrange top 0 -1 withscores
    1) "sql"
    2) "100"
    3) "java"
    4) "200"
    5) "c++"
    6) "300"
    7) "c"
    8) "400"
    127.0.0.1:6379> zincrby top 400 sql
    "500"
    127.0.0.1:6379> zrange top 0 -1 withscores
    1) "java"
    2) "200"
    3) "c++"
    4) "300"
    5) "c"
    6) "400"
    7) "sql"
    8) "500"
    
  • zrem:删除该集合下,指定的元素
    127.0.0.1:6379> zrange top 0 -1 withscores
    1) "java"
    2) "200"
    3) "c++"
    4) "300"
    5) "c"
    6) "400"
    7) "sql"
    8) "500"
    127.0.0.1:6379> zrem top java
    (integer) 1
    127.0.0.1:6379> zrange top 0 -1 withscores
    1) "c++"
    2) "300"
    3) "c"
    4) "400"
    5) "sql"
    6) "500"
    
  • zcount :统计该集合,分数区间内的元素个数
    127.0.0.1:6379> zrange top 0 -1 withscores
    1) "c++"
    2) "300"
    3) "c"
    4) "400"
    5) "sql"
    6) "500"
    127.0.0.1:6379> zcount top 100 400
    (integer) 2
    
  • zrank :返回该值在集合中的排名,从0开始
    127.0.0.1:6379> zrank top java
    (nil)
    127.0.0.1:6379> zrank top c++
    (integer) 0
    127.0.0.1:6379> zrank top c
    (integer) 1
    

新数据类型

Bitmaps

简介

现代计算机用二进制(位)作为信息的基础单位,1个字节等于8位,合理地使用操作位能够有效地提高内存使用率和开发效率。

Redis提供了Bitmaps这个数据类型可以实现对位的操作:

  • Bitmaps本身不是一种数据类型,实际上它就是字符串key-value,但是它可以对字符串的位进行操作。
  • Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0|1,数组的下表在Bitmaps中叫做偏移量。

常用操作命令

  • setbit L设置Bitmaps中某个偏移量的值(0|1),offset从0开始
    127.0.0.1:6379> setbit 20220825 1 1
    (integer) 0
    127.0.0.1:6379> setbit 20220825 6 1
    (integer) 0
    127.0.0.1:6379> setbit 20220825 11 1
    (integer) 0
    127.0.0.1:6379> setbit 20220825 15 1
    (integer) 0
    127.0.0.1:6379> setbit 20220825 19 1
    (integer) 0
    
    注意:第一次初始化Bitmaps时,如果偏移量非常大,那么整个初始化过程执行会比较慢,可能会造成Redis的阻塞;
  • getbit :获取Bitmaps中某个偏移量的值
    127.0.0.1:6379> getbit 20220825 1
    (integer) 1
    127.0.0.1:6379> getbit 20220825 0
    (integer) 0
    127.0.0.1:6379> getbit 20220825 6
    (integer) 1
    127.0.0.1:6379> getbit 20220825 8
    (integer) 0
    127.0.0.1:6379> getbit 20220825 9
    (integer) 0
    127.0.0.1:6379> getbit 20220825 19
    (integer) 1
    
  • bitcount [start end]:统计字符串从start字节到end字节比特值为1的数量,参数可以使用负数值:-1表示最后一位,-2表示倒数第二位,start end是指bit数组的字节的下表数,二者皆包含
    127.0.0.1:6379> bitcount 20220825 0 1
    (integer) 4
    127.0.0.1:6379> bitcount 20220825 0 3
    (integer) 5
    127.0.0.1:6379> bitcount 20220825 0 0
    (integer) 2
    127.0.0.1:6379> bitcount 20220825 -2 -1
    (integer) 3
    127.0.0.1:6379> bitcount 20220825 -1 -2
    (integer) 0
    
  • bitop and(or/not/xor) [key…]:复合操作,可以做多个Bitmaps的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在sestkey中
    127.0.0.1:6379> setbit 20220824 1 1
    (integer) 0
    127.0.0.1:6379> setbit 20220824 2 1
    (integer) 0
    127.0.0.1:6379> setbit 20220824 3 1
    (integer) 0
    127.0.0.1:6379> setbit 20220825 2 1
    (integer) 0
    127.0.0.1:6379> setbit 20220825 3 1
    (integer) 0
    127.0.0.1:6379> setbit 20220825 4 1
    (integer) 0
    127.0.0.1:6379> bitop and 20220824_25 20220824 20220825
    (integer) 1
    127.0.0.1:6379> bitop or 20220824_26 20220824 20220825
    (integer) 1
    127.0.0.1:6379> bitcount 20220824_25
    (integer) 2
    127.0.0.1:6379> bitcount 20220824_26
    (integer) 4
    

HyperLogLog

简介

技术问题,求几个钟不重复元素个数的问题;
解决:

  • 关系型数据库中,使用distinct count计算不重复个数
  • Redis中可以使用hash、set、bitmaps等数据结构来处理
    但是随着数据不断增加,导致占用空间越来越大,非常大的数据集是不切实际的。

Redis HyperLogLog是用来做技术统计的算法;
优点:在输入元素的数量或者体积非常非常大时,计算基数所需要的空间总是固定的,而且是很小的

在Redis里面,每个HyperLogLog健只需要花费12KB内存就可以计算接近2^64个不同元素的基数,这和计算基数时,元素越多消耗内存就越多的集合形成鲜明对比。

但是,因为HyperLogLog智慧根据输入元素来计算基数,而不会存储输入元素本身,所以HyperLogLog不能像集合那样,返回 输入的各个元素;

基数

比如数据集{1,3,5,7,5,7,8},那么这个数据集的基数集为{1,3,5,7,8},基数(不重复元素)为5。技术的估计就是在误差可接受的范围内,快速计算基数;

常用命令

  • pfadd [element…]:添加指定所有元素到HyperLogLog中,成功返回1,否则返回0

    127.0.0.1:6379> pfadd program java
    (integer) 1
    127.0.0.1:6379> pfadd program php
    (integer) 1
    127.0.0.1:6379> pfadd program java
    (integer) 0
    127.0.0.1:6379> 
    127.0.0.1:6379> pfadd program c++ c
    (integer) 1
    
  • pfcount [key…]:计算HLL的近似基数,可以计算多个HLL

    127.0.0.1:6379> pfadd k1 a b c a d e
    (integer) 1
    127.0.0.1:6379> pfcount k1
    (integer) 5
    127.0.0.1:6379> pfadd k1 f g
    (integer) 1
    127.0.0.1:6379> pfcount k1
    (integer) 7
    127.0.0.1:6379> pfadd k1 f g
    (integer) 0
    127.0.0.1:6379> pfcount k1
    (integer) 7
    
  • pfmerge [sourcekey…]:将一个活多个HLL合并后的结果存储在另一个HLL中

    127.0.0.1:6379> pfadd k2 a b f g
    (integer) 1
    127.0.0.1:6379> pfadd k2 x z v n m
    (integer) 1
    127.0.0.1:6379> pfmerge k3 k1 k2
    OK
    127.0.0.1:6379> pfcount k3
    (integer) 12
    

Geospatial

简介

Redis3.2中增加了堆GEO类型 的支持。GEO,Geographic,地理信息的缩写,该类型,就是元素的二维坐标,在地图上就是经纬度。Redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作;

常用命令

  • geoadd [longitude,latitude,member…]:添加地理位置(经度,纬度,名称)

    127.0.0.1:6379> geoadd china 121.47 31.23 shanghai 106.50 29.53 chongqing
    (integer) 2
    

    两级无法直接添加,一般会下载城市数据,直接通过Java程序一次性导入。有效的经度从-180度到180度。有效的纬度从-85.05112878度到85.05112878度;
    当坐标位置超出指定范围时,该命令将会返回一个错误;

    127.0.0.1:6379> geoadd china 121.47 188 shanghaia
    (error) ERR invalid longitude,latitude pair 121.470000,188.000000
    

    已经添加的数据,是无法再次往里面添加的;

  • geopos [member…]:获得指定地区的坐标值

    127.0.0.1:6379> geopos china shanghai
    1) 1) "121.47000163793563843"
       2) "31.22999903975783553"
    
  • geodist [m|km|ft|mi]:获取两个位置之间的直线距离。单位:m-米(默认值);km-千米;mi-英里;ft-英尺

    127.0.0.1:6379> geodist china shanghai chongqing
    "1447673.6920"
    127.0.0.1:6379> geodist china shanghai chongqing km
    "1447.6737"
    127.0.0.1:6379> geodist china shanghai hubei
    (nil)
    
  • georadius radius m|km|mi|ft:以给定的经纬度为中心,找出某以半径的内的元素

    127.0.0.1:6379> georadius china 121.47 31.23 5000 km
    1) "chongqing"
    2) "shanghai"
    127.0.0.1:6379> georadius china 120 30.23 1000 km
    1) "shanghai"
    

配置文件

UNITS 单位

配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit,大小写不敏感

INCLUDE 包含

配置文件中可以包含其他配置文件内容,作为公共部分提取出来

另外需要注意的时,如果将此配置写在redis.conf 文件的开头,那么后面的配置会覆盖引入文件的配置,如果想以引入文件的配置为主,那么需要将 include 配置写在 redis.conf 文件的末尾

MODULES

redis3.0的爆炸功能是新增了集群,而redis4.0就是在3.0的基础上新增了许多功能,其中这里的 自定义模块配置就是其中之一。通过这里的 loadmodule 配置将引入自定义模块来新增一些功能。

NETWORK 网络

bind IP1 [IP2 …]

# bind 192.168.1.100 10.0.0.1     # listens on two specific IPv4 addresses
# bind 127.0.0.1 ::1              # listens on loopback IPv4 and IPv6
# bind * -::*                     # like the default, all available interfaces

项配置绑定的IP并不是远程访问的客户端的IP地址,而是本机的IP地址。
本机的IP地址是和网卡(network interfaces)绑定在一起的,配置这项后,Redis只会接收来自指定网卡的数据包。比如我的主机有以下网卡:

[root@ecs-340806 ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether fa:16:3e:74:cb:c1 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.122/24 brd 192.168.0.123 scope global noprefixroute dynamic eth0
       valid_lft 53898sec preferred_lft 53898sec
    inet6 fe80::f816:3eff:fe74:cbc1/64 scope link 
       valid_lft forever preferred_lft forever

如果我想要让Redis可以远程连接的话,就需要让Redis监听eht0这块网卡,也就是要加上配置bind 127.0.0.1 192.168.0.122,这样既可以本地访问,也能够远程访问。(主要bind只能有一行配置,如果有多个网卡要监听,就配置多个ip,用空格隔开,否者只有配置的最后一个bind生效)。

protected-mode 保护模式

如果protected-mode是yes的话,如果没有指定bind或者没有指定密码,那么只能本地访问

port 端口号

配置Redis监听的端口号,默认6379

tcp-backlog TCP半连接队列长度配置

在进行TCP/IP连接时,内核会维护两个队列

  • syns queue用于保存已收到sync但没有接收到ack的TCP半连接请求。
    由/proc/sys/net/ipv4/tcp_max_syn_backlog指定
    [root@ecs-340806 ipv4]# cat /proc/sys/net/ipv4/tcp_max_syn_backlog 
    1024
    
  • accept queue,用于保存已经建立的连接,也就是全连接。由/proc/sys/net/core/somaxconn指定
    [root@ecs-340806 ipv4]# cat /proc/sys/net/core/somaxconn 
    1024
    

需要同时提高somaxconn和tcp_max_syn_backlog的值来确保生效

timeout 超时关闭连接

客户端经过多少时间(单位秒)没有操作就关闭连接,0代表永不关闭

timeout 0

tcp-keepalive TCP连接保活策略

TCP连接保活策略,可以通过tcp-keepalive配置项来进行设置,单位为秒,假如设置为60秒,则server端会每60秒向连接空闲的客户端发起一次ACK请求,以检查客户端是否已经挂掉,对于无响应的客户端则会关闭其连接。如果设置为0,则不会进行保活检测,默认是300秒

GENERAL 通用配置

daemonize 启动方式

是否以守护(后台)进程的方式启动,默认no

pidfile 进程pid文件

redis启动后会把pid写入到pidfile指定的文件中

loglevel logfile 日志

loglevel用于配置日志打印机别,默认notice

  • debug:能设置的最高的日志级别,打印所有信息,包括debug信息
  • verbose:打印除了debug日志之外的所有日志
  • notice:打印除了debug和verbose级别的所有日志
  • warning:仅打印非常重要的信息

logfile:指定日志写入的文件

databases 数据库的数量

redis默认有16个数据库,编号从0开始

always-show-logo 启动时否显示log

always-show-logo no

SNAPSHOTTING 快照

这里的配置主要用来做持久化操作

save 持久化保存时间

save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存 
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

这里是用来配置触发 Redis的持久化条件,也就是什么时候将内存中的数据保存到硬盘。
如果只是用Redis的缓存功能,不需要持久化,那么可以注释掉所有的 save 行来停用保存功能。可以直接一个空字符串来实现停用:save “”

stop-writes-on-bgsave-error

默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了

rdbcompression

默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大

rdbchecksum

默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能

dbfilename

设置快照的文件名,默认是 dump.rdb

dir

设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。使用上面的 dbfilename 作为保存的文件名

REPLICATION

replicaof

################################# REPLICATION #################################
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
#   +------------------+      +---------------+
#   |      Master      | ---> |    Replica    |
#   | (receive writes) |      |  (exact copy) |
#   +------------------+      +---------------+
#
# 1) Redis replication is asynchronous, but you can configure a master to
#    stop accepting writes if it appears to be not connected with at least
#    a given number of replicas.
# 2) Redis replicas are able to perform a partial resynchronization with the
#    master if the replication link is lost for a relatively small amount of
#    time. You may want to configure the replication backlog size (see the next
#    sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
#    network partition replicas automatically try to reconnect to masters
#    and resynchronize with them.
#
# replicaof <masterip> <masterport>

使用replicaof使Redis实例成为副本

replica-serve-stale-data

当一个副本失去它与主服务器的连接时或主从复制在进行时,是否依然可以允许客户访问可能过期的数据。在"yes"情况下,slave继续向客户端提供只读服务,有可能此时的数据已经过期;在"no"情况下,任何向此server发送的数据请求服务(包括客户端和此server的slave)都将被告知"error"

replica-read-only

配置副本是否为只读,开启后从则不能写入数据

repl-diskless-sync

主从数据复制是否使用无硬盘复制功能。默认值为no。

repl-diskless-sync-delay

当启用无硬盘备份,服务器等待一段时间后才会通过套接字向从站传送RDB文件,这个等待时间是可配置的。 这一点很重要,因为一旦传送开始,就不可能再为一个新到达的从站服务。从站则要排队等待下一次RDB传送。因此服务器等待一段 时间以期更多的从站到达。延迟时间以秒为单位,默认为5秒。要关掉这一功能,只需将它设置为0秒,传送会立即启动。默认值为5。

repl-ping-replica-period

# Replicas send PINGs to server in a predefined interval. It's possible to
# change this interval with the repl_ping_replica_period option. The default
# value is 10 seconds.
#
# repl-ping-replica-period 10

slave根据指定的时间间隔向master发送ping请求。默认10秒

repl-timeout

# The following option sets the replication timeout for:
#
# 1) Bulk transfer I/O during SYNC, from the point of view of replica.
# 2) Master timeout from the point of view of replicas (data, pings).
# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
#
# It is important to make sure that this value is greater than the value
# specified for repl-ping-replica-period otherwise a timeout will be detected
# every time there is low traffic between the master and the replica. The default
# value is 60 seconds.
#
# repl-timeout 60

主从同步的超时时间,确保这个值大于指定的repl-ping-slave-period,否则在主从间流量不高时每次都会检测到超时

repl-disable-tcp-nodelay

repl-disable-tcp-nodelay:同步之后是否禁用从站上的TCP_NODELAY 如果你选择yes,redis会使用较少量的TCP包和带宽向从站发送数据。但这会导致在从站增加一点数据的延时。 Linux内核默认配置情况下最多40毫秒的延时。如果选择no,从站的数据延时不会那么多,但备份需要的带宽相对较多。默认情况下我们将潜在因素优化,但在高负载情况下或者在主从站都跳的情况下,把它切换为yes是个好主意。默认值为no。

SECURITY 安全

ACL

ACL:访问控制列表。
有两种方法配置ACL:

  • 在命令行通过ACL命令进行配置
  • 在Redis配置文件中开始,可以直接在redis.conf中配置,也可以通过外部aclfile配置,aclfile path。
    配置语法:user … acl rules …,例如 user worker +@list +@connection ~jobs:* on >ffa9203c493aa99

redis默认有一个default用户。如果default具有nopass规则(就是说没有配置密码),那么新连接将立即作为default用户登录,无需通过AUTH命令提供任何密码。否则,连接会在未验证状态下启动,并需要AUTH验证才能开始工作。

描述用户可以做的操作的ACL规则如下

启用或禁用用户(已经建立的连接不会生效)

  • on 启用用户,该用户可以验证身份登陆。
  • off 禁用用户,该用户不允许验证身份登陆。

允许/禁止用户执行某些命令

  • + 允许用户执行command指示的命令
  • - 禁止用户执行command指示的命令
  • +@ 允许用户执行category分类中的所有命令
  • -@ 禁止用户执行category分类中的所有命令
  • +|subcommand 允许执行某个被禁止的command的子命令subcommand。没有对应的-规则。
  • allcommands +@all的别名,允许执行所有命令。
  • nocommands -@all的别名,禁止执行所有命令。

允许/禁止用户访问某些Key

  • ~ 添加用户可以访问的Key,比如*代表用户可以访问所有key,K*代表用户可访问以K开头的key。
  • allkeys 等价于~*
  • resetkeys ~ 刷新允许模式的列表,会覆盖之前设置的模式。例如 user test ~* resetkeys K*,则只允许访问匹配K*的键,*失效。

为用户配置有效密码

  • > 将密码添加到用户的有效密码列表中。例如user test >mypass on,则用户test可以使用mypass验证。
  • < 将密码从用户的有效密码列表中删除,即不可以使用该密码验证。
  • nopass 使用任意密码都可以成功验证。
  • resetpass 清除用户可用密码列表的数据,并清除 nopass 状态。之后该用户不能登陆。直到重新设置密码/设置nopass。
  • reset 重置用户到初始状态。该命令会执行以下操作:resetpass, resetkeys, off,-@all。

ACL日志配置
设置ACL日志最大长度,默认128个记录。这个日志是存在内存里的。

acllog-max-len 128

外部ACL文件配置
默认位置etc/redis/users.acl,我们可以在这个文件中定义所有用户的ACL控制信息。

aclfile /etc/redis/users.acl

requirepass 密码

设置redis连接密码  
比如: requirepass 123 表示redis的连接密码为123.

rename-command

命令重命名,对于一些危险命令例如:    
–flushdb(清空数据库)    
–flushall(清空所有记录)    
–config(客户端连接后可配置服务器)    
–keys(客户端连接后可查看所有存在的键)  
作为服务端redis-server,常常需要禁用以上命令来使得服务器更加安全,禁用的具体做法是是:
rename-command FLUSHALL “”
也可以保留命令但是不能轻易使用,重命名这个命令即可:
rename-command FLUSHALL abcdefg  
这样,重启服务器后则需要使用新命令来执行操作,否则服务器会报错unknown command。

CLIENTS 客户端配置

maxclients

设置可以同时连接客户端的最大数量。默认该项设置为 10000 个客户端。达到限制值后的连接会被拒绝并会返回错误信息

maxclients 10000

MEMORY MANAGEMENT 内存管理

maxmemory

指定Redis最大内存限制。达到内存限制时,Redis将尝试删除已到期或即将到期的Key,如果设置为0 。表示不作限制。

maxmemory-policy noeviction

配置达到最大内存限制后,Redis进行何种操作。默认noeviction
总共有8种策略可供选择:

  • volatile-lru:只对设置了过期时间的Key进行淘汰,淘汰算法近似LRU(LRU:最近使用 Least Recently Used)。
  • allkeys-lru:对所有Key进行淘汰,LRU。
  • volatile-lfu:只对设置了过期时间的Key进行淘汰,淘汰算法近似的LFU。
  • allkeys-lfu:对所有Key进行淘汰,LFU。
  • volatile-random:只对设置了过期时间的Key进行淘汰,淘汰算法为随机淘汰。
  • allkeys-random:对所有Key进行淘汰,随机淘汰。
  • volatile-ttl:只对设置了过期时间的Key进行淘汰,删除即将过期的即ttl最小的。
  • noeviction:永不删除key,达到最大内存再进行数据装入时会返回错误。

对于可以通过删除key来释放内存的策略,如果没有key可以删除了,那么也会报错。

maxmemory-samples

使用LRU/LFU/TTL算法时采样率

Redis使用的是近似的LRU/LFU/minimal TTL算法。主要是为了节约内存以及提升性能。Redis配置文件有maxmemory-samples选项,可以配置每次取样的数量。Redis每次会选择配置数量的key,然后根据算法从中淘汰最差的key。

maxmemory-samples 5

可以通过修改这个配置来获取更高的淘汰精度或者更好的性能。默认值5就可以获得很好的结果。选择10可以非常接近真是的LRU算法,但是会耗费更多的CPU资源。3的话更快但是淘汰结果不是特别准确。

maxmemory-eviction-tenacity 10

如果写入流量异常大,则可能需要增加此值。降低此值可能会降低延迟,但有被逐出的风险处理有效性0=最小延迟,10=默认值,100=不考虑延迟的过程

replica-ignore-maxmemory yes

配置Redis主从复制时,从库超过maxmemory也不淘汰数据。这个配置主要是为了保证主从库的一致性,因为Redis的淘汰策略是随机的,如果允许从库自己淘汰key,那么会导致主从不一致的现象出现(master节点删除key的命令会同步给slave节点)

active-expire-effort 1

设置过期keys仍然驻留在内存中的比重,默认是为1,表示最多只能有10%的过期key驻留在内存中,该值设置的越小,那么在一个淘汰周期内,消耗的CPU资源也更多,因为需要实时删除更多的过期key。

LAZY FREEING 惰性删除

Redis有两个删除keys的原语。一个是DEL并且它是一个阻塞的删除对象的操作。意味着server会停止处理新的command以便以同步的方式回收与对象关联的所有内存。如果被删除的key关联的是一个小对象,那么执行DEL命令所需要的时间非常短,与Redis中其它O(1)或O(log_N)的命令时间开销几乎一样。然鹅,如果key与包含了数百万个元素的大对象相关联,那么服务器为了完成删除命令会阻塞很长时间(甚至几秒钟)。

出于以上原因,Redis提供了非阻塞的删除原语,例如UNLINK(非阻塞式的DEL)和FLUSHALL、FLUSHDB命令的ASYNC选项,以便在后台回收内存。这些命令会在常量(固定的)时间内执行。另外一个线程会在后台尽可能快的以渐进式的方式释放对象。

使用DEL,UNLINK以及FLUSHALL和FLUSHDB的ASYNC选项是由用户来控制的。这应该由应用程序的设计来决定使用其中的哪一个。 然鹅,作为其它操作的副作用,Redis server有时不得不去删除keys或者刷新整个数据库。具体来说,Redis在以下情况下会独立于用户调用而删除对象:

1) 由于maxmemory 和maxmemory policy的设置,为了在不超出指定的内存限制而为新对象腾出空间而逐出旧对象;

2) 因为过期:当一个key设置了过期时间且必须从内存中删除时;

3) 由于在已经存在的key上存储对象的命令的副作用。例如,RENAME命令可能会删除旧的key的内容,当该key的内容被其它内容代替时。类似的,SUNIONSTORE或者带STORE选项的SORT命令可能会删除已经存在的keys。SET命令会删除指定键的任何旧内容,以便使用指定字符串替换。

4)在复制过程中,当副库与主库执行完全重新同步时,整个数据库的内容将被删除,以便加载刚刚传输的RDB文件。

在上述所有情况下,默认情况是以阻塞方式删除对象,就像调用DEL一样。但是,你可以使用以下配置指令专门配置每种情况,以非阻塞的方式释放内存,就像调用UNLINK一样。

lazyfree-lazy-eviction

内存达到设置的maxmemory时,是否使用惰性删除,对应上面 1)

lazyfree-lazy-expire

过期keys是否惰性删除,对应上面 2)

lazyfree-lazy-server-del

内部删除选项,对应上面选项 3)的情况是否惰性删除

replica-lazy-flush

slave接收完RDB文件后清空数据是否是惰性的,对应上面情况 4)

lazyfree-lazy-user-del

是否将DEL调用替换为UNLINK,注释里写的从user code里替换DEL调用为UNLINK调用可能并不是一件容易的事,因此可以使用以下选项,将DEL的行为替换为UNLINK

THREADED I/O

Redis大体上是单线程的,但是也有一些场景使用额外的线程去做的,比如UNLINK、slow I/O accesses。

现在还可以在不同的I/O线程中处理Redis客户端socket读写。(只是网络IO这块儿成了多线程,执行命令的那个家伙,还是单线程!)特别是因为写操作很慢,通常Redis的用户使用pipeline来提升每个核心下的Redis性能,并且运行多个Redis实例来实现扩展。使用多线程I/O,不需要使用pipeline和实例切分,就可以轻松的提升两倍的性能。

默认情况下,多线程是禁用的,我们建议只在至少有4个或更多内核的机器中启用多线程,至少保留一个备用内核。使用超过8个线程不太可能有多大帮助。我们还建议仅当您确实存在性能问题时才使用线程化I/O,因为除非Redis实例能够占用相当大的CPU时间,否则使用此功能没有意义。

io-threads 配置IO线程数

如果你的机器是4核的,可以配置2个或者3个线程。如果你有8核,可以配置6个线程。通过下面这个参数来配置线程数:

io-threads 4

将io-threads设置为1将只使用主线程。当启用I/O线程时,我们只使用多线程进行写操作,也就是说,执行write(2)系统调用并将Client缓冲区传输到套接字。

io-threads-do-reads

是否启用读取线程和协议解析:

io-threads-do-reads no

注意 1:此配置指令不能在运行时通过 CONFIG SET 更改。Aso 此功能当前在 SSL 时不起作用
注意 2:如果你想使用 redis-benchmark 测试 Redis 加速,请确保你也在线程模式下运行基准测试本身,使用 --threads 选项来匹配 Redis 线程数,否则你将无法 注意改进。

KERNEL OOM CONTROL

设置OOM时终止哪些进程

在 Linux 上,可以提示内核 OOM 杀手在内存不足时应该首先杀死哪些进程。启用此功能使Redis 主动控制其所有进程的oom_score_adj 值,具体取决于它们的角色。默认分数将尝试在所有其他进程之前杀死后台子进程,并在主进程之前杀死副本。

oom-score-adj no

  • no:对oom-score-adj不做任何修改(默认值)
  • yes:relative的别名
  • absolute:oom-score-adj-values配置的值将写入内核
  • relative:当服务器启动时,使用相对于oom_score_adj初始值的值,然后将其限制在-1000到1000的范围内。因为初始值通常为0,所以它们通常与绝对值匹配。

oom-score-adj-values 0 200 800

当使用oom-score-adj选项(不为no)时,该指令控制用于主、从和后台子进程的特定值。数值范围为-2000到2000(越高意味着死亡的可能性越大)。
非特权进程(不是根进程,也没有CAP_SYS_RESOURCE功能)可以自由地增加它们的价值,但不能将其降低到初始设置以下。这意味着将oom score adj设置为“相对”,并将oom score adj值设置为正值将始终成功

# 分别控制主进程、从进程和后台子进程的值 
oom-score-adj-values 0 200 800

KERNEL transparent hugepage CONTROL

当使用 oom-score-adj 时,该指令控制用于主、副本和后台子进程的特定值。值范围为 -2000 到 2000(更高意味着更有可能被杀死)。
非特权进程(不是 root,并且没有 CAP_SYS_RESOURCE 功能)可以自由地增加它们的值,但不能将其降低到其初始设置以下。这意味着将 oom-score-adj 设置为“relative”并将 oom-score-adj-values 设置为正值将始终成功。
通常内核透明大页面控件默认设置为“madvise”或“never”(/sys/kernel/mm/transparent_hugepage/enabled),在这种情况下,此配置无效。在设置为“始终”的系统上,redis 将尝试专门为 redis 进程禁用它,以避免专门针对 fork(2) 和 CoW 的延迟问题。
如果出于某种原因您更喜欢保持启用状态,您可以将此配置设置为“no”,并将内核全局设置为“always”。

disable-thp yes

APPEND ONLY MODE AOF持久化配置

appendonly

开始/关闭aof

appendonly no

appendfilename

aof文件名称

appendfilename "appendonly.aof"

appendfsync

执行fsync()系统调用刷盘的频率

appendfsync everysec
  • everysec:每秒执行,可能会丢失最后一秒的数据。
  • always:每次写操作执行,数据最安全,但是对性能有影响。
  • no:不强制刷盘,由内核决定什么时候刷盘,数据最不安全,性能最好。

no-appendfsync-on-rewrite

当有后台保存任务时,关闭appendfsync
当后台在执行save任务或者aof文件的rewrite时,会对磁盘造成大量I/O操作,在某些Linux配置中,Redis可能会在fsync()系统调用上阻塞很长时间。需要注意的是,目前还没有很好的解决方法,因为即使是在不同的线程中执行fsync()调用也会阻塞write(2)调用。

为了缓解上述问题,可以使用以下选项,防止在进行BGSAVE或者BGREWRITEAOF时在主进程中调用fsync()。

这意味这如果有其它子进程在执行saving任务时,Redis的行为相当于配置了appendfsync none。实际上,这意味着在最坏的情况下(使用Linux默认设置),可能丢失最多30s的日志。

如果您有延迟的问题(性能问题),将此设置为“yes”,否则,设置为“no”。从持久化的角度看,这是最安全的选择。

no-appendfsync-on-rewrite no

auto-aof-rewrite 自动重写aof文件

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
  • auto-aof-rewrite-percentage:默认值为100。aof自动重写配置,当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候,Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
  • auto-aof-rewrite-min-size:设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写。

aof-load-truncated

在Redis启动过程的最后,当AOF数据加载回内存时,可能会发现AOF文件被截断。当运行Redis的系统崩溃时,可能会发生这种情况,尤其是在安装ext4文件系统时,没有data=ordered选项(然而,当Redis本身崩溃或中止,但操作系统仍然正常工作时,这种情况不会发生)。

Redis可以在出现这种情况时带着错误退出,也可以加载尽可能多的数据(现在是默认值),并在发现AOF文件在最后被截断时启动。以下选项控制此行为。

如果aof load truncated设置为yes,则会加载一个被截断的aof文件,Redis服务器开始发送日志,通知用户该事件。否则,如果该选项设置为“no”,服务器将因错误而中止并拒绝启动。当选项设置为“no”时,用户需要使用“redis-check-aof”实用程序修复AOF文件,然后才能重新启动服务器。

请注意,如果在中间发现AOF文件已损坏,服务器仍将退出并出现错误。此选项仅适用于Redis尝试从AOF文件读取更多数据,但找不到足够字节的情况。

aof-use-rdb-preamble 开启混合持久化

当重写AOF文件时,Redis能够在AOF文件中使用RDB前导,以更快地重写和恢复。启用此选项后,重写的AOF文件由两个不同的节组成:

[RDB file][AOF tail]

加载时,Redis识别出AOF文件以“Redis”字符串开头,并加载带前缀的RDB文件,然后继续加载AOF尾部。

aof-use-rdb-preamble yes

LUA SCRIPTING LUA脚本相关

lua-time-limit 配置LUA脚本最大执行时长

Lua 脚本的最大执行时间(以毫秒为单位)。如果达到最大执行时间,Redis 将记录脚本在最大允许时间后仍在执行,并开始回复带有错误的查询。
如果将其设置为 0 或负值以无警告地无限执行。

lua-time-limit 5000

REDIS CLUSTER 集群配置

cluster-enabled 允许集群模式

只有以集群模式启动的Redis实例才能作为集群的节点

cluster-config-file 集群配置文件

cluster-config-file nodes-6379.conf

每个 Cluster 节点都有一个集群配置文件。此文件不适合手动编辑。它由 Redis 节点创建和更新。
每个 Redis Cluster 节点都需要不同的集群配置文件。确保在同一系统中运行的实例没有重叠的集群配置文件名。

cluster-node-timeout 节点超时时间

集群模式下,master节点之间会互相发送PING心跳(毫秒)来检测集群master节点的存活状态,超过配置的时间没有得到响应,则认为该master节点主观宕机。

cluster-node-timeout 15000

cluster-replica-validity-factor 设置副本有效因子

副本数据太老旧就不会被选为故障转移的启动者。

副本没有简单的方法可以准确测量其“数据年龄”,因此需要执行以下两项检查:

  • 如果有多个复制副本能够进行故障切换,则它们会交换消息,以便尝试为具有最佳复制偏移量的副本提供优势(已经从master接收了尽可能多的数据的节点更可能成为新master)。复制副本将尝试按偏移量获取其排名,并在故障切换开始时应用与其排名成比例的延迟(排名越靠前的越早开始故障迁移)。
  • 每个副本都会计算最后一次与其主副本交互的时间。这可以是最后一次收到的PING或命令(如果主机仍处于“已连接”状态),也可以是与主机断开连接后经过的时间(如果复制链路当前已关闭)。如果最后一次交互太旧,复制副本根本不会尝试故障切换。

第二点的值可以由用户调整。特别的,如果自上次与master交互以来,经过的时间大于(node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period,则不会成为新的master。

较大的cluster-replica-validity-factor可能允许数据太旧的副本故障切换到主副本,而太小的值可能会阻止群集选择副本。

为了获得最大可用性,可以将cluster-replica-validity-factor设置为0,这意味着,无论副本上次与主机交互的时间是什么,副本都将始终尝试故障切换主机。(不过,他们总是会尝试应用与其偏移等级成比例的延迟)。

0是唯一能够保证当所有分区恢复时,集群始终能够继续的值(保证集群的可用性)。

cluster-replica-validity-factor 10

cluster-migration-barrier

设置master故障转移时保留的最少副本数

群集某个master的slave可以迁移到孤立的master,即没有工作slave的master。这提高了集群抵御故障的能力,因为如果孤立master没有工作slave,则在发生故障时无法对其进行故障转移。

只有在slave的旧master的其他工作slave的数量至少为给定数量时,slave才会迁移到孤立的master。这个数字就是cluster-migration-barrier。值为1意味着slave只有在其master至少有一个其他工作的slave时才会迁移,以此类推。它通常反映集群中每个主机所需的副本数量。

默认值为1(仅当副本的主副本至少保留一个副本时,副本才会迁移)。要禁用迁移,只需将其设置为非常大的值。可以设置值0,但仅对调试有用,并且在生产中很危险。

cluster-migration-barrier 1

cluster-require-full-coverage 哈希槽全覆盖检查

默认情况下,如果Redis群集节点检测到至少有一个未覆盖的哈希槽(没有可用的节点为其提供服务),它们将停止接受查询。这样,如果集群部分关闭(例如,一系列哈希槽不再被覆盖),那么所有集群最终都将不可用。一旦所有插槽再次被覆盖,它就会自动返回可用状态。

然而,有时您希望正在工作的集群的子集继续接受对仍然覆盖的密钥空间部分的查询。为此,只需将cluster-require-full-coverage选项设置为no。

cluster-require-full-coverage yes

cluster-replica-no-failover 是否自动故障转移

当设置为“yes”时,此选项可防止副本在主机故障期间尝试故障切换master。但是,如果被迫这样做,主机仍然可以执行手动故障切换。

这在不同的场景中很有用,尤其是在多个数据中心运营的情况下,如果不在DC(DataCenter?)完全故障的情况下,我们希望其中一方永远不会升级为master。

cluster-replica-no-failover no

cluster-allow-reads-when-down

集群失败时允许节点处理读请求
此选项设置为“yes”时,允许节点在集群处于关闭状态时提供读取流量,只要它认为自己拥有这些插槽。

这对两种情况很有用。第一种情况适用于在节点故障或网络分区期间应用程序不需要数据一致性的情况。其中一个例子是缓存,只要节点拥有它应该能够为其提供服务的数据。

第二个用例适用于不满足三个分片集群,但又希望启用群集模式并在以后扩展的配置。不设置该选项而使用1或2分片配置中的master中断服务会导致整个集群的读/写服务中断。如果设置此选项,则只会发生写中断。如果达不到master的quorum(客观宕机)数值,插槽所有权将不会自动更改。

cluster-allow-reads-when-down no

CLUSTER DOCKER/NAT support

声明访问IP、port

以下三项设置对NAT网络或者Docker的支持。因为NAT端口映射的IP地址在局域网之外是没办法访问到的,因此在这种情况下,要声明集群的公网网关(NAT映射)/宿主机的IP地址,以便局域网之外也可以访问到NAT映射后的/Docker容器内的Redis集群中的每个实例。

cluster-announce-bus-port集群节点之间进行数据交换的额外端口。

cluster-announce-ip 
cluster-announce-port 
cluster-announce-bus-port

# Example:
# cluster-announce-ip 10.1.1.5
# cluster-announce-port 6379
# cluster-announce-bus-port 6380

SLOW LOG 慢日志

Redis的慢查询日志功能用于记录执行时间超过给定时长的命令请求,用户可以通过这个功能产生的日志来监视和优化查询速度

slowlog-log-slower-than 设置慢日志记录阈值

超过这个值的命令会被记录到慢日志中,默认10000微秒。

slowlog-log-slower-than <microseconds>

slowlog-max-len 慢日志文件大小

可以通过这个配置改变慢日志文件的最大长度,超过这个长度后最旧的记录会被删除。默认128。

slowlog-max-len 128

LATENCY MONITOR 延迟监控

Redis延迟监控子系统在运行时对不同的操作进行采样,以收集与Redis实例可能的延迟源相关的数据。

通过延迟命令,用户可以打印图表和获取报告。

系统仅记录在等于或大于通过延迟监视器阈值配置指令指定的毫秒数的时间内执行的操作。当其值设置为零时,延迟监视器将关闭。

默认情况下,延迟监控是禁用的,因为如果没有延迟问题,通常不需要延迟监控,而且收集数据会对性能产生影响,虽然影响很小,但可以在大负载下进行测量。如果需要,可以在运行时使用命令CONFIG SET latency-monitor-threshold 轻松启用延迟监控。

latency-monitor-threshold 设置延迟阈值

latency-monitor-threshold 0

EVENT NOTIFICATION 事件通知

Redis 可以将密钥空间中发生的事件通知 Pub/Sub 客户端。documented at https://redis.io/topics/notifications
例如,如果启用了键空间事件通知,并且客户端对存储在数据库 0 中的键“foo”执行 DEL 操作,则将通过 Pub/Sub 发布两条消息:in the Database 0, two messages will be published via Pub/Sub:
可以在一组类中选择 Redis 将通知的事件,每个类都由一个字符标识:

  • PUBLISH keyspace@0:foo del
  • PUBLISH keyevent@0:del foo
  • K Keyspace events, published with keyspace@ prefix.
  • E Keyevent events, published with keyevent@ prefix.
  • g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, …
  • $ String commands
  • l List commands
  • s Set commands
  • h Hash commands
  • z Sorted set commands
  • x Expired events (events generated every time a key expires)
  • e Evicted events (events generated when a key is evicted for maxmemory)
  • t Stream commands
  • d Module key type events
  • m Key-miss events (Note: It is not included in the ‘A’ class)
  • A Alias for g$lshzxetd, so that the “AKE” string means all the events
  •    (Except key-miss events which are excluded from 'A' due to their
    
  •     unique nature).
    
  • notify-keyspace-events" 将一个由零个或多个字符组成的字符串作为参数。空字符串表示禁用通知。
  • 示例1:启用列表和通用事件,从事件名称的角度来看,使用:
    notify-keyspace-events Elg
  • 示例2:获取订阅频道名称__keyevent@0__:expired 的过期密钥流使用:
    notify-keyspace-events Ex
  • 默认情况下所有通知都是禁用的,因为大多数用户不需要此功能并且该功能有一些开销。请注意,如果您没有指定 K 或 E 中的至少一个,则不会传递任何事件。
notify-keyspace-events ""

GOPHER SERVER Gopher协议

gopher-enabled no

是否开启Gopher协议

ADVANCED CONFIG 高级设置

hash-max-ziplist

设置Hash底层数据结构由ziplist转为hashtable的阈值

当Hash类型的keys只包含了少量的实体并且实体的大小没有超过给定的阈值时,Hash底层会使用ziplist来存储数据而不是使用hashtable以节省空间。

hash-max-ziplist-entries 512 
hash-max-ziplist-value 64

当一个Hash类型的key包含的实体数量超过了hash-max-ziplist-entries的值或者某个实体的大小超过了hash-max-ziplist-value的值(单位字节),那么底层编码就会升级成hashtable。

list-max-ziplist-size

设置List底层数据结构quicklist中单个ziplist的大小

Redis中List数据结构的底层使用的是quicklist的数据结构,本质上是ziplist作为节点串起来的linkedlist。可以通过该项设置来改变每个ziplist的最大大小(ziplist中的fill属性,超过这个值就会开启一个新的ziplist)。总共提供了-5到-1五个选项:

  • -5:最大大小为64Kb,不推荐作为正常情况下的负载
  • -4:最大大小为32Kb,不推荐
  • -3:最大大小为16Kb,不是很推荐
  • -2:最大大小为8Kb,good
  • -1:最大大小为4Kb,good
    默认值是-2

list-compress-depth

ziplist不够zip啊,所以再压缩一下吧。实际上是考虑了这样的场景,即List数据结构两端访问频率比较高,但是中间部分访问频率不是很高的情况,那么使用ziplist存放这部分结构就有点浪费,是不是可以把这部分结构进行压缩(LZF算法压缩)呢?有下面几个值:

0:代表不压缩,默认值
1:两端各一个节点不压缩
2:两端各两个节点不压缩
…:依次类推。。。

list-compress-depth 0

set-max-intset-entries

Set数据结构只有在一种情况下会使用intset来存储:set由能转成10进制且数值在64bit有符号整形数值组成时。下面的配置设置了intset能存储的最大entities数量,超过这个数量会转成hashtable存储。默认512个

set-max-intset-entries 512

zset-max-ziplist

设置ZSet底层数据结构由ziplist转为skiplist的阈值

zset-max-ziplist-entries 128 
zset-max-ziplist-value 64

hll-sparse-max-bytes

设置HyperLogLog底层稀疏矩阵转为稠密矩阵的阈值
HyperLogLog当在计数比较小时会使用稀疏矩阵来存储,只有当计数达到阈值时,才会转为稠密矩阵。

超过16000的值是完全无用的,因为这种情况下使用稠密矩阵更加节省内存。

建议的值是3000左右,以便在不降低太多PFADD速度的情况下获取空间有效编码的好处,稀疏编码的PFADD的时间复杂度为O(N)。当不考虑CPU占用时而考虑内存占用时,这个值可以升到10000左右。

hll-sparse-max-bytes 3000

stream-node-max

自定义Stream宏节点大小

可以通过stream-node-max-bytes选项修改Stream中每个宏节点能够占用的最大内存,或者通过stream-node-max-entries参数指定每个宏节点中可存储条目的最大数量。

stream-node-max-bytes 4096
stream-node-max-entries 100

activerehashing 是否开启Rehash

Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存。

客户端输出缓存控制

客户端输出缓冲区限制可用于强制断开由于某种原因从服务器读取数据速度不够快的客户端(一个常见原因是发布/订阅客户端不能像发布服务器生成消息那样快地使用消息)。

对于三种不同类型的客户端,克制设置不同的限制:

normal:一般客户端包含监控客户端
replica:副本客户端(slave)
pubsub:客户端至少订阅了一个pubsub通道或模式。
每个客户端输出缓冲区限制指令语法:

  • 输出缓冲区限制
    client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
    

一旦达到限制或者达到之后又过了秒,那么客户端会立即被断开连接。

例如,如果为32兆字节,和分别为16兆字节,10秒,则如果输出缓冲区的大小达到32兆字节,客户端将立即断开连接,但如果客户端达到16兆字节并连续超过限制10秒,客户端也将断开连接。

默认情况下,普通客户端不受限制,因为它们不会在没有请求(以推送方式)的情况下接收数据,而是在请求之后接收数据,因此只有异步客户端可能会创建一个场景,其中请求数据的速度比读取数据的速度快。

相反,pubsub和副本客户端有一个默认限制,因为订阅者和副本以推送方式接收数据。

硬限制或软限制都可以通过将其设置为零来禁用。

  • client-output-buffer-limit
    client-output-buffer-limit normal 0 0 0
    client-output-buffer-limit replica 256mb 64mb 60
    client-output-buffer-limit pubsub 32mb 8mb 60
    

client-query-buffer-limit 客户端query buffer大小

客户端query buffer大小不能超过该项配置的值。

每个Client都有一个query buffer(查询缓存区或输入缓存区), 它用于保存客户端的发送命令,redis server从query buffer获取命令并执行。如果程序的Key设计不合理,客户端使用大量的query buffer,这会导致redis server比较危险,很容易达到maxmeory限制,导致缓存数据被清空、数据无法写入和oom.
https://blog.csdn.net/u012271526/article/details/107208295

client-query-buffer-limit 1gb

proto-max-bulk-len 批量请求单个字符串限制

默认512mb,可以通过下面选项修改

proto-max-bulk-len 512mb

hz Redis执行任务频率

Redis调用一个内部函数来执行许多后台任务,比如在超时时关闭客户端连接,清楚从未被请求过的过期key…

并非所有任务都已相同的频率执行,但Redis根据指定的hz值检查要执行的任务。

默认情况下,hz的值为10.提高这个值会让Redis在空闲的时候占用更多的CPU,但同时也会让Redis在有很多keys同时过期时响应更快并且可以更精确的处理超时。

范围在1到500之间,但是超过100通常不是一个好主意。大多数用户应该使用缺省值10,只有在需要非常低延迟的环境中才应该将值提高到100。

hz 10

dynamic-hz 动态hz配置

根据客户端连接的数量动态的调整hz的值,当有更多的客户端连接时,会临时以hz设置基准提高该hz的值。默认开启。

dynamic-hz yes

aof-rewrite-incremental-fsync AOF重写时执行fsync刷盘策略

当一个子系统重写AOF文件时,如果启用了以下选项,则该文件将每生成32MB的数据进行fsync同步。这对于以更增量的方式将文件提交到磁盘并避免较大的延迟峰值非常有用。

aof-rewrite-incremental-fsync yes

rdb-save-incremental-fsync 保存RDB文件时执行fsync刷盘策略

当redis保存RDB文件时,如果启用以下选项,则每生成32 MB的数据,文件就会同步一次。这对于以更增量的方式将文件提交到磁盘并避免较大的延迟峰值非常有用。

rdb-save-incremental-fsync yes

LFU设置

设置Redis LFU相关。Redis LFU淘汰策略实现有两个可调整参数:lfu-log-factor和lfu-decay-time。

计数器衰减时间是必须经过的时间,以分钟为单位,以便将关键计数器除以 2(或者如果它的值小于 <= 10,则递减)。

lfu-log-factor 10

lfu-decay-time 的默认值是 1。一个特殊的值 0 表示每次碰巧被扫描时都会衰减计数器。

lfu-decay-time 1

ACTIVE DEFRAGMENTATION 碎片整理

主动(在线)碎片整理允许Redis服务器压缩内存中数据的少量分配和释放之间的空间(内存碎片),从而回收内存。

碎片化是每个分配器(幸运的是,Jemalloc比较少发生这种情况)和某些工作负载都会发生的自然过程。通常需要重启服务器以降低碎片,或者至少清除所有数据并重新创建。然而,多亏了Oran Agra为Redis 4.0实现的这一功能,这个过程可以在服务器运行时以“hot”的方式在运行时发生(类似热部署的意思,不需要停止服务)。

基本上,当碎片超过某个级别(参见下面的配置选项)时,Redis将通过利用特定的Jemalloc功能(以了解分配是否导致碎片并将其分配到更好的位置)开始在连续内存区域中创建值的新副本,同时释放数据的旧副本。对所有键递增地重复该过程将导致碎片降至正常值。

需要了解的重要事项:

1.默认情况下,此功能被禁用,并且仅当您编译Redis以使用我们随Redis源代码提供的Jemalloc副本时,此功能才有效。这是Linux版本的默认设置。

2.如果没有碎片问题,则无需启用此功能。

3.一旦遇到内存碎片,可以在需要时使用命令CONFIG SET activedefrag yes启用此功能。

配置参数能够微调碎片整理过程的行为。如果你不确定它们是什么意思,最好不要改变默认值。

activedefrag

开启活动碎片整理

active-defrag-ignore-bytes

启动活动碎片整理的最小内存碎片阈值

active-defrag-threshold-lower

启动活动碎片整理的最小内存碎片百分比

active-defrag-threshold-upper

尝试释放的最大百分比

active-defrag-cycle-min

最少CPU使用率

active-defrag-cycle-max

最大CPU使用率

active-defrag-max-scan-fields

最大扫描量

jemalloc-bg-thread

使用后台线程

Redis服务配置常用参数

# Basic - 基础参数
daemonize yes
supervised auto
databases 16
bind 127.0.0.1 10.20.172.108
port 6379
dir database/redis
pidfile var/run/redis.pid 

# Security - 安全参数
requirepass www.weiyigeek.top
rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
rename-command FLUSHDB b840fc02d524045429941cc15f59e41cb7be6c53
rename-command FLUSHALL b840fc02d524045429941cc15f59e41cb7be6c54
rename-command EVAL b840fc02d524045429941cc15f59e41cb7be6c55
rename-command DEBUG b840fc02d524045429941cc15f59e41cb7be6c56
rename-command SHUTDOWN b840fc02d524045429941cc15f59e41cb7be6c57

# Persistent(RDB/AOF) - 持久化配置
save 900 1  
save 300 10
save 60 10000
# - RDB 方式
rdbchecksum yes
rdbcompression yes
dbfilename dump-master.rdb
stop-writes-on-bgsave-error no
# - AOF 方式
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-rewrite-incremental-fsync yes
aof-load-truncated yes

# Memory Limit and Policy - 内存使用限制与内存策略
maxmemory 2gb
maxmemory-policy volatile-lru

# Master-Slave  - 主从模式关键参数
slaveof 127.0.0.1 6379 
masterauth redispass
slave-read-only yes
slave-priority 100
slave-serve-stale-data yes
repl-disable-tcp-nodelay no

# Cluster - 集群模式关键参数
cluster-enabled yes 
cluster-config-file  data/nodes.conf
cluster-node-timeout 5000
# - 当负责一个插槽的主库下线且没有相应的从库进行故障恢复时集群仍然可用
cluster-require-full-coverage no 
# - 只有当一个主节点至少拥有其他给定数量个处于正常工作中的从节点的时候,才会分配从节点给集群中孤立的主节点
cluster-migration-barrier 1
# - 参数配置,该配置意思是主节点写数据完成后,最少需要同步的slave数量。
min‐replicas‐to‐write 1

# Connection related - 连接超时参数
timeout 300 
tcp-keepalive 60 

# Lua execute - 脚本执行查询时间
lua-time-limit 5000

# Slow Query - Slow 日志查询与长度
slowlog-log-slower-than 10000
slowlog-max-len 128

# Cache and Buffer - 缓存限制
# client-output-buffer-limit normal 0 0 0
# client-output-buffer-limit slave 256mb 64mb 60
# client-output-buffer-limit pubsub 32mb 8mb 60

# Logging - 日志相关配置
loglevel warning
logfile "/database/redis/logs/redis-6379.log"

发布和订阅

是什么

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息;

Redis客户端可以订阅任意数量的频道。

命令行实现

  • 打开一个客户端订阅 channel 1

    127.0.0.1:6379> subscribe channel1
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "channel1"
    3) (integer) 1
    
  • 打开第二个客户端,给channel1 发布消息

    127.0.0.1:6379> publish channel1 hello
    (integer) 1
    

    返回值 1 是订阅者的数量

  • 打开第一个客户端就能消息

    127.0.0.1:6379> subscribe channel1
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "channel1"
    3) (integer) 1
    1) "message"
    2) "channel1"
    3) "hello"
    
  • 注意:
    发布的消息没有持久化,只能收到订阅后发布的消息

Jedis操作Redis

创建Module添加依赖

<dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
    </dependencies>

注意Redis开启远程访问

  • 开放Linux对应Redis端口,个人玩的时候可以直接禁用Linux的防火墙
systemctl stop/disable firewalld.service
  • Redis配置文件中相关网络配置
    如果开启了peotected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接收本机的响应;

简单测试连接

public static void main(String[] args) {
        // 创建Jedis对象
        Jedis jedis = new Jedis("xxx",6379);
        // 密码
        jedis.auth("password");
        // 测试
        String ping = jedis.ping();
        System.out.println(ping);
    }

如果返回pong就是连接成功

使用连接处

public static JedisPool getJedisPool(){
        if (null == jedisPool){
            synchronized (JedisPoolUtil.class){
                JedisPoolConfig poolConfig = new JedisPoolConfig();
                // 最大连接数
                poolConfig.setMaxTotal(200);
                poolConfig.setMaxIdle(32);
                poolConfig.setMaxWaitMillis(180*1000);
                // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
                poolConfig.setBlockWhenExhausted(true);
                // 在获取连接的时候检查有效性, 默认false
                poolConfig.setTestOnBorrow(true); // ping pong
                jedisPool = new JedisPool(poolConfig,"xxx",6379,60*1000,"xxx");
            }
        }
        return jedisPool;
    }

测试相关数据类型

Set<String> keys = jedis.keys("*");
for (String key : keys) {    
    System.out.println(key);
}
@Test
    public void test2(){
        // 创建Jedis对象
        Jedis jedis = new Jedis("xxx",6379);
        // 密码
        jedis.auth("xxx");
        jedis.set("k1","l1");
        jedis.set("k2","l2");
        System.out.println(jedis.get("k1"));
        Set<String> keys = jedis.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
        System.out.println(jedis.ttl("k1"));
        System.out.println(jedis.exists("k1"));
        jedis.mset("s1","1","1","2","3","4","1","2");
        System.out.println(jedis.mget("s1","1","3"));
        System.out.println("==============");
        // 操作List
        jedis.lpush("l1","1","2","3.4","2.2","1");
        System.out.println(jedis.lrange("l1", 0, -1));
        System.out.println("==============");
        // set
        jedis.sadd("set1","xgt","wp","zyk");
        Set<String> s1 = jedis.smembers("set1");
        for (String s : s1) {
            System.out.println(s);
        }
        // hash
        System.out.println("==============");
        jedis.hset("h1","username","xgt");
        jedis.hset("h1","id","1");
        System.out.println(jedis.hget("h1", "username"));
        Map<String,String> map = new HashMap();
        map.put("s","a");
        map.put("b","b");
        map.put("c","c");
        jedis.hset("h2",map);
        System.out.println(jedis.hget("h2", "s"));
        System.out.println(jedis.hmget("h2", "s", "b", "c"));
        // zset
        System.out.println("================");
        jedis.zadd("z",100d,"math");
        jedis.zadd("z",101d,"english");

        System.out.println(jedis.zrange("z", 0, -1));
    }

手机验证码模拟

发送验证码,随机生成验证码,每个手机号每天只能输入三次

package per.xgt;

import redis.clients.jedis.Jedis;

import java.util.concurrent.ThreadLocalRandom;

/**
 * @Author: gentao9527
 * @date: 2022/8/25 18:55
 * @Description: TODO
 * @Version: 1.0
 */
public class VerificationCode {

    private static Jedis getJedis() {
        // 创建Jedis对象
        Jedis jedis = new Jedis("xxx", 6379);
        // 密码
        jedis.auth("xxx");
        return jedis;
    }

    public static void main(String[] args) {
        // 验证码发送
        setVerifyCode("15823232323");

        // 校验验证码
        //verifyCode("15823232323","597616");
        //verifyCode("15823232323","11");
    }

    // 校验验证码
    public static void verifyCode(String phone, String code) {
        // 连接Redis
        Jedis jedis = getJedis();
        // 获得Redis中的验证码
        String codeVerification = "codeVerification" + phone;

        String localCode = jedis.get(codeVerification);
        // 判断验证码
        if (null == localCode) {
            System.out.println("请先生成验证码!");
            jedis.close();
            return;
        } else if (localCode.equals(code)) {
            System.out.println("成功");
        } else {
            System.out.println("失败");
        }

        jedis.close();
    }

    // 每个收集每天只能发送三次,验证码放到redis中
    public static void setVerifyCode(String phone) {
        // 连接Redis
        Jedis jedis = getJedis();
        // 拼接key
        // 手机发送次数
        String countVerification = "countVerification" + phone;
        // 验证码
        String codeVerification = "codeVerification" + phone;

        // 每个手机每天只能发送三次
        String count = jedis.get(countVerification);
        if (null == count) {
            // 没有发送次数,第一次发送
            // 设置发送次数为1
            jedis.setex(countVerification, 24 * 60 * 60, "1");
        } else if (Integer.valueOf(count) <= 2) {
            // 发送次数+1
            jedis.incr(countVerification);
        } else {
            // 发送了3次了,不能再发送
            System.out.println("发送次数已达上限!");
            jedis.close();
            return;
        }

        // 发送的验证码放在redis里面
        String localCode = getCode();
        System.out.println(localCode);
        jedis.setex(codeVerification, 2 * 60, localCode);

        // 关闭jedis
        jedis.close();

    }

    // 生成验证码方式1
    public static String getCode() {
        String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
        return code;
    }

    // 生成验证码方式2
    public static String getLocalCode() {
        String code = String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));
        return code;
    }

}

SpringBoot整合Redis

依赖

<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.2.1.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--redis-->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--Spring2.X继承Redis所需commons-pool2-->
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>

    </dependencies>

配置文件

# 应用名称
spring.application.name=Redis-03-SpringBoot

# 服务器地址
spring.redis.host=xxx
# 端口号
spring.redis.port=6379
# 密码
spring.redis.password=xxx
# redis数据库索引(默认为0)
spring.redis.database=0
# 连接超时时间(毫秒)
spring.redis.timeout=1800000
# 连接处最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
# 最大阻塞等待时间(负数表示没有限制)
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=1

配置类

package per.xgt.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * @Author: gentao9527
 * @date: 2022/8/26 10:14
 * @Description: TODO
 * @Version: 1.0
 */
@Configuration
@EnableCaching // 开启缓存操作
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

测试

@Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/test")
    public String test1(){
        // 设置值到redis
        redisTemplate.opsForValue().set("username","xgt");
        // 获取值
        String username = (String)redisTemplate.opsForValue().get("username");
        return username;
    }

事务和锁

事务

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

Redis事务的主要作用就是串联多个命令防止别的命令插队。

Redis事务三特性

  • 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;
  • 没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行;
  • 不保证原子性:事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚;

Multi、Exec、discard

从输入Multi命令开始,输入的命令都会一次进入命令队列中,但不会执行,知道输入Exce后,Redis会将之前的命令队列中的命令一次执行。
组队的过程中可以功过discard来放弃组队。

事务的错误处理

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

事务冲突

解决:

悲观锁

每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样被人想拿这个数据就会blok直到它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等。读锁、写锁等都是在做操作之前先上锁。

乐观锁

每次拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

库存遗留问题:因为判断版本号机制,所以会导致库存遗留,实际操作次数小于可操作次数:例如2000人买票,500张票,却只卖了100张;

Lua

Lua是一个小巧的脚本语言,可以很容易被C/C++代码调用,也可以反过来调用C/C++,Lua并没有提供强大的库,一个完整的Lua解释器不过200K,所以Lua不适合作为开发独立应用程序的语言而是作为嵌入式脚本语言。

将复杂的或多步的Redis操作,写为一个脚本,一次提交给redis执行,较少反复连接redis的次数,提升性能;

Lua脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作;

但是注意redis的lua脚本功能,只有在redis2.6以上的版本才可以使用。利用lua脚本淘汰用户,解决库存遗留问题;实际上是redis利用其单线程的特性,用任务队列的方式解决多任务并发问题;

watch

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

unwatch

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

持久化

Redis提供了2种不同形式的持久化方式

  • RDB(Redis DataBase)
  • AOF(Append Only File)

RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时将快照文件直接读入到内存里;

如何执行?

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效

优点:

  • 适合大规模的数据恢复;
  • 堆数据完整性和一致性要求不高更适合使用
  • 节省磁盘空间
  • 恢复速度快

缺点:

  • RDB的缺点是最后一次持久化后的数据可能丢失;
  • Fork的时候内存中数据被克隆了一份,大致2倍的膨胀性需要考虑;
  • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能;
fork
  • fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量,环境变量,程序计数器等)数值都和原来进程一致,但是会多一个全新的进程,并最为原进程的子进程
  • 在Linux程序中,fork会产生一个和父进程完全相同的子进程,但子进程在此后会被exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”;
  • 一般情况父进程和子进程会共用一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程;

配置

dbfilename dump.rdb #rdb持久化文件名

dir ./  #rdb持久化文件保存路径

stop-writers-on-bgsave-error yes #当redis无法写入硬盘的时候,直接关掉redis的写操作

rdbcompression yes #对于存储到磁盘中的快照进行压缩存储,利用LZF算法进行压缩

rdbchecksum yes #在存储快照后,可以让redis使用CRC64算法来进行数据校验,但是会增加大约10%的性能消耗,如果希望获取到最大的性能提升可以关闭

save 900 1
save 300 10
save 60 10000 #设置符合的快照触发条件,默认是1分钟内改了1W次,5分钟改了10次,15分钟改了1次

save VS bgsave:

  • svae 只管保存,其他不管全部阻塞。手动保存,不建议;
  • bgsave 会在后台异步进行快照操作,快照同时还可以响应客户端请求

可以通过lastsave命令获取最后一次执行快照的时间;

AOF

是什么

以日志 的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只追加文件但不可以改文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

AOF持久化流程

  • 客户端的请求写命令会被append追加到AOF缓冲区;
  • AOF缓冲区根据AOF持久化策略[always、everysec、no]将操作sync同步到磁盘的AOF文件中;
  • AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
  • Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

特点

AOF在redis.conf中配置文件名称,默认为appendonly.aof;
aof文件的保存路径同rdb的路径一致;
AOF默认不开启;

AOF和RDB同时开启时,redis默认取aof的数据(因为数据不会存在丢失);

异常恢复AOF

  • 如果遇到AOF文件损坏,通过redis-check-aof --fix appendonly.aof 进行恢复
  • 备份被写坏的AOF文件
  • 重启redis,然后重新加载

Rewrite压缩

是什么

AOF采用文件追加方式,文件会越来越大,为避免出现此情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof

原理

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指吧rdb的快照,以二进制的形式附在新的aof都不,作为已有的历史数据,替换掉原来的流水账操作;

流程
  • bgrewriteaof触发重写,判断当前是否有bgsave或bgrewriteaof在运行,如果有,则等待结束后再继续执行;
  • 主进程fork出子进程执行重写操作,保证主进程不会阻塞;
  • 子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原aof文件完整以及新aof文件生成器件的新的数据修改动作不会丢失;
  • 子进程写完新的aof文件后向主进程发信号,父进程更新统计信息,主进程吧aof_rewrite_buf中的数据写入到新的aof文件
  • 使用新的aof文件覆盖旧的aof文件,完成aof重写

优缺点

优点:

  • 备份机制更文件,丢失数据概率更低
  • 可读的日志文本,通过操作AOF文件,可以处理误操作

缺点:

  • 比起RDB占用更多的磁盘空间
  • 恢复备份速度要慢
  • 每次读写都同步的话,有一定的性能压力
  • 存在个别BUG,造成恢复不成功

主从复制

是什么

主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

能干嘛

  • 读写分离,性能扩展
  • 容灾快速恢复

搭建一主多从

  • 创建配置文件夹,将配置文件复制过来
[root@ecs-340806 myredis]# cp /etc/redis.conf  /myredis/redis.conf
  • 创建三个配置文件,分别命名
vi redis6379.conf
vi redis6389.conf
vi redis6399.conf
  • 在配置文件中写入响应配置内容
include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
include /myredis/redis.conf
pidfile /var/run/redis_6389.pid
port 6389
dbfilename dump6389.rdb
include /myredis/redis.conf
pidfile /var/run/redis_6399.pid
port 6399
dbfilename dump6399.rdb
  • 启动redis
[root@ecs-340806 myredis]# redis-server /myredis/redis6379.conf 
[root@ecs-340806 myredis]# redis-server /myredis/redis6389.conf 
[root@ecs-340806 myredis]# redis-server /myredis/redis6399.conf 
[root@ecs-340806 myredis]# ps -ef | grep redis
root     22784     1  0 Aug16 ?        00:48:21 redis-server *:6379
root     26052     1  0 10:19 ?        00:00:00 redis-server *:6389
root     26059     1  0 10:19 ?        00:00:00 redis-server *:6399
root     26067 25640  0 10:19 pts/0    00:00:00 grep --color=auto redis
  • info replication
    查看是主还是从

  • 配从(库)不配主(库)
    使用slaveof 成为某个实例的从服务器

注意:

  • masterauth作用:主要是针对master对应的slave节点设置的,在slave节点数据同步的时候用到。
  • requirepass作用:对登录权限做限制,redis每个节点的requirepass可以是独立、不同的。

所以需要在从节点配置连接主库的密码

masterauth your_password

在从机上执行slaveof 主机IP PORT

127.0.0.1:6389> slaveof 127.0.0.1 6379
OK
127.0.0.1:6389> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:468b2ffdac4d4437d6f8f65e69aebac02534aa95
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14

查看主机情况

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6389,state=online,offset=224,lag=0
slave1:ip=127.0.0.1,port=6399,state=online,offset=224,lag=0
master_failover_state:no-failover
master_replid:468b2ffdac4d4437d6f8f65e69aebac02534aa95
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:224
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:224
  • 查看同步操作
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6389> keys *
1) "k1"
  • 从库无法进行写操作
127.0.0.1:6389> set k2 v2
(error) READONLY You can't write against a read only replica.

注意

  • 从服务器重启后,会变成主服务器,需要再次执行slaveof将之变为从服务器;

  • 当从服务器挂掉重启后,数据会从头开始复制同步;

  • 主服务器挂掉后,从服务器默认还是从服务器;

主从复制原理

  • 从服务器连接上主服务器之后,从服务器向主服务器进行数据同步的消息sync命令
  • 主服务器接到从服务器发送过来的同步消息,把主服务器数据进行持久化,rdb文件,把rdb文件发送从服务器,从服务器拿到rdb进行读取,存盘并加载到内存中;
  • 每次主服务器进行写操作后,将新的所有接收到的修改命令以此传给从服务器进行数据同步;
  • 只要是重新连接,以此完全同步(全量复制)将被执行;

薪火相传

上一个从节点可以是下一个从节点的主服务器,从服务器同样可以接收其他从节点的连接和同步请求,难么该从节点作为了链条中下一个的主节点,可以有效减轻主服务器的写压力,去中心化降低风险;

用slaveof ,
中途变更转向:会清除之前的数据,重新建立拷贝最新的;

风险:一旦某个slave宕机,后面的slave都没法备份;主机挂了,从机还是机,无法写数据了;

从库升级为主库

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改;
用slaveof no one 将从机变为主机

哨兵模式

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票自动将从库转换为主库;

  • 自定义的目录下新建sentinel.conf文件
    sentinel monitor mymaster 127.0.0.1 6379 1
    # 连接密码
    sentinel auth-pass mymaster 123456
    
    mymaster为监控对象起的服务器名称;
    1为至少有多少个哨兵同意迁移的数量;
  • 启动哨兵
    redis-sentinel /myredis/sentinel.conf
    
过程
  • 从下线的主服务器的所有从服务里面挑选一个从服务,将其转成主服务,选择条件依次为:
    1.选择优先级靠前的,默认在redis.conf中 slave-priority 100或者是replica-priority 100,值越小优先级越高
    2.选择偏移量(获得原主机数据最全的)最大的
    3.选择runid最小的(每个redis实例启动后都会随机生成一个40位的runid);
  • 挑选出新的主服务之后,sentinel向原主服务的从服务发送slaveof新主服务的命令,复制新master
  • 当已下线的服务重新上线时,sentinel会向其发送slaveof命令,让其成为新主的从

问题:
1.复制延时:由于所有的写操作都是现在Master上操作,然后同步更新到slave上,所以从Master同步到slave及其上有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave及其数量的增加也会使这个问题更加严重;

Java连接
private static JedisSentinelPool jedisSentinelPool = null;

    public static Jedis getJedisFromSentinel(){
        if (null == jedisSentinelPool){
            synchronized (JedisFromSentinel.class){
                if (null == jedisSentinelPool){
                    HashSet<String> sentinelSet = new HashSet<>();
                    sentinelSet.add("192.168.11.101:26379");
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    // 最大连接数
                    poolConfig.setMaxTotal(20);
                    poolConfig.setMaxIdle(5);
                    poolConfig.setMinIdle(2);
                    poolConfig.setMaxWaitMillis(180*1000);
                    // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
                    poolConfig.setBlockWhenExhausted(true);
                    // 在获取连接的时候检查有效性, 默认false
                    poolConfig.setTestOnBorrow(true); // ping pong
                    jedisSentinelPool = new JedisSentinelPool("mymaster",sentinelSet,poolConfig);

                }
            }
        }
        return jedisSentinelPool.getResource();
    }

集群

问题

容量不够,redis如何进行扩容?
并发写操作,redis如何分摊?
主从模式,薪火相传,主机宕机导致IP地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。
之前是通过代理主机来解决,但是redis3.0中提供了解决方案,就是去中心化集群;

代理主机:

是什么

Redis集群实现了堆Redis的水平的扩容,即启动N个节点,将整个数据库分不存在这N个节点中,每个节点存储总数据的1/N;

Redis集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求;

集群搭建

  • 修改redis.conf配置

cluster-enable yes:打开集群模式
cluster-config-file nodes-6379.conf:设置节点配置文件名
cluster-node-timeout 15000:设置节点失联时间,超过该时间(毫秒),自动进行主从切换

include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
# 密码设置
masterauth xxx 
requirepass xxx

复制6380 6381 6389 6390 6391五份配置文件

[root@ecs-340806 myredis]# ll
total 120
-rw-r--r-- 1 root root   176 Aug 29 17:38 redis6379.conf
-rw-r--r-- 1 root root   176 Aug 30 09:55 redis6380.conf
-rw-r--r-- 1 root root   176 Aug 30 09:55 redis6381.conf
-rw-r--r-- 1 root root   176 Aug 30 09:55 redis6389.conf
-rw-r--r-- 1 root root   176 Aug 30 09:55 redis6390.conf
-rw-r--r-- 1 root root   176 Aug 30 09:55 redis6391.conf
-rw-r--r-- 1 root root 92226 Aug 29 10:05 redis.conf
-rw-r--r-- 1 root root   441 Aug 29 15:12 sentinel.conf

修改里面端口6379改为响应的端口

:%s/xxx/zzz #将xxx改为zzz
  • 启动6台redis服务,节点文件生成正常
[root@ecs-340806 myredis]# redis-server /myredis/redis6379.conf 
[root@ecs-340806 myredis]# redis-server /myredis/redis6380.conf 
[root@ecs-340806 myredis]# redis-server /myredis/redis6381.conf 
[root@ecs-340806 myredis]# redis-server /myredis/redis6389.conf 
[root@ecs-340806 myredis]# redis-server /myredis/redis6390.conf 
[root@ecs-340806 myredis]# redis-server /myredis/redis6391.conf 
[root@ecs-340806 myredis]# ps -ef | grep redis
root     14558     1  0 10:07 ?        00:00:00 redis-server *:6379 [cluster]
root     14565     1  0 10:07 ?        00:00:00 redis-server *:6380 [cluster]
root     14571     1  0 10:07 ?        00:00:00 redis-server *:6381 [cluster]
root     14578     1  0 10:08 ?        00:00:00 redis-server *:6389 [cluster]
root     14584     1  0 10:08 ?        00:00:00 redis-server *:6390 [cluster]
root     14591     1  0 10:08 ?        00:00:00 redis-server *:6391 [cluster]
root     14602 14389  0 10:08 pts/1    00:00:00 grep --color=auto redis
[root@ecs-340806 myredis]# ll
total 144
-rw-r--r-- 1 root root   114 Aug 30 10:07 nodes-6379.conf
-rw-r--r-- 1 root root   114 Aug 30 10:07 nodes-6380.conf
-rw-r--r-- 1 root root   114 Aug 30 10:07 nodes-6381.conf
-rw-r--r-- 1 root root   114 Aug 30 10:08 nodes-6389.conf
-rw-r--r-- 1 root root   114 Aug 30 10:08 nodes-6390.conf
-rw-r--r-- 1 root root   114 Aug 30 10:08 nodes-6391.conf
-rw-r--r-- 1 root root   177 Aug 30 10:05 redis6379.conf
-rw-r--r-- 1 root root   177 Aug 30 10:05 redis6380.conf
-rw-r--r-- 1 root root   177 Aug 30 10:05 redis6381.conf
-rw-r--r-- 1 root root   177 Aug 30 10:05 redis6389.conf
-rw-r--r-- 1 root root   177 Aug 30 10:06 redis6390.conf
-rw-r--r-- 1 root root   177 Aug 30 10:06 redis6391.conf
-rw-r--r-- 1 root root 92226 Aug 29 10:05 redis.conf
-rw-r--r-- 1 root root   441 Aug 29 15:12 sentinel.conf
  • 进入安装目录的src下
[root@ecs-340806 src]# cd /opt/redis-6.2.1/src/
  • 执行命令,进行集群操作
redis-cli -a XXX --cluster create --cluster-replicas 1 xxx:6379 xxx:6380 xxx:6381 xxx:6389 xxx:6390 xxx:6391

注意:IP要是真实IP地址

Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica xxx:6390 to xxx:6379
Adding replica xxx to xxx:6380
Adding replica xxx:6389 to xxx:6381
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 20745474fcd68fe0d03c2f9346bab8ba57ec826b xxx:6379
   slots:[0-5460] (5461 slots) master
M: 58960931ecc6cc1e75da75b85f8a1558dfa1f1ec xxx:6380
   slots:[5461-10922] (5462 slots) master
M: cf40266ca054ef88b9749bfd6481a5cd053bb256 xxx:6381
   slots:[10923-16383] (5461 slots) master
S: 4cece0c7dee36c4e77eb4ac0d0ad1fb24f6b6718 xxx:6389
   replicates 20745474fcd68fe0d03c2f9346bab8ba57ec826b
S: acaa7e3929796f53f8907fa31e84990184dfcb82 xxx:6390
   replicates 58960931ecc6cc1e75da75b85f8a1558dfa1f1ec
S: ae0801e51b24f1cccff7c54deb1d46e00c393f74 xxx:6391
   replicates cf40266ca054ef88b9749bfd6481a5cd053bb256
Can I set the above configuration? (type 'yes' to accept): 

yes 接收

[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
  • 测试集群连接
redis-cli -c -p 6379 -a XXX

执行cluster nodes查看节点信息;

如何分配

一个集群至少要有三个主节点;
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点;

分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不再一个IP地址上;

slots

代表插槽;
一个Redis集群包含16384个插槽,数据库中的每个健都属于这16384个插槽的其中一个;

集群使用公式(CRC16)% 16384 来计算健key属于哪个插槽,其中CRC16(key)语句 用于计算健key的CRC16校验;
集群中的每个节点负责处理一部分插槽。

注意:
在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis的实例地址和端口;
Redis-cli客户端提供了 -c 参数实现自动重定向;

不再一个slot下的键值,是不能使用megt,mset等多建操作的,可以通过 {} 来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去;

相关操作

  • cluster keyslot :获取key的插槽值
  • cluster countkeysinslot N:插槽值N里面有几个key
  • cluster getkeysinslot N M:返回插槽中m个插槽中的健

故障恢复

如果主节点下线,从节点能自动升为主节点,15秒超时;
主节点恢复后,回来变成从机;

  • 如果所有某一段插槽的从节点都宕掉,redis服务是否还能继续?

如果某一段插槽的主从都挂掉,而 cluster-require-full-coverage 为 yes ,那么整个集群都挂掉;
如果 cluster-require-full-coverage 为no,那么该插槽数据全都不能使用,也无法存储;

集群的Jedis操作

public static void main(String[] args) {
        // 创建对象,也可以将HostAndPort放入一个Set集合
        Set<HostAndPort> HostAndPortSet = new HashSet();
        HostAndPortSet.add(new HostAndPort("ip",6379));

        // pool配置类
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接数
        jedisPoolConfig.setMaxTotal(200);
        jedisPoolConfig.setMaxIdle(32);
        jedisPoolConfig.setMaxWaitMillis(180*1000);
        // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
        jedisPoolConfig.setBlockWhenExhausted(true);
        // 在获取连接的时候检查有效性, 默认false
        jedisPoolConfig.setTestOnBorrow(true); // ping pong

        JedisCluster jedisCluster = new JedisCluster(HostAndPortSet,3000,3000,5,"password",jedisPoolConfig);
        // 操作
        String s = jedisCluster.set("k1", "wocao");
        System.out.println(s);
        String k1 = jedisCluster.get("k1");
        System.out.println(k1);
        jedisCluster.close();
    }

集群的不足

  • 多键操作是不被支持的
  • 多键的Redis事务是不被支持的
  • Lua脚本不被支持
  • 由于集群方案出现较晚,代理或者客户端分片的方案想要迁移至redis Cluster,需要整体迁移而不是逐步过渡,复杂度较大;

缓存穿透

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。
出现原因:
1.redis大量key查询不到,去数据库里面查;
2.出现很多非正常URL访问;

解决方案:
一个一定不存在循环及查询不到的数据,犹豫缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查询不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义;

  • 对空值缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果null进行缓存,设置空结果的过期时间会很短,最长不超过五分钟;

  • 设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单i作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问i不再bitmaps里面,进行拦截,不允许访问;

  • 采用布隆过滤器:实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数),可以用于检索一个元素是否在一个集合中,优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难;

  • 进行实时监控:当发现redis的命中率开始极速降低,需要排查访问对象和访问的数据库。和运维人员配合,可以设置黑名单限制服务;

缓存击穿

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮;

出现原因:
1.某个key过期了,大量访问使用这个key,数据库访问压力瞬时增加

解决方案:

  • 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长;

  • 实时调整:现场监控哪些数据热门,实时调整key过期时长;

  • 使用锁:
    1.在缓存失效的时候(判断拿出来的值为空),不是立即去load db;
    2.先试用缓存工具的某些带成功操作返回值的操作(比如redis的setnx)
    3.当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
    4.当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法;

缓存雪崩

在极少时间段内,查询大量key的集中过期情况;
缓存失效时得雪崩效应对底层系统的冲击非常可怕;

解决方案:

  • 构建多级缓存架构:nginx缓存+redis缓存+其他缓存

  • 使用锁或队列:使用锁或队列的方式来保证不会有大量的线程堆数据库一次性进行 读写,从而避免失效时大量的并发请求落到底层存储系统上。不适合高并发情况;

  • 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存;

  • 将缓存失效时间分散开:可以在原有的失效时间上增加一个随机值,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件;

分布式锁

主流实现方案:

  • 基于数据库实现分布式锁;
  • 基于缓存(redis)实现;
  • 基于Zookeeper实现;

每一种分布式锁解决方案都有各自的优缺点;

实现

命令

set sku:1:info "OK" NX PX 10000

EX second:设置键的过期时间为second秒。SET key value EX second 效果等同于 SETEX key second value;

PX millisecond:设置键的过期时间为millisecond毫秒。SET key value PX millisecond效果等同于PSETEX key millisecond value;

NX:只在健不存在时,才能对键进行设置操作。SET key value NX 效果等同于 SETNX key value

问题

1.UUID防误删锁
在释放锁的时候,首先判断当前uuid和要释放锁uuid是否一样;

2.删除操作缺乏原子性
在线程a查询判断的时候是相同的,但是删除执行前,刚好过期被自动释放,下一个线程b生成了UUID上了锁,a删除了b线程上的锁;

使用Lua脚本实现操作原子性;

锁的条件

  • 互斥性:在任意时刻,只有一个客户端能持有锁;
  • 不会发生死锁:即使有一个客户端在持有锁的器件崩溃而没有主动解锁,也能保证后续其他客户端能加锁
  • 同一客户端操作:加锁和解锁必须由同一个客户端,不能把别人加的锁解了;
  • 原子性:加锁和解锁必须具有原子性;

权限控制

ACL

redis ACL是Access control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的健来限制某些连接;
在redis5版本之前,redis安全规则只有密码控制,还有通过rename来调整高危命令;redis6则提供ACL的功能对用户进行更细粒度的控制权限;

  • 接入权限:用户名和密码
  • 可以执行的命令
  • 可以操作的key

查看用户权限

"user default on #154b3c2ad82bda247a0abfe0bda4196eef931fb3dc4aa2ebec01f8e8ca324b81 ~* &* +@all"

default:用户名
on/off:是否启用
nopass/154b3c2ad82bda247a0abfe0bda4196eef931fb3dc4aa2ebec01f8e8ca324b81:有没有密码
~*:可操作的key
+@all:可执行的命令

查看具体操作有哪些

127.0.0.1:6379> acl cat
 1) "keyspace"
 2) "read"
 3) "write"
 4) "set"
 5) "sortedset"
 6) "list"
 7) "hash"
 8) "string"
 9) "bitmap"
10) "hyperloglog"
11) "geo"
12) "stream"
13) "pubsub"
14) "admin"
15) "fast"
16) "slow"
17) "blocking"
18) "dangerous"
19) "connection"
20) "transaction"
21) "scripting"
127.0.0.1:6379> acl cat string
 1) "stralgo"
 2) "setex"
 3) "strlen"
 4) "mget"
 5) "getset"
 6) "mset"
 7) "getrange"
 8) "incrby"
 9) "substr"
10) "incrbyfloat"
11) "incr"
12) "setnx"
13) "get"
14) "decr"
15) "getdel"
16) "decrby"
17) "set"
18) "psetex"
19) "append"
20) "setrange"
21) "getex"
22) "msetnx"

查看当前用户

127.0.0.1:6379> acl whoami
"default"

创建和编辑用户ACL

  • 启动和禁用用户:on/off
    注意:已验证的连接仍然可以工作,如果默认用户被标记off,则新连接将在未进行身份验证的情况下启动,并要求用户使用auth选项发送auth或hello,以便以某种方式进行身份验证;
  • +:将指令添加到用户可以调用的指令列表中
  • -:从用户可执行指令列表移除指令
  • +@:添加该类别用户要调用的所有指令,有效类别为@admin、@set、@sortedset…等,通过调用ACL CAT命令查看完整列表。特殊类别@all表示所有命令,包括当前存在于服务器中的命令,以及将来将通过模块加载的命令
  • -@:从用户可调用指令中移除类别
  • allconmands:+@all的别名
  • noconmand:-@all的别名
  • <pattem>:添加可作为用户可操作的健的模式,例如*允许所有的健

例子:

创建一个带密码的用户

用户名flh,密码123456,只能操作xgt:打头的健,只能执行get操作

127.0.0.1:6379> acl setuser flh on >123456 ~xgt:* +get
OK

切换用户

auth flh 123456
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值