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

tech2022-09-18  113

Redis是一个用C语言开发的高速缓存数据库,高级的key:value存储系统缓存穿透: 指查询一个一定不存在的数据,由于缓存是不命中是需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库中去查询,造成缓存穿透。 解决方案: 最简单粗暴的方法:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们就把这个空结果进行缓存,但它的过期时间会很短,最长不超过5分钟。 Redis支持的数据类型: Strings(字符串)lists(字符串列表)hashes(字典)sets(字符串集合)zset(有序字符串集合) Key的建议 key不要太长,尽量不要超过1024字节,这不经消耗内存,还会降低查询效率key不要太短,可读性会降低在一个项目中,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,lettuceRedis的持久化的两种策略: 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-lrunoeviction: 当内存不足以容纳新写入数据时,新写入操作就会报错。不推荐!allkeys-lru: 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用。allkeys-random: 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key.不推荐volatile-lru: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最少使用的key。这种情况一般是把redis即当做缓存,同时又做持久化存储的时候才用。volatile-ttl :当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。如果没有设置expire的key,不满足先决条件(prerequisites),那么volatile-lru , volatile-random 和volatile-ttl 策略的行为和noeviction(不删除) 基本上一致。

最新回复(0)