From 2d6445071e9b823664ee49c41dea1d40069ea3dd Mon Sep 17 00:00:00 2001 From: Guide Date: Mon, 19 Aug 2024 17:00:05 +0800 Subject: [PATCH] =?UTF-8?q?[docs=20update]=E9=83=A8=E5=88=86=E9=9D=A2?= =?UTF-8?q?=E8=AF=95=E9=A2=98=E5=9B=9E=E7=AD=94=E5=AE=8C=E5=96=84&?= =?UTF-8?q?=E5=8D=9A=E5=AE=A2=E9=A1=B9=E7=9B=AE=E6=8E=A8=E8=8D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/cs-basics/network/http1.0-vs-http1.1.md | 65 ++++++++++++++++++- docs/database/redis/redis-questions-01.md | 30 +++++++-- docs/open-source-project/practical-project.md | 1 + .../spring-knowledge-and-questions-summary.md | 62 +++++++++++++++++- 4 files changed, 147 insertions(+), 11 deletions(-) diff --git a/docs/cs-basics/network/http1.0-vs-http1.1.md b/docs/cs-basics/network/http1.0-vs-http1.1.md index eab4c324..f0bb9850 100644 --- a/docs/cs-basics/network/http1.0-vs-http1.1.md +++ b/docs/cs-basics/network/http1.0-vs-http1.1.md @@ -70,9 +70,70 @@ Host: example1.org HTTP/1.1 引入了范围请求(range request)机制,以避免带宽的浪费。当客户端想请求一个文件的一部分,或者需要继续下载一个已经下载了部分但被终止的文件,HTTP/1.1 可以在请求中加入`Range`头部,以请求(并只能请求字节型数据)数据的一部分。服务器端可以忽略`Range`头部,也可以返回若干`Range`响应。 -如果一个响应包含部分数据的话,那么将带有`206 (Partial Content)`状态码。该状态码的意义在于避免了 HTTP/1.0 代理缓存错误地把该响应认为是一个完整的数据响应,从而把他当作为一个请求的响应缓存。 +`206 (Partial Content)` 状态码的主要作用是确保客户端和代理服务器能正确识别部分内容响应,避免将其误认为完整资源并错误地缓存。这对于正确处理范围请求和缓存管理非常重要。 -在范围响应中,`Content-Range`头部标志指示出了该数据块的偏移量和数据块的长度。 +一个典型的 HTTP/1.1 范围请求示例: + +```bash +# 获取一个文件的前 1024 个字节 +GET /z4d4kWk.jpg HTTP/1.1 +Host: i.imgur.com +Range: bytes=0-1023 +``` + +`206 Partial Content` 响应: + +```bash + +HTTP/1.1 206 Partial Content +Content-Range: bytes 0-1023/146515 +Content-Length: 1024 +… +(二进制内容) +``` + +简单解释一下 HTTP 范围响应头部中的字段: + +- **`Content-Range` 头部**:指示返回数据在整个资源中的位置,包括起始和结束字节以及资源的总长度。例如,`Content-Range: bytes 0-1023/146515` 表示服务器端返回了第 0 到 1023 字节的数据(共 1024 字节),而整个资源的总长度是 146,515 字节。 +- **`Content-Length` 头部**:指示此次响应中实际传输的字节数。例如,`Content-Length: 1024` 表示服务器端传输了 1024 字节的数据。 + +`Range` 请求头不仅可以请求单个字节范围,还可以一次性请求多个范围。这种方式被称为“多重范围请求”(multiple range requests)。 + +客户端想要获取资源的第 0 到 499 字节以及第 1000 到 1499 字节: + +```bash +GET /path/to/resource HTTP/1.1 +Host: example.com +Range: bytes=0-499,1000-1499 +``` + +服务器端返回多个字节范围,每个范围的内容以分隔符分开: + +```bash +HTTP/1.1 206 Partial Content +Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5 +Content-Length: 376 + +--3d6b6a416f9b5 +Content-Type: application/octet-stream +Content-Range: bytes 0-99/2000 + +(第 0 到 99 字节的数据块) + +--3d6b6a416f9b5 +Content-Type: application/octet-stream +Content-Range: bytes 500-599/2000 + +(第 500 到 599 字节的数据块) + +--3d6b6a416f9b5 +Content-Type: application/octet-stream +Content-Range: bytes 1000-1099/2000 + +(第 1000 到 1099 字节的数据块) + +--3d6b6a416f9b5-- +``` ### 状态码 100 diff --git a/docs/database/redis/redis-questions-01.md b/docs/database/redis/redis-questions-01.md index 4e848049..75da2702 100644 --- a/docs/database/redis/redis-questions-01.md +++ b/docs/database/redis/redis-questions-01.md @@ -47,13 +47,22 @@ Redis 内部做了非常多的性能优化,比较重要的有下面 3 点: 那既然都这么快了,为什么不直接用 Redis 当主数据库呢?主要是因为内存成本太高且 Redis 提供的数据持久化仍然有数据丢失的风险。 -### 分布式缓存常见的技术选型方案有哪些? +### 除了 Redis,你还知道其他分布式缓存方案吗? + +如果面试中被问到这个问题的话,面试官主要想看看: + +1. 你在选择 Redis 作为分布式缓存方案时,是否是经过严谨的调研和思考,还是只是因为 Redis 是当前的“热门”技术。 +2. 你在分布式缓存方向的技术广度。 + +如果你了解其他方案,并且能解释为什么最终选择了 Redis(更进一步!),这会对你面试表现加分不少! + +下面简单聊聊常见的分布式缓存技术选型。 分布式缓存的话,比较老牌同时也是使用的比较多的还是 **Memcached** 和 **Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**。 Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。 -有一些大厂也开源了类似于 Redis 的分布式高性能 KV 存储数据库,例如,腾讯开源的 [Tendis](https://github.com/Tencent/Tendis) 。Tendis 基于知名开源项目 [RocksDB](https://github.com/facebook/rocksdb) 作为存储引擎 ,100% 兼容 Redis 协议和 Redis4.0 所有数据模型。关于 Redis 和 Tendis 的对比,腾讯官方曾经发过一篇文章:[Redis vs Tendis:冷热混合存储版架构揭秘](https://mp.weixin.qq.com/s/MeYkfOIdnU6LYlsGb24KjQ) ,可以简单参考一下。 +有一些大厂也开源了类似于 Redis 的分布式高性能 KV 存储数据库,例如,腾讯开源的 [**Tendis**](https://github.com/Tencent/Tendis) 。Tendis 基于知名开源项目 [RocksDB](https://github.com/facebook/rocksdb) 作为存储引擎 ,100% 兼容 Redis 协议和 Redis4.0 所有数据模型。关于 Redis 和 Tendis 的对比,腾讯官方曾经发过一篇文章:[Redis vs Tendis:冷热混合存储版架构揭秘](https://mp.weixin.qq.com/s/MeYkfOIdnU6LYlsGb24KjQ) ,可以简单参考一下。 不过,从 Tendis 这个项目的 Github 提交记录可以看出,Tendis 开源版几乎已经没有被维护更新了,加上其关注度并不高,使用的公司也比较少。因此,不建议你使用 Tendis 来实现分布式缓存。 @@ -62,7 +71,9 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来 - [Dragonfly](https://github.com/dragonflydb/dragonfly):一种针对现代应用程序负荷需求而构建的内存数据库,完全兼容 Redis 和 Memcached 的 API,迁移时无需修改任何代码,号称全世界最快的内存数据库。 - [KeyDB](https://github.com/Snapchat/KeyDB): Redis 的一个高性能分支,专注于多线程、内存效率和高吞吐量。 -不过,个人还是建议分布式缓存首选 Redis ,毕竟经过这么多年的生考验,生态也这么优秀,资料也很全面。 +不过,个人还是建议分布式缓存首选 Redis ,毕竟经过这么多年的生考验,生态也这么优秀,资料也很全面! + +PS:篇幅问题,我这并没有对上面提到的分布式缓存选型做详细介绍和对比,感兴趣的话,可以自行研究一下。 ### 说一下 Redis 和 Memcached 的区别和共同点 @@ -314,10 +325,17 @@ String 的常见应用场景如下: ### String 还是 Hash 存储对象数据更好呢? -- String 存储的是序列化后的对象数据,存放的是整个对象。Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息,Hash 就非常适合。 -- String 存储相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String 就非常适合。 +简单对比一下二者: -在绝大部分情况,我们建议使用 String 来存储对象数据即可! +- **对象存储方式**:String 存储的是序列化后的对象数据,存放的是整个对象,操作简单直接。Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息,Hash 就非常适合。 +- **内存消耗**:Hash 通常比 String 更节省内存,特别是在字段较多且字段长度较短时。Redis 对小型 Hash 进行优化(如使用 ziplist 存储),进一步降低内存占用。 +- **复杂对象存储**:String 在处理多层嵌套或复杂结构的对象时更方便,因为无需处理每个字段的独立存储和操作。 +- **性能**:String 的操作通常具有 O(1) 的时间复杂度,因为它存储的是整个对象,操作简单直接,整体读写的性能较好。Hash 由于需要处理多个字段的增删改查操作,在字段较多且经常变动的情况下,可能会带来额外的性能开销。 + +总结: + +- 在绝大多数情况下,**String** 更适合存储对象数据,尤其是当对象结构简单且整体读写是主要操作时。 +- 如果你需要频繁操作对象的部分字段或节省内存,**Hash** 可能是更好的选择。 ### String 的底层实现是什么? diff --git a/docs/open-source-project/practical-project.md b/docs/open-source-project/practical-project.md index 4d63af8f..1c5e2d70 100644 --- a/docs/open-source-project/practical-project.md +++ b/docs/open-source-project/practical-project.md @@ -28,6 +28,7 @@ icon: project - [paicoding](https://github.com/itwanger/paicoding):一款好用又强大的开源社区,基于 Spring Boot 系列主流技术栈,附详细的教程。 - [forest](https://github.com/rymcu/forest):下一代的知识社区系统,可以自定义专题和作品集。后端基于 SpringBoot + Shrio + MyBatis + JWT + Redis,前端基于 Vue + NuxtJS + Element-UI。 - [community](https://github.com/codedrinker/community):开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap。 +- [OneBlog](https://gitee.com/yadong.zhang/DBlog):简洁美观、功能强大并且自适应的博客系统,支持广告位、SEO、实时通讯等功能。 - [VBlog](https://github.com/lenve/VBlog):V 部落,Vue+SpringBoot 实现的多用户博客管理平台! - [My-Blog](https://github.com/ZHENFENG13/My-Blog): SpringBoot + Mybatis + Thymeleaf 等技术实现的 Java 博客系统,页面美观、功能齐全、部署简单及完善的代码,一定会给使用者无与伦比的体验。 diff --git a/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md b/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md index ed4abf66..667c848f 100644 --- a/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md +++ b/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md @@ -316,12 +316,68 @@ Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。 prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。 +有状态 Bean 示例: + +```java +// 定义了一个购物车类,其中包含一个保存用户的购物车里商品的 List +@Component +public class ShoppingCart { + private List items = new ArrayList<>(); + + public void addItem(String item) { + items.add(item); + } + + public List getItems() { + return items; + } +} +``` + 不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。 -对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法: +无状态 Bean 示例: -1. 在 Bean 中尽量避免定义可变的成员变量。 -2. 在类中定义一个 `ThreadLocal` 成员变量,将需要的可变成员变量保存在 `ThreadLocal` 中(推荐的一种方式)。 +```java +// 定义了一个用户服务,它仅包含业务逻辑而不保存任何状态。 +@Component +public class UserService { + + public User findUserById(Long id) { + //... + } + //... +} +``` + +对于有状态单例 Bean 的线程安全问题,常见的三种解决办法是: + +1. **避免可变成员变量**: 尽量设计 Bean 为无状态。 +2. **使用`ThreadLocal`**: 将可变成员变量保存在 `ThreadLocal` 中,确保线程独立。 +3. **使用同步机制**: 利用 `synchronized` 或 `ReentrantLock` 来进行同步控制,确保线程安全。 + +这里以 `ThreadLocal`为例,演示一下`ThreadLocal` 保存用户登录信息的场景: + +```java +public class UserThreadLocal { + + private UserThreadLocal() {} + + private static final ThreadLocal LOCAL = ThreadLocal.withInitial(() -> null); + + public static void put(SysUser sysUser) { + LOCAL.set(sysUser); + } + + public static SysUser get() { + return LOCAL.get(); + } + + public static void remove() { + LOCAL.remove(); + } +} +``` ### Bean 的生命周期了解么?