Как синхронизировать постоянное написание и периодическое чтение и обновление - PullRequest
0 голосов
/ 13 февраля 2020

Определение проблемы:

У нас есть это устройство IOT, каждое из которых отправляет нам журналы о местонахождении автомобилей. Мы хотим вычислить расстояние, на котором машина путешествует онлайн! поэтому, когда приходит журнал (после помещения его в очередь и т. д. c), мы делаем это:

type Delta struct {
    DeviceId string
    time     int64
    Distance float64
}
var LastLogs = make(map[string]FullLog)
var Distances = make(map[string]Delta)


func addLastLog(l FullLog) {
    LastLogs[l.DeviceID] = l
}
func AddToLogPerDay(l FullLog) {
    //mutex.Lock()
    if val, ok := LastLogs[l.DeviceID]; ok {
        if distance, exist := Distances[l.DeviceID]; exist {
            x := computingDistance(val, l)
            Distances[l.DeviceID] = Delta{
                DeviceId: l.DeviceID,
                time:     distance.time + 1,
                Distance: distance.Distance + x,
            }
        } else {
            Distances[l.DeviceID] = Delta{
                DeviceId: l.DeviceID,
                time:     1,
                Distance: 0,
            }
        }
    }
    addLastLog(l)

}

, который в основном вычисляет расстояние, используя функцию полезности! поэтому в Distances каждый идентификатор устройства сопоставляется с некоторым пройденным расстоянием! теперь вот где начинается проблема: хотя эти расстояния добавляются к Distances map, я хочу, чтобы подпрограмма go помещала эти данные в базу данных, но, поскольку существует много устройств и много журналов и так далее, выполняют этот запрос для каждого журнала. не очень хорошая идея Так что мне нужно это каждые 5 секунд, что означает, что каждые 5 секунд пытаться очистить список всех последних расстояний, добавленных на карту. Я написал эту функцию:

func UpdateLogPerDayTable() {
    for {
        for _, distance := range Distances {
            logs := model.HourPerDay{}
            result := services.CarDBProvider.DB.Table(model.HourPerDay{}.TableName()).
                Where("created_at >?  AND device_id = ?", getCurrentData(), distance.DeviceId).
                Find(&logs)
            if result.Error != nil && !result.RecordNotFound() {
                log.Infof("Something went wrong while checking the log: %v", result.Error)
            } else {
                if !result.RecordNotFound() {
                    logs.CountDistance = distance.Distance

                    logs.CountSecond = distance.time

                    err := services.CarDBProvider.DB.Model(&logs).
                        Update(map[string]interface{}{
                            "count_second":   logs.CountSecond,
                            "count_distance": logs.CountDistance,
                        })
                    if err.Error != nil {
                        log.Infof("Something went wrong while updating the log: %v", err.Error)
                    }

                } else if result.RecordNotFound() {
                    dayLog := model.HourPerDay{
                        Model:         gorm.Model{},
                        DeviceId:      distance.DeviceId,
                        CountSecond:   int64(distance.time),
                        CountDistance: distance.Distance,
                    }
                    err := services.CarDBProvider.DB.Create(&dayLog)
                    if err.Error != nil {
                        log.Infof("Something went wrong while adding the log: %v", err.Error)
                    }
                }
            }
        }
        time.Sleep(time.Second * 5)
    }
}

она называется go utlis.UpdateLogPerDayTable() в другой подпрограмме go. Однако здесь есть много проблем:

  1. Я не знаю, как обезопасить Distances, поэтому, когда я добавляю его в другую процедуру, я читаю его где-то еще, все в порядке! (Проблема в том, что что я хочу использовать go каналов и не знаю, как это сделать)
  2. Как мне запланировать задачи в go для этой проблемы?
  3. Возможно, я добавлю Redis для хранения всех устройств, которые подключены к Интернету, чтобы я мог быстрее выполнять запрос выбора и просто обновлять фактическую базу данных. также добавьте время истечения для redis, так что если устройство не отправляло и данные в течение некоторого времени, оно исчезает! куда мне поместить этот код?

Извините, если моих объяснений было недостаточно, но мне действительно нужна помощь. специально для реализации кода

1 Ответ

1 голос
/ 13 февраля 2020

Go имеет действительно крутой паттерн, используя для / select по нескольким каналам. Это позволяет группировать дистанционные записи, используя как тайм-аут, так и максимальный размер записи. Использование этого шаблона требует использования каналов.

Прежде всего, смоделируйте ваши расстояния как канал:

distances := make(chan Delta)

Затем вы отслеживаете текущий пакет

var deltas []Delta

Тогда

ticker := time.NewTicker(time.Second * 5)

var deltas []Delta

for {
  select {
     case <-ticker.C:
        // 5 seconds up flush to db
        // reset deltas
     case d := <-distances:
        deltas = append(deltas, d)
        if len(deltas) >= maxDeltasPerFlush {
           // flush
           // reset deltas
        }
  }
}

Я не знаю, как защитить расстояния, поэтому, когда я добавляю его в другую процедуру, я читаю его где-то еще, все в порядке! (Проблема в том, что Я хочу использовать go каналов и не знаю, как это сделать)

Если вы намереваетесь сохранить карту и совместно использовать память, вам необходимо защитить ее с помощью взаимное исключение (мьютекс) для синхронизации доступа между go подпрограммами. Использование канала позволяет отправлять копию на канал, устраняя необходимость синхронизации через объект Delta. В зависимости от вашей архитектуры вы также можете создать конвейер из go подпрограмм, соединенных каналами, что позволит сделать так, чтобы только одна подпрограмма go ( monitor go подпрограмма ) обращалась к Delta , также устраняя необходимость синхронизации.

Как я могу запланировать задачи в go для этой проблемы?

Использование канала в качестве примитива для того, как вы проходите Deltas для различных go подпрограмм:)

Возможно, я добавлю redis для хранения всех устройств, подключенных к Интернету, чтобы быстрее выполнять запрос на выборку и просто обновлять фактическую базу данных. также добавьте время истечения для redis, так что если устройство не отправляло и данные в течение некоторого времени, оно исчезает! куда мне поместить этот код?

Это зависит от вашей законченной архитектуры. Вы можете написать декоратор для операции выбора, который будет сначала проверять redis, а затем go в БД. Клиент этой функции не должен был бы знать об этом. Операции записи могут быть выполнены таким же образом: запись в постоянное хранилище, а затем запись в redis с использованием кэшированного значения и срока действия. Используя декораторы, клиенту не нужно было бы об этом знать, он просто выполнял операции чтения и записи, и логика кэша c была бы реализована внутри декораторов. Для этого есть много способов, и это во многом зависит от того, где находится ваша реализация.

...