Redis学習ノート2:簡易twitter版PHP+redis

6997 ワード

1. Data layout
redisを使用すると、テーブルはありません.したがって、keyはオブジェクトを識別し、valueは必要な値を格納する必要があります.retwis(twitterの簡易バージョン)では、INCR操作でユーザーを一意のIDで識別できます.
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を関連付けます.
2. Followers,following, and updates
1人のユーザーにはファンがいるはずで、他の人のファンでもあるはずです.コレクション(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文字の微博をどのように表示しますか?リスト構造を使用できます.ページ全体にユーザーが発表したコンテンツが表示されている場合は、LRANGEを使用してlistのすべてのコンテンツを読み取ることができます.
posts:1000 => a list of post ids - every new post is LPUSHed here

3. Authentication
ランダムで推測できない文字列を使用してユーザーを検証し、クッキーを設定することができます.ユーザー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.一致する場合、認証されたクッキーとして、ランダムで予測不可能な文字列「fea 5 e 81 ac 8 ca 77622 bed 1 c 2132 a 021 f 9」が生成される.
簡単な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.当該ユーザの認証クッキーを取得し、クッキーがない場合、当該ユーザはログインしていない.もしあれば、このクッキーのユーザー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が一致しているかどうかを一度判断する必要がありますか?クッキーがあるかどうかを判断すれば、ユーザーがログインしているかどうかを知ることができるのではないでしょうか.BUGが存在するため、複数のクッキーが1人のユーザを指す可能性があるが、IDは一意である(実際にはIDによってユーザを検証する).では、ユーザーがログアウトするときは、古い認証を削除し、ユーザーに新しい認証を付与する必要があります.(ここで個人的に推測するのは、ユーザの認証クッキーは、アルゴリズムによってユーザ名、パスワードなどを関連付けて生成される予測不可能な文字列である.したがって、ユーザがログインするにはクッキーと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");

4. Updates
ユーザーがマイクロブログを発表したコードは、次のようなものです.
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("
"," ",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");

5. Paginating updates
この段落の原理は分からないので、実際のフレームワークを構築して実行し、コードをデバッグすることで、その原理を理解することができます.
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を実現する時間を探します.