分布式——基于Redis的高性能缓存

1. 概述

之前在学校一直都没怎么关注分布式的的应用,在实际的开发场景中,因为都是一些小的项目,也没类似的需求。来到公司后才知道,几乎所有的大的应用都是在分布式环境下运行,就是每个业务都是独立开始部署的,同时每个应用都部署了N台机器。分布式环境一个非常大的区别就是应用并不是运行在一个 JVM 上的,那么所有以本地内存执行为前提的相关技术都将不适用,都需要重写。
所以我一直打算从零开始整理所有在分布式环境下所需要应用或改造的技术,于是就有了这个系列——分布式应用开发与学习。

关于分布式,另一个非常重要的一点就是缓存,如何在分布式环境下构建高效的缓存系统,在大佬的指点下,开始研究整理了 Redis 相关的资料,主要应用分布式环境下的缓存,替换之前用的基于内存的。当然,像 Memcached 这类产品其实也算得上是分布式环境下的替代品,也非常的不错,在各个场景及行业中也有非常多的应用,表现也不错。但 Redis 是最近出来的,性能更好的,天生基于分布式环境开发的产品,所以先研究一下。

2. Redis简介

Redis通常被描述为Key-Value存储引擎,当然这是准确的,但更好的描述应该是一个数据结构引擎。那是什么意思?Redis支持五种不同的数据结构:Strings(字符串)Hashes(哈希)Lists(列表)Sets(集合)Sorted Sets(有序集)。每个都具有独特的特征并支持独特的命令。

Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。

2.1 特点

Redis 与其他 key - value 缓存产品有以下三个特点:

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

2.2 优势

  • 数据都放在内存中,性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的Strings(字符串)Hashes(哈希)Lists(列表)Sets(集合)Sorted Sets(有序集)*数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知,key 过期等等特性。

3. Redis 安装与启动

Redis 的安装还是比较简单的,目前这个开源项目还是托管在 Google Code 上,下载源码后就可以配置编译了(目前稳定版本为:2.0.4):

1
2
3
4
$ wget http://Redis.googlecode.com/files/Redis-2.0.4.tar.gz 
$ tar xzf Redis-2.0.4.tar.gz
$ cd Redis-2.0.4
$ make

make完后 Redis-2.0.4目录下会出现编译后的Redis服务程序Redis-server,还有用于测试的客户端程序Redis-cli
下面启动Redis服务.

1
$./redis-server

注意这种方式启动Redis 使用的是默认配置。也可以通过启动参数告诉Redis使用指定配置文件使用下面命令启动.

1
$ ./redis-server redis.conf

redis.conf是一个默认的配置文件。我们可以根据需要使用自己的配置文件。

启动Redis服务进程后,就可以使用测试客户端程序Redis-cli和Redis服务交互了。

4. Redis 数据类型

4.1 Strings

Strings是五种数据结构中最简单的一种。其实严格来说这个名字不太准确(这里的 String 并不是指我们常说的字符串),Strings只不过是我们看起来的表象,实际存储的都是二进制序列化对象(blob),应该说是单一的 KEY-VALUE 来描述也许更准确。这种数据类型最常见的的方法是GETSet

1
2
3
Sets pages:about "about us"
GET pages:about
about us

当然Strings结构还可以做更多的事情,支持很多其他可用的命令,比如,INCR或者GETRANGE

所有关于 Hashes 的命令都在这里:https://redis.io/commands#string

4.2 Hashes——哈希

Hashes数据结构跟我们平时理解的(哈希/字典)是一致的。不是直接操作 Key(比如使用Strings),而是操纵 Key 的某个字段。

它常见的语法结构是:HSET key field value,它将哈希表key中的域field的值设为value,如果key不存在,一个新的哈希表被创建并进行HSET操作。比如:

1
2
3
4
5
redis> HSET myhash field1 "Hello"
(integer) 1
redis> HGET myhash field1
"Hello"
redis>

与Redis中的其他所有内容一样,字段和值最终都是字节数组,因此它们可以是任何内容,但是字段往往是字符串。那么我们何时该使用Hashes而不是Strings?例如,以下两者有什么区别?(我使用JSON来表示一个复杂的值,实际Redis仍然会将其序列化为一个字节数组)

1
2
3
SET users:goku {race: 'sayan', power: 9001}
HSET users:goku race sayan
HSET users:goku power 9001

其实这取决于你将如何去控制和查询,如果您需要控制单个字段,并且不想将整个对象拉入应用程序,请使用Hashes,否则,Strings可能就是你想要的。

所有关于 Hashes 的命令都在这里:https://redis.io/commands#hash

4.3 Lists——列表

Lists 的作用是将一个数组的值关联到一个单的 Key 上,实际上,可以(并且应该)将它们视为动态数组。Lists可以进行插入,追加,弹出,推入,裁剪等操作。列表还有一个用法就是,Redis不支持二级索引,只能通过 Key 访问数据,这个时候就可以用列表来模拟索引(虽然这当然不是他们唯一的用途):

1
2
3
4
5
length = redis.lpush('users:newest', 'user:goku')
if length > 100
# 只保留 100 个,超过时将前面的删除
redis.rpop('users:newest')
end

上面的代码保留了对列表中最新注册用户的引用。这里我们实时维护列表长度,尽管可以将其移动到后台任务,就是我们插入时不再作息超过 100 个时的逻辑(任由列表不断的增长,让后台的任务去管理这个长度),获取用户的时候只需要获取 10 个就行,如下所示:

1
2
3
4
#得到10个最新用户 
keys = redis.lrange('users:newest', 0, 10)
#multi获得实际的10用户对象
redis.mget(*keys)

所有关于 Hashes 的命令都在这里:https://redis.io/commands#list

4.4 Sets——集合

Sets很像Lists,它提供了我们数学中常说的集合的语义(给定集合中没有重复的值)。还提供了差集并集等操作,比如SDIFFSUNIONSUNIONSTORE等。

1
2
3
4
5
6
SADD friends:leto ghanima
SADD friends:leto duncan
SADD friends:paul duncan
SADD friends:paul gurney
SINTER friends:leto friends:paul
1) "duncan"

4.5 Sorted Sets——排序集合

排序集合集合的基础之上添加了权重功能,我们可以给每个元素添加一个权重/分数,并可以对这个权重/分数信息进行相应的操作(排序,比较等),比如我们要查询一下权重/分类500-100 的元素:

1
2
3
4
5
6
ZADD friends:leto 1000 ghanima
ZADD friends:leto 994 duncan
ZADD friends:leto 2 farad'n
ZRANGEBYSCORE friends:leto 500 1000
1) "duncan"
2) "ghanima"

5. Redis Key 查询

在Redis中,只能通过 Key 查询数据。即使我们使用哈希,我们也不能查找某个 Hashesstreet 字段值等于 xxx 的数据。当我们查看列表时,我们看到了如何构建二级索引。管理自己的二级索引可能会很麻烦,有时它无法在复杂性方面进行扩展。

除了上述五种数据结构外,Redis还有一些针对 Key 的命令,用来管理所有的缓存 Key。像DELEXISTSRENAME。可能最常用的是KEYS命令,它接受一个表达式并返回找到的键。例如,如果我想删除8月份的所有排名,我可以这样做:

1
2
keys = redis.keys("ranks:daily:*:201108*")
redis.del(*keys)

注意:keys 命令会线性地遍历所有找到匹配的键,很慢,建议只将它用于调试和开发目的

6. 其他

Redis还是比较易于设置和维护的,数据在单个文件中持久保存到磁盘,只需复制即可备份。我们所有操作的数据都应该适合内存(因为Redis本身是基于内存存储数据的)。

Redis支持主从复制,但目前还不会执行自动故障转移,也不会执行任何类型的分片,所以我们需要自己来实现HAProxy。Redis Cluster是即将发布的主要版本,应该能解决这个难题。

另外,Redis还支持事务,管道等更多命令,更多管理功能,Key 的自动过期的能力,甚至还有发布和订阅API 等,感兴趣的话可以自行去官网了解。