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

[docs update]完善Redis事务的介绍

This commit is contained in:
Guide 2023-03-24 17:34:58 +08:00
parent 2696950220
commit b2351c877d
7 changed files with 125 additions and 45 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -1 +1 @@
<mxfile host="Electron" modified="2022-08-22T01:01:48.794Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="nH91fIJ1FQXltYQjMT6B" version="13.4.5" type="device"><diagram id="9BE8MdjfxC0TaVaDFPY0" name="Page-1">7Vrbcuo2FP0aPSbjq2w/2uCk92aah7ZPHcUW4MRY1IgA/fpKtoQvEgTOMQkZMmfmxNq62XvtJS1tBOzRfHNfosXsV5LiHFhGugH2GFiWaRqQ/eGWbW0JAq82TMssFY0aw2P2HxZGQ1hXWYqXnYaUkJxmi64xIUWBE9qxobIk626zCcm7sy7QFCuGxwTlqvXPLKUz+V0waCp+wNl0JqeGrlPXzJFsLT5lOUMpWbdMdgzsUUkIrZ/mmxHOufekY+p+d3tqd29W4oIe0wEvn4t/bh7+QOOU0PtFdFNuf74Ro7yifCW+WLws3UoXlGRVpJgPYgI7Ws8yih8XKOG1awY6s83oPBfVKVrOdm3VN5TT4ZLiTcsk3vgekzmm5ZY1kbW+8J6IH9MR5XULDenhWQsIxxBGJCJguhu78RF7EG46wWWW6rLYBaELWGjHEPgxf+aWMZDB3vIm+27addmSluQFj0hOSmYpSMFaRpMsz3smlGfTghUT5k3M7BH3YsZCNRQV8yxN+TRajBoUjT5ME1JQwTzLGAY2y+jBBlXYfA1q1tlAMxQkcMqYLoqkpDMyJQXK48ba81nT5hdCFsJ3z5jSrXAeWlHShZY5sNz+JfpXhb954daVxfGmXTneilILERMeQmRJVmWCD3y3LdZMVE4xPdBOBCr3yUF8S5wjmr12V8fB0bIVii1J8oKpqYKY52wr2Bf0LSj6Lm0TDFh2irA/SRQ2shqY+PhpMtBqFrgdWuxo0qaFq9LCOxstNB69Blo4n5IWzh5aqHv256aF/eG00Hj0/LQYLrzdocNWdH0gGXuVHW5Ob5e37R4gNb9ErwaTsCzRttVswRss989jO915HM9tD/dme/leTUjUb9AEyM4n3x4z7h5y/vbJydmXco5Ggb8rOaHiaNXDRRryw18jnjuCt3deeUCUCeqisliGvXOoPPBZp2jkNznXcpurUcDS9p3UtOzgtruoOn1y1quIQs4jhrL943g+FLUs+zpVinekSvEvSqV4Cj9/ZOP8zkzVqdgBQQhiH0QeCO+ExR+D2AOBA0KfP7D//YhXRSHw7xTsT0pI9InbW0MnEwwT7RqaesGTMdBxWNkoNQLH1J2H4bkWUV9dRGMIAgh8D8QOiCIQweohrKDYWVzgM3AgbxwxY8wtQcAROy9KE2sPSvAJugdZdjxKMLg0lIK3t7oPzc4FsJfmsTQegxqPOWdTBzJx3IlsXRzzZceplh2jCuh9cXwdCbvA727xuoSdaWqQPFvGzlRPu186TxFnpqGoM7evzo4VerqxjjzRDaX0TPUUVTHTBNFYsJfxlqkHJh2iKt0emcC3TuHzt+5LuhMYNlMXe7p9KYCejQbalwKvnzV0VW7a77kvmZozmAYmZolAELdE3lXBFGiW0PeFSZXilfK+q0Bp/VTFdsegAoX/ZuVcF0y29+Ew6cQ4I5HPeVSziSGlBe56YHLcj4bJclT9cQ1ZCBFNb2YhzMGzzt8Hl/sF10G44GXBBb/gOgjXZf0Uaak3W77g0uzplwKXRmJYMKfCNx0g4b8rIitulpXXQtbAhItNU8meptVfnhFkOmRUCZVRdQfK5Ulbfjq7qyzhT+gV3fN7fHLKp7Lp7vEjAzs4cPEZcfUiez2zXtXtv9ukkB3Zt9evK3pfZq4mR084j1DyMq3s/clbseiLcktDRdW/akzKAoMUYtDlC6bJTBbkVcKBsj+7Y748uuhu2ekyn+bpYosVmzuPdd6guTpqx/8D</diagram></mxfile> <mxfile host="Electron" modified="2023-03-24T02:15:32.077Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="a9N6Y6CqlEFiDDgChlI2" version="20.3.0" type="device"><diagram id="9BE8MdjfxC0TaVaDFPY0" name="Page-1">7Vtbc6s2EP41ekyG++XR2DjnnPa0meah7VNHARmTYOSCnNj99ZWEMGDJSZz4QjJMZhxpdQPtp0+7KwTM8WJ9U8Dl/CeOUQYMLV4DcwIMQ9c1h/5jkk0lcT2/EiRFGotKjeAu/Q8JoSakqzRGZaciwTgj6bIrjHCeo4h0ZLAo8HO32gxn3VGXMEGS4C6CmSz9M43JvH4vx28KvqE0mddDO7ZVldzD6DEp8CoXAwLDnDrT6dSrihew7ky8aTmHMX5uicwQmOMCY1KlFusxytjk1vNWtZvuKd0+eIFy8pYGqHzI/7m6/QNOYkxulsFVsfnlSvTyBLMVql+DPyzZ1DPEXxGxTnRgBs/zlKC7JYxY6TPFBJXNySITxTEs59u6M5yTKVykGcPGD0SCAqZ5SXv/iXMsyu/wquB9zQmhKjdsc0R/6EuwH1ahvE4wTjIEl2l5HeEFL4hKXnU6q3qnyXb/thGIEUpS4MetYumUB/Ks1VOACoLWLZGYxRuEF4gUdBCtLvWERgXkdUvkn1sAqrU+b2HH0oQQCtAm274bvdGEUN0BajRkNYY2GNnAd0HoAC9kaSaZMMmuhul7k64aq3kb4wwXVJLjHDF1pVm2I4JZmuQ0G9HZRFQesFlM6eoaiYJFGsdsGCVuGmRpKugIsjC0XkDpCMAxtB3gODJwPAVujJPBRpOwgGJKjyKLCzLHCc5hFjbSHa01dX7FeCm094AI2Qj1wRXBXXDRCSw2f4n2PPM3y1zbdXaybhdONiLXwoTu9AITB9BLWT/IPl2YYvODRYLIC/XE8mV6ehFzBcogSZ+629zREWRKxFPi6BERXQZWltE9fR8VtOAhqblFO3SPjSHyZtFejro4Jo6xwfh2hye2vNHmCVvmCfdkPKFQ58ATl+EJ61I8wZuOigJuWhWWOKVz0+r5lgkaKJt6d8szzR0j9bD6NFE9QYPl7au8H97WHhKTbeGBxN5NYubFSUyhztOT2NcmI/skJCOxgqWpWWHbRcWGolWDk4PZyuqOY7n2y2xlXYCt7D1s9dvAVsdzzSyFT39WtnIkLcvqzeMRi4A1qui40DtRmVtIqIuec4mhmUoi+ERe96uU01KcrfCpa9kHmckw3evuPmfteusViUrcpOqqSyb+20juWLximIOR35d91X2jke8de//9EGe5Emd9p/38TkU89mgBfwRCDwQuGE2FxJuA0AW+BUYeS9BfL2BFwQh4UwmPB4WiJTLrbmqzGXKir72pSWaTwgTXVQFH51S7mifvaqEDfAd4LggtEAQgcHhixHGwldjAo8hwWOWACkMm8X0Gl9NCZGZ8cYg4ft8g4r9u+AwnUjta9J2uFnVToUVHoUXrZPZrHcfpLHXVwmabgMU3AY2v8H0LezikOg8j+F7XntVdBZZ0BZZOdkqly/G5wRfqoS+kazvhENt7pyuka6a/41bZ9nm9IV0Os3Bu0kEwEfxFmYtas9SUDfghe6ADzziE0d5rqqhCNEiPbeR+aVPFd3dOxWoVtYnJPKepoiuCNAqMUEkA/LDl8QwYORNGlObseTEiO8XcB55yRLQ+zaGWkc8Rwb7RsQaMnA8j3sUxonKLKX14jEEqHqEwUaJmwMhZMGIpvtQ6L0YMS7Z5h+jsZaKzYnm9Gp3Vj348+jEI2QOEPh2EnH5ByBkg9Okg1K8vRg35+/MBQn2HUL/OGQ2FvWw4GRHz3wGX8+8K1wVXJdck1YamO8t1U0hTCf/PDpqoUT3mVveY356w2UEki/BMuWT0Az7BG3ZpqR7yvmiau8zzp/4/c+MCZorXrR5oK37V6TrK64agQsO2dT8j3hm8R1mwveuksPbr9eGJfMtVCPgf75NQYOBcdFo+IhLN60x9MarnYda3uw+22Q2EGqobQqpTNf1w94FmmztkVfSzualnhv8D</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -374,7 +374,7 @@ Redis 中的 Set 类型是一种无序集合,集合中的元素没有先后顺
**需要随机获取数据源中的元素的场景** **需要随机获取数据源中的元素的场景**
- 举例 :抽奖系统、随机。 - 举例 :抽奖系统、随机点名等场景
- 相关命令:`SPOP`(随机获取集合中的元素并移除,适合不允许重复中奖的场景)、`SRANDMEMBER`(随机获取集合中的元素,适合允许重复中奖的场景)。 - 相关命令:`SPOP`(随机获取集合中的元素并移除,适合不允许重复中奖的场景)、`SRANDMEMBER`(随机获取集合中的元素,适合允许重复中奖的场景)。
## Sorted Set有序集合 ## Sorted Set有序集合

View File

@ -59,7 +59,7 @@ Redis 提供了两个命令来生成 RDB 快照文件:
appendonly yes appendonly yes
``` ```
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。 开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据持久化方式( `fsync`策略)的配置来决定何时将其同步到硬盘中的 AOF 文件。
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof` AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`
@ -120,6 +120,12 @@ AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的
Redis 7.0 版本之前如果在重写期间有写入命令AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。 Redis 7.0 版本之前如果在重写期间有写入命令AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
Redis 7.0 版本之后AOF 重写机制得到了优化改进。下面这段内容摘自阿里开发者的[从Redis7.0发布看Redis的过去与未来](https://mp.weixin.qq.com/s/RnoPPL7jiFSKkx3G4p57Pg) 这篇文章。
> AOF 重写期间的增量数据如何处理一直是个问题在过去写期间的增量数据需要在内存中保留写结束后再把这部分增量数据写入新的AOF文件中以保证数据完整性。可以看出来AOF 写会额外消耗内存和磁盘IO这也是Redis AOF 写的痛点,虽然之前也进行过多次改进但是资源消耗的本质问题一直没有解决。
>
> 阿里云的Redis企业版在最初也遇到了这个问题在内部经过多次迭代开发实现了Multi-part AOF机制来解决同时也贡献给了社区并随此次7.0发布。具体方法是采用base全量数据+inc增量数据独立文件存储的方式彻底解决内存和IO资源的浪费同时也支持对历史AOF文件的保存管理结合AOF文件中的时间信息还可以实现PITR按时间点恢复阿里云企业版Tair已支持这进一步增强了Redis的数据可靠性满足用户数据回档等需求。
**相关 issue** [Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439) **相关 issue** [Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439)
## Redis 4.0 对于持久化机制做了什么优化? ## Redis 4.0 对于持久化机制做了什么优化?
@ -151,4 +157,10 @@ Redis 7.0 版本之前如果在重写期间有写入命令AOF 可能会使
- Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。 - Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。
- 不建议单独使用 AOF因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。 - 不建议单独使用 AOF因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。
- 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。 - 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。
## 参考
- Redis persistence - Redis 官方文档https://redis.io/docs/management/persistence/
- The difference between AOF and RDB persistencehttps://www.sobyte.net/post/2022-04/redis-rdb-and-aof/
- Redis AOF 持久化详解 - 程序员历小冰http://remcarpediem.net/article/376c55d8/

View File

@ -18,11 +18,11 @@ head:
[Redis](https://redis.io/) 是一个基于 C 语言开发的开源数据库BSD 许可),与传统数据库不同的是 Redis 的数据是存在内存中的内存数据库读写速度非常快被广泛应用于缓存方向。并且Redis 存储的是 KV 键值对数据。 [Redis](https://redis.io/) 是一个基于 C 语言开发的开源数据库BSD 许可),与传统数据库不同的是 Redis 的数据是存在内存中的内存数据库读写速度非常快被广泛应用于缓存方向。并且Redis 存储的是 KV 键值对数据。
为了满足不同的业务场景Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap。并且Redis 还支持事务 、持久化、Lua 脚本、多种开箱即用的集群方案Redis Sentinel、Redis Cluster 为了满足不同的业务场景Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap、HyperLogLog、GEO。并且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。
![try-redis](https://oss.javaguide.cn/github/javaguide/database/redis/try.redis.io.png) ![try-redis](https://oss.javaguide.cn/github/javaguide/database/redis/try.redis.io.png)
@ -30,11 +30,11 @@ Redis 没有外部依赖Linux 和 OS X 是 Redis 开发和测试最多的两
### Redis 为什么这么快? ### Redis 为什么这么快?
Redis 内部做了非常多的性能优化,比较重要的主要有下面 3 点: Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:
- Redis 基于内存,内存的访问速度是磁盘的上千倍; 1. Redis 基于内存,内存的访问速度是磁盘的上千倍;
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到); 2. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到);
- Redis 内置了多种优化过后的数据结构实现,性能非常高。 3. Redis 内置了多种优化过后的数据结构实现,性能非常高。
下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770) 。 下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770) 。
@ -69,7 +69,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。 3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。
4. **Redis 在服务器内存使用完之后可以将不用的数据放到磁盘上。但是Memcached 在服务器内存使用完之后,就会直接报异常。** 4. **Redis 在服务器内存使用完之后可以将不用的数据放到磁盘上。但是Memcached 在服务器内存使用完之后,就会直接报异常。**
5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。** 5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。**
6. **Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。** Redis 6.0 引入了多线程 IO 6. **Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。** Redis 6.0 针对网络数据的读写引入了多线程)
7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。** 7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。**
8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。** 8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。**
@ -79,13 +79,13 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
下面我们主要从“高性能”和“高并发”这两点来回答这个问题。 下面我们主要从“高性能”和“高并发”这两点来回答这个问题。
**高性能** **1、高性能**
假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。 假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
**这样有什么好处呢?** 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。 **这样有什么好处呢?** 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
**高并发** **2、高并发**
一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右4 核 8g ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 Redis 的情况Redis 集群的话会更高)。 一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右4 核 8g ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 Redis 的情况Redis 集群的话会更高)。
@ -115,6 +115,8 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
> 实际项目中也没见谁使用 Redis 来做消息队列,对于这部分知识点大家了解就好了。 > 实际项目中也没见谁使用 Redis 来做消息队列,对于这部分知识点大家了解就好了。
先说结论:可以是可以,但不建议使用 Redis 来做消息队列。和专业的消息队列相比,还是有很多欠缺的地方。
**Redis 2.0 之前,如果想要使用 Redis 来做消息队列的话,只能通过 List 来实现。** **Redis 2.0 之前,如果想要使用 Redis 来做消息队列的话,只能通过 List 来实现。**
通过 `RPUSH/LPOP` 或者 `LPUSH/RPOP`即可实现简易版消息队列 通过 `RPUSH/LPOP` 或者 `LPUSH/RPOP`即可实现简易版消息队列
@ -145,7 +147,7 @@ null
**Redis 2.0 引入了 发布订阅 (pub/sub) 解决了 List 实现消息队列没有广播机制的问题。** **Redis 2.0 引入了 发布订阅 (pub/sub) 解决了 List 实现消息队列没有广播机制的问题。**
pub/sub 中引入了一个概念叫 channel频道发布订阅机制的实现就是基于这个 channel 来做的。 pub/sub 中引入了一个概念叫 **channel频道**,发布订阅机制的实现就是基于这个 channel 来做的。
pub/sub 涉及发布者和订阅者(也叫消费者)两个角色: pub/sub 涉及发布者和订阅者(也叫消费者)两个角色:
@ -184,7 +186,11 @@ pub/sub 既能单播又能广播,还支持 channel 的简单正则匹配。不
### String 的应用场景有哪些? ### String 的应用场景有哪些?
- 常规数据(比如 session、token、、序列化后的对象的缓存 String 是 Redis 中最简单同时也是最常用的一个数据结构。String 是一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。
String 的常见应用场景如下:
- 常规数据(比如 session、token、序列化后的对象、图片的路径的缓存
- 计数比如用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数; - 计数比如用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数;
- 分布式锁(利用 `SETNX key value` 命令可以实现一个最简易的分布式锁) - 分布式锁(利用 `SETNX key value` 命令可以实现一个最简易的分布式锁)
- ...... - ......
@ -282,7 +288,7 @@ struct sdshdr {
- 用户 id 为 key - 用户 id 为 key
- 商品 id 为 field商品数量为 value - 商品 id 为 field商品数量为 value
![Hash维护简单的购物车信息](./images/hash-shopping-cart.png) ![Hash维护简单的购物车信息](https://oss.javaguide.cn/github/javaguide/database/redis/hash-shopping-cart.png)
那用户购物车信息的维护具体应该怎么操作呢? 那用户购物车信息的维护具体应该怎么操作呢?
@ -302,18 +308,37 @@ Redis 中有一个叫做 `sorted set` 的数据结构经常被用在各种排行
![](https://oss.javaguide.cn/github/javaguide/database/redis/2021060714195385.png) ![](https://oss.javaguide.cn/github/javaguide/database/redis/2021060714195385.png)
[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 的「技术面试题篇」就有一篇文章详细介绍如何使用 Sorted Set 来设计制作一个排行榜。 [《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 的「技术面试题篇」就有一篇文章详细介绍如何使用 Sorted Set 来设计制作一个排行榜,感兴趣的小伙伴可以看看
![](https://oss.javaguide.cn/github/javaguide/database/redis/image-20220719071115140.png) ![](https://oss.javaguide.cn/github/javaguide/database/redis/image-20220719071115140.png)
### 使用 Set 实现抽奖系统需要用到什么命令 ### Set 的应用场景是什么
Redis 中 `Set` 是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的 `HashSet`
Set 的常见应用场景如下:
- 存放的数据不能重复的场景:网站 UV 统计(数据量巨大的场景还是 `HyperLogLog`更适合一些)、文章点赞、动态点赞等等。
- 需要获取多个数据源交集、并集和差集的场景:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集) 、订阅号推荐(差集+交集) 等等。
- 需要随机获取数据源中的元素的场景:抽奖系统、随机点名等等。
### 使用 Set 实现抽奖系统怎么做?
如果想要使用 Set 实现一个简单的抽奖系统的话,直接使用下面这几个命令就可以了:
- `SADD key member1 member2 ...`:向指定集合添加一个或多个元素。
- `SPOP key count` 随机移除并获取指定集合中一个或多个元素,适合不允许重复中奖的场景。 - `SPOP key count` 随机移除并获取指定集合中一个或多个元素,适合不允许重复中奖的场景。
- `SRANDMEMBER key count` : 随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。 - `SRANDMEMBER key count` : 随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。
### 使用 Bitmap 统计活跃用户怎么做? ### 使用 Bitmap 统计活跃用户怎么做?
使用日期(精确到天)作为 key然后用户 ID 为 offset如果当日活跃过就设置为 1。 Bitmap 存储的是连续的二进制数字0 和 1通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte所以 Bitmap 本身会极大的节省储存空间。
你可以将 Bitmap 看作是一个存储二进制数字0 和 1的数组数组中每个元素的下标叫做 offset偏移量
![img](https://oss.javaguide.cn/github/javaguide/database/redis/image-20220720194154133.png)
如果想要使用 Bitmap 统计活跃用户的话,可以使用日期(精确到天)作为 key然后用户 ID 为 offset如果当日活跃过就设置为 1。
初始化数据: 初始化数据:
@ -346,6 +371,11 @@ Redis 中有一个叫做 `sorted set` 的数据结构经常被用在各种排行
### 使用 HyperLogLog 统计页面 UV 怎么做? ### 使用 HyperLogLog 统计页面 UV 怎么做?
使用 HyperLogLog 统计页面 UV主要需要用到下面这两个命令
- `PFADD key element1 element2 ...`:添加一个或多个元素到 HyperLogLog 中。
- `PFCOUNT key1 key2`:获取一个或者多个 HyperLogLog 的唯一计数。
1、将访问指定页面的每个用户 ID 添加到 `HyperLogLog` 中。 1、将访问指定页面的每个用户 ID 添加到 `HyperLogLog` 中。
```bash ```bash
@ -358,7 +388,11 @@ PFADD PAGE_1:UV USER1 USER2 ...... USERn
PFCOUNT PAGE_1:UV PFCOUNT PAGE_1:UV
``` ```
## Redis 线程模型 ## Redis 持久化机制(重要)
Redis 持久化机制RDB 持久化、AOF 持久化、RDB 和 AOF 的混合持久化) 相关的问题比较多,也比较重要,于是我单独抽了一篇文章来总结 Redis 持久化机制相关的知识点和问题: [Redis 持久化机制详解](./redis-persistence.md) 。
## Redis 线程模型(重要)
对于读写命令来说Redis 一直是单线程模型。不过,在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作, Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)。 对于读写命令来说Redis 一直是单线程模型。不过,在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作, Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)。
@ -388,7 +422,7 @@ Redis 通过 **IO 多路复用程序** 来监听来自客户端的大量连接
- 文件事件分派器(将 socket 关联到相应的事件处理器) - 文件事件分派器(将 socket 关联到相应的事件处理器)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) - 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
![文件事件处理器](https://oss.javaguide.cn/github/javaguide/database/redis/redis-event-handler.png) ![文件事件处理器file event handler](https://oss.javaguide.cn/github/javaguide/database/redis/redis-event-handler.png)
相关阅读:[Redis 事件机制详解](http://remcarpediem.net/article/1aa2da89/) 。 相关阅读:[Redis 事件机制详解](http://remcarpediem.net/article/1aa2da89/) 。
@ -520,15 +554,10 @@ Redis 提供 6 种数据淘汰策略:
7. **volatile-lfuleast frequently used**:从已设置过期时间的数据集(`server.db[i].expires`)中挑选最不经常使用的数据淘汰。 7. **volatile-lfuleast frequently used**:从已设置过期时间的数据集(`server.db[i].expires`)中挑选最不经常使用的数据淘汰。
8. **allkeys-lfuleast frequently used**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。 8. **allkeys-lfuleast frequently used**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。
## Redis 持久化机制
Redis 持久化机制RDB 持久化、AOF 持久化、RDB 和 AOF 的混合持久化) 相关的问题比较多,也比较重要,于是我单独抽了一篇文章来总结 Redis 持久化机制相关的知识点和问题: [Redis 持久化机制详解](./redis-persistence.md) 。
## 参考 ## 参考
- 《Redis 开发与运维》 - 《Redis 开发与运维》
- 《Redis 设计与实现》 - 《Redis 设计与实现》
- Redis 命令手册https://www.redis.com.cn/commands.html - Redis 命令手册https://www.redis.com.cn/commands.html
- WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153) - WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153)
- The difference between AOF and RDB persistencehttps://www.sobyte.net/post/2022-04/redis-rdb-and-aof/
- Redis AOF 持久化详解 - 程序员历小冰http://remcarpediem.net/article/376c55d8/

View File

@ -14,9 +14,19 @@ head:
## Redis 事务 ## Redis 事务
### 什么是 Redis 事务?
你可以将 Redis 中的事务理解为 **Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。**
Redis 事务实际开发中使用的非常少,功能比较鸡肋,不要将其和我们平时理解的关系型数据库的事务混淆了。
除了不满足原子性和持久性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。明明一次批量执行多个命令就可以了,这种操作实在是看不懂。
因此Redis 事务是不建议在日常开发中使用的。
### 如何使用 Redis 事务? ### 如何使用 Redis 事务?
Redis 可以通过 **`MULTI``EXEC``DISCARD``WATCH`** 等命令来实现事务(transaction)功能。 Redis 可以通过 **`MULTI``EXEC``DISCARD``WATCH`** 等命令来实现事务(Transaction)功能。
```bash ```bash
> MULTI > MULTI
@ -131,22 +141,36 @@ Redis 的事务和我们平时理解的关系型数据库的事务不同。我
3. **持久性Durability** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。 3. **持久性Durability** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
4. **一致性Consistency** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的; 4. **一致性Consistency** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
Redis 事务在运行错误的情况下除了执行过程中出现错误的命令外其他命令都能正常执行。并且Redis 事务是不支持回滚roll back操作的。因此Redis 事务其实是不满足原子性的(而且不满足持久性) Redis 事务在运行错误的情况下除了执行过程中出现错误的命令外其他命令都能正常执行。并且Redis 事务是不支持回滚roll back操作的。因此Redis 事务其实是不满足原子性的。
Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。 Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。
![Redis 为什么不支持回滚](https://oss.javaguide.cn/github/javaguide/database/redis/redis-rollback.png) ![Redis 为什么不支持回滚](https://oss.javaguide.cn/github/javaguide/database/redis/redis-rollback.png)
你可以将 Redis 中的事务就理解为 **Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。**
除了不满足原子性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。明明一次批量执行多个命令就可以了,这种操作实在是看不懂。
因此Redis 事务是不建议在日常开发中使用的。
**相关 issue** : **相关 issue** :
- [issue452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) 。 - [issue#452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) 。
- [Issue491:关于 redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491) - [Issue#491:关于 Redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491)
### Redis 事务支持持久性吗?
Redis 不同于 Memcached 的很重要一点就是Redis 支持持久化,而且支持 3 种持久化方式:
- 快照snapshottingRDB
- 只追加文件append-only file, AOF
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
与 RDB 持久化相比AOF 持久化的实时性更好。在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
```bash
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
appendfsync no #让操作系统决定何时进行同步一般为30秒一次
```
AOF 持久化的`fsync`策略为 no 、everysec 时都会存在数据丢失的情况 。always 下可以基本是可以满足持久性要求的,但性能太差,实际开发过程中不会使用。
因此Redis 事务的持久性也是没办法保证的。
### 如何解决 Redis 事务的缺陷? ### 如何解决 Redis 事务的缺陷?
@ -160,7 +184,7 @@ Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常
另外Redis 7.0 新增了 [Redis functions](https://redis.io/docs/manual/programmability/functions-intro/) 特性,你可以将 Redis functions 看作是比 Lua 更强大的脚本。 另外Redis 7.0 新增了 [Redis functions](https://redis.io/docs/manual/programmability/functions-intro/) 特性,你可以将 Redis functions 看作是比 Lua 更强大的脚本。
## Redis 性能优化 ## Redis 性能优化(重要)
### 使用批量操作减少网络传输 ### 使用批量操作减少网络传输
@ -173,7 +197,9 @@ Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常
其中,第 1 步和第 4 步耗费时间之和称为 **Round Trip Time (RTT,往返时间)** ,也就是数据在网络上传输的时间。 其中,第 1 步和第 4 步耗费时间之和称为 **Round Trip Time (RTT,往返时间)** ,也就是数据在网络上传输的时间。
**使用批量操作可以减少网络传输次数,进而有效减小网络开销,大幅减少 RTT。** 使用批量操作可以减少网络传输次数,进而有效减小网络开销,大幅减少 RTT。
另外,除了能减少 RTT 之外,发送一次命令的 socket I/O 成本也比较高(涉及上下文切换,存在`read()``write()`系统调用),批量操作还可以减少 socket I/O 成本。这个在官方对 pipeline 的介绍中有提到https://redis.io/docs/manual/pipelining/ 。
#### 原生批量操作命令 #### 原生批量操作命令
@ -206,19 +232,31 @@ Redis 中有一些原生支持批量操作的命令,比如:
原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意: 原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意:
- 原生批量操作命令是原子操作pipeline 是非原子操作 - 原生批量操作命令是原子操作pipeline 是非原子操作
- pipeline 可以打包不同的命令,原生批量操作命令不可以 - pipeline 可以打包不同的命令,原生批量操作命令不可以
- 原生批量操作命令是 Redis 服务端支持实现的,而 pipeline 需要服务端和客户端的共同实现。 - 原生批量操作命令是 Redis 服务端支持实现的,而 pipeline 需要服务端和客户端的共同实现。
顺带补充一下 pipeline 和 Redis 事务的对比:
- 事务是原子操作pipeline 是非原子操作。两个不同的事务不会同时运行,而 pipeline 可以同时以交错方式执行。
- Redis 事务中每个命令都需要发送到服务端,而 Pipeline 只需要发送一次,请求次数更少。
> 事务可以看作是一个原子操作,但其实并不满足原子性。当我们提到 Redis 中的原子操作时主要指的是这个操作比如事务、Lua 脚本不会被其他操作比如其他事务、Lua 脚本)打扰,并不能完全保证这个操作中的所有写命令要么都执行要么都不执行。这主要也是因为 Redis 是不支持回滚操作。
![](https://oss.javaguide.cn/github/javaguide/database/redis/redis-pipeline-vs-transaction.png)
另外pipeline 不适用于执行顺序有依赖关系的一批命令。就比如说你需要将前一个命令的结果给后续的命令使用pipeline 就没办法满足你的需求了。对于这种需求,我们可以使用 **Lua 脚本** 另外pipeline 不适用于执行顺序有依赖关系的一批命令。就比如说你需要将前一个命令的结果给后续的命令使用pipeline 就没办法满足你的需求了。对于这种需求,我们可以使用 **Lua 脚本**
#### Lua 脚本 #### Lua 脚本
Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作一条命令执行,可以看作是原子操作。一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰,这是 pipeline 所不具备的。 Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作一条命令执行,可以看作是 **原子操作** 也就是说,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰,这是 pipeline 所不具备的。
并且Lua 脚本中支持一些简单的逻辑处理比如使用命令读取值并在 Lua 脚本中进行处理,这同样是 pipeline 所不具备的。 并且Lua 脚本中支持一些简单的逻辑处理比如使用命令读取值并在 Lua 脚本中进行处理,这同样是 pipeline 所不具备的。
不过, Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上。 不过, Lua 脚本依然存在下面这些缺陷:
- 如果 Lua 脚本运行时出错并中途结束,之后的操作不会进行,但是之前已经发生的写操作不会撤销,所以即使使用了 Lua 脚本,也不能实现类似数据库回滚的原子性。
- Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上。
### 大量 key 集中过期问题 ### 大量 key 集中过期问题
@ -293,7 +331,7 @@ Biggest string found '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28
**参考答案** [Redis 内存碎片详解](https://javaguide.cn/database/redis/redis-memory-fragmentation.html)。 **参考答案** [Redis 内存碎片详解](https://javaguide.cn/database/redis/redis-memory-fragmentation.html)。
## Redis 生产问题 ## Redis 生产问题(重要)
### 缓存穿透 ### 缓存穿透
@ -480,4 +518,5 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB然后直接删
- 《Redis 开发与运维》 - 《Redis 开发与运维》
- 《Redis 设计与实现》 - 《Redis 设计与实现》
- Redis Transactions : https://redis.io/docs/manual/transactions/ 。 - Redis Transactions : https://redis.io/docs/manual/transactions/
- What is Redis Pipelinehttps://buildatscale.tech/what-is-redis-pipeline/