Redis
Redis学习笔记
1、NoSQL数据库
NoSQL数据库:非关系型数据库,
特点:
-
方便拓展(数据之间不存在关系)
-
数据量大,高性能(读写操作性能强)
-
数据类型多样(不需要事先设计数据库)
2、NoSQL数据库的四大分类
KV键值存储数据库
- redis...
列存储数据库
- HBase
文档型数据量
- MongoDB...
图形数据量
- Neo4J、InfoGrid
3、Redis概述
Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
4、Redis安装
Windows下安装
- 1、下载地址:https://github.com/tporadowski/redis/releases
- 2、启动服务:通过redis安装目录下的redis-server.exe启动
- 3、Redis命令行程序:redis-cli.exe
Linux下安装
5、Redis基础知识
redis有十六个数据库。默认使用的是第0个。
常用命令
- select index:切换数据库,index-第几个数据库
- dbsize:查看当前数据库使用的大小
- **keys **:查看当前数据库的所有key
- flushdb:清空当前数据库
- flushall:清空所有数据库
Redis是单线程的
为什么Redis是单线程还这么快?
Redis是将所有数据存在内存中的,所以使用单线程,除去多线程的cpu切换上下文,效率才是最高的。
6、Redis-Key操作
- exists key名称:存在返回1,不存在返回0
- move key名称 1 : 移除指定的键值对
- expire key名称 秒数 : 设置指定key多久后过期
- ttl key名称 :查看该key还有多久过期,返回-2则已经过期
- type key名称 :查看该key的类型
7、五大数据类型
String(字符串)
-
set key value 设置值
-
get key 获取值
-
exists key 判断当前值是否存在,存在为返回1,不存在返回0
-
append key"字符串" 给当前key的值追加字符串,如果key不存在,则新建
-
strlen key 获取当前key值的长度
-
incr key 当前key的值自增1(针对于整型)
-
decr key 当前key的值自减1(针对于整型)
-
incrby key20 当前key的值自增20(针对于整型)
-
decrby key10 当前key的值自减10(针对于整型)
-
getrange key 0 3 获取当前key的值的第0位到第3位的字符串(截取字符串)
-
getrange key 0 -1 获取当前key的值的全部字符串
-
**setrange key1 ***** 替换指定位置的字符串
-
setex key1 10 "hello222" 设置key1的值10秒后过期
-
ttl key1 查询key1多少秒过期,返回-2则已经过期
-
setnx key value 如果key不存在则创建,如果存在则创建失败
-
mset k1 v1 k2 v2 k3 v3 批量设置值
-
mget k1 k2 k3 批量获取
-
msetnx k2 v2 k4 v4 不存在才创建,一个创建失败所有失败
-
set user:1001:name zhangsan 设置对象(key的设计,对象名:编号:属性名)
-
getset key value 先获取值返回,再设置新值
List
- 所有的list命令都是用l开头,先插入的在右侧
- lpush city chongqing,beijing:向list集合左侧添加一个或者多个值
- lrange city 0 -1:遍历集合(倒序)
- rpush city hunan:向city集合右侧侧添加一个或者多个值
- lpop city:移除city集合左侧第一个值
- rpop city:移除city集合右侧第一个值
- lindex city 0:获取city集合中指定位置角标的元素
- llen city:获取city集合的长度
- lrem city 3 chongqing:移除city集合中的指定个数的value
- ltrim city 0 1:截取city集合中的指定范围的值
- rpoplpush city city2:将city集合中的最右侧的值移动到city2集合的最左侧
- lset city 0 aaa:替换city集合中指定下标的值为新值,该下标位置不存在值,则报错
- linsert city before aaa aaabefore:在集合指定元素前,插入指定元素
- linsert city after aaa aaaafter:在集合元素指定元素后,插入指定元素
- 总结
- redis的List集合本质是一个链表。
- 操作两侧元素效率高,操作中间元素效率低。
set
- set集合中所有命令s开头,存入的值不重复
- sadd myset 111 222:向myset集合中添加一个或多个值,逗号隔开
- smembers myset:取出myset集合中的所有值
- sismember myset 111:判断该值在myset集合中是否存在,存在返回1,不存在返回0
- scard myset:返回myset集合中的元素个数
- srem myset 444 555:移除myset集合中的一个或多个值,逗号隔开
- srandmember myset 2:随机取出myset集合中的指定个数元素,默认取出一个
- spop myset 2:随机删除myset集合中的指定个数元素,默认删除一个
- smove myset myset2 111:将指定元素从myset集合移动到myset2集合中去
- sdiff key1 key2:返回key1,key2集合的元素差集,key1中存在,但key2中不存在的元素
- sinter key1 key2:返回key1,key2集合的元素交集,相同元素
- sunion key1 key2:返回key1,key2集合的元素并集,所有元素,去重。
hash(哈希)
-
hash仍然是key-value的形式存储元素,但value变为存放map集合
-
hset person name zhangsan:设置值
-
hget person name:取出person中,name为key的value
-
hgetall person:取出person中的所有键值对,结果如下:
1) "name" 2) "zhangsan" 3) "age" 4) "18" 5) "address" 6) "chongqing" -
hmset person name zhangshan age 18 address beijing:设置多个键值对的值
-
hmget person name age address:取出多个键的值
-
hdel person age:删除指定key的键值对
-
hlen person:获取person中有多少个键值对
-
hexists person name:判断person中指定key是否存在,存在返回1,不存在返回0
-
hkeys person:获取person中的所有key
-
hvals person:获取person中的所有value
-
hincrby person age 2:给person中指定key的值增加指定增量
-
hsetnx person sex 1:person中设置的key存在则设置失败,不存在则可以设置
zset
在set的基础上,中间为添加一个值,用于排序
- zadd myzset 2 two 3 three 4 four:添加一个或者多个值,key和值中间为排序位
- zrange myzset 0 -1:遍历所有元素,从小到大排序元素
- ZRANGEBYSCORE myzset -inf +inf:从小到大排序元素
- zrevrange myzset 0 -1:从大到小排序元素
- ZRANGEBYSCORE myzset 2 3 withscores:查询排序字段在2到3之间的元素,并且带上排序位的值
- zrem myzset zero:移除具体元素
- zcard myzset:返回有序集合中的元素个数
- zcount myzset 0 10:获得指定区间的成员数量
8、三大特殊数据类型
GEO
地理位置,通过经纬度确定位置。
-
geoadd:添加一个或多个地理位置坐标
规则:两级无法添加,实际通过程序导入地理位置信息
有效经度范围:-180到180度 有效纬度范围:-85.05112878至85.05112878度超过此范围,redis会返回错误信息。
GEOADD key longitude latitude member [longitude latitude member ...] longitude:地理位置的经度 latitude:地理位置的纬度 member:地理位置名称 -
geopos:根据名称查询一个或多个地理位置信息
GEOPOS key member [member ...] -
geodist:获取两个地理位置的之间的距离
GEODIST key member1 member2 [m|km|ft|mi] 单位 m:米(默认) km:千米 ft:英尺 mi:英里 -
georadius:获取指定经纬度坐标的指定范围内的地理位置集合
GEORADIUS key longitude latitude radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count]longitude latitude标识了地理位置的坐标,radius表示范围距离(多大半径距离),距离单位可以为m|km|ft|mi,还有一些可选参数:- WITHCOORD:传入WITHCOORD参数,则返回结果会带上匹配位置的经纬度。
- WITHDIST:传入WITHDIST参数,则返回结果会带上匹配位置与给定地理位置的距离。
- ASC|DESC:默认结果是未排序的,传入ASC为从近到远排序,传入DESC为从远到近排序。
- WITHHASH:传入WITHHASH参数,则返回结果会带上匹配位置的hash值。
- COUNT count:传入COUNT参数,可以返回指定数量的结果。
-
georadiusbymember:获取指定地理位置的指定范围内的地理位置集合
GEORADIUSBYMEMBER key member radius [m|km|ft|mi] [WITHCOORD] [WITHDIST] [ASC|DESC] [WITHHASH] [COUNT count] -
geohash:返回一个或多个地理位置的hash值
Hyperloglog
用于做基数(不重复元素)统计的算法。所占用的内存很小。
- PFADD key element [element ...]:添加指定元素到 HyperLogLog 集合中
- PFCOUNT key [key ...]:返回给定 HyperLogLog 集合的基数估算值
- PFMERGE destkey sourcekey [sourcekey ...]:将多个 HyperLogLog 合并为一个 HyperLogLog,
- destkey:合并后的新HyperLogLog集合的名称
Bitmap
位图,按顺序储存0和1两种状态。
-
SETBIT key offset value:设置指定下标的状态(0或1)
- 顺序必须按照0开始,每次设置+1的规则
-
GETBIT key offset:查看指定下标的状态值
-
bitcount key [start end]:统计key集合中,状态为1的数量,不指定开始和结束下标,默认统计全部
9、事务
- redis事务的本质:是一组命令的集合!将redis命令按顺序执行
- redis事务的特性:
- 一次性
- 顺序性
- 排他性
redis的事务没有事务隔离的概念,redis单条命令保证原子性,但事务不保证原子性。
redis开启事务后,命令只进行入队操作,只有发起执行事务命令时,才会执行。
-
redis事务的使用:
-
开启事务:multi
-
命令入队:需要执行的redis命令集合
-
执行事务:exec
在开启事务后,可以使用 discard 命令放弃事务。即所有的命令失效,返回到开启事务前。
127.0.0.1:6379> multi #开启事务 OK 127.0.0.1:6379> set k1 v1 #redis命令 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> exec #执行事务 1) OK # 执行结果 2) OK 3) OK127.0.0.1:6379> multi #开启事务 OK 127.0.0.1:6379> set q1 v1 QUEUED 127.0.0.1:6379> set q2 v2 QUEUED 127.0.0.1:6379> discard #放弃事务 OK
-
-
redis事务出现异常:
-
编译型异常,即redis命令格式出错
事务队列中的所有命令都不会执行。出现事务错误异常
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 #命令出错 (error) ERR wrong number of arguments for 'set' command 127.0.0.1:6379> set k1 k2 QUEUED 127.0.0.1:6379> exec #返回错误提示 (error) EXECABORT Transaction discarded because of previous errors.-
运行时异常,redis命令在执行时,才会出现的错误
事务队列中,出错的命令不会执行,正确的命令会执行
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 "aaa" QUEUED 127.0.0.1:6379> incr k1 #错误语法:对字符串进行+1操作 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR value is not an integer or out of range #错误命令执行出错。其余执行成功 3) OK 4) OK -
-
redis监视:
redis中可以通过watch命令对key进行监视,如果在开启事务过程中,该key的值发生变化,则执行事务失败。
127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> watch money #监听money的值 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby money 10 QUEUED 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> exec (nil) # 由于开启事务后,money的值发生变化,所以执行事务失败,返回null
10、Jedis
jedis是redis官方推荐的java连接工具,使用Java操作redis的中间件。
Jedis的使用
-
导入依赖
redis.clients jedis 3.3.0 -
创建对象
//创建jedis对象,参数:地址+端口 Jedis jedis = new Jedis("127.0.0.1",6379); //jedis类的方法和redis命令一致 //测试连接 System.out.println(jedis.ping()); jedis.set("name","zhangsan"); String name = jedis.get("name"); System.out.println(name);
Jedis整合redis事务
//创建jedis对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
//开启事务,multi:事务对象
Transaction multi = jedis.multi();
//执行命令操作
try {
//执行命令
multi.set("address1","beijing");
multi.set("address2","chongqing");
//未出现异常,执行事务
multi.exec();
} catch (Exception e) {
//出现异常,放弃事务
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("address1"));
System.out.println(jedis.get("address2"));
//关闭连接
jedis.close();
}
11、SpringBoot整合Redis
SpringBoot在2.X版本后,连接redis的底层工具由jedis替换为lettuce
对比:
? jedis:采用直连,多线程操作不安全,如果想实现线程安全,需要使用jedis pool连接池
? lettuce:底层采用netty,实例可以在多个线程中共享,线程安全
-
1、引入坐标依赖
org.springframework.boot spring-boot-starter-data-redis -
2、配置连接
# 配置redis spring.redis.host=127.0.0.1 spring.redis.port=6379 -
3、通过RedisTemplate操作redis
@Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { redisTemplate.opsForValue().set("address1","chongqing"); System.out.println(redisTemplate.opsForValue().get("address1")); }
RedisTemplate的使用
https://blog.csdn.net/ruby_one/article/details/79141940
RedisTemplate 对五种数据结构的操作
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
12、自定义RedisTemplate
redisTemplate无法存入未序列化的对象
解决方案:
-
实体类实现序列化接口
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private String username; private Integer age; } -
将Java对象转换为json字符串存入
User user = new User("李四", 33); //对象转为json字符串 String userJson = new ObjectMapper().writeValueAsString(user); redisTemplate.opsForValue().set("user",userJson);
自定义RedisTemplate
由于redis自动配置类中存在@ConditionalOnMissingBean(name = "redisTemplate")注解,所以当自定义redisTemplate时,自动配置类会失效。
/**
* 自定义redisTemplate注解
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用string的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value的序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
RedisTemplate的工具类
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map
13、windows环境下,redis配置文件的几点设置
-
windows下redis服务默认加载redis.windows-service.conf配置文件,修改为加载redis.windows.conf文件
-
1、删除windows服务下的redis服务
- a、通过redis服务-->属性-->找到服务名称
- b、管理员打开cmd窗口
- c、执行命令:sc delete 服务名称
-
2、注册redis服务
使用cmd进入redis目录运行 redis-server --service-install redis.windows.conf --service-name redis -
在windows服务中,将redis服务设为自动
-
-
在windows服务中,手动启动redis服务,即可后台运行redis服务
-
通过redis-server.exe启动redis服务,会找不到配置文件
Warning: no config file specified, using the default config. In order to specify a config file use c:\java\redis\redis-server.exe /path/to/redis.conf- 通过配置文件启动服务:redis-server.exe redis.windows.conf
14、redis.conf配置文件属性
units 单位
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit.单位大小写不敏感 1GB 1Gb 1gB 是一样的。
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
INCLUDES 包含
导入其他redis配置文件
# include .\path\to\local.conf
# include c:\path\to\other.conf
NETWORK 网络
bind 127.0.0.1 #绑定访问redis数据库的ip地址
protected-mode yes #是否开启保护模式
port 6379 #端口设置
-
tcp-backlog 511
设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 已经完成三次握手队列。在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到
/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的效果。 -
timeout 0
设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接。0为关闭。
-
Tcp-keepalive 0
单位为秒,指定 TCP 连接是否为长连接,"侦探"信号有 server 端维护。默认为 0,表示禁用,则不会进行Keepalive检测
GENERAL 通用
daemonize no #是否以守护进程开启,默认为no -
loglevel notice :日志等级,四种等级
- debug:该等级,会产生大量日志信息,一般用于测试和开发阶段。
- verbose:记录较多的日志信息,与debug等级相似。
- notice:(默认等级),记录少量的日志信息,适用于生产环境。
- warning:打印非常重要或关键的日志信息。
-
logfile "":日志的输出文件位置地址。为空时直接日志直接打印在控制台。
-
databases 16:redis默认16个数据库。
SNAPSHOTTING 快照
快照:数据持久化到本地硬盘的方式之一
# 保存数据快照的频率,即将数据持久化到 dump.rdb 文件中的频度。
save 900 1 # 900秒内有一个key改变过就备份
save 300 10 # 300秒内有10个key改变过就备份
save 60 10000 # 60秒内有10000个key改变就触发备份
#持久化出错时,redis是否继续工作。
stop-writes-on-bgsave-error yes
#是否压缩rdb文件,需消耗cpu资源。
rdbcompression yes
# 在存储快照后,还可以让redis使用CRC64算法来进行数据检验
# 这样做会增加10%的性能消耗,
# 如果想获得最大的性能提升,则可以关闭此功能
rdbchecksum yes
# 保存时的文件名称,断电重启时读取的文件名称
dbfilename dump.rdb
# rdb文件保存的目录
dir ./
SECURITY 安全
# 获取登录密码
config get requirepass
127.0.0.1:8686> config get requirepass
1) "requirepass"
2) "51310400"
# 查询启动时所在的目录
config get dir
127.0.0.1:8686> config get dir
1) "dir"
2) "/alidata/redis-5.0.3/db"
# 设置redis密码
config set requirepass 123456
# 登录redis
[root@izm5e2q95pbpe1hh0kkwoiz /]# redis-cli -p 8686
127.0.0.1:8686> ping
(error) NOAUTH Authentication required.
127.0.0.1:8686> auth 51310400
OK
127.0.0.1:8686> ping
PONG
LIMITS 限制
-
maxclients 10000:能连上redis数据库的最大客户端数量
-
maxmemory
:redis 配置的最大的内存容量,达到最大内存时启动清除策略。 -
maxmemory-policy noeviction:内存容量达到上限的处理策略
- (1)volatile-lru:使用LRU算法移除key,只对设置了过期时间的键
(2)allkeys-lru:使用LRU算法移除key
(3)volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
(4)allkeys-random:移除随机的key
(5)volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
(6)noeviction:不进行移除。针对写操作,只是返回错误信息
- (1)volatile-lru:使用LRU算法移除key,只对设置了过期时间的键
-
maxmemory-samples 5
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,
redis默认会检查这么多个key并选择其中LRU的那个
APPEND ONLY MODE
aof配置
- appendonly no:默认不开启aof持久化模式,默认使用rdb持久化模式。
- appendfilename "appendonly.aof":aof持久化文件名
- appendfsync everysec:执行同步的模式
- always:每次修改都会同步。消耗内存
- everysec:每秒执行同步,有可能会丢失这一秒的数据。
- no:不执行同步,操作系统自动同步,速度最快。
15、Redis持久化
Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能
RDB持久化
RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
RDB 持久化功能所生成的 RDB 文件是一个经过压缩的二进制文件,通过该文件可以还原生成 RDB 文件时的数据库状态
-
手动触发rdb持久化
- save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
- bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短
-
自动触发rdb持久化
-
配置根据key发生改变的频率,触发rdb持久化
# 保存数据快照的频率,即将数据持久化到 dump.rdb 文件中的频度。 save 900 1 # 900秒内有一个key改变过就备份 save 300 10 # 300秒内有10个key改变过就备份 save 60 10000 # 60秒内有10000个key改变就触发备份 -
执行shutdown和flushall命令时,且没有开启AOF持久化功能则自动执行bgsave。
-
-
恢复rdb持久化的数据
将备份文件(dump.rdb)放到redis启动目录下,redis启动时,会自动加载备份的数据。
-
优点
- Redis加载RDB恢复数据远远快于AOF的方式(适合大规模的数据恢复)。
- RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复(对数据完整性和一致性要求不高)
-
缺点
-
数据丢失:在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。
-
版本不兼容:RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。
-
影响性能:fork子进程的时候,会消耗内存。
-
AOF持久化
在redis操作时,每当有修改数据库的命令时,会将命令追加写入到aof文件的末尾,在恢复数据库时,会将aof文件中的修改命令依次执行,还原数据库数据。
配置在配置文件中的APPEND ONLY MODE 模块中,aof持久化默认是不开启的,如果需要开启,需将appendonly属性改为yes
appendonly yes
appendfsync everysec #执行同步的模式
#always:每次修改都会同步。消耗内存
#everysec:每秒执行同步,有可能会丢失这一秒的数据。
#no:不执行同步,操作系统自动同步,速度最快。
-
redis在启动时,会通过appendonly.aof文件恢复数据。
注:windows环境下,redis-server.exe默认不会通过redis.windows.conf配置文件启动服务,所以aof配置和恢复数据将失效。在启动服务时,通过配置文件启动服务:redis-server.exe redis.windows.conf。
-
aof文件遭到破坏时,redis服务会启动失败,出现以下错误:
Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix通过redis-check-aof工具,修复appendonly.aof文件,会aof文件中问题命令以后的所有内容,只保留没问题的命令:
C:\Java\Redis>redis-check-aof.exe --fix appendonly.aof #windows下的命令 0x66: Expected \r\n, got: 6164 AOF analyzed: size=196, ok_up_to=81, diff=115 This will shrink the AOF from 196 bytes, with 115 bytes, to 81 bytes Continue? [y/N]: y -
优点:
- 模式为每次修改都同步时,可以保证数据的完整性。
- 模式为每一秒同步一次修改,可能丢失最后一秒数据。
- 从不同步,效率最高
-
缺点:
- aof文件远远大于rdb文件,文件恢复速度也非常慢。
扩展:
1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
- RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。
5、性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
- 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
- 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。
如何选择使用哪种持久化方式?
? 一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
? 如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
? 有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进 行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
16、Redis发布订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
- [PSUBSCRIBE pattern [pattern ...]] :订阅一个或多个符合给定模式的频道。
- [PUBSUB subcommand [argument [argument ...]]]:查看订阅与发布系统状态。
- [PUBLISH channel message]:将信息发送到指定的频道。
- [PUNSUBSCRIBE [pattern [pattern ...]]]:退订所有给定模式的频道。
- [SUBSCRIBE channel [channel ...]]:订阅给定的一个或多个频道的信息。
- [UNSUBSCRIBE [channel [channel ...]]]:指退订给定的频道。
演示
-
发送端
127.0.0.1:6379> PUBLISH pd1 "hello redis" #向pd1频道发送消息,返回值为:成功接收的订阅者个数。 (integer) 1 127.0.0.1:6379> PUBLISH pd1 "22222" (integer) 2 -
接收端
127.0.0.1:6379> PSUBSCRIBE pd1 #订阅pd1频道 Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "pd1" 3) (integer) 1 #订阅成功,等待接收消息 1) "pmessage" #类型为消息 2) "pd1" # 频道名称 3) "pd1" 4) "22222" # 消息内容原理
Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍此加深对 Redis 的理解。
Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
缺点
- 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
- 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
应用
- 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
- 多人在线聊天室。
稍微复杂的场景,我们就会使用消息中间件MQ处理。
17、Redis主从复制
概述:
-
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
-
作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础。
-
为什么使用集群
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),原因如下:
1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;
2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。
电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。
主从复制,读写分离! 80% 的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用! 一主二从!
只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis!
-
总结
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
环境查看
只配置从库,不用配置主库!
127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # redis从机的数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
windows下开启多个redis服务
-
1、复制redis配置文件。
-
2、修改新复制的备份文件的信息,防止冲突。内容包括:
port 6380 #端口号 logfile "6380.log" #日志文件名称 dbfilename dump6380.rdb #持久化文件名 -
3、根据这些配置文件安装redis服务,进入redis安装目录,执行命令启动服务。
redis-server.exe --service-install redis.6380.conf --service-name redis6380 redis-server.exe --service-install redis.6381.conf --service-name redis6381 redis-server.exe --service-install redis.6382.conf --service-name redis6382 ...... -
4、在windows服务中,手动开启服务
-
5、通过命令分别连上redis服务
redis-cli.exe -h 127.0.0.1 -p 6380 redis-cli.exe -h 127.0.0.1 -p 6381 redis-cli.exe -h 127.0.0.1 -p 6382
一主二从
默认情况下,每台redis服务器都是主机(master),只需要配置从机。
临时配置从机(通过命令行配置)
-
在从机中配置谁为主机,通过配置跟随谁。
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # SLAVEOF+主机ip+主机端口 OK 127.0.0.1:6380> info replication # Replication role:slave #该服务器,角色已变为从机 master_host:127.0.0.1 #主机的ip master_port:6379 #主机的端口 master_link_status:up master_last_io_seconds_ago:9 master_sync_in_progress:0 slave_repl_offset:14 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:92fece5c1b55c582589fef7676bf417b38e233c5 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:1 #从机数量 slave0:ip=127.0.0.1,port=6380,state=online,offset=210,lag=0 #从机信息 master_replid:92fece5c1b55c582589fef7676bf417b38e233c5 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:210 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:210
永久配置从机(修改配置文件)
slaveof
细节
-
主机可以读写,从机只能读不能写,主机中的数据,会同步到从机中。
主机中写入数据
127.0.0.1:6379> set name zhangsan OK从机中读取数据
127.0.0.1:6380> get name #从机中读取主机中写入的数据 "zhangsan" 127.0.0.1:6380> set address beijing #从机中写入数据会报错 (error) READONLY You can't write against a read only replica. -
主机断开连接(宕机)
- 默认从机还是连接到主机上的,但确实主机的写操作。
- 如果断开的主机重新连接,从机也能从主机上同步数据。
-
从机断开连接(宕机)
- 如果是命令行配置的从机,该从机变为主机。
- 只有当重新将该服务器,配置上主机,才能从主机获取数据。
复制原理
-
Slave 启动成功连接到 master 后会发送一个sync同步命令
-
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
-
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
-
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!
从机变为主机
- 当主机断开连接时,从机可以使用命令:
SLAVEOF no one让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动)!
18、哨兵模式
概述
-
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
-
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵的作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
多哨兵模式(哨兵集群)
-
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
-
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
开启哨兵模式
-
1、编写哨兵模式配置文件(命名必须为:sentinel.conf)
? 最少配置如下:
# sentinel monitor 被监控的名称 主机ip 主机端口 1 sentinel monitor myredis 127.0.0.1 6379 1 -
2、启动哨兵模式
-
windows环境下
redis-server.exe sentinel.conf --sentinel -
linux环境下
redis-sentinel myconf/sentinel.conf
-
-
3、哨兵模式启动成功
C:\Java\Redis>redis-server.exe sentinel.conf --sentinel [14064] 18 May 22:59:57.768 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo [14064] 18 May 22:59:57.768 # Redis version=5.0.10, bits=64, commit=1c047b68, modified=0, pid=14064, just started [14064] 18 May 22:59:57.768 # Configuration loaded _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 5.0.10 (1c047b68/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 14064 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' [14064] 18 May 22:59:57.772 # Sentinel ID is 1dacfa6cc5335ee44b10f6f205b82371fa7188f8 [14064] 18 May 22:59:57.773 # +monitor master redis 127.0.0.1 6379 quorum 1 监听主机 [14064] 18 May 22:59:57.774 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ redis 127.0.0.1 6379 [14064] 18 May 22:59:57.775 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:51.249 # +sdown master redis 127.0.0.1 6379 [14064] 18 May 23:00:51.249 # +odown master redis 127.0.0.1 6379 #quorum 1/1 一个哨兵认为主机断开 [14064] 18 May 23:00:51.250 # +new-epoch 1 [14064] 18 May 23:00:51.250 # +try-failover master redis 127.0.0.1 6379 [14064] 18 May 23:00:51.251 # +vote-for-leader 1dacfa6cc5335ee44b10f6f205b82371fa7188f8 1 [14064] 18 May 23:00:51.251 # +elected-leader master redis 127.0.0.1 6379 [14064] 18 May 23:00:51.251 # +failover-state-select-slave master redis 127.0.0.1 6379 [14064] 18 May 23:00:51.358 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:51.358 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:51.470 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:51.902 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:51.902 # +failover-state-reconf-slaves master redis 127.0.0.1 6379 [14064] 18 May 23:00:51.965 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:52.923 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:53.938 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ redis 127.0.0.1 6379 [14064] 18 May 23:00:54.001 # +failover-end master redis 127.0.0.1 6379 主机断开连接 [14064] 18 May 23:00:54.001 # +switch-master redis 127.0.0.1 6379 127.0.0.1 6381 选举6381作为新主机 [14064] 18 May 23:00:54.002 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ redis 127.0.0.1 6381 [14064] 18 May 23:00:54.002 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ redis 127.0.0.1 6381 [14064] 18 May 23:01:24.089 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ redis 127.0.0.1 6381 [14064] 18 May 23:02:40.044 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ redis 127.0.0.1 6381 [14064] 18 May 23:02:50.023 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ redis 127.0.0.1 6381 # 原主机6379重新连接,但只能作为6381的从机
19、Redis缓存穿透和雪崩
缓存穿透(即查询不到)
概念
- 在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
解决方案
-
布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

-
缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(即量太大,缓存过期)
概念
-
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
-
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方案
-
1.设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
-
2.加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩
概念
-
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
-
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
-
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

-
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
解决方案
redis高可用
- 这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
限流降级
- 这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
- 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。