Redis知识补充(1)

1)Redis本身就是在内存中进行存储数据的,那么为什么不直接定义一个变量来针对数据直接进行存储呢?因为Redis主要是应用于分布式系统,才能发挥它的最大威力,如果只是一个单机程序,通过变量存储数据的方式,是比Redis有着更好的选择

2)因为自己主机定义的变量只能在自己的主机上进行使用,自己的主机有着自己的进程,其他的主机是处于别的进程上面的,如果别的主机也就是别的进程想要进行访问其他主机上的数据只能就通过进程间通信Redis就是基于网络,可以把自己内存中的变量交给别的进程,甚至别的主机上的进程使用,从而让任何一个应用程序能够访问到redis中的变量,从而使数据在分布式系统中可以达到共享使用的效果;

3)和MYSQL相比,更大的劣势就是存储空间特别慢,大多数的互联网产品,都是存在着2 8原则的,20%的热点数据能满足80%的访问需求,增加redis会使系统的复杂程度大大提升,而且如果数据发生修改,还涉及到redis和MYSQL的数据同步问题;

说一下什么是Redis?

1)在内存中存储数据,key都是String类型,Value可以是Redis中任意的数据结构,就比如说MYSQL主要是用过表的形式来进行存储和组织数据的,称之为是关系型数据库,而Redis主要是通过键值对的方式来进行组织数据的,就被称之为是非关系型数据库;

2)可编程的:针对于redis的操作,可以通过简单的交互式命令进行操作,也可以通过脚本的方式,批量的进行一些操作

3)可扩展的:可以在Redis原有的功能基础上再进行扩展,Redis提供了一组API

Redis知识补充(1)_第1张图片 4)持久化:Redis知识补充(1)_第2张图片

5)支持集群

Redis知识补充(1)_第3张图片

6)支持主从复制:

Redis知识补充(1)_第4张图片Redis为什么这么快?

1)Redis数据在内存中,就比访问磁盘的数据库要快很多

2)Redis的核心功能都是比较简单的逻辑,核心功能都是比较简单的操作内存的数据结构

3)在网络角度上,Redis使用了IO多路复用的方式,就是使用一个线程来管理多个Socket连接

4)Redis使用的是单线程模型,虽然更高版本的Redis引入了多线程,但是这样的单线程模型,减少了不必要的线程之间的竞争开销,使用IO多路复用减少等待Socket轮询等待的时间

注意:使用多线程提高效率的前提是CPU密集型的任务,使用多个线程可以充分的利用CPU多核资源,但是Redis的核心任务主要是操作把内存中的数据结构,是不会操作很多CPU的

5)CPU一个核同一时间只能处理一个线程,现代CPU一个物理核心可以有多个逻辑核心

8核16线程,每一个物理核心上有两个逻辑核心,每一个逻辑核心只能同时处理一个线程

使用线程池的时候,线程中的线程数设置成多少适合的呢?

测试+实践

不同的程序特点不同,此时要进行设置的线程数也是不同的,和程序具体的特点关系很大,需要考虑两个极端情况:

1)CPU密集型:每一个线程要执行的任务都是狂转CPU,进行一系列算术运算,此时线程池线程数最多也不应该超过CPU核数,此时如果你设置的更大也没啥用,CPU密集型任务要一直占用CPU,搞那么多线程,但是CPU的坑不够了

2)IO密集型:每一个线程干的工作只是等待IO,读写网卡,读写硬盘,等待用户输入,是不吃CPU的,此时这样的线程处于阻塞状态,是不会参与CPU的调度的,这个时候多搞一些线程也无所谓,此时也不再受限于CPU盒数了,此时此刻理论上线程数设置成无穷大都可以,当然实际上是不行了,因为每一个线程都会占用一定的资源;

然而实际上在实际开发中并没有上述的程序符合这两种理想模型,真实的程序往往一部分要吃CPU,一部分要等待IO,但是具体这个程序,几成工作量是吃CPU的,几成工作量是等待IO的,这个就不确定,有的程序比较多吃CPU8分吃CPU,2分等待IO,线程数就设置的少点

实践中:确定线程数量通过实验的方式,先设置一个数字,看看程序执行效率和系统资源占用是否符合要求,时间开销以及系统资源占用情况,逐渐增大或者是减少这个数字,反复尝试几个数字,挑一个最合适的,不同的程序最终得到的结果肯定是不一样的

Redis的应用场景:

1)实时存储:把Redis当成了数据库,大多数考虑到数据存储,优先考虑的是大,但是仍然有一些场景,考虑的是快,搜索引擎也就是广告搜索(商业搜索),他对于性能的要求是非常高的,所以在实际情况下并没有使用到MYSQL这样的数据库,而是把所有需要进行检索的数据都存放到了内存里面,就是使用类似于Redis这样的内存数据库来完成的,但是像这样的内存数据库,需要进行存储大量的数据,是需要不少的硬件资源的,所以只能靠充值了

如果将redis当成数据库,那么Redis中存放的是全量数据,这里面的数据是不可以随便丢的

2.1)用作于缓存或者是存储session:使用MYSQL存数据太慢,根据二八原则,将热点数据存储在redis中,Redis中存放的是部分数据,全量数据都是依靠MYSQL来进行存储的,哪怕Redis的数据没了,还可以从MYSQL这边加载回来

2.2)介绍一下SessionStorage:

Cookie:主要是用来实现用户信息的保存,是需要Session来进行配合的,Session是存储在应用服务器上面的,在Session里面服务器才真正的保存了用户数据,而Cookie里面只是进行存储了用户的唯一身份标识,SessionID只是浏览器在这边存储了用户的一个唯一身份标识

 Redis知识补充(1)_第5张图片

那么如何解决上述问题呢?

1)想办法让负载均衡器,把办法把同一个用户的请求始终打到同一个机器上面,不是通过轮询的方式而是通过分配userID这样的方式来分配机器;

2)把会话信息单独的拎出来,放到一组独立的机器上面进行存储,况且如果说机器发生了重启,那么保存在内存中的Session会话信息也并不会丢失

Redis知识补充(1)_第6张图片

3)消息队列:充当服务器,基于这个消息队列,可以实现一个网络版本的生产者消费者模型

在分布式系统中,服务器和服务器之间,有时候也是需要使用到生产者消费者模型的,它的优势就是解耦合,肖锋填谷

redis是不适合的场景:不适合存储大规模数据

redis服务器本体负责存储和管理数据

Redis知识补充(1)_第7张图片

Redis的客户端和服务器可以在同一台主机上,也可以在不同的主机上,但是实际上redis服务器不光给一个客户端提供服务,还有可能给很多个客户端提供服务 

Redis知识补充(1)_第8张图片

Redis知识补充(1)_第9张图片1)在单机环境下,还是需要使用HashMap来直接操作内存,而使用Redis是先通过网络进行通信,然后再来操作内存网络传输的速度是远远小于直接操作内存的速度的,现在即使是万兆网卡,虽然已经达到了操作磁盘的速度但是相比于操作内存的速度来说还是要慢上很多的

2)那么在实际的开发场景中,是否要使用到redis呢?还是要结合具体的需求来进行确定,引入redis的缺点,会使系统变得更慢,但是有了redis之后,就可以把数据单独来进行存储,后续涉及到应用服务器重启的时候,是不会影响到数据的内容的,况且如果未来要扩展到分布式系统使用redis也是最佳的,所以我们在引入一个技术的时候,一定要想清楚来龙去脉,想清楚能够解决什么问题,引入了什么问题

1)set key value这里面的key和value都是字符串,对于上述的key和value都不需要加双引号,当然要是给key和value加上引号或者是双引号都是可以的,redis中的命令不区分大小写

2)整体上来说Redis是键值对信息,key固定就是一个字符串,value实际上会有多种类型

3)keys pattern是用来查询当前服务器上匹配的key,可以通过一些特殊符号或者是通配符来描述key的模样,匹配上述模样的key,?匹配任意字符,*是匹配0个或者是多个任意字符,[ae]只能匹配到a或者是e,[abcde]只能匹配abcde别的不行,相当于是给出固定的选项了

[^e]表示排除错误选项,排除e,就是遇到e无法进行匹配,只要不是e都能进行匹配

[a-b]匹配这个a-b范围内的字符,包含两侧边界

4)keys *的时间复杂度是O(n),所以在生产环境上禁止使用keys *,生产环境上的key可能会非常多,而redis只是一个单线程的服务器,执行keys *非常长,于是就使redis服务器被阻塞了,就无法给其他redis客户端提供服务了

Redis知识补充(1)_第10张图片

生产环境/线上环境:

1)办公环境:公司发的电脑,可能是笔记本或者是台式机,现在使用的办公环境和是8C16G12G

2)开发环境:有的时候开发环境和办公环境是同一个,有的时候开发环境就是一个

28C128G64G

Redis知识补充(1)_第11张图片

3)测试环境:测试工程师使用的

4)前面的办公环境,开发环境,测试环境也通称为线下环境,外界用户是无法进行访问到的

而线上环境则是外界用户能够直接访问的,一旦生产环境挂了,必定会给用户使用带来影响,必定会导致公司营收

5)exists:判断一个key或者是多个key是否存在,exists key[key....]返回值:返回key存在的个数

时间复杂度O(1),redis组织这些key的形式就是以hash表的方式进行来进行组织的,redis自身的这些键值对,都是通过哈希表的方式来组织的,而redis中的具体的值中的某一个值又可以是一些数据结构

Redis知识补充(1)_第12张图片

6)redis是一个客户端服务器的程序,客户端和服务器之间是通过网络来进行通信的,每一次我们在控制台上面敲下的命令都会由Redis客户端把他包装成一个请求发送给Redis服务器,服务器再返回响应,第一种写法,一次请求一次响应,就完成了这样的命令,第二种写法是需要四次网络通信才可以完成,这种分开的写法会产生更多轮次的网络通信,网络通信的效率比较低,但是成本比较高(相比内存比);

7)当进行网络通信的时候,发送方发送一个数据,这个数据就需要从应用层到物理层,层层进行封装,每一层都需要加上报头和报尾,就类似于说发一个快递,要包装一下,要包装好几层,接收方收到一个数据,这个数据就需要从物理层到应用层层分用,需要把每一层协议中的报头或者是报尾进行拆分掉,收到一个快递,要拆快递,要拆很多层;

8)还有最终数据封装好了之后需要通过网卡进行转发进行接收,网卡本身是IO设备,效率比CPU比内存还要慢上很多,即使是万兆网卡,他的读写速度也是无法和内存相提并论的,跟何况,客户端和服务器不一定是在一个主机上,中间还可能隔着很远,这个时候的网络传输就需要消耗更多资源,Redis也清楚上述问题,redis很多命令都是一次性支持多个key的命令操作;

9)del+key删除一个或者是多个key,时间复杂度是O(1),返回值是删掉的key的个数,如果是将redis作为数据库,那么此时误删的概率就会变得非常大了

Redis知识补充(1)_第13张图片

如果将redis作为消息队列,具体情况具体分析

10)expire作用是给指定的key设置过期时间,key的存活时间超过这个给定的值就被自动删除,很多业务场景都会有时间限制的,比如说验证码,设置的时间单位是秒,比如说优惠卷,基于redis实现分布式锁,为了避免不能正确解锁的情况,在加锁的情况出现死锁的情况,都会设置一个过期时间,所谓的使用redis作为分布式锁,就是给redis写一个固定的key value,语法格式是expire+key+秒,时间复杂度是O(1),返回值是1设置成功,0设置失败,此处想要通过这个命令来进行设置过期时间,满足的条件必须是针对已经存在的key进行设置

pexpire + key +毫秒

11)ttl命令是进行查看当前key的剩余过期时间是多少,之前在学习网络协议的时候,IP协议,在IP协议报头中就有一个字段叫做TTL,IP数据包中的TTL并不是使用时间来衡量过期的,而是使用次数,pttl+key,如果返回-1表示没有设置相关关联时间,-2表示key不存在

redis的过期策略是如何进行实现的?

一个redis中可能会同时存在很多很多的key,这些key中很大一部分都设置了过期时间,此时redis服务器怎么知道那些key将会被过期删除,哪些key还没有过期呢?

如果说直接进行遍历所有的key,那肯定是行不通的,效率会变得非常的低,一方面效率比较低,另一方面占用CPU又非常的高,这显然是不科学的,Redis此时采用的策略是两方面

1)惰性删除:假设这个key已经到过期时间了,但是暂时还没有删它,key此时还存在,紧接着,后面又来了一次访问,正好使用到了这个key,于是这次访问就会使redis服务器触发删除key的操作,同时在返回一次nil

2)定期删除:每一次抽取一部分,进行验证时间,保证这个抽取检查的过程十足够快的,redis通过进行抽取key的数量来进行控制整体消耗的时间,如果redis中的key很多,那么整体小号的时间就很长从而保证定期删除不会消耗太多的时间

那么为什么会对时间有这么严格的要求呢?

因为当前redis是一个单线程的程序,所有的任务都在主线程完成,处理每一个命令的任务以及说扫描过期key这些操作都是在一个线程内完成的,redis内部实现了一个叫做事件循环的东西,在这个循环里面会不断地进行扫描,看当前有哪些事情要做,假设如果扫描过期key占用了过多的时间,就会导致redis处理命令会造成阻塞,就产生了类似于执行keys *这样的效果,redis就不能快速处理请求了

3)虽然有了上述两种策略相结合,但是整体的效果确实一般,仍然有可能有很多过期的key残留了,没有及时地删除掉,redis为了对上述进行补充,还提供了一系列的内存淘汰策略

redis中并没有采取定时器的方式来实现删除过期key,如果有多个过期key,其实本质上是可4)以通过一个定时器来高效在节省CPU的前提下来进行处理多个key,之前写过的基于优先级队列就可以实现一个高效的定时器,但是redis为什么没有采用这种定时器的方式呢?

原因可能是因为基于定时器实现势必要引入多线程,redis的早期版本就引入了多线程的基调,引入多线程就打破了作者的初衷

class TimeTask{
    private String name;//任务名称
    private long delay;//延迟时间
    private TimerTask task;//任务名称
    public TimeTask(String name,long delay){
        this.name=name;
        this.delay=delay;
        this.task=new TimerTask() {
            @Override
            public void run() {
            System.out.println("我是任务"+name+"执行了");
            }
        };
    }
    public TimerTask getTask(){return task;}
    public long getDelay(){return delay;}
}
class TimeWheel{
    private int size;//时间轮的大小
    private List> buckets;//桶列表
    private Timer timer;//定时器
    private int currentIndex;//当前指针位置
    public TimeWheel(int size){
        this.size=size;
        this.buckets=new ArrayList<>();
        for(int i=0;i());//给桶里面添加若干个ArrayList<>();
        }
    this.timer=new Timer();
    }
    //新增任务
    public void addTask(TimeTask task){
        int index=(currentIndex+(int)(task.getDelay()/1000))%size;
        buckets.get(index).add(task);
    }
    //删除任务
    public void removeTask(TimeTask task){
        for(List list:buckets){
            list.removeIf(t->t==task);
        }
    }
    //启动时间轮
    public void start(){
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                List bucket=buckets.get(currentIndex);
                for(TimeTask task:bucket){
                    task.getTask().run();
                }
                bucket.clear();
                currentIndex=(currentIndex+1)%size;//不断进行移动指针执行任务
            }
        },0,1000);
    }

    public static void main(String[] args) {
        TimeWheel wheel=new TimeWheel(100);
        TimeTask task1=new TimeTask("任务1",5*1000);
        TimeTask task2=new TimeTask("任务2",6*1000);
        TimeTask task3=new TimeTask("任务3",7*1000);
        wheel.addTask(task1);
        wheel.addTask(task2);
        wheel.addTask(task3);
        wheel.start();

    }
}

java 定时器实现原理(时间轮算法) - 掘金 (juejin.cn)

简单介绍一下定时器实现的两种方式:

1)基于优先级队列或者是堆:之前学过的队列是先进先出,但是优先级队列是按照指定的优先级来出,优先级高是自定义的,所以在redis过期key的场景中,就可以通过过期时间越早,那么是优先级越高,现在假设有很多key时间过期了,就可以把这些key加入到一个优先级队列中,指定优先级是过期时间早的,优先出队列,队首元素就是最早要过期的key

我们可以在定时器中分配一个线程,让这个线程去检查队首元素,看看是否过期即可,如果队首元素还没有过期,那么后续的元素一定还没有过期,此时扫描县城就不需要遍历所有的key只是需要盯住这个队首元素即可,另外再进行多线程扫描检查元素过期时间的时候也是不能检查的太频繁,此时的做法就是根据当前时刻和队首元素的过期时间,设置一个等待时间,等待时间差不多到了,系统再来唤醒这个线程,此时进行扫描线程也不需要高频扫描队首元素,把CPU的开销也节省下来了,但是万一在线程进行休眠的时候,来了一个新的任务是比现在当前队首元素执行任务的时间还早执行,那么可以在新任务添加的时候,唤醒一下刚才的线程,重新检查一下队首元素,再来根据时间差距来调整阻塞时间即可

2)基于时间轮:我们可以把时间划分成很多个小段

Redis知识补充(1)_第14张图片

Redis知识补充(1)_第15张图片

Redis知识补充(1)_第16张图片

Redis知识补充(1)_第17张图片

 简单介绍一下Redis中各种数据类型的编码方式:

1)String类型:

1.1)raw是最基本的字符串,底层就是一个byte数组

1.2)int,String类型底层也是可以使用int的编码形式的,当value就是一个整数的时候,此时redis就是用int来进行保存

1.3)embstr,针对于短字符串做出的特殊优化

2)hash类型:

2.1)使用HashTable来进行构成

2.2)使用ZipList底层实现是一个压缩列表,空间利用率比较高,主要的应用场景就是当hash表中元素个数比较少的时候进行使用,哈希表就直接优化成ZipList了(本质上在JAVA中也是一个List)压缩列表可以节省空间,进行顺序查询的时候肯定还是需要进行遍历的,如果ZipList中的元素特别多,就不能通过遍历的方式查找元素了,虽然HashTable占用的空间大,但是查询速度快了

Redis知识补充(1)_第18张图片

3)list类型:

底层是使用链表或者是压缩列表来实现的,底层如果使用链表所占用的内存空间比较大,但是底层如果使用压缩列表所占用的内存空间比较小,但是自从redis3.2之后,针对于list数据结构就引入了新的实现方式,quicklist,他同时兼顾了linkedlist和ziplist的优点,quicklist底层是一个链表,每一个元素又是一个ZipList,每一个ZipList长度又很小

4)ZSet:

intset:底层实现的是集合存放的都是整数

skiplist:跳表底层的实现也是链表,不同于普通的链表,每一个节点上面有多个指针域,巧妙的搭配这些指针域的指向就可以做到从跳表上查询元素的时间复杂度是O(logN)

Redis知识补充(1)_第19张图片

Redis的单线程模型:

1)redis只是使用一个线程,来进行处理所有的命令请求,并不是说一个Redis服务器进程内部真的只有一个线程,其实redis内部也是存在多个线程的,多个线程是在进行处理网络IO,从网卡上面或者是从Socket上面读取数据之后,还是会把数据交给核心的主线程进行处理,执行命令,解析命令都是由主线程进行处理的

2)假设现在有多个客户端进行操作redis

Redis知识补充(1)_第20张图片

Redis知识补充(1)_第21张图片1)CPU密集型的任务使用相同CPU盒数相同的线程,CPU密集型的程序要消耗很多CPU资源

2)但是对于IO密集型任务,由于CPU经常需要等待IO操作完成,因此,在等待过程中,CPU可以去做其他事情,因此采用多线程可以提高程序执行效率,这实质是并发处理

一个计算量很大的程序(也称作CPU密集型程序),多线程跑的时候,不存在阻塞的情况,可以充分利用所有的CPU的核,假如当前CPU只有四核,要使程序运行的效率最高,需使线程池的容量为4,同时跑4个线程的计算任务,如果小于4,则CPU的四核得不到最大利用率,如果大于4,CPU会进行线程间的切换,会有多余的开销,如果是一个需从网络上传下载资源或读取磁盘的程序,可能会由于IO而阻塞程序,如果其他线程在等待阻塞线程结束,会造成CPU资源的浪费

3)redis能够使用单线程模型很好的工作,主要原因在于redis的核心业务逻辑都是短平快的,不太消耗CPU资源也就不会吃太多CPU核数了,所以redis要特别小心,如果某一个操作所占用的时间特别的长,就会阻塞其他命令的执行

redis是单线程模型,但是他为什么这么快呢?

redis这么快,是相比于MYSQL和Oracle其他关系型数据库来说的;

1)redis是操作内存,而MYSQL是操作磁盘

2)redis的核心功能,要比数据库的核心功能更简单,比如说数据库对于数据的插入和删除和查询,都是有更复杂的功能支持,这样的功能势必要花费更多的开销,比如说针对于插入删除以及数据库中的各种约束,都会是数据库做额外的工作,就比如说主键约束和unique约束,当你在进行向数据库中插入一条记录的时候,首先还需要进行查询,本来我的操作是一个简单的插入,结果你现在先进行查询,再来进行插入,而且这个查询操作速度快不快,还是需要看数据量级,因此这样的操作势必就会拖慢速度,MYSQL的查询,比如说分组,连表查询,功能非常复杂,redis要完成的工作更简单,功能也简单,提供的功能相比于MYSQL很少;

3)Redis带来的单线程模型,避免了一些不必要的线程阻塞和线程进行切换的开销,Redis每一个基本操作都是短平快,都不是什么消耗CPU特别高的操作,就算搞多个线程也没啥提升

4)Redis在处理网络IO的时候,使用了epoll这样的IO多路复用机制

1)一个线程管理多个Socket,针对于TCP来说,服务器每进行服务一个客户端,都会给这个客户端安排一个Socket,通过这个Socket来使Redis客户端和服务器之间进行通信,假设一个服务器要服务多个客户端,同时就有很多个Socket,但是这些Socket都是在无时不刻地在进行传输数据吗?

2)很多情况下,每一个客户端和服务器之间的交互也没有那么频繁,排除特殊场景来说,除了上传文件下载文件,看直播之类的,但是一般是web服务器,网络之间的Socket通信也是没有那么频繁的,此时这么多Socket大部分时间是没有数据需要传输的,同一时刻只有少数Socket是活跃的,最开始进行介绍TCP服务器的时候,有一个版本就是为每一个客户端分配一个线程,但是客户端多了,线程就多了,系统开销也就变得更大了,一方面这些线程都是在进行等待的,IO多路复用,一个线程来处理多个Socket,这是操作系统给程序员来提供的机制,提供了一组API,内部的功能就是操作系统内核来进行实现的

3)epoll是事件通知机制,而select比较麻烦,老板都不喊,我就接着三家老板逐个问,此时就可以让一个线程同时干三件事,但是可以高效完成这三件事情的前提是这三件事情的交互都不频繁,大部分时间都在等,如果这三件事都是交互特别频繁的,还是老老实实搞几个线程靠谱,一个线程就容易忙不过来;

Redis知识补充(1)_第22张图片

1)Redis中的key都是字符串,但是value的类型是存在差异的,Redis中的字符串,直接就是按照二进制的方式进行存储的,它不会做任何的编码转换,存的是啥,取出来的还是啥,之前在谈到MYSQL的时候,知道了MYSQL默认的字符集就是拉丁文,如果尝试插入中文就会插入失败

2)Redis中不仅仅可以进行存放文本数据,还可以进行存放整数,普通的文本字符串,JSON,XML二进制数据,图片,视频,音频,但是实际上音视频的体积很可能会很大,Redis针对于String类型,限制了大小是512M,Redis本身是单线程模型,希望进行的操作都比较迅速

String数据类型中的常见命令:

一)Set命令

1)set key value ex 10(这个命令能够保证原子性,设置key和设置时间是同时完成的)

这个命令相当于是set key value,expire key 10,总而言之我们是可以通过这个命令来设置一个超时时间,ex设置超时时间,单位是秒,px设置超时时间,单位是毫秒

2)nx如果这个key不存在,则进行设置,如果这个key存在,则不进行设置,xx正好和nx相反,必须key存在,才进行设置,如果key不存在,那么就不会进行设置,直接返回nil

3)Redis官方文档给出的说明,[]表示一个单独的单元,表示可选项,就是可有可无的,其中的|是或的意思,多个只能出现一个,[]和[]之间只能出现一个

总结:set命令是如果这个key不存在,就创建新的键值对,如果key存在,则是让新的value覆盖旧的value,但是这可能会改变原来的数据类型,原来这个key设置的TTL时间也会失效

二)flushAll(把redis上面的键值对都可以带走)/flushDb

三)mget和mset

虽然可以进行设置多组键值对,但是也不需要设置的太多,很有可能将redis阻塞住

时间复杂度O(N),此时的N并不是整个Redis服务器中所有key的数量,而只是当前命令中一共给出的key的数量,也可以认为是O(1)

四)setNx/SetEx/pSetEx

setNx不存在才可以进行添加键值对,返回值为1设置成功

Redis知识补充(1)_第23张图片

SetEx:设置过期时间,单位是秒

Redis知识补充(1)_第24张图片

pSetEx:设置过期时间单位是毫秒 

Redis知识补充(1)_第25张图片

五)针对于整数进行相加减操作:

1)下面操作的前提是key所对应的value必须是一个整数,这些操作的返回值就是进行相加或者是相减之后的返回值,或者你一开始进行设置的整数不能超过整数的操作的最大范围

此时能够操作的整数的范围是64位,相当于JAVA中的long

2)如果incr操作的key不存在,那么就会将这个key对应的value值设置成1

3)针对于decr来说,我们可以把key所对应的value进行-1操作,key所对应的value必须是整数,在64位的范围之内,如果这个key所对应的value不存在,那么直接当成0来处理,返回结果也是最终运算之后的值;

上述的操作时间复杂度都是O(1)

 Redis知识补充(1)_第26张图片

 Redis知识补充(1)_第27张图片

 Redis知识补充(1)_第28张图片

六:其他命令 

1)append:如果一个key中已经存在并且是一个String,那么命令会将value追加到String的后面,如果key不存在,效果等同于是SET命令,时间复杂度O(1)返回值:追加的字符串的长度

Redis知识补充(1)_第29张图片

Redis知识补充(1)_第30张图片

Redis知识补充(1)_第31张图片解决的方法就是在进行启动redis客户端的时候,就可以加上一个--raw这样的选项,就可以使redis客户端能够自动地将二进制数据尝试进行翻译

2)getrange begin end,这里面的begin和end都是元素对应位置的下标,正常情况下都是从0开始的整数,redis的下标是可以支持负数的,-1就是倒数第一个元素,下标是len-1的元素

Redis知识补充(1)_第32张图片

如果redis中字符串保存的是汉字,此时进行字符串的切分,很可能切出来的并不是完整的汉字了,上述的代码就是强行的切出了中间的四个字节,随便这么一切,那么在utf-8码表上就不知道能查出什么了,JAVA中字符串的基本单位是字符(JAVA中的字符,是占两个字节的的字符),JAVA中相当于是已经帮我们把汉字的编码都准备好了,JAVA中的String相当于是把汉字的编码转换都处理好了

3)setrange key offset value返回值是新进行替换的字符串长度,offset是偏移量是从第几个字节开始进行替换,如果当前value是一个中文字符串,那么在进行setrange的时候,是很有可能会搞出问题的

Redis知识补充(1)_第33张图片

那么如果说这个key原本就不存在于redis中,那么该如何进行使用呢?

结果就会凭空生成出一个字节,这个字节里面的内容就是0X00,aaa就被追加到0X00的后面,所以说setrange针对于不存在的key也是可以操作的,不过会把offset之前的内容会给填充成0X00

Redis知识补充(1)_第34张图片

4)strlen获取字符串的长度单位是字节, 在JAVA中字符串的单位就是以字符为单位的,JAVA中的一个char等于两个字节,JAVA中的char基于unicode这样的编码方式就可以进行表示中文等符号

JAVA中的char使用的是unicode编码,一个汉字就可以代表两个字节

JAVA中的String使用的是utf-8编码,一个汉字就是三个字节了,但是在JAVA的标准库内部,在进行上述的操作过程中一般是感知不到编码方式的变换的

但是Redis并不擅长数据统计,比如想要在上述的Redis中统计播放量前100的视频有哪些,基于Redis搞就会变得很麻烦,相比之下,如果是基于MYSQL,使用order by进行排序操作也是十分方便的

Redis知识补充(1)_第35张图片

像Redis这样的缓存常常用来存储热点数据,也就是高频被使用的数据,这个热点数据常常结合业务场景来进行使用,可以将最近经常使用的数据作为热点数据,这就蕴含了一个问题,一旦某一个数据经常被使用到了,那么是很有可能在最近这段时间就会被反复使用到

但是上述的策略也是存在着一定的问题的:

随着时间的推移,肯定会有越来越多的key在redis上面访问不到,从而从MYSQL读取并写入到redis里面了,那么此时Redis中的数据是不是会变得越来越多?

1)把数据写给redis的同时,同时给这个key设置一个过期时间

2)Redis在内存不足的时候,也提供了淘汰策略 

Redis知识补充(1)_第36张图片

防作弊:放置一个视频用户反复去刷播放量 

按照不同维度来计数:

看看多少个用户只看了个视频的开头,全部看完,点赞数,分享数,评论数

避免单点问题:现在这些数据只能在一个redis上面存放吗?万一这个redis服务器挂了呢?想办法对数据做一些校验,做一些冗余,保证整体系统可用性可以变得更高

Cookie:浏览器这边进行存储数据的方式

Session:服务器这边进行存储数据的机制

Hash数据结构的常见的命令: 

Redis知识补充(1)_第37张图片

一)hget命令和hset命令:

hset:返回值是设置成功的键值对的个数,也就是设置成功的field-value的个数,此处的value只能是字符串了,一次只能进行设置一组键值对

Redis知识补充(1)_第38张图片

 Redis知识补充(1)_第39张图片

hget key field

Redis知识补充(1)_第40张图片

 hexists key field

二)del命令:删除key所对应的field值,返回值是本次删除的字段个数

del删除的是key,而hdel删除的是field,这个field有可能存在也有可能不存在,如果你使用del+key就会把整个hash中的字段全部进行删除了,无论是原来的hash中有多少field现在就是一股脑全部进行删除

Redis知识补充(1)_第41张图片

 三)hkeys:能够获取到hash中的所有字段,hkeys+key

这个操作也是十分危险的,类似于之前所说的keys*,主要我们也不知道某一个hash中是否会存在大量的field

这个命令可以将hash结构中的key所对应的所有的field字段全部列出来,这个操作会根据key找到对应的hash,这个过程的时间复杂度是O(1),然后再来遍历hash这个操作的时间复杂度是O(N),此处的N指的应该是hash中的元素个数

Redis知识补充(1)_第42张图片

hkeys key这个操作是先根据key找到对应的hash,O(1),然后在进行遍历hash,时间复杂度可以达到O(N),这里面的N指的是hash中的元素个数

谈到O(N),有的时候O(N)表示

1)redis中的整体的key的个数

2)当前命令中key的个数(这种情况可以看作是O(1))

3)当前key对应的value中的元素个数

四)hvals key和hkeys相对,是用来获取到hash中的所有value

H系列的命令是用来保证key对应的value得是hash类型的,时间复杂度是O(N),N是hash中的元素个数,如果hash非常大,就有可能导致redis服务器被阻塞住

Redis知识补充(1)_第43张图片

5)hgetall key

最终返回的是key所对应的所有field字段和value字段,这样的命令也是有可能会阻塞redis服务器的,多数情况下,不需要进行查询所有的field,只需要进行查询其中的某几个field即可

Redis知识补充(1)_第44张图片

6)hmget+key+field1+field2.....

1)hmget是类似于之前的mget,可以一次去查询多个field,但是hget一次只能查询一个field

最终查询出来的value的顺序和最终匹配的field的顺序是匹配的

2)有没有hmset来一次性的设置多个field和value值呢,有,但是不会被经常使用,因为hset已经支持一次性设置多个key和value了

Redis知识补充(1)_第45张图片

Redis知识补充(1)_第46张图片

Concurrent扩容的时候会针对hash表中的元素重新进行hash,每一次操作只是搬运一点点

7)hlen+key获取到hash中的元素个数

1)时间复杂度是O(1)使用一个变量直接进行存一下就可以了,不需要进行遍历,返回的是指定key的field-value键值对个数

2)hsetnx:类似于说是setnx,不存在这个值的时候才会设置成功,当存在这个值的时候就会设置失败

Redis知识补充(1)_第47张图片

hash数据类型中的value,当然也是可以根据数字来进行处理的,hincrby就可以进行加减整数,hincrbyfloat就可以加减小数,但是使用频率并不高

Redis知识补充(1)_第48张图片

hstrlen key field返回的是字符,求的是value的长度 

1)像上面的这种hkeys,hvals和hgetall都是存在着一定风险的,hash中的元素个数太多,执行的耗时时间太长,从而会阻塞Redis

2)hscan进行遍历redis中的hash,但是他属于渐进式遍历,敲一个命令,每一次遍历一小部分,再敲一次,每一次在进行遍历一小部分,连续执行多次,就可以进行完成整体的遍历过程了,化整为0

3)压缩:压缩的本质,是针对于数据重新进行编码,不同的数据,有着不同的特点,结合这些特点进行精妙的设计,重新编码之后,就能够缩小体积:

Redis知识补充(1)_第49张图片

4)ZipList也是同理,内部的数据结构也是精心进行设计的,目的就是为了节省内存空间,如果使用一个普通的哈希表,可能说会浪费一定的内存空间,哈希表本身就是一个数组,数组上有的位置有元素,有的位置没有元素,ZipList所付出的代价就是,进行读写元素速度是比较慢的如果元素个数少,那么说明慢的并不明显,但是如果元素变得多了,此时就会雪上加霜

Redis知识补充(1)_第50张图片

如果进行存储结构化的数据,使用hash类型要更合适一些,结构化的数据就是类似于数据库表中的这样的结构

下图是关系型数据库中记录的两条用户信息,用户的属性表现为表的列,每一条用户信息表示为表的行,如果映射关系表示这两个用户信息

Redis知识补充(1)_第51张图片

这种方式相当于把同一个数据的各个属性都分散开进行表示了,低内聚;

Redis知识补充(1)_第52张图片

Redis知识补充(1)_第53张图片

1)上述场景使用String数据类型也能做到,此时就需要使用JSON这样的数据结构,如果使用String的格式来表示User对象,如果想要进行获取到其中的某一个field,或者说修改某一个field,只需要把整个JSON都进行读取出来,这样就非常方便的进行修改或者是获取任何一个属性的值了

2)如果使用hash的方式来进行表示UserInfo,就可以使用field来表示对象中的每一个属性,也就是数据表的每一个列,这样就十分方便的修改或者获取任意一个属性的值了,使用hash的方式要比String的方式读写field字段要更有效,但是付出的是空间的代价,所以我们需要控制哈希在ZipList和hashtable两种内部编码的转换,可能会造成内存的极大消耗

Redis知识补充(1)_第54张图片

Redis知识补充(1)_第55张图片高内聚就是把功能相关的模块集合在一起,举例: 用户模块,就可以把用户的相关内容放在一起,比如用户个人信息,用户一些收藏等,新闻模块,就可以把新闻相关的内容放在一起,比如新闻的一起查看,新闻的详情展示等,如果把新闻相关的内容放到用户模块里面,首先新闻相关得内容和用户模块没有多大得关联性,这样得内聚就不是高聚合 

低耦合就是两个模块或者是代码之间的关联关系,关联关系越大,越容易相互影响,同时也认为是耦合性比较强

list数据结构以及常见命令:

Redis知识补充(1)_第56张图片

Redis知识补充(1)_第57张图片

Redis知识补充(1)_第58张图片注意:lindex可以获取到元素的值,ltrem能够返回被删除元素的值,list列表中的数据是可以重复的,但是hash结构中的field确是不可以重复的,因为当前的list的头和尾都能够高效的的插入元素,于是就可以把这个List当作是一个栈或者是队列来进行使用了

Redis知识补充(1)_第59张图片

Redis如果给定的区间非法,那么比如说超出下标Redis就会尽可能地进行获取到对应的内容

 

你可能感兴趣的:(redis,数据库,缓存)