---
# System prepended metadata

title: 2020 年 08 月討論

---

2020 年 08 月討論
===

## Cindy
https://hackmd.io/@cindyliu923/HJ0vRHZmD

## 蒼時

## 詹昇

https://hackmd.io/@5Qs45kiLTZGZg3r154VjPQ/HJXK3zCfP#/

##  參考資料

* https://pdabrowski.com/articles/how-sidekiq-really-works
* https://draveness.me/sidekiq/

## Sidekiq 與 Redis

### Background Job

在軟體開發中我們經常會把一些需要長時間處理的任務放到背景來執行，一般來說我們會使用 Queue 的機制來將任務記錄下來，並且在有空閑的 Worker 時取出任務來處理。

> 個人認為在電腦多工的概念上是類似的，不過在這邊我們是為了讓應用程式能將過長且不需要即時回應的任務延後處理，實際上也就是「非同步」的概念。

### Ruby 的簡易實作

在 Ruby 的 `thread` 標準函式庫中已經提供了 `Queue` 物件，內置了一個鎖（Lock）來確保寫入跟取出不會有競爭的情況存在，因此我們可以很簡單的利用 Thread 實現一個單機版本的 Sidekiq。

```ruby
class Task
  def initialize(*args, &block)
    @args = args
    @proc = block
  end

  def perform
    @proc.call @args
  end
end

class Worker
  def initialize(idx, queue)
    @idx = idx
    @thread = nil
    @queue = queue
  end

  def run!
    return unless @thread.nil?

    @thread = Thread.new do
      loop do
        task = @queue.pop
        ret = task.perform
        puts "Worker #{@idx} run: #{ret}"
      end
    end
  end
end

queue = Queue.new
workers = 3.times.map { |idx| Worker.new(idx, queue) }
workers.each(&:run!)

count = 0
loop do
  4.times do
    task = Task.new(count) { |args| "Task run #{args[0]}" }
    count += 1
    queue << task
  end
  sleep 1
end
```

### 使用 Redis 保存 Queue

我們可以利用 Redis 的 LIST 功能把任務的資訊記錄到 Redis 裡面，並且利用 `RPUSH` / `BLPOP` 指令來取出資料

```ruby
require 'redis'
require 'json'

class RedisQueue
  def initialize
    @conn = Redis.new
  end

  def pop
    _, count = @conn.blpop 'example-queue' # 以 Blocking 方式取出資料，如果 LIST 是空的則進入 Blocking 狀態
    CountTask.new(count.to_i)
  end

  def <<(task)
    @conn.rpush 'example-queue', task.count
  end
end

class CountTask
  attr_reader :count

  def initialize(count)
    @count = count
  end

  def perform
    "Task run in #{count}"
  end
end

class Worker
  def initialize(idx)
    @idx = idx
    @thread = nil
    @queue = RedisQueue.new # 因為共用 Connection 會 Blocking 所有人所以先各自建立連線
  end

  def run!
    return unless @thread.nil?

    @thread = Thread.new do
      loop do
        task = @queue.pop
        ret = task.perform
        puts "Worker #{@idx} run: #{ret}"
      end
    end
  end
end

queue = RedisQueue.new
workers = 3.times.map { |idx| Worker.new(idx) }
workers.each(&:run!)

count = 0
loop do
  4.times do
    task = CountTask.new(count)
    count += 1
    queue << task
  end
  sleep 1
end
```


