JAVA后端知识点碎片化整理 基础篇(四) 数据存储(主要讲Redis)

因为马上开始2019秋招、平时的学习比较琐碎、JAVA后端博大精深,想在暑假这段时间从头开始整理JAVA知识点查缺补漏,迎战2019秋招。主要参考(微信公众号)JAVA团长与(博客园)五月的仓颉的知识点复习线,对其列出的每一个的知识点再一次的咀嚼并谈谈自己的理解。(平时从这两位学到很多,也非常感谢身边同行的人)补充一下:其中知识点的讲解参考了之前看过的博客讲解或者书籍,之所以称之为基础篇,主要是加深对Java技术栈的宏观认识。

(补充一:Mysql的基础架构20200517)

Mysql的基础架构:当执行一条SQL语句的时候,将会分别经过 客户端 ==》连接器 == 》查询缓存 ==》 分析器 ==》优化器 == 》 执行器 == 》 数据库引擎。重要:优化器的执行流程

Server层:(1)连接器负责管理链接,show processlist可以看到当前连接;还包括权限验证,给这个连接附上你的权限信息。(2)8.0Mysql取消了这个缓存比较鸡肋,因为缓存失效比较频繁,只有表更新就会失效,缓存命中低。(3)分析器:解析SQL语句判断是否赋予Mysql语法。(4)优化器:决定使用哪个索引,Join操作还决定表的连接顺序。(5)执行器,判断你对表T是否有权限,并根据表的定义调用引擎的接口。

一、Mysql索引使用的注意事项

索引的作用:在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,用于提高查询速度。建立合适索引是mysql优化的重要手段。事实上索引也是一种表,保存着主键和索引的字段,以及一个能将每个记录指向实际表中的指针。

使用ALTER TABLE创建索引

//普通索引alter table table_name add index index_name (column_list) ;

//唯一索引alter table table_name add unique (column_list) ;

//主键索引alter table table_name add primary key (column_list) ;

使用CREATE INDEX语句对表增加索引

CREATE INDEX可用于对表增加普通索引和UNIQUE索引,可用于建表时创建索引,create只能一下两种索引

CREATE INDEX index_name ON table_name(username(length));

CREATE UNIQUE INDEX index_name ON table_name(column_list)

Alter TABLE /DROP INDEX来删除索引

drop index index_name on table_name;

alter table table_name drop index index_name;

alter table table_name drop primary key;(一个表只能有一个唯一索引所以 删除一个就可以了)

组合索引ALTER TABLE USER_DEMO ADD INDEX name_city_age(LOGIN_NAME(16),CITY,AGE),合理的使用组合索引有利于提高效率。

利弊:缩影虽然大大提高了查询的速度,但是同时却会降低更新表的速度,因为更新表时,mysql不仅要保存数据,还要保存一下索引文件。索引会占用磁盘空间,如果在一个大表上建立了多种组合索引,则索引文件会膨胀很大。

技巧:1、索引中不会包含null,只要有一列含有Null值,那么这一列对符合索引就是无效的。

2、使用短对串列进行索引,如果可以就应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

3、索引列排 序号mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个列的排序,如果需要最好给这些列建复合索引。

    4.like语句操作一般情况下不鼓励使用like操作,如果非使用不可,注意正确的使用方式。like ‘%aaa%’不会使用索引,而like ‘aaa%’可以使用索引。

   5.不要在列上进行运算

   6.不使用NOT IN 、<>、!=操作,但<,<=,=,>,>=,BETWEEN,IN是可以用到索引的

   7.索引要建立在经常进行select操作的字段上。

   8.索引要建立在值比较唯一的字段上。

二、分表与分库使用场景以及设计方式

对于大型的互联网应用来说,数据库单表的记录行可能达到千万级甚至亿级,并且数据库面临着极高的并发访问,采用Master-Slave复制模式只能对读进行扩展,而且Slave的数量收到Master能力和负载的限制。因此需要对数据库吞吐能力进一步扩展,以满足提高并发访问和海量数据存储的需要。(对于访问极为频繁且数量巨大的单表来说,我们首先要做的就是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐,这就是分表)。例如根据userid%256,进行分表,前台更具对应的订单存储的表进行访问。这样userid便成为一个必须的查询条件,否则将会由于无法定位数据存储的表而无法对数据进行访问。(拆分后的表一般数量是2的n次方,就是上面拆分成256张表的由来),假设userid是257。

分库:当数据库master服务器无法承受住写操作的压力时,不管如何扩展slave服务器,此时都没有意义了。因此我们必须换一种思路,对数据库进行拆分,从而提高数据库写入能力。与分表策略类似,分库可以采用一个关键字取模的方式对数据访问进行路由,如下图所示,还是之前的订单表,假设userid字段的值为258,将原有的单库分为256个,那么应用对数据库的访问将被路由到第二个库。(补充一个随机分片:随机分片其实并不是随机的遵循一定规则,通常我们会采用hash取模的方法进行分片拆分,所以有些时候也需要离散分片,随机分片的数据相对比较均匀不容易出现热点和并发访问的瓶颈,当时后期分片集群扩容需要数据迁移,使用一致性hash算法能很大程度避免这个问题,所以很多中间件分片集群都会采用一致性hash算法,减少扩容时对原有数据的影响,hash环-0到2的32次方-顺时针-只迁移修改的数据)

整合到最后一期可以这样理解,userid单库单表可以拆分成256个库,每个库包含1024个表,那么按照前面所提到的路由策略,对于userid=262145访问,中间变量=262145%(256*1024)=1 路由过程如下,库=中间变量/1024=0;表=1%1024,这意味着对user_id的修改被路由到第一个库第一个表中执行。

三、分库分表带来的分布式困境与应对之策

数据迁移与扩容问题:数据迁移一般做法是程序先读出数据,然后按照指定的分表策略在写入到各个表中,扩容问题在分布式系统中考虑一致性hash算法,首先在服务器节点的hash值并将其配置到0~2的32次方的圆上,然后采用同样的方法求出存储数据键的hash值并映射到相同的圆上。然后从数据映射的位置开始顺时针查找键的hash值,并映射到相同圆上。在从数据映射位置开始顺时针查找将数据保存到第一个遇到的服务器上,如果超过2的32次方还找不到,将其保存到第一台服务器上。一致性hash原理

表关联问题:设计之初就应该避免联合查询,通过程序中进行拼装,或者反范式化设计进行规避。

分页和排序问题:随着分库分表的演变,也会遇到夸库和跨表的排序问题,为了最终结果的准确性,需要在不同的分表中将数据进行排序和返回,并将不同分表返回的结果集进行汇总和再次排序,最后返回给用户。

分布式事务问题:CAP理论,BASE理论。。。那以满足数据强一致性,一般情况下是存储数据尽可能达到用户一致,保证系统经过一段较短的时间的自我恢复和修正,数据最终达到一致。

分布式的全局ID的设计,对真个系统中数据唯一标识。(项目初始不采用分库分表的设计,随着业务的增长在无法继续优化的情况下再考虑分库和分表提高系统的性能)

四、数据库

关系型数据库MySQL:MySQL是最流行的关系型数据库,在互联网产品中应用比较广泛。一般情况下,Mysql数据库示意选择方案,关系型数据库要保证事务的强一致性。同时还要可以执行复杂的SQL查询,值得注意的是是前期尽量减少表的联合查询。随着数据量的增长Mysql已经满足不了大型互联应用的需求,因此Redis基于内存存储数据,可以极大的提高查询性能,对产品的架构上有很好的补充,例如为了提高服务端接口的访问速度,尽可能将读的频率比较高的数据存在redis中。

内存数据库Redis随着数据量的增长MySql已经满足不了大型互联网应用的需求,因此Redis基于内存存储数据,可以极大的提高查询能力,对于产品架构做了很好的补充,例如:为了提高服务端接口的访问速度,尽可能将读频率高的热点放入Redis,使用更多的内存换取CPU的资源,增大内存的消耗,提高程序的运行速度。(Redis做缓存需要考虑数据不一致与脏读、缓存的更新机制、缓存的可用性、缓存服务降级、缓存穿透、缓存预热等缓存使用问题

文档数据库MongoDB:MongoDB是对传统的关系型数据库的补充,他非常适合高伸缩性的场景,他是可扩展性的表结构。基于这一点,可以将预期范围内,表结构不断扩张的Mysql表结构,通过MongoDB来存储,这保证了表结构的扩展性。此外、日志系统数据量比较大,如果用MongoDB数据库来存储这些数据,利用分片集群支持海量数据,MongoDB还吃大尺寸数据存储,(GridFS存储方案就是基于MongoDB的分布式文件存储系统){这里对MongoDB不熟悉:事务支持MongoDB目前只支持单文档事务,需要复杂事务支持场景的暂时不适合。灵活的文档模型:JSON格式存储最接近真实对象模型,对开发者友好,方便快速开发迭代。高可用复制集:满足数据高可靠、服务高可用的需求、运维简单、故障自动切换。高可用复制集:满足数据高可靠、服务高可用的需求、运维简单。可扩展分片集群:海量数据存储,服务能力水平扩展。}

列族数据库HBASE : HBase适合海量数据的存储和高性能实时查询 ,他是运行在HDfas文件之上的,并且作为MapReduce分布式作处理目标数据库,支持离线分析应用。在数据仓库、数据集市、商业智能等领域发挥越来越多的作用,在数以千计的企业中支撑了大量的大数据分析场景的应用。

全文搜索引擎ElasticSearch:在一般情况下,关系型数据库的模糊查询都是通过like方式进行查询,like“value%”可以使用索引,但是对于like“%value%”缩影这样的方式执行全表查询在小规模数据表中,不存在性能问题,但是对于海量数据的全表扫描是一个非常可怕的事情。ElasticSearch作为一个建立在全文搜索引擎A基础上的实时分布式搜索和分析引擎。还可以支持多词条查询,匹配度与权重等,可以将ElasticSeach作为关系型数据的全文搜索功能的补充,将需要进行全文额搜索的数据缓存一份到ElasticSeach上达到复杂的业务和提高查询速度的目的。

五、Redis介绍

Redis的数据类型:Redis支持五种数据类型,String(字符串)、hash(哈希)、list(列表)、set(集合)以及zset(有序集合)。 String字符串是redis的基本类型,一个key一个value,redis的stirng是二进制安全的,也就是redis的string可以包含任何数据。比如JPG图片和序列化对象。string类型的一个键最多能存储512M。插入操作SET name “lizhongheng” 获取操作GET nameHash: Redis hash是一个键值对集合,是string类型的field和value的映射表,hash特别适合用于存储对象。插入操作HMSET myhash field "hello" field "world" 获取操作HGET myhash field1/HGET myhash field2。List :链表lpush(这个是左边插入的意思啊 还有其他的操作可以百度) runoob redis (integer)1(这个是返回的值,表示该list中存在数目);lpush runoob mongodb (integer)2;lpush runoob rabitmq (integer) 3;  这样的话我们从做开始遍历就是rabitmq/mongodb/redis,列表中最多元素存储是2的32次方。Set(集合)是string类型的无序集合,集合是通过hash表实现的所以添加删除查找的复杂度都是O(1)。添加一个string到key对应的set集合中,成功返回1,如果元素已经在集合中返回0,如果key对应的set不存在就会返回错误。sadd runoob redis (integer) 1;sadd runoob mongodb (integer) 1;sadd runoob mongodb (integer)1;sadd runoob rabitmq(integer)1;sadd runoob rabitmq(integer)0(这个代表重复了)。查询操作smembers runoob .....zset(sorted set有序集合)和set一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正式通过分数来为集合中的成员从小到大的排序。zset的成员是唯一的,但分数是可以重复的。zadd runoob 0 redis (integer) 1;zadd runoob 0(这个0代表的权重) mongodb (integer)1;zadd runoob 0 rabitmq (integer)1;zadd runoob 0 rabitmq(integer)0。

String(字符串) 二进制安全 可以包含任何数据比如jpg或者序列化的对象,一个键值最大是512M。Hash 键值对集合,即编程语言中的Map类型,适合存储对象,并且可以像数据库中的update一个属性一样值修改某一项属性值,一般用于存储、读取、修改用户属性。List(列表) 链表(双向链表)增删块,提供了操作某一个元素的API,1、最新消息排行等功能2、消息队列。Set(集合)哈希表的实现,元素不重复。 1、添加、删除、查找的复杂度都是O(1)2、为集合提供了交集、并集、差集等操作。应用场景比如共同好友,利用唯一性统计访问网站的独立ip3、共同好友推荐。Sorted set有序集合,将Set中的元素增加一个权重参数score,元素按score有序排列。数据插入集合时,已经进行天然排序。

 

六、Redis深入浅出:从看到的角度redis是一种key/value数据库,key与value均是使用的对象,分为string、list、hash、set、sorted sort。从内部实现的角度,redis又有八大底层数据结构,REDIS_ENCODING_INT long类型的整数,REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串,REDIS_ENCODING_RAW 简单动态字符串,REDIS_ENCODING_HT 字典,REDIS_ENCODING_LINKEDLIST 双端链表,REDIS_ENCODING_ZIPLIST压缩链表,REDIS_ENCODING_INTSET整数集合,REDIS_ENCODING_SKIPLIST跳跃表和字典。5大类型分别基于八中底层数据结构有不同的实现。

特点:存储效率:redis是专门用于存储数据的,他对于计算机资源主要消耗在于内存,因此节省内存是他非常重要的一个方面,他以为着Redis一定是非常精细地考虑了压缩数据,减少内存碎片问题。快速响应时间:与快速响应时间相对,是高吞吐量。Redis是用于提供在线访问的,对于单个请求的响应时间要求很高,因此快速响应时间是比高吞吐量更重要的目标。(这两个目标相互矛盾)。单线程(Redis的性能瓶颈不在CPU资源,而在于内存访问和网络IO,而采用单线程设计的好处是极大了简化了数据结构和算法的实现,相反Redis通过一部IO和pipeline(netty中)等机制实现了高速的并发访问)。本质上;是一种典型的空间换时间的策略,使用更多的内存换取CPU资源,通过增加系统的内存消耗,来加快程序的运行速度。

这里说一个Redis中比较重要的基础的数据结构:dict。dict是维护key与value映射的数据结构,与很多语言相类似,Redis的一个database中所有的key与value的映射,就是用dict来维护。dict本质是为了解决算法的查找时间(Searching)一般查找问题的解法分为两个大类:一个是基于各种平衡树(数据库),另一个是基于hash表的(后面会提到)。我们平常使用各种map和dictionary,大都是基于hash表实现的,在不要求数据存储有序,且保持较低的hash值冲突的前提下,基于hash表的查找性能能做到非常高效,接近0(1),而且实现简单。dict他是采用某个hash函数从key计算得到在hash表中的位置,采用拉链法解决冲突,并在装载因子超过预定值时自动扩展内存,引发rehash重哈希。Redis的dict实现最显著的特点就在与重哈希。它采用了一种称为增重式的重hash的方法,在需要扩展内存时一次性对所有key进行rehash,而且将重hash操作分享到对于dict的操作。之所以这样设计,是为了避免重hash期间单个请求的响应时间剧烈增加,这个与前面提到的“快速响应时间的设计原则是相符的”

  B树与hash的区别于联系:在关系型数据库中,索引大多采用B/B+树来作为存储结构,而全文搜索引擎主要采用hash的存储结构。这是因为hash的结构导致的检索效率非常高,索引的检索可以一次到位O(1),B树需要从根接到到枝节点,最后才能到叶节点进行多次的IO操作,所以hash的效率远高于B树。数据库索引采用B树结构也是有着自己原因的:hash索引仅能满足等于或不等于,而不能使用范围操作,这是因为hash索引比较的是经常hash运算后的hash的值,只能进行等值过滤,因为经过hash算法处理后的大小关系,不能保证与处理前的hash大小关系相对应,这也就是由于hash本身所导致的。另外就是hash索引遇到了大量hash值相等的情况后性能不一定会比B-tree树高。b-tree完全基于key的比较,与二叉树的道理相同,相当于建个排序后的数据集,使用二叉查找算法实际上也会非常快,受数据增重影响非常小。

  顺便再提一下hash冲突的解决的两种重要方法,1、拉链法、就是在每个位置下拉一条链表,通过set操作时发现重复就填入这个链表中,hashmap的实现非常类似,在某一个链表非常长时,效率也会降低,我们可以通过让hash表尽量的足够长或者将链表用红黑树的数据结构代替来提高效率。2、开放地址法,当发生hash冲突时后,按照某一种方法继续探测hash表中其他存储单元,直到找到空位置位置(极端条件就会遍历整个表)例如第一次hash后发现被占用,那么就H(key)+d如此重复直到找到某一个存储单元为空,将关键字key的数据元素存放到该单元。(增量d可以有不同的取法,我们根据取法不同将其有不同称呼,例如d=1、2、3、4称为线性探测再散列)(2)di=1的平方、2的平方、3的平方这样叫做二次探测再散列。开放地址法:容易产生堆积问题;不适于大规模数据存储;散列函数的设计对冲突会有很大的影响;插入时可能会出现多次冲突的现象,删除的元素是多个冲突元素中的一个,需要对后面的元素做处理实现比较复杂;节点规模很大时会浪费很多空间。链地址法:处理冲突简单,且无堆积现象,平均查找长度短;链表中的节点是动态申请的,适合构造表不能确定长度,链地址法相对于开放地址法比较省空间,插入节点放在链首,删除会比较方便。

七、  Redis的持久化:当我们对数据库进行写操作时主要经过了如下五个个步骤,首先客户端向服务端发送写操作(数据在客户端的内存中),然后数据库服务端接收到写请求的数据(此时数据在服务端的内存中),接着服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中),再接着操作系统将缓冲区的数据转移到磁盘控制器中(数据在磁盘缓冲中),磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。数据的传递由客户端内存-》服务端内存-》系统内存缓存区-》磁盘缓冲区-》进入磁盘。

     但是这是面临着数据损坏的可能,所谓数据损坏,就是数据无法恢复,上面我们讲的都是如何通过保证数据是确定写到磁盘上去,但是写到磁盘上可能并不意味着数据不会损坏,比如我们可能一次写请求会进行两次不同的写操作,当意外发生时,可能会导致一次写操作安全完成,但是另一次还没有进行。如果数据库的数据文件结构组织不合理,就可能导致数据完全不能回复的状况出现。

    这里通常也有三种策略来组织数据,以防止数据文件损坏到无法恢复的情况:(1)最粗糙的处理,就是不通过数据组织形式保证数据的可恢复性。而是通过配置数据同步备份的方式,在数据文件损坏后通过数据备份来进行恢复,实际上MongoDb在不开启操作日志,通过配置Replica Sets时就是这种情况。(2)另一种就是在上面基础上添加一个操作日志,每次操作时记一下操作的行为,这样我们可以通过操作日志来进行恢复,因为操作日志是顺序添加的方式,所以不会出现操作日志无法恢复的情况。(3)更保险的做法是数据不进行旧数据的修改,以追加方式去完成写操作,这样数据本身就是一份日志。

    Redis提供RDB持久化和AOF持久化:

    RDB机制的优势和策略:这是默认的持久化方式,这种方式就是讲内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。(先将数据写入临时文件,写入成功后,在替换之前的文件,用二进制压缩存储)其中的save操作时保存内存快照,每次内存快照都是讲内存数据完整写入到磁盘一次,并不是知识增量同步脏数据,如果数据量增大的话,而且写操作比较多,必然会引起大量的磁盘Io操作,可能会严重的影响性能。优势:(配置3种 900秒后如果有一个key改变,就快照、300秒如果至少10个key变化就内存快照、60秒如果有10000个key变化就内存快照)一旦采用该方式,那么你的整个Redis数据库将只能包含一个文件,这样非常方便进行备份。比如你可能打算每一天归档一份数据;方便 存储,可以将一个个RDB文件移动到存储介质,RDB在大数据集时速度比AOF恢复速度快;RDB可以最大化Redis性能:父进程在保存RDB文件时唯一要做的就是fork一个子线程,然后这个子进程就会处理接下来的保存工作,父进程无需执行任何磁盘的IO操作。劣势:如果需要尽量避免数据丢失,那么RDB不适合你,虽然Redis允许你设置不同的保存点来控制RDB的保存的频率,但是因为RDB文件需要保存整个数据集的状态,所以他并不是一个轻松的操作,因此你可能会至少五分钟保存一次RDB文件。一旦中间发生故障,那么你可能会丢失好几分钟的数据。每次保存RDB的时候Redis需要fork出一个子线程,由这个子线程来进行实际的持久化操作,在数据庞大的时候fork会非常耗时,造成服务器在某毫秒内停止处理客户端请求。数据集非常巨大时候并且CPU资源非常紧张,那么这种停止时间甚至可能长达整整一秒。(虽然AOF也需要fork,但无论AOF重写的执行间隔有多长,数据的耐久性都不会有任何损失)

   AOF机制的优势与劣势:该机制可以带来更高的数据安全性,即数据持久性。AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作、查询操作,以文本的方式记录,可以打开文件看到详细的操作记录,说白就是日志和分布式中的数据一致性的实现有异曲同工之妙。优势:1、这里面有三种同步策略,每秒同步、每修改同步与不同步。事实上每秒同步也是异步完成的,其效率非常高,但是如果系统在这一秒宕机了,那么这一秒的数据也会丢失。而每修改同步,我们可以将其视为同步持久化,每次发生数据变化都会被立即记录到磁盘中,可以预见这种方式在效率上是最低的(过多的同步次数)。2、由于该机制对日志文件的写入类似append,因此在写入过程中即时出现了宕机现象,也不会破坏日志文件已经存在的内容。如果我们本次操作知识写入了一半的数据就出现了系统崩溃的问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性问题。3、如果日志过大,Redis可以自动启动一个rewrite机制(安全机制的核心),即Redis以append模式不断地修改数据写入到磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewreite切换可以更好的保证数据的安全性。4、AOF包含一个格式清晰,易于理解的日志文件用于记录所有的修改操作,事实上,我们也可以通过该文件完成数据的重建。劣势:1、相对于相同数量的数据集,AOF文件通常要大于RDB文件(这是由于命令日志是一个非常庞大的数据,管理维护成本非常高、所以恢复起来也比较慢)。RDB在恢复数据集时的速度比AOF的恢复速度要快。2、根据同步策略的不同,AOF在运行效率上往往会慢与RDB,总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

总结:其实二者选择的标准就看系统愿意牺牲一些性能,换取更高的缓存一致性,还是愿意在写操作频繁的时候,不启用备份来换取更高性能,待手动运行save时候,在做备份rdb。rdb有点类似最终一致性,而aof中每次写都进行日志存储就是类似于强一致性的要求了。(具体的取舍应该根据具体情况确定自己的需求)

八、Redis为什么是单线程的:Redis是一种基于内存的采用的单进程单线程模型的KV数据库,是由C语言编写的。Redis官方对此说明是其基于Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽,既然单线程容易实现,而且CPU不会成为瓶颈,那么顺理成章就采用了单线程。(容易实现。。。单线程下的任何原子操作都可以几乎无代价的实现)补充:关于单线程无法充分发挥多核cpu的性能,我们可以通过多个Redis实例来完善,通过多建立进程的方式发挥多核CPU的性能。

九、Redis快速原因总结:1、完全基于内存的,绝大部分操作时纯的内存操作,非常快速,数据在内存中,类似hashMap查找和操作的时间复杂度比较低O(1)。2、数据结构简单,对数据的操作也简单,Redis中的数据结构是专门进行设计(详细见上)。3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多线程或者多线程之间的切换造成CPU的损耗,不用去考虑锁的问题,不存在加锁和释放锁。4、IO多路复用模型,非阻塞IO(这里IO多路复用指的是,利用select、poll、epoll统建监控多个IO流的操作,空闲的时候会把当前线程阻塞掉,一个或多个IO事件时,就从阻塞状态中唤醒,于是程序轮询一遍所有的流)。

十、缓存使用的注意事项:1、热点数据,缓存才有价值 2、频繁修改的数据,看情况考虑使用缓存3、数据不一致(对缓存设置一个失效时间,超过失效时间就要从数据库中重新加载,因此应用要容忍一定时间的数据不一致性,类似于最终一致性)4、缓存更新机制(数据出现了不一致如何处理,比如设置超时时间,即时有脏数据进入缓存,最多存在30分钟)5、缓存预热(缓存系统启动加载好热点数据)6、缓存服务降级(为了防止redis服务故障,Redis出现问题,不去数据库查询而是直接返回默认值给用户)7、缓存穿透(因为不恰当的业务或者恶意攻击导致请求一些不存在的数据所有请求都会对数据造成很大压力,甚至崩溃,有一个简单的对策就是讲不存在的数据都缓存起来)

(20200517补充)

十一、Redis sort set为什么使用跳表不实用平衡树:参考地址1、skiplist和各种平衡树(如avl、红黑树等)的元素是有序排列的,而hash表上只能做单个key查找,所谓范围查找,是指查找大小在指定两个值之间的所有节点。2、在做范围查询的时候,平衡树比skiplist操作要复杂很多,在平衡树上我们找到指定范围大小的值后,还需要中序遍历的顺序继续寻找其他不超过大值的节点。这里实现复杂,skiplist在进行查询到最小值只需要在第一层链表上遍历到最大值。3、平衡树的缺点就是插入删除容易引发子树的调整,而skiplist插入与删除只改变相邻节点的指针,操作简单快速。4、从内存占用上来说,skiplist比平衡树更灵活,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。(p是最小间隔)

附 平衡树的优点:跳表只能保证平均耗时为O(logn),最差情况下却是O(n),但是平衡树却能做到一定为O(logn)。

十二、Redis 的压缩链表设计  引用知乎redis源码解析

压缩链表(ziplist)是一系列特殊编码的内存块构成的列表,它对于Redis的数据存储优化有着非常重要的作用。首先对于普通双端链表而言,每一个node都包含三个指针,分别指向直接前驱、直接后继、还有指向value的指针。同时对于链表节点的占用内存反复的申请与释放会导致内存碎片的产生。目前Redis链表等初始的哈希对象都是压缩链表作为底层实现,(从计算机角度而言,这种连续内存的设计充分的发挥了cpu缓存)。(举list为例)当list-max-ziplist-entries(最大元素个数) >512;list-max-ziplist-value(最大字符串长度)>64,list不再使用压缩链表。(过大的连续内存不好获取

  • <zlbytes>,该字段固定是一个四字节的无符号整数,用于表示整个压缩链表所占用内存的长度(以字节为单位),这个长度数值是包含这个<zlbytes>本身的。
  • <ztail>,该字段固定是一个四字节的无符号整数,用于表示在链表中最后一个节点的偏移字节量,借助这个字段,我们不需要遍历整个链表便可以在链表尾部执行Pop操作。
  • <zllen>,该字段固定是一个两个字节的无符号整数,用于表示链表中节点的个数。但是该字段最多只能表示2^16-2个节点个数;超过这个数量,也就是该字段被设置为2^16-1时, 意味着我们需要遍历整个链表,才可以获取链表中节点真实的数量。
  • <entry>,该字段表示链表中的一个节点,同一个链表中的节点,其长度大概率是不同的,因此需要特殊的方式来获取节点的长度,具体的内容会在下一个部分详细介绍。
  • <zlend>,该字段可以被认为是一个特殊的<entry>节点,用作压缩链表的结束标记,只有一个字节,存储着0xFF,一旦我们遍历到这个特殊的标记,便意味着我们完成了对这个压缩链表的遍历。

压缩链表结构使用如上方式保持连续,取消了双端链表中指针的使用,具体编码类型不叙述了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值