Redis使用和部分源码剖析以及Django缓存和redis的关系

时间:2023-03-09 05:06:08
Redis使用和部分源码剖析以及Django缓存和redis的关系

0.特点:

    a.持久化

    b.单进程、单线程

    c.5大数据类型

      d.用于操作内存的软件。

      e.虽然是缓存数据库但是可以做持久化的工作

  MySQL是一个软件,帮助开发者对一台机器的硬盘进行操作。

  redis是一个软件,  帮助开发者对一台机器的内存进行操作。

1.使用redis.那么现在我的云服务器上安装了redis,并且启动:

Redis使用和部分源码剖析以及Django缓存和redis的关系

启动以后会看到如下的界面:

Redis使用和部分源码剖析以及Django缓存和redis的关系

这里服务器已经启动。

2.redis配置文件初识:

配置文件路径:cd /etc/redis/redis.conf

Redis使用和部分源码剖析以及Django缓存和redis的关系

如果出现redis下次无法启动的问题,找到该进程,关闭即可重新使用redis。

lsof -i:6379
kill pid
root@iZbp17qwke8fau3qzwo15lZ:~# ps -ef |grep redis
redis 4625 1 0 Oct20 ? 00:01:24 /usr/bin/redis-server 0.0.0.0:6379
root 5761 1 0 Oct21 ? 00:00:48 redis-server *:6379
root 6526 6502 0 09:22 pts/1 00:00:00 grep --color=auto redis
root@iZbp17qwke8fau3qzwo15lZ:~# kill -9 4625

3.下面就是使用python连接到redis.

首先

pip3 install redis

在文件中:

import redis

conn=redis.Redis(host="47.99.191.149",port=6379,password='xxxxx')   #链接到redis
conn.set("x1",'alex') #给redis设置一个值 val=conn.get('x1') #获取x1对应的值
print(val) #alex

第二种方式:

import redis
#推荐使用连接池,链接不断开,不用长期connect
pool=redis.ConnectionPool(host="47.99.191.149",port=6379,password='xxxx',max_connections=1000)
conn=redis.Redis(connection_pool=pool)
conn.set('foo','Bar')

这种都不好,最后墙裂推荐使用单例模式来使用链接池!

1.创建一个单独的文件

import redis
pool = redis.ConnectionPool(host="47.99.191.149", port=6379, password='xxxxx', max_connections=1000)

 

2.在下面文件中导入这个,就是一个天然的单例连接池。(这也是提升redis性能的一个点)

import redis
from redis_poll import pool
# 创建连接池
while True:
key=input("请输入key:")
val=input("请输入val:")
#去连接池中获取链接
conn = redis.Redis(connection_pool=pool)
#设置值
conn.set(key, val)

连接池的源码分析:

使用连接池。不会夯住,因为使用了IO多路复用
while True:
r,w,e=select.select([sk,sk,sk])
    一直在监测
监测到某个sk发来消息,然后处理完返回
三个都来,因为redis是单线程单进程,一个个的来,不用担心效率,内存操作很快。 源码中的连接池这么做!
在set的过程中建立socket对象,发了一个命令过去,发完把这connection从in_use_connection移除,
然后放到可用列表中 self.avaliable_connections.append()进去。
等下次有人想用的时候,直接pop一下把这个对象拿出来继续用。

本质:

本质就是维护一个已经和服务端链接成功的socket.以后再次发送数据直接获取socket,直接send数据,节省了开支,
这就是为什么使用连接池速度快的原因。

最后补充一个小点:

mysql端口号:3306
redis端口 :6379

接着昨天的内容继续写:

首先介绍的就是redis的五大数据类型:

        redis ={
k1:'', #字符串
k2:[1,2,3,4,4,2,1], #列表
k3:{1,2,3,4}, #集合
k4:{name:123,age:666},#字典
k5:{('alex',60),('eva',80),('yuan',70)} #有序集合
}

操作字典:

import redis
pool=redis.ConnectionPool(host='47.99.191.149',port=6379,password="cyy520",max_connections=1000)
conn=redis.Redis(connection_pool=pool) #字典操作:
conn.hset('k4','username','alex')
conn.hset('k4','age',18)
'''
上面的设置相当于下面这种结构
redis={
k4:{
username:alex,
age:18
}
}
'''
val=conn.hget('k4','username') #获取字典内username的值
print(val) #b'alex'
vals=conn.hgetall('k4')             #获取字典内所有的键值
print(vals) #{b'username': b'alex', b'age': b'18'}
 

第二种多种设置方式:

conn.hmset('k5',{'username': 'alex', 'age': ''})     #直接给k5设置键值对,不用像上面一个个的设置
val2=conn.hmget('k5','username','age') #获取多个值。
print(val2) #[b'alex', b'19']

计数器:

原来的数据都写在数据库,每次做更新压力会大。现在不写在数据库,这一天都在redis里写,每天0点只往数据库更新一次,减少数据库的压力。

#计数器:
print(conn.hget('k4','age')) #b'18'
conn.hincrby('k4','age',amount=1) #每次增加1,amount为负时则自减
print(conn.hget('k4','age')) #b'19'

现在抛出了一个问题:

#如果reids的k4对应的字典中假设有1000W条数据,请打印所有的数据

# result=conn.hgetall('k4')
# print(result) #不可取,数据太多内存无法承受,爆栈

如果数据非常的多怎么把呢?通过hgetall取出全部的话,瞬间内存爆栈!

推荐通过下面方法取:

ret=conn.hscan_iter('k4',count=100)  #100个100个的取
for item in ret:
print(item)

这个就是做成一个生成器,一个一个的迭代取。

源码中是这么写的:

    def hscan_iter(self, name, match=None, count=None):
"""
Make an iterator using the HSCAN command so that the client doesn't
need to remember the cursor position. ``match`` allows for filtering the keys by pattern ``count`` allows for hint the minimum number of returns
"""
cursor = ''
while cursor != 0:
#起始位置:0
cursor, data = self.hscan(name, cursor=cursor,
match=match, count=count)
#corsor=100,data=数据
for item in data.items():
yield item #在此yield住

注意事项:
    -拿到的数据是bytes.
    -redis操作时,只有第一层的value支持:list,dict...

            redis={
k3:[1,2,3], #只支持第一层的列表
k4:{
id:1,
title:"xxx",
price_list:[
{id:1,title:"xx"},
{id:2,title:"oo"},
{id:3,title:"qq"},
{id:4,title:"aa"},
]
#把列表json.dumps一下变成字符串
#取回来的时候bytes转成字符串,然后json.loads回来即可
}
}

Redis操作列表:

1.列表左插入

import redis
conn=redis.Redis(host="47.99.191.149",port=,password='cyy520') #列表左插入
# conn.lpush('k1',)
# conn.lpush('k1',)

2.列表右插入

#列表右插入
# conn.rpush('k1',33)

3.左获取

# 左获取
# val=conn.lpop('k1')
# print(val)
# val=conn.blpop('k1',timeout=3)
# print(val) #去取k1的数据,没有数据就夯住,可以加超时时间,过时返回None

4.右获取

#右获取
# val=conn.rpop('k1')
# print(val)
# val=conn.brpop('k1',timeout=3)
# print(val) #去取k1的数据,没有数据就夯住,可以加超时时间,过时返回None

这里在以前业务中使用到这里的一个点:

'''
在这里把爬虫的URL放到一个队列中,爬虫每次去取URL爬取,我们在这边往里面放地址,
放到redis,使用分布式爬取,2台机器共享一个队列,然后每次都brpop一下。
'''

最后就是redis的其他类型都有上面提到的生成器逐步取数据,只有列表没有提供方法,那么需要我们自己来用生成器配合看过源码来照猫画虎做一个。

#通过yield创造一个生成器,一点点的获取数据,灵感源于字典生成器源码
def list_iter(key,count=2):
index=0
while True:
data_list=conn.lrange(key, index, index+count-1)
if not data_list:
return
index+=count for item in data_list:
yield item

利用这个方法就可以通过调用List_iter方法逐步取数据了。

for item in list_iter('k1',count=3):
print(item)

Redis支持事务操作:

import redis
'''
redis={
k1:[1,2,3,4,5]
}
''' conn=redis.Redis(host="47.99.191.149",port=6379,password='cyy520') pipe=conn.pipeline(transaction=True) #创建一个pipe,事务为True
pipe.multi() pipe.set('k2',123)
pipe.hset('k3','n1',666)
pipe.lpush('k4','oldboy') pipe.execute() #一次发送三个命令,要成功都成功,要失败都失败。

4.Django使用redis

1.手动操作redis

想要在django程序中使用redis需要先安装一个模块:

pip3 install django-redis

然后在django的配置文件中设置一下。

#redis配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://47.99.191.149:6379", #redis服务器地址
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100}, #最大连接池100
"PASSWORD": "cyy520",
}
}
}

这样在视图中就可以导入使用redis了。

import redis
from django.shortcuts import render,HttpResponse
from django_redis import get_redis_connection #导入连接池 def index(request):
conn=get_redis_connection('default') #拿到defalut这个redis连接池
conn.set("name","egon") #设置值
return HttpResponse("设置成功!") def order(request):
conn=get_redis_connection('default')
name=conn.get("name")
return HttpResponse(name) #返回值

这样访问order就可以拿到这个对应的值,egon.

Redis使用和部分源码剖析以及Django缓存和redis的关系

2.全站缓存

'django.middleware.cache.UpdateCacheMiddleware'      #最上面
...其他中间件
'django.middleware.cache.FetchFromCacheMiddleware' #最下面

这样全站都缓存上了。

3.视图缓存

只给单视图缓存,把刚才的中间件注释掉。

from django.views.decorators.cache import cache_page

@cache_page(60*15)   #60为秒
def index(request):
ctime=str(time.time())
return HttpResponse(ctime)

4.局部缓存

  应用场景。比如抢购界面的商品简介等等不需要一直加载,可以做缓存,而剩余个数需要实时刷新。

{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>商品剩余个数</h1> {% cache 10 缓存key %}
<div>商品简介</div>
{% endcache %} </body>
</html>v

1.首先{% load cache %}

2.然后给需要缓存的地方加上

    {% cache 10  缓存key %}
<div>商品简介</div>
{% endcache %}

这样这部分东西就会缓存,cache后面的是失效时间,10s,  后面是在redis里面放的缓存key,下面div里面的是key对应的值。

最后补充一点就是rest-framework的访问频率限制就是放在缓存系统中:

源码:

from rest_framework.throttling import SimpleRateThrottle

这里的cache=default_cache

class SimpleRateThrottle(BaseThrottle):
"""
A simple cache implementation, that only requires `.get_cache_key()`
to be overridden. The rate (requests / seconds) is set by a `rate` attribute on the View
class. The attribute is a string of the form 'number_of_requests/period'. Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') Previous request information used for throttling is stored in the cache.
"""
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

点进来发现

cache = DefaultCacheProxy()

这个类就是下面的。

class DefaultCacheProxy:
"""
Proxy access to the default Cache object's attributes. This allows the legacy `cache` object to be thread-safe using the new
``caches`` API.
"""
def __getattr__(self, name):
return getattr(caches[DEFAULT_CACHE_ALIAS], name) def __setattr__(self, name, value):
return setattr(caches[DEFAULT_CACHE_ALIAS], name, value) def __delattr__(self, name):
return delattr(caches[DEFAULT_CACHE_ALIAS], name) def __contains__(self, key):
return key in caches[DEFAULT_CACHE_ALIAS] def __eq__(self, other):
return caches[DEFAULT_CACHE_ALIAS] == other