1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

[docs update]添加 Redis String 类型的底层实现

This commit is contained in:
guide 2022-12-03 11:14:20 +08:00
parent fe85a93810
commit d6957bd534
9 changed files with 122 additions and 13 deletions

View File

@ -64,7 +64,7 @@ tag:
> 有能力的人会逐渐升职加薪,是金子总会发光的!!! > 有能力的人会逐渐升职加薪,是金子总会发光的!!!
**2.从底向上,如果父结点比该元素,则该节点和父结点交换,直到无法交换** **2.从底向上,如果父结点比该元素,则该节点和父结点交换,直到无法交换**
![堆-插入元素2](./pictures/堆/堆-插入元素2.png) ![堆-插入元素2](./pictures/堆/堆-插入元素2.png)

View File

@ -9,7 +9,7 @@ tag:
> >
> JavaGuide 已获得作者授权,并对原文内容进行了完善。 > JavaGuide 已获得作者授权,并对原文内容进行了完善。
## 数据库命规范 ## 数据库命规范
- 所有数据库对象名称必须使用小写字母并用下划线分割 - 所有数据库对象名称必须使用小写字母并用下划线分割
- 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) - 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
@ -84,7 +84,7 @@ InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能
存储字节越小,占用也就空间越小,性能也越好。 存储字节越小,占用也就空间越小,性能也越好。
**a.某些字符串可以转换成数字类型存储比如可以将 IP 地址转换成整数据。** **a.某些字符串可以转换成数字类型存储比如可以将 IP 地址转换成整数据。**
数字是连续的,性能更好,占用空间也更小。 数字是连续的,性能更好,占用空间也更小。

View File

@ -21,7 +21,7 @@ head:
关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一行就存放着一条数据(比如一个用户的信息)。 关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一行就存放着一条数据(比如一个用户的信息)。
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/5e3c1a71724a38245aa43b02_99bf70d46cc247be878de9d3a88f0c44.png) ![关系型数据库表关系](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/5e3c1a71724a38245aa43b02_99bf70d46cc247be878de9d3a88f0c44.png)
大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID)。 大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID)。
@ -376,7 +376,7 @@ InnoDB 不光支持表级锁(table-level locking),还支持行级锁(row-level
**表级锁和行级锁对比** **表级锁和行级锁对比**
- **表级锁:** MySQL 中锁定粒度最大的一种锁是针对非索引字段加的锁对当前操作的整张表加锁实现简单资源消耗也比较少加锁快不会出现死锁。其锁定粒度最大触发锁冲突的概率最高并发度最低MyISAM 和 InnoDB 引擎都支持表级锁。 - **表级锁:** MySQL 中锁定粒度最大的一种锁(全局锁除外)是针对非索引字段加的锁对当前操作的整张表加锁实现简单资源消耗也比较少加锁快不会出现死锁。其锁定粒度最大触发锁冲突的概率最高并发度最低MyISAM 和 InnoDB 引擎都支持表级锁。
- **行级锁:** MySQL 中锁定粒度最小的一种锁,是针对索引字段加的锁,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 - **行级锁:** MySQL 中锁定粒度最小的一种锁,是针对索引字段加的锁,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
### 行级锁的使用有什么注意事项? ### 行级锁的使用有什么注意事项?

View File

@ -12,6 +12,18 @@ head:
content: Redis基础数据结构总结String字符串、List列表、Set集合、Hash散列、Zset有序集合 content: Redis基础数据结构总结String字符串、List列表、Set集合、Hash散列、Zset有序集合
--- ---
Redis 共有 5 种基本数据结构String字符串、List列表、Set集合、Hash散列、Zset有序集合
这 5 种数据结构是直接提供给用户使用的,是数据的保存形式,其底层实现主要依赖这 8 种数据结构简单动态字符串SDS、LinkedList双向链表、Hash Table哈希表、SkipList跳跃表、Intset整数集合、ZipList压缩列表、QuickList快速列表
Redis 基本数据结构的底层数据结构实现如下:
| String | List | Hash | Set | Zset |
| :----- | :--------------------------- | :------------------ | :-------------- | :---------------- |
| SDS | LinkedList/ZipList/QuickList | Hash Table、ZipList | ZipList、Intset | ZipList、SkipList |
Redis 3.2 之前List 底层实现是 LinkedList 或者 ZipList。 Redis 3.2 之后,引入了 LinkedList 和 ZipList 的结合 QuickListList 的底层实现变为 QuickList。
你可以在 Redis 官网上找到 Redis 数据结构非常详细的介绍: 你可以在 Redis 官网上找到 Redis 数据结构非常详细的介绍:
- [Redis Data Structures](https://redis.com/redis-enterprise/data-structures/) - [Redis Data Structures](https://redis.com/redis-enterprise/data-structures/)
@ -144,7 +156,7 @@ Redis 中的 List 其实就是链表数据结构的实现。我在 [线性数据
更多 Redis List 命令以及详细使用指南,请查看 Redis 官网对应的介绍https://redis.io/commands/?group=list 。 更多 Redis List 命令以及详细使用指南,请查看 Redis 官网对应的介绍https://redis.io/commands/?group=list 。
**通过 `RPUSH/LPOP` 或者 ` LPUSH/RPOP`实现队列** **通过 `RPUSH/LPOP` 或者 `LPUSH/RPOP`实现队列**
```bash ```bash
> RPUSH myList value1 > RPUSH myList value1

View File

@ -12,6 +12,8 @@ head:
content: Redis特殊数据结构总结HyperLogLogs基数统计、Bitmap 位存储、Geospatial (地理位置)。 content: Redis特殊数据结构总结HyperLogLogs基数统计、Bitmap 位存储、Geospatial (地理位置)。
--- ---
除了 5 种基本的数据结构之外Redis 还支持 3 种特殊的数据结构 Bitmap、HyperLogLog、GEO。
## Bitmap ## Bitmap
### 介绍 ### 介绍

View File

@ -20,7 +20,7 @@ head:
为了满足不同的业务场景Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap。并且Redis 还支持事务 、持久化、Lua 脚本、多种开箱即用的集群方案Redis Sentinel、Redis Cluster 为了满足不同的业务场景Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap。并且Redis 还支持事务 、持久化、Lua 脚本、多种开箱即用的集群方案Redis Sentinel、Redis Cluster
Redis 没有外部依赖Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。 Redis 没有外部依赖Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。
个人学习的话,你可以自己本机安装 Redis 或者通过 Redis 官网提供的[在线 Redis 环境](https://try.redis.io/)来实际体验 Redis。 个人学习的话,你可以自己本机安装 Redis 或者通过 Redis 官网提供的[在线 Redis 环境](https://try.redis.io/)来实际体验 Redis。
@ -36,7 +36,7 @@ Redis 内部做了非常多的性能优化,比较重要的主要有下面 3
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到); - Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到);
- Redis 内置了多种优化过后的数据结构实现,性能非常高。 - Redis 内置了多种优化过后的数据结构实现,性能非常高。
下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770) 下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770) 。
![why-redis-so-fast](./images/why-redis-so-fast.png) ![why-redis-so-fast](./images/why-redis-so-fast.png)
@ -95,7 +95,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
### Redis 除了做缓存,还能做什么? ### Redis 除了做缓存,还能做什么?
- **分布式锁** 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。相关阅读:[《分布式锁中的王者方案 - Redisson》](https://mp.weixin.qq.com/s/CbnPRfvq4m1sqo2uKI6qQw) - **分布式锁** 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:[分布式锁详解](https://javaguide.cn/distributed-system/distributed-lock.html)
- **限流** :一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》](https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA)。 - **限流** :一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》](https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA)。
- **消息队列** Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka有主题和消费组的概念支持消息持久化以及 ACK 机制。 - **消息队列** Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka有主题和消费组的概念支持消息持久化以及 ACK 机制。
- **复杂业务场景** :通过 Redis 以及 Redis 扩展(比如 Redisson提供的数据结构我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。 - **复杂业务场景** :通过 Redis 以及 Redis 扩展(比如 Redisson提供的数据结构我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。
@ -113,6 +113,10 @@ Redis 5.0 新增加的一个数据结构 `Stream` 可以用来做消息队列,
相关文章推荐:[Redis 消息队列的三种方案List、Streams、Pub/Sub](https://javakeeper.starfish.ink/data-management/Redis/Redis-MQ.html)。 相关文章推荐:[Redis 消息队列的三种方案List、Streams、Pub/Sub](https://javakeeper.starfish.ink/data-management/Redis/Redis-MQ.html)。
### 如何基于 Redis 实现分布式锁?
关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:[分布式锁详解](https://javaguide.cn/distributed-system/distributed-lock.html) 。
## Redis 数据结构 ## Redis 数据结构
### Redis 常用的数据结构有哪些? ### Redis 常用的数据结构有哪些?
@ -140,6 +144,83 @@ Redis 5.0 新增加的一个数据结构 `Stream` 可以用来做消息队列,
在绝大部分情况,我们建议使用 String 来存储对象数据即可! 在绝大部分情况,我们建议使用 String 来存储对象数据即可!
### String 的底层实现是什么?
Redis 是基于 C 语言编写的,但 Redis 的 String 类型的底层实现并不是 C 语言中的字符串(即以空字符 `\0` 结尾的字符数组),而是自己编写了 [SDS](https://github.com/antirez/sds)Simple Dynamic String简单动态字符串 来作为底层实现。
SDS 最早是 Redis 作者为日常 C 语言开发而设计的 C 字符串,后来被应用到了 Redis 上,并经过了大量的修改完善以适合高性能操作。
Redis7.0 的 SDS 的部分源码如下https://github.com/redis/redis/blob/7.0/src/sds.h
```c
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
```
通过源码可以看出SDS 共有五种实现方式 SDS_TYPE_5并未用到、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64其中只有后四种实际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
| 类型 | 字节 | 位 |
| -------- | ---- | ---- |
| sdshdr5 | < 1 | <8 |
| sdshdr8 | 1 | 8 |
| sdshdr16 | 2 | 16 |
| sdshdr32 | 4 | 32 |
| sdshdr64 | 8 | 64 |
对于后四种实现都包含了下面这 4 个属性:
- `len` :字符串的长度也就是已经使用的字节数
- `alloc`总共可用的字符空间大小alloc-len 就是 SDS 剩余的空间大小
- `buf[]` :实际存储字符串的数组
- `flags` :低三位保存类型标志
SDS 相比于 C 语言中的字符串有如下提升:
1. **可以避免缓冲区溢出** C 语言中的字符串被修改比如拼接一旦没有分配足够长度的内存空间就会造成缓冲区溢出。SDS 被修改时,会先根据 len 属性检查空间大小是否满足要求,如果不满足,则先扩展至所需大小再进行修改操作。
2. **获取字符串长度的复杂度较低** C 语言中的字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。SDS 的长度获取直接读取 len 属性即可,时间复杂度为 O(1)。
3. **减少内存分配次数** 为了避免修改(增加/减少字符串时每次都需要重新分配内存C 语言的字符串是这样的SDS 实现了空间预分配和惰性空间释放两种优化策略。当 SDS 需要增加字符串时Redis 会为 SDS 分配好内存,并且根据特定的算法分配多余的内存,这样可以减少连续执行字符串增长操作所需的内存重分配次数。当 SDS 需要减少字符串时,这部分内存不会立即被回收,会被记录下来,等待后续使用(支持手动释放,有对应的 API
4. **二进制安全** C 语言中的字符串以空字符 `\0` 作为字符串结束的标识这存在一些问题像一些二进制文件比如图片、视频、音频就可能包括空字符C 字符串无法正确保存。SDS 使用 len 属性判断字符串是否结束,不存在这个问题。
多提一嘴,很多文章里 SDS 的定义是下面这样的:
```c
struct sdshdr {
unsigned int len;
unsigned int free;
char buf[];
};
```
这个也没错Redis 3.2 之前就是这样定义的。后来,由于这种方式的定义存在问题,`len``free` 的定义用了 4 个字节造成了浪费。Redis 3.2 之后Redis 改进了 SDS 的定义,将其划分为了现在的 5 种类型。
### 购物车信息用 String 还是 Hash 存储更好呢? ### 购物车信息用 String 还是 Hash 存储更好呢?
由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储: 由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储:

View File

@ -7,7 +7,9 @@ tag:
**Java 11** 于 2018 年 9 月 25 日正式发布这是很重要的一个版本Java 11 和 2017 年 9 月份发布的 Java 9 以及 2018 年 3 月份发布的 Java 10 相比,其最大的区别就是:在长期支持(Long-Term-Support)方面,**Oracle 表示会对 Java 11 提供大力支持,这一支持将会持续至 2026 年 9 月。这是据 Java 8 以后支持的首个长期版本。** **Java 11** 于 2018 年 9 月 25 日正式发布这是很重要的一个版本Java 11 和 2017 年 9 月份发布的 Java 9 以及 2018 年 3 月份发布的 Java 10 相比,其最大的区别就是:在长期支持(Long-Term-Support)方面,**Oracle 表示会对 Java 11 提供大力支持,这一支持将会持续至 2026 年 9 月。这是据 Java 8 以后支持的首个长期版本。**
![](https://img-blog.csdnimg.cn/20210603202746605.png) 下面这张图是 Oracle 官方给出的 Oracle JDK 支持的时间线。
![](https://img-blog.csdnimg.cn/4c1611fad59449edbbd6e233690e9fa7.png)
**概览(精选了一部分)** **概览(精选了一部分)**

View File

@ -5,7 +5,17 @@ tag:
- Java新特性 - Java新特性
--- ---
Java 17 在 2021 年 9 月 14 日正式发布Java 17 是一个长期支持LTS版本这次更新共带来 14 个新特性: Java 17 在 2021 年 9 月 14 日正式发布是一个长期支持LTS版本。
下面这张图是 Oracle 官方给出的 Oracle JDK 支持的时间线。可以看得到Java
17 最多可以支持到 2029 年 9 月份。
![](https://img-blog.csdnimg.cn/4c1611fad59449edbbd6e233690e9fa7.png)
Java 17 将是继 Java 8 以来最重要的长期支持LTS版本是 Java 社区八年努力的成果。Spring 6.x 和 Spring Boot 3.x 最低支持的就是 Java 17。
这次更新共带来 14 个新特性:
- [JEP 306:Restore Always-Strict Floating-Point Semantics恢复始终严格的浮点语义](https://openjdk.java.net/jeps/306) - [JEP 306:Restore Always-Strict Floating-Point Semantics恢复始终严格的浮点语义](https://openjdk.java.net/jeps/306)
- [JEP 356:Enhanced Pseudo-Random Number Generators增强的伪随机数生成器](https://openjdk.java.net/jeps/356) - [JEP 356:Enhanced Pseudo-Random Number Generators增强的伪随机数生成器](https://openjdk.java.net/jeps/356)

View File

@ -5,7 +5,9 @@ tag:
- Java新特性 - Java新特性
--- ---
Java 18 在 2022 年 3 月 22 日正式发布非长期支持版本。Java 18 带来了 9 个新特性: Java 18 在 2022 年 3 月 22 日正式发布,非长期支持版本。
Java 18 带来了 9 个新特性:
- [JEP 400:UTF-8 by Default默认字符集为 UTF-8](https://openjdk.java.net/jeps/400) - [JEP 400:UTF-8 by Default默认字符集为 UTF-8](https://openjdk.java.net/jeps/400)
- [JEP 408:Simple Web Server简易的 Web 服务器)](https://openjdk.java.net/jeps/408) - [JEP 408:Simple Web Server简易的 Web 服务器)](https://openjdk.java.net/jeps/408)