Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4998760
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2011-03-30 13:56:23

In the in our series on we talked about how to get started and the basics of the simple data structures that are available in redis. The simple structures are good for basic operations like storing strings and keeping counters but, using it for anything more complex requires relating one set of data to another. At first glance this is a bit of a problem since redis by design is a flat dictionary with no relations but, with a bit of application code and adherence to some mental programming standards you can build some quite complex applications using redis.

We hinted at this style of structure in the first article when we got to talking about sets with our like example of story voting.

  1. r_server.sadd("story:5419:upvotes", "userid:9102")

Here we have a set in a redis called “story:5419:upvotes” and every element stored in the set is called “userid:xxxx”. This is an example of a basic relation.

Let’s make this example simpler and go with the concept of username / password storage for a website. Instead of using MySQL to store this information we will use redis.

Relations in a NoSQL world

We have to cheat in redis’s flat name space to make relations in our data. Redis isn’t going to be aware of these relations and unlike RDBMS (like MySQL), redis does nothing to help us out. No index’s, no nifty SQL syntax with WHERE or JOIN to do the work for us. We have to handle all of the relational logic in application code, which in turn means you (the developers) have to do extra documentation explaining just how everything fits together in redis or you are going to lose your data. The benefit you though is raw speed and flexibility during development. Don’t like the constraints one data model is giving you? Start over and just recode; no need to alter a DB schema or change a DB server; redis doesn’t care.

Our requirements
  • Users have a username
  • Usernames have a password in a one-to-one fashion
  • We must be able to create a user and assign a password
  • Users must be able to login (check username against password)
  • We must be able to delete a username and their associated password
Designing our redis namespace

This the format of the description of the redis namespace we’ll be using
[key] – {datatype} – (example values)

We also will use the notation *variable* to denote that in a key name is a variable relating to something else.

[users] – {set} – (“adam”, “bob”, “carol”)
[user:*username*:fullname] – {string} – (“Adam Smith”, “Bob Barker”, “Carol Burnett”)
[user:*username*:password] – {string} – (md5 hash password, no example)

That’s it. As long as we stick with this design pattern here we can keep track of all of the users in our system and handle logins.

Creating a new user

 

  1. r = redis.Redis("localhost")

  2. from hashlib import md5

  3.     

  4. def add_user(username, fullname, password):

  5.     if r.sadd("users", username):

  6.         r.set("user:%s:fullname" % username, fullname)

  7.         r.set("user:%s:password" % username, md5(password).hexdigest() )

  8.         return True

  9.     else:

  10.         return False

We’ve created a function called add_user where we are taking in a username, the full name of the user, and password and will return True if we successfully create a new user and False if we don’t. Remember the redis command SADD (Set Add) returns False if the object already exists in the set, so if a user with that username already exists, we can’t create a new user so we return False; otherwise we add it to the set and then we set the associated keys “fullname” and “password” to the appropriate vaues. Hashing the password with a md5 hash since we never store passwords anywhere in plain text.

Let’s take a look at what this looks like being called.

  1. >>> add_user("bob", "Bob Barker", "priceisright")

  2. True

  3. >>> add_user("bob", "Bob Barker", "priceisright")

  4. False

The first time we try to add the username “bob” we succeed but, the second time it fails since there is already a user with that username. Let’s look at the data stored in redis.

 

  1. >>> r.smembers("users")

  2. set(['bob'])

  3. >>> r.get("user:bob:fullname")

  4. 'Bob Barker'

  5. >>> r.get("user:bob:password")

  6. '543f24fdc95f4e3f52fc7a4f2166167e'

We see our key “user” that has a set stored with only one element, our username “bob”. And now we have our related keys “user:*username*:fullname” and “user:*username*:password” which in practice are “user:bob:fullname” and “user:bob:password”. As long as we stick to the naming convention we should always know where everything is!

Let’s go ahead and create a few more users just for good measure

 

  1. >>> add_user("adam", "Adam Smith", "wealthofnations")
  2. True
  3. >>> add_user("carol", "Carol Burnett", "eartug")
  4. True
  5. >>> r.smembers("users")
  6. set(['carol', 'bob', 'adam'])
Logging a user in

Now that we have created our relational data store in redis and have a few users in there, lets try to log one in.

 

  1. def authenticate_user(username, password):

  2.     #if username in r.smembers("users"):

  3.     if r.sismember("user", username):

  4.         passhash = md5(password).hexdigest()

  5.         if passhash == r.get("user:%s:password" % username):

  6.             return True

  7.         else:

  8.             return False

  9.     else:

  10.         return False

First thing we do is check to see if the username is n the set of all of the elements in the key “users” using the method sismember (set – is a member). If the username isn’t there we are going to return False since, that user doesn’t even exist. If the user does exist, I take a hash of their submitted password just like we did when we added the user, then we fetch that password from the related key and see if they are the same, if they are, we return True!

Let’s take a look at this in action

 

  1. >>> authenticate_user("ghost", "idontexist!")
  2. False
  3. >>> authenticate_user("adam", "keynes")
  4. False
  5. >>> authenticate_user("adam", "wealthofnations")
  6. True
  7. >>> authenticate_user("bob", "priceisright")
  8. True

So we can see here, a user that doesn’t exist is not authenticated. A user that does exist but, has the wrong password is denied. Given the right username and the right password a user gets in!. And just to show we can handle multiple users like a proper relational system should “bob” authenticates properly as well

Deleting a user

Eventually we are going to need to delete a user from our system and that is as easy everything else we have done as long as we stick to the same namespace we laid out before

 

  1. def delete_user(username):

  2.     if username in r.smembers("users"):

  3.         r.srem("users", username)

  4.         r.delete("user:%s:fullname" % username)

  5.         r.delete("user:%s:password" % username)

  6.         return True

  7.     else:

  8.         return False

Again the first thing we do is check that the user exists first, since if they don’t no need to do everything else. After we know the user exists, we remove them from the set of “users” then remove their related fullname and password keys. Let’s check this out in action

 

  1. >>> authenticate_user("adam", "wealthofnations")
  2. True
  3. >>> delete_user("adam")
  4. True
  5. >>> authenticate_user("adam", "wealthofnations")
  6. False
  7. >>> delete_user("adam")
  8. False
  9. >>> r.smembers("users")
  10. set(['bob', 'carol'])

We can authenticate the user “adam” properly. Then we delete him, and now he can’t authenticate any more. If we try to delete him again we see that we can’t since he doesn’t exist.

Wrapping Up

We can make very complex relational data structures in redis as long as we document and properly use the name space. Keep everything as simple and as straight forward as possible and document everything throughly. Also make sure all your actions on redis can be keep as modular as possible by wrapping them in functions or classes, just like in this example. Then if for some reason you need to alter your “schema” you only have one place to change it in your application.

Footnote:

This is an example and is not a secure method for handling user submitted data and does no sanitization or sanity checks on the information. I am aware of this but, this post was not designed to be a comprehensive analysis of AAA methods just a quick example of how to do basic relations in a key-value store.

阅读(2184) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~