redis启动
访问redis客户端
redis的关闭
进入客户端后使用命令shutdown
找到redis-server的线程号,直接杀死该线程
相关知识
redis相关命令
键相关命令
命令 | 作用 |
---|---|
**keys *** | 查看当前数据库的所有键 |
set key value | 添加键值 |
exists key | 判断某个key是否存在 |
type key | 查看key的类型 |
del key | 删除指定的key数据 |
unlink key | 根据value选择非阻塞删除 |
expire key seconds | 给指定的Key设置过期时间 |
ttl key | 查看当前key还有多少秒过期,-1表示永不过期,-2表示已过期 |
select nums | 切换数据库,redis默认有16个数据库,默认使用0号数据库 |
dbsize | 产看当前数据库key的数量 |
flushdb | 清空当前库 |
flushall | 清空所有的库 |
typedef struct redisObject {
unsigned type:4; //对象的数据类型
unsigned encoding:4; //对象的编码格式
unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr; //指向真正的数据
} robj;
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;//总是2的指数
unsigned long sizemask;//size-1 用于将key的hash映射到对应的bucket
unsigned long used;//已有的数据个数
} dictht;
//字典数据结构,包含两个hashtable
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
quicklist是一个双向链表,而且是一个基于ziplist的双向链表,quicklist的每个节点都是一个ziplist
ziplist长度的确定
参数值大于0表示ziplist中的元素个数
参数值小于0表示ziplist的内存大小
通过如下的参数确定
list-max-ziplist-size -2
跳表是一种可以进行二分查找的有序链表,采用空间换时间的设计思路,跳表在原有的有序链表上面增加了多级索引(例如每两个节点就提取一个节点到上一级),通过索引来实现快速查找。跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都为O(logn),空间复杂度为 O(n)。跳表非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
跳跃表的每个节点都生成随机的层数,插入操作只用修改系欸但前后的指针,不需要对多个节点都进行调整
skiplist与hashtable,AVL的比较
Redis中的跳跃表
typedef struct zskiplistNode {
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist
初始化跳跃表
命令 | 作用 |
---|---|
set key value | 添加键值对 |
get key | 查询对应键的值 |
append key value | 将给定的value追加到原value的末尾 |
strlen key | 获取当前键的值的长度 |
setnx key value | 当key不存在时,设置key的值 |
incr key | 将键值增加1,只能对数字值进行操作,若当前key不存在则将Key的值设为1 |
decr key | 将键值减少1,只能对数字值进行操作,若当前key不存在则将Key的值设为-1 |
incrby / decrby key step | 与incr和decr一致,可以自定义步长 step |
mset key1 value1 key2 value2 … | 同时设置一个或者多个键值对 |
mget key1 key2… | 同时获取多个value |
msetnx key1 value1 key2 value2… | 同时设置一个或者多个键值对,当且仅当所有的Key都不存在时才会设置 |
getrange key start end | 获取值的范围,与java中的substring类似,前后都是闭包 |
setrange key start value | 用value的值覆盖从start开始的值,有几位覆盖几位 |
setex key seconds value | 设置键值的同时设置过期时间 |
getset key value | 得到旧的值,同时用value替换原有的值 |
type = OBJ_STRING
命令 | 作用 |
---|---|
lpush/rpush key1 value1 key2 value2 | 从左边/右边插入一个或者多个值 |
lpop/rpop key | 从左边或者右边弹出一个值,值在键在,值光键亡 |
lrange key start stop | 按照索引下标获得元素,从左到右,0表示左边第一个,-1表示右边第一个 |
lindex key index | 根据index的值获取元素(从左到右) |
llen key | 获取链表的长度 |
linsert key before/after value newvalue | 在value的前面或者后面插入newvalue |
lrem key n value | 从左边删除n个指定的value |
lset key index value | 将index处的值替换为value |
压缩链表 ziplist ,列表的元素个数小于512个,列表的每个元素的值都小于64字节,redis使用压缩列表作为List类型的底层数据结构
快速链表 quicklist(宏观上是双向链表)
命令 | 作用 |
---|---|
SADD key member | 向set中添加一个或多个元素 |
SISMEMBER key member | 判断一个元素是否存在于set中 |
SREM key member | 移除set中的指定元素 |
SCARD key | 返回set中元素的个数 |
SMEMBERS | 获取set中的所有元素 |
SINTER key1 key2 | 求key1与key2的交集 |
SDIFF key1 key2 | 求key1与key2的差集 |
SUNION key1 key2 | 求key1和key2的并集 |
命令 | 作用 |
---|---|
HSET key field value | 添加或者修改hash类型key的filed的值 |
HGET key field | 获取一个hash类型的key的field的值 |
HMSET key field value [field value …] | 设置一个hash类型的key的多个field的值 |
HMGET key field [field …] | 获取一个hash类型的key的多个field的值 |
HGETALL key | 获取一个hash类型的key的所有field和value |
HKEYS key | 获取一个hash类型的key的所有field |
HVALS key | 获取一个hash类型的key的所有value |
HSETNX key field value | 设置filed-value,不存在就设置,存在则无效 |
命令 | 作用 |
---|---|
ZADD key score member | 添加一个或多个元素到sorted set ,如果已经存在则更新其score值 |
ZREM key member | 删除sorted set中的一个指定元素 |
ZSCORE key member | 获取sorted set中的指定元素的score值 |
ZRANK key member | 获取sorted set 中的指定元素的排名 |
ZCARD key | 获取sorted set中的元素个数 |
ZCOUNT key min max | 统计score值在给定范围内的所有元素的个数 |
ZINCRBY key increment member | 让sorted set中的指定元素自增,步长为指定的increment值 |
ZRANGE key min max | 按照score排序后,获取指定排名范围内的元素 |
ZRANGEBYSCORE key min max | 按照score排序后,获取指定score范围内的元素 |
ZDIFF、ZINTER、ZUNION | 求差集、交集、并集 |
常用命令 | 作用 |
---|---|
SETBIT key offset value | 向指定位置存入0或者1 |
GETBIT key offset | 获取指定位置的bit |
BITCOUNT key [start end] | 获取指定范围内1的个数 |
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] | 操作指定位置的bit type指定符号数(u为无符号,i为有符号) |
BITPOS key bit [start] [end] | 获取指定范围内第一个出现的1 |
UV**:全称U**nique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
PV**:全称P**age View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。
HyperLogLog通常用于基数统计。使用少量固定大小的内存,来统计集合中唯一元素的数量。统计结果不是精确值,而是一个带有0.81%标准差(standard error)的近似值。所以,HyperLogLog适用于一些对于统计结果精确度要求不是特别高的场景,例如网站的UV统计。
常用命令 | 作用 |
---|---|
PFADD key element… | 添加数据到HyperLogLog |
PFCOUNT key | 统计HyperLogLog中的个数,有一定的误差 |
PFMERGE destkey sourcekey… | 将多个HyperLogLog合并为一个 |
常用命令 | 作用 |
---|---|
GEOADD key longitude latitude member | 添加一个或者多个位置的经纬度信息到某个集合 |
GEODIST key member1 member2 [m|km] | 返回集合中两个位置的距离 |
GEOHASH key member … | 返回某个位置的hash |
GEOPOS key member … | 返回某个位置的经纬度信息 |
GEOSERACH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km] [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH] | 在指定范围内搜索member,并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形 |
基于Stream可以实现一个相对完善的消息队列
常用命令
消费者组:将多个消费者放到一个组里,监控同一个队列
消费者组命令 | 作用 |
---|---|
XGROUP CREATE key groupname ID|$ [MKSTREAM] | 创建消费者组 |
ID指定队列中消息位置,0表示第一个,$表示最后一个 | |
XGROUP DESTORY key groupName | 删除指定的消费者组 |
XGROUP CREATECONSUMER key groupname consumername | 给指定的消费者组添加消费者 |
XGROUP DELCONSUMER key groupname consumername | 删除消费者组中的指定消费者 |
读取队列中的消息
group:消费组名称
consumer:消费者名称,如果消费者不存在,会自动创建一个消费者
count:本次查询的最大数量
BLOCK milliseconds:当没有消息时最长等待时间
NOACK:无需手动ACK,获取到消息后自动确认
STREAMS key:指定队列名称
ID:获取消息的起始ID:
“>”:从下一个未消费的消息开始
其它:根据指定id从pending-list中获取已消费但未确认的消息,例如0,是从pending-list中的第一个消息开始
确认已经处理过的消息
package com.lzx;
import redis.clients.jedis.Jedis;
public class JedisTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.248.131", 6379);
String result = jedis.ping();
System.out.println(result);
jedis.close();
}
}
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static{
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(8);
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMinIdle(0);
jedisPool = new JedisPool(jedisPoolConfig,"192.168.248.131",6379,1000);
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
使用步骤
引入相关的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
在application.yml中配置Redis信息
spring:
redis:
host: 192.168.248.131
port: 6379
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000
注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString(){
redisTemplate.opsForValue().set("name","jack");
Object name = redisTemplate.opsForValue().get("name");
System.out.println("name"+name);
}
RedisTemplate序列化方式
RedisTemplate可以接受任意的Object作为值写入Redis,但是在写入之前会把Object序列化为字节形式,默认采用JDK序列化
自定义RedisTemplate的序列化方式
定义配置类,更改RedisTemplate的值和键的序列化方式
@Configuration
public class RedisConfigure {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
return template;
}
}
在存放对象时,为了能够得到反序列化的对象,jackson序列化器会额外存放对象的class类信息,带来额外的内存开销
get user:1001
{"@class":"com.lzx.domain.User","name":"张飞","age":34}
为了节省内存,可以使用StringRedisTemplate对象,只能存放String类型的键和值,StringRedisTemplate的key和value默认使用StringRedisSerializer序列化器,当我们需要存放对象时,自己手动进行序列化,取出对象时,手动进行反序列化。
注入StringRedisTemplate类,手动实现序列化和反序列化
package com.lzx;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lzx.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
@SpringBootTest
public class StringRedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//序列化类
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testStringRedisTemplate() throws JsonProcessingException {
User user = new User("张三", 18);
String jsonString = mapper.writeValueAsString(user);
stringRedisTemplate.opsForValue().set("user:1002",jsonString);
String s = stringRedisTemplate.opsForValue().get("user:1002");
User res = mapper.readValue(s, User.class);
System.out.println(res);
}
}
内存淘汰:低一致性需求
超时剔除:
主动更新:
实践方案
请求的数据在缓存和数据库中都不存在
解决方法:
接口校验
缓存空对象
布隆过滤
大量数据同时过期
Redis服务宕机
热点key问题,一个热点key(被高并发访问的且缓存重建业务较复杂)忽然失效
互斥锁
逻辑过期
在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时直接将快照文件读到内存里
RDB持久化在四种情况下会执行:
执行原理:bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
fork采用的是copy-on-write技术:
执行过程:
持久化文件的相关信息
在redis.conf中可以进行配置,默认为dump.rdb
文件的默认保存路径是当前路径 ./
# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
快照的策略
rdb文件的恢复
优势
劣势
以日志的形式来记录每个写操作,将redis执行过的所有的写指令记录下来,只允许追加文件但是不可以改写文件,redis重启时会将日志文件中记录的写指令重新执行一遍以完成数据的恢复工作。
持久化流程
写命令被追加到AOF缓冲区内
AOF缓冲区根据AOF缓冲策略【always,everysec,no】将操作同步到磁盘的AOF文件中
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
AOF文件大小超过重写策略或者手动重写时,会通过bgrewirteaof命令对AOF文件重写,来压缩AOF文件容量
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
redis服务重启时,会重新加载AOF文件中的写指令来达到数据恢复的目的
aof文件的相关信息
可以在redis.conf配置文件中修改文件名称,默认名称是appendonly.aof
aof文件的保存路径与rdb文件的保存路径一致,都是当前路径
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
AOF的启动与异常恢复
AOF的同步频率
重写压缩(rewrite)
no-appendfsync-on-rewrite
重写流程
发生在AOF重写时,重写后的文件前面部分是rdb数据,后半部分是AOF新增命令
配置文件中的相关解释,默认为开启状态
# When rewriting the AOF file, Redis is able to use an RDB preamble in the
# AOF file for faster rewrites and recoveries. When this option is turned
# on the rewritten AOF file is composed of two different stanzas:
# [RDB file][AOF tail]
# When loading, Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, then continues loading the AOF
# tail.
aof-use-rdb-preamble yes
当redis的内存空间(maxmemory参数配置)已满时,redis将根据配置的淘汰策略(maxmemory-policy参数配置)进行内存淘汰,主要有以下8种
LRU算法
LFU算法
主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点
两个概念
主要步骤
增量同步: 只更新slave与master存在差异的部分数据
repl_backlog原理
repl_baklog文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset,直到数组被填满,此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset,如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖,这时只能做全量同步
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令
主观下线
客观下线
选出领头sentinel,通常是最先发现master下线的sentinel
领头sentinel在下线的Master的slave中选取一个作为新的master,判断依据如下**(亲信优先)**:
sentinel给选中的slave节点发送slaveof no one命令**(独立宣言)**,让该节点成为master。
sentinel通知其他剩余的slave节点,从新的master上开始复制 (俯首称臣)。
sentinel将故障节点标记为slave,当故障节点恢复后自动成为新的master的slave节点。
[外链图片转存中…(img-sAcuv05H-1658536819498)]