这个系列好久没有更新了,主要是之前在实现了那么多功能后处于一个迷惘期,不知道下一步开发/学习的方向是什么。在参考了一些相关资料后,决定给这个博客加上基于redis的缓存机制,点亮Redis这个技能。在这篇博文中,我们将给博客的相关数据加上Redis缓存,以便用户在访问博客时可以更快加载。
Redis是一款基于内存的key-value型数据库,广泛用于缓存服务。由于它将数据存储在内存中,使得其加载数据的速度非常快。并且,Redis也支持持久化,可以将数据存储于硬盘中,当Redis再次启动时,Redis会从硬盘中读取之前存储的数据文件,将其再加载回内存。通常来讲,使用Redis的意义在于减少对硬盘数据库的访问,即当访问网站内容时,先判断在Redis缓存中是否存在,若存在的话可以直接从内存中读取数据,而无需访问硬盘上的数据库;若不存在的话再从硬盘数据库中读取,同时将新数据保存在缓存中,以便之后使用。
Redis的安装可以直接从Redis的官网(此官网最近处于抽风期)上下对应的安装包,下好后解压即可。当解压好后,我们可以进到其目录下的src目录,如我的目录为redis-4.0.8/src,输入
./redis-server
启动redis服务器,如果看到以下画面说明Redis启动成功:
这里出于习惯,我为启动redis服务器设置了别名runredis,大家也可以根据自己的习惯设置别名。
这样,我们的Redis环境就算初步准备好了。Redis的配置文件这里可以先不用特别设置,用默认的就好。
然后,我们要安装redis的python api,这个包已经包含在pypi里,所以我们直接输入命令:
pip3 install redis
就可以完成安装。
我们这次要对博客的标题、内容以及阅读数加入缓存机制,思路如下:
当访问某个博客页面时,首先检查当前博客的标题、内容、阅读数是否在redis缓存中;
若三者都在redis中,直接从redis中取得博客的标题、内容,并对redis中的阅读数+1;
若有某项不在redis中,则从数据库中取得该值,并将其加入redis,以便以后使用,且依然对redis中的阅读数+1;
当某篇博客处于编辑时,在完成编辑并保存时删除redis的标题和内容缓存,以防止缓存和数据库数据不一致;
当某篇博客被删除后,从redis中删除该博客的标题、内容和阅读数缓存;
当首页被访问时,将所有博客在redis中的阅读数回写到sqlite数据库中。
因此,我们需要改动blogs App的相关页面函数以及首页函数。首先来修改blogs/views.py中的content函数:
# blogs/views.py content
from redis import StrictRedis,ConnectionPool
# ...
def content(request,blogId):
pool = ConnectionPool(host='localhost',port='6379',db=0)
redis = StrictRedis(connection_pool=pool)
blog = Blog.objects.get(id=blogId)
title_key = blogId + '_title'
content_key = blogId + '_content'
readcount_key = blogId + '_readcount'
if redis.exists(title_key):
blog_title = redis.get(title_key)
else:
redis.set(title_key, blog.title)
blog_title = redis.get(title_key)
if redis.exists(content_key):
blog_content = redis.get(content_key)
else:
redis.set(content_key, blog.content)
blog_content = redis.get(content_key)
if redis.exists(readcount_key):
redis.incr(readcount_key)
else:
redis.set(readcount_key, blog.readcount)
redis.incr(readcount_key)
comment = Comment.objects.filter(attachedblog=blog)
request.session['currblogId'] = blogId
blogContent = {
'blog_title':blog_title,
'content':blog_content,
'comment_list':comment
}
return render(request,'blogs/content.html',blogContent)
# ...
我们在这里使用ConnectionPool对象建立一个对默认redis数据库的连接,用于之后向redis里加入数据。由于需要保存三个数据在redis,因此我设计了三个key:用于存储标题的title_key,用于存储内容的content_key以及用于保存阅读数的readcount_key,这里使用博客的id+后缀的方式来确保key的唯一性(因为博客的id是唯一的)。在这里,使用redis.exists(key)函数来判断某个key是否存在于redis中,而使用redis.set(key,value)和redis.get(key)函数用于存取redis中的数据。至于redis.incr(key),则是对key中储存的数字值+1,这里用于对阅读数+1。
这里有个小问题没有解决:由于不清楚redis能否存储对象,所以我只能存储blog对象的属性值。然而,为了显示博客下的comment,我又必须通过
blog = Blog.objects.get(id=blogId)
取得对象,才能在后面拿到当前博客下的评论。这样一来,似乎在这里使用redis没有必要了?因为无论如何我都需要从硬盘数据库中拿到这个博客对象到内存中,然后再获取它的标题、内容以及阅读数。这样看来,在这里使用redis有些多此一举,并不能实现绕过硬盘直接拿内存数据的作用。不过,这里使用redis最大的好处是将阅读数的增加放置在了内存中,相比之前的每阅读一次就调用一次blog.save()相比还是节省了很多开销。
下面我们来修改blogs/views.py下的addBlog函数和deleteblog函数。
def clearRedis(keys):
pool = ConnectionPool(host='localhost', port='6379', db=0)
redis = StrictRedis(connection_pool=pool)
for key in keys:
if redis.exists(key):
redis.delete(key)
pool.disconnect()
def addBlog(request):
if request.method == 'POST':
if request.session.has_key('currentblogId'):
blogId = request.session['currentblogId']
tmpBlog = Blog.objects.get(id=blogId)
if tmpBlog:
form = BlogForm(request.POST,instance=tmpBlog)
tmpBlog = form.save(commit=False)
tmpBlog.draft = False
tmpBlog.save()
result_info = 'Success'
else:
form = BlogForm(request.POST)
if form.is_valid():
newBlog = form.save(commit=False)
newBlog.auther = Users.objects.get(username=request.session['username'])
newBlog.draft = False
category = Category.objects.get(categoryname=newBlog.category)
category.blogcount = category.blogcount+1
category.save()
newBlog.save()
result_info = 'Success'
# 当编辑已有博客时,删除redis中内容
title_key = blogId + '_title'
content_key = blogId + '_content'
clearRedis([title_key, content_key])
del request.session['currentblogId']
else:
form = BlogForm(request.POST)
if form.is_valid():
newBlog = form.save(commit=False)
newBlog.auther = Users.objects.get(username=request.session['username'])
newBlog.draft = False
category = Category.objects.get(categoryname=newBlog.category)
category.blogcount = category.blogcount + 1
category.save()
newBlog.save()
result_info = 'Success'
del request.session['currentblogId']
return HttpResponseRedirect(reverse('blogs:addblogResult', kwargs={'info': result_info}))
else:
if request.session['username'] != 'anony':
form = BlogForm()
else:
return render(request, 'blogs/failedoperation.html')
return render(request, 'blogs/addblog.html', {'form':form})
这里我们写一个函数clearRedis,它接受一个key的list,并将这个list中的key都从redis中清空。在addBlog函数中,加粗的地方就是我们新增的代码,使用clearRedis将标题和内容从redis中清除出去。如果没有这一段的话,我们会碰到这样的问题:当一篇博客编辑后发表,缓存中却保存的是旧的内容,因此更新后的博客直至缓存过期都无法被看到。因此我们需要在编辑博客后删除博客的缓存。
def deleteblog(request,blogId):
blog = Blog.objects.get(id=blogId)
if blog.auther.username == request.session['username']:
blog.delete()
title_key = blogId + '_title'
content_key = blogId + '_content'
readcount_key = blogId + '_readcount'
clearRedis([title_key, content_key, readcount_key])
blogList = Blog.objects.filter(auther=request.session['username'])
else:
return render(request, 'blogs/failedoperation.html')
return HttpResponseRedirect(reverse('blogs:blogmanage'))
deleteblog函数中添加的代码与addBlog类似,只不过把阅读数也从缓存中清空,从而将这篇博客从所有数据库中“斩尽杀绝”。
最后一步,修改myblog/views.py中的index函数,将redis中保存的博客阅读数回写到sqlite中,以便在前端显示出来:
def index(request):
try:
username = request.session['username']
user = Users.objects.get(username=username)
except KeyError:
user = Users.objects.get(username='anony')
request.session['username'] = 'anony'
except Users.DoesNotExist:
user = Users.objects.get(username='anony')
request.session['username'] = 'anony'
blogList = Blog.objects.filter(draft=False).order_by('title')
# 从redis中将每篇博客的阅读数回写到数据库中
pool = ConnectionPool(host='localhost', port='6379', db=0)
redis = StrictRedis(connection_pool=pool)
for blog in blogList:
readcount_key = str(blog.id)+'_readcount'
if redis.exists(readcount_key):
blog.readcount = redis.get(readcount_key)
blog.save()
pool.disconnect()
searchform = searchForm()
# get all unread message count
unreadmeg = InfoMessage.objects.filter(attachUser=user).filter(isRead=False)
# username = request.session.get('username')
content = { 'blog_list':blogList,
'curruser':user,
'searchform':searchform,
'msgcount':len(unreadmeg)
}
return render(request, 'myblog/index.html', content)
加粗部分是我们这次新增的代码(如果细心读的话会发现代码与第八篇相比不仅多了redis的东西,还多了用户消息以及实现的一个简单搜索的功能,但这部分的实现个人感觉不是太正规,因此暂时不讲)。在主页上,对于每篇博客我们都要从redis中拿到其阅读数,并将其回写到sqlite中,这样每当有人访问主页时,都会看到正确的阅读数。
在这篇博客中,给我们的博客加入了简单的redis缓存系统,将博客的标题、内容以及阅读数均存在了缓存中。预计最近一段时间都会围绕着redis缓存做文章,希望大家继续关注,也希望大家可以帮我思考一下文中提到的那个小问题~