Redis远程字典服务器(3)——常用数据结构和单线程模型

目录

一,常用数据结构

[1.0 前言](#1.0 前言)

[1.1 string](#1.1 string)

[1.2 hash](#1.2 hash)

[1.3 list](#1.3 list)

[1.4 set](#1.4 set)

[1.5 zset](#1.5 zset)

[1.6 演示](#1.6 演示)

二,关于单线程模型

[2.1 关于Redis的单线程](#2.1 关于Redis的单线程)

[2.2 Redis为什么快](#2.2 Redis为什么快)

一,常用数据结构

1.0 前言

Redis是采用键值对的方式来存储数据的,key强制为字符串类型,value可以为其它的类型,常用的有5种:string(字符串)、list(列表)、hash(哈希)、set(集合)、zset(有序集合),当前版本的redis支持10个,但除了上面5种的其它5种只在某些特殊场景下才会用到,到时候只需要查阅文档即可~

注意:Redis在实现上述数据结构的时候,会在源码层面,针对上述实现进行特定的优化 ,来达到节省 时间/空间 的效果;而这个"特定的优化",就是指Redis内部实现这些数据类型时,会用到其它数据结构的编码方式

例子:Redis承诺,我这个hash表,它进行查询插入删除等操作都保证是O(1),所以Redis的hahs内部可能是其它的数据结构,但是能达到hash的效果,并且达到O(1)

结论:对于上面的5种数据类型,只是Redis承诺给你的,它内部的编码方式是Redis自己实现的,比如我提供一个string的类型,但是它是用字符数组实现的,但是能达到string的效果

1.1 string

数据类型 内部编码
string raw
string int
string embstr
  • raw:是最基本的字符串,底层就是一个char数组(C/C++)或者byte数组(Java)
  • int:Redis也可以用来实现一些"计数"这样的功能,当value就是一个整数的时候,Redis可能直接用int来存
  • embstr:这个是用来针对短字符串进行的特殊优化

1.2 hash

数据类型 内部编码
hash hashtable
hash ziplist
  • hashtable:就是Redis内部自己的哈希表的实现,虽然实现方式可能不太一样,但是整体思路和我们之前学的差不多
  • ziplist:这个叫做"压缩列表",也是一种优化策略,在Redis中,如果很多key的value是hash类型,而且hash里面元素比较少的时候,可能就被优化成ziplist了,能节省空间

1.3 list

数据类型 内部编码
list linklist
list ziplist
  • linklist:这个就是最基本的链表结构,双向带头循环链表
  • ziplist:和上面一样,是压缩列表

上面的两种方式是老版本Redis中的实现方式,在Redis 3.2 版本后,就用quicklist来实现了

  • quicklist: quicklist本体是链表,但是里面的每个元素是ziplist,同时兼顾了两种类型的优点,把空间和效率都折衷兼顾到。(该类型有点类似于C++的deque,但是Java标准库目前我没有了解到有类似的结构)

1.4 set

数据类型 内部编码
set hashtable
set intset
  • hashtable:和上面一样的,是Redis自己实现的哈希表
  • intset:这个也是一个set,但是这个set里面存的类似都是int整数

1.5 zset

数据类型 内部编码
zset skiplist
zset ziplist

这个类型有点特殊,上面四种我们都可以在C++的STL中找到比较相似的容器,skiplist叫做"跳表",这个结构很像我们之前做过的一道题很像:LCR 154. 复杂链表的复制 - 力扣(LeetCode)

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

1.6 演示

bash 复制代码
OBJECT encoding key #该命令可以查看value底层具体的数据类型

二,关于单线程模型

2.1 关于Redis的单线程

前面我们说Redis是单线的,其实指的是Redis只用一个线程去处理所有的命令请求,其实Redis内需也是有多个线程的,它们处理的是网络IO等。

场景:假设现在有2个客户端,同时操作Redis服务器,服务器中有键值对,如下图:

然后客户端发送incr counter命令,表示使key的value进行 +1 操作,两个客户端一起发送时就会有线程安全问题

问题:所以这两个客户端"并发"发起了上述的请求,是否意味着服务器那边也会存在类似的线程安全问题呢?

解答 :并不会。因为Redis服务器实际上是单线程模型,保证了当前收到的多个请求是串行执行的,多个命令到达Redis后,也要在队列中排队,再等待服务器从里面一个一个取

Redis能够使用单线程模型很好的工作,原因在于**Redis的核心业务逻辑,都是短平快的,不太消耗CPU资源,**也就不太吃多核了

弊端:Redis就必须要特别小心,如果某个操作占用时间长,就会阻塞其它命令的执行

2.2 Redis为什么快

  1. Redis主要操作是访问内存,MySQL是访问硬盘,访问内存快很多
  2. Redis的核心功能逻辑,比数据库更简单(数据库对于数据的插入删除查询,都有更复杂的功能,从而势必要花非更多的开销
  3. 单线程模型,避免了一些不必要的线程竞争开销,Redis的每个操作都是短平快的,都是不怎么消耗的CPU的内存数据操作
  4. Redis处理网络IO的时候,使用了epoll这样的IO多路复用机制

下面简单解释下第四点:

  1. 一个线程就可以管理多个socket,针对TCP来说,服务器这边每次要服务一个客户端,就要维护一个socket,一个服务器服务多个客户端,就要维护多个socket。
  2. 但是很多情况下,这些socket也不是一直在传输数据,大部分的socket大部分时间是静默的 --> 同一时刻只有少数socket是活跃的
  3. 我们最开始介绍TCP服务器时,是利用线程池给每个socket分配一个线程的,客户端多了,线程就多了,开销就大了,于是就有了IO多路复用,就可以用一个线程来处理socket,这是操作系统给程序员提供的机制,提供了一套API,内部的功能都是操作系统内核实现的
  4. Linux上提供的IO多路复用,主要是三套API,select,pool,epoll,其中epoll是运行效率最高的机制

举个例子:家里面三个人想吃小吃,三个人分别想吃"饺子","炒饭","煎饼果子",下面有三种方案:

  1. 让一个人去买,先买饺子,跟老板说了之后就等着,当拿到饺子的时候再去买炒饭,煎饼果子同理(原始单线程)
  2. 三个人一起去,各去买自己想吃的(多线程)
  3. 让一个人去,先买饺子,跟老板说了之后,在老板准备饺子的期间,我直接去买炒饭和煎饼果子,然后就是三个同时在做,做好了时候老板喊我去拿,然后我就去拿 (epoll多路复用)

用第三种方法,就可以让一个线程同时做三件事前提是 三件事交互都不频繁,大部分时间都是在等,上面标粗体的老板喊我去拿,这种有通知机制的多路复用就叫做epoll,而另一种select和上面差不多,但是"老板不会喊我",它没有通知机制,只能"我自己来问",效率不高

如果三件事交互是很频繁的,那还是老老实实搞多线程靠谱

相关推荐
苍煜20 小时前
SpringBoot单体应用到分布式下的数据库锁、事务、Redis事务、分布式锁、分布式事务协调
数据库·spring boot·分布式
风筝在晴天搁浅20 小时前
设置一个带超时时间的LRU缓存
缓存
AI进化营-智能译站21 小时前
ROS2 C++开发系列18-STL容器实战:deque缓存激光雷达数据|priority_queue调度任务
开发语言·c++·缓存·ai
xmjd msup21 小时前
mysql的分区表
数据库·mysql
Lyyaoo.21 小时前
【JAVA Spring面经】Spring 事务失效情况
java·数据库·spring
MeAT ITEM21 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dovens21 小时前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
IOT.FIVE.NO.121 小时前
claude code desktop cowork报错解决和记录Workspace..The isolated Linux environment ...
linux·服务器·数据库
Rick199321 小时前
mysql 慢查询怎么快速定位
android·数据库·mysql
科技小花1 天前
全球化深水区,数据治理成为企业出海 “核心竞争力”
大数据·数据库·人工智能·数据治理·数据中台·全球化