标签: redis

  • php下用redis解决秒杀超卖问题

    php下用redis解决秒杀超卖问题

    秒杀超卖问题,就是有一个商品抢购活动,一个商品假如有100件库存,但是在抢购时有200人来抢购,这时就会并发,原本只有100的库存但是抢购的人过多,就会发生数据库里原本只有100的库存但是库存为0的时候还会有人提交成功,这就是超卖。

      今天简单的用redis的队列来解决超卖问题。因为redis有list类型,list类型其实就是一个双向链表。通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。先进先出  一端进  一端出这就是队列。这里用redis就解决了并发的问题,在队列里前一个走完之后,后一个才会走。

      实现原理

       将库存循环lpush进一个redis值goods_number里去,然后在下单的时候依次rpop出来。这样就是下一个单取出来一个,然后等goods_number的值为0时,停止下单。

      首先建立三个表 store商品表  order订单表  log日志表

    CREATE TABLE `store` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `goods_id` int(11) NOT NULL,
      `sku_id` int(10) unsigned NOT NULL DEFAULT '0',
      `number` int(10) NOT NULL DEFAULT '0',
      `freez` float(11,2) NOT NULL DEFAULT '0.00' COMMENT '虚拟库存',
      `price` int(10) NOT NULL COMMENT '价格:单位为分',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='库存';
     
    CREATE TABLE `order` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `order_sn` char(32) NOT NULL,
      `user_id` int(11) NOT NULL,
      `status` int(11) NOT NULL DEFAULT '0',
      `goods_id` int(11) NOT NULL DEFAULT '0',
      `sku_id` int(11) NOT NULL DEFAULT '0',
      `number` int(11) NOT NULL,
      `price` int(10) NOT NULL COMMENT '价格:单位为分',
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4743 DEFAULT CHARSET=utf8 COMMENT='订单表';
     
    CREATE TABLE `order` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `order_sn` char(32) NOT NULL,
      `user_id` int(11) NOT NULL,
      `status` int(11) NOT NULL DEFAULT '0',
      `goods_id` int(11) NOT NULL DEFAULT '0',
      `sku_id` int(11) NOT NULL DEFAULT '0',
      `number` int(11) NOT NULL,
      `price` int(10) NOT NULL COMMENT '价格:单位为分',
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4743 DEFAULT CHARSET=utf8 COMMENT='订单表';

    代码如下:

    <?php
    namespace Home\Controller;
    use Think\Cache\Driver\Redis;
    use Think\Controller;
    class TestController extends Controller
    {
        public function index()
        {
            $wheres = array();
            $wheres['goods_id'] = 1;
            $number = M('store')->where($wheres)->getField('number');
            $redis = new Redis();
            for ($i = 0; $i < $number; $i++) {
                $redis->lpush('goods_number', 1);
            }
            echo $redis->llen('goods_number');
        }
        //生成唯一订单号  
        function build_order_no()
        {
            return date('ymd') . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
        }
        //记录日志  
        function insertLog($event, $type = 0)
        {
            $data['event'] = $event;
            $data['type']  = $type;
            $res = M('log')->add($data);
        }
        //模拟下单操作  
        //下单前判断redis队列库存量  
        function order()
        {
            $sku_id  = 11;  //传入已知的sku_id;
            $wheres = array();
            $wheres['sku_id'] = $sku_id;
            $good_info = M('store')->where($wheres)->find();
            $user_id  = rand(1, 200);
            $goods_id = $good_info['goods_id'];
            $price    = $good_info['price'];
            $number   = 1; //抢购时每次买一件商品
    
            $redis = new Redis();
            $count = $redis->rpop('goods_number');  //下单时做rpop 从goods_number中取出1
            if ($count == 0) {
                $this->insertLog('error:no goods_number redis');
                return;
            }
    
            if (($good_info['number'] - $number) <= 0) {
                $this->insertLog('商品售罄');  //如果库存为0写入日志 并停止下单操作
                return;
            }
    
            //生成订单    
            $order_sn = $this->build_order_no();
            $data = array();
            $data['order_sn'] = $order_sn;
            $data['user_id']  = $user_id;
            $data['goods_id'] = $goods_id;
            $data['sku_id']   = $sku_id;
            $data['number']   = $number;
            $data['price']    = $price;
            $order_rs = M('order')->add($data);
            //库存减少  
            $wheres['sku_id'] = $sku_id;
            $store_rs = M('store')->where($wheres)->setDec('number', $number);
            if ($store_rs) {
                $this->insertLog('库存减少成功');
            } else {
                $this->insertLog('库存减少失败');
            }
        }
    }
    

    先访问:/test/index 将redis的list初始化

    然后可以访问:/test/order 进行模拟

  • PHP Redis – zSet(有序集合)

    PHP Redis – zSet(有序集合)

    有序集合与集合一样,string类型元素的集合,不允许重复的成员。

    有序集合,每个元素都会关联一个 double 类型的分数。Redis 通过分数为集合的成员进行从小到大的排序

    有序集合的成员是唯一的,但分数 (score) 可以重复。

    集合是通过哈希表实现的, 集合中最大的成员数为 232 – 1 (4294967295, 每个集合可存储40多亿个成员)。

    添加元素(zAdd)

    // zAdd(key,increment,value)
    $redis->zAdd('score', 550, '语文'); $redis->zAdd('score', 66, '高数'); $redis->zAdd('score', 77, '公共英语'); $redis->zAdd('score', 88, '计算机组成原理',99,'Java程序设计');

    元素分值增减(zIncrBy)

    // zIncrBy(key,increment,value);increment值可为正/负值
    $redis->zIncrBy('score', -10, '语文');
    $redis->zAdd('score', 10, '高数');  

    获取排行(zRevRange)

    // 根据排序后的数据,升序和降序的列表获取
    $redis->zRevRange('score', 0, 2);

    获取指集合中成员排名(zRank,zRevRank)

    // 返回有序集合中指定成员的排名。按分数值递减排序。分数值最大者排名为0。
    $redis->zRevRank('score','语文');
    // 返回有序集合中指定成员的排名,按分数值递增排序。分数值最小者排名为0。
    $redis->zRank('score', 历史);

    获取指定索引区间内的成员(Zrange,zReverseRange)

    // Zrange('set_key', start, end,[true]) 。 true:是否返回各个元素的分数值,默认为false
    // 成员按分数值递增排序。具有相同分数值的成员按字典序倒序排列。
    // start 和 stop 以 0 表示有序集第一个成员,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
    $redis->Zrange('score',0,-1,true);
    // zReverseRange('set_key', start, end,[true]) 成员按分数值递减排序,分数值相同的则按字典序的倒序排序
    $redis->zReverseRange('score',0,-1,true);

    获取指定分值区间的元素(zRangeByScore,zRevRangeByScore)

    // zRangeByScore(key,min,max,[withscores]|[LIMIT offset count])
    $redis->zRangeByScore('score',60,90,['withscores' => true]);
    // zRevRangeByScore(key,max,min,[withscores]|[LIMIT offset count])
    $redis->zRevRangeByScore('score',90,60,['withscores' => true]);
    注:
      ['WITHSCORES' => true] 显示整个有序集合成员的 score 值
      ['LIMIT' =>[0,2]] 自定义返回的有序集合起始位置及条数

    获取元素个数(zCard)

    $redis->zCard('score');

    获取指定分值区间内的元素个数(zCount)

    $redis->zCount('score', 60, 90);

    获取指定元素的 数据 (zScore)

    $redis->zScore('score','历史')

    计算有序集合中指定区间内成员数量(Zlexcount)

    // Zlexcount('key_name','[value1','[value2')
    $redis->Zlexcount('score','[英语','[语文');

    计算给定的有序集的交集数量(Zinterstore)

    // 计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该结果集储存到指定集合
    // 默认情况下,结果集中某个成员的分数值是所有给定集下该成员分数值之和
    // Zinterstore('zset_Key',['keys'|'keyt'])
    $resdis->Zinterstore('score',['score1']);

    计算给定的一个或多个有序集的并集/交集(Zunionstore/zinterstore)

    //计算给定一个或多个有序集的交集,元素相加,并将其存储到目的有序集中
    $redis->zinterstore(‘new_zset_key’,[‘score’,’score1′]);
    //计算给定一个或多个有序集的并集,元素相加,并将其存储到目的有序集中
    $redis->zunionstore(‘new_zset_key’,[‘score’, ‘score1’]);

    删除元素(zRem)

    // 删除有序集中的一个或多个成员,忽略不存在的成员。返回删除的元素个数
    $redis->zRem('score', '历史','盈余');

    根据排名删除(zRemRangeByRank)

    // 自 0 开始
    $redis->zRemRangeByRank('score', 0, 2);

    根据指定分值区间删除(zRemRangeByScore)

    //删除score在[60, 90]之间的元素
    $redis->zRemRangeByScore('score', 60, 90);
  • Python3 Django框架中如何使用cache缓存代码实例

    Python3 Django框架中如何使用cache缓存代码实例

    场景描述

    在Django开发中,我们在 views.py 里用到了很多的sql查询,很多的跨表跨库显示,而且数据量已达到百万级,为了加快速度显示,这边启用了缓存。

    一开始我们想使用 @cache_page() 来达到缓存的目的,但是这样的缓存没有找到清除缓存的方法。

    所以我们只好使用cache的方式来缓存,这里用时间(年-月-日 时:分)md5后作为缓存的key。

    而且百度一下以后发现,关于django的cache讲述得很少。我们这里做一下简单的笔记。需要说明一下的是 cache.get_or_set ,我一开始以为是如果有这个值就自动获取,但是发现页面任然很慢。所以查了一下官方手册。 cache.get_or_set 手册里面写的是默认是设置新值。大家使用时一定要防止踩这个坑。

    cache.get_or_set(key, default, timeout=DEFAULT_TIMEOUT, version=None)

    If you want to get a key’s value or set a value if the key isn’t in the cache, there is the get_or_set() method. It takes the same parameters as get() but the default is set as the new cache value for that key, rather than simply returned:

    Python3 Django框架中的缓存机制

    views.py 代码实例

    from django.core.cache import cache
    @login_required
    def index( request ):
        if request.GET.get('date_joined_0') is None :
            lastDate = SnmpDiff.objects.values('update_time').filter( type = 'computer_room.snmp_95' ).order_by('-id')[1]
            date_joined_0 =  lastDate['update_time'].strftime("%Y-%m-%d")
            date_joined_1 =  lastDate['update_time'].strftime("%H:%M")
        else :
            date_joined_0 = request.GET.get('date_joined_0').replace('/','-')
            date_joined_1 = request.GET.get('date_joined_1')
        date = date_joined_0 + ' ' + date_joined_1
        cache_key = hashlib.md5(('index_'+date).encode(encoding='UTF-8')).hexdigest()
        # result = cache.get_or_set(cache_key, getIndexResult(date),60*60)
        if not cache.get(cache_key) :
            result = cache.get_or_set(cache_key, getIndexResult(date),60*60)
        else:
            result = cache.get(cache_key)

    现在除了第一次打开慢一点之外,其他时间打开飞快。

  • Python3 Django框架中的缓存机制

    Python3 Django框架中的缓存机制

    概述:对于中等流量的网站来说,尽可能的减少开销是非常必要的。缓存数据就是为了保存那些需要很多计算资源的结果,这样的话就不必在下次重复消耗计算资源。获取数据的数据的时候就是去缓存中拿,拿到了直接返回,没拿到就去数据库中查询,筛选,然后缓存到数据库, 然后返回给模板。

    Django自带了一个健壮的缓存系统来保存动态页面,避免每次请求都重新计算。

    Django提供了不同级别的缓存策略,可以缓存特定的视图的输出、可以仅仅缓存那些很难计算出来的部分、或者缓存整个网站

    目的:优化数据结构;优化了对数据的查询;筛选,过滤;减少了对磁盘的IO

    官方文档:https://docs.djangoproject.co…

    设置缓存

    通过设置决定把数据缓存在哪里,是数据库中、文件系统中还是内存中

    默认缓存(内存)

    CACHES={
        'default':{
            'BACKEND':'django.core.cache.backends.locmem.LocMemCache',
            'LOCATION': 'unique-snowflake',
            'TIMEOUT':60
        }
    }

    参数TIMEOUT:缓存的默认过期时间,以秒为单位

    • 默认为300秒
    • 设置为None,表示永不过期
    • 设置为0造成缓存立即失效

    文件缓存

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
            'LOCATION': 'c:/foo/bar',
            'TIMEOUT':300,
        }
    }

    数据库缓存

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
            'LOCATION': 'my_cache_table',
            'TIMEOUT': '60',
            'KEY_PREFIX': 'bbs',
            'VERSION': '1',
            'OPTIONS': {
                'MAX_ENTRIES': '300'
            }
        }
    }

    创建缓存表: python manage.py createcachetable

    drf的缓存

    https://github.com/chibisov/drf-extensions

    这是drf的一个扩展,不止增强了缓存还有其他的。

    pip install drf-extensions
    

    缓存viewset中的retrieve和list 方法是很常见的。这就是为什么CacheResponseMixin存在。

    获取数据的才会用到cache

    from rest_framework_extensions.cache.mixins import CacheResponseMixin

    把这个CacheResponseMixin,放在list之前。尽量放在第一个继承的类

    class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    

    设置过期时间

    REST_FRAMEWORK_EXTENSIONS = {
        'DEFAULT_CACHE_RESPONSE_TIMEOUT': 5
    }
    

    根据自己需求加缓存。目前这个缓存使用的是内存。每次系统重启会丢失。

    redis缓存

    官网: http://django-redis-chs.readt…

    redis操作文档:http://redisdoc.com

    默认使用redis中的1数据库,但可以指定使用哪个db

    安装pip install django-redis

    配置

    # ---配置Session和Cache---
    CACHES={
        'default':{
            'BACKEND': 'django_redis.cache.RedisCache',
            'LOCATION':'127.0.0.1:6379/12', # 指定db12
            'TIMEOUT':60,
            'OPTIONS': {
                'CLIENT_CLASS': 'django_redis.client.DefaultClient',  # 指定连接Redis的客户端类
                # 'PASSWORD': 'mysecret',
                 # "SOCKET_CONNECT_TIMEOUT": 5,  # in seconds
                 # "SOCKET_TIMEOUT": 5,  # in seconds
                 # "CONNECTION_POOL_KWARGS": {"max_connections": 100},
                 # "CONNECTION_POOL_CLASS": "myproj.mypool.MyOwnPool",
            }
        }
    }
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
    # --结束Session和Cache配置
    参数解释
    SOCKET_CONNECT_TIMEOUTsocket 建立连接超时设置
    SOCKET_TIMEOUT连接建立后的读写操作超时设置
    CONNECTION_POOL_KWARGS设置连接池的最大连接数量
    CONNECTION_POOL_CLASS自己的连接池子类

    连接池

    django-redis 使用 redis-py 的连接池接口, 并提供了简单的配置方式. 除此之外, 你可以为 backend 定制化连接池的产生。redis-py 默认不会关闭连接, 尽可能重用连接。

    连接池概念

    为什么使用连接池?首先Redis也是一种数据库,它基于C/S模式,因此如果需要使用必须建立连接,稍微熟悉网络的人应该都清楚地知道为什么需要建立连接,C/S模式本身就是一种远程通信的交互模式,因此Redis服务器可以单独作为一个数据库服务器来独立存在。假设Redis服务器与客户端分处在异地,虽然基于内存的Redis数据库有着超高的性能,但是底层的网络通信却占用了一次数据请求的大量时间,因为每次数据交互都需要先建立连接,假设一次数据交互总共用时30ms,超高性能的Redis数据库处理数据所花的时间可能不到1ms,也即是说前期的连接占用了29ms,连接池则可以实现在客户端建立多个链接并且不释放,当需要使用连接的时候通过一定的算法获取已经建立的连接,使用完了以后则还给连接池,这就免去了数据库连接所占用的时间。

    配置默认连接池

    配置默认连接池很简单, 你只需要在 CACHES 中使用 CONNECTION_POOL_KWARGS 设置连接池的最大连接数量即可

    你可以得知连接池已经打开多少连接:

    from django.core.cache import get_cache
    from django_redis import get_redis_connection
    r = get_redis_connection("default")  # Use the name you have defined for Redis in settings.CACHES
    connection_pool = r.connection_pool
    print("Created connections so far: %d" % connection_pool._created_connections)

    使用自己的连接池子类

    有时你想使用自己的连接池子类. django-redis 提供了 CONNECTION_POOL_CLASS 来配置连接池子类

    myproj/mypool.py

    from redis.connection import ConnectionPool
    class MyOwnPool(ConnectionPool):
        # Just doing nothing, only for example purpose
        pass

    缓存的用法

    单个view缓存

    django.views.decorators.cache.cache_page装饰器用于对视图 views.py 的输出进行缓存

    from django.views.decorators.cache import cache_page
    @cache_page(60 * 2)
    def index(request):
        # return HttpResponse("sunck is a good man")
        return HttpResponse("sunck is a nice man")
    参数:
    timeout : 有效时长  # we've written it as 60 * 15 for the purpose of readability
    cache: 缓存到哪一个库中;很少使用;针对于系统配置了多个缓存
           如: @cache_page(timeout=60, cache='filecache')
    key_prefix: 前缀      

    模板片段缓存

    cache标签: 参数

    • 缓存时间,以秒为单位
    • 给缓存片段起名字
    {#{% load static from staticfiles %}#}
    {% load static %}
    {% load cache %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>主页</title>
    {#    <link rel="stylesheet" type="text/css" href="/static/css/index.css">#}
        <link rel="stylesheet" type="text/css" href="{% static 'css/index.css' %}">
    </head>
    <body>
        <h1>sunck is a nice man</h1>
        {% cache 120 sunck %}
            <h1>nice man</h1>
            <!--<h1>good man</h1>-->
        {% endcache %}
    </body>
    </html>

    原生cache

    from django.core.cache import cache
    查看所有缓存的key: cache.keys('*')
    设置:cache.set(键, 值, 有效时间)
    获取:cache.get(键)
    删除:cache.delete(键)
    清空:cache.clear()
    >>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
    >>> cache.getmany(['a','b','c'])
    {'a': 1, 'b': 2, 'c': 3}
    cache.delete_pattern("foo_*") # 全局通配符
  • WordPress使用Redis同一服务器多站点 缓存配置 防止串内容

    WordPress使用Redis同一服务器多站点 缓存配置 防止串内容

    WordPress 如何启用 Memcached 内存缓存,这边Redis 跟Memcache一样也可以用于缓存,Memcache与Redis的区别这里就不详细说明。关于WordPress的redis插件,我们这里推荐:Redis Object Cache 安装方法直接在插件里搜索即可。

    这里说明的是,如果同一台服务器多站点是使用了Redis或者Memcache的插件就会导致串内容。A站点内容显示到B站点,B站点内容同样显示到A站点去。这里我们说一下详细的插件及配置来解决这个串站的问题。

    在 wp-config.php 中确认配置

    确保缓存已打开

    define('WP_CACHE', true);

    设置缓存的盐值

    define("WP_CACHE_KEY_SALT",'147hub'); //如果多站点共存,不同的站点设置不同的盐值即可

    开启后效果如下图:

    配置 WP Super Cache 插件

    redis成功开启之后就可以在WP Super Cache的高级配置中开启“使用对象缓存系统来存储文件”。

WeChat
↑