当我们使用redis时候,我们并没有表。所以我们需要key来标识对象,而value来存储所需要的值。对于retwis(twitter的简易版本)来说,我们可以通过唯一的ID来标识用户,通过INCR操作即可:
127.0.0.1:6379> set next_user_id 1000 OK 127.0.0.1:6379> INCR next_user_id (integer) 1001 127.0.0.1:6379> HMSET user:1001 username lcj password p1pp2 OK 127.0.0.1:6379> HSET users lcj 1001 (integer) 0 127.0.0.1:6379> INCR next_user_id (integer) 1002 127.0.0.1:6379> HMSET user:1002 username voler password pp2pp3 OK 127.0.0.1:6379> HSET users voler 1002 (integer) 0我们通过不断的INCR,来得到唯一的ID。这里user:1001为哈希表,用来标识一个用户,而我们通过哈希表users,来关联用户和ID。
一个用户应该有粉丝,并且他也应该是其他人的粉丝。我们可以使用集合(set)来标识粉丝。但是我们可以使用sorted set来标识它们,用创建粉丝的unix时间当做排序字段,而用户的ID作为实际的值。而粉丝可用以下表示:
followers:1000 =>sorted set of uids of all the followers users following:1000 =>sorted set of uids of all the following users其中,1000为用户的ID,则我们可以通过以下的代码达到互粉:
127.0.0.1:6379> ZADD followers:1001 1401267618 1002 (integer) 1 127.0.0.1:6379> ZADD followers:1002 1401267618 1001 (integer) 1那么,我们如何显示用户发表的140个字的微博呢?我们可以使用list结构。而如果整页显示用户发表的内容,我们可以使用LRANGE来读取list的所有内容:
posts:1000 => a list of post ids - every new post is LPUSHed here
我们可以使用一个随机不可猜测的字符串来验证用户,并用来设置cookie,我们甚至要把用户ID和其字符串关联起来,存储于一个哈希表中:
127.0.0.1:6379> HSET user:1000 auth fea5e81ac8ca77622bed1c2132a021f9 (integer) 1 127.0.0.1:6379> HSET auths fea5e81ac8ca77622bed1c2132a021f9 1000 (integer) 1为了进行验证我们需要进行以下几个步骤:
1. 得到用户名和密码(登录时候获知)
2. 检查用户名是否存在“用户哈希表”中
3. 如果存在,则得到其用户ID(如1000)
4. 检查用户user:1000的密码是否匹配。如果不匹配则报错。
5. 如果匹配则生成一个随机不可预测的字符串“fea5e81ac8ca77622bed1c2132a021f9”作为认证的cookie。
简单的PHP代码如下:
include("retwis.php"); # Form sanity checks if (!gt("username") || !gt("password")) goback("You need to enter both username and password to login."); # The form is ok, check if the username is available $username = gt("username"); $password = gt("password"); $r = redisLink(); $userid = $r->hget("users",$username); if (!$userid) goback("Wrong username or password"); $realpassword = $r->hget("user:$userid","password"); if ($realpassword != $password) goback("Wrong useranme or password"); # Username / password OK, set the cookie and redirect to index.php $authsecret = $r->hget("user:$userid","auth"); setcookie("auth",$authsecret,time()+3600*24*365); header("Location: index.php");当然,我们也要检查用户是否已经登录:
1. 得到此用户的认证cookie,如果没有cookie,则此用户未登录。如果有的话,我们则得到此cookie的用户ID。
2. 判断此用户的ID和登录的用户是否匹配,如果匹配,则提示信息:
function isLoggedIn() { global $User, $_COOKIE; if (isset($User)) return true; if (isset($_COOKIE['auth'])) { $r = redisLink(); $authcookie = $_COOKIE['auth']; if ($userid = $r->hget("auths",$authcookie)) { if ($r->hget("user:$userid","auth") != $authcookie) return false; loadUserInfo($userid); return true; } } return false; } function loadUserInfo($userid) { global $User; $r = redisLink(); $User['id'] = $userid; $User['username'] = $r->hget("user:$userid","username"); return true; }而为什么这里要多判断一次:用户ID是否已经匹配呢?不是只要判断是否存在cookie则可以知道用户是否登录了吗?因为可能存在BUG产生多个cookie指向一个用户,但是ID却是唯一的(实际上是通过ID来验证用户)。那么当用户注销时候,我们需要删除旧的认证,给用户赋值一个新的认证即可。(这里有一点个人推测是:用户的认证cookie是唯一的,即是通过算法关联用户名,密码等生成的一个不可预测的字符串。所以我们判断用户是否登录需要cookie和ID,因为用户退出时也会生成一个认证字符串):
include("retwis.php"); if (!isLoggedIn()) { header("Location: index.php"); exit; } $r = redisLink(); $newauthsecret = getrand(); $userid = $User['id']; $oldauthsecret = $r->hget("user:$userid","auth"); $r->hset("user:$userid","auth",$newauthsecret); $r->hset("auths",$newauthsecret,$userid); $r->hdel("auths",$oldauthsecret); header("Location: index.php");
用户发表微博的代码类似这样:
127.0.0.1:6379> set next_post_id 1000 OK 127.0.0.1:6379> INCR next_post_id (integer) 1001 127.0.0.1:6379> HMSET post:1001 user_id $owner_id time $time body "I am happy" OK我们如果更新了微博,则需要将内容呈现给自己的粉丝:
include("retwis.php"); if (!isLoggedIn() || !gt("status")) { header("Location:index.php"); exit; } $r = redisLink(); $postid = $r->incr("next_post_id"); $status = str_replace("\n"," ",gt("status")); $r->hmset("post:$postid","user_id",$User['id'],"time",time(),"body",$status); $followers = $r->zrange("followers:".$User['id'],0,-1); $followers[] = $User['id']; /* Add the post to our own posts too */ foreach($followers as $fid) { $r->lpush("posts:$fid",$postid); } # Push the post on the timeline, and trim the timeline to the # newest 1000 elements. $r->lpush("timeline",$postid); $r->ltrim("timeline",0,1000); header("Location: index.php");
此段的原理不懂,最好通过搭建实际的框架运行,并调试代码,即可明白其原理:
function showPost($id) { $r = redisLink(); $post = $r->hgetall("post:$id"); if (empty($post)) return false; $userid = $post['user_id']; $username = $r->hget("user:$userid","username"); $elapsed = strElapsed($post['time']); $userlink = "<a class=\"username\" href=\"profile.php?u=".urlencode($username)."\">".utf8entities($username)."</a>"; echo('<div class="post">'.$userlink.' '.utf8entities($post['body'])."<br>"); echo('<i>posted '.$elapsed.' ago via web</i></div>'); return true; } function showUserPosts($userid,$start,$count) { $r = redisLink(); $key = ($userid == -1) ? "timeline" : "posts:$userid"; $posts = $r->lrange($key,$start,$start+$count); $c = 0; foreach($posts as $p) { if (showPost($p)) $c++; if ($c == $count) break; } return count($posts) == $count+1; }如果以后工作要用到redis和python,我倒找个时间用python实现一下这个好玩的retwis。