Redis

Redis是一种支持 key-value 等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。 默认端口:6379

启动 Redis

下载 Windows版 msi文件:https://github.com/microsoftarchive/redis/releases
在环境变量中配好 Redis 的安装目录后,直接打开 cmd,启动 Redis 客户端
启动客户端后, 在客户端cmd窗口中测试 Redis 是否正常工作,设置密码(命令行设置的密码在服务重启后失效
注意:在 Windows 环境下,Redis 不支持后台运行模式,因此在启动 Redis 服务器时必须保持 cmd 窗口一直打开。(如果想要在后台运行 Redis,可以考虑使用虚拟机或者 Linux 环境下的 Redis)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    C:\Users\蔡枫>redis-cli   # 启动 Redis 客户端(默认Redis服务端未关闭)
    127.0.0.1:6379> ping # 在 Redis 客户端中输入 ping 命令
    PONG # 返回 PONG 表示 正常工作
    127.0.0.1:6379> config set requirepass chskj.2020 # 修改密码
    OK
    127.0.0.1:6379> auth chskj.2020 # 验证密码
    OK
    127.0.0.1:6379> config get requirepass # 查看密码
    1)"requirepass"
    2)"chskj.2020"

启动(重启) Redis 服务器,客户端

进入 Redis 的安装目录(D:\Redis),打开 cmd 窗口(地址栏cmd回车),执行 redis-server.exe redis.windows.conf,即可启动 Redis 服务器
启动Redis服务器后,在 Redis 安装目录打开另一个 cmd 窗口,执行 redis-cli.exe,(或直接双击文件夹中的redis-cli.exe)即可启动(重启)Redis 客户端

Redis中有16个数据库(Database),每个数据库都是一个独立的命名空间,用于存储键值对数据。这些数据库被编号为0到15,默认情况下客户端连接到数据库0。可以通过SELECT命令选择数据库来切换不同的数据库空间,每个数据库都是相互隔离的,数据不会互相干扰。
每个数据库都可以包含键值对数据,命令和配置,但请注意,Redis的每个数据库是相对较小的,因此可以将不同类型的数据存储在不同的数据库中,以便更好地组织和管理数据。

1
SELECT 1   # 切换到数据库1

数据类型

Redis所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash。 三种特殊的数据类型,分别是 HyperLogLogs(基数统计),Bitmaps (位图) 和 geospatial(地理位置)。 Redis5.0 中还增加了一个数据结构Stream,它借鉴了Kafka的设计,是一个新的强大的支持多播的可持久化的消息队列。

结构类型 结构存储的值 结构的读写能力
String字符串 可以是字符串、整数或浮点数 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;
List列表 一个链表,链表上的每个节点都包含一个字符串 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;
Set集合包 含字符串的无序集合 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等
Hash散列 包含键值对的无序散列表 包含方法有添加、获取、删除单个元素
Zset有序集合 和散列一样,用于存储键值对字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定; 包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素

String字符串

  • String是redis中最基本的数据类型,一个key对应一个value。
  • String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
  • 实战场景
    • 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
    • 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。session:常见方案spring session + redis实现session共享,
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    127.0.0.1:6379> set hello world    # 设置存储在给定键中的值
    OK
    127.0.0.1:6379> get hello # 获取存储在给定键中的值
    "world"
    127.0.0.1:6379> del hello # 删除存储在给定键中的值
    (integer) 1
    127.0.0.1:6379> get hello
    (nil)
    127.0.0.1:6379> set counter 2
    OK
    127.0.0.1:6379> get counter
    "2"
    127.0.0.1:6379> incr counter # 将键存储的值加1
    (integer) 3
    127.0.0.1:6379> get counter
    "3"
    127.0.0.1:6379> incrby counter 100 # 将键存储的值加上整数
    (integer) 103
    127.0.0.1:6379> get counter
    "103"
    127.0.0.1:6379> decr counter # 将键存储的值减1
    (integer) 102
    127.0.0.1:6379> get counter
    "102"

List列表

  • Redis中的List其实就是链表(Redis用双端链表实现List)。
  • 使用List结构,我们可以轻松地实现最新消息排队功能(比如新浪微博的TimeLine)。List的另一个应用就是消息队列,可以利用List的 PUSH 操作,将任务存放在List中,然后工作线程再用 POP 操作将任务取出进行执行。
  • 使用列表的技巧:lpush+lpop=Stack(栈),lpush+rpop=Queue(队列),lpush+ltrim=Capped Collection(有限集合),lpush+brpop=Message Queue(消息队列)
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    127.0.0.1:6379> lpush mylist 1 2 ll ls mem   # 将给定值推入到列表左端,RPUSH 将给定值推入到列表右端
    (integer) 5
    127.0.0.1:6379> lrange mylist 0 -1 # 获取列表在给定范围上的所有值
    1) "mem"
    2) "ls"
    3) "ll"
    4) "2"
    5) "1"
    127.0.0.1:6379> lindex mylist -1 # 可以使用负数下标,以 -n 表示列表的倒数第n个元素
    "1"
    127.0.0.1:6379> rpop mylist # 从列表的右端弹出一个值,并返回被弹出的值;lpop 从左弹
    "1"
    127.0.0.1:6379> lindex mylist 10 # index不在 mylist 的区间范围内
    (nil)

Set集合

  • Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
  • Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
  • 实战场景
    • 标签(tag),给用户添加标签,或用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
    • 点赞,或点踩,收藏等,可以放到set中实现
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    127.0.0.1:6379> sadd myset hao hao1 xiaohao hao  # 向集合添加一个或多个成员	
    (integer) 3
    127.0.0.1:6379> scard myset # 获取集合的成员数
    (integer) 3
    127.0.0.1:6379> smembers myset # 返回集合中的所有成员
    1) "xiaohao"
    2) "hao1"
    3) "hao"
    127.0.0.1:6379> sismember myset hao # 判断 member 元素是否是集合 key 的成员
    (integer) 1

Hash散列

  • Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
  • 实战场景 - 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    127.0.0.1:6379> hset user name1 hao               # 添加键值对	
    (integer) 1
    127.0.0.1:6379> hset user email1 hao@163.com
    (integer) 1
    127.0.0.1:6379> hgetall user # 获取散列中包含的所有键值对
    1) "name1"
    2) "hao"
    3) "email1"
    4) "hao@163.com"
    127.0.0.1:6379> hget user user # 获取指定散列键的值
    (nil)
    127.0.0.1:6379> hget user name1
    "hao"
    127.0.0.1:6379> hset user name2 xiaohao
    (integer) 1
    127.0.0.1:6379> hdel user name1 # 如果给定键存在于散列中,那么就移除这个键
    (integer) 1
    127.0.0.1:6379> hgetall user
    1) "email1"
    2) "hao@163.com"
    3) "name2"
    4) "xiaohao"

Zset有序集合

  • Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
  • 有序集合的成员是唯一的, 但分数(score)却可以重复。有序集合是通过两种数据结构实现:
    1. 压缩列表(ziplist): ziplist是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。它能在O(1)的时间复杂度下完成list两端的push和pop操作。但是因为每次操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关
    2. 跳跃表(zSkiplist): 跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这是采用跳跃表的主要原因。跳跃表的复杂度是O(log(n))。
  • 实战场景 - 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao  # 将一个带有给定分值的成员添加到有序集合里面
    (integer) 2
    127.0.0.1:6379> ZRANGE myscoreset 0 -1 # 根据元素在有序集合中所处位置,从有序集合中获取多个元素
    1) "xiaohao"
    2) "hao"
    127.0.0.1:6379> ZSCORE myscoreset hao
    "100"
    127.0.0.1:6379> zrem myscoreset hao # 如果给定元素成员存在于有序集合中,那么就移除这个元素
    (integer) 1

HyperLogLog

采用一种基数算法,用于完成独立总数的统计。(同一个人多次访问,只记一访问量)(独立访客)
占据空间小,无论统计多少个数据,只占12K的内存空间
不精确的统计算法,标准误差为 0.81%。

Bitmap

不是一种独立的数据结构,实际上就是字符串
支持按位存取数据,可以将其看成是byte数组
适合存储索大量的连续的数据的布尔值。(记录一个人连续一年每天的签到情况 0/1)(日活跃用户)


RedisTemplate

  • Spring Boot提供了RedisTemplate作为与Redis交互的强大工具。通过RedisTemplate,您可以在java项目中执行各种Redis命令来操作数据,包括字符串、列表、集合、散列、有序集合等。以下是一些常见的Redis操作示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 存储数据:
    redisTemplate.opsForValue().set("myKey", "myValue");
    String value = (String) redisTemplate.opsForValue().get("myKey");
    // 列表操作:
    redisTemplate.opsForList().leftPush("myList", "value1");
    List<String> myList = redisTemplate.opsForList().range("myList", 0, -1);
    // 集合操作:
    redisTemplate.opsForSet().add("mySet", "member1", "member2");
    Set<String> mySet = redisTemplate.opsForSet().members("mySet");
    // 散列操作:
    Map<String, String> myHash = new HashMap<>();
    myHash.put("field1", "value1");
    myHash.put("field2", "value2");
    redisTemplate.opsForHash().putAll("myHash", myHash);
    String fieldValue = (String) redisTemplate.opsForHash().get("myHash", "field1");
    // 有序集合操作:
    redisTemplate.opsForZSet().add("myZSet", "member1", 1.0);
    Set<String> myZSet = redisTemplate.opsForZSet().range("myZSet", 0, -1);
  • 公共命令
    1
    2
    redisTemplate.delete(key);  // 删除一个数据结构
    redisTemplate.hasKey(key); // 是否存在一个数据结构

Redis 事务管理

  • 要使用编程式事务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void testTransaction() {
    Object result = redisTemplate.execute(new SessionCallback() {
    @Override
    public Object execute(RedisOperations redisOperations) throws DataAccessException {
    String redisKey = "text:tx";
    redisOperations.multi(); // 启用事务
    redisOperations.opsForSet().add(redisKey, "zhangsan");
    redisOperations.opsForSet().add(redisKey, "lisi");
    redisOperations.opsForSet().add(redisKey, "wangwu");
    // redis事务内做查询,无效
    // redis事务中的所有代码在事务提交后一起执行
    System.out.println(redisOperations.opsForSet().members(redisKey));
    return redisOperations.exec(); // 提交事务
    }
    });
    }

Redis缓存问题

在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问Mysql等数据库。这样可以大大缓解数据库的压力。当缓存库出现时,必须要考虑如下问题:

  1. 缓存穿透
    • 问题来源
      缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义,
      在流量大时,可能DB就挂掉了,要是有人利用不存在的kev频繁攻击我们的应用,这就是漏洞.
      如发起为id为”-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
    • 1、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
      2、从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒 (设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
      3、布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,
  2. 缓存击穿
    • 问题来源: 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力.
    • 1、设置热点数据永远不过期
      2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 /3务不可用时候,进行熔断,失败快速返回机制。
      3、加互斥锁
  3. 缓存雪崩
    • 问题来源: 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
    • 1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
      2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中
      3、设置热点数据永远不过期.
  4. 缓存污染(或满了)
    • 缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空间.
    • 缓存污染会随着数据的持续增加而逐渐显露,随着服务的不新运行,缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的,如果缓存空间满了,再往缓存里写数据时就会有额外开销,影响Redis性能。这部分额外开销主要是指写的时候判断淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作。

缓存淘汰策略

Redis共支持八种淘汰策略,分别是noeviction, volatile-random、 volatile-ttl, volatile-ru、 volatile-lfuallkeys-lru、allkeys-Iandom 和 allkevs-lfu 策略。主要看分三类看:

  • 不淘汰
    noeviction (v4.0后默认的):一旦缓存被写满了,再有写请求来时,Redis 不再提供服务,而是直接返回错误。这种策略不会淘汰数据,所以无法解决缓存污染问题。一般生产环境不建议使用。
  • 对设置了过期时间的数据中进行淘汰
    随机 volatile-random:在设置了过期时间的键值对中,进行随机删除。因为是随机删除,无法把不再访问的数据筛选出来,所以可能依然会存在缓存污染现象,无法解决缓存污染问题。
    tvolatile-ttl:Redis在筛选需删除的数据时,越早过期的数据越优先被选择。随机删除就无法解决缓存污染问题。
    volatile-lru:LRU 算法的全称是 Least Recently Used,按照最近最少使用的原则来筛选数据
    volatile-lfu:LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
  • 全部数据进行淘汰
    随机 allkeys-random:从所有键值对中随机选择并删除数据。
    allkeys-lru:使用 LRU 算法在所有数据中进行筛选。
    allkeys-lfu:使用 LFU 算法在所有数据中进行筛选。

Redis 持久化

Redis 提供了两种主要的持久化方式,用于在系统重启时保持数据的持久性:RDB 持久化和 AOF 持久化。
用户可以根据实际需求选择 RDB 持久化、AOF 持久化或两者结合使用。通常情况下,AOF 持久化是更安全的选择,因为它可以提供更好的持久性保障,但也需要更多的磁盘空间。

  1. RDB 持久化:
    • RDB 持久化是通过在指定的时间间隔内将内存中的数据集快照写入磁盘的方式来实现的。
    • 快照是一个二进制文件,它记录了某个时间点上 Redis 数据集的所有键值对。
    • RDB 持久化是一个“点对点”操作,它在指定的时间点创建了一个数据快照。
    • RDB 持久化适用于备份、灾难恢复等场景。
      RDB 持久化的配置选项可以在 Redis 配置文件中进行设置,例如:
      1
      2
      3
      save 900 1      # 表示在 900 秒(15分钟)内,如果至少有 1 个 key 发生了变化,则执行快照操作
      save 300 10 # 表示在 300 秒(5分钟)内,如果至少有 10 个 key 发生了变化,则执行快照操作
      save 60 10000 # 表示在 60 秒内,如果至少有 10000 个 key 发生了变化,则执行快照操作
  2. AOF 持久化:
    • AOF(Append Only File)持久化记录了服务器执行的所有写操作指令,以追加的方式将这些指令记录到一个文件中。
    • AOF 持久化是一个“追加”操作,每个写操作都被追加到文件末尾。
    • AOF 持久化适用于对数据的实时持久化需求。
      AOF 持久化的配置选项可以在 Redis 配置文件中进行设置,例如:
      1
      2
      appendonly yes            # 启用 AOF 持久化
      appendfsync everysec # 每秒钟执行一次 fsync 操作
    • appendfsync 选项可以设置 alwayseverysecnoalways 表示每个写命令都会立即被写入磁盘,everysec 表示每秒执行一次 fsync 操作,no 表示由操作系统自行决定何时进行写入磁盘操作。
    • Redis 还提供了 AOF 重写机制,可以通过 auto-aof-rewrite-percentageauto-aof-rewrite-min-size 选项配置。这允许 Redis 在不停机的情况下优化 AOF 文件的大小。