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

0.特点:

    a.持久化

    b.单进程、单线程

    c.5大数据类型

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

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

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

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

 

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

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

这里服务器已经启动。

2.redis配置文件初识:

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

如果出现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:'123',      #字符串
            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': '19'})     #直接给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 = '0'
        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=6379,password='cyy520')

#列表左插入
# conn.lpush('k1',11)
# conn.lpush('k1',22)

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.

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

 

posted @ 2018-10-20 19:23  G先生  阅读(547)  评论(0编辑  收藏  举报

:guocheng