Redis基础知识回顾&相关指令

  • Redis是一个用C语言开发的高速缓存数据库,高级的key:value存储系统
  • 缓存穿透:
  1. 指查询一个一定不存在的数据,由于缓存是不命中是需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库中去查询,造成缓存穿透。
  • 解决方案:
  1. 最简单粗暴的方法:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们就把这个空结果进行缓存,但它的过期时间会很短,最长不超过5分钟。
  • Redis支持的数据类型:
  1. Strings(字符串)
  2. lists(字符串列表)
  3. hashes(字典)
  4. sets(字符串集合)
  5. zset(有序字符串集合)
  • Key的建议
  1. key不要太长,尽量不要超过1024字节,这不经消耗内存,还会降低查询效率
  2. key不要太短,可读性会降低
  3. 在一个项目中,key最好使用统一 命名模式。
  • String
set mystr "hello world!" //设置字符串类型
get mystr //读取字符串类型

127.0.0.1:6379> set mynum "2"
OK
127.0.0.1:6379> get mynum
"2"
127.0.0.1:6379> incr mynum
(integer) 3
127.0.0.1:6379> get mynum
"3"
  • list
    • redis中list底层实现上是一个链表,而不是数组,所以在列表中任意一个结点插入和删除元素的速度较快,但是对于大数据量的列表中,定位一个元素会比较慢。
    • 常用操作:LPUSH、RPUSH、LRANGE
//新建一个list叫做mylist,并在列表头部插入元素"1"
127.0.0.1:6379> lpush mylist "1" 
//返回当前mylist中的元素个数
(integer) 1 
//在mylist右侧插入元素"2"
127.0.0.1:6379> rpush mylist "2" 
(integer) 2
//在mylist左侧插入元素"0"
127.0.0.1:6379> lpush mylist "0" 
(integer) 3
//列出mylist中从编号0到编号1的元素
127.0.0.1:6379> lrange mylist 0 1 
1) "0"
2) "1"
//列出mylist中从编号0到倒数第一个元素
127.0.0.1:6379> lrange mylist 0 -1 
1) "0"
2) "1"
3) "2"

 

  • 集合set
    • redis中的集合时无序的,集合中的元素没有先后顺序
    • 相关操作:添加新元素、删除已有元素、取交集、并集、取差
//向集合myset中加入一个新元素"one"
127.0.0.1:6379> sadd myset "one" 
(integer) 1
127.0.0.1:6379> sadd myset "two"
(integer) 1
//列出集合myset中的所有元素
127.0.0.1:6379> smembers myset 
1) "one"
2) "two"
//判断元素1是否在集合myset中,返回1表示存在
127.0.0.1:6379> sismember myset "one" 
(integer) 1
//判断元素3是否在集合myset中,返回0表示不存在
127.0.0.1:6379> sismember myset "three" 
(integer) 0
//新建一个新的集合yourset
127.0.0.1:6379> sadd yourset "1" 
(integer) 1
127.0.0.1:6379> sadd yourset "2"
(integer) 1
127.0.0.1:6379> smembers yourset
1) "1"
2) "2"
//对两个集合求并集
127.0.0.1:6379> sunion myset yourset 
1) "1"
2) "one"
3) "2"
4) "two"
  • 有序集合 sorted sets
    • 有序集合中的每个元素都关联一个序号(score),这就是排序的依据。
    • 很多时候,我们都将redis中有序集合叫做zsets,因为有序集合相关操作指令都是以z开头的:zrange,zadd , zrevrange , zrangebyscore
127.0.0.1:6379> zadd myzset 1 baidu.com 
(integer) 1
//向myzset中新增一个元素360.com,赋予它的序号是3
127.0.0.1:6379> zadd myzset 3 360.com 
(integer) 1
//向myzset中新增一个元素google.com,赋予它的序号是2
127.0.0.1:6379> zadd myzset 2 google.com 
(integer) 1
//列出myzset的所有元素,同时列出其序号,可以看出myzset已经是有序的了。
127.0.0.1:6379> zrange myzset 0 -1 with scores 
1) "baidu.com"
2) "1"
3) "google.com"
4) "2"
5) "360.com"
6) "3"
//只列出myzset的元素
127.0.0.1:6379> zrange myzset 0 -1 
1) "baidu.com"
2) "google.com"
3) "360.com"

 

  • 哈希
    • 哈希是redis-2.0版本以后才有的数据结构
    • hashes是存储字符串和字符串值之间的映射

//建立哈希,并赋值
127.0.0.1:6379> HMSET user:001 username antirez password P1pp0 age 34 
OK
//列出哈希的内容
127.0.0.1:6379> HGETALL user:001 
1) "username"
2) "antirez"
3) "password"
4) "P1pp0"
5) "age"
6) "34"
//更改哈希中的某一个值
127.0.0.1:6379> HSET user:001 password 12345 
(integer) 0
//再次列出哈希的内容
127.0.0.1:6379> HGETALL user:001 
1) "username"
2) "antirez"
3) "password"
4) "12345"
5) "age"
6) "34"
  • 支持的Java客户端有 Redisson,jedis,lettuce
  • Redis的持久化的两种策略:
  1. Redis分布式锁

Redis分布式锁其实就是在系统里面占一个“坑”,其他程序也要占坑的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。

占坑一般使用setnx(set if not exists)指令,只允许被一个程序占有,使用完调用del释放锁。

redis分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。

2.Redis的散列表(建议)

把相关的信息放到散列表里面存储,而不是把每个字段单独存储,这样可以有效的减少内存使用。比如讲Web系统的用户对象,应该放到散列表里面再整体存储到Redis,而不是把用户的姓名、年龄、密码、邮箱等字段分别设置Key进行存储。

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最少使用的数据淘汰。
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  • no-enviction(驱逐):禁止驱逐数据。

 

  • redis的两种持久化方式:RDB(Redis DataBase) 和AOF(Append Only File)
    • RDB:就是在不同的时间点,将redis存储的数据生成快照 并存储到磁盘等介质上。
    • AOF:则是换一个角度来实现持久化,那就是将redis执行过的所有指令记录下来,下次redis重新启动时,只要把这些指令从前到后再重复执行一遍,就可以实现数据恢复了。
    • 其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF进行数据恢复,这是因为AOF方式的数据恢复完整度更高。
    • 如果没有数据持久化的需求,也可以完全关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库。
    • RDB:
      • 将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。
      • redis在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用到这个临时文件替换上次持久化好的文件,正式这种特性,让我们可以随时进行备份,因为快照文件总是完整可用的。对于RDB方式,redis会单独创建fork一个子进程来进行持久化,而主进程是不会进行任何IO操作,这样就确保了redis极高的性能。如果需要大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那么RDB方式就不太适合。因为即使你每5分钟都持久化一次,当redis故障时,忍让会有近5分钟的数据丢失,所以redis还提供了另一种持久化方式,那就是AOF。
    • AOF:
      • 只允许追加不允许改写的文件,将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令执行一遍
      • 通过配置redis.conf中的appendonly yes 就可以打开AOF功能,如果有写操作,redis就会追加到AOF文件的末尾。
      • 默认的AOF持久化策略是每秒钟把缓存中的写指令记录到磁盘中,因为这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。
      • 如果在追加日志时,恰好遇到磁盘空间满,inode满或断点等情况导致日志写入不完整,redis提供redis-check-aof工具进行日志恢复。
      • 因为采用了追加方式,如果不做任何处理的话,AOF文件会变得越来越大,Redis提供了AOF文件重写rewrite机制,即当AOF文件的大小超过所设定的阈值时,redis就会启动AIF文件的内容压缩,只保留可以恢复数据的最小指令集。
        • 假如调用了100次INCR指令,在AOF文件中就要存储100条指令,但是这明显是很抵消的,完全可以吧100条指令合并成一条set指令,这就是重写机制的原理。
  • redis的事务处理
    • MULTI :用来组装一个事务
    • EXEC:用来执行一个事务
    • DISCARD:用来取消一个事务
    • WATCH:用来监视一些key,一旦这些key在事务执行之前被改变,则取消事务的执行
redis> MULTI //标记事务开始
OK
redis> INCR user_id //多条命令按顺序入队
QUEUED
redis> INCR user_id
QUEUED
redis> INCR user_id
QUEUED
redis> PING
QUEUED
redis> EXEC //执行
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) PONG

 

  • WATCH
    • 用于监视key是否被改动过,而且支持同时监视多个key,只要还没真正触发事务,WATCH都会尽职尽责的监视,一旦发现某个key被修改了,在执行EXEC时就会返回nil,表示事务无法触发。

127.0.0.1:6379> set age 23
OK
127.0.0.1:6379> watch age //开始监视age
OK
127.0.0.1:6379> set age 24 //在EXEC之前,age的值被修改了
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 25
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec //触发EXEC
(nil) //事务无法被执行
  • 最常见的关于事务的错误
    • 调用EXEC之前的错误
    • 调用EXEC之后的错误
    • 调用EXEC之前的错误,有可能是由于语法有误导致的,也有可能是内存不足导致,只要出现某个命令无法成功写入缓冲队列的情况,redis都会进行记录,在客户端调用EXEC时,redis会拒绝执行这一事务。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> haha //一个明显错误的指令
(error) ERR unknown command 'haha'
127.0.0.1:6379> ping
QUEUED
127.0.0.1:6379> exec
//redis无情的拒绝了事务的执行,原因是“之前出现了错误”
(error) EXECABORT Transaction discarded because of previous errors.

 

  • 调用EXEC之后的错误,redis采取了完全不同的策略,即redis不会理睬这些错误,而是继续向下执行事务中的其他命令,这是因为,对于应用层面的错误,并不是redis自身需要考虑和处理的问题,所以一个事务中如果某一条命令执行失败,并不会影响接下来的其他命令的执行。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 23
QUEUED
//age不是集合,所以如下是一条明显错误的指令
127.0.0.1:6379> sadd age 15 
QUEUED
127.0.0.1:6379> set age 29
QUEUED
127.0.0.1:6379> exec //执行事务时,redis不会理睬第2条指令执行错误
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get age
"29" //可以看出第3条指令被成功执行了
  • redis为什么这么快?
    • 纯内存操作
    • 单线程操作,避免了频繁的上下文切换
    • 采用了非阻塞I/O多路复用机制
  • redis五中数据类型主要用途
    • String:最常规的get、set操作,Value可以是String也可以是数字,一般做一些复杂的计数功能的缓存
    • hash:这里的value存放的结构化的对象,比较方便操作其中某个对象,常用于单点登录,用这种数据结构存放用户信息,以cookie作为key,设置30分钟为缓存过期时间,很好的模拟session。
    • list:可用作简单的消息队列,也可以用lrange,做基于redis 的分页功能,响应速度简直不要太快。
    • set:set存储的是不重复的值,所以可以用做全局去重的功能,相较于JVM自带的set,效率更好。
    • sorted set:多了一个权重参数score ,集合中的元素可以按score进行排列,可以做排行榜操作,取TOP N。
  •  redis的过期策略及内存淘汰机制:
    • 分析:
      • 假设你的redis只能存储5个G的数据,可是你写了10个G,由于只有一个redis服务器,那么会删除5G的数据,该如何删除?还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,该如何解决?
        • redis采用的是定期删除 + 惰性删除策略
      • 为什么不用定时删除策略?
        • 定时删除,用一个定时器来负责监听key,过期则自动删除,虽然内存及时释放,但是十分消耗CPU资源。在打并发请求下,CPU要将时间应用在处理请求,而不是删除key。因此不适合采用定时删除策略。
      • 定期删除+惰性删除是如何工作的?
      • 定期删除,redis默认每隔100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查。因此如果只采用定期删除策略,同时也会导致很多key到时间没有被删除掉。所以还要搭配惰性删除。惰性删除,就是在获取某个key的时候,redis会检查一下,这个key如果设置了过期时间,那么是否过期了,如果过期了,此时就会删除。
      • 采用定期删除+ 惰性删除就没其它问题了?当然不是,如果定期删除没删除key,且你也没及时去请求key,也就是惰性删除也没有生效。这样redis内存就会越来越高,那么需要采用内存淘汰机制。在redis-conf中配置
      • # maxmemory-policy volatile-lru
      • noeviction: 当内存不足以容纳新写入数据时,新写入操作就会报错。不推荐!
      • allkeys-lru: 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用。
      • allkeys-random: 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key.不推荐
      • volatile-lru: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最少使用的key。这种情况一般是把redis即当做缓存,同时又做持久化存储的时候才用。
      • volatile-ttl :当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
      • 如果没有设置expire的key,不满足先决条件(prerequisites),那么volatile-lru , volatile-random 和volatile-ttl 策略的行为和noeviction(不删除) 基本上一致。

课程的实战源码是我在 GitHub 上开源项目 spring-boot-projects 中的其中一个项目代码,目前已有 2300 多个 star,项目截图如下: 由于项目比较受大家欢迎,因此心中就出现了将它做成教学视频的想法,也就是你现在了解的这个课程《SpringBoot入门及前后端分离项目开发》,本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 项目实践开发。Spring Boot 介绍、前后端分离、API 规范等内容旨在让读者更加熟悉 SpringBoot 及企业开发中需要注意的事项并具有使用 SpringBoot 技术进行基本功能开发的能力;这最后的项目实战为课程的主要部分,我会带着大家实际的开发一个前后端分离的 Spring Boot 实践项目,让大家实际操作并从无到有开发一个线上项目,并学习到一定的开发经验以及其中的开发技巧,旨在让读者具有将 Spring Boot 真正应用于项目开发的能力; 以下为实践项目的页面和功能展示,分别为:登录页面 列表页面(分页功能) 图片上传功能 富文本编辑器整合使用 实践项目的主要功能和页面就是这些,通过项目展示大家也能够感受到,在实际应用开发中的高频次功能都已经实现,稍加修改就可以运用到企业开发中,整个项目的开发模式为前后端分离的模式,即 Spring Boot 提供后端接口,前端页面通过 Ajax 异步调用接口的方式与后端服务器进行交互并将数据填充至页面中,这也是目前企业开发中比较重用的开发模式,希望大家能够了解并且能够实际的上手开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值