1. 主要问题

1.1 什么是redis?

答:redis 是一个远程内存的非关系型数据库。

1.2 redis能够存储什么数据?存储量如何?

答:string 、list、hash、set、zset;

redis官网(https://redis.io/topics/data-types),一个String类型的value最大可以存储512M;

redis 4g redis 4g内存可以存储多少数据_redis面试题


list、hash、set、zset元素个数最多为2^32-1个,也就是4294967295个。注意: jedis 的源码,支持String 类型的value 存储1G。

redis 4g redis 4g内存可以存储多少数据_应用场景_02

1.3 redis的主要优点?redis为什么快?

答:速度快;因为他是内存数据库,直接在内存中处理请求。

1.4 redis的缺点?如何解决?

答:1)内存存储,不在硬盘中;
解决办法
采用两种方式进行持久化(同步数据SNAPSHOT、同步命令AOF);
2)redis服务器单片处理数据量有限;
解决办法:
采用服务器集群、数据分片、数据主从复制,可以解决,些技术此处只做该要介绍,具体实现方式参考《redis-in-action》一书;

1.5 redis 能不能用作一般网络系统的主数据库?存在有那些问题?如何解决?

答:可以,需要考虑数据持久化、服务器集群、数据分片等问题,具体操作见下;

2. 应用场景和实现

2.1 分页查询

/**
     * 代码清单1-8
     * 1。 获取分页文章的ids;
     * 2。 获取分页文章的所有信息;
     *
     * @param conn redis 链接
     * @param page 查询文章的页码
     * @param order 文章分组数据zset 的key
     * @return 分页文章的查询结果
     */
    public List<Map<String, String>> getArticles(Jedis conn, int page, String order) {

        int start = (page - 1) * ARTICLES_PER_PAGE;
        int end = start + ARTICLES_PER_PAGE - 1;

        // 获取分页文章的ID;
        Set<String> ids = conn.zrevrange(order, start, end);
        List<Map<String, String>> articles = new ArrayList<Map<String, String>>();
        for (String id : ids) {
            // 根据文章ID,获取文章的所有信息;
            Map<String, String> articleData = conn.hgetAll(id);
            articleData.put("id", id);
            articles.add(articleData);
        }

        return articles;
    }

    /**
     * 代码清单1-10
     * 1。 将文章加入分组的zset中;
     * 2。 获取zset中的分页数据;
     *
     * @param conn redis 链接
     * @param group 指定分组名称
     * @param page 页码数
     * @param order 文章zset
     * @return 分组、分页后的所有文章
     */
    public List<Map<String, String>> getGroupArticles(Jedis conn, String group, int page, String order) {

        // 构建文章分组的键名
        String key = order + group;
        if (!conn.exists(key)) {
            ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX);

            // 将group:group、order中的文章,取最大值,加入key中
            conn.zinterstore(key, params, "group:" + group, order);
            conn.expire(key, 60);

        }
        return getArticles(conn, page, key);
    }

2.2 分布式锁

2.3 数据持久化

redis将数据持久化到硬盘有两种方法:

方法一: 快照(snapshotting),将某一时刻的所有数据都写入硬盘(dump.rdb快照文件);
save 60 1000                   // 60s内发生1000次写入触发bgsave,创建快照,
stop-writes-on-bgsave-error no // 发生错误是否停止写入
rdbcompression yes             // 是否压缩
dbfilename dump.rdb            // 快照文件名称

快照的其他命令:
SAVE:创建快照。接到save命令的redis服务器在快照创建完成之前不会相应任何其他命令。
BGSAVE:创建快照。redis服务器会创建一个子进程,在快照创建完成之前父进程继续处理其他命令。
SHUTDOWN:关闭服务器。redis会马上执行SAVE阻塞其他命令,在SAVE完成后关闭服务器。
SYNC:一个redis服务器向另一个redis服务器发送SNYC,如果主服务器没有执行BGSAVE,那么,主服务器就会执行BGSAVE命令。

方法二: 只追加文件(append-only-file,AOF),将被执行的命令复制到硬盘里;
appendonly yes   				// 是否开启AOF
appendfsync-no-rewrite everysec // 每秒执行一次同步,将写命令同步到硬盘
auto-aof-rewrite-percentage 100 // 
auto-aof-rewrite-min-size 64mb  // 当AOF文件大于64MB,并且体积至少是上次重写后体积的2倍,Redis 将触发BGREWRITEAOF

两个方法可以单独使用,也可以同时使用;
BGREWRITEAOF:优化AOF文件。移出冗余命令,缩小文件体积,类似于BGSAVE名命,子进程执行。

2.4 数据分片

分片: 就是基于某些简单的规则,将数据划分为更小的部分,然后根据数据所属的部分来决定将数据发送到那个位置上;

/**
     * 一个简单的分片规则,通过分片数量和的键计算新的分片的ID,从而,将数据存储到不同的hset中
     *
     * @param base          原来存储的hash的基础键
     * @param key           该条数据的key
     * @param totalElements 存储数据的总量
     * @param shardSize     设计的分片数量
     * @return 生成的分片编号:新hash的键,用于存储该条数据的分片的键
     */
    public String shardKey(String base, String key, long totalElements, int shardSize) {
        long shardId = 0;
        if (isDigit(key)) {
            shardId = Integer.parseInt(key, 10) / shardSize;
        } else {
            CRC32 crc = new CRC32();
            crc.update(key.getBytes());
            long shards = 2 * totalElements / shardSize;
            shardId = Math.abs(((int) crc.getValue()) % shards);
        }
        return base + ':' + shardId;
    }

    /**
     * 保存该条数据到分片中
     *
     * @param conn          jedis链接
     * @param base          原来数据存储的键
     * @param key           该条数据的key
     * @param value         该条数据的值
     * @param totalElements 存储数据的总量
     * @param shardSize     设计的分片数量
     * @return 保存是否成功
     */
    public Long shardHset(
            Jedis conn, String base, String key, String value, long totalElements, int shardSize) {
        String shard = shardKey(base, key, totalElements, shardSize);
        return conn.hset(shard, key, value);
    }

    /**
     * 从分片中获取该条数据
     *
     * @param conn          jedis链接
     * @param base          原来数据存储的键
     * @param key           该条数据的key
     * @param totalElements 存储数据的总量
     * @param shardSize     设计的分片数量
     * @return 获取的数据
     */
    public String shardHget(
            Jedis conn, String base, String key, int totalElements, int shardSize) {
        String shard = shardKey(base, key, totalElements, shardSize);
        return conn.hget(shard, key);
    }

2.5 集群

单台redis 服务器的读写性能和单片容量有限,然后就有了集群;
扩展读性能: 将主服务器的数据复制到重服务器,从而可以同时访问多台从服务器;
扩展写性能: 不建议将数据写入不同服务器,后续数据汇总会出现诸多问题;
主从链: 使用树状结构,在减少数据传输次数,同时能降低主服务器的传输负载;
处理系统故障: 详细信息参考《redis-in-action》page73-76;

2.6 降低内存占用

redis 可以存储string 、list、set、hash、zset。
但是list、set、hash、zset他们的结构被称为体积较大的结构,使用会产生许多冗余空间,这四种数据结构可以被分割成为多个体积较小的结构。
list: 它的数据结构实际上是一个双向链表结构,每个节点包含了三个指针占用的三个空间、一个字符串长度占用空间、一个剩余长度占用空间、字符串本身和一个额外字节占消耗用空间。
压缩效率:以’'ten"占用的空间为例,采用list,该节点将消耗21字节的内存,采用压缩列表,只会消耗2个字节内存,很令人心动。
但,万物是公平的,当使用压缩列表这种数据结构时,当存储的元素的大小过大和数量长时,就会对redis的性能产生影响。其他三种结构类似。

开启压缩列表的配置文件:

// 《redis-in-action》介绍:list压缩列表长度限制在500-2000,单个元素体积小于128字节,list压缩列表的性能会处于合理的范围之内。
list-max-ziplist-entries 1024 // list压缩列表,最大元素数量;
list-max-ziplist-value 64    // list压缩列表, 每个节点最大体积64字节

hash-max-ziplist-entries 512 // hash压缩列表,最大元素数量
hash-max-ziplist-value 64    // hash压缩列表, 每个节点最大体积64字节

zset-max-ziplist-entries 128 // zset压缩列表,最大元素数量
zset-max-ziplist-value 64    // zset压缩列表, 每个节点最大体积64字节

set-max-intset-entries  512  // set限制使用十进制整数,

当压缩列表中的元素超过了这个配置,那么,压缩列表将将会失效,数据结构转化为普通的数据结构,即使之后数据再次满足配置。