• Kaito
    2021-08-21
    1、很多人认为 Redis 是单线程,这个描述是不准确的。准确来说 Redis 只有在处理「客户端请求」时,是单线程的。但整个 Redis Server 并不是单线程的,还有后台线程在辅助处理一些工作 2、Redis 选择单线程处理请求,是因为 Redis 操作的是「内存」,加上设计了「高效」的数据结构,所以操作速度极快,利用 IO 多路复用机制,单线程依旧可以有非常高的性能 3、但如果一个请求发生耗时,单线程的缺点就暴露出来了,后面的请求都要「排队」等待,所以 Redis 在启动时会启动一些「后台线程」来辅助工作,目的是把耗时的操作,放到后台处理,避免主线程操作耗时影响整体性能 4、例如关闭 fd、AOF 刷盘、释放 key 的内存,这些耗时操作,都可以放到后台线程中处理,对主逻辑没有任何影响 5、后台线程处理这些任务,就相当于一个消费者,生产者(主线程)把耗时任务丢到队列中(链表),消费者不停轮询这个队列,拿出任务就去执行对应的方法即可: - BIO_CLOSE_FILE:close(fd) - BIO_AOF_FSYNC:fsync(fd) - BIO_LAZY_FREE:free(obj) / free(dict) / free(skiplist) 课后题:Redis 后台任务使用 bio_job 结构体来描述,该结构体用了三个指针变量来表示任务参数,如果我们创建的任务,所需要的参数大于 3 个,你有什么应对方法来传参么? 最直接的方法就是,把参数换成数组类型,这样就可以传递任意数量参数了。因为这里 Redis 的后台任务都比较简单,最多 3 个参数就足够满足需求,所以 job 直接写死了 3 个参数变量,这样做的好处是维护起来简单直接。
    展开
    共 4 条评论
    25
  • Darren
    2021-08-21
    今天这节课收获满满,以前看源码的时候,没注意过daemonize()方法,因为不是做C语言的,因此只是想着后台启动,没想到原始是这么启动起来的。 回答下问题,其实这个问题Redis的作者在源码中已经注释了 struct bio_job { time_t time; /* Time at which the job was created. */ /* Job specific arguments pointers. If we need to pass more than three * arguments we can just pass a pointer to a structure or alike. */ void *arg1, *arg2, *arg3; }; void*代表任意类型的指针,因此当参数多于三个时,可以传递数组或者结构。
    
    10
  • 曾轼麟
    2021-08-23
    感谢老师的文章,发现老师前面的问题都是为了这章节埋下伏笔的,一样首先回答老师的提问:有什么应对方法来传参么? 答: 最好的方式就是使用指针数组,因为指针数组本身就是一个个指针,可以通过index的顺序标记参数的含义类型,通过index就能快速获取不同的参数对应的指针(这个方案在最新的Redis代码中也有体现) 本篇文章确实让我赞叹,老师刚好通过本篇文章,联合前面几篇文章的问题,一气呵成的给出解答,那么我这边就借老师的气场补充一下我自己的理解和认识: 1、Redis是一个多进程多线程的程序: 通过这篇文章也能很清晰的认识到,在Redis中不但有fork的方式创建进程,也有通过pthread_create的方式创建线程,二者都能起到异步执行任务的效果 2、fork是一个沉重的方案: 除了以守护进程的方式启动时候会进行fork,bgsave也会进行fork。但是fork比thread的代价大的多,fork出来的子进程会复制一份父进程的虚拟地址表(虚拟内存技术,子进程复制父进程的地址表,复用原有的地址空间,当某个地址上的数据涉及修改的时候才会把数据复制一份到自己的地址空间)从而也可能会导致出现写时复制等内存高损耗的开销。 3、Thread需要解决并发问题: 多线程虽然资源开销没有fork那么沉重,但是由于多线程的地址空间都属于同一个进程(线程属于进程),那么必然要解决并发问题。然而Redis的设计很巧妙,无论是bioInit的bioProcessBackgroundJobs使用分type的方式让每给线程依次执行列表上的任务,还是initThreadedIO(老师本篇没提到)使用信号量的方式控制线程的协调,都能避开内存共享带来的并发问题,从而即享受了多线程的优势,又避免了多线程的劣势。 读完本篇文章,非常建议大家去阅读一下《深入理解计算机系统》的 【第8章-异常控制流】 和 【第9章-虚拟内存】
    展开
    
    9
  • Milittle
    2021-08-23
    这节课我学到了什么: 1. 第一、redis不是单线程的,而是一个主线程,处理IO,另外有三个线程分别处理关闭fd、异步AOF刷盘、延迟释放。 2. 我们从bio文件中可以看到函数之间的配合。 回答一下课后题目: 可以看到最新版的代码,redis开发人员已经把这个重构了: bio_job里面包含了fd(给关闭文件和异步刷盘用的),lazy_free_fn、free_args给延迟释放用的,一个是函数,一个是函数所需要用的参数。 备注:结合老师讲的源码版本和自己结合最新的版本看,你会在这个过程里面学到不少,有一些时候问题,就被解决了,你也可以从中学习到一些。
    
    4
  • 🤐
    2022-07-24
    想起了我自己为私有化设计的定时任务调度。
    
    
  • 🐟🐙🐬🐆🦌...
    2022-03-24
    bio_pending[type] 的++ 和 -- 不是原子的吧,一个消费线程--,提交任务在++,看着也没加锁,????
    共 1 条评论
    
  • ikel
    2021-11-04
    pthread_attr_setstacksize 函数,来重新设置线程栈大小,这样做的目的是什么呢 对内存操作单线程我觉得是为了避免锁操作的资源开销吧
    
    
  • 一步
    2021-08-22
    后台线程有3个,后台进程有几个的?
    共 2 条评论
    
  • 可怜大灰狼
    2021-08-21
    如果所需要参数大于3个,我想可以把多个参数都封装到一个list或者dict中,然后占用arg1向实际业务方法传,只不过实际业务方法代码需要从list或dict再拆解出参数。
    
    