zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Android多屏幕适配
    android应用签名详解
    内部类与静态内部类详解
    SpringBoot整合Spring Retry实现重试机制
    行为型模式之模板方法模式
    行为型模式之操作复杂对象结构(访问者模式)
    行为型模式之算法的封装与切换(策略模式)
    行为型模式之处理对象的多种状态及其相互转换(状态模式)
    行为型模式之对象间的联动(观察者模式)
    行为型模式之撤销功能的实现(备忘录模式)
  • 原文地址:https://www.cnblogs.com/geogre123/p/9822665.html
Copyright © 2011-2022 走看看