什么是redis

redis是一种k-v存储形式的 nosql型数据库,常用的数据类型有五种,String,list,set,zset,hash,根据不同的场景可以选择不同的存储类型去使用;redis的数据是可以设置过期时间的,也可以做持久化数据的操作,持久化数据的方式主要有两种RDB和AOF,并且基于redis的特性也可以做分布式锁,还可以解决项目中的一些业务场景问题。

免费客户端下载链接:https://github.com/lework/RedisDesktopManager-Windows/releases

value常用类型及使用场景

String类型
常用命令: set,get,mget ,decr,incr等。
(写入值,取值,数值+1,数值-1)
使用场景:普通存值

List类型
常用命令:lpush,rpush,lpop,rpop,lrange(取指定位置的值),阻塞获取
(从头插入,从尾插入,从头删除一个并返回元素,从尾删除第一个并返回元素,返回范围结果)
使用场景:比如粉丝列表,关注等等列表信息
实现方式:
Redis list的实现为一个双向链表,可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

Hash类型
常用命令:hget,hset,hgetall 等。
value是类似于map,多个feild+value
(获取指定值,设置值,取出所有值)
使用场景:一般存储对象都是用Hash类型来存

set类型
常用命令:sadd,srem,spop,smembers,sunion,sinter
(添加元素,删除元素,获取所有元素,获取两个set的并集,获取两个set的交集)
使用场景:因为有求并集交集的方法,并且所存储的元素不可重复,所以可以很方便的实现共同好友,共同关注等应用场景
在这里插入图片描述

zset类型
常用命令:zadd,zrange,zrem,zcard等
(添加元素,返回指定区间内的元素,用于移除有序集中的一个或多个成员,用于计算元素个数)
使用场景:可以做带权重的队列b,然后按序获取任务
在这里插入图片描述

redis的持久化机制

实现方式有两种:分别是RDB和AOF。
RDB:就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上

聊一聊RDB:
RDB的方式就是通过快照的方式去将数据完整的存储在磁盘上,在快照过程中,会生成一个临时文件,当持久化过程结束后才会去替换上次持久化的临时文件。并且快照过程是创建了一个子进程去执行的,并且主进程会停止IO操作,这样会更加高效。
当然也是有弊端的,比如设置了每隔五分钟去快照保存一次,当redis发生故障的时候,那么也有可能会丢失这五分钟的数据,所以有另一种持久化机制那就是AOF。

AOF:则是换了一个角度去持久化数据,就是将写命令去全部存储起来,当你重启的时候再把这些命令执行一遍,这样数据就恢复了

聊一聊AOF:
AOF方式很简单,就是将所有写命令存储起来,我们通过配置redis.conf中的appendonly yes就可以打开AOF功能,默认的AOF持久化策略是每一秒中记录一次,所以即便故障也只会丢失这一秒钟的数据。因为是采用追加的方式,所以AOF文件会越来越大,如果不做任何处理那么磁盘空间很可能被存满,为此,redis提供了AOF文件重写机制,当AOF超过设定的阈值,那么redis就会启用AOF文件内容压缩,只保留可以恢复数据的最小指令集。当恢复时出现错误,还可以使用redis提供的redis-check-aof工具去校验修复。

(重写机制)
首先会创建一个子进程去读取当前AOF文件进行重写,重写到一个临时文件中。主进程会将新收到的写命令一遍写入到缓存区,一遍写入到原来的AOF文件中,这样保证了原来AOF文件的可用性,当子进程重写结束后,会将缓存区中新的写命令追加到新的AOF文件中,然后将新的AOF与旧的AOF文件进行替换。
总结
这两种方式是可以同时使用的,如果redis重启,那么将优先使用AOF的方式去恢复数据,因为这样恢复的数据更加完整,当然如果没有持久化的需求,那么就可以关闭,作为一个纯内存数据库,就像memcache一样

主从结构

Redis是支持主从同步的,也支持一主多从,主从结构一是为了纯粹的冗余备份,二是为了提高读的效率。
Redis的主从同步是异步执行的,所以不会降低Redis的效率
主从架构中,可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样可以提高主服务器的处理性能。一般从服务器都会设置为只读模式,这样可以防止数据被误修改。

Redis集群的几种方式

主从模式
特点:

  1. 主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库

  2. 从数据库一般都是只读的,并且接收主数据库同步过来的数据

  3. 一个master可以拥有多个slave,但是一个slave只能对应一个master

  4. slave挂了不影响其他slave的读和master的读和写,重新启动后会将数据从master同步过来

  5. master挂了以后,不影响slave的读,但redis不再提供写服务,master重启后redis将重新对外提供写服务

  6. master挂了以后,不会在slave节点中重新选一个master

机制:当slove启动的时候,会向master发送SYNC指令,并进行RDB快照和缓存快照期间的命令,然后将快照文件跟缓存的命令在slove执行、复制初始化后,master每次接收到的写命令都会同步发送给slave,保证主从数据一致性。
缺点:
从上面可以看出,master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务。

Sentinel模式(哨兵模式)
特点:

  1. 哨兵模式是建立在主从模式上的
  2. 当master挂掉后会从slove中选取出新的master,并且会修改配置文件,其他slove的配置文件也会被修改
  3. 当原来的master重启后也将不再是master,而会变成slove,然后去同步新的master的数据
  4. 一个sentinel或sentinel集群可以管理多个主从Redis,多个sentinel也可以监控同一个redis
  5. sentinel最好不要和Redis部署在同一台机器,不然Redis的服务器挂了以后,sentinel也挂了

机制:
1.每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个 PING 命令也就是心跳机制
2.如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被sentinel标记为主观下线。
3.如果一个master被标记为主观下线,那么所有监控它的哨兵会再次发送命令来确认是否为主观下线
4.当有足够多数量的哨兵确认它为主观下线后,就会被标记为客观下线,并对主服务器执行故障转移。

Cluster模式(集群模式)
当数据量过大的时候,会使用该模式
特点

  1. 多个redis节点网络互连
  2. 每一个节点都是一主一从或一主多从的结构,从库不提供服务,仅作为备份
  3. 客户端可以连接任意节点进行读写

RedisTemplate之opsForValue使用说明

1.set(K key, V value)

新增一个字符串类型的值,key是键,value是值。

redisTemplate.opsForValue().set("stringValue","bbb");  

2、get(Object key)

获取key键对应的值。

String stringValue = redisTemplate.opsForValue().get("key")

3、append(K key, String value)

在原有的值基础上新增字符串到末尾。

redisTemplate.opsForValue().append("key", "appendValue");
String stringValueAppend = redisTemplate.opsForValue().get("key");
System.out.println("通过append(K key, String value)方法修改后的字符串:"+stringValueAppend);  

4、get(K key, long start, long end)

截取key键对应值得字符串,从开始下标位置开始到结束下标的位置(包含结束下标)的字符串。

String cutString = redisTemplate.opsForValue().get("key", 0, 3);  
System.out.println("通过get(K key, long start, long end)方法获取截取的字符串:"+cutString);  
``
## 5getAndSet(K key, V value)
获取原来key键对应的值并重新赋新值。
```java
String oldAndNewStringValue = redisTemplate.opsForValue().getAndSet("key", "ccc");  
System.out.print("通过getAndSet(K key, V value)方法获取原来的值:" + oldAndNewStringValue );  
String newStringValue = redisTemplate.opsForValue().get("key");  
System.out.println("修改过后的值:"+newStringValue);  

6、setBit(K key, long offset, boolean value)

key键对应的值value对应的ascii码,在offset的位置(从左向右数)变为value。

redisTemplate.opsForValue().setBit("key",1,false);  
newStringValue = redisTemplate.opsForValue().get("key")+"";  
System.out.println("通过setBit(K key,long offset,boolean value)方法修改过后的值:"+newStringValue);  

7、getBit(K key, long offset)

判断指定的位置ASCII码的bit位是否为1。

boolean bitBoolean = redisTemplate.opsForValue().getBit("key",1);  
System.out.println("通过getBit(K key,long offset)方法判断指定bit位的值是:" + bitBoolean);  

8、size(K key)

获取指定字符串的长度

Long stringValueLength = redisTemplate.opsForValue().size("key");  
System.out.println("通过size(K key)方法获取字符串的长度:"+stringValueLength);  

9、increment(K key, double delta)

以增量的方式将double值存储在变量中。

double stringValueDouble = redisTemplate.opsForValue().increment("doubleKey",5);   
System.out.println("通过increment(K key, double delta)方法以增量方式存储double值:" + stringValueDouble);  

10、increment(K key, long delta)

以增量的方式将long值存储在变量中。

double stringValueLong = redisTemplate.opsForValue().increment("longKey",6);   
System.out.println("通过increment(K key, long delta)方法以增量方式存储long值:" + stringValueLong);  

11、setIfAbsent(K key, V value)

如果键不存在则新增,存在则不改变已经有的值。

boolean absentBoolean = redisTemplate.opsForValue().setIfAbsent("absentKey","fff");  
System.out.println("通过setIfAbsent(K key, V value)方法判断变量值absentValue是否存在:" + absentBoolean);  
if(absentBoolean){  
    String absentValue = redisTemplate.opsForValue().get("absentKey")+"";  
    System.out.print(",不存在,则新增后的值是:"+absentValue);  
    boolean existBoolean = redisTemplate.opsForValue().setIfAbsent("absentKey","eee");  
    System.out.print(",再次调用setIfAbsent(K key, V value)判断absentValue是否存在并重新赋值:" + existBoolean);  
    if(!existBoolean){  
        absentValue = redisTemplate.opsForValue().get("absentKey")+"";  
        System.out.print("如果存在,则重新赋值后的absentValue变量的值是:" + absentValue);  

12、set(K key, V value, long timeout, TimeUnit unit)

设置变量值的过期时间。

redisTemplate.opsForValue().set("timeOutKey", "timeOut", 5, TimeUnit.SECONDS);  
String timeOutValue = redisTemplate.opsForValue().get("timeOutKey")+"";  
System.out.println("通过set(K key, V value, long timeout, TimeUnit unit)方法设置过期时间,过期之前获取的数据:"+timeOutValue);  
Thread.sleep(5*1000);  
timeOutValue = redisTemplate.opsForValue().get("timeOutKey")+"";  
System.out.print(",等待10s过后,获取的值:"+timeOutValue);  

13、set(K key, V value, long offset)

覆盖从指定位置开始的值。

redisTemplate.opsForValue().set("absentKey","dd",1);  
String overrideString = redisTemplate.opsForValue().get("absentKey");  
System.out.println("通过set(K key, V value, long offset)方法覆盖部分的值:"+overrideString);  

14、multiSet(Map<? extends K,? extends V> map)

设置map集合到redis。

Map valueMap = new HashMap();  
valueMap.put("valueMap1","map1");  
valueMap.put("valueMap2","map2");  
valueMap.put("valueMap3","map3");  
redisTemplate.opsForValue().multiSet(valueMap);  

15、multiGet(Collection keys)

根据集合取出对应的value值。

//根据List集合取出对应的value值  
List paraList = new ArrayList();  
paraList.add("valueMap1");  
paraList.add("valueMap2");  
paraList.add("valueMap3");  
List<String> valueList = redisTemplate.opsForValue().multiGet(paraList);  
for (String value : valueList){  
    System.out.println("通过multiGet(Collection<K> keys)方法获取map值:" + value);  
}

16、multiSetIfAbsent(Map<? extends K,? extends V> map)

如果对应的map集合名称不存在,则添加;如果存在则不做修改。

Map valueMap = new HashMap();  
valueMap.put("valueMap1","map1");  
valueMap.put("valueMap2","map2");  
valueMap.put("valueMap3","map3");  
redisTemplate.opsForValue().multiSetIfAbsent(valueMap); 

启动客户端命令

redis-cli.exe -h 127.0.0.1 -p 6379



缓存穿透

指:客户端的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,请求就都会打到数据库

解决方案:

1.缓存空对象

优点:(简单粗暴)
缺点:可能造成短期的不一致,额外消耗内存
在这里插入图片描述

2.布隆过滤器

它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
优点:内存占用少,没有多余的key
确定:实现复杂,存在误判可能
在这里插入图片描述

3.基础校验

可以根据要查询的key,做基础的校验过滤,过滤掉不可能存在的key

缓存雪崩

指在同一时段大量的缓存key同时过期或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力
解决方案:

1.给不同的key的TTL添加随机值
2.利用Redis集群提高服务的可用性
3.给缓存业务添加降级限流策略
4.给业务添加多级缓存

缓存击穿

缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,失效原因主要是设置了TTL
在这里插入图片描述

主要思想就是让key永不过期!
解决方案:

1.使用互斥锁,在缓存重建阶段持有锁才可以操作

(可以基于Redis实现互斥锁,SETNX)
优点:强一致性,实现简单,没有额外的内存消耗
确定:线程需要等待,性能受影响,可能有死锁风险
在这里插入图片描述

2.逻辑过期

在value里增加一个字段用来存储过期时间,并且重建缓存数据通过异步执行,先返回过期数据
优点:线程无需等待,性能较好
缺点:不保证一致性,有额外内存消耗,实现复杂
在这里插入图片描述
在这里插入图片描述

3.定时检查热点key并更新过期时间
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐