1. 主要问题
1.1 什么是redis?
答:redis 是一个远程内存的非关系型数据库。
1.2 redis能够存储什么数据?存储量如何?
答:string 、list、hash、set、zset;
redis官网(https://redis.io/topics/data-types),一个String类型的value最大可以存储512M;
list、hash、set、zset元素个数最多为2^32-1个,也就是4294967295个。注意: jedis 的源码,支持String 类型的value 存储1G。
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限制使用十进制整数,
当压缩列表中的元素超过了这个配置,那么,压缩列表将将会失效,数据结构转化为普通的数据结构,即使之后数据再次满足配置。