zoukankan      html  css  js  c++  java
  • Redis(Remote Dictionary Server)

    一、基本概念

    Redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言的API。

    与传统数据库不同的是redis的数据是存在内存中的,所以读写速度非常快,因此redis被广泛应用于缓存方向。另外,redis也经常用来做分布式锁。Redis提供了多种数据类型来支持不同的业务场景。除此之外,redis支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案。

    为什么要用redis/为什么要用缓存-->高性能;高并发

    为什么要用redis而不用map/guava做缓存?

    缓存分为本地缓存和分布式缓存。以Java为例,使用自带的map或者guava实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着jvm的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性

    使用redis或memcached之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持redis或memcached服务的高可用,整个程序架构上较为复杂。

    redis的线程模型

    redis内部使用文件事件处理器file event handler,这个文件事件处理器是单线程的,所以redis才叫做单线程的模型。它采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器进行处理。

    文件事件处理器的结构包含4个部分:

    多个socket;IO多路复用程序;文件事件分派器;事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

    多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理

    redis和memcached的区别

    1.redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcached支持简单的数据类型,String。

    2.redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中

    3.集群模式,memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是redis目前是原生支持cluster模式的

    4.Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路IO复用模型

    验证redis是否启动:

    ps -ef|grep redis

    lsof -i:6379

    netstat

    分布式数据库中CAP原理:CAP+BASE

    一致性:一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。

    可用性:可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4个9的可用性表示系统99.99%的时间是可用的。在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。

    分区容忍性网络分区指分布式系统中的节点被划分为多个区域,每个区域内部都可以通信,但是区域之间无法通信。在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。

    权衡:在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP理论实际上是要在可用性和一致性之间做权衡

    可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,

    为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性

    为了保证可用性(AP),允许读取所有节点的数据,但是数据可能不一致

    传统的ACID:原子性、一致性、独立性、持久性

    CAP:强一致性、可用性、分区容错性

    CAP理论的核心是:一个分布式系统不可能同时很好的满足这三个,最多只能同时较好的满足两个

    CA-单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大     传统Oracle

    --------------------------->NoSQL数据库必须包含P

    CP-满足一致性,分区容忍性的系统,通常性能不是特别高               Redis、Mongodb

    AP-满足可用性,分区容忍性的系统,通常可能对一致性要求低一些          大多数网站架构的选择

    数据库事务一致性需求:

    BASE:

    分布式:

    集群:

    二、操作命令

    Redis服务器的基本操作

    dbsize      ------      获取当前数据库中键的个数      ------返回当前数据库中键的个数

    info      ------获取Redis数据库的状态信息      ------返回Redis数据库的状态信息

    flushdb      ------清空当前数据库中所有的键      ------成功返回ok

    flushall      ------清空所有数据库中的所有的键      ------成功返回ok

    bgsave      ------将数据保存到rdb中,在后台运行      ------返回后台运行开始

    save      ------将数据保存到rdb中,在前台运行      ------成功返回ok

    config get *      ------获取redis的配置信息

    config get bind      ------获取监听地址

    config get dir      ------获取redis的配置目录

    config set timeout 1000      ------设置连接超时时间

    一些和键相关的通用操作命令

    keys-->keys *     keys set* -->用于查找所有符合给定模式的键,返回符合模糊匹配条件的键

    exists-->exists list-->查找名为list的键是否存在,有list键返回1,否则返回0

    del-->del list-->删除list键,成功返回1,否则返回0

    expire-->expire set1 10-->修改set1的过期时间为10秒,成功返回1,否则返回0

    ttl-->ttl set1-->查看set1键还有多长时间过期,单位是秒,当set1不存在时,返回-2,没设置剩余时间返回-1,否则返回剩余生存时间

    persist-->persist zset-->取消zset的过期时间,成功返回1,否则返回0

    select-->select 1-->选择数据库,默认进入0数据库,成功返回ok,失败返回错误消息

    move-->move set1 2-->把set1移动到2数据库,成功返回1,失败返回错误消息

    randomkey-->randomkey-->随机返回一个键

    rename-->rename key4 keyfansik-->重命名一个键

    type-->type keyfansik-->查看一个键的类型

    redis键命令的基本语法:COMMAND KEY_NAME

    Redis keys命令: 

    DEL key:该命令用于在key存在时删除key

    DUMP key:序列化给定key,并返回被序列化的值

    EXISTS key:检查给定key是否存在

    EXPIRE key seconds:设置过期时间,过期删除

    PEXPIRE key milliseconds:设置key的过期时间以毫秒计

    KEYS pattern:查找所有符合给定模式的key

    move key db:将当前数据库的key移动到给定的数据库db当中

    ttl key:以秒为单位,返回key的剩余生存时间

    type key:返回key所存储的值的类型

    set key k:覆盖

    <?php
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379, 1); //短链接,本地host,端口为6379,超过1秒放弃链接
    //$redis->open('127.0.0.1', 6379, 1); //同上
    $redis->pconnect('127.0.0.1', 6379, 1); //长链接
    //$redis->popen('127.0.0.1', 6379, 1);  //同上
    $redis->auth("2020May07");
    $redis->select('0');  //选择redis库
    $redis->close();  //释放资源
    $redis->ping();  //检查是否还在连接
    $redis->ttl("key");  //查看失效时间
    $redis->persist("key");  //移出失效时间
    $redis->sort('key');
    
    $redis->expire('key', 10);  //设置失效时间
    $redis->move('key', 15);  //把当前的key移到15号库中
    
    //监视一个或多个key,如果在事务执行之前这个key被其他命令所改动,那么事务将被打断
    $redis->watch('key', 'keyn');
    $redis->unwatch('key', 'keyn');
    
    //开启事务,事务块内的多条命令会按照先后顺序放进一个队列中,最后由EXEC命令在一个原子时间内执行
    $redis->multi(Redis::MULTI);
    //开启管道,事务块内的多条命令会按照先后顺序被放进一个队列中,最后由EXEC命令在一个原子时间内执行
    $redis->exec();
    ?>

    Redis常见数据结构-->五大数据类型:String、List、Set、Hash、Zset

    Binary-safe strings:string 一个key对应一个value,string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象。string类型是redis最基本的数据类型,一个redis中字符串value最多可以是512M          String数据结构是简单的key-value类型,value不仅可以是String,也可以是数字,二进制数据等。String类型的值可以被视为整型,可以让“incr”命令族操作(incrby、decr、decrby),在这种情况下,该整型的值限制在64位有符号整数。常规key-value缓存应用;常规计数:微博数,粉丝数等。

    常用命令:SET key value  设置指定key的值

        GET key  获取指定key的值

        GETRANGE key start end  返回key中字符串值的子字符

        GETSET key value  将给定key的值设为value,并返回key的旧值

        GETBIT key offset  对key所存储的字符串值,获取指定偏移量上的位

        MGET key1 key2  获取所有(一个或多个)给定key的值

        SETBIT key offset value  对key所存储的字符串值,设置或清楚指定偏移量上的位

        SETEX key seconds value  将值value关联到key,并将key的过期时间设为seconds

        SETNX key value  只有在key不存在时设置key的值

        SETRANGE key offset value  用value参数覆写给定key所存储的字符串值,从偏移量offset开始

        STRLEN key  返回key所存储的字符串值的长度

        MSET key value key value  同时设置一个或多个key-value对

        MSETNX key value key value  同时设置一个或多个key-value对,当且仅当所有给定key都不存在

        INCR key

        INCRBY key increment

        DECR key

        DECRBY key decrement

        Append key value

        set/get/del/append/strlen    append key value

        INCR(自增1) key / DECR key / INCRBY key increment / DECRBY key decrement

        getrange key start end  相当于between and

        setrange key  xxxx

        setex(set with expire)键秒值/setnx(set if not exist)

        mset/mget/msetnx

     应用场景:使用Redis实现页面缓存

    在动态生成网页的时候,通常会使用模板语言来简化网页的生成操作。但是对于一些不常发生变化的页面,并不需每次访问都动态生成,对这些页面进行缓存,可以减少服务器压力。在输出动态内容之前添加一个中间件,由这个中间件来调用Redis缓存函数,对于不能缓存的页面,函数直接生成页面并返回,对于能够缓存的页面,函数首先从Redis缓存中取出并返回缓存页面,如果缓存页面不存在则生成并缓存到Redis数据库中,并指定过期时间

    <?php
    class redisCache {
        private $redis;
        private $lifetime;  //缓存文件有效期
        private $cacheid;  //缓存文件路径
    
        //构造方法
        function __construct($lifetime=1800)
        {
            $this->redis = new Redis();
            $this->redis->pconnect('127.0.0.2', 6379);
    
            //取得当前页面完整url用md5加密组合,作为页面内容缓存的键
            $this->cacheid = md5($_SERVER['REQUEST_URI']);
            $this->lifetime = $lifetime;
        }
    
        //写入缓存,以浏览器缓存的方式取得页面内容
        public function write() {
            $content = ob_get_contents();  //从浏览器页面获取全部的缓存内容
            ob_end_flush();  //浏览器页面缓存结束,内容输出到页面
    
            //将内容写入Redis中,生成缓存
            if ($this->redis->set($this->cacheid, $content)) {
                //设置缓存生存期
                $this->redis->expireAt($this->cacheid, time()+$this->lifetime);
            } else {
                echo "写入缓存失败";
            }
        }
    
        //加载缓存
        public function load() {
            $content = $this->redis->get($this->cacheid);
    
            if ($content) {
                echo $content;
                exit();  //载入缓存后终止原页面程序的执行,缓存无效则运行原页面程序生成缓存
            } else {
                ob_start(); //开启浏览器缓存用于在页面结尾处取得页面内容
            }
        }
    
        //清除缓存
        public function clean() {
            if (!$this->redis->del($this->cacheid)) {
                echo "清除缓存失败!";
            }
        }
    }
    
    //用法
    $cache = new redisCache(10);
    $cache -> clean();
    $cache -> load();  //装载缓存,缓存有效则不执行以下页面代码
    echo date("Y-m-d : H:i:s");
    $cache->write();  //首次运行或缓存过期,生成缓存
    ?>

    Hash:键值对集合,是一个string类型的field和value的映射表,hash特别适合用于存储对象,后续操作的时候,可以直接仅仅修改这个对象中的某个字段的值。常用命令:hget,hset,hgetall等

        KV模式不变,但V是一个键值对

        hset/hget/hmset/hmget/hgetall/hdel

        hlen

        hexists key  在key里面的某个值的key

        hkeys/hvals

        hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,后续操作的时候,可以直接仅仅修改这个对象中的某个字段的值。比如可以hash数据结构来存储用户信息,商品信息等等

    常用命令:HDEL key field1 [field2]       删除一个或多个哈希表字段

        HEXISTS key field      查看哈希表key中,指定的字段是否存在

        HGET key field      获取存储在哈希表中指定字段的值

        HGETALL key  获取在哈希表中指定key的所有字段和值

        HINCRBY key field increment  为哈希表key中的指定字段的整数值加上增量increment

        HINCRYBYFLOAT key field increment  为哈希表key中的指定字段的浮点数值加上增量increment

        HKEYS key  获取所有哈希表中的字段

        HLEN key  获取哈希表中字段的数量

        HMGET key field1 field2  获取所有给定字段的值

        HMSET key field1 value1 field2 value2  同时将多个field-value对设置到哈希表key中

        HSET key field value  将哈希表key中的字段field的值设为value

        HSETNX key field value  只有在字段field不存在时,设置哈希表字段的值

        HVALS key  获取哈希表中所有值

    使用Redis实现购物车功能

    List:LinkedList

        Redis列表是简单的字符串列表,就是一个链表或者一个队列

      一个字符串链表,left、right都可以插入添加

      如果键不存在,创建新的链表 ;如果键已存在,新增内容; 如果值全移除,对应的键也就消失了

      list就是链表,Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的list结构来实现。

      Redis list的实现为一个双向链表,可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销

      可以通过lrange命令,就是从某个元素开始读取多少个元素,可以基于list实现分页查询,基于redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高

    常用命令:

        lpush/rpush key value1 value2  将一个或多个值插入到列表头部

        lrange key start stop  获取列表指定范围内的元素

        lpop/rpop key  移出并获取列表的第一个元素

        lindex key index  通过索引获取列表中的元素

        llen key  获取列表长度

        lrem n key 删除n个key

        ltrim key  开始index  结束index  截取指定范围的值后再赋给key

        rpoplpush  源列表  目的列表

        lset key index value  通过索引设置列表元素的值

        linsert key before|after pivot value    在列表的元素前或者后插入元素

     应用场景:微博的关注列表、粉丝列表等都可以用Redis的List结构来实现,有的应用使用Redis的List类型实现消息队列,以完成多程序之间的消息交换

    生产者-消费者:lpush-->生产者,rpop-->消费者。如果此时,消费者程序在取出消息元素后立刻崩溃,由于该消息已经被取出且没有被正常处理,那么就认为该消息已经丢失,由此可能导致业务数据丢失,或者业务状态不一致等现象的发生。然而,通过使用rpoplpush命令,消费者程序在从主消息队列取出消息之后再将其插入备份队列中,直到消费者程序完成正常的处理逻辑,再将该消息从备份队列中删除。同时,还可以提供一个守护进程,当发现备份队列中的消息过期时,重新将其放回主消息队列中,以便其他消费者程序继续处理

    实现消息队列:消息队列在异步处理、实现数据顺序排列获取、瞬间爆发等场景应用最为多见

    异步处理

    实现数据顺序排列的获取

    瞬间爆发

    //listpush.php
    <?php
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    
    $msg = array('a', 'b');
    
    foreach ($msg as $k=>$v) {
        $redis->rpush("mylist", $v);
    }
    ?>
    
    //listpop.php
    <?php
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    
    $value = $redis->lpop('mylist');
    
    if ($value) {
        echo "出队的值".$value;
    } else {
        echo "出队完成";
    }
    ?>

    使用消息队列发布微博

    可能出现1秒有上万个用户同时发布消息的情况-->redis  使用redis的List类型作为消息队列,把用户发布的消息暂时存储在消息队列中,接着使用一个cron程序把消息队列中的消息插入MySQL。这样有效降低MySQL的并发量

    <?php
    $uid = get_uid();
    $content = get_content();
    $timestamp = time();
    
    $weibo = new Weibo();
    $weibo -> post($uid, $content, $timestamp)
    ?>
    ---------------------------------------------------------
    
    
    <?php
    $redis = new Redis('127.0.0.1', 6379);
    $redis->connect();
    
    $weibo_info = array(
        'uid'=>get_uid(),
        'content'=>get_content(),
        'timestamp'=>time()
    );
    
    $redis->lpush('weibo_list', json_encode($weibo_info));
    $redis->close();
    ?>
    
    <?php
    $redis = new Redis('127.0.0.1', 6379);
    $redis->connect();
    $weibo = new Weibo();
    
    while (True) {
        if ($redis->lsize('weibo_list') > 0) {
            $info = $redis -> rpop('weibo_list');
            $info = json_decode($info);
            $weibo -> post($info->uid, $info->content, $info->timestamp);
        } else {
            sleep(1);
        }
    }
    
    $redis->close();
    ?>
    //使用消息队列的好处是可扩展性好,当一台redis服务器不能应付大量并发时,使用“一致性Hash算法”把并发分发到不同Redis服务器

    Sets:string类型的无序集合,是通过hashtable实现的

        sadd/smembers/sismember  SADD key member1 member2 向集合添加一个或多个成员

        scard key,获取集合里面的元素个数

        srem key value 删除集合中元素

        srandmember key 某个整数,随机出几个数

        spop key 随机出栈  移除并返回集合中的一个随机元素

        差集:sdiff  在第一个set里面而不再后面任何一个set里面

        交集:sinter  返回给定所有集合的交集

        并集:sunion  返回给定所有集合的并集

        set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的。

        当需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于set轻易实现交集、并集、差集的操作

        比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。

    应用场景:共同好友

     Redis替代文件存储Session

    //PHP的session_set_save_handler(callback open, callback close, callback read, callback write, callback destroy, callback gc) {}
    @param  open  当session打开时调用此函数。接收两个参数,第一个参数是保持session的路径,第二个参数是session的名字
    @param  close  当session操作完成时调用此函数。不接收参数
    @param  read  以session id作为参数,通过session id从数据存储方中取得数据,并且返回此数据。如果数据为空,可以返回一个空字符串。此函数在调用session_start之前被触发
    @param  write  当数据存储时调用
    @param  destroy
    @param  gc

    Sorted Sets:和set一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数却可以重复。

        Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

        不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

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

        集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 

        

         常用命令:zadd,zrange,zrem,zcard等

        和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列

        比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息

  • 相关阅读:
    最后之作-Last Order(bushi
    Hydra's plan
    CSP-S2019游记
    题解 P1407
    初赛排序知识
    题解 洛谷P3469
    二分图网络流知识总结
    题解 SGU294 He's Circles
    noip复习——逆元
    noip复习——线性筛(欧拉筛)
  • 原文地址:https://www.cnblogs.com/liushoudong/p/12677200.html
Copyright © 2011-2022 走看看