Сумничать как пишется слово сумничать

Автор аннита рудкова на чтение 5 мин. просмотров 39 опубликовано 22.12.2021 словари - сумничать. толковый словарь русского языка. поиск по

На чтение 5 мин. Просмотров 39 Опубликовано

Словари — сумничать. Толковый словарь русского языка. Поиск по слову, типу, синониму, антониму и описанию. Словарь ударений.

«Сумничать» или «с умничать»: как правильно написать?

Найдено определений:

сумничатьсумничать

толковый словарь

сов. неперех. разг.

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

толковый словарь ушакова

СУ́МНИЧАТЬ, сумничаю, сумничаешь (разг.). совер. к умничать.

толковый словарь ожегова

энциклопедический словарь

СУ́МНИЧАТЬ -аю, -аешь; св. (нсв. у́мничать). Неодобр. Сказать что-л., стараясь показать свой ум или сделать что-л. по-своему, считая себя умнее других.

академический словарь

-аю, -аешь; сов. разг. пренебр.

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

орфографический словарь

формы слов

су́мничать, су́мничаю, су́мничаем, су́мничаешь, су́мничаете, су́мничает, су́мничают, су́мничая, су́мничал, су́мничала, су́мничало, су́мничали, су́мничай, су́мничайте, су́мничавший, су́мничавшая, су́мничавшее, су́мничавшие, су́мничавшего, су́мничавшей, су́мничавших, су́мничавшему, су́мничавшим, су́мничавшую, су́мничавшею, су́мничавшими, су́мничавшем

синонимы

смудрить, смудровать, намудрствовать, намудрить

морфемно-орфографический словарь

грамматический словарь

полезные сервисы

«Сумничать» или «с умничать»: как правильно написать?

Содержание

  1. Как правильно пишется
  2. Какое правило применяется
  3. Примеры предложений
  4. Как неправильно писать

Затрудняетесь в том, как писать «сумничать» или «с умничать»? Нам следует вспомнить простое орфографическое правило. Обратимся к нему и определим верное написание данного глагола. Давайте разбираться.

Как правильно пишется

В соответствии с правилами правописания, данный глагол пишется в одно слово – сумничать.

Какое правило применяется

Мы знаем, что в русском языке есть как приставка «с», пишущаяся со словами слитно, так и предлог «с», пишущийся всегда раздельно. Нам следует определить, чем является «с» в представленном случае. Орфографическое правило гласит, что глаголы никогда не употребляются с предлогами. Получается, что «с» – префикс, который, естественно, пишется слитно. Сравним с аналогичными случаями: сбежать, слезть, съездить, сымитировать и т.п. Таким образом, выбираем вариант слитного написания.

Примеры предложений

  • Он попытался сумничать, но это вышло так нелепо, что все только рассмеялись.
  • Я хотел сумничать, но запутался в своих мыслях и промолчал.

Проверь себя: «Безкорыстно» или «бескорыстно» как пишется?

Как неправильно писать

Неверно писать данный глагол в два слова – с умничать.

«Сумничать» или «с умничать»: как правильно написать?

«Сумничать» или «с умничать»: как правильно написать?

Не знаете, какое написание активного в употреблении глагола сумничать» или «с умничатьверное? Как определить, слитный или раздельный вариант отвечает языковой норме? Выполним морфемный анализ слова, чтобы выбрать правильный вариант.

Как правильно пишется?

Анализируемое слово следует писать слитно с согласной «с» – «сумничать».

Какое правило применяется?

«Сумничать» – это неопределённая форма глагола («что сделать?»), которая употребляется в значении «сказать или сделать что-либо, стараясь показать себя умным». Вычленим в изучаемом глаголе следующие морфемы:

  • «с» – префикс;
  • «ум» – корень;
  • «н», «ича» – суффиксы;
  • «ть» – глагольное окончание.

Видим, что «с» выступает префиксом, который пишется со словом слитно.

В рассматриваемом случае «с» не является предлогом, т.к. эта служебная часть речи не может стоять перед глаголами. Сравним: схитрить, сымитировать, срубить, станцевать и др.

Примеры предложений

  • Ваня захотел сумничать, но его перебил старшеклассник.
  • Отец снисходительно улыбнулся, потому что я снова собирался сумничать.

Как неправильно писать

Нельзя писать глагол раздельно с буквой «с» – «с умничать». Также некорректно – «сумнечать», «сумнитчать».

«Сумничать» или «с умничать»: как правильно написать?

СУМНИЧАТЬ

СУМНИЧАТЬ
СУМНИЧАТЬ

Толковый словарь Ожегова1949-1992

Смотреть что такое “СУМНИЧАТЬ” в других словарях:

  • СУМНИЧАТЬ — СУМНИЧАТЬ, сумничаю, сумничаешь (разг.). совер. к умничать. Толковый словарь Ушакова. Д.Н. Ушаков. 1935 1940 … Толковый словарь Ушакова

  • сумничать — смудрить, смудровать, намудрствовать, намудрить Словарь русских синонимов … Словарь синонимов

  • Сумничать — сов. неперех. разг. Сказать или сделать что либо, стараясь показать себя умным, умнее других. Толковый словарь Ефремовой. Т. Ф. Ефремова. 2000 … Современный толковый словарь русского языка Ефремовой

  • сумничать — сумничать, сумничаю, сумничаем, сумничаешь, сумничаете, сумничает, сумничают, сумничая, сумничал, сумничала, сумничало, сумничали, сумничай, сумничайте, сумничавший, сумничавшая, сумничавшее, сумничавшие, сумничавшего, сумничавшей, сумничавшего,… … Формы слов

  • сумничать — с умничать, аю, ает … Русский орфографический словарь

  • сумничать — (I), су/мничаю, чаешь, чают … Орфографический словарь русского языка

  • сумничать — аю, аешь; св. (нсв. умничать). Неодобр. Сказать что л., стараясь показать свой ум или сделать что л. по своему, считая себя умнее других … Энциклопедический словарь

  • сумничать — аю, аешь; св. (нсв. у/мничать); неодобр. Сказать что л., стараясь показать свой ум или сделать что л. по своему, считая себя умнее других … Словарь многих выражений

  • сумничать — с/ум/н/ича/ть … Морфемно-орфографический словарь

  • УМНИЧАТЬ — УМНИЧАТЬ, умничаю, умничаешь, несовер. (к сумничать) (разг.). Говорить, стараясь выказать свой ум (ирон.). «Он умничает глупо, а дурачится умно.» Д.Давыдов. || делать по своему, мудрить, считая себя умнее других. Воли мне мало. «Ребята мои… … Толковый словарь Ушакова

Как правильно пишется слово делаешь?

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

Шаг 1. Определяем, куда падает ударение

Для начала стоит определить ударное или безударное окончание глагола. Используйте примеры ниже, чтобы точно обозначить ударный слог.

  • Ударные окончания встречаются у слов: кричи’шь, молчи’шь, шуми’шь, сопи’шь, мычи’шь, стучи’шь, гори’шь, ворчи’шь.
  • Безударные окончания у слов: ве’ртишь, сте’лешь, ви’дишь, хо’чешь, мо’жешь, сле’пишь.

Если в слове есть приставка вы-, при определении ударения ее убираем и проверяем ударный слог без нее. Например, в словах «вы’стучишь», «вы’растишь» (от слова «растить») ударение падает на приставку вы-, и это уводит нас от правильного варианта употребления слова. Следует убрать приставку и проверить ударение в слове без неё: «стучи’шь», «расти’шь». Иногда приставка вы- неотделима от слова, в этом случае окончание является безударным, например: вы’растешь (от слова «вырасти»).

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

Шаг 2. Вспоминаем спряжения глаголов

Напомним, в русском языке есть 2 спряжения глаголов: первое и второе. Если говорить обычным языком, то это правила, по которым меняются глаголы в зависимости от лиц, числа, времени и родов по аналогии с падежами у имен существительных.

Если слово относится к первому (I) спряжению, то его формы (личные окончания) будут -у, -ю, -ешь, -ет, -ем, -ете, -ут (-ют). Приведем пример:

стелИТЬ -> я стелЮ -> ты стелЕШЬ -> он стелЕТ -> мы стелЕМ -> вы стелЕТЕ -> они стелЮТ.

Если слово относится ко второму (II) спряжению, то его формы (личные окончания) будут -у (-ю), -ишь, -ит, -им, -ите, -ат (-ят). Приведем пример:

носИТЬ -> я ношУ -> ты носИШЬ -> он носИТ -> мы носИМ -> вы носИТЕ -> они носЯТ

Также есть исключения, которые спрягаются не по стандартным правилам. Такие глаголы называют разноспрягаемыми (стоит запомнить):

хотЕТЬ -> я хочУ -> ты хочЕШЬ -> он хочЕТ -> мы хотИМ -> вы хотИТЕ -> они хотЯТ

бежАТЬ -> я бегУ -> ты бежИШЬ -> он бежИТ -> мы бежИМ -> вы бежИТЕ -> они бегУТ

чтИТЬ -> я чтУ -> ты чтИШЬ -> он чтИТ -> мы чтИМ -> вы чтИТЕ -> они чтУТ

есть -> я ем -> ты ешь -> он ест -> мы едим-> вы едите -> они едят

дать -> я даю -> ты дашь-> он даст -> мы дадим -> вы дадите -> они дадут

Осталось правильно определить спряжение. Идем дальше…

Шаг 3. Определяем спряжение глагола правильно

Если на Шаге 1 у нас вышло безударное окончание, тогда определим начальную форму глагола (инфинитив). Напомним, что это будет глагол, от которого образовано данное слово, отвечающий на вопросы: «Что делать?», «Что сделать?». Пример: пишешь — писать (что делать?). Попробуйте определить инфинитив самостоятельно, а для проверки смотрите ниже пункт «Морфологический разбор».

Определяем окончание глагола в начальной форме (инфинитиве). И в зависимости от окончания относим наш пример к первому или второму склонению и используем -ешь или -ишь, соответственно.

К первому спряжению относятся все глаголы кроме глаголов второго спряжения и разноспрягаемых.

К глаголам второго спряжения относятся:

  • все, которые оканчиваются на -ить (исключения: брить, зиждиться и стелить).
  • 4 слова-исключения на -ать: слышать, дышать, держать, гнать;
  • 7 слов-исключений на —еть: смотреть, видеть, ненавидеть, обидеть, терпеть, зависеть, вертеть.

Для окончаний под ударением (кроме исключений, описанных выше) стоит использовать для проверки форму этого в 3 лице множественного числа (они что делают?).

  • Если окончание соответствует второму спряжению, а именно: -ат или -ят, глагол 2 лица в единственном числе будет заканчиваться на -ишь.

стучАТ — стучИШЬ, сопЯТ — сопИШЬ, кричАТ — кричИШЬ.

  • Если окончание соответствует первому спряжению, а именно: -ут или -ют, глагол 2 лица в единственном числе будет заканчиваться на -ешь.

разобьЮТ — разобьЁШЬ, льЮТ — льЕШЬ, жгут — жжЁШЬ.

Морфологический разбор слова делаешь

1. Часть речи — глагол
2. Морфологические признаки:
Начальная форма: делать (инфинитив);
Постоянные признаки: 1-е спряжение, переходный, несовершенный вид;
Непостоянные признаки: изъявительное наклонение, единственное число, настоящее время, 2-е лицо.
3. Синтаксическая роль: Может быть различным членом предложения, смотрите по контексту.

Примеры использования и цитаты

Что делаешь, Руслан несчастный, Один в пустынной тишине? Людмилу, свадьбы день ужасный, Всё, мнится, видел ты во сне. На брови медный шлем надвинув, Из мощных рук узду покинув, Ты шагом едешь меж полей, И медленно в…

«Руслан и Людмила» — Пушкин Александр

Разбесил начальник отделения. Когда я пришел в департамент, он подозвал меня к себе и начал мне говорить так: «Ну, скажи, пожалуйста, что ты делаешь?» — «Как что? Я ничего не делаю», — отвечал я. «Ну, размысли хорошенько! ведь…

«Что делать» — Чернышевский Николай

Ну на что это похоже, что не могу я видеть женщины равнодушно: как только подойду, так теряю и рассудок и всякое соображение, говоришь и делаешь такие вещи, что после как будто тебе все это во сне снилось. Ну с чего я так…

Как пишется: «делаешь» или «делаеш»?

Как правильно пишется слово: «делаешь» или «делаеш»?

Как правильно писать слово: «делаешь» или «делаеш»?

Хочется проиллюстрировать правило, о котором дальше пойдёт речь, примерами.

Если пишешь красиво, правильно, тогда прослывёшь человеком аккуратным и грамотным.

Рисуешь и поёшь — ты творческий и талантливый человек.

Думаешь перед тем, как сказать, чтобы не обидеть собеседника, — ты вежливый, культурный, взрослый и ответственный.

Делаешь всё вовремя — ты человек пунктуальный и воспитанный.

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

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

Слово «делаешь» — это точно глагол, ведь оно обозначает действие, отвечая на вопрос «Что делаешь?». Оно относится ко второму лицу, употреблено в единственном числе. К тому же, в конце его — шипящий.

Вывод: слово «делаешь» нужно писать с мягким знаком.

Добрый день. Давайте разберемся, нужно ли писать мягкий знак после шипящей в слове

«делаеш*». Узнать это можно при помощи правил русского языка и начать надо с определения

части речи этого слова. Для этого подбираем вопрос, на который может ответить заданное слово,

подходит: «Что делаешь?» и мы определяем часть речи глагол. По правилам РСЯ, в глаголах после

шипящей буквы пишут мягкий знак всегда. Значит, его нужно писать в данном слове.

Правильный ответ: «делаешь».

Ты все правильно делаешь для своего бизнеса и скоро у тебя все получиться.

Ты делаешь каждый день упражнения для мышц спины?

Слово «делаешь» — это глагол настоящего времени, второго лица, единственного числа. По правилам русского языка, с мягким знаком после шипящих пишутся следующие глаголы:

  • глаголы неопределенной формы;
  • глаголы изъявительного наклонения, которые имеют второе лицо, единственное число;
  • глаголы повелительного наклонения;
  • перед суффиксом -ся- или -те- в глаголах;

В данном случае подойдет второй вариант. Поэтому глагол «делаешь» изъявительного наклонения, 2 л., ед.ч., пишется с мягким знаком после шипящей.

Правильно будет: делаешь.

Пример предложения со словом «делаешь»: ты делаешь это задание абсолютно правильно.

Как пишется делаешь или делаеш, с мягким знаком или без него, нельзя определить на слух. В конце этого слова, как и в задаваемом вопросе «что делаешь?», звучит всегда твердый шипящий звук [ш].И его не сделает мягким никакой графический знак в виде мягкого знака.

И все-таки в слове «делаешь» пишется мягкий знак после шипящего, который является индикатором, определителем ФОРМЫ 2 лица единственного числа глагола настоящего или будущего времени, например:

ты (что делаешь?) пишешь, рисуешь, говоришь;

ты (что сделаешь?)скажешь, ответишь, убежишь.

Слово «делаешь» правильно пишется с мягким знаком как форма 2 лица глагола в соответствии с орфографическим правилом русского языка.

Ты делаешь эту картину из кофейных зерен?

А как ты делаешь это упражнение?

Чтобы ответить на поставленный автором вопрос, разберемся, что за слово делаеш*.

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

Итак, слово делаеш* ( что делаешь? ) — глагол, стоящий в форме изъяснительного наклонения, настоящего времени, второго лица, единственного числа.

Правило гласит, что у глаголов в форме второго лица единственного числа после шипящих согласных на конце следует ставить мягкий знак: пишешь, танцуешь, моешь, найдешь, пропустишь, зайдешь и так далее.

Делаем вывод: глагол делаешь нужно писать с мягким знаком на конце после шипящей ш.

Для начала определим часть речи слова «делаешь»- отвечает на вопрос что делаешь?, стало быть это глагол.

Делаешь — изъявительное наклонение, единственное число, настоящее время, 2-е лицо. В русском языке есть правило — в окончании глаголов 2-ого лица, единственного числа после шипящих пишется мягкий знак. Следовательно, правильно писать — делаешЬ.

Ты всегда делаешь все наоборот или просто хочешь позлить меня?

Когда делаешь с настроением, то и работа не в тягость.

Ты медленно делаешь домашнее задание, таким образом, мы опоздаем на концерт.

Слово «делаешь» является глаголом настоящего времени и чтобы выяснить, как же пишется «делаеш» или «делаешь», с мягким знаком «ь» или же без мягкого знака», необходимо вспомнить одно правило русского языка. Оно гласит, что глаголы во втором лице, заканчивающие на шипящую — пишутся с мягким знаком «ь» после шипящей.

К примеру: спишь, ешь, разговариваешь, вздыхаешь, думаешь, пишешь, бродишь, накрываешь, смотришь.

Следовательно пишем правильно — делаешЬ.

Когда делаешь домашнее задание в спокойной обстановке, то все получается без ошибок и помарок.

Слово «делаеш#» отвечает на вопрос «что делаешь?», поэтому перед нами — глагол. Написать глагол без ошибок нам поможет правило, которым определена постановка мягкого знака после шипящих.

Правило гласит, что если имеется глагол в единственном числе 2-го лица в настоящем или будущем времени, то после шипящих проставляется мягкий знак. А наш глагол соответствует всем условиям правила, поэтому будем ставить после «ш» мягкий знак: «делаешЬ».

Аналогично пишутся и такие глаголы, как смотришь, пишешь, рассматриваешь, обижаешь, ползешь, отрицаешь, созерцаешь.

Когда делаешь каждое утро зарядку, то наполняешься бодростью на весь день.

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

Слово «делаешь» является глаголом. Если слово является глаголом, то после него пишется мягкий знак.

Ниже приведу пример:

«Ты все делаешь неправильно».

Прежде всего давайте разберемся, какая переда нами часть речи.

Слово отвечает на вопрос «что делаешь ?», следовательно, перед нами глагол.

Далее посмотрим, в кокой форме он стоит:

глагол стоит в форме второго лица единственного числа.

Все глаголы, стоящие в этой форме пишутся с мягким знаком на конце.

Например:

Итак, правильно писать так: делаешь.

Примеры предложений с данным словом:

Какие красивые вещи ты делаешь своими руками!

Все, что ты делаешь, очень важно для нашего общего дела.

Ты делаешь так, как тебе велят, подумай и сделай по-своему.

«Не требуется» — это даже и не слово, а два слова. Первое — частица, а второе — глагол.

Думается, что такое предположение не было голословным, поскольку второе слово («требуется») отвечает на вопрос «что делает?» (или «что делается?», если создавать вопрос с обычной формальностью, не вдаваясь в смысл). Да и обладает другими категориальными признаками глагола.

А первое слово («не») не может быть признано приставкой, потому что если в языке есть слово «требуется», но не может быть слова «нетребуется». Это понятно из правила, обуславливающего соответствующую раздельность глаголов с «НЕ».

Итак, глагол «требоваться относится к совокупности тех, которые не сливаются с «НЕ». Писать «нетребуется» нельзя.

Ещё одно простейшее доказательство того — возможность принудительного разделения «НЕ» и глагола вставленным словом. Например: «не очень требуется», «не слишком требуется», «не каждый год требуется» и так далее.

Других доказательств не потребуется.

Предложение.

  • «А что, разве не требуется даже подтверждения своей почты?».

«Чёрно-белый» — это сложное имя прилагательное, являющееся высокочастотным представителем так называемой колоративной лексики (выражение цвета) с возможной коннотацией «блеклый во всех отношениях» и с модальностью «отрицание цвета».

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

Писать «чёрно белый» (раздельно) или «чёрнобелый» (слитно) нельзя.

чёрно-белый

Например (предложения).

  • «Любая чёрно-белая фотография носит в себе оттенки старины».
  • «В третьем зале музея стояли чёрно-белые телевизоры».
  • «Ваня Мельничаненко почему-то воспринимал мир только в чёрно-белых тонах».

1) Утверждение: Это непреступный (находящийся в рамках закона) случай простой женской хитрости.

2) Отрицание:

Случай этот — не преступный, здесь скорее нарушение общественной морали.

Случай этот отнюдь не преступный.

Планы у подростков были не преступные, а вполне безобидные.

Надо сказать, что обе формы, слитная и раздельная, используются крайне редко, в отличие от омофона «неприступный» (с большой частотностью). Особенно это касается слитного написания, когда поисковик указывает на ошибку и предлагает найти слово «неприступный».

Сочетание «со мной» (ударение на «О«, которая после «Н«) — это ни что другое, как предлог «С» с местоимением «Я«. Но мы эмпирически понимаем, что говорить «Пойдём с я» нельзя.

  • «Со» — вариант «с», иногда используемый, в частности, перед [м] плюс согласная. Например: «со многими». Это из разряда «подо», «предо», «передо», «ко», «во», «надо», «обо» и так далее.
  • «Мной» («мною») — это указанное выше «Я» в творительном падеже. «С кем? — со мной (со мною)». Личное местоимение.

Предлоги нельзя в таких случаях подсоединять к личным местоимениям. Подобные примеры: «с тобой (с тобою)», «с ней (с нею»)», «с ним».

Писать «сомной» нельзя. Нужен пробел.

Предложения:

  • «Со мной всё в полном порядке, Трофим, а с тобой ничего не случилось ли?».
  • «Будь со мной, Игнатий, не когда тебе это необходимо, а всегда».

Слово «повеселее» находится в составе систематизированного языка, в числе подобных единиц («получше», «понастойчивее», «похуже» и так далее).

Элемент «по-«, который мы при написании таких слов порой не знаем, к приставкам его отнести или к предлогам, является всё-таки приставкой.

Оттолкнёмся от имени прилагательного «весёлый» и от наречия «весело». И у первого, и у второго слов имеются формы (одинаковые!) сравнительной степени, которые образуются так:

  • «Весёлый — веселее — повеселее».
  • «Весело — веселее — повеселее».

повеселее и повеселей

К простейшей классической форме прибавляется наша приставка, преобразуя её в разговорную. Этот приём — системный. Пишется приставка слитно. Писать «по веселее» (или «по веселей») нельзя.

Занимаешься или занимаешся, как пишется правильно?

Слово «зани­ма­ешь­ся» пра­виль­но пишет­ся с мяг­ким зна­ком после шипя­ще­го как фор­ма гла­го­ла 2 лица един­ствен­но­го чис­ла насто­я­ще­го вре­ме­ни соглас­но пра­ви­лу орфографии.

Чтобы выбрать, как пра­виль­но пишет­ся «зани­ма­ешь­ся» или «зани­ма­еш­ся», с мяг­ким зна­ком или без него, опре­де­лим часть речи, грам­ма­ти­че­скую фор­му и мор­фем­ный состав слова.

Часть речи слова «занимаешься»

Чтобы понять, к какой части речи при­над­ле­жит эта лек­се­ма, вос­поль­зу­ем­ся предложением:

Чем ты занима́ешься сего­дня с утра?

Интересующее нас сло­во обо­зна­ча­ет дей­ствие и отве­ча­ет на вопрос: что дела­ешь? Выявленные грам­ма­ти­че­ские при­зна­ки помо­гут уста­но­вить, что это сло­во явля­ет­ся фор­мой 2 лица един­ствен­но­го чис­ла воз­врат­но­го гла­го­ла «зани­мать­ся», на что ука­зы­ва­ет пост­фикс -ся:

заним а ешь ся — корень/суффикс/окончание/постфикс

"Одеваешься" как пишется

Правописание слова «занимаешься»

Орфографическая про­бле­ма состо­ит в напи­са­нии лич­но­го окон­ча­ния гла­го­ла, в кото­ром соглас­ный [ш], как и в задан­ном вопро­се (что дела­ешь?), зву­чит все­гда твер­до. В напи­са­нии рас­смат­ри­ва­е­мой воз­врат­ной фор­мы гла­го­ла вос­поль­зу­ем­ся пра­ви­лом орфо­гра­фии:

Мягкий знак слу­жит не для смяг­че­ния соглас­ных, а для обо­зна­че­ния грам­ма­ти­че­ских форм гла­го­лов, например,

  • увлечь,
  • сте­речь,
  • отсечь

2. форм 2 лица един­ствен­но­го чис­ла насто­я­ще­го и буду­ще­го времени

  • ты пишешь,
  • ты рису­ешь,
  • ты сбе­ре­жешь,
  • ты отпра­вишь

3. форм 2 лица един­ствен­но­го и мно­же­ствен­но­го чис­ла пове­ли­тель­но­го наклонения

  • уни­что­жить — уни­чтожь, уничтожьте;
  • обес­пе­чить — обес­печь, обеспечьте;
  • поды­то­жить — поды­тожь, подытожьте.

Наличие пост­фик­са -ся (-сь) в воз­врат­ных фор­мах гла­го­лов не вли­я­ет на напи­са­ние мор­фо­ло­ги­че­ско­го мяг­ко­го зна­ка. Понаблюдаем за напи­са­ние форм 2 лица един­ствен­но­го чис­ла насто­я­ще­го и буду­ще­го вре­ме­ни воз­врат­ных глаголов:

  • учить­ся — ты учишь­ся;
  • осме­леть — ты осмелишься;
  • улы­бать­ся — ты улыбаешься;
  • вол­но­вать­ся — ты волнуешься.

В этой сту­дии ты занима́ешься игрой на гитаре?

Целый день занима́ешься реше­ни­ем это­го вопро­са, а все никак не получается.

Примеры предложений из художественной литературы

Так ты зани­ма­ешь­ся все тем же? (Лев Толстой. Анна Каренина).

Раз уж зани­ма­ешь­ся рабо­тор­гов­лей на афри­кан­ском побе­ре­жье, не рас­счи­ты­вай на мир­ную и без­бо­лез­нен­ную кон­чи­ну (Жюль Верн. Пятнадцатилетний капитан).

Твой отец гово­рит, что ты всю жизнь зани­ма­ешь­ся нищен­ством (Марк Твен. Принц и нищий).

Значение слова «делать»

android bar znachenije

ДЕ́ЛАТЬ, —аю, —аешь; прич. страд. прош. де́ланный, —лан, -а, -о; несов., перех. (сов. сделать).

1. Создавать обычно с помощью инструментов, специальных приспособлений, машин и т. п. различного рода предметы, вещи, изделия; изготовлять, производить. Делать станки. Делать мебель. Делать ткани. Делать фарфор.Я гайки делаю, а ты для гайки делаешь винты. Маяковский, Кем быть? Люди спешно принялись строить сушила, рыть огромные колодцы, делать для них крыши. В. Попов, Сталь и шлак. Мы срываем большие листья лопуха и делаем из них себе зеленые колпаки. Кузьмин, Круг царя Соломона. || Создавать (произведения литературы, живописи, музыки и т. п. Я начал делать эскизы итальянской фантазии на народные темы. Чайковский, Письмо Н. Ф. Мекк, 16-17 янв. 1880. Я сижу теперь дома, обложившись грудами фотографий, блокнотов и путевых карт, — делаю эту книжку. Песков, Путешествие с молодым месяцем.

2. Заниматься чем-л., работать, проявлять какую-л. деятельность. Я хотел приняться за работу — не мог; хотел ничего не делать и не думатьи это не удалось. Тургенев, Ася. [Треплев:] Мама, перемени мне повязку. Ты это хорошо делаешь. Чехов, Чайка. Нине всё приходилось делать самой: ходить на базар, готовить обед, стирать белье. Б. Емельянов, Мечта. || В сочетании с некоторыми категориями существительных употребляется в значении: совершать, выполнять, производить: а) С существительными, обозначающими какой-л. процесс, вид занятия, работы. Делать уроки. Делать опыты.[Саня] побежал в ванну, выскочил в одних трусиках и стал делать зарядку. Каверин, Два капитана. б) С отглагольными существительными со значением действия. Делать выбор. Делать выговор. Делать попытку.В ее комнате делали обыск. Чехов, Переполох. Доктор непроизвольно вздрагивал, хотя он делал усилия, чтобы сдержаться. Первенцев, Огненная земля. Танк заметался, делая крутые развороты. Бубеннов, Белая береза. в) С существительными, обозначающими отдельные движения (живого существа, предмета). Машина делает крутой поворот.Он нерешительно делает два шага вперед. Чехов, Серьезный шаг. Флаг-капитан делает ладонью жест сверху вниз. Лавренев, Стратегическая ошибка. || В сочетании со словами, указывающими меру, количество, обозначает: совершать работу, движение в этом объеме, в этом количестве. Делать сто оборотов в минуту.Загремели имена водителей Кондрина, Гонтарева, делавших регулярно четыре рейса в смену. Фадеев, Ленинград в дни блокады. Если делать в сутки десять-двенадцать километров, он дойдет до своих за три, самое большее за четыре дня. Б. Полевой, Повесть о настоящем человеке.

3. также без доп. Поступать, действовать каким-л. образом. Делать все по-своему.Увидев Павлика целым и невредимым, тетя бросилась к нему, не зная, что делать — плакать или смеяться. Катаев, Белеет парус одинокий. — Я скажу тебе, что делать, отец. Домой иди! Горбатов, Непокоренные. || Оказывать, причинять. Делать добро. Делать одолжение. □ — Только о том и думает [Карл Иваныч] всю жизнь, — прошептал я, — как бы мне делать неприятности. Л. Толстой, Детство. Чем больше делаешь для человека, тем сильнее любишь. Крымов, Инженер.

4. из кого-чего. Обращать, превращать в кого-, что-л. Делать из кого-л. посмешище.Брат был идолом всего нашего семейства, а из меня делал, что хотел. Пушкин, Рославлев. || кем-чем. Приводить в какое-л. состояние или положение. Делать кого-л. своим помощником. Делать кого-л. несчастным. Делать искусство достоянием масс.Останавливаясь лишь ночью во время сильных дождей, делавших проселочные дороги непроезжими, шли машины с командным составом завода. В. Попов, Сталь и шлак. || Придавать какой-л. вид. Очки делали его [Саши] детское лицо смешным. М. Горький, В людях. Кораблев явился — в свободной вышитой белой рубашке, которая очень шла к нему и делала похожим на какого-то великого русского художника. Каверин, Два капитана.

Русский [ править ]

Наречие, определительное, сравнения и уподобления; неизменяемое.

Приставка: по-; корень: -друж-; суффиксы: -еск-и [Тихонов, 1996] .

Произношение [ править ]

    : [ pɐ‿ˈdruʐɨskʲɪ ]

Семантические свойства [ править ]

Значение [ править ]

  1. так, как характерно для друзей ◆  Отсутствует пример употребления (см. рекомендации ).

Синонимы [ править ]

Антонимы [ править ]

Гиперонимы [ править ]

Гипонимы [ править ]

Родственные слова [ править ]

  • существительные: друг
  • прилагательные: дружеский
  • наречия: дружески
  • предлоги: по
  • имена собственные: Дружок
  • фамилии: Дружинин, Дружников
  • пр.  существительные: друг, друган, друганок, друганочек, другиня, дружба, дружбан, дружбанок, дружбаночек, дружелюбие, дружелюбье, дружественность, дружина, дружинник, дружинница, дружишко, дружище, дружка, дружок, дружочек, недруг, недружелюбие, недружелюбье, недружественность, подруга, подружаечка, подружайка, подружаня, подруженька, подружка, содружество
  • прилагательные: дружелюбный, дружеский, дружественный, дружинный, дружный, малодружественный, недружелюбный, недружеский, недружественный, недружный
  • глаголы: додружить, додружиться, дружить, дружиться, задружить, задружиться, надружить, надружиться, подружить, подружиться, продружить, сдружить, сдружиться, удружить
  • наречия: друг в друга, друг друга, друг другу, друг за другом, друг к другу, друг на друга, друг от друга, друг под друга, друг против друга, друг с другом, друг у друга, дружелюбно, дружески, дружественно, дружно, недружелюбно, недружески, недружественно, недружно, по дружбе, по-дружески, сам-друг

Этимология [ править ]

Из по- + дружески (от дружеский), далее от друг, далее от праслав.  *drugъ , от кот. в числе прочего произошли: ст.-слав.  дрѹгъ (греч. φίλος), русск., белор., укр. друг , болг.  друг , сербохорв. дру̑г, словенск.  drȗg , чешск., словацк. druh , др.-польск. drug; восходит к праиндоевр. *dhreugh- «охранять». Родственно лит. draũgas «спутник, товарищ», латышск. draugs «друг», др.-прусск. draugiwaldūnen (вин. п.) «сонаследник», др.-исл. draugr (поэт.) «муж», готск. (driugan) «воевать» (греч. στρατεύειν), англос. dréogan «совершать, добиваться», готск. (gadraúhts) (στρατιώτης), др.-в.-нем. trucht «отряд воинов, свита», др.-в.-нем. truhtîn «военачальник, князь», лит. sudrugti «присоединиться» Использованы данные словаря М. Фасмера. См. Список литературы.

Фразеологизмы и устойчивые сочетания [ править ]

Перевод [ править ]

Это незаконченная статья. Вы можете помочь проекту, исправив и дополнив её .
В частности, следует уточнить сведения о:

«По-дружески» или «по дружески»?

«По-дружески» или «по дружески» − многие не задумываются над написанием слова, считая выбор между раздельным и дефисным написанием непринципиальным. Между тем, правильное написание слова определяет часть речи и характеризует уровень грамотности человека.

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

Как правильно пишется

Сочетание предлога «по» и прилагательного «дружеский» образует наречие, которое отвечает на вопросы

  • «как?»;
  • «каким образом?».

Согласно существующему правилу, образованные таким способом наречия приобретают приставку: «по», которая пишется со словом не слитно, а через дефис: «по-дружески».

Подтверждает дефисное написание наречия и другое правило: через дефис пишутся наречия с суффиксами:

  • «-(с)ки»;
  • «-ьи».

Примеры предложений

Рассмотрим несколько примеров:

  1. Несмотря на вчерашнюю ссору, он выглядел спокойным и разговаривал по-дружески. То ли он на самом деле так добр, то ли задумал что-то.
  2. Учитель предложил поговорить по душам, по-дружески. Но обратной реакции не было. Илья не хотел разговаривать совсем.
  3. Алексей по-дружески предложил свою помощь. И тем самым очень нас выручил.
  4. Новый сотрудник представился и по-дружески протянул руку Ивану Захаровичу. Тот вяло пожал ему руку.
  5. Ее взгляд больше не был колючим, наоборот, приветливым и по-дружески теплым.

Морфемный разбор слова «по-дружески»

Наречие имеет следующий состав:

  • приставка − «по-»;
  • корень − «-друж-»;
  • суффикс − «-еск-»;
  • суффикс − «-и».

Неизменяемое наречие имеет два суффикса. У слова нет окончаний. Основа слова: «по-дружески».

Приставка «по-» пишется не слитно, а через дефис.

Неправильное написание слова «по-дружески»

Некорректно писать слово слитно − «подружески» и раздельно, без дефиса − «по дружески». Кроме того, следует исключить возможные орфографические ошибки:

  • «падружески»;
  • «па дружески»;
  • «по дружиски»;
  • «подружиски».

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

Заключение

Для того чтобы всегда писать правильно, необходимо запомнить: все наречия (вопросы: «как?», «каким образом?»), образованные от прилагательных с помощью приставки «по-», всегда пишутся через дефис.

Как пишется слово «по-дружески» и почему?

В правописании этого слова будем придерживаться таким правилом: наречие с приставкой -по- и оканчивающееся на -ски- пишется через дефис (примеры: по-людски, по-человечески и тд).

Таким образом, правильно это слово пишется так: по — дружески.

автор вопроса выбрал этот ответ лучшим

Искат­ель прикл­ючени­й [100K]

Добрый день, чтобы понять, какой вариант правильный:

Нужно обратится к правилам русского языка, в которых можно найти следующие:

Следовательно есть только один правильный ответ: по-дружески.

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

Он по-дружески похлопал меня по плечу.

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

по-приятельски, по-человечески, по-братски, по-матерински.

З В Ё Н К А [702K]

Наречие «по-дружески» пишется только через дефис. К этому нас склоняют причины, очень подробно описанные здесь.

Мы имеем дело с наречием, которое образовано при помощи так называемого префиксально-суффиксального способа словообразования. Для русских наречий он достаточно характерен, и схема «перед прилагательным ставим префикс по-, а в конце прилагательного добавляем вместо окончания -и» очень известна. Именно она применена и сейчас — «дружеский — по-дружески». Отсоединив «-ий», к основе с разных сторон добавляется «по-» и «-и».

Получившиеся таким способом наречия пишутся с дефисом после «по».

«По-дружески» является наречием, которое образовалось от прилагательного «дружеский» с помощью приставки «по» и суффикса «ск».

Согласно правилу такие наречия пишутся через дефис.

Пример предложения со словом «по-дружески»:

Мы разошлись по-дружески.

По-дружески так не поступают.

Еще наречия, образованные аналогичным способом:

по-человечески, по-людски, по-отечески.

В вопросе абсолютно верно приведено написание слова «по-дружески», то есть с использованием дефиса.

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

как в нашем примере, а также

В данных словах обязательно должны присутствовать

Тори Торич­ка [57.4K]

Наречие «по-дружески» следует писать через дефис.

Руководствуемся следующим правилом: если наречие начинается с ПО и имеет суффиксы -И, -ОМУ, -ЕМУ, то следует писать через дефис.

Пример употребления: поступить по-дружески.

По-дружески — это наречие, которое пишется через дефис. Существует правило написания таких наречий, если у них имеется приставка по, то она пишется через дефис, например, по-нашему, по-русски, по-немецки и т.д.

Наречие по-дружески обязательно надо писать с дефисом. Вообще все наречия, начинающиеся с «по» и оканчивающиеся на «ски» пишутся через дефис.

Подробнее читаем здесь.

А вот наречия, которые пишутся слитно, к примеру.

Данное слово нужно писать с использованием дефиса вот так: по-дружески. Дело в том, что слово «по-дружески» представляет собой , а как известно из правил русского языка, наречия с такой приставкой как «по» нужно писать через дефис.

Как пишется слово «по-дружески» и почему?

В правописании этого слова будем придерживаться таким правилом: наречие с приставкой -по- и оканчивающееся на -ски- пишется через дефис (примеры: по-людски, по-человечески и тд).

Таким образом, правильно это слово пишется так: по — дружески.

автор вопроса выбрал этот ответ лучшим

Искат­ель прикл­ючени­й [100K]

Добрый день, чтобы понять, какой вариант правильный:

Нужно обратится к правилам русского языка, в которых можно найти следующие:

Следовательно есть только один правильный ответ: по-дружески.

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

Он по-дружески похлопал меня по плечу.

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

по-приятельски, по-человечески, по-братски, по-матерински.

З В Ё Н К А [702K]

Наречие «по-дружески» пишется только через дефис. К этому нас склоняют причины, очень подробно описанные здесь.

Мы имеем дело с наречием, которое образовано при помощи так называемого префиксально-суффиксального способа словообразования. Для русских наречий он достаточно характерен, и схема «перед прилагательным ставим префикс по-, а в конце прилагательного добавляем вместо окончания -и» очень известна. Именно она применена и сейчас — «дружеский — по-дружески». Отсоединив «-ий», к основе с разных сторон добавляется «по-» и «-и».

Получившиеся таким способом наречия пишутся с дефисом после «по».

«По-дружески» является наречием, которое образовалось от прилагательного «дружеский» с помощью приставки «по» и суффикса «ск».

Согласно правилу такие наречия пишутся через дефис.

Пример предложения со словом «по-дружески»:

Мы разошлись по-дружески.

По-дружески так не поступают.

Еще наречия, образованные аналогичным способом:

по-человечески, по-людски, по-отечески.

В вопросе абсолютно верно приведено написание слова «по-дружески», то есть с использованием дефиса.

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

как в нашем примере, а также

В данных словах обязательно должны присутствовать

Тори Торич­ка [57.4K]

Наречие «по-дружески» следует писать через дефис.

Руководствуемся следующим правилом: если наречие начинается с ПО и имеет суффиксы -И, -ОМУ, -ЕМУ, то следует писать через дефис.

Пример употребления: поступить по-дружески.

По-дружески — это наречие, которое пишется через дефис. Существует правило написания таких наречий, если у них имеется приставка по, то она пишется через дефис, например, по-нашему, по-русски, по-немецки и т.д.

Наречие по-дружески обязательно надо писать с дефисом. Вообще все наречия, начинающиеся с «по» и оканчивающиеся на «ски» пишутся через дефис.

Подробнее читаем здесь.

А вот наречия, которые пишутся слитно, к примеру.

Данное слово нужно писать с использованием дефиса вот так: по-дружески. Дело в том, что слово «по-дружески» представляет собой , а как известно из правил русского языка, наречия с такой приставкой как «по» нужно писать через дефис.

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

Теперь я постараюсь показать, как этот пакет можно использовать на примере простейшего бэкенда для апплета “Труд всем”. Немного поясню идею этого апплета. Допустим у нас есть любой сайт — от хомяка до новостной ленты, а в любом свободном углу при обновлении страницы показана случайная вакансия. Код апплета будет отправлять запрос на сервер и получать в качестве результата HTML код (уже готовый рендер) для вставки на страницу сайта.

Правда интригующе? Где же получать информацию о вакансиях? Где хранить эту информацию? Какие критерии отбора вакансий использовать? Для того, чтобы узнать ответы на эти вопросы, прошу заглянуть под кат!

Определимся с требованиями к нашему бэкэнду:

  1. Выборка осуществляется из 25 последних опубликованных за прошедшую неделю вакансий по ключевому слову «программист».
  2. По запросу выдавать случайную вакансию из этой выборки.
  3. Рассчитываем на RPS близкий к 100к.
  4. Среднее время ответа 60 мс.

Из этих требований становится ясно, что нам нужен кеш. Мы не можем выполнять поиск вакансии “налету”, поскольку не уверены, что получим ответ за такое короткое время. Сам я, когда пробовал выполнять запросы к публичному АПИ поиска вакансий, получал среднее время ответа около 200 мс, что не соответствует нашим требованиям. Кроме того, сервис поиска вакансий не дает возможности рандомизировать ответ. Ну и последний довод, самый весомый: всегда кешируйте ответ сторонних сервисов, потому что это экономит ресурсы вашего сервера, сервера стороннего АПИ и драгоценное время пользователя, который ждет ваш ответ, а в случае когда сервис платный, вы сэкономите деньги.

Из кеширующих инструментов я не могу вам сегодня предложить такие замечательные вещи, как супербыстрые key-value базы данных наподобие Redis или memcached, просто потому, что это уведет нас от темы. И прошу меня простить за реализацию кеша в ОЗУ нашего приложения, все-таки у нас не такие сложные требования для того, чтобы собрать какую-то приличную систему, которая бы имела базу данных, очереди сообщений, деплоилась в кубернетес и автоматически масштабировалась. Однако, не огорчайтесь, те, кто подпишется на мой канал, наверняка дождутся и таких статей.

А сейчас GO пилить то, что есть…

Труд всем

Для начала разберемся, откуда будем получать информацию о вакансиях. Конечно, мы не будем генерировать ее рандомайзерами, это будут реальные данные с официального бесплатного портала. Работа России — федеральная государственная информационная система, проект Федеральной службы по труду и занятости. Этот портал помогает гражданам найти работу, а работодателям — сотрудников. Все услуги портала предоставляются бесплатно, и поэтому мы просто берем их апи и прикручиваем к своему бэкэнду в качестве ресурса.

Как сервис

Как вы помните из предыдущей статьи, ресурсы нашего бэкэнда мы держим в виде интерфейсов Service, т.е. структур, которые имплементируют следующие методы:

    Service interface {
        Init(ctx context.Context) error
        Ping(ctx context.Context) error
        Close() error
    }

Определим какие поля требуются нашей структуре: мьютекс, потому у нас будет конкурентный доступ к списку вакансий, сам список вакансий, как слайс, может быть, http.Client, если мы хотим обернуть все тестами. Ну и давайте еще сохранять последнее время обновления списка вакансий, чтобы как-то выполнять инвалидацию кеша.

type trudVsem struct {
    mux         sync.RWMutex
    requestTime time.Time
    client      *http.Client
    vacancies   []Vacancy
}

Обратите внимание на мьютекс, который я использую. Учитывая то, что кол-во запросов на чтение случайной вакансии будет во много раз превышать кол-во операций обновления этих вакансий, я сделал выбор в сторону RWMutex, который позволяет брать блокировку на чтение отдельно от блокировок на запись, предоставляя возможность одновременного чтения данных из разных горутин. Будем использовать метод RLock для блокировок при чтении, это позволит нескольким горутинам выполнять чтение одновременно, пока нет никаких операций записи. А простой Lock для блокировок при записи, позволит блокировать небезопасный конкурентный доступ к защищаемой памяти, как в обычном мьютексе (под капотом там именно обычный мьютекс).

Итак. Проштудировав документацию открытого АПИ сервиса “Работа России” я собрал структуры, необходимые для маппинга Json данных. Возможно будет немного избыточно, и некоторые поля мы вообще не задействуем в дальнейшем, но я подумал, что лучше будет удалить потом, чем постоянно сверяться с документацией и добавлять какие-то поля в процессе разработки. Кроме того, как ни крути, количество данных в сети мы изменить не сможем — чужой АПИ нам все равно будет присылать все что знает независимо от того, есть ли у нас под эти данные соответствующие поля в наших структурах или нет.

Раскройте, чтобы увидеть код этих структур

const (
    serviceURL    = "https://opendata.trudvsem.ru/api/v1/vacancies"
    prefetchCount = 25
)

type (
    Meta struct {
        Total int `json:"total"`
        Limit int `json:"limit"`
    }
    Region struct {
        RegionCode string `json:"region_code"`
        Name       string `json:"name"`
    }
    Company struct {
        Name string `json:"name"`
    }
    Vacancy struct {
        ID           string  `json:"id"`
        Source       string  `json:"source"`
        Region       Region  `json:"region"`
        Company      Company `json:"company"`
        CreationDate string  `json:"creation-date"`
        SalaryMin    float64 `json:"salary_min"`
        SalaryMax    float64 `json:"salary_max"`
        JobName      string  `json:"job-name"`
        Employment   string  `json:"employment"`
        Schedule     string  `json:"schedule"`
        URL          string  `json:"vac_url"`
    }
    VacancyRec struct {
        Vacancy Vacancy `json:"vacancy"`
    }
    Results struct {
        Vacancies []VacancyRec `json:"vacancies"`
    }
    Response struct {
        Status  string `json:"status"`
        Meta    Meta
        Results Results `json:"results"`
    }
)

Для тех, кто знает как работает маршалинг структур в GO, тут нет ничего нового, для остальных немного пояснения: теги (текст в одинарных кавычках), прилагаемые к полям структур, не влияют непосредственно на маппинг данных или сериализацию, тут опять нет никакой магии. Теги используются для получении информации о полях структур в рантайме. Для получения доступа к ним используется пакет reflect, который еще называют рефлексией в go. Я об этом говорю, потому что если вы хотите строить легковесные и производительные алгоритмы, то не стоит использовать рефлексию и ничего, что прямо или косвенно ее использует, вместо этого есть кодогенераторы, которые, пользуясь рефлексией, создают в своем рантайме уже готовый код на языке go, который содержит все необходимые для маршалинга/демаршалинга функции, которые, в свою очередь, уже в нашем рантайме не используют рефлексию. К таким относятся, например пакет easyjson. Подход с кодогенерацией не на много сложнее, чем подход с использованием json.Decoder, но мы его использовать пока не будем в силу того, что это опять мимо темы.

Давайте же займемся реализацией интерфейса. Первым у нас будет метод Init, который должен подготовить все ресурсы сервиса к работе. Сделаем следующее: проинициализируем рандомизатор, выберем http-клиент по умолчанию и сразу выделим необходимое место в памяти под список вакансий.

func (t *trudVsem) Init(context.Context) error {
    rand.Seed(time.Now().UnixNano())
    t.client = http.DefaultClient
    t.vacancies = make([]Vacancy, 0, prefetchCount)
    return nil
}

Для новичков сделаю еще одну ремарку: использование пакета rand может быть обосновано только в алгоритмах, которые не чувствительны к качеству генератора случайных чисел, в других случаях (например, в криптографических алгоритмах), требуется использование генератора из пакета crypto/rand.

Следующим в очереди будет метод Ping, как мы помним, он должен использоваться для самодиагностики и возвращать error, если произошла критическая ошибка, которая не позволяет работать дальше. Такой случай для этого сервиса я себе представить пока не могу, поэтому будем возвращать всегда nil. Функция Ping будет вызываться контроллером рантайма, а если быть точнее, ServiceKeeper должен циклично пинговать наши сервисы с определенными промежутками времени до тех пор, пока не получит команду на завершение работы. Давайте глянем, как ее реализовать.

func (t *trudVsem) Ping(context.Context) error {
    if time.Since(t.requestTime).Minutes() > 1 {
        t.requestTime = time.Now()
        go t.refresh()
    }
    return nil
}

Проверяем время обновления кеша, если прошло больше минуты, инициируем обновление кеша в фоновой горутине, чтобы не ломать процесс анализа работоспособности сервиса. Возвращаем nil. И вот вам вопрос на засыпку: какая ошибка в имплементации этой функции? Подсказка следующая: синтаксических ошибок нет, все прекрасно компилируется и замечательно работает. Давайте подискутируем об этом в комментариях?

Последняя функция имплементирующая интерфейс Service, необходимая для того, чтобы нашу структуру можно было считать сервисом и передать в ServiceKeeper под контроль. И в ней нечего обсуждать — она идеальна.

func (t *trudVsem) Close() error {
    return nil
}

Обновление вакансий

Поскольку функция рефреша списка вакансий должна обновлять данные списка вакансий, то именно в ней мы должны использовать блокировку записи (чтения/записи). После выполнения t.mux.Lock() чтение списка вакансий будет недоступно, пока не выполнится t.mux.Unlock(), но мы должны будем гарантировать это выполнением t.mux.RLock() в читающей функции. Прошу обратить внимание на следующую деталь: с самого начала функции никакие блокировки не ставятся, а выполняется функция вызывающая загрузку обновленных вакансий loadLastVacancies само выполнение этой функции не приводит к обновлению списка, поэтому блокировок тут не нужно. Это кажется очевидным, но тем не менее, ситуация, когда разработчик оборачивает в Lock/Unlock лишние операции, довольно распространена. Всегда ограничивайте блокировкой наименьший участок кода, все, что можно выполнить без блокировок, нужно выполнять без блокировок. Блокировки — это всегда вынужденное зло.

func (t *trudVsem) refresh() {
    vacancies, err := t.loadLastVacancies(context.Background(), "программист", 0, prefetchCount)
    if err != nil {
        log.Println(err)
        return
    }
    t.mux.Lock()
    t.vacancies = t.vacancies[:0]
    for _, v := range vacancies {
        t.vacancies = append(t.vacancies, v.Vacancy)
    }
    t.mux.Unlock()
}

Вероятно, все знают, почему я использовал t.vacancies = t.vacancies[:0] а не make([]Vacancy, 0, prefetchCount). Это сделано для уменьшения кол-ва аллокаций памяти. На самом деле этот участок кода не критичный и выделение памяти в этом месте не приведет к деградации даже при самых высоких нагрузках, но я позволил себе сумничать. Выражение slice[:0] просто установит длину слайса (len) в 0, оставив его вместительности (cap) прежнее значение, а это значит, что выполнение append будет наполнять слайс заново, просто перетирая старые значения новыми.

Теперь посмотрим, как выполняется загрузка вакансий, комментарии по коду четко отделяют три этапа этой функции: создание запроса, передача его по сети, парсинг ответа. Если какой-то из этапов вернет ошибку, мы просто пробросим ее в качестве ответа для вызывающей функции.

func (t *trudVsem) loadLastVacancies(ctx context.Context, text string, offset, limit int) ([]VacancyRec, error) {
    // создадим HTTP-запрос для получения данных
    req, err := newVacanciesRequest(ctx, text, offset, limit)
    if err != nil {
        return nil, err
    }
    // выполняется запрос данных
    resp, err := t.client.Do(req)
    if err != nil {
        return nil, err
    }
    // выполняем парсинг данных
    parsed, err := parseResponseData(resp)
    // не забываем закрывать Body
    resp.Body.Close()
    return parsed.Results.Vacancies, err
}

Давайте посмотрим, как выполняется создание HTTP-запроса:

func newVacanciesRequest(ctx context.Context, text string, offset, limit int) (*http.Request, error) {
    URL, err := url.ParseRequestURI(serviceURL)
    if err != nil {
        return nil, err
    }
    query := url.Values{
        "text":         []string{text},
        "offset":       []string{strconv.Itoa(offset)},
        "limit":        []string{strconv.Itoa(limit)},
        "modifiedFrom": []string{time.Now().Add(-time.Hour * 168).UTC().Format(time.RFC3339)},
    }
    URL.RawQuery = query.Encode()
    return http.NewRequestWithContext(ctx, http.MethodGet, URL.String(), http.NoBody)
}

И тут есть о чем подискутировать, ведь в самой первой строке я выполняю парсинг константы! Каждый раз вызов этой функции будет выполнять парсинг константы serviceURL и даже возвращать ошибку, если она возникнет. Как-то не очень умно, да? Можно же было использовать литерал url.URL структуры. А еще весьма прилично и даже наглядно будет смотреться вызов функции форматирования fmt.Sprintf, как в примере под спойлером.

Пример

    weekAgo := url.QueryEscape(time.Now().Add(-time.Hour * 168).UTC()
    newURL := serviceURL + fmt.Sprintf(
        "?text=%s&offset=%d&limit=%d&modifiedFrom=%s",
        url.QueryEscape(text),
        offset,
        limit,
        weekAgo.Format(time.RFC3339)),
    )

Но я сейчас сам отвечу на свой же вопрос. Я хочу быть уверенным в том, что сделал минимум ошибок: в нелепом тексте ?text=%s&offset=%d&limit=%d&modifiedFrom=%s так легко допустить синтаксическую ошибку и так трудно ее искать потом. Кроме того, ошибку можно допустить, устанавливая значение константы serviceURL. Я не говорю уже о том, что не все параметры в примере с fmt.Sprintf были обернуты в url.QueryEscape и я не знаю, кто будет решать, какой параметр нужно оборачивать, а какой нет. И самое главное, я не могу дать гарантии, что доработка такого кода не сломает логики; кто-то может сломать форматирующую строку или добавить параметр, который не будет проходить через QueryEscape, и у нас выйдет какой-нибудь нелепый query injection.

Преимущества моего варианта в том, что ParseRequestURI может обнаружить некоторые синтаксические ошибки в константе serviceURL, далее литерал url.Values и вызов его метода Encode позволит правильно вписать пары ключ/значение в наш HTTP-запрос, он экранирует все недопустимые символы по всем канонам URL, и никаких ошибок тут не будет. Ну и как итог URL.String() вернет нам какую-то гарантию корректного URL. Единственное, что можно было бы сделать — вынести парсинг константы отдельно, но этот участок кода настолько нетребователен к производительности, что разделять эту логику и выносить куда-то ее часть, я считаю нецелесообразным.

Немного по поводу длинного и неприятного выражения time.Now().Add(-time.Hour * 168).UTC().Format(time.RFC3339). Его логика, думаю, всем понятна — мы получаем текущее время, уменьшаем его на 168 часов (это ровно неделя), переводим в UTC и форматируем по стандарту RFC3339 — это то, чего от нас ждет сервис вакансий. Но я хотел сказать другое. Когда мы видим в коде среди простых выражений сложное, у нас должно возникать желание упростить код. Давайте же спрячем это страшное выражение внутрь функции и вместо него будем вызывать функцию:

const hoursInWeek = 168

func modifiedFrom() string {
    return time.Now().Add(-time.Hour * hoursInWeek).UTC().Format(time.RFC3339)
}

Парсинг ответа от сервиса вакансий будет состоять из демаршалинга тела http.Response в нашу структуру с помощью базового json.Decoder и минимальной проверки на валидность этих данных. Ничего такого о чем бы я хотел сказать отдельно.

func parseResponseData(resp *http.Response) (result Response, err error) {
    decoder := json.NewDecoder(resp.Body)
    if err = decoder.Decode(&result); err != nil {
        return
    }
    // мы ожидаем статус 200
    if result.Status != "200" {
        err = errors.New("wrong response status")
        return
    }
    // если вакансий в слайсе нет, тут явно что-то не то
    if len(result.Results.Vacancies) == 0 {
        err = io.EOF
    }
    return
}

Итак, вот наш первый checkpoint — готов единственный ресурс нашего бэкэнда. Готов сервис бесплатных вакансий. На уровне кода он плоский с единственной внешней связью в виде вызова стороннего АПИ через HTTP и никаких кешей и прочих премудростей. На верхнем уровне мы считаем, что у него все-таки есть кеш, потому что для выполнения своих функций, ему не требуется выполнять вызов стороннего апи.

Пишем бэкэнд с использованием полученного опыта

В предыдущей статье я описал код runtime-контроллера, а в главе выше описал код сервиса вакансий, который будет являться единственным ресурсом нашего бэкэнда. Теперь пора аккумулировать полученный опыт и построить работающее приложение. Вероятно, из предыдущей статьи не становится понятным, как использовать runtime-контроллер так, чтобы изнутри кода с бизнес-логикой были доступны ресурсы приложения. Но давайте сейчас посмотрим на код main:

type server struct {
    // все ресурсы нашего бэкэнда перечислены здесь
    trudVsem trudVsem
}

func main() {
    var srv server
    var svc = appctl.ServiceKeeper{
        Services: []appctl.Service{
            &srv.trudVsem, // регистрируем ссылку на ресурс trudVsem
        },
        PingPeriod:      time.Millisecond * 500, // периодичность вызова Ping
    }
    var app = appctl.Application{
        MainFunc:           srv.appStart, // эта функция будет запущена с Run
        Resources:          &svc, // регистрируем ServiceKeeper
        TerminationTimeout: time.Second * 10, // для порядка
    }
    // стартуем
    if err := app.Run(); err != nil {
        logError(err)
        os.Exit(1)
    }
}

Немного освежим память. Структура server понятна — она состоит всего из одного поля — структуры сервиса вакансий trudVsem. Это не интерфейс и оно не скрывает реализации, кроме того, я поместил весь код этого приложения внутри одного пакета, а это значит, что никакого сокрытия реализации тут не будет и из кода с бизнес логикой мне будут доступны даже приватные методы и поля структуры trudVsem. Не делайте так, когда пишете реальное приложение. Я же себе это позволил, потому что это снова вне темы. Просто хочу сказать, что тот сервис, который я передал ServiceKeeper на контроль, как ресурс приложения, мы должны передать в качестве сервиса в функцию выполняющуюся, как основной поток. MainFunc будет запущена строго после того, как все ресурсы будут проинициализированы и у нас есть гарантия, что trudVsem.Init к тому времени уже будет успешно выполнено.

Структура Application получает указатель на ServiceKeeper и знает только о том, что нужно запустить Init, выполнить в фоновой горутине Watch и следить за сообщениями от ОС. Вся эта логика будет запущена в то время, когда мы вызовем Run и любое из следующих трех событий позволит потоку выполнения пойти дальше:

  1. Возврат из функции MainFunc.
  2. Возврат из функции ServiceKeeper.Watch.
  3. Сигнал от операционной системы о завершении работы.

А что там еще за logError? Это я обернул логирование ошибки в вызов функции, чтобы не сильно нагружать по коду вызовами fmt.Errorf.

func logError(err error) {
    if err = fmt.Errorf("%w", err); err != nil {
        println(err)
    }
}

Запуск HTTP сервера и контроль его Graceful Shutdown

Функция appStart будет запускать HTTP сервер из стандартного пакета net/http. Я в очередной раз прошу прощения за magic в коде. Все таймауты и номера портов должны быть спрятаны под конфигами. Конфигурацию можно передать, как переменные окружения операционной системы, опции командной строки или в конфигурационном файле. В идеале следует предусмотреть все три способа передачи конфигурации, возможно, мы с вами займемся этим в будущих статьях, но сейчас для демонстрации результатов мы это опустим.

В стандартной реализации HTTP сервера уже предусмотрен процесс мягкого завершения работы (Graceful Shutdown) с помощью метода Shutdown. В документации сказано, что вызов этого метода приведет к отключению HTTP сервера без прерывания выполнения текущих запросов. Сначала будет выключен листенер, чтобы предотвратить поступление из сети новых запросов, затем будут разорваны все спящие соединения (в состоянии IDLE), а затем будет выполнено ожидание завершения обработки активных запросов и выход.

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

Код функции appStart(context.Context, <-chan struct{}) error

func (s *server) appStart(ctx context.Context, halt <-chan struct{}) error {
    var httpServer = http.Server{
        Addr:              ":8900",
        Handler:           s,
        ReadTimeout:       time.Millisecond * 250,
        ReadHeaderTimeout: time.Millisecond * 200,
        WriteTimeout:      time.Second * 30,
        IdleTimeout:       time.Minute * 30,
        BaseContext: func(_ net.Listener) context.Context {
            return ctx
        },
    }
    var errShutdown = make(chan error, 1)
    go func() {
        defer close(errShutdown)
        select {
        case <-halt:
        case <-ctx.Done():
        }
        if err := httpServer.Shutdown(ctx); err != nil {
            errShutdown <- err
        }
    }()
    if err := httpServer.ListenAndServe(); err != http.ErrServerClosed {
        return err
    }
    err, ok := <-errShutdown
    if ok {
        return err
    }
    return nil
}

В качестве Handler для HTTP сервера мы передаем сам указатель на структуру server, для того, чтобы наша структура могла стать обработчиком HTTP запросов, мы должны имплементировать метод ServeHTTP. В качестве Addr нужно передавать адрес локального порта, на котором будем ожидать запросы. В моем случае это будет порт 8900 на всех доступных сетевых интерфейсах. И в качестве BaseContext я порекомендовал HTTP серверу отдавать контекст с которым была запущена функция appStart. Может быть, это решение не очень аккуратное, потому что хоть на данном этапе у нас и есть гарантии, что контекст не будет отменен внезапно и незапланированно, но никаких гарантий в том, что это не станет происходить в дальнейшем, когда мы или кто-то другой станет развивать пакет с runtime-контроллером. Я бы рекомендовал для обработки HTTP запросов использовать всегда чистый контекст — в нем корректная информация о статусе и мы получаем cancel только когда клиент сам закрывает соединение.

Обратите внимание на запуск горутины в середине функции. Это вынужденная необходимость, поскольку вызов ListenAndServe блокирует дальнейшее выполнение. Мы должны предусмотреть варианты вызова Shutdown в случае получения сигнала через канал halt — этот канал, напомню, закрывается, когда операционная система передала сигнал о завершении работы. Дополнительно к этому случаю я добавил еще один случай, когда мы выполняем Shutdown — при “протухании” основного контекста case <-ctx.Done(), таким образом в коде появляется select с двумя кейсами.

В нижней части функции я читаю из канала, который использую для передачи информации об ошибке мягкого завершения работы. Выполнение функции может не дойти до этого участка кода, если ListenAndServe вернет какую-то ошибку, которая немедленно будет отдана в качестве результата для вызывающей функции. Однако, если ошибки не произошло, мы можем проверить корректно ли завершилась операция Shutdown, для этого в канал errShutdown передается ошибка, если таковая возникла. Обратите внимание на то, что я сделал этот канал буферизованным, это значит, что процесс записи в него не остановит выполнение горутины, в противном случае мы рискуем “замерзнуть” в месте выполнения errShutdown <- err и получить “утечку горутины”.

    err, ok := <-errShutdown
    if ok {
        return err
    }
    return nil

Напомню, что чтение из канала возвращает кортеж, вторым значением которого будет булево значение, показывающее успешность чтения данных. Простыми словами, если ok != true, значит в канале нет данных и он уже закрыт.

Мне нравится эта функция тем, что она оставляет некоторый простор для рефакторинга. Мы можем попробовать вынести из нее литерал http.Server и запуск горутины, контролирующей сигнал halt. Можем завернуть все magic numbers в конфигурацию и как-нибудь иначе выполнить синхронизацию ошибки вызова Shutdown. Но я пока просто оставлю это здесь.

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

Обработка HTTP-запроса

Давайте начнем реализацию бизнес-логики. При получении запроса мы должны обратиться к сервису trudVsem и попросить у него случайную вакансию. Если данных нет, вернем HTTP 204 No Content, если что-то есть, выполним рендер вакансии в HTML и вернем его в качестве ответа.

Раскройте, чтобы увидеть код ServeHTTP и renderVacancy

func (s *server) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
    vacancy, ok := s.trudVsem.GetRandomVacancy()
    if !ok {
        w.WriteHeader(http.StatusNoContent)
        return
    }
    data, err := renderVacancy(vacancy)
    if err != nil {
        logError(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    w.Header().Add("Content-Type", "text/html;charset=utf-8")
    w.WriteHeader(http.StatusOK)
    if _, err = w.Write(data); err != nil {
        logError(err)
    }
}

func renderVacancy(vacancy Vacancy) ([]byte, error) {
    var w = bytes.NewBuffer(nil)
    if _, err := w.WriteString(fmt.Sprintf("<h3>%s (%s)</h3>", vacancy.JobName, vacancy.Region.Name)); err != nil {
        return nil, err
    }
    if _, err := w.WriteString(fmt.Sprintf("<p class='description'>Компания: %s ищет сотрудника на должность '%s'.</p>", vacancy.Company.Name, vacancy.JobName)); err != nil {
        return nil, err
    }
    if _, err := w.WriteString(fmt.Sprintf("<p class='condition'>Условия: %s, %s.</p>", vacancy.Employment, vacancy.Schedule)); err != nil {
        return nil, err
    }
    if vacancy.SalaryMin != vacancy.SalaryMax && vacancy.SalaryMax != 0 && vacancy.SalaryMin != 0 {
        if _, err := w.WriteString(fmt.Sprintf("<p class='salary'>зарплата от %0.2f до %0.2f руб.</p>", vacancy.SalaryMin, vacancy.SalaryMax)); err != nil {
            return nil, err
        }
    } else if vacancy.SalaryMax > 0 {
        if _, err := w.WriteString(fmt.Sprintf("<p class='salary'>зарплата %0.2f руб.</p>", vacancy.SalaryMax)); err != nil {
            return nil, err
        }
    } else if vacancy.SalaryMin > 0 {
        if _, err := w.WriteString(fmt.Sprintf("<p class='salary'>зарплата %0.2f руб.</p>", vacancy.SalaryMin)); err != nil {
            return nil, err
        }
    }
    if _, err := w.WriteString(fmt.Sprintf("<a href='%s'>ознакомиться</a>", vacancy.URL)); err != nil {
        return nil, err
    }
    return w.Bytes(), nil
}

Принимая во внимание то, что выглядит это не очень, я сделаю небольшое отступление от основной темы. У меня вышла довольно сложная для восприятия функция renderVacancy к тому же, скорее-всего, она будет в большей степени влиять на производительность. А еще код бизнес-логики вшит прямо в обработчик запросов, хотя, нам для примера сойдет. В конце-концов, чтобы немного упростить код, мы могли бы сделать следующее:

  1. Добавить поле template *template.Template в нашу структуру server.
  2. Заполнить ее при запуске программы srv.template = template.Must(template.New("render").Parse(htmlTemplate))

И изменить нашу логику примерно вот так:

Раскройте, чтобы поглядеть, что вышло

const htmlTemplate = `<h3>{{ .JobName }} ({{ .Region.Name }})</h3>
<p class='description'>Компания: {{ .Company.Name }} ищет сотрудника на должность '{{ .JobName }}'.</p>
<p class='condition'>Условия: {{ .Employment }}{{ if .Schedule }}, {{ .Schedule }}{{ end }}.</p>
{{ if or (.SalaryMin) (.SalaryMax) }}
<p class='salary'>
{{ if and (ne .SalaryMax .SalaryMin) (gt .SalaryMax 0.0) (gt .SalaryMin 0.0) }}зарплата от {{ .SalaryMin }} до {{ .SalaryMax }} руб.{{ else }}
{{ if .SalaryMin }}зарплата {{ .SalaryMin }} руб.{{ else }}{{ if .SalaryMax }}зарплата {{ .SalaryMax }} руб.{{ end }}{{ end }}
{{ end }}
</p>
{{ end }}
<a href='{{ .URL }}'>ознакомиться</a>`

type server struct {
    template *template.Template
    trudVsem trudVsem
}

func renderVacancy(vacancy Vacancy, tpl *template.Template) ([]byte, error) {
    var w = bytes.NewBuffer(nil)
    if err := tpl.Execute(w, vacancy); err != nil {
        return nil, err
    }
    return w.Bytes(), nil
}

func (s *server) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
    .   .   .
    data, err := renderVacancy(vacancy, s.template)
    .   .   .

Да, выглядит получше, но такой рендер снизит скорость обработки запроса в два раза. Правда-правда, я проверял. Это вполне рабочий вариант, но без шаблона с одними только w.WriteString(fmt.Sprintf на моем компьютере я получаю около 220к RPS, а с шаблонизатором чуть больше 100к (нагрузочное тестирование будет дальше).

Что можно сделать еще? Чтобы увеличить производительность, мы можем попробовать заранее создавать буфер данных []byte и заполнять его с помощью append, а не w.WriteString(fmt.Sprintf. По понятным причинам такой подход еще сильнее усложнит код, а толку от него будет не очень много — мы можем снизить количество аллокаций и может даже избавимся от нескольких случаев “убегания в кучу”, но не от всех. Еще мы можем сразу писать в http.ResponseWriter, вроде как хорошая идея, но тоже не даст прироста производительности. А если сделать наоборот — чтобы упростить код, мы можем разбить рендер на отдельные блоки: блок рендера заголовка, описания компании, условий труда и отдельно блок с заработной платой. Вот и отлично, пока оставим в голове этот план и вернемся к нашим баранам.

Функция выборки случайной вакансии, которую мы использовали, но еще не описали, содержит обещанный RLock в качестве блокировки при чтении. Это позволяет множеству горутин выполнять этот код не мешая друг другу. Единственное, что заблокирует вход сюда, это Lock при обновлении списка вакансий. Выбор случайной вакансии нам поможет сделать rand.Intn(len(t.vacancies)).

func (t *trudVsem) GetRandomVacancy() (vacancy Vacancy, ok bool) {
    t.mux.RLock()
    defer t.mux.RUnlock()
    if ok = len(t.vacancies) > 0; !ok {
        return
    }
    vacancy = t.vacancies[rand.Intn(len(t.vacancies))]
    return
}

Нагрузочное тестирование

Итак, кажется, что все готово. Пора проверить на работоспособность. Я запускаю приложение и перехожу по адресу http://localhost:8900/test. Выглядит так, как будто сработало: я вижу вакансию на должность программиста; обновляю страницу и вижу следующую вакансию. Но давайте проверим, производительность нашего приложения. Все-таки в требованиях указано 100 RPS.

Нагрузочное тестирование удобно проводить с помощью утилиты wrk. Wrk легко устанавливается и позволяет настраивать параметры нагрузочного тестирования. Для своих тестов я решил использовать 15 активных коннектов и 10 рабочих потоков. Для этого запускаю утилиту с ключами -t 10 -c 15. Первый тест сразу обрадовал, я получил результат ~224k RPS. И среднее время ответа в 68 микросекунд, а максимальное в 20 миллисекунд, что явно удовлетворяет нашим требованиям. Вот детализированные результаты тестирования:

devalio@devastator:~$ wrk -t 10 -c 15 http://localhost:8900/test
Running 10s test @ http://localhost:8900/test
  10 threads and 15 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    68.69us  223.22us  20.48ms   97.01%
    Req/Sec    22.52k   842.70    25.47k    76.31%
  2260841 requests in 10.10s, 1.51GB read
Requests/sec: 223848.81
Transfer/sec:    153.06MB

Ну что ж, отлично! Двойной резерв по производительности, это то, что нам нужно. А среднее время ответа даже меньше миллисекунды. Но нужно ли на этом останавливаться? Кажется, я там пропустил одну функцию, реализация которой была не самым удачным примером кода. Да, были какие то мысли, как можно ее переделать, но они строились на предположениях, а не на фактах. Что-то придется с этим делать.

Рефакторинг

Ну вот, я сам себя раздразнил и поскольку уже обещал показать, как я рассуждал в процессе, придется быть честным и приступить к рефакторингу функции renderVacancy. Недолго думая, я посмотрел, какой процент времени выполнение запроса проводит в этой функции. Для этого добавил в секцию импорта _ "net/http/pprof", а в самое начало функции main вот такой вызов HTTP-листенера:

    go http.ListenAndServe(":9900", nil)

Пакет net/http/pprof при подключении сам настраивает роутинг по умолчанию, поэтому сразу имею доступ ко всем полезным функциям профилировщика. Когда мы снимаем профили, обязательно нужно, чтобы приложение было нагружено, иначе мы будем видеть метрики “холостого хода”, поэтому я запускаю нагрузочный тест, увеличив время его выполнения до 20 секунд, и одновременно с этим запускаю команду на снятие профиля:

devalio@devastator:~$ go tool pprof http://localhost:9900/debug/pprof/profile?seconds=15
Fetching profile over HTTP from http://localhost:9900/debug/pprof/profile?seconds=15
Saved profile in /home/devalio/pprof/pprof.___go_build_github_com_iv_menshenin_appctl_example.samples.cpu.001.pb.gz
File: ___go_build_github_com_iv_menshenin_appctl_example
Type: cpu
Time: Nov 16, 2021 at 6:24am (MSK)
Duration: 15s, Total samples = 26.97s (179.80%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) 

Для тех, кто не пользуется go tool pprof или не знает об этом инструменте, очень рекомендую изменить свое мнение и нагуглить хорошие инструкции по работе с этим профилировщиком. А пока покажу немногое из того, что он умеет.

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

На этом графике я выделил блок, который меня интересует. Обратите внимание на то, что сама обработка запроса (суммарно за все время профилирования) заняла около 5 секунд процессорного времени, а из них чуть больше 3х секунд ушло на renderVacancy.

Следующая команда, которая мне нравится — list, позволяет показать исходный код и расставить ключевые метрики прямо напротив строки кода к которым они относятся. В качестве аргумента эта команда может принимать шаблон поиска по коду, я ввожу list ServeHTTP, чтобы найти функцию обработки HTTP-запроса. Поглядите на картинку ниже, слева от исходного кода мы увидим два столбца. Первый столбец показывает какое кол-во процессорного времени взяла на себя функция, исходный код которой мы видим на экране, без учета времени вложенных в нее функций. Второй — суммарное процессорное время с учетом вложенных вызовов. А вот и наш data, err := renderVacancy(vacancy) — обратите внимание на то, что в левом столбце пусто, а в правом 3 секунды, это значит, что время, потраченное на выполнение этой части кода, относится не к функции ServeHTTP, а ко вложенному в нее вызову renderVacancy.

cwlvv

Т.е. львиная доля Latency, который мы видим в нагрузочных тестах, уходит на рендер вакансии в HTML формат, что дает мне повод заняться оптимизацией в этом месте. И, конечно же, я пошел самым простым путем: решил выполнить рендер прямо в сервисе, который хранит вакансии, ведь данные вакансий не изменяются, а поэтому нам не нужно выполнять рендер “налету”.

Я добавил поле render []byte к структуре Vacancy, вынес рендер вакансии в сервис вакансий и разбил его на кусочки. Затем разбил страшную процедуру рендера на небольшие куски. Итоги рендера я сохраню в поле render и, когда мне потребуется получить вакансию, я буду сразу лить ее в HTTP-респонз вот так:

func (s *server) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
    vacancy, ok := s.trudVsem.GetRandomVacancy()
    if !ok {
        w.WriteHeader(http.StatusNoContent)
        return
    }
    w.Header().Add("Content-Type", "text/html;charset=utf-8")
    w.WriteHeader(http.StatusOK)
    // сразу вливаем готовый HTML в ResponseWriter
    if err := vacancy.RenderTo(w); err != nil {
        logError(err)
    }
}

  .   .   .

// а эта функция находится в пакете с сервисом “Труд всем”
func (v Vacancy) RenderTo(w io.Writer) error {
    _, err := w.Write(v.render)
    return err
}

Раскройте, чтобы увидеть функции рендера

func (v Vacancy) renderBytes() ([]byte, error) {
    var w = bytes.NewBufferString("")
    if err := v.renderHead(w); err != nil {
        return nil, err
    }
    if err := v.renderDesc(w); err != nil {
        return nil, err
    }
    if err := v.renderConditions(w); err != nil {
        return nil, err
    }
    if err := v.renderSalary(w); err != nil {
        return nil, err
    }
    if err := v.renderFooter(w); err != nil {
        return nil, err
    }
    return w.Bytes(), nil
}

func (v Vacancy) renderHead(w io.StringWriter) error {
    _, err := w.WriteString(fmt.Sprintf("<h3>%s (%s)</h3>", v.JobName, v.Region.Name))
    return err
}

func (v Vacancy) renderDesc(w io.StringWriter) error {
    _, err := w.WriteString(fmt.Sprintf("<p class='description'>Компания: %s ищет сотрудника на должность '%s'.</p>", v.Company.Name, v.JobName))
    return err
}

func (v Vacancy) renderConditions(w io.StringWriter) error {
    _, err := w.WriteString(fmt.Sprintf("<p class='condition'>Условия: %s, %s.</p>", v.Employment, v.Schedule))
    return err
}

func (v Vacancy) renderSalary(w io.StringWriter) error {
    if v.SalaryMin != v.SalaryMax && v.SalaryMax != 0 && v.SalaryMin != 0 {
        if _, err := w.WriteString(fmt.Sprintf("<p class='salary'>зарплата от %0.2f до %0.2f руб.</p>", v.SalaryMin, v.SalaryMax)); err != nil {
            return err
        }
    } else if v.SalaryMax > 0 {
        if _, err := w.WriteString(fmt.Sprintf("<p class='salary'>зарплата %0.2f руб.</p>", v.SalaryMax)); err != nil {
            return err
        }
    } else if v.SalaryMin > 0 {
        if _, err := w.WriteString(fmt.Sprintf("<p class='salary'>зарплата %0.2f руб.</p>", v.SalaryMin)); err != nil {
            return err
        }
    }
    return nil
}

func (v Vacancy) renderFooter(w io.StringWriter) error {
    _, err := w.WriteString(fmt.Sprintf("<a href='%s'>ознакомиться</a>", v.URL))
    return err
}

Еще немного поразмыслив, я понял, что теперь мне незачем хранить слайс элементов в структуре Vacancy, теперь достаточно будет хранить массив готовых HTML, и сделал еще кое-какие изменения:

type (
    VacancyRender []byte
    trudVsem struct {
        mux         sync.RWMutex
        requestTime time.Time
        client      *http.Client
        vacancies   []VacancyRender // вот тут
    }
)

// и тут
func (r VacancyRender) RenderTo(w io.Writer) error {
    _, err := w.Write(r)
    return err
}

// и вот этот кусочек вот в этой функции
func (t *trudVsem) refresh() {
    .    .    .
        var  newVacancy = v.Vacancy
        if rendered, err := newVacancy.renderBytes(); err == nil {
            t.vacancies = append(t.vacancies, rendered)
        }
    .    .    .

Провел нагрузочное тестирование еще раз и получил прирост производительности аж на 30%. Теперь вижу следующие результаты:

  10 threads and 15 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    64.87us  251.00us   9.09ms   97.39%
    Req/Sec    29.48k     2.09k   34.76k    64.72%
  2959186 requests in 10.10s, 1.97GB read
Requests/sec: 292990.05
Transfer/sec:    199.60MB

Хорошо. Я остался доволен производительностью этого приложения. Немного беспокоит то, что рендер теперь выполняет сервис trudVsem, а я хотел от него только получение/хранение пакета вакансий. Но с этим могу смириться, поскольку свою цель считаю достигнутой: я показал, как можно использовать пакет, который мы написали в предыдущей статье.

Заключение

Начиная первую из двух статей “Пишем сервис на GO”, я задался целью: показать, как можно построить простое но полноценное веб-приложение на GO. Какие нюансы нужно учитывать при разработке веб-сервиса и на что нужно обращать внимание — это все я постарался разобрать. Да, согласен, в своей статье я описал весьма простое приложение, которое можно было уместить в 100-200 строчек кода, но мне кажется, что даже в решении такой мелкой задачи мне удалось найти и обратить внимание на определенные нюансы разработки веб-сервисов на GO.

Конечно, писать плоский и неструктурный код не задумываясь, что у приложения есть жизненный цикл — это довольно легко. И на самом деле даже может что-то получиться, но чем больше разрастается функциями наше приложение, тем сложнее нам понять, где та самая ниточка, дернув за которую мы заставим наше приложение правильно закрыться. Может для кого-то эта проблема выглядит, как из пальца высосаная, ведь есть же теперь всякие кубернетесы — все, что упало поднимут заново, ну а если наше приложение перед завершением наплодит 500-ых респонзов, то на балансировщике можно выкрутиться ретраями. Но я встречал в своей практике веб-приложение, которое закладывалось, как приложение могущее в перспективе HighLoad, но фактически не поддерживающее не только Graceful Shutdown, но и банальных ContextTimeout. Поэтому постарался вложить между строк правильное понимание структуры go-приложений — такие штучки, как каналы для синхронизации при конкурентности или мьютексы, которые могут блокировать запись и при этом разрешать одновременное чтение. А в некоторых местах попробовал спорить сам с собой, чтобы показать, что в разработке не все так однозначно — где-то нам нужен быстрый код, а где-то мы можем написать код не очень производительный, но зато более стабильный. Все-таки разработка — это процесс непрерывного появления на свет какой-то истины в форме имплементации поставленной задачи на определенном языке программирования, а что помогает рождать истину? Конечно дискуссии и споры.

Чего мы достигли в процессе разработки кода, описанного в этих статьях:

  1. Мы написали инфраструктурный код — структуры Application и ServiceKeeper, которые помогают нам контролировать системы жизнеобеспечения нашего сервиса.
  2. Разработали сервис поиска случайной вакансии с использованием вызова API на удаленном сервере.
  3. Провели нагрузочное тестирование и ознакомились с одним из способов профилирования и оптимизации нашего кода.

Что ж, я считаю достижение этих целей достойной наградой за потраченное время. Если кому-то статья показалась полезной, можете поставить лайк. Если есть такие, для кого эта статья разрушила последний барьер на пути к большому плаванию под эгидой “пишу микросервисы на golang с нуля”, то я более чем доволен. Прошу ознакомиться с полным кодом на моем github.

Ну а я уже полон новых идей и хочу незамедлительно приступить к следующей статье.

wncqlp9abeml4npwzsybuvhzcta

  • Сумма сошла как пишется
  • Сулейман стальский рассказ о себе краткое содержание
  • Сумашествие или сумасшествие как пишется
  • Сузим круг как пишется
  • Суд осириса древнеегипетский рисунок на папирусе рассказ история 5 класс