Коли виникають великі навантаження на базу данних то ставлять більший сервер, потім ставлять два крутих сервера, далі може ще трішки їх додають, але чим далі - тим все складніше, бо маштабування в звичайних базах данних майже відсутнє. І сьогодні хочу розповісти про key-value бази даних і про приклад їх використання на прикладі блогу зробленого в Django і в якості бази даних використовую Redis. Але зараз вже 6-та година ранку і писати мені лінь, тому дальше буде скрінкаст:
Django and Redis from presidentua on Vimeo.
А знявши скрінкаст побачив, що трішки ранковий голос мене підкачав та й занадто тихо говорю, тому потрібно б розповісти і словами.
Отдже Redis це представник key-value бази даних, тобто такої бази де немає sql, а є лише дві команди set і get, тобто по суті звичайний масив, але через таку простоту база даних працює надзвичайно швидко і дуже легко маштабується і нові сервера доставляються за декілька хвилин. Хоча ці ж обмеження заставляють думати над архітектурою БД набагато більше чим зі звичайною.
Всі ісходники системи можна глянути на github.com/presidentua/Dredis
Отже перейдемо до найбільше цікавого коду, це до файлу views.py, про те нащо потрібні деякі строчки розповім:
import hashlib import time from datetime import datetime import settings from django.shortcuts import redirect from django.views.generic.simple import direct_to_template from models import redis def login_require(func): def wrapper(*args,**kwargs): session = args[0].COOKIES.get('session','error') if session != redis.get('admin:session'): return redirect('/admin/login/') return func(*args,**kwargs) return wrapper def login(request): if request.method == 'POST': password = request.POST.get('password', '') if password == str( redis.get('admin:password') ): response = redirect('/admin/') rand = str( datetime.now() ) + settings.SECRET_KEY session = hashlib.sha1(rand).hexdigest() redis.set('admin:session', session) response.set_cookie('session', session) return response return direct_to_template(request, 'login.html', {}) def logout(request): response = redirect('/') response.set_cookie('session', '') return response @login_require def admin(request): return direct_to_template(request, 'admin.html', {}) @login_require def add_post(request): if request.method == 'POST': id = int(time.time()) redis.push('post:list', id) redis.set('post:title:%s'%id, request.POST['title']) redis.set('post:text:%s'%id, request.POST['text']) redis.set('post:category:%s'%id, request.POST['category']) redis.push('category:item:%s'%request.POST['category'], id) for item in request.POST['tags'].split(' '): redis.push('post:tags:%s'%id, item) redis.push('tags:item:list:%s'%item, id) if redis.get('tags:item:count:%s'%item) > 0: redis.incr('tags:item:count:%s'%item) else: redis.set('tags:item:count:%s'%item, 0) return redirect('/') categories = redis.lrange('category:list', 0, -1) return direct_to_template(request, 'add_post.html', { 'categories': categories, }) @login_require def add_category(request): if request.method == 'POST': redis.push('category:list', request.POST['category']) categories = redis.lrange('category:list', 0, -1) return direct_to_template(request, 'add_category.html', { 'categories': categories, }) def main(request): raw_posts = redis.lrange('post:list', 0, -1) posts = [] for item in raw_posts: posts.append({ 'id': item, 'title': redis.get('post:title:%s'%item), }) return direct_to_template(request, 'main.html', { 'posts': posts, }) def view(request, id=1): title = redis.get('post:title:%s'%id) text = redis.get('post:text:%s'%id) category = redis.get('post:category:%s'%id) tags = redis.lrange('post:tags:%s'%id, 0, -1) return direct_to_template(request, 'view.html', { 'title': title, 'text': text, 'category': category, 'tags': tags, }) def tags(request, tags=''): raw_posts = redis.lrange('tags:item:list:%s'%tags, 0, -1) posts = [] for item in raw_posts: posts.append({ 'id': item, 'title': redis.get('post:title:%s'%item), }) return direct_to_template(request, 'main.html', { 'posts': posts, }) def category(request, category=''): raw_posts = redis.lrange('category:item:%s'%category, 0, -1) posts = [] for item in raw_posts: posts.append({ 'id': item, 'title': redis.get('post:title:%s'%item), }) return direct_to_template(request, 'main.html', { 'posts': posts, })
В 8-мій строчці йде імпортування моделі, а по суті там створюється обьект для роботи з базою.
Далі з 10-тої строчки йде декоратор для тих views, що відносяться до адмінки. Цей код звіряє сесію, і якщо вона є в базі даних, то пускає в адмінку, якщо ні, то переадресую на форму для вводу пароля.
З 18 строчки функція, що показує поле з паролем, і якщо пароль правильний, то створює сесію в базі даних і записує кукіси юзеру.
З 30 строчки код лише обнуляє сесію.
З 40 строчки код по збереженню постів. Спочатку ми створюємо унікальний ідентифікатор цього посту на основі дати публікації, далі записуємо всі дані в окремі ключі. Також в список з ключем post:list в нас список, де зберігаємо ідентифікатори всіх постів, щоб потім їх змогли знайти. Также бачимо що ми зберігаємо категорію таким кодом в 46 строчкі, а потім в 47 строчці маємо список цієї категорії, і до нього додаємо наш ідентифікатор посту для того щоб потім по категорії змогли знайти всі пости, що в ній. Теж саме ми маємо трохи нижче з тегами, але для тегів ще й зберігаємо кількість постів з цим тегом, для того щоб будувати хмарку тегів.
В принципі далі код вже досить стандартний.
Якщо вас зацікавила база данних Редіс, то вона хоститься тут - http://code.google.com/p/redis/
PS: не потрібно дивитися по безпеці цей код, це лише тестовий приклад. А цікаво ви побачили проблемний участок в безпеці. Це не бага, це завдання на хакерську смикалку ;)
