Redis


1. Redis的简介

  • Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库高速缓存消息队列代理

  • 它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型

  • 内置复制、Lua 脚本、LRU 收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel 提供高可用,通过Redis Cluster 提供自动分区。

  • 简言之,Redis是一种面向“键/值”对数据类型的内存数据库,可以满足我们对海量数据的快速读写需求

2. Redis的特点

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
  • Redis不仅仅支持简单的k-v类型的数据,同时还提供list,set,zset,hash等数据结构的存储
  • Redis支持数据的备份,即 master-slave 主从模式的数据备份

3. Redis的优势

  • 性能极高——Redis读的速度为11w/s,写的速度为8.1w/s
  • 丰富的数据类型 (Strings,Lists,Hashes,Sets即Ordered Sets )
  • 原子性——Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行
  • 丰富的特性——Redis还支持publish/subscribe,通知,key过期等特性。

4. 常用的NoSql数据库

  • hbase

    • 存储海量的数据(数十亿行,百万列)
  • redis

    • 基于内存的nosql数据库
    • 一般使用redis做缓存或者队列
  • mogodb

    • 在结构化数据比较简单的情况下,可以使用mogodb代替mysql,作为一个高性能的数据库

5. Redis的安装

5.1 Redis单机版的安装

  • 准备源码安装包

    redis-3.2.0.tar.gz

  • 解压

    1
    tar -zxvf redis-3.2.0.tar.gz -C /opt/
  • 重命名

    1
    mv redis-3.2.0/ redis
  • 安装gcc编译器

    1
    yum -y install gcc  make automake autoconf libtool  libatomic libatomic_ops-devel
  • 链接库文件(arm版本以外可以忽略这一步)

    1
    2
    #只在arm64的架构中会出现atomic库找不到的问题,x86_64不会有此问题
    ln -s /usr/lib64/libatomic.so.1.2.0 /usr/lib/libatomic.so
  • 编译安装源码

    1
    make && make install

    注意:编译安装完成之后会在src目录下出现redis的相关命令

  • 修改redis.conf的配置项

    1
    2
    3
    bind 0.0.0.0   (默认是127.0.0.1,这是本地回环地址,只能本地应用程序之间进行通信)
    daemonize yes(后台运行)
    logfile /opt/redis/logs/redis.log(日志文件,目录必须存在)
  • 启动Redis-Server

    1
    src/redis-server redis.conf
  • 启动redis客户端

    1
    src/redis-cli  -h node1 -p 6379

5.2 Redis集群的安装

1
2
3
node1  master
node2 slave
node3 slave
  • 把前面安装好的单机版的redis拷贝到node2 node3
  • 修改redis.conf
1
bind  nodexxxxx
  • 在从节点(node2 node3) redis.conf 末尾追加

1
slaveof node1 6379
1
#slave-read-only yes		#默认已经有这个配置了,redis的默认配置就是从节点只读不写	

ps:注意启动集群时尽量先启动主节点再启动从节点

6. Redis中配置的查看

Redis的配置文件位于Redis的安装目录之下,文件名为redis.conf。可以通过config命令来查看或设置配置项

config get confName: 获取配置的值

config set confName confVal: 设置配置的值

1
使用config配置的模式,只是在当前的会话中有效,如果redis重新启动则失效了

7. Redis中的数据类型

  • string(字符串)
  • hash(哈希)
  • list(列表)
  • set(集合)
  • zset(sorted set)有序的集合

8. 数据类型之String(字符串)

  • string是redis中最基本的数据类型

  • 一个key对应一个value

  • string类型的value是基于二进制存储的,所以完全可以存储图片和影音等二进制数据

  • string类型的value大小最大512mb

  • 存储数据: set name value

  • 一次性存储多个数据 mset name admin age 20 sex nan

  • 读取数据: get name

  • 删除redis中的key说对应的value: del key

  • 设置多个值

    1
    MSET key1 value1  key2 value2 key3 value3...
  • 计数器操作

    1
    2
    3
    4
    5
    set mage 20  #添加一个value
    incr mage #自增1
    decr mage #自减1
    incrby mage 100 #自增指定的值
    decrby mage 55 #自减指定的值
  • 添加具有过期时间的键值对

    1
    2
    setex  age1 5 20  # 5s的过期时间 
    ttl key # 查看redis中的过期时间,-1代表永久有效
  • 给 value 追加字符串

    1
    APPEND age1 hello
  • 单独给某个key对应的值设置过期时间

    1
    expire key seconds  #给某个key对应的value设置过期时间

9. 数据类型之Hash(哈希)

  • Redis hash 是一个键值对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象 object

  • 存储一个数据: HSET KEY fieldName value

  • 获取数据: HGET KEY fieldName

  • 设置多个数据: HMSET info sex nan address gansu

  • 获取多个数据: HMGET info sex address

  • 删除hash中的多个个键值对: HDEL info admin ...

  • 删除redis中的key说对应的value: del key

  • 获取指定的key的全部键值对 HGETALL info

  • 获取指定的key对应的元素的个数: HLEN info

10. 数据类型之List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

  • 首部添加数据: LPUSHloves java c++ python..
  • 尾部添加元素: RPUSH linux hadoop..
  • 查看数据: lrange loves 0 10000
  • 首部移除元素: LPOP loves
  • 尾部移除元素: RPOP loves

列表最多可存储 2^32-1元素 (4294967295, 每个列表可存储40多亿)

11. 数据类型之Set(集合)

Redis的Set是string类型的无序集合。

  • 集合中添加元素: SADD key member member member ...

  • 获取集合中所有的元素: SMEMBERS loves

  • 集合中的元素是不能重复,如果添加的 value 已经存在在 redis 中则返回0,不存在则返回1

  • 判断元素是否在集合中: SISMEMBER loves java

  • 删除集合中指定的元素: SREM loves java

  • 获取集合中元素的个数 : SCARD loves

  • 随机获取集合中的元素: SRANDMEMBER loves 2 ;随机从集合中取出两个元素

  • 随机移除元素: spop loves

  • 差集,返回在第一个set里面而不在后面任何一个set里面的项: sdiff love1 love2

  • 交集,返回多个set里面都有的项: sinter loves1 loves2

  • sinterstore:交集并保留结果。格式是:sinterstore 存放结果的key 多个set的key

  • sunion:并集。格式是:sunion 多个set的key

  • sunionstore:并集并保留结果。格式是:sunionstore 存放结果的set的key 多个set的key

12. 数据类型之ZSet(有序集合)

  • Redis zset和set一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序

  • zset的成员是唯一的,但分数(score)却可以重复

  • 给zset添加数据: ZADD loves 5 java

  • 获取固定得分范围的数据: ZRANGEBYSCORE loves 1 20

  • 查看zset集合的成员个数 ZCARD mscore

  • 获取指定值的分数 ZSCORE mscore java

13. Redis中的常用命令

13.1 键值对相关

  • keys * 取出当前所有的key

  • exists name 查看redis是否有name这个key

  • del name 删除key name

  • expire confirm 100 设置confirm这个key100秒过期

  • ttl confirm 获取confirm 这个key的有效时长

  • persist confirm 移除confirm这个key的过期时间

  • select 0 (在redis中总共有16个数据库,默认是0号数据库)

  • move confirm 1 将当前数据库中的key移动到其他的数据库中

  • randomkey 随机返回数据库里面的一个key

  • rename key2 key3 重命名key2 为key3

  • type key2 返回key的数据类型

  • SHUTDOWN 关闭redis数据库

13.2 服务器相关

  • select 0~15 编号的数据库
  • quit /exit 退出客户端
  • dbsize 返回当前数据库中所有key的数量
  • info 返回redis的相关信息
  • flushdb 删除当前选择数据库中的所有key
  • flushall 删除所有数据库中的数据

14. Redis中的API操作

  • pom依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    </dependency>
  • 常用操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package com.uplooking.bigdata.redis;

    import org.junit.Test;
    import redis.clients.jedis.Jedis;

    public class RedisTest {
    @Test
    public void testString() {
    //创建Jedis
    Jedis jedis = new Jedis("uplooking03");
    jedis.select(0);
    jedis.set("name", "value");
    System.out.println(jedis.get("name"));
    }


    @Test
    public void testHash() {
    //创建Jedis
    Jedis jedis = new Jedis("uplooking03");
    jedis.select(0);
    jedis.hset("info", "name", "admin");
    jedis.hset("info", "age", 12 + "");
    System.out.println(jedis.hget("info", "name"));
    }


    @Test
    public void testList() {
    //创建Jedis
    Jedis jedis = new Jedis("uplooking03");
    jedis.select(0);
    jedis.rpush("loves", "java", "c++", "python");
    jedis.lpop("loves");
    System.out.println(jedis.lrange("loves", 0, 10000));
    System.out.println(jedis.rpop("loves"));
    System.out.println(jedis.lrange("loves", 0, 10000));
    }
    }

15. Redis设置密码

1
requirepass root  #配置密码为root
1
2
3
登录redis:
redis-cli -h node1
auth root

16. Redis可视化工具

1
RedisDesktopManager

17. SpringBoot集成Redis

  • 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    </parent>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    </dependencies>
    </project>
  • 注入redisTemplate

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.up;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;

    @SpringBootApplication
    public class Application implements CommandLineRunner {

    public static void main(String[] args) {
    SpringApplication.run(Application.class);
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void run(String... args) throws Exception {
    // redisTemplate.opsForValue().set("name123", "xiaohua");
    // redisTemplate.opsForList().leftPush("loves", "java");
    redisTemplate.opsForHash().put("score", "java", String.valueOf(30));
    }
    }

18. Redis的持久化策略

reids是一个key-value存储系统,为了保证效率,缓存在内存中,但是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,以保证数据的持久化;

所以:redis是一个支持持久化的内存数据库,可以将内存中的数据同步到磁盘保证持久化;

Redis的持久化策略:2种

1
2
rdb: rdb数据快照形式,是直接把内存中的数据保存到一个dump文件中,定时保存;
aof:把所有的对redis的服务器进行'写操作'的命令都存到一个文件里,命令的集合;

默认redis只开启的是rdb策略,我们可以手动开启aof策略,当开启aof策略时,服务器启动时优先使用aof策略;

rdb策略

原理: 当redis需要做持久化时,redis会fork一个子进程;子进程将数据写到磁盘上一个临时RDB文件中;当子进程完成写临时文件后,将原来的RDB替换掉,这样的好处就是可以copy-on-write(写时复制技术)

redis.conf配置:

1
2
3
save 900 1 
save 300 10
save 60 10000

默认是如上配置:

1
2
3
4
5
900秒之内,如果超过1个key被修改,则发起快照保存;
300秒内,如果超过10个key被修改,则发起快照保存
1分钟之内,如果1万个key被修改,则发起快照保存

注意:以上的配置只要触发任意一个操作都会执行备份rdb快照的操作 'BGSAVE'

这种方式不能完全保证数据持久化,因为是定时保存,所以当redis服务down掉,就会丢失一部分数据,而且数据量大,写操作多的情况下,会引起大量的磁盘IO操作,会影响性能;

所以,如果这两种方式同时开启,如果对数据进行恢复,不应该用rdb持久化方式对数据库进行恢复

AOF 策略

AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集,它可以实现每次操作都持久化;

AOF 文件中的命令全部以 redis 协议的格式来保存,新命令会被追加到文件的末尾。 redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。redis 还可以同时使用 AOF 持久化和 RDB 持久化。在这种情况下,当 redis 重启时,它会优先使用AOF文件来还原数据集, 因为 AOF 文件保存的数据集通常比RDB文件所保存的数据集更完整。也可以关闭持久化功能,让数据只在服务器运行时存在。

配置方式:启动aof持久化的方式

1
appendonly yes 

AOF文件刷新的方式,有三种

appendfsync everysec

1
2
3
4
appendfsync always:每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但速度也是最慢的,一般不推荐使用
appendfsync everysec:每秒钟都调用fsync刷新到AOF文件,很快,但可能会丢失一秒以内的数据;(推荐)
appendfsync no:依靠OS进行刷新(一般30s左右一次刷新),redis不主动刷新AOF,但安全性就差。
默认并推荐每秒刷新,这样在速度和安全上都做到了兼顾。

redis 持久化策略优先级

AOF优先于RDB
RDB性能优于AOF,因为里面没有重复
Redis一次性将数据加载到内存中,一次性预热

生产环境的备份

为以防万一(机器坏掉或磁盘坏掉),最好定期把使用filesnapshotting 或 Append-only 生成的 rdb .aof 文件备份到远程机器上,然后可以用crontab定时(比如每半小时)scp一次。如果没有使用redis的主从功能 ,半小时备份一次应该是可以了;并且如果应用数据量不大的话,可以单机部署,做主从有点浪费。具体还是要根据应用而定。

生产环境恢复数据

  1. 确保redis是未启动状态
  2. 把备份的rdb或者aof文件放入redis的安装路径 /opt/redis
  3. 启动redis,会主动加载rdb或者aof备份文件,加载哪个文件取决于是否开启aof,没开启默认加载rdb,开启了优先加载aof;

19. redis 的雪崩、穿透和击穿

redis 缓存雪崩

所谓的 redis 缓存雪崩指的是:大量的应用请求无法在 Redis 缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。redis 中大量 key 集中过期或者 redis 服务器宕机,从而导致大量请求从数据库获取数据,导致数据库服务器访问压力过大。

解决方式:

  1. 若是由于大量 key 过期所造成的,可以给 key 的 ttl( Time To Live) 设置一个过期时间
  2. 若是因为 redis 服务器宕机所导致的,可以搭建 redis 集群,保证高可用
  3. 可以从请求量层面进行解决,对缓存业务添加限流和服务降级策略
  4. 可以添加多级缓存,比如说 nginx 缓存

redis 缓存击穿

所谓的 redis 缓存击穿指的是:热点 key 的过期,从而导致大量访问热点 key 的请求访问数据库,从而导致数据库压力过大。key 中对应数据存在,当 key 中对应的数据在缓存中过期,而此时又有大量请求访问该数据,缓存中过期了,请求会直接访问数据库并回设到缓存中,高并发访问数据库会导致数据库崩溃。

解决方式:热点 key 通常是我们通过后台进行批量添加的,比如秒杀活动,解决热点 key 失效,可以重建缓存

  1. 互斥锁实现:只允许一个线程对 redis 缓存进行重建,其他线程处于等待状态,可以通过 redis 当中的 setnx 实现。缺点是串行化执行,效率低。优点是一致性高。
  2. 逻辑过期:给需要缓存的数据添加一个逻辑过期字段,通过对逻辑过期字段的判断,判断数据有无过期,如果过期则开启一个线程进行缓存重建,并且返回之前的数据。缺点是数据的一致性低,优点是相应速度快。
  3. 在缓存访问非常频繁的热点数据时,不要设置过期时间

redis 缓存穿透

所谓的 redis 缓存穿透指的是:redis 当中没有数据,数据库当中也没有数据,请求每次都是访问数据库,而数据库有没有数据返回。这样一来,缓存也就成了“摆设”,如果应用持续有大量请求访问数据,就会同时给缓存和数据库带来巨大压力

解决方式:

  1. 缓存空值。缺点是有点浪费内存空间,如果这样的请求过多,会导致redis当中有大量这种无用缓存,可以给其设置一个ttl。
  2. 使用布隆过滤器。布隆过滤器原理是一个二进制的数组,存储的是0或1,在添加元素时进行多次hash运算,得到多个0或1,存储到相应位置。
  3. 热点参数的限流降级
  4. 进行权限校验
  5. 做好数据格式的校验

文章作者: Yang Shiyu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yang Shiyu !
  目录