28 Matching Annotations
  1. Last 7 days
    1. 但如果在函数内部进行扩容操作,会分配新的底层数组,但原始Slice不会引用新的数组

      解决方式:1. 返回新的数组2. 调用方保证容量充足3 使用指针

  2. Mar 2026
    1. 因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信

      设备文件在磁盘上的原因 持久化标识:让不相关的进程能找到这个通道

      权限控制:利用文件系统的权限机制(rwx)

      命名空间:利用文件系统的目录结构管理管道 这个设备文件确实会写入磁盘,但它非常小,只包含:

      文件元数据(文件名、权限、时间戳)

      文件类型标记(p 表示 pipe)

      不包含传输的数据!

    1. 后台服务消费消息:后台服务从消息队列中消费秒杀成功的消息,执行以下操作: 1、为用户创建订单记录; 2、使用乐观锁将数据库中的库存数量减少1; 3、通过唯一标识(如用户ID+商品ID+时间戳)防止重复消费。

      go语言代码

    1. 它追求的是在这个集群里堆更多的机器,来扛住更高的并发,保证一台机器宕机了别的机器还能顶上(高可用)

      多机房部署

    1. 布隆过滤器:我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。 #

      ?

    2. 集群

      集群:解决数据量大的问题 主从:解决高可用的问题,避免单点故障 生产环境 = 集群中的每个分片内部都是主从架构,既解决了数据量大,又解决了高可用

    3. Redis 提供了 3 种写回硬盘的策略, 在 Redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:

      // 简化的Redis AOF写入流程 void writeAOF(char* command) { // 1. 先写入Redis自己的缓冲区(用户空间) redis_aof_buffer.append(command);

      // 2. write()系统调用:从用户空间拷贝到内核空间
      //   这个调用很快,只是拷贝数据
      write(redis_aof_fd, redis_aof_buffer);
      // 数据现在在内核缓冲区(Page Cache)中
      
      // 3. 根据appendfsync策略处理
      switch (appendfsync) {
          case ALWAYS:
              // 调用fsync(),强制内核立即把数据刷到硬盘
              // 系统会阻塞直到硬盘确认写入完成
              fsync(redis_aof_fd);
              break;
      
          case EVERYSEC:
              // 不立即刷盘,而是记录一下"需要刷盘"
              // 后台线程每秒执行一次fsync()
              mark_for_async_fsync();
              break;
      
          case NO:
              // 什么都不做,完全交给操作系统
              // 操作系统自己决定什么时候刷盘(通常缓冲区满了或系统空闲)
              break;
      }
      

      }

    4. 就将网络IO的处理改成多线程的方式了

      主线程是“事件分发器”:主线程的核心工作不再是亲自读写数据,而是专注于通过 I/O 多路复用机制(epoll_wait)高效地监听海量连接,快速发现哪些连接已经准备好数据了。它的作用就像一个“事件感知器”和“任务分发器”。

      I/O 线程是“数据搬运工”:主线程一旦感知到有数据就绪,就将具体的“搬运”任务(读取、解析、写回)交给多个 I/O 线程并行执行。这充分利用了多核 CPU 的能力来加速网络数据的处理,从而让主线程可以腾出手来,更快地处理源源不断的就绪事件。

    5. 就将网络IO的处理改成多线程的方式了

      当主线程通过 epoll 发现有多个 socket 已经准备好数据后,它会将这些 读/写任务分发给一个 I/O 线程池,由这些线程并行地去执行数据的读取、解析和写入操作。

    6. 所以 Redis 采用单线程(网络 I/O 和执行命令)那么快

      假设一个典型的Redis操作流程

      1. 从网络读取命令(I/O)

      2. 在内存中执行数据结构操作(内存访问)

      3. 将结果写回网络(I/O)

      耗时分布(经验值)

      network_io_time = 70% # 网络传输 memory_access_time = 28% # 内存操作 cpu_compute_time = 2% # 实际CPU计算

    7. # ZSet用过吗

      要实现一个能实时获取近一天内浏览量最高数据的“天维度”排行榜,核心在于解决数据的“时效性”与“持久性”之间的矛盾

      你提到的“数据一旦写入便永久存储”确实是使用Redis ZSET时需要考虑的问题。针对“近一天”这个滑动的时间窗口,业界主要有两种经典的设计模式,你可以根据业务对实时性和精确度的要求来选择。

      方案一:按时间片分割 + 动态聚合(滑动窗口)

      这个方案的核心思想是将数据按照固定的时间片(如小时)切分存储,查询时再动态合并近24小时的数据。它能够精确地反映“从当前时间往前推24小时”这个动态变化的窗口,非常适合需要高实时性和高精度的场景。

      • 存储结构:为每个小时创建一个独立的ZSET Key,例如 views:article:20250401:14。Key中包含日期和小时,这样既清晰又便于管理。
      • 写入逻辑:当有浏览量产生时,通过 ZINCRBY 命令,将对应小时Key中对应文章的分数(score)加1。同时,可以为这个Key设置一个过期时间(例如48小时或72小时),让Redis自动清理旧数据,避免内存无限增长。
      • 读取逻辑(获取实时近24小时榜)
        1. 确定时间范围:计算当前时间前24小时包含了哪些小时段(例如,最多24个Key)。
        2. 合并数据:使用 ZUNIONSTORE 命令,将这些小时Key的分数进行聚合(SUM),结果存储到一个临时的ZSET中。
        3. 获取排行榜:从临时ZSET中通过 ZREVRANGE 获取浏览量最高的前N个文章ID。
        4. 清理临时Key:查询完成后,及时删除这个临时ZSET。

      这种方式的优点是数据精确,但由于需要动态聚合多个Key,如果并发量极高,频繁执行 ZUNIONSTORE 可能会对Redis造成一定压力。作为一种优化,可以考虑将排行榜结果缓存几秒到几十秒。

      方案二:单Key + Key过期(日维度轮替)

      这个方案更加简单直接,以“天”为粒度进行切割。它的逻辑很符合直觉:“今天的排行榜”就用一个单独的Key来存储

      • 存储结构:使用一个Key,例如 ranking:article:20250401,代表某一天的排行榜。
      • 写入逻辑:当产生浏览量时,调用 ZINCRBY ranking:article:20250401 {articleId} 1,累加当天的分数。
      • 读取逻辑:要获取“近一天”的排行榜,其实就是直接获取“今天”这个Key的Top N。因为“昨天”的数据已经不属于“近一天”的范畴了。
      • 数据生命周期管理
        • 设置TTL:在为当天的Key写入第一条数据时,为其设置一个过期时间,例如72小时。这样,昨天的数据在明天过后就会被自动清理。
        • 定时任务:你也可以通过一个定时任务(如每天凌晨),将昨天的排行榜数据持久化到MySQL等数据库中作为历史归档,然后删除或重命名Redis中的旧Key,为当天的新Key做准备。

      这种方案最大的优点是实现简单,查询性能极高(无需聚合)。但它无法做到真正的“实时滑动窗口”,因为每天的榜单在午夜0点会准时重置。如果你所说的“近一天”严格指“过去24小时”(例如,周一上午10点看的是周日上午10点到现在的数据),那么这个方案就不够精确了。

      方案对比与总结

      为了让你更清晰地选择,我将两种方案的核心区别整理如下:

      | 维度 | 方案一:按时间片分割 + 动态聚合 | 方案二:单Key + Key过期(日维度轮替) | | :--- | :--- | :--- | | 时间窗口精度 | 。是真正的、精确到秒的“过去24小时”滑动窗口。 | 。是固定的“自然日”窗口,每天0点重置。 | | 查询性能 | 中等。需要动态聚合多个Key,可能产生额外开销。 | 极高。直接读取单个Key即可。 | | 实现复杂度 | 较高。需要维护多个Key,并编写聚合逻辑。 | 非常简单。逻辑清晰易懂。 | | 内存效率 | 。可以对每个小时Key设置短TTL,及时释放内存。 | 一般。单个Key数据量大,但过期后才会释放。 | | 适用场景 | 对实时性要求极高的场景,如实时热点新闻榜、实时商品热销榜。 | 业务意义与自然日对齐的场景,如每日热销商品榜、每日用户积分榜。 |

      总结建议

      • 如果你的业务要求绝对实时,且“近一天”的定义是严格的“过去24小时”(如微博热搜),那么方案一是更合适的选择。
      • 如果你的业务场景中,“天榜”就是指自然日的排行(如每日销售额榜单),并且希望实现最简单,那么方案二就能很好地满足需求。

      在实际的大型系统中,这两种模式也常常结合使用。例如,可以按小时粒度存储原始数据(方案一),同时通过定时任务提前聚合好“今日榜单”(方案二的结果)以加速查询,从而兼顾精度与性能。

      希望以上的分析和方案能帮助你设计出合适的排行榜系统。如果你对某个方案的代码实现细节,或者如何对聚合查询做进一步优化感兴趣,我们可以继续深入探讨。

  3. Feb 2026
    1. Doublewrite Buffer

      关于是否影响性能:顺序写入:第一阶段将多个页合并成一次顺序写入,效率很高

      批量操作:通常是一次性写入几十个页(默认128个)

      收益大于成本:虽然增加了一次写入,但换来了数据安全保障

    2. 数据库三大范式是什么?

      设计数据库表时需要满足三大范式,从而减少数据冗余、避免数据异常(保证一致性),并确保数据完整性。实际的大数据量、高并发场景下,下严格遵循会导致查询速度慢(join)链接,故常常选择存储一些冗余数据

    1. 有更多的 P 可以一起工作,加速执行完所有的 G。

      G是groutine, P负责调度,P的数量与内核线程数相等?M又是什么

    1. Runtime 维护所有的 goroutines,并通过 scheduler 来进行调度

      runtime通过scheduler来高效调度goruntine, 实现goruntine到线程间映射?管理?,最大程度利用资源