Задать вопрос
  • Когда использование Cgo оправданно?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Основной недостаток использования Cgo - это снижение производительности.
    Вызовы C/C++ достаточно затратны по ресурсам, т.к. C ничего не знает о данных в Go и для вызова C необходимо полностью сохранять все регистры и переключать стек, за счёт этого и возрастают накладные расходы, соответственно снижается производительность.

    Использование Cgo имеет смысл, когда есть объёмные библиотеки написанные на C/C++, которые можно использовать. При этом написание кода на чистом Go намного затратнее, чем использование этих библиотек с Cgo.

    > в каких кейсах следует использовать cgo для улучшения производительности
    На сколько я понимаю при вызове простых функций производительность не улучшится, а наоборот, скорее ухудшится.
    Но не исключаю, что есть кейсы, когда есть серьёзные расчёты/жёсткое управление памятью (частые выделения/освобождения), когда за счёт того, что в этом случае не будет использован сборщик мусора можно получить увеличения производительности.

    У меня был подобный кейс на Perl, но принцип тот же.
    При скачивании HTML страниц размер занимаемой RAM скриптом постоянно увеличивался и в итоге "съедал" всю память на сервере.

    Задача скрипта была скачивать HTML страницы, извлекать из них все ссылки на внешние ресурсы.
    Я принял решение и написал функцию на С, которая выкачивала страницу, извлекала ссылки, очищала память и возвращала в Perl уже готовый список ссылок. Скрипты перестали постоянно "пухнуть", их можно было запустить в несколько раз больше по количеству на том же сервере + производительность стала явно выше.

    В общем всё сильно зависит от задачи, но, думаю, более 90% кейсов будет связано с тем, что намного дешевле использовать готовую библиотеку C/C++ с Cgo, чем переписать эту библиотеку на чистом Go.
    Ответ написан
    Комментировать
  • Как сделать, чтобы в редактируемом циклом значении сохранялись результаты итераций?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    попробуйте такой вариант
    var fileWork = ioutil.ReadFile("testFile.txt")
    // создаём переменную которую будем перезаписывать, сразу наполняем данными и сразу приводим к string чтобы было удобно работать
    new_fileWork := string(fileWork)
    for _, link := range arrLinks {        
        var regul = link + ".txt"
        myRegexp, err := regexp.Compile(regul)
        // new_fileWork передаем как параметр, его же и обновляем, за счёт этого при каждой итерации данные не теряются как в вашем примере
        new_fileWork = myRegexp.ReplaceAllString(new_fileWork , link)   // Редактируем.
    }
    var new_fileWorkB = []byte(new_fileWork)

    Если не поможет - сделайте песочницу чтобы можно было посмотреть полный код и напишите какой результат ожидаете получить.
    Ответ написан
  • Как разрешить вызов api контроллера, только одному серверу по ip (который отправляет вебхук)?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Чаще всего, в таких случаях, используют секретный ключ, который передают или в параметрах запроса (так лучше не делать, т.к. секретный ключ будет виден в логах промежуточных прокси и сервера) или в HTTP заголовке (этот вариант безопаснее, чем передача в параметрах запроса).

    Реже используют вариант с цифровой подписью запроса. Этот вариант ощутимо безопаснее, чем передавать секретный ключ в открытом виде. При таком варианте вы не только знаете о том, что запрос отправил нужный вам сервер, но и о том, что запрос не был изменён никем (например прокси сервером).

    Но оба эти варианта требуют доработки со стороны отправляющего запрос сервера.
    Как я понимаю для вас этот вариант не доступен.

    В принципе вариант защиты по IP адресу вполне нормальный.
    Самый простой способ - настроить firewall, т.е. закрыть доступ всем к вашему сервису и открыть только для IP сервера, который делает запросы.

    Если вариант с firewall не подходит - можно обеспечивать защиту на стороне Go.
    IP можно получать из запроса HTTP https://pkg.go.dev/net/http#Request

    Вот готовый пример
    package main
    
    import (
        "fmt"
        "log"
        "net/http"
    )
    
    func handler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Your IP is %s", r.RemoteAddr)
    }
    
    func main() {
        http.HandleFunc("/", handler)
        log.Fatal(http.ListenAndServe(":8080", nil))
    }

    Иногда (зависит от настроек веб сервера) реальный IP может приходить в HTTP заголовках запроса
    ip := r.Header.Get("X-Forwarded-For")
    // или
    ip := r.Header.Get("X-Real-IP")
    Ответ написан
    3 комментария
  • Где лучше хранить глобальные структуры?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если брать стандартную структуру проекта - я бы отталкивался от того есть ли смысл эту структуру делать доступной. Если да - тогда смотрел бы в сторону /pkg, если нет /internal.

    По хорошему, я бы сделал отдельный пакет для этой структуры и в зависимости от того используется ли она для внутренних целей или нет - положил бы пакет в /pkg или в /internal

    Для чего используется ваша глобальная структура?
    Ответ написан
    Комментировать
  • Как настроить subroute в gorilla mux golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Надо убрать '/' из example.com/, т.е. должно быть router.Host("example.com").Subrouter()
    Ответ написан
    1 комментарий
  • Как правильно передать множество параметров в функцию в Golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    В таких случаях лучше передавать структуру как параметр.
    Пример
    type User struct {
        Name string
        Email string
        // все нужные поля далее
    }
    
    func CreateUser(r.Context(), user)
    Ответ написан
    Комментировать
  • Нужен ли Nginx для веб приложения на Golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Не нужен, в Go есть полноценный веб сервер.

    Nginx есть смысл использовать в случае если есть необходимость обслуживать больше одного домена на одном и том же IP:Port, ну и для раздачи статики (изображений, CSS, Javascript и т.д.)

    Еще есть смысл использовать Nginx когда у вас большая нагрузка и ваш сервис на Go работает на нескольких серверах - Nginx'ом можно балансировать нагрузку между этими серверами.
    Ответ написан
    9 комментариев
  • В чем сложность поддержки проектов на Go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Более четырёх лет пишу на Go, лично для меня поддерживать проекты на других языках сложнее ))
    До Go много лет писал на Perl/PHP/Python/Java/C/Ruby on rails/JavaScript и "крутил" еще не мало чего.
    Сейчас пишу только на Go и Python.

    Основная сложность в Go - это то, что если вы начинаете писать "криво" - то писать так очень трудно :))
    Подход к реализации в Go достаточно непривычный для тех, кто приходит из скриптовых языков.
    Например запрет циклических импортов - это самое первое, что "взрывает" мозг :)
    Нужно время чтобы адаптироваться после скриптовых языков.

    Про обработку ошибок - это да, это, пожалуй, единственное что мне не нравится в Go.
    Для себя я решил эту проблему за 1-2 часа :) и больше меня это не беспокоит.

    По ООП - в Go оно есть, лично мне оно нравится намного больше, чем ООП в других языках ))
    Всё, что реально нужно - всё есть, но при этом ничего лишнего нет.
    Интерфейсы - просто прекрасны :)) Каналы, горутины - это просто "счастье" :))

    А если объективно - то я бы не стал говорить о сложности поддержки проекта применимо к какому то конкретному языку.

    Как правило сложность поддержки возникает там, где нормальная архитектура не заложена в проект.
    Т.е. проект как могла так и писала куча людей плюс, как обычно, сделать нужно было побыстрее )), от этого никто вообще не думал об архитектуре и получилось то, что получилось :))
    Про покрытие тестами и документацию мы вообще не говорим )))

    А когда в таком проекте нужно что-то доработать или исправить ошибку, то почти всегда очень не просто найти то место, где нужно править, и после правок в одном месте, как правило, что-то ломается в другом :)

    Как минимум, нужно соблюдать принципы SOLID, иначе код получается, мягко говоря, не поддерживаемым.
    Но понимание этих принципов так же приходит с опытом, их нельзя научиться соблюдать просто прочитавши книгу или статью. Нужно сделать несколько проектов используя эти принципы, написать криво, понять как было бы правильно, всё переделать и так несколько раз.
    Тогда, со временем, начнёт получаться понятный и легко поддерживаемый код.

    Так мы приходим к тому, что если опыта нет - тогда проект поддерживать будет достаточно тяжело не взирая на язык и т.д.

    Хочется добавить, что как бы не писали про Go, что он простой и т.д. - он явно сложнее Python/PHP и т.д.
    Это еще один повод сказать, что поддерживать код на Go сложно ))

    Также стоит учитывать, что проекты которые пишут на Go зачастую сложнее, чем проекты для которых выберут скриптовый язык. Go выбирают когда планируются большие нагрузки, параллельная обработка чего-либо и т.д., а это, в свою очередь, дополнительно усложняет проект.
    Ответ написан
    7 комментариев
  • Является ли передача переменной по указателю затратной операцией?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Я видел эти же или похожие бенчмарки, которые приведены в этой статье, год или два назад.
    Тут важно отметить, что методика тестирования в статье не учитывает очень важный момент - там передача значения происходит всего один раз, т.е. функцию вызвали один раз и на том всё.

    Но часто бывает так, что данные приходится передавать многократно.

    Например есть функция, которая извлекает из базы данные, вторая функция обрабатывает эти данные, во время работы второй функции - вызываются еще несколько разных функций и все эти данные в каждую из них передаются, а потом еще все эти данные передаются в JSON сериализатор и т.д.
    Т.е. одни и те же данные передаются много раз из функции в функцию.

    Если не использовать передачу по указателю - данные будут копироваться каждый раз при вызове функции.
    В таком случае передача данных по указателю однозначно отработает быстрее даже с учётом того, что будет задействован сборщик мусора.

    В общем если структуры небольшого размера и используются всего один-два раза - быстрее отработает передача/создание такой структуры по значению как и написано в статье.

    Но если эти данные нужно будет передавать во много разных функций - то в большинстве случаев передача через указатель будет быстрее, даже с учётом того, что garbage collector это ресурсоёмко.
    Ответ написан
    Комментировать
  • Как можно измерить производительность http сервера?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Для подобного тестового кода можно использовать ab (Apache HTTP server benchmarking tool)

    Например так
    ab -n 10000 -c 1000 http://localhost:8080/

    Где:
    -n это количество запросов, которое нужно сделать
    -с это количество одновременных запросов

    Запустил у себя на одном из серверов, результат вот такой
    Server Software:
    Server Hostname:        localhost
    Server Port:            8080
    
    Document Path:          /
    Document Length:        5 bytes
    
    Concurrency Level:      1000
    Time taken for tests:   0.509 seconds
    Complete requests:      10000
    Failed requests:        0
    Write errors:           0
    Total transferred:      1210000 bytes
    HTML transferred:       50000 bytes
    Requests per second:    19627.39 [#/sec] (mean)
    Time per request:       50.949 [ms] (mean)
    Time per request:       0.051 [ms] (mean, across all concurrent requests)
    Transfer rate:          2319.25 [Kbytes/sec] received

    Т.е. при 1000 одновременных запросов сервер сможет обрабатывать в среднем 19627 запросов в секунду

    Но важно понимать, что в реальном проекте у вас будет не один endpoint, и что каждый endpoint, в зависимости от того, что он будет делать - будет показывать разный RPS.

    Например если сейчас вы добавите еще один endpoint и будете делать запросы в базу для формирования ответа - RPS будет значительно меньше.
    Ответ написан
  • Почему Go с горутинами работает на одном ядре?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Горутины выполняются на разных процессорах как и должны, у вас четвёртое число слишком большое, все горутины завершают выполнение, а одна продолжает работать. По этому и создаётся впечатление, что они все работают на одном ядре.

    Чтобы в этом убедиться добавим два fmt.Printf, чтобы получилось вот так
    package main
    
    import (
        "fmt"
        "runtime"
        "sync"
    )
    
    var wg sync.WaitGroup
    
    func main() {
        runtime.GOMAXPROCS(8)
        arr := []int{1343434, 1343434300, 234343400, 334343434000, 400434340, 203434340, 4232, 23545, 15535, 353535, 33434434, 5334345, 3533434345, 3535}
        for idx, el := range arr {
            wg.Add(1)
            go test(el, idx)
        }
        wg.Wait()
    }
    
    func test(el int, idx int) {
        fmt.Printf("%d started: %d\n", idx, el)
        for i := 0; i < el; i++ {
            el = el - 1
        }
        fmt.Printf("%d completed: %d\n", idx, el)
        defer wg.Done()
    }


    Вывод будет примерно таким
    4 started: 400434340
    8 started: 15535
    8 completed: 7767
    13 started: 3535
    13 completed: 1767
    6 started: 4232
    6 completed: 2116
    10 started: 33434434
    3 started: 334343434000
    7 started: 23545
    7 completed: 11772
    5 started: 203434340
    12 started: 3533434345
    11 started: 5334345
    2 started: 234343400
    1 started: 1343434300
    11 completed: 2667172
    9 started: 353535
    9 completed: 176767
    0 started: 1343434
    0 completed: 671717
    10 completed: 16717217
    5 completed: 101717170
    4 completed: 200217170
    2 completed: 117171700
    1 completed: 671717150
    12 completed: 1766717172

    При внимательном просмотре станет видно, что нет записи 3 completed.

    меняю элементы с индексами 4 и 5 на такие же числа 334343434000, 334343434000, т.е. делаем чтобы все горутины отработали, но чтобы 3 осталось, т.е. вот так

    arr := []int{1343434, 1343434300, 234343400, 334343434000, 334343434000, 334343434000, 4232, 23545, 15535, 353535, 33434434, 5334345, 3533434345, 3535}


    Запускаем заново и видим, что теперь 3 ядра отлично загружены
    %Cpu0  :  0.3 us,  1.0 sy,  0.0 ni, 98.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    %Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    %Cpu2  :  1.7 us,  0.7 sy,  0.0 ni, 97.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    %Cpu3  :  1.6 us,  2.9 sy,  0.0 ni, 95.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    %Cpu4  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    %Cpu5  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    %Cpu6  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    %Cpu7  :  5.6 us,  1.0 sy,  0.0 ni, 93.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    Ответ написан
    Комментировать
  • Не могу загрузить в базу данных mysql значения golang. как это решить?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    У вас не запущен сервис MySQL, либо MySQL не слушает localhost:3306
    Ответ написан
  • Как создать переменую областью видимости пакета,чтобы не видели другие горутины и без их блокировки?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно сделать что-то тапа такого.
    Реализация не очень красивая, но, думаю, это даст вам понимание в каком направлении можно двигаться.
    package pkg2
    import "fmt"
    
    type Pkg2 struct {
         str2 []string
    }
    
    func New() *Pkg2 {
        return &Pkg2{
            str2: []string{},
        }
    }
    
    func (p *Pkg2) Test(str string){
        p.str2 = append(p.str2, str)
        fmt.Printf("str:%v %v \n", str, p.str2)
    }


    package main
    
    import (
      "sync"
      "pkg2"
    )
    var wg sync.WaitGroup
    
    func main() {
      text:=[]string{"a1", "a2", "a3", "a4", "a5"}
      for i,str:=range text{
        wg.Add(1)
        p := pkg2.New()
        go start(str, p)
      }
      wg.Wait()
    }
    
    func start(str string, p *pkg2.Pkg2){
        p.Test(str)
        wg.Done()
    }
    Ответ написан
    3 комментария
  • Как обычно логируют краши от паник?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если говорить про http сервер - я перенял практику из Django.

    У меня есть http middleware, который:
    - вызывает recover
    - нормально отвечает на запрос, а не падает, т.е. 500 + что мы уже знаем об ошибке и работаем над её устранением
    - собирает всю информацию по запросу, http заголовки, cookies, настройки сервера, переменные окружения и т.д. и оповещает меня по почте/телеграм

    Это даёт мне возможность узнавать о проблеме до того, как кто-то напишет в поддержку.

    Люди, в хорошем смысле, удивляются, когда у них что-то не получается, сервер отвечает 500, они не поймут что делать. А я уже узнал об этом, исправил ошибку и написал им письмо о том, что была ошибка, но уже всё исправлено :)
    Ответ написан
    Комментировать
  • Go run автоматическая пересборка при изменении кода?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Я использую github.com/cespare/reflex для автоматической сборки при изменении кода, очень удобно
    Ответ написан
    Комментировать
  • Для чего нужен указатель структуры в слайсе?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Тут сильно зависит от того, как вы будете использовать данные.

    Например, если вы во все функции передаёте players
    someFunc1(players)
    someFunc2(players)
    someFunc3(players)

    тогда указатели на конкретного player'a использовать особо смысла нет, от этого будет даже хуже, т.к. при создании нового player'a (при взятии адреса) будет задействован сборщик мусора, что вызовет бесполезную доп. нагрузку.

    Если же player структура не маленькая или их очень много, и обработка будет происходить каждого player'a в отдельности + при этом будет использоваться много разных функций, например вот так
    func main() {
        var players[] *Player
        players = append(players, &Player{Id: 1, Name: "Bob"})
        ...
        players = append(players, &Player{Id: 100, Name: "Bob"})
        for i := range players {
            someFunc1(players[i])
            someFunc2(players[i])
            someFunc3(players[i])
            someFunc4(players[i])
            // или одна из someFunc вызывает еще какие то функции и передаёт players туда 
        }
    }

    в этом случае вы получите выигрыш в производительности за счёт того, что при вызове каждой функции будет копироваться лишь указатель (8байт на 64битных процессорах) вместо всей структуры
    Если точнее - вы получите доп. нагрузку за счёт того, что будет задействован сборщик мусора, но за счёт того, что вместо большого объёма данных при каждом вызове функции будет копироваться лишь указатель - вы получите выигрыш, который нивелирует проигрыш от сборщика мусора.

    На всякий случай о том, когда есть смысл использовать указатель, а когда нет
    https://qna.habr.com/q/1046708#answer_2019152
    Ответ написан
    3 комментария
  • Как получить адресс биткоин кошелька?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Попробуйте вот такой вариант
    import ecdsa
    import hashlib
    import base58
    
    # тут ваш ключ
    str_private_key = "L3KijrvAsMuLaCaEMaj2LcHwRpp3Koxkab6ipX7s9LfewoEU69g6"
    
    private_key = base58.b58decode_check(str_private_key)
    private_key = private_key[1:]
    
    signing_key = ecdsa.SigningKey.from_string(private_key, curve = ecdsa.SECP256k1)
    verifying_key = signing_key.get_verifying_key()
    public_key = bytes.fromhex("04") + verifying_key.to_string()
    
    sha256_1 = hashlib.sha256(public_key)
    
    ripemd160 = hashlib.new("ripemd160")
    ripemd160.update(sha256_1.digest())
    
    hashed_public_key = bytes.fromhex("00") + ripemd160.digest()
    checksum_full = hashlib.sha256(hashlib.sha256(hashed_public_key).digest()).digest()
    checksum = checksum_full[:4]
    bin_addr = hashed_public_key + checksum
    
    result_address = base58.b58encode(bin_addr)
    print ("Bitcoin address ", result_address)


    В этом видео https://www.youtube.com/watch?v=tX-XokHf_nI объясняют все детали.
    Ответ написан
    6 комментариев
  • Выполнение/вызов функции из Go файла при нажатии на кнопку html?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Алгоритм у вас будет примерно таким...
    При нажатии на кнопку у вас будет вызвана функция (обработчик события onClick у кнопки).
    В этой функции вы сделаете AJAX запрос по протоколу HTTP на ваш сервер, где запущено ваше ПО на Go.
    В main.go вы запускаете HTTP сервер, который примет запрос от вашей функции JavaScript, обработает его и ответит.

    Разберём самый простой вариант с методом GET, чтобы вам было проще тестировать.

    На сервере (своём компьютере) запускаете main.go примерно такого содержания
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func helloHandler(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "hello\n")
    }
    
    func main() {
        http.HandleFunc("/hello", helloHandler)
    
        http.ListenAndServe(":8081", nil)
    }

    Этот сервис будет слушать порт 8081 любого IP адреса на сервере, где будет запущен.
    Предположим, что вы тестируете на своём компьютере, в этом случае для проверки вам нужно в браузере откройте url `127.0.0.1:8081/hello`
    После этого вы должны увидеть в ответе просто текст: "hello".
    Когда этот этап будет завершен - вы сможете перейти к вызову этого URL уже из JavaScript.
    Для этого можете использовать то, что вам привычнее, если такого нет - можете воспользоваться этим примером
    const req = new XMLHttpRequest();
      const url='http://127.0.0.1:8081/hello';
      req.open("GET", url);
      req.send();
      req.onreadystatechange=(e)=>{
        console.log(req.responseText)
      }

    PS: Если не понятно или что-то не получится - пишите, помогу разобраться.
    Ответ написан
    21 комментарий
  • Может кто-то посоветовать книги для создания блокчейна?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Хорошие книги:
    Андреас Антонопулос - Осваиваем биткойн. Программирование блокчейна.
    Имран Башир - Блокчейн архитектура криптовалюты
    Калле Розенбаум - Грокаем технологию Биткоин
    Ответ написан
    Комментировать
  • Как сделать автоматическое заполнение форм на сайте через скрипт Golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно использовать встроенную функцию, так будет надёжнее:
    formData := url.Values{}
    formData.Set("user_forms[0][name]", authDate.fName)
    ...
    resp, err := http.PostForm(postUrl, formData)


    Но судя по коду, вам нужно больше, чем просто отправить данные формы.
    Вы явно хотите пройти аутентификацию, результатом отправки данных в подобную форму, скорее всего, будет cookie, по которому вас дальше сайт будет аутентифицировать.

    Для решения такой задачи будет лучше использовать какой нибудь web scraping framework.

    Например:
    https://github.com/gocolly/colly
    https://github.com/anaskhan96/soup
    Ответ написан
    2 комментария