Redis 实战

环境搭建

基础环境

用户

1
2
3
4
5
6
7
8
9
10
11
12
13
# 增加用户,并赋予其密码
$ adduser redis
$ passwd redis # ur password for redis user

# 赋予用户可以 sudo 的权限
$ chmod u+w /etc/sudoers
$ vim /etc/sudoers
# 找到 `root ALL=(ALL) ALL` 这行,并在下面添加 redis 用户
redis ALL=(ALL) ALL
$ chmod u-w /etc/sudoers

# 切换到 redis 用户
$ su - redis

目录

1
2
3
4
$ cd /home/redis

# 存放软件目录 & 安装目录 & 日志目录 & 持久化数据存放目录
$ mkdir install && mkdir software && mkdir logs && mkdir data

分布式集群

下载

1
2
$ cd ~/install
$ wget http://download.redis.io/releases/redis-3.2.9.tar.gz

解压分发

1
2
3
4
5
$ tar zxvf redis-3.2.9.tar.gz -C ~/software
$ cd ~/software/
$ scp -r redis-3.2.9/ redis@yuzhouwan02:/home/redis/software/
$ scp -r redis-3.2.9/ redis@yuzhouwan03:/home/redis/software/
$ ln -s redis-3.2.9/ redis

安装

1
2
3
4
5
6
7
8
9
10
$ cd ~/software/redis
$ make -j8
# 输出如下信息,表示 make 成功
Hint: It's a good idea to run 'make test' ;)
make[1]: Leaving directory `/home/redis/software/redis-3.2.9/src'

$ vim ~/.bash_profile
REDIS_HOME=~/software/redis
PATH=$PATH:$HOME/bin:$REDIS_HOME/src
$ source ~/.bash_profile

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ mv redis.conf redis.conf.bak
$ vim redis.conf
bind 0.0.0.0
port 6379
# 启动 Redis 服务时,存储进程 pid 的位置
pidfile /home/redis/software/redis/redis.pid
logfile "/home/redis/logs/redis.log"
# 持久化文件存储目录
dir /home/redis/data
# 开启集群模式
cluster-enabled yes
# 集群模式下的节点配置信息
cluster-config-file nodes.conf
# 集群中各节点间连接超时时间
cluster-node-timeout 5000
# 允许数据持久化追加
appendonly yes

$ scp -r ~/software/redis/redis.conf redis@yuzhouwan02:/home/redis/software/redis/
$ scp -r ~/software/redis/redis.conf redis@yuzhouwan03:/home/redis/software/redis/

启动

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
41
42
43
44
$ nohup redis-server redis.conf 1>/dev/null 2>&1 &
$ ps -ef | grep redis
redis 12781 9204 0 10:15 pts/0 00:00:00 redis-server 127.0.0.1:6379 [cluster]

$ redis-cli -c -p 6379
127.0.0.1:6379> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:0
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0


# 因为 redis 集群至少需要 6 个节点,所以如果只想部署在 3 台机器上的话,可以考虑在每一台上,启动两个 redis 进程,对应端口、日志路径、存储目录,则需要依次更改
$ cd ~/software && mkdir redis2 && cp redis/redis.conf redis2/
$ mkdir /home/redis/data2
$ vim redis2/redis.conf
bind 0.0.0.0
port 6380
# 启动 Redis 服务时,存储进程 pid 的位置
pidfile /home/redis/software/redis2/redis.pid
logfile "/home/redis/logs/redis2.log"
# 持久化文件存储目录
dir /home/redis/data2
# 开启集群模式
cluster-enabled yes
# 集群模式下的节点配置信息
cluster-config-file nodes2.conf
# 集群中各节点间连接超时时间
cluster-node-timeout 5000
# 允许数据持久化追加
appendonly yes

$ scp -r ~/software/redis2/redis.conf redis@yuzhouwan02:/home/redis/software/redis2/
$ scp -r ~/software/redis2/redis.conf redis@yuzhouwan03:/home/redis/software/redis2/

$ nohup redis-server redis2/redis.conf 1>/dev/null 2>&1 &
# 如果启动不了,但是没有任何日志,需检查 redis2/redis.conf 是否和 redis/redis.conf 有路径、端口的冲突

集群配置

安装 Ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Debian / Ubuntu
$ apt-get install ruby

# CentOS / Fedora / RHEL
$ sudo yum install ruby

# Offline
# 下载 https://www.ruby-lang.org/en/downloads/
$ cd ~/install
$ wget https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.1.tar.gz
$ tar zxvf ruby-2.4.1.tar.gz -C ~/software
$ cd ~/software/ruby-2.4.1 && mkdir ../ruby
$ ./configure prefix=/home/redis/software/ruby
$ make -j8 && make -j8 install

$ vim ~/.bash_profile
REDIS_HOME=~/software/redis
RUBY_HOME=~/software/ruby
PATH=$PATH:$HOME/bin:$REDIS_HOME/src:$RUBY_HOME/bin
$ source ~/.bash_profile

$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]
安装 Redis 插件
1
2
3
4
5
6
7
8
9
10
# Online
$ gem update --system
$ gem install redis
# 如果下载速度过慢,可考虑使用国内的 ruby 源
$ gem sources remove http://rubygems.org/
$ gem sources -a https://ruby.taobao.org/

# Offline
$ cd ~/install
$ wget https://rubygems.org/downloads/redis-3.2.2.gem
redis-trib 创建分布式集群
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
41
42
43
44
45
46
47
48
49
50
$ redis-trib.rb create --replicas 1 192.168.1.101:6379 192.168.1.102:6379 192.168.1.103:6379 192.168.1.101:6380 192.168.1.102:6380 192.168.1.103:6380
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.1.101:6379
192.168.1.102:6379
192.168.1.103:6379
Adding replica 192.168.1.102:6380 to 192.168.1.101:6379
Adding replica 192.168.1.101:6380 to 192.168.1.102:6379
Adding replica 192.168.1.103:6380 to 192.168.1.103:6379
M: 4c0fb081525a5f1893479225576b75f03cca065d 192.168.1.101:6379
slots:0-5460 (5461 slots) master
M: 4f9fc4536f6e4e30666264738d632b1bb54799f0 192.168.1.102:6379
slots:5461-10922 (5462 slots) master
M: 4dde95537b69d2b23f2f9a4cd2a357a1e4af756e 192.168.1.103:6379
slots:10923-16383 (5461 slots) master
S: 6ffea478b1e5d1187b85adc5b0bd11b6601dd556 192.168.1.101:6380
replicates 4f9fc4536f6e4e30666264738d632b1bb54799f0
S: 1213855d2c0d1b35020895af45dcd734da50ef2c 192.168.1.102:6380
replicates 4c0fb081525a5f1893479225576b75f03cca065d
S: 94478f8642d5a0cef1a989a620f132221e35fc8a 192.168.1.103:6380
replicates 4dde95537b69d2b23f2f9a4cd2a357a1e4af756e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 192.168.1.101:6379)
M: 4c0fb081525a5f1893479225576b75f03cca065d 192.168.1.101:6379
slots:0-5460 (5461 slots) master
1 additional replica(s)
S: 6ffea478b1e5d1187b85adc5b0bd11b6601dd556 192.168.1.101:6380
slots: (0 slots) slave
replicates 4f9fc4536f6e4e30666264738d632b1bb54799f0
M: 4dde95537b69d2b23f2f9a4cd2a357a1e4af756e 192.168.1.103:6379
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 1213855d2c0d1b35020895af45dcd734da50ef2c 192.168.1.102:6380
slots: (0 slots) slave
replicates 4c0fb081525a5f1893479225576b75f03cca065d
S: 94478f8642d5a0cef1a989a620f132221e35fc8a 192.168.1.103:6380
slots: (0 slots) slave
replicates 4dde95537b69d2b23f2f9a4cd2a357a1e4af756e
M: 4f9fc4536f6e4e30666264738d632b1bb54799f0 192.168.1.102:6379
slots:5461-10922 (5462 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
redis-cli 集群状态检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 至此,大功告成~
$ redis-cli -c -p 6379
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_sent:376
cluster_stats_messages_received:376

关闭

1
$ pkill redis

遇到的坑

cannot load such file — zlib

描述
1
2
3
4
5
6
7
$ gem install -l redis-3.2.2.gem

# 如果出现如下报错,则应该先安装 zlib-devel
ERROR: Loading command: install (LoadError)
cannot load such file -- zlib
ERROR: While executing gem ... (NoMethodError)
undefined method `invoke_with_build_args' for nil:NilClass
解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ yum install zlib-devel
# 离线环境下,推荐先在有网环境中,使用 --downloadonly 的方式,把依赖包直接下载下来(也可在 https://www.rpmfind.net 里面找,但比较低效)
$ yum reinstall --downloadonly --downloaddir=/opt/software/zlib zlib-devel zlib libc.so.6 glibc-common glibc libfreebl3.so zlib-1.2.3-27.el6 -y
$ ll /opt/software/zlib
-rw-r--r-- 1 root root 4558520 Feb 18 2016 glibc-2.12-1.166.el6_7.7.i686.rpm
-rw-r--r-- 1 root root 14887196 Feb 18 2016 glibc-common-2.12-1.166.el6_7.7.x86_64.rpm
-rw-r--r-- 1 root root 118976 Nov 9 2011 nss-softokn-freebl-3.12.9-11.el6.i686.rpm
-rw-r--r-- 1 root root 73604 Sep 26 2011 zlib-1.2.3-27.el6.i686.rpm
-rw-r--r-- 1 root root 73864 Sep 26 2011 zlib-1.2.3-27.el6.x86_64.rpm
-rw-r--r-- 1 root root 44728 Sep 26 2011 zlib-devel-1.2.3-27.el6.x86_64.rpm

# 可能存在 "循环依赖" 和 "依赖冲突"
# 前者,可以通过 force 强制安装好,其中一个
$ rpm -ivh glibc-2.12-1.166.el6_7.7.i686 --nodeps --force
# 后者,可以通过 nodeps 不删除被依赖包,进行卸载
# 【注意】/lib64/libz* 需要提前备份好,否则卸载掉 zlib 之后,会因为缺少 libz.so.1,导致 yum/rpm/ssh 等命令均失效!
$ rpm -e --nodeps zlib-1.2.3-29.el6.x86_64

# 安装完成之后,将 Ruby 重新编译安装一遍,即可

ERR Slot 0 is already busy

描述
1
2
# 执行 redis-trib.rb 遇到
ERR Slot 0 is already busy (Redis::CommandError)
解决
1
2
# 通过 cluster reset soft 命令解决
$ redis-cli -h 127.0.0.1 -p 6379 cluster reset soft && redis-cli -h 127.0.0.1 -p 6380 cluster reset soft
1
2
OK
OK

ERR Invalid node address specified

描述
1
2
3
# 执行创建 Redis 集群
$ redis-trib.rb create --replicas 1 yuzhouwan01:6379 yuzhouwan02:6379 yuzhouwan03:6379 yuzhouwan01:6380 yuzhouwan02:6380 yuzhouwan03:6380
# 报错 ERR Invalid node address specified
解决
1
2
# 目前为止,只支持 IP,不支持 Hostname,因此将主机名替换为 IP 即可(PR#2323 中准备解决该问题)
$ redis-trib.rb create --replicas 1 192.168.1.101:6379 192.168.1.102:6379 192.168.1.103:6379 192.168.1.101:6380 192.168.1.102:6380 192.168.1.103:6380

Jedis 客户端

连接集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void init(DynamicPropUtils DP) {
Object clusterListObj = DP.get(PROJECT_NAME, "redis.cluster.list");
String clusterList;
if (clusterListObj == null || StrUtils.isEmpty(clusterList = clusterListObj.toString())) {
String error = String.format("Cannot get [%s-redis.cluster.list] from Dynamic PropUtils!", PROJECT_NAME);
_log.error(error);
throw new RuntimeException(error);
}
String[] hostAndPort;
Set<HostAndPort> jedisClusterNodes = new HashSet<>();
for (String clusters : clusterList.split(",")) {
hostAndPort = clusters.split(":");
jedisClusterNodes.add(new HostAndPort(hostAndPort[0], Integer.valueOf(hostAndPort[1])));
}
JedisPoolConfig conf = new JedisPoolConfig();
conf.setMaxTotal(1000);
conf.setMinIdle(50);
conf.setMaxIdle(100);
conf.setMaxWaitMillis(6 * 1000);
conf.setTestOnBorrow(true);
pool = new JedisCluster(jedisClusterNodes, conf);
}

集群操作

1
2
3
4
5
6
public Long putSet(String key, String... values) {
return pool.sadd(key, values);
}
public Set<String> getSet(String key) {
return pool.smembers(key);
}

资源释放

1
2
3
public void close() throw IOException {
if (pool != null) pool.close();
}

过期事件回调

配置集群

1
2
3
4
5
6
7
# 通过 redis-cli 进行动态配置
$ redis-cli -h localhost -p 6380
CONFIG SET notify-keyspace-events AKE # AKE: 意味着通知所有事件

# 修改 redis.conf,并重启集群使其生效
$ vim redis.conf
notify-keyspace-events AKE

服务端验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ redis-cli
127.0.0.1:6379> set yuzhouwan01 blog
OK
127.0.0.1:6379> expire yuzhouwan01 3
(integer) 1
127.0.0.1:6379> ttl yuzhouwan01
(integer) 0
127.0.0.1:6379> ttl yuzhouwan01
(integer) -2

$ redis-cli -h localhost -p 6379 --csv psubscribe '*'
"pmessage","*","__keyspace@0__:yuzhouwan01","set"
"pmessage","*","__keyevent@0__:set","yuzhouwan01"
"pmessage","*","__keyspace@0__:yuzhouwan01","expire"
"pmessage","*","__keyevent@0__:expire","yuzhouwan01"
"pmessage","*","__keyspace@0__:yuzhouwan01","expired"
"pmessage","*","__keyevent@0__:expired","yuzhouwan01"

连接集群

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
private String getClusterList(DynamicPropUtils DP) {
Object clusterListObj = DP.get(PROJECT_NAME, "redis.cluster.list");
String clusterList;
if (clusterListObj == null || StrUtils.isEmpty(clusterList = clusterListObj.toString())) {
String error = String.format("Cannot get [%s-redis.cluster.list] from Dynamic PropUtils!", PROJECT_NAME);
_log.error(error);
throw new RuntimeException(error);
}
return clusterList;
}
private JedisPoolConfig buildConf() {
// org.apache.commons.pool2.impl.BaseObjectPoolConfig
JedisPoolConfig conf = new JedisPoolConfig();
conf.setMaxTotal(1000);
conf.setMinIdle(50);
conf.setMaxIdle(100);
// conf.setMaxWaitMillis(6 * 1000);
conf.setTestOnCreate(true);
conf.setTestOnBorrow(true);
conf.setTestOnReturn(true);
conf.setTestWhileIdle(true);
// conf.setTimeBetweenEvictionRunsMillis(1);
conf.setNumTestsPerEvictionRun(30);
return conf;
}
// 这里不要使用 JedisCluster,因为 cluster 模式下,jedis 只会连接一个节点,会导致监听不到其他节点上的事件
private void initPools(DynamicPropUtils DP) {
String clusterList = getClusterList(DP);
pools = new LinkedList<>();
String[] hostAndPort;
for (String clusters : clusterList.split(",")) {
hostAndPort = clusters.split(":");
pools.add(new JedisPool(buildConf(), hostAndPort[0], Integer.valueOf(hostAndPort[1])));
}
}

回调监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try (RedisClusterConnPool pool = new RedisClusterConnPool(dp, true)) {
List<JedisPool> jedis = pool.getPools();
JedisPubSub jedisPubSub = new JedisPubSub() {
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
_log.info("onPSubscribe {} {}", pattern, subscribedChannels);
}
@Override
public void onPMessage(String pattern, String channel, String message) {
_log.info("onPMessage: {}, Channel: {}, Message: {}", pattern, channel, message);
}
};
for (JedisPool j : jedis)
j.getResource().psubscribe(jedisPubSub, "__keyevent@*__:expired" /*"__key*__:*"*/ /*"*"*/);
}

测试

1
2
3
4
5
6
7
8
pool.put("yuzhouwan01", "blog01");
pool.expire("yuzhouwan01", 2);
Thread.sleep(2100);
assertEquals(null, pool.get("yuzhouwan01"));

2017-07-26 16:18:56.045 | INFO | onPMessage Pattern: *, Channel: __keyevent@0__:set, Message: yuzhouwan01 | com.yuzhouwan.bigdata.redis.notification.RedisNotification.onPMessage | RedisNotification.java:61
2017-07-26 16:18:56.045 | INFO | onPMessage Pattern: *, Channel: __keyevent@0__:expire, Message: yuzhouwan01 | com.yuzhouwan.bigdata.redis.notification.RedisNotification.onPMessage | RedisNotification.java:61
2017-07-26 16:18:57.064 | INFO | onPMessage Pattern: *, Channel: __keyevent@0__:expired, Message: yuzhouwan01 | com.yuzhouwan.bigdata.redis.notification.RedisNotification.onPMessage | RedisNotification.java:61

实战技巧

匹配删除

1
2
3
4
5
6
7
8
$ redis-cli flushdb
$ redis-cli --raw -n 1 keys "yuzhouwan*" | xargs -L1 -I{} redis-cli move {} 0
$ redis-cli
127.0.0.1:6379> keys yuzhouwan*
(empty list or set)

# 也可以安装使用 redis-utils-cli(https://www.npmjs.com/package/redis-utils-cli)
$ redis-utils del 127.0.0.1 yuzhouwan*

数据容灾

 主从热备 + 二级缓存 + 从库 AOF(主从切换)

参考

全局流控

流控方案

Leaky bucket 漏桶算法

 桶内水溢出时,可以把消息放到队列中等待

Token bucket 令牌桶算法

 桶内令牌不足时,可以把消息放到队列中等待

实现方式

Guava RateLimiter

 Guava 无法做到全局流控

Redis Expire 机制

 如果设置的超时时间过长,可能对内存有一定的损耗

Hystrix 限流

 支持 自动降级、熔断与恢复、依赖隔离、异常记录、流量控制、实时监控,不过会存在一定的 代码侵入 和 性能损耗 的问题(1%~5%)

Hystrix

(图片来源:Hystrix™ 官网)

参考

Tips: Full code is here.

技术内幕

缓存穿透 vs 缓存雪崩 vs 缓存失效

缓存穿透

场景描述

 一般的缓存系统,都是按照 key 去缓存查询,如果不存在对应的 value,就应该去后端系统查找(比如 DB)。如果 key 对应的 value 是一定不存在的,并且对该 key 并发请求量很大,就会对后端系统造成很大的压力,我们称之为缓存穿透

解决方案
  • 对查询结果为空的情况也进行缓存,缓存过期时间设置短一点(避免消耗太多的缓存空间),或者该 key 对应的数据 insert 了之后清理缓存
  • 对一定不存在的 key 进行过滤。可以把所有的可能存在的 key 放到一个大的 Bitmap 中,查询时通过该 Bitmap 过滤
  • 排查是否是自身程序或者数据的问题,亦或是外部恶意攻击或者爬虫,导致大量访问不存在的 key 值

缓存雪崩

场景描述

 缓存雪崩,是指当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如 DB)带来很大压力

解决方案
  • 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待
  • 不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
  • 做二级缓存,A1 为原始缓存,A2 为拷贝缓存。A1 失效时,可以访问 A2。A1 缓存失效时间设置为短期,A2 设置为长期
  • 保证缓存层服务的高可用,后端组件做好限流措施,并提前预演缓存层失效的场景

缓存失效

场景描述

 缓存失效,是指缓存集中在一段时间内失效,DB 的压力凸显

解决方案

 这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上

其他

 当发生大量的缓存穿透,例如对某个失效的缓存的大并发访问就造成了缓存雪崩

 缓存失效的同时发生雪崩效应,对底层系统的冲击将会非常大。这时候,可以使用双缓存机制,在工作缓存之外另外维护一层灾备缓存

双缓存 vs 二级缓存

 一般的,用 Keepalived 做双机热备,而用 J2Cache 来做二级缓存

Tips: Full code is here.

为什么 Redis 一致性算法使用 Raft,而不是 Paxos

 没有应用场景上的区别,主要是因为 Raft 算法更为简单好懂易实现

协议上的简化

 最后主要 RPC 只有两个,其他协议的二阶段、三阶段也都变成 看起来像是一阶段

Term 概念的强化

 看起来似乎 Paxos 也有重选 Leader 的机制,但是强化概念,并增加一个 Term,包含有一个 Leader、Entry 与 Term 相关的属性等都大大简化了流程

Log 只会从 Leader 到 Follower 单向同步

 实现一下,会发现减少了很多问题,代码实现的复杂度也下降了很多

Redis vs Memcached vs MongoDB

性能

  • 都比较高,性能对我们来说应该都不是瓶颈
  • 从 TPS 的角度来看,Redis 和 Memcached 差不多,要略优于 MongoDB

操作的便利性

  • Redis
    丰富一些,数据操作方面,Redis 更好一些,较少的网络 I/O 次数
  • Memcached
    数据结构单一
  • MongoDB
    支持丰富的数据表达,索引,最类似关系型数据库,支持的查询语言非常丰富

内存空间的大小和数据量的大小

  • Redis
    在 2.0 版本后增加了自己的 VM 特性,突破物理内存的限制;可以对 K-V 设置过期时间(类似 Memcached)
  • Memcached
    可以修改最大可用内存,采用 LRU 算法
  • MongoDB
    适合大数据量的存储,依赖操作系统 VM 做内存管理,吃内存也比较厉害,服务不要和其他的放在一起

可用性

  • Redis
    依赖客户端来实现分布式读写;主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制,因性能和效率问题,所以单点问题比较复杂
    不支持自动 sharding,需要依赖程序设定一致 hash 机制
    一种替代方案是,不用Redis本身的复制机制,采用自己做主动复制(多份存储),或者改成增量复制的方式(需要自己实现),一致性问题和性能的权衡
  • Memcached
    本身没有数据冗余机制,也没必要;对于故障预防,采用依赖成熟的 hash 或者环状的算法,解决单点故障引起的抖动问题。
  • MongoDB
    支持 master-slave、replicaset(内部采用 Paxos 选举算法,自动故障恢复)、auto sharding 机制,对客户端屏蔽了故障转移和切分机制。

持久化

  • Redis
    对于数据持久化和数据恢复,Redis 支持(快照、AOF):依赖快照进行持久化,AOF 增强了可靠性的同时,对性能有所影响
  • Memcached
    不支持,通常用在做缓存,提升性能
  • MongoDB
    从 1.8 版本开始采用 binlog 方式支持持久化的可靠性

事务性

  • Redis
    事务支持比较弱,只能保证事务中的每个操作连续执行
  • Memcached
    在并发场景下,用 CAS 保证一致性
  • MongoDB
    不支持事务

数据分析

  • MongoDB
    内置了数据分析的功能(MapReduce),其他不支持

应用场景

  • Redis
    数据量较小的更性能操作和运算上
  • Memcached
    用于在动态系统中减少数据库负载,提升性能;做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用 sharding)
  • MongoDB
    主要解决海量数据的访问效率问题

欢迎加入我们的技术群,一起交流学习

群名称 群号
人工智能(高级)
人工智能(进阶)
BigData
算法