[翻译]Redis教程一篇

NoSQLFan上看到的一篇Redis教程,原文链接:http://labs.alcacoop.it/doku.php?id=articles:redis_land

自己随便翻译了下。

 

ALCA in Redis-land

Short summary of an adventurous journey in the NOSQL world with Redis.

The legs of our journey

As every other journey, the ours too is made of legs, so let’s write them down before going on:

  1. Redis? What is it?
  2. Available datatypes
  3. Where’s my tables?
  4. A simple use case
  5. Back home

Leg 1: Redis? What is it?

Leg 1: Redis? 是什么?

To make a long story short, Redis is a sort of key-value database on steroids. Why steroids? Simply because Redis is extremely fast (all data are loaded in memory and saved on disk, if desired) and feature rich (it supports various datatypes along with many complex operations on them).

Redis是一个key-value数据库 on sterodis.Why steroids?简单的来说Redis是非常的快(如果可以的话,所有数据都被加载到内存中,保存在磁盘上)而且有很多的特点(支持多种独立的数据类型,和在类型上的复杂操作)

扫描二维码关注公众号,回复: 1410467 查看本文章

An important peculiarity of Redis is the fact that it’s not a database in the common meaning of the word; it is a database in the sense that it stores and keeps data for you, but it doesn’t provide any SQL dialect to play with them (as relational databases do). Don’t be afraid, Redis is not a store-and-lose database, it just doesn’t provide our beloved SQL and all its amenities, but it has a robust and tested protocol that lets you interact with it.

Redis的一个重要的特性是它不是一般定义的数据库;它为你存储数据,但是不支持SQL操作(这点和关系型数据库不同)。不过不要害怕,Redis并不是一个会丢失数据的数据库,它只是不支持SQL,但是它有其他的操作方式。

In Redis you’ll not deal with tables, selects, joins, views; there’re no integer or varchar fields and so on. You have to play with a very low level dataset structure and with more primitive datatypes.

Redis中你不需要面对表、查询、关联、视图;没有数字或者字符类型等等。你不得不面对更加低级别的数据结构和更多的原始的数据类型。

Leg 2: Available datatypes

Leg 2: 有效的数据类型

Let’s give a more deep look at how this odd database work. As seen in the previous leg, Redis is based on a key-value store paradigm and we’ll focus for the moment on its "key" part.

让我们来深入的看下这个古怪的数据库是如何工作的。在上面一章讲到,Redis是基于key-value存储的数据库,我们关注下这个”key”部分。

Keys are simply strings (officially not binary safe. UPDATE: now, key strings are binary safe.) such as "username" or "password". You can’t use space in keys, but alphanumeric values such as ".", ":", "_" (and so on) work fine, so keys like "user_name", "user:123:age", "user:123:username" are perfectly legal ones.

Keys就是简单的字符串(官方说这个不是二进制安全的。更新:现在,Key字符串是二进制安全的了)比如说”username”或者”password”。你不能在keys中使用空格,但是字符例如”.””:””_”(等等)是可以的,所以像 ”user_name” ”user:123:age””user:123:username”是完全合法的。

We must put a lot of attention to keys because, unlike field names in the RDBMS world, they are fully engaged in the game. As we’ll see in the following sub-legs, Redis doesn’t knows about tables, so simple tasks such as "SELECT username from users WHERE user_id=123;" have to be approached in a different way. As an example, to obtain in Redis the same result of the previous SQL query, we’ll have to get the value of the key "user:123:username", so, as you can see, keys could bring "information" (such as user ids) with them and play a very different role than the field names of an SQL table.

我们必须更多的关注key,因为和关系型数据库中的字段名称不同,Redis不识别表,所以简单的查询,比如 SELECT username from users WHERE user_id=123;不得不用另外一种途径来实现。在Redis中要获取和这个SQL相同的结果的话,我们就需要获取key”user:123:username”的值,你可以看到keys可以带一些信息的(比如”user”),这个和关系型数据库中的字段名字是完全不同的。

Now that things about keys should be more clear, let’s take a tour of the available datatypes.

现在关于key我们比较清楚了,让我们来看下有效的数据类型吧。

Strings

Redis strings are the most basic available datatype; they are normal, binary-safe strings of maximum dimension of 1 Gb.

Redis strings是最基本有效的数据类型,他们是普通的、二进制安全的字符串,最大长度是1Gb

You can set a key to a string value using the SET command and get it using GET. If you need to store a numeric value, such as counter, you can safely save it on a string and use INCR or DECR to increment or decrement it.

可以使用SET命令设置key的字符串值或者用GET命令或者它的值。如果你需要存储一个数字,比如要计数,任然可以保存到字符串中,然后使用INCR或者DECR来增加或者减少它。

Lists

Redis lists are collections of Redis strings sorted by insertion order. You can think of a list as a chain, you can add a new link on the left extremum (the head) or on the right (the tail); you can also add a link in the center, but you have to broke another link before.

Redis lists Redis字符串的集合,它按照插入的顺序储存。你可以把list看成是个链,你可以在它的最左边()或者最右边()增加新的链接。你也可以在中间增加,不过你需要先把原来的list断开成2个。

You can add elements to a list using the LPUSH and RPUSH commands (L for left and R for right), get an element (removing it) with LPOP and RPOP and get a range of elements (without removing any) with LRANGE. You can also add an element on a given point with LSET, but, as for the chain example, this operation is much more slower than a simple push.

你可以用LPUSHRPUSH命令来增加元素到list中,用LPOP或者RPOP来获取一个元素,或者用LRANGE来获取一段范围的元素。你可以可以用LSET来在给定的一个点增加元素,但是这个操作将会比简单的PUSH操作慢很多。

Hashes

At the time of writing, hashes are the newest datatype, implemented few days ago, it will be released shortly in the coming release. Hashes are a relatively long waited feature that helps storing data in a more coherent and neat way. Hashes implements a sort of key-value paradigm inside a key, so, for example an hash "user" may cointain some different fields to wich correspond different values, just like hashes do in programming languages such as ruby, javascript, etc…

Hashs是几天前才出来的新类型,很快会在后续的正式版中出现。这个新的特性将帮助更加有条理和整洁的方式保存数据。Hashes是将key-value分类存储来实现的,所以一个”user”hash可能对应很多不同的值,就像其他编程语言中的hash一样,比如ruby,javascript等等。

Sets

Redis sets are just like the homonymous mathematical structure, collection of distinct objects; in this particular case the objects are Redis strings. As you could argue, sets are different from lists for two main reasons: elements are unordered and they are all distinct, you can’t have two equal elements.

Redis sets就像是homonymous mathematical structure,不同值的集合。它是由Redis字符串组成的。Setslists的主要区别有2点:元素是无序的和他们都是唯一的,你不可能得到2个一样的元素。

You can add an element to a set with the command SADD, remove an element with SREM, get (and remove) a random element with SPOP and perform the set operations of union, intersection and difference with SUNION, SINTER and SDIFF respectively.

你可以用命令SADD添加元素,或者用SREM来删除元素,或者用SPOP来随机返回并删除名称为keyset中一个元素,你还可以用SUNION,SINTERSIDFF命令来对set进行集合、交集、差集的操作。

Ordered sets

Redis ordered sets are quite like normal sets, with the exception that every set element has an associated weight used to take this element in order with others.

Redis ordered sets和普通的set十分相似,只是每个set中的元素都被分配了一个权重用来排序。

You can work with sorted sets as with normal ones, what changes is just the names of the commands: ZADD to add an element, ZREM to remove it and so on. Two different commands are ZINCR and ZSCORE, the former is used to increment the score of an element and the latter to get its score.

你可以和操作set一样来操作ordered sets,唯一不同的只是命令的名字变了:ZADD增加元素,ZREM移除元素等等。2个不同的命令是ZINCRZSCORE,前者用来增加元素的积分,后者用来获取指定key的排序sets集合中成员的积分。

Leg 3: Where are my tables?

Leg3: 我的表在哪?

So, working with Redis data is really different from what we’re used to with SQL tables, you haven’t a language to query the server, you just have some commands to manipulate the keys stored in the database. Commands are datatype-sensitive, you can’t use a set command on a list, otherwise you’ll get an error. Commands could be issued via the redis-cli or using one of the many wrappers for your preferred programming language. In the following, we’ll be agnostic about the way you’ll query Redis, we’ll just write the commands and you’ll issue them as you prefer.

Redis 数据操作和我们操作SQL表有很多的不同,你无法在server上使用语言来查询。,你只有一些命令来操作存储在数据库中的keys。命令是数据类型敏感的,你不能对list使用set的命令,这样会导致错误。命令可以通过redis-cli或者你喜欢的语言的封装接口来执行。接下来我们只会写出命令,你可以通过你喜欢的方式来执行这些命令。

Let’s think about a simple SQL table where we could save users for some application:

让我们考虑一个简单的SQL表,我们可以通过一些程序来保存用户信息到这个包里面:

id

username

password

name

surname

1

user1

pass1

Bob

Smith

2

user2

pass2

Mario

Rossi

Storing the data

We want now to save the same data in Redis, so we have to design our database to fit this situation. Maybe, it’s better to think about the problem from an application-centric point of view; in the SQL version, our application would get users’ data issuing a SELECT statement having as clause the users’ id; in other words, we need a way to distinguish between different data entries. Additionally, we need to get user’s data with the sole knowledge of an unique identifier. This situation could be solved solved if we use the user id as a part of the redis key, so the records of the previous table would translate as

现在我们想保存相同的数据到Redis里面,所以我们需要设计我们的数据库来适合这个场景。当然我们如果能从程序的角度来考虑这个问题那更好。在使用SQL的时候,程序通过对表usersID字段来查询得到想要的用户数据。换个说法,就是说我们需要一个方法来区别不同的数据入口。也就是说我们需要一个专有的唯一标示来获取数据。在这个场景下我们可以使用user di作为redis key的一部分来解决问题,所以上面的数据可以转换成:

Key

Value

user:1:username

user1

user:1:password

pass1

user:1:name

Bob

user:1:surname

Smith

user:2:username

user2

user:2:password

pass2

user:2:name

Mario

user:2:surname

Rossi

Well, given the id of an user, we could now get all its data reading the keys user:id:username, user:id:password, user:id:name and user:id:surname.

然后只要有了用户的id,我们就可以获取所有这个key的数据。

Loggin’ in users

Our data are suitable for a login process, so we have to provide a way to gets the user’s id, given the username; in other words, we have to establish a relation between usernames and user ids. This can be done if whe add another redis key to our data design: user:username:id.

我们的数据还要适合登录的流程,所以我们不得不提供给用户名,获取用户id的方法。也就是我们必须建立用户名和用户ID的关联。我们可以增加另外一个redis key来解决这个问题。

Key

Value

user:user1:id

1

user:user2:id

2

So, if Mario Rossi will try to login to our application, we can get his id knowing its username and so we’ll get all its data.

所以,如果Mario Rossi登录我们系统的时候,我们可以通过用户名获取他的ID,然后就可以获取所有他的数据了。

Primary keys?

Another problem is the uniqueness of our ids. In the SQL world we say, for example, "id int primary key auto_increment", now we have to implement a similar behavior to ensure that we have a different id for each user. As in the previous case, Redis has a solution: we can simply create another key "user:next_id" and use it as a counter that we manipulate via the INCR command whenever we add a new user.

另外一个问题是id的唯一性。在SQL里面我们只需要写"id int primary key auto_increment"就可以了,但是现在我们要在Redis里面实现这个功能来确保每个用户有不同的id。在前面的例子里面,Redis有个解决方法:我们可以创建另外一个key “user:next_id” ,当我们添加用户的时候,就使用INCR命令来增加这个值。

SELECT * FROM users;

The next problem is to get the user list. We could think that what we’ve already done is enough to get this list: we could get the current value "user:next_id" and get users’ data from 0 to this value in one or more steps. Well, now let’s think about an user that has cancelled it’s subscription (our next problem), when we’ll traverse all the integers from 0 to user:next_id we’ll find its old id that now should not has any data.

接下来的问题是如何获取用户列表。先让我们看看我们如何可以获取这个清单:我们可以从 “user:next_id”获取当前的用户数,然后从0到这个值一个个读取用户信息。然后让我们想想如果有用户取消了注册(下个问题),我们会发现无法查询到这个值对应的用户信息。

Althought this is not a serious problem, we wouldn’t waste our time trying to get the data of users that don’t exist anymore, so we’ll create a new key "user:list" of list (or set) type in wich we’ll add all new user ids, removing them when needed. We prefer using a list because this give us the possibility to do a sort of "pagination" (maybe you’re thinking about "LIMIT"?) using the LRANGE command.

虽然这不是一个严重的问题,但是我们不想浪费我们的时间去读取一个不存在的用户。所以我们需要创建一个新的list(或者set)类型的key “user:list” ,我们会添加新的用户id并在需要的时候删除它。我们推荐使用list因为这样我们可以用LRANGE命令来对数据做分页。

Deleting users

The last problem is a matter of "data integrity"; what would happen when we delete a user? We sould remove anything that references to it. In other words, we should delete all the keys user:id:*, user:username:id and the id in "user:list".

上一个问题是数据的健全性。当我们删除数据的时候会发生什么?我们需要移除所有的相关数据。也就是说我们必须删除所有的key user:id:*, user:username:id "user:list"中的id

 

Leg 4: A simple use case

Leg4: 一个简单的案例

As an example of what we learned in this journey, let’s try to design an application that will work as a virtual library that let us group our books by topics. This example will be slightly more complex than the user’s table, but we’ll teach us how to handle relations with Redis.

接下来我们用我们学到的东西来实现一个简单的例子。设计一个虚拟图书馆的程序。我们可以通过书的标题来分组。这个例子比用户表的例子要复杂,但是它可以更好的让我们掌握用Redis创建关联。

In our application, we should be able to collect our books, storing informations such as title, author(s), topic(s), pages, price, ISBN and a description. Obviously, we could have a book with more than one author and that cover more than one topic at the same time (for example, a book that is about programming ad ruby). Additionally, one author could have written more than one book and a topic could be dealed with in more than one book. From what we’ve written it arises clearly that there’re a many-to-many relation between authors and books and between topics and books.

在这个程序里面,我们需要能收集并存储书的信息,例如:书名、作者、主题、页数、价格、ISBN和描述。很明显,一本书是可以有多个作者,并且一个本书可以有多个主题(比如一本的主题可以使 程序 ruby )。一个作者可以写多余一本的书;一个主题可以在多余一本的书上声明。也就是说 书和作者  主题和书  都是多对多的关系。

The SQL case

First of all, let’s try to design the SQL tables (with some data) that model this situation, in order to see how works the translation in the Redis idiom:

首先尝试设计SQL(带一些数据),来看下在Redis中事物应该如何工作。

Books

id

title

pages

price

isbn

description

1

Programming Ruby: The Pragmatic Programmers’ Guide

829

$26

0974514055

The bible of the ruby programming language

2

Erlang Programming

496

$42

0596518188

An interesting introduction to erlang

Authors

id

name

surname

1

Dave

Thomas

2

Chad

Fowler

3

Andy

Hunt

4

Francesco

Cesarini

5

Simon

Thompson

Topics

id

name

description

1

programming

Books about programming

2

ruby

Books about ruby

3

erlang

Books about erlang

Books-Authors

book_id

author_id

1

1

1

2

1

3

2

4

2

5

Books-Topics

book_id

topic_id

1

1

1

2

2

1

2

3

The Redis case

It should be already clear that the trhee tables Books, Authors and Topics are not a problem; we’ve already learned how to save this data in Redis in the previous leg (see the next image). The problem arises when considering the link tables Books-Authors and Books-Topics that implement the two many-to-many relations. How to implement them? Let’s think about topics only, the problem for authors is merely a twin (with a different name) of the former and we’ll use the same strategy.

首先要确认的是 书、作者和主题这3个表是肯定要有的。我们已经在之前学过在Redis中如何保存数据了。然后让我们考虑书-作者和书-主题这2个多对多的实现方法。让我们先考虑主题,因为作者和主题的问题是一样的,我们可以采用相同的策略来解决。

For every book we have to know the topics that belongs to it and, in the same way, for every topic we have to know every book that deals with it. In other words, for each book we need a collection of the related topic ids and for each topic a collection of the related book ids. This is a problem where sets fit perfectly! We’re going to create two sets, book:id:topic and topic:id:books, in the former we’ll save the topic ids that match the book and in the latter we’ll save the ids of the books that match this topic. For example, referring to the data shown in the previous sub-leg, the book "Programming Erlang" (book id 2) will have a key book:2:topics of set type, with elements (1, 3); while the topic programming will have a set topic:1:books with elements (1, 2).

对于每本书,我们需要知道它的所有主题,同样对于每个主题我们需要知道和它相关的每本书。换句话说,对于每本书,我们需要一个主题ID的集合。对于每个主题,我们需要相关的图书ID的集合。这个问题可以用sets来解决!我们可以创建2setsbook:id:topic topic:id:books,第一个sets用来保存和图书匹配的主题ID,第二个sets用来保存和主题匹配的图书ID。比如在上一节提到的图书“Programming Erlang(ID2)就会有key book:2:topicsset类型,对应的值是(1,3); 主题 programming 会有个 topic:1:books set,对应的值是(1,2)

As said early, the same works for authors, so we could say that a SQL’s many-to-many relation translates in Redis as two sets. This is really useful, because it give us the ability to obtain an important feature for free: we could easily find a book that covers more than one topic doing a set intersection between all topic:id:books sets related to the topics we are interested on. So, for example, the intersection between topic:1:books (books about programming) and topic:2:books (books about ruby) will return the set (1), i.e. only the book "Programming Ruby" with id 1.

之前就说过,对于作者的处理是一样的,所以在Redis中会用2sets来处理SQL中的多对多的关系。这个十分有用,因为它可以让我们可以随时获取想要的重要信息:我们可以十分轻松的找到包含多个主题的书(通过对相关的主题所对应的topic:id:books做差集),比如对topic:1:books (programming相关的书) topic:2:books (ruby相关的书)做差集,就可以得到"Programming Ruby"的结果。

A special care should be used whenever we remove something from our database. As shown in the previous figure, for example, topics have a reference to books and books have a reference to topics, so, what to remove? Well, surely we should remove every book:id:* keys, but before doing this we have to traverse all topics to remove the book id from the set topic:id:books. Additionally, we should remove the book id from the book:list list. If we want to remove a topic, we have to procede in a very similar way: before removing each topic:id:* key we have to traverse all books referenced by the topic we want to delete and remove the topic id from the set book:id:topics. The same apply for authors.

有个特殊的例子需要考虑下,就是如何删除信息。因为主题和书有关联,书和主题也有关联,所以如何删除?当然我们肯定需要删除所有的book:id:* keys,但是在做这个之前我们不得不遍历所有的主题,从topic:id:books中删除图书ID。还有我们要从book:list 中删除图书ID。如果要删除注意,我们需要做相同的操作:在删除每个topic:id:* key之前要遍历所有和该主题相关的书籍并且从book:id:topics中删除主题ID。对于删除作者的步骤也一样。

We like fun and programming, so to lear by doing consider taking a look to the companion code of our virtual library. It is implemented in ruby, using ezmobius’ redis-rb. Both the code and the look’n’feel are very rough, so feel free to make a pull request if you have improvements.

Leg 5: Back home

Our journey is reaching its end, we’re quite back home and so now it’s time to pack our luggages and buy some souvenirs.

In the luggage we have to put all we’ve learned about Redis: datatypes, commands, oddities and so on.

The souvenirs are the three patterns we’ve learned:

  • handling the unique id problem with a string key and the INCR command;
  • handling a user login via an user:username:id type key;
  • handling many-to-many relations with sets.

Our journey is completed and now we’re definitely at home; we just hope to don’t suffer of post-holidays stress, but in any case we’ve the right cure: having fun coding free software!

References

Contacts

Domenico Delle Side (domenico.delleside AT alcacoop.it), AlcaCoop (info AT alcacoop.it)

 

 

猜你喜欢

转载自action825.iteye.com/blog/1417094