简单学会redis

redis

含义

基于内存的键值(key-value )存储,都是单线程操作,常被用于缓存,提高数据读写速度,减轻对数据库存储与访问压力,但不是普通缓存中间件,适合用在:高并发,低延迟,需要原子计数或者复杂数据结构的应用。

redis的性能极高,目前读可以达到十五万,写可以达到十万

数据类型及命令

String类型(字符串,数字):

dart 复制代码
命令格式:
    set key value  //存入键值对
    get key //根据键取出值
    incr key //把值递增1
    decr key //把值递减1(这个只记递增也可以,key设成负数)
    del key //根据键删除键值对
    setex key timeout value //存入键值对,但有时效,timeout表示失效时间,单位s
    ttl key //可以查询当前的key还剩余多长时间过期
    setnx key value //如果key存在,不做操作,如果key不存在,就存入键值对
以下为不常用命令:
    incrby key num //偏移值,可以做批量增删,是incr做不到的
    mset k1 v1 k2 v2 //批量存入键值对,可以加入多个
    mget k1 k2 //批量取出键值,可以取出多个
    append key 'value' //在键对应的原值后面拼接新内容value
    setrange key index value 
    //修改键对应位置的值,index表示开始索引位置。
    //如果新值长度大于索引开始的原值长度,就直接向后追加全部内容;
    //如果小于,就只覆盖新值长度,原值没被覆盖的地方保持不变;
    //如果index大于原值长度,空的位置会用零字节补全,但是最好不要这么勇,数据容易出错
    
  • String类型常用操作场景:缓存对象,计数器,限流,验证码

Hash类型(字段-值映射):

dart 复制代码
命令格式:
    hset key hashkey hashvalue //存入一个键值对,值同样也是一个键值对
    hget key hashkey //根据hash对象的键来获取对应的值
    hexists key hashkey //判断hash对象是否含有这个键
    hdel key hashkey //根据hash对象键删除该键值对
以下为不常用命令:
    hincrby key hashkey num //给hashkey对应的值增加偏移量,可以是正负数,就代表加或者减
    hlen key //获取hash对象键的数量
    hkeys key //获取该对象中所有的键
    hvals key //获取该对象中所有的值
    hgetall key //获取该对象中所有的数据
    hsetnx key hashkey hashvalue //和setnx一样,没有就添加,有就不操作
    
  • Hash类型常用操作场景:用户信息,购物车,对象缓存

List类型(双向链表):

arduino 复制代码
命令格式:
    rpush key value //往列表的右边添加数据,多个数据中间用空格隔开
    lrange key start end //范围显示列表数据,显示完整数据设置0 -1
    lpush key value //往列表的左边添加数据,规则同上
    lpop key //弹出列表最左边的数据,同时列表中失去这个数据
    rpop key //弹出列表最右边的数据,同时列表中失去这个数据
    llen kehy //获取列表的长度
以下为不常用命令:
    linsert key before/after refVal newVal //在参考值之前/之后插入数据
    lset key index value //根据index索引修改对应的数据
    lrem key count value 
    //count大于0,从左往右找count个数据值为value的,删除
    //count小于0,从右往左找|count|个数据值为value的,删除
    //count=0,删除所有数据值等于value的
    ltrim key start end // 根据范围截取列表,列表就会只剩这个范围的数据
    lindex key index //根据索引获取列表中的数据
  • List类型常用操作场景:简单队列,时间线,消息列表

Set类型(无序去重集合):

csharp 复制代码
命令格式:
    sadd key value //往set集合里面添加元素
    smembers key //列出集合中所有元素
    srem key value //删除集合中的某个元素
    spop key count //随机弹出集合中的元素,弹出后集合中不再包含
    sdiff key1 key2 //得到key1中特有元素(属于差集)
    sinter key1 key2 //得到两个集合中的共同包含的元素(交集)
    sunion key1 key2 //得到两个集合中的全部元素(并集)
    scard key //返回集合集合中的元素个数
以下为不常用命令:
    sdiffstore var key1 key2 //得到差集的元素,存入另一个set集合(var)
    sinterstore var key1 key2 //得到交集的元素,存入另一个set集合
    sunionstore var key1 key2 //得到并集的元素,存入另一个set集合
    smove key1 key2 value //把key1中的某个元素移动到key2的集合中
    sismember key value //判断该集合中是否包含某个元素
    srandmember key count //随机获取集合中的某个元素,原集合当中依然存在
    
  • Set类型常用操作场景:去重,共同关注,随机抽奖

Zset(sorted_set,有序集合):

arduino 复制代码
命令格式:
    zadd key score column //存入分数和名称,分数就是权重
    zincrby key num column //给分数添加对应的偏移量,依旧是可以正负数,代表加减
    zrange key start end //按照分数升序输出名称
    zrevrange key start end //按照分数降序输出名称
    zrank key name //按照升序返回这个名称对应的排名,都是从0开始算
    zrevrank key name //按照降序返回这个名称对应的排名
    zcard key //返回元素个数
以下为不常用指令:
    zrangebyscore key min max [withscores] //按照分数范围升序输出名称,withscores表示带着分数输出,可选可不选
    zrevrangebyscore by max min [withscores] //按照分数范围降序输出名称
    zrem key name //根据名称删除对应的元素
    zremrangebyscore key min max [withscores] //根据分数范围删除元素
    zremrangebyrank key start end //根据排名返回删除元素
    zcount key min max //根据分数范围统计个数
  • Zset类型常用操作场景:排行榜,延时队列

总结

  • 如果要排序选用zset
  • 如果数据是多个且允许重复选用list
  • 如果数据是多个且不允许重复选用set
  • 剩下的使用string
  • 如果是对象类型也可使用hash

设计key注意:唯一性,可读性,灵活性,时效性

设计value注意:根据项目需求来决定

注:ttl查询剩余时间,-2代表这个key是永久的,没有设置过期时间,永远都不会过期,-1代表这个key不存在,可能是已经过期了,也可能是最开始根本没有创建

使用redis的高级命令

arduino 复制代码
常用指令:
    keys * //可以用来模糊查询,就比如keys user:*,就代表查询的是user:开头的
    exists //查询是否存在某个key
    expire //设置某个key的过期时间,可以根据ttl来查看剩余时间
    persist //取消过期时间
    flushdb //清空当前redis数据库
    flushall //清空所有redis数据库
    dbsize //查看redis数据库中的key数量
以下是不常用指令:
    select //选择对应数据库 0-15
    move [key] [数据库下标] //把对应的key移动到其他数据库中
    randomkey //随机返回数据库里的一个key,数据库中仍然存在
    rename //给对应的key重命名
    echo //打印,后面写什么就打印什么
    info //获取数据库信息
    config get * //返回所有配置

注:keys * 只有在个人测试 的时候可以使用,在数据量很大的情况下去遍历会阻塞redis

redis的登录最好设置一个很复杂的密码,不然很容易被破解,一秒钟可以尝试十五万次

redis-cli -a 密码

redis的事务

arduino 复制代码
由四个命令实现:
    MULTI    // 开启事务,开始排队命令
    EXEC     // 依次执行排队中的命令
    DISCARD  // 取消事务,丢弃所有排队命令
    WATCH    // 监视 key,用于乐观锁
    UNWATCH  // 取消监视

注:redis事务不支持回滚,即不会撤销之前已经执行命令,也不会跳过后面未执行的命令

WATCH用来监视对应的key,必须配套使用,进行到EXEC的时候,如果被监视的key被其他客户端修改了,则返回(nil),事务就不会执行。

redis持久化机制

分为RDB方式和AOF方式

RDB是把某一时刻的内存数据压缩保存为一个二进制文件,默认为dump.rdb;是按照条件生成"快照"

可以有对应的触发条件,如下,多个条件满足任意一个就会触发BGSAVE(异步),这是自动触发

bash 复制代码
# 900 秒内至少有 1 次修改
save 900 1
# 300 秒内至少有 10 次修改
save 300 10
# 60 秒内至少有 10000 次修改
save 60 10000

工作流程是:满足条件触发后,先fork(拷贝)一份子进程,子进程把当前内存数据写成RDB文件,写完后替换为dump.rdb,就是RDB保存的默认文件,就是保存成功了,最后通知主进程保存完成。

注:fork完成,主进程是继续正常运行的;写入期间父进程如果修改内存页,会通过Copy On Write(写时复制)复制该页;RDB文件写入完成后通过rename原子替换。

优点:文件占用空间小;恢复速度快,启动时直接加载快照;对redis正常读写性能影响较小

缺点:数据丢失窗口大,两次快照之间的数据可能会全部丢失;fork可能造成顺势阻塞,内存页写入多时内存占用可能翻倍;不适合需要分钟秒级的恢复数据


AOF是把每条修改数据的命令以redis协议格式追加到文件末尾,默认文件为appendonly.aof;本质上就是命令日志

在AOF的配置方面,有一个fsync策略,由三种配置

perl 复制代码
always:每条命令都执行fsync
everysec:每秒执行一次fsync
no:由操作系统觉得合适刷盘

在生产环境中通常会使用第二种,everysec,对比起来性价比最高,最多丢一秒数据

AOF会无限追加,文件会越来越大,redis会执行AOF Rewrite,用当前数据生成一份最小的,替换旧文件,而它也有对应的触发条件,两个都满足的情况下,就触发

arduino 复制代码
# 比上次重写后增长了 100%
auto-aof-rewrite-percentage 100
# 当AOF文件超过 64MB
auto-aof-rewrite-min-size 64mb

重写工作流程是:依旧是满足条件触发重写之后,先fork一份子进程,子进程基于当前内存生成一份AOF文件,主进程继续接收写命令,并缓存到内存,新文件写完后,把缓存中的命令追加到新文件,原子替换旧AOF文件

启动恢复时:如果开了AOF,优先加载AOF文件,如果没开,再加载RDB文件,因为AOF通常比RDB数据更完整

优点:数据更安全;文件可读,便于人工检查;适合作为恢复和审计手段

缺点:文件比RDB大;恢复速度比RDB慢,高并发写入对磁盘有一定压力

redis的内存淘汰机制

含义:redis内存达到上限之后,在还没有过期的key当中,通过一些方式挑选出key删除来释放内存

前提:要配置maxmemory,默认内存不设上限,只有设了上限,内存达到上限才会触发淘汰

八种淘汰策略

  • noeviction:不淘汰任何 key;内存满了后,写命令返回 OOM 错误(默认策略)
  • allkeys-lru:所有 key 中淘汰最近最少使用 -- 主流选择
  • volatile-lru:只从设置了过期时间的 key 中淘汰最近最少使用
  • allkeys-random:所有 key 中随机淘汰
  • volatile-random:只在有过期时间的 key 中随机淘汰
  • volatile-ttl:只在有过期时间的 key 中淘汰剩余 TTL 最短的
  • allkeys-lfu:所有 key 中淘汰访问频率最低 -- 主流选择
  • volatile-lfu:只在有过期时间的 key 中淘汰访问频率最低

过期key处理机制

注:过期时间到期不代表key立刻从内存当中消失

  • 惰性删除 :当客户端访问key时,redis先检查key是否过期,如果过期就删除,当作key不存在处理,如果没过期就正常返回。
    • 优点:节省cpu
    • 缺点:如果过期key一直不被访问,就会一直占内存
  • 定期删除:redis会周期性的从设置了过期时间的key中查询并删除已经过期的key,而不用去遍历所有的key;如果多起key比例依然很高,就多轮采样

注:大多数情况是两种删除机制配合使用

redis缓存三大经典问题

本质上都是缓存没抗住请求,压力到了数据库

  • 缓存穿透
    • 查询的key在缓存和数据库中都不存在,所以请求会绕过缓存,直接达到数据库
    • 如果大量不存在的id同时请求,数据库会崩
    • 解决方法:
      • 缓存空值:数据库查不到时,也把空结果缓存 1 到 5 分钟。
      • 布隆过滤器:查询前先判断 ID 是否存在,过滤器说不存在就直接返回。
      • 参数校验:拦截非法 ID、超长参数、明显异常请求。
      • 限流:对高频无效请求做限流和封禁。
  • 缓存击穿
    • 某个热点key正好过期,同一时间大量请求都来查这个key,数据库承受巨大压力
    • 和穿透的区别:穿透是数据本来就不存在;击穿是数据存在,但key过期了
    • 解决方法:
      • 互斥锁:只让一个请求回源查数据库并重建缓存,其他请求等待。
      • 逻辑过期:缓存不真正过期,值里带一个过期时间,发现逻辑过期后异步刷新。
      • 后台定时刷新:热点 key 永不过期,由后台任务主动更新。
      • 本地缓存兜底:Redis 之上的本地缓存再挡一层。
      • 预热:活动开始前提前把热点 key 加载进缓存。
  • 缓存雪崩
    • 两种含义:1.大量key在同一时间段集中过期;2.redis实例宕机,不可用
    • 解决方法:
      • 过期时间加随机值:TTL = 基础时间 + random(0, 300)
      • 多级缓存:本地缓存 + Redis,减少直接打 DB。
      • Redis 高可用:Sentinel、Cluster,避免单点宕机。
      • 熔断、降级、限流:Redis不可用时,让部分请求快速失败或走降级数据。
      • 预热:上线前把热点数据提前加载。
      • DB 保护:数据库连接池限流、读写分离。

实际运用

注:此处用java举例

1.jedis

  • 首先要在pom.xml里面导入有关jedis的maven依赖(在其他正常配置都完成的状态下)
xml 复制代码
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  • 然后就可以在src里创建自己的包,创建一个新类
  • 以下代码就是简单尝试jedis的测试
java 复制代码
public class TestJedis {
    public static void main(String[] args) {
        //创建jedis连接池
        JedisPool pool = new JedisPool("localhost", 6379);
        //拿到jedis对象
        Jedis jedis = pool.getResource();
        //设置密码,没有就跳过
        jedis.auth("123456");
        //操作redis,调用的方式和命令行中的命令是一个名字
        jedis.set("str","hello redis");
        jedis.set("num","10");
        jedis.incrBy("num",1);

        jedis.hset("user","name","xiaoming");
        jedis.hset("user","age","18");
        Map<String, String> map = new HashMap<>();
        map.put("name","xiaowang");
        map.put("age","10");
        //hset不允许map里面存多个,如果要存入多个一起传就用hmset
        jedis.hmset("user2",map);

        jedis.rpush("hobby","java","python","c","php");

        jedis.sadd("java","java1","java2","java3","java4","java5");

        jedis.zadd("score",80,"xiaoming");
        jedis.zadd("score",20,"xiaowang");
        jedis.zadd("score",90,"xiaomei");
        //释放资源
        jedis.close();
        pool.destroy();
    }
}

2.SpringBoot

  • 首先还是要导入对应依赖
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 其次在资源配置的部分添加有关redis的配置
properties 复制代码
spring.application.name=testjedis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456
  • 最后就是在test包里面创建自己的包,再创建一个测试类
  • 以下代码是用集成SpringBoot的方式来测试redis
java 复制代码
@SpringBootTest
class TestjedisApplicationTests {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        //操作字符串
        redisTemplate.opsForValue().set("name","xx");
        redisTemplate.opsForValue().set("num","10");
        redisTemplate.opsForValue().increment("num",10);
        //setnx操作,虽然是一句话,但是在redis里是两句话,容易出现脏数据
        redisTemplate.opsForValue().setIfAbsent("num","10",30, TimeUnit.SECONDS);

        //操作hash类型数据
        redisTemplate.opsForHash().put("user","name","xx");
        redisTemplate.opsForHash().put("user","age","10");

        Map<Object,Object> map = new HashMap<>();
        map.put("name","xx");
        map.put("age","10");
        redisTemplate.opsForHash().putAll("usertrue",map);

        //操作list类型数据
        redisTemplate.opsForList().rightPushAll("arr","a","b","c","d");
        String arr = redisTemplate.opsForList().leftPop("arr");
        System.out.println(arr);

        //操作set类型
        redisTemplate.opsForSet().add("num","10","9","8");
        List<String> num = redisTemplate.opsForSet().pop("num", 2);
        for (String s : num) {
            System.out.println(s);
        }

        //操作zset
        redisTemplate.opsForZSet().add("z","java",80);
        redisTemplate.opsForZSet().add("z","java77",87);
        redisTemplate.opsForZSet().add("z","java56",99);
        Set<String> range = redisTemplate.opsForZSet().rangeByScore("z", 60, 100);
        for (String s : range) {
            System.out.println(s);
        }

        Set<String> keys = redisTemplate.keys("*");
        for (String s : keys) {
            System.out.println(s);
        }
    }
相关推荐
踏着七彩祥云的小丑4 小时前
忘记Redis是否安装过时查看
数据库·redis·缓存
☆凡尘清心☆6 小时前
CentOS Stream 9 Redis 7.2.7 源码编译一键安装脚本
linux·运维·redis·centos
麻瓜code9 小时前
【Redis 】数据类型、持久化与过期删除
数据库·redis·缓存
☆凡尘清心☆9 小时前
CentOS Stream 9 编译安装 Redis 7.2.7 完整版详细步骤
linux·redis·centos
晚安code10 小时前
RocketMQ 延迟消息实战:延迟双删策略解决 Redis 缓存一致性
redis·rocketmq
山城码农笑松哥10 小时前
Redis 8.8 新特性实操笔记:原生数组、INCREX 限流、XNACK 流处理
redis·笔记·junit
changshuaihua00111 小时前
Redis 详解:Redis 与 MySQL 的区别、数据类型与缓存三大问题
数据库·redis·缓存
随风M记忆s12 小时前
Redis支持事务吗?了解Redis的持久化机制吗?
数据库·redis·bootstrap
独行侠影a1 天前
APScheduler+Redis 分布式定时任务:解决多实例任务重复执行
数据库·redis·分布式