快手面试

今天面试了快手,一面直接挂。
我面的高级研发工程师,结果标题显示专家,问了hr,说这只是标题,具体会根据经历匹配相应的职级。

面试过程,先自己我介绍,然后出了下面5个题。
先问了一个协程池设计。
参考 https://studygolang.com/articles/15477
里面有点错误我改了一下。

```
package main

import (
        "fmt"
        "time"
)

/* 有关Task任务相关定义及操作 */
//定义任务Task类型,每一个任务Task都可以抽象成一个函数
type Task struct {
        f func() error //一个无参的函数类型
}

//通过NewTask来创建一个Task
func NewTask(f func() error) *Task {
        t := Task{
                f: f,
        }

        return &t
}

//执行Task任务的方法
func (t *Task) Execute() {
        t.f() //调用任务所绑定的函数
}

/* 有关协程池的定义及操作 */
//定义池类型
type Pool struct {
        //对外接收Task的入口
        EntryChannel chan *Task

        //协程池最大worker数量,限定Goroutine的个数
        worker_num int

        //协程池内部的任务就绪队列
        JobsChannel chan *Task
}

//创建一个协程池
func NewPool(cap int) *Pool {
        p := Pool{
                EntryChannel: make(chan *Task),
                worker_num:   cap,
                JobsChannel:  make(chan *Task),
        }

        return &p
}

//协程池创建一个worker并且开始工作
func (p *Pool) worker(work_ID int) {
        //worker不断的从JobsChannel内部任务队列中拿任务
        for task := range p.JobsChannel {
                //如果拿到任务,则执行task任务
                task.Execute()
                fmt.Println("worker ID ", work_ID, " 执行完毕任务")
        }
}

//让协程池Pool开始工作
func (p *Pool) Run() {
        //1,首先根据协程池的worker数量限定,开启固定数量的Worker,
        //  每一个Worker用一个Goroutine承载
        for i := 0; i < p.worker_num; i++ {
                go p.worker(i)
        }

        //2, 从EntryChannel协程池入口取外界传递过来的任务
        //   并且将任务送进JobsChannel中
        for task := range p.EntryChannel {
                p.JobsChannel <- task
        }

        //3, 执行完毕需要关闭JobsChannel
        close(p.JobsChannel)

        //4, 执行完毕需要关闭EntryChannel
        //close(p.EntryChannel) // 这里关闭会报错的
}

//主函数
func main() {
        //创建一个Task
        t := NewTask(func() error {
                fmt.Println(time.Now())
                return nil
        })

        //创建一个协程池,最大开启3个协程worker
        p := NewPool(3)

        //开一个协程 不断的向 Pool 输送打印一条时间的task任务
        go func() {
                for {
                        p.EntryChannel <- t
                }
               close(p.EntryChannel ) // 要在这里关闭
        }()

        //启动协程池p
        p.Run()

}
```
  1. redis 实现一个延迟队列
    我给的答案是 循环队列轮询的方式。
    新建一个队列,每个格子就是一秒钟,将执行的任务按照执行时间放到相应的格子,然后程序每秒去遍历对应的格子。这种方式有缺点。
    比较好的实现。基于Redis实现延时队列服务 - 程序人生ly - 博客园

  2. 100亿用户签到统计问题
    描述:

存储100亿用户30天内的签到数据

能够快速知道某天有多少人签到了

能够快速知道某日到某日中多少人签到了

能够快速知道用户A在某天是否签到。

我答得是使用 set day (user)
查询某几天的时候要对这几天的数据作并集(应该是性能不是很好)
比较专业的解法 :redis如何做亿级用户登录日活统计?(内含资料) - 知乎
https://blog.csdn.net/z69183787/article/details/104335721

  1. 设计一个压测工具
    描述:
    能够指定 并发数 压测时间,
    压测结束后输出 平均请求耗时 最大请求耗时 QPS 失败率

可以使用context 去控制压测协和的停止,最后在main里把所有结果进行汇总。

type Result struct {
	// 成功数,失败次数,等数据。
}

func pressTest(ctx context.Context, result chan Result) {
	res := Result{}
	for  {
		select {
		case <-ctx.Done():
			break
		} 
		// 测试接口,更新res
	}
	
	return res
}

//主函数
func main() {
	c, _ :=context.WithTimeout(context.Background(), 10*time.Second) // 设置时间
	result := make(chan Result)
	// 有10个压测线程 // 设置个数
	for i:=0;i<10 ;i++ {
		go pressTest(c, result)
	}
	totalResult := Result{}

	for i:=0;i<10;i++ {
		subRes := <-result
		// 得到子结果汇总到总结果中totalResult。
	}
	close(result)
	
	fmt.Println(totalResult)
}
  1. 设计一个直播间打赏排行系统?

直播运营活动中经常会有这样的需求,根据用户送礼情况做排名。这个排行榜具有以下特点:

  1. 用户每次请求会返回用户的排名
  2. 送礼金额越多粉丝排名越靠前
  3. 相同金额送礼越早越靠前
  4. 排行榜会随着粉丝送礼变化而不断变化

我的答案是使用zset user weight , 但是相同金额时间比较这方面我是考虑把金额和时间进行计算得到一个优先级的值 。具体是weight = money * 10^20 + time

下面文章大致思路一样,但是当打赏金额,比较大的时候可能不支持了。
https://blog.csdn.net/u201012980/article/details/79394614

虽然挂了,不管怎么样,学到知道就可以,下次面试又有更高级的东西可以讲。

1 个赞

请教一下,协程池那里
EntryChannel和JobsChannel共用一个channel不行么,分成两个channl的作用是?

从运行上来说是可以的,分开可能是为了更好管理,具体没有深究。

是审核系统吗, 怎么面试题和我这么像

是电商。