Redis全面解析

1 篇文章 0 订阅

目录

一.Redis概述

1.概述

2.官网

二.Redis安装配置和启动

1.安装c语言需要的gcc环境

2.下载并解压缩 Redis 源码压缩包

3.编译 Redis 源码

4.启动redis

三.Redis数据类型

1.redis中key的设计规范

2.string类型

 3.hash类型(散列表key field value)

4.list类型(列表)

 5.set类型(无序集合)

6.sortedset类型(有序集合)

四.Redis持久化

1.RDB

2.AOF

3.持久化优先级

4.redis的rdb bgsave存储的数据是拷贝给子进程的吗?

5.redis持久化最佳实践

五.Redis过期删除策略

1.设置redis键的过期时间 【EXPIRE key 过期时间】

2.查询redis键的过期时间【TTL key】

3.删除键的过期时间【PERSIST key】

4.过期删除策略

六.Reids实现分布式锁

1.引入依赖

2.配置文件

3.代码示例(推荐使用redisson)

七.Redis使用lua脚本

1.redis使用lua的方式

2.EVAL命令

3.Lua脚本汇总调用redis命令

4.Linux命令行使用lua脚本

八.布隆过滤器

1.redis安装布隆过滤器

2.使用

九.RedisTemplate常用语法

十.Redis的经典面试题

1.缓存穿透

2.缓存击穿

3.缓存雪崩


一.Redis概述

1.概述

        Redis是一个使用C语言开发的内存数据库,Redis 除了做缓存之外,也经常用来做分布式锁。Redis提供了多种数据类型来支持不同的业务场景,还支持事务、持久化、Lua脚本、多种集群方案。

2.官网

(1)官网:https://redis.io/

(2)中文官网:http://www.redis.cn/

二.Redis安装配置和启动

1.安装c语言需要的gcc环境

yum install -y gcc-c++
yum install -y wget

2.下载并解压缩 Redis 源码压缩包

wget https://download.redis.io/releases/redis-6.2.4.tar.gz
mkdir /usr/local/redis
tar -zxvf redis-6.2.4.tar.gz -C /usr/local/redis

3.编译 Redis 源码

cd /usr/local/redis/redis-6.2.4/src
make && make install

执行完毕后redis就安装成功啦!

4.启动redis

不推荐直接前台启动,窗口关闭redis也随之关闭了下面介绍后台启动redis

后台启动步骤(守护进程):

(1)修改redis.conf文件

<1>修改daemonize no ---> daemonize yes,目的是为了让redis启动在linux后台运行

 <2>修改redis的工作目录:(名称随意)

<3>修改bind地址(不修改可能别的机器连接不上)

ip改为给本机分配的ip

(2)进入bin目录启动服务(如果是src目录重命名为bin)

redis-server ../redis.conf

(3)查看redis是否启动成功

ps -ef|grep redis

(4)后台启动的关闭方式

进入bin目录执行以下命令

redis-cli shutdown

(5)bin目录下命令说明

redis-server :启动 redis 服务
redis-cli :进入 redis 命令客户端
redis-benchmark : 性能测试的工具
redis-check-aof : aof 文件进行检查的工具
redis-check-dump : rdb 文件进行检查的工具
redis-sentinel : 启动哨兵监控服务

(6)命令行方式开启客户端

redis-cli -h 127.0.0.1 -p 6379

-h:redis服务器的ip地址
-p:redis实例的端口号

三.Redis数据类型

1.redis中key的设计规范

(1)可读性

        以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如 业务名:表名:id

(2)简洁性

        <1>保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:

        <2>不要包含特殊字符,比如空格、换行、单双引号以及其他转义字符。

(3)避免bigkey

        <1>为了避免String类型的bigkey,尽量把String类型的大小控制在10KB以下。

        <2>为了避免集合类型的bigkey,对应的设计规范是,尽量把集合类型的元素个数控制在1万以下。

2.string类型

(1)介绍

        string 数据结构是简单的 key-value 类型。

(2)常用命令

        set,get,strlen,exists,decr,incr,setnx

(3)一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。

(4)string类型实际应用

单值缓存

set key value

get key

对象缓存

set user:1 value(json格式数据)  【使用 表名:id 作为key】

eg:set user:1 '{"name":"zs","age":23}'

分布式锁

SETNX product:10001 true          // 返回1代表获取锁成功
SETNX product:10001 false         // 返回0代表获取锁失败
.......执行业务操作
DEL product:10001                       // 执行完业务 释放锁
SET product:10001 true ex 10 nx // 防止程序意外终止导致死锁

计数器

INCR article:readcount:101

 3.hash类型(散列表key field value)

(1)介绍

        hash是一个string类型的field和value的映射表,特别适合用于存储对象。比string类型存储对象更节省空间,对于内存和cpu的消耗也更小。

 (2)常用命令

        hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。

(3)应用场景

        系统中对象数据的存储。

(4)hash类型实际应用

对象操作

key:购物车:用户id
field:商品id
value:商品数量

<1>添加1件商品:hset cart:1001 10088 1
<2>增加1件商品:hincrby cart:1001 10088 1
<3>商品总数: hlen cart:1001
<4> 删除商品:hdel cart:1001 10088
<5>获取购物车所有商品: hgetall cart:1001

4.list类型(列表)

(1)介绍

        Redis的list的实现为一个双向链表

(2)常用命令

        rpush,lpop,lpush,rpop,lrange、llen 等。

(3)应用场景

        发布订阅或消息队列、慢查询,比如:微博、朋友圈、公众号等,关注的文章列表展示。

(4)list类型实际应用

公众号文章列表展示:key是msg:tangbb:id  value是文章id

lpush msg:tangbb:001 10010 从左边存入id为10010的文章
lpush msg:tangbb:001 10011 从左边存入id为10011的文章
lrange msg:tangbb:001 0 1     从左边查询最新的数据start为0end为1

 5.set类型(无序集合)

(1)介绍

        redis中set类似于java中的Hashset,当你想存储一个不重复的列表数据,set是一个很好的选择,并且set可以轻易的实现并集、交集、差集的操作,set是无需的。

(2)常用命令

        sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。

(3)使用场景

         需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景。比如共同关注、共同粉丝、共同喜好等功能。

(4)set类型实际应用

微信抽奖小程序

<1>点击参与抽奖 讲参与者id加入集合  [sadd key value1 value2...]
sadd wxward 001 002 003 004 005
<2>查询参与者id  [smembers key]
smembers wxward
<3>抽出2名中奖者  [SRANDMEMBER  key count]  [SPOP key count]
SRANDMEMBER wxward 2   随机选择2个,不会移除数据
SPOP wxward 2   随机选择2个,并移除数据

并集交集差集

set1 [a,b,c]
set2 [b,c,d]
set3 [c,d,e]
交集:SINTER set1 set2 set3  [c]
并集:SUNION set1 set2 set3  [a,b,c,d,e]
差集:SDIFF set1 set2 set3  [a]
差集计算方式:set1 - (set2并set3)=[a,b,c]-[b,c,d,e] = [a] 只保留a中单独存在的元素
共同关注A的人:可以用交集来实现
我可能认识的人:可以使用差集来实现,把我关注的人求差集

6.sortedset类型(有序集合)

(1)介绍

        和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。

(2)常用命令

        zadd,zcard,zscore,zrange,zrevrange,zrem 等。

(3)应用场景

        需要对数据根据某个权重进行排序的场景。比如排行榜等。

(4)sortedset类型实际应用

百度热搜榜

<1>添加热搜数据 [ zadd key score value ]
        zadd hotnews 1 iPhone14或将采用叹号屏   
<2>点击新闻为其数值+1 [  zincrby key score value ]
        zincrby hotnews 1 iPhone14或将采用叹号屏
<3>展示当日排行前10[ zrevrange key start end]
        zrevrange hotnews 0 9

四.Redis持久化

1.RDB

(1)概念

        在指定的时间间隔能对你的数据进行快照存储,保存文件的后缀是 .rdb。

(2)RDB的触发

        <1> save和bgsave命令都可以生成rdb文件

save:会阻塞当前Redis服务器,直到RDB文件创建完毕为止,线上应该禁止使用。
bgsave:该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。

        <2> redis.conf配置自动触发规则

redis.conf:
# 时间策略
save 900 1 # 表示900 秒内如果至少有 1 个 key 的值变化,则触发RDB
save 300 10 # 表示300 秒内如果至少有 10 个 key 的值变化,则触发RDB
save 60 10000 # 表示60 秒内如果至少有 10000 个 key 的值变化,则触发RDB
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes

(3)RDB总结

1.执行效率高,适用于大规模数据的备份恢复。自动备份不会影响主线程工作。
2.备份的文件占用空间比AOF的小。
3.可能会造成部分数据丢失。
4.自动备份通过fork进程来执行备份操作,而fork进程会阻塞主进程。

2.AOF

(1)概念

        记录每次对服务器写的操作(命令),当服务器重启的时候会重新执行这些命令来恢复原始的数据,文件结尾是 .aof 。(默认不开启)

(2)AOF开启和触发

        开启配置redis.conf

# 可以通过修改redis.conf配置文件中的appendonly参数开启
appendonly yes
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的。
dir .
# 默认的文件名是appendonly.aof,可以通过appendfilename参数修改
appendfilename appendonly.aof

        触发规则配置redis.conf

#每执行一个命令保存一次 高消耗,最安全
#appendfsync always
#每一秒钟保存一次
appendfsync everysec
#只写入不保存, AOF或Redis关闭时执行,由操作系统触发刷新文件到磁盘
#appendfsync no

(3)AOF原理

        AOF执行步骤如下:

1.所有的写命令会追加到 AOF 缓冲中
2.AOF 缓冲区根据对应的策略向硬盘进行同步操作。
3.随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
4.当 Redis 重启时,可以加载 AOF 文件进行数据恢复。

3.持久化优先级

        如果既有RDB文件又有AOF文件优先加载谁?

        答:如果有AOF,优先加载AOF执行备份。如果没有AOF,加载RDB执行备份。

4.redis的rdb bgsave存储的数据是拷贝给子进程的吗?

答:不是拷贝给子进程,(内存使用率超过50%,直接拷贝不就gg了)
是通过写实复制实现的

5.redis持久化最佳实践

混合使用RDB和AOF,内存快照以一定的频率执行,在两次快照之间使用AOF记录这期间的所有命令操作。

五.Redis过期删除策略

1.设置redis键的过期时间 【EXPIRE key 过期时间】

(1)EXPIRE :表示将键 key 的生存时间设置为 ttl
(2)PEXPIRE :表示将键 key 的生存时间设置为 ttl 毫秒
(3)EXPIREAT :表示将键 key 的生存时间设置为 timestamp 所指定的数时间戳。
(4)PEXPIREAT :表示将键 key 的生存时间设置为 timestamp 所指定的毫秒数时间戳。

2.查询redis键的过期时间【TTL key】

(1)TTL :以秒的单位返回键 key 的剩余生存时间。
(2)PTTL :以毫秒的单位返回键 key 的剩余生存时间。

3.删除键的过期时间【PERSIST key】

        PERSIST :表示将key的过期时间移除。

4.过期删除策略

(1)定时删除:在设置某个key的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。

(2)惰性删除:设置该key过期时间后,当需要该key时,再检查其是否过期,如果过期就删掉它,反之返回该key。

(3)定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key。

六.Reids实现分布式锁

1.引入依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.8.2</version>
        </dependency>
    </dependencies>

2.配置文件

server:
  port: 8899
spring:
  redis:
    host: 127.0.0.1
    port: 6379

3.代码示例(推荐使用redisson)

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * redis使用分布式锁解决高并发下单超卖问题
 *
 * @author tangbb
 * @date 2022/9/7
 */
@RestController
@RequestMapping("/redis")
public class RedisLockController {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //商品编号作为key
    private String key = "iphone14:20220908";
    //定义持有锁的key
    private String lockKey = "iphone:lock";

    @PostConstruct
    public void init() {
        //初始化商品库存
        redisTemplate.opsForValue().set(key, "10");
    }

    /**
     * 方案一:基于setnx实现
     * 弊端:setnx 与 expire 不是原子操作
     *
     * @return
     */
    @RequestMapping("/redisLock")
    public String redisLock() {
        //1.获取锁
        Boolean lock_flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "1");
        //获取到锁执行业务操作,减库存
        if (lock_flag) {
            //2.设置锁的过期时间
            redisTemplate.expire(lockKey, 5, TimeUnit.SECONDS);
            try {
                //3.获取库存
                Integer count = Integer.valueOf(redisTemplate.opsForValue().get(key));
                if (count > 0) {
                    //4.扣减库存
                    redisTemplate.opsForValue().set(key, String.valueOf(count - 1));
                    System.out.println("我抢到iPhone14啦");
                    return "success";
                } else {
                    return "failed";
                }
            } catch (NumberFormatException e) {
                e.printStackTrace();
            } finally {
                //5.释放锁
                redisTemplate.delete(lockKey);
            }
        }
        //未获取到锁,直接返回
        return "do not get lock";
    }

    /**
     * 方案二:基于lua脚本实现
     * 弊端:如果  业务处理耗时 > 锁的超时时间   另一个线程就会进来导致超卖
     *
     * @return
     */
    @RequestMapping("/luaLock")
    public String luaLock() {
        //1.使用lua脚本 获取锁、设置锁的超时时间
        String clientId = UUID.randomUUID().toString() + Thread.currentThread().getId();
        String lockAndExpireLua = "" +
                "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) ; return true " +
                "else return false " +
                "end";
        Boolean lock_flag = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                Boolean islock = redisConnection.eval(
                        lockAndExpireLua.getBytes(),
                        ReturnType.BOOLEAN,
                        1,
                        lockKey.getBytes(),
                        clientId.getBytes(),
                        "5".getBytes()
                );
                return islock;
            }
        });
        //2.获取到锁执行业务操作
        if (lock_flag) {
            //3.获取库存
            try {
                Integer count = Integer.valueOf(redisTemplate.opsForValue().get(key));
                if (count > 0) {
                    //4.扣减库存
                    redisTemplate.opsForValue().set(key, String.valueOf(count - 1));
                    System.out.println("我抢到iPhone14啦");
                    return "success";
                } else {
                    return "false";
                }
            } catch (NumberFormatException e) {
                e.printStackTrace();
            } finally {
                //5.使用lua脚本保证锁一定会被释放
                String unlockLua = "" +
                        "if redis.call('get',KEYS[1]) == ARGV[1] then redis.call('del',KEYS[1]); return true " +
                        "else return false " +
                        "end";
                Boolean unlock_flag = redisTemplate.execute(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                        Boolean unlock = redisConnection.eval(
                                unlockLua.getBytes(),
                                ReturnType.BOOLEAN,
                                1,
                                lockKey.getBytes(),
                                clientId.getBytes()
                        );
                        return unlock;
                    }
                });
                if (unlock_flag) {
                    System.out.println(clientId + "的锁已释放");
                }
            }
        }
        //未获取到锁,直接返回
        return "do not get lock";
    }

    /**
     * 方案三:锁续期
     * 给拿到锁的线程创建一个守护线程(看门狗),守护线程定时/延迟 判断拿到锁的线程是否还继续持有锁,如果持有则为其续期
     *
     * @return
     */
    //创建守护线程池(看门狗)
    ScheduledExecutorService watchDogExecutorService;
    //创建队列
    ConcurrentSkipListSet<String> setQueue = new ConcurrentSkipListSet<String>();

    @PostConstruct
    public void init2() {
        watchDogExecutorService = Executors.newScheduledThreadPool(1);
        //编写续期的lua
        String expireLockLua = "" +
                "if redis.call('get',KEYS[1]) == ARGV[1] then redis.call('expire',KEYS[1],ARGV[2]) ; return true " +
                "else return false " +
                "end";
        watchDogExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Iterator<String> iterator = setQueue.iterator();
                while (iterator.hasNext()) {
                    String clientId = iterator.next();
                    Boolean newFlag = redisTemplate.execute(new RedisCallback<Boolean>() {
                        @Override
                        public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                            Boolean flag = null;
                            try {
                                flag = redisConnection.eval(
                                        expireLockLua.getBytes(),
                                        ReturnType.BOOLEAN,
                                        1,
                                        lockKey.getBytes(),
                                        clientId.getBytes(),
                                        "5".getBytes()
                                );
                            } catch (Exception e) {
                                e.printStackTrace();
                                System.out.println("锁续期失败");
                            }
                            return flag;
                        }
                    });
                }
            }
        }, 0, 1, TimeUnit.SECONDS);
    }

    @RequestMapping("/expireLock")
    public String expireLock() {
        //1.使用lua脚本 获取锁、设置锁的超时时间
        String clientId = UUID.randomUUID().toString() + Thread.currentThread().getId();
        String lockAndExpireLua = "" +
                "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) ; return true " +
                "else return false " +
                "end";
        Boolean lock_flag = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                Boolean islock = redisConnection.eval(
                        lockAndExpireLua.getBytes(),
                        ReturnType.BOOLEAN,
                        1,
                        lockKey.getBytes(),
                        clientId.getBytes(),
                        "5".getBytes()
                );
                return islock;
            }
        });
        //2.获取到锁执行业务操作
        if (lock_flag) {
            /**
             * 关键步骤:获取锁成功后让守护线程为其续期
             */
            setQueue.add(clientId);
            //3.获取库存
            try {
                Integer count = Integer.valueOf(redisTemplate.opsForValue().get(key));
                if (count > 0) {
                    //4.扣减库存
                    redisTemplate.opsForValue().set(key, String.valueOf(count - 1));
                    //模拟业务超时(可以保证只有一个线程持有锁,其他线程需要等他释放锁后才能抢到)
                    TimeUnit.SECONDS.sleep(10);
                    System.out.println("我抢到iPhone14啦");
                    return "success";
                } else {
                    return "false";
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                /**
                 * 关键步骤:解除锁续期
                 */
                setQueue.remove(clientId);
                //5.使用lua脚本保证锁一定会被释放
                String unlockLua = "" +
                        "if redis.call('get',KEYS[1]) == ARGV[1] then redis.call('del',KEYS[1]); return true " +
                        "else return false " +
                        "end";
                Boolean unlock_flag = redisTemplate.execute(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                        Boolean unlock = redisConnection.eval(
                                unlockLua.getBytes(),
                                ReturnType.BOOLEAN,
                                1,
                                lockKey.getBytes(),
                                clientId.getBytes()
                        );
                        return unlock;
                    }
                });
                if (unlock_flag) {
                    System.out.println(clientId + "的锁已释放");
                }
            }
        }
        //未获取到锁,直接返回
        return "do not get lock";
    }

    /**
     * 方案四:redisson实现
     * Redisson内置了一系列的分布式对象,分布式集合,分布式锁,分布式服务等诸多功能特性。
     */
    @Value("${spring.redis.host}")
    String host;
    @Value("${spring.redis.port}")
    String port;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port);
        return Redisson.create(config);
    }

    @Autowired
    RedissonClient redissonClient;

    @GetMapping("/redissonLock")
    public String redissonLock() {
        //1.获取锁
        RLock lock = redissonClient.getLock(lockKey);
        //2.加锁
        lock.lock();
        try {
            Integer count = Integer.parseInt(redisTemplate.opsForValue().get(key));
            if (count > 0) {
                //3.减库存
                redisTemplate.opsForValue().set(key, String.valueOf(count - 1));
                //模拟业务耗时
                TimeUnit.SECONDS.sleep(1);
                System.out.println("我抢到iPhone14啦");
                return "success";
            } else {
                return "failed";
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.释放锁
            lock.unlock();
        }
        return "do not get lock";
    }
}

七.Redis使用lua脚本

1.redis使用lua的方式

使用EVAL命令对lua脚本进行求值

2.EVAL命令

(1)格式

EVAL script numkeys key [key ...] arg [arg ...]

(2)参数解释

script :参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数
numkeys : 用于指定键名参数的个数。
key [key ...] ,是要操作的键,可以指定多个,在lua脚本中通过 KEYS[1] , KEYS[2] 获取
arg [arg ...] ,附加参数,在lua脚本中通过 ARGV[1] , ARGV[2] 获取。

(3)EVAL实例

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

3.Lua脚本汇总调用redis命令

(1)redis.call(),返回值就是redis命令执行的返回值,如果出错,则返回错误信息,不继续执行

(2)redis.pcall(),返回值就是redis命令执行的返回值,如果出错,则记录错误信息,继续执行

实例

eval "return redis.call('set',KEYS[1],ARGV[1])" 1 n1 zhaoyun

4.Linux命令行使用lua脚本

(1)格式

redis-cli --eval lua_file key1 key2 , arg1 arg2 arg3

eval 后面参数是lua脚本文件, .lua 后缀。

不用写 numkeys ,而是使用 , 隔开。注意 , 前后有空格。

(2)实例:使用lua脚本实现将一个数字原子性乘以N倍,且运行过程不会被其他客户端打断。

新建test.lua文件,上传到redis的bin目录下(其他目录也可以,记住就行)

local num = redis.call('get', KEYS[1]);
if not num then
	return 0;
else
	local res = num * ARGV[1];
	redis.call('set',KEYS[1], res);
	return res;
end
[root@master bin]# redis-cli -h 192.168.10.100 -p 6379 --eval test.lua test , 8
(integer) 0
[root@master bin]# redis-cli -h 192.168.10.100 -p 6379 --eval test.lua test , 8
(integer) 8
[root@master bin]# redis-cli -h 192.168.10.100 -p 6379 --eval test.lua test , 8
(integer) 64
[root@master bin]# redis-cli -h 192.168.10.100 -p 6379 --eval test.lua test , 8
(integer) 512
[root@master bin]# 

八.布隆过滤器

1.redis安装布隆过滤器

插件形式安装

redis布隆过滤器插件地址下载最新的release源码,在编译服务器进行解压编译

 wget https://github.com/RedisBloom/RedisBloom/archive/v2.2.4.tar.gz

解压插件进行插件的编译

tar -xvzf RedisBloom-2.2.4.tar.gz
cd RedisBloom-2.2.4
make

编译得到动态库rebloom.so 启动redis时,如下启动即可加载bloom filter插件

配置文件形式配置

#在redis配置文件(redis.conf)中加入该模块即可
vim redis.conf
#添加
loadmodule /root/bloom/redisbloom-2.2.4/rebloom.so (前面为你自己的路径)

启动命令挂载

redis-server redis.conf --loadmodule /usr/rebloom/rebloom.so INITIAL_SIZE 1000000   ERROR_RATE 0.0001   
#容量100万, 容错率万分之一, 占用空间是4m

2.使用

        1.通过redisson实现布隆过滤器代码示例如下(存在返回ture,不存在返回false)

        布隆过滤器判断存在,则有可能存在

        布隆过滤器判断不存在,则一定不存在

public class RedissonBloomFilter {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
//        config.useSingleServer().setPassword("1234");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
        //初始化布隆过滤器:预计元素为100000000L,偏差率为3%
        bloomFilter.tryInit(100000000L,0.03);
        //将号码10086插入到布隆过滤器中
        bloomFilter.add("10086");
        //判断下面号码是否在布隆过滤器中
        //输出false
        System.out.println(bloomFilter.contains("123456"));
        //输出true
        System.out.println(bloomFilter.contains("10086"));
    }
}

九.RedisTemplate常用语法

1.redisTemplate.opsForValue();//操作字符串
2.redisTemplate.opsForHash();//操作hash
3.redisTemplate.opsForList();//操作list
4.redisTemplate.opsForSet();//操作set
5.redisTemplate.opsForZSet();//操作有序set

stringRedisTemplate.opsForValue().set("keyname","value");
stringRedisTemplate.opsForValue().get("keyname");

十.Redis的经典面试题

1.缓存穿透

(1)概念

        缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,造成数据库的压力倍增的情况。

(2)解决方案

         对于数据库中没有的数据我们设置一个默认值,比如id为-1时,我们仍将空值设置到缓存,并给其一个失效时间。

2.缓存击穿

(1)概念

        redis的一个key过期后,有大量用户请求这个key的数据,导致这些请求去访问数据库,造成数据库压力倍增,针对一个key而言。

(2)解决方案

        设置热点key永不过期。

3.缓存雪崩

(1)概念

         redis中多个key同时过期,这时有大量用户请求这些key的数据,导致这些请求去访问数据库,造成数据库压力倍增,针对多个key而言。

(2)解决方案

         将每个key的失效时间加一个随机值

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr Tang

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值