ΠΡΠ΅ΠΌ ΠΏΡΠΈΠ²Π΅Ρ. ΠΠ΅Π»ΠΈΠΌΡΡ ΠΏΠ΅ΡΠ΅Π²ΠΎΠ΄ΠΎΠΌ Π·Π°ΠΊΠ»ΡΡΠΈΡΠ΅Π»ΡΠ½ΠΎΠΉ ΡΠ°ΡΡΠΈ ΡΡΠ°ΡΡΠΈ, ΠΏΠΎΠ΄Π³ΠΎΡΠΎΠ²Π»Π΅Π½Π½ΠΎΠΉ ΡΠΏΠ΅ΡΠΈΠ°Π»ΡΠ½ΠΎ Π΄Π»Ρ ΡΡΡΠ΄Π΅Π½ΡΠΎΠ² ΠΊΡΡΡΠ°
Apache Beam ΠΈ DataFlow Π΄Π»Ρ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠΎΠ² ΡΠ΅Π°Π»ΡΠ½ΠΎΠ³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Google Cloud
ΠΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΠ΅: ΠΠ»Ρ Π·Π°ΠΏΡΡΠΊΠ° ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ° ΠΈ ΠΏΡΠ±Π»ΠΈΠΊΠ°ΡΠΈΠΈ Π΄Π°Π½Π½ΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ Π»ΠΎΠ³Π° Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π» Google Cloud Shell, ΠΏΠΎΡΠΊΠΎΠ»ΡΠΊΡ Ρ ΠΌΠ΅Π½Ρ Π²ΠΎΠ·Π½ΠΈΠΊΠ»ΠΈ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ Ρ Π·Π°ΠΏΡΡΠΊΠΎΠΌ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ° Π½Π° Python 3. Google Cloud Shell ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ Python 2, ΠΊΠΎΡΠΎΡΡΠΉ Π»ΡΡΡΠ΅ ΡΠΎΠ³Π»Π°ΡΡΠ΅ΡΡΡ Ρ Apache Beam.
Π§ΡΠΎΠ±Ρ Π·Π°ΠΏΡΡΡΠΈΡΡ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ, Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠΊΠΎΠΏΠ°ΡΡΡΡ Π² Π½Π°ΡΡΡΠΎΠΉΠΊΠ°Ρ
. Π’Π΅ΠΌ ΠΈΠ· Π²Π°Ρ, ΠΊΡΠΎ ΡΠ°Π½ΡΡΠ΅ Π½Π΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π»ΡΡ GCP, Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΡ ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ 6 ΡΠ°Π³ΠΎΠ², ΠΏΡΠΈΠ²Π΅Π΄Π΅Π½Π½ΡΡ
Π½Π° ΡΡΠΎΠΉ
ΠΠΎΡΠ»Π΅ ΡΡΠΎΠ³ΠΎ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ Π±ΡΠ΄Π΅Ρ Π·Π°Π³ΡΡΠ·ΠΈΡΡ Π½Π°ΡΠΈ ΡΠΊΡΠΈΠΏΡΡ Π² ΠΎΠ±Π»Π°ΡΠ½ΠΎΠ΅ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ Google ΠΈ ΡΠΊΠΎΠΏΠΈΡΠΎΠ²Π°ΡΡ ΠΈΡ
Π² Π½Π°ΡΡ Google Cloud Shel. ΠΠ°Π³ΡΡΠ·ΠΊΠ° Π² ΠΎΠ±Π»Π°ΡΠ½ΠΎΠ΅ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ Π΄ΠΎΡΡΠ°ΡΠΎΡΠ½ΠΎ ΡΡΠΈΠ²ΠΈΠ°Π»ΡΠ½Π° (ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΉΡΠΈ
Π ΠΈΡΡΠ½ΠΎΠΊ 2
ΠΠΎΠΌΠ°Π½Π΄Ρ, ΠΊΠΎΡΠΎΡΡΠ΅ Π½Π°ΠΌ Π½ΡΠΆΠ½Ρ Π΄Π»Ρ ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ°ΠΉΠ»ΠΎΠ² ΠΈ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊ, ΠΏΠ΅ΡΠ΅ΡΠΈΡΠ»Π΅Π½Ρ Π½ΠΈΠΆΠ΅.
# Copy file from cloud storage
gsutil cp gs://<YOUR-BUCKET>/ * .
sudo pip install apache-beam[gcp] oauth2client==3.0.0
sudo pip install -U pip
sudo pip install Faker==1.0.2
# Environment variables
BUCKET=<YOUR-BUCKET>
PROJECT=<YOUR-PROJECT>
Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π½Π°ΡΠ΅ΠΉ Π±Π°Π·Ρ Π΄Π°Π½Π½ΡΡ ΠΈ ΡΠ°Π±Π»ΠΈΡΡ
ΠΠΎΡΠ»Π΅ ΡΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ ΠΌΡ Π²ΡΠΏΠΎΠ»Π½ΠΈΠ»ΠΈ Π²ΡΠ΅ ΡΠ°Π³ΠΈ, ΡΠ²ΡΠ·Π°Π½Π½ΡΠ΅ Ρ Π½Π°ΡΡΡΠΎΠΉΠΊΠΎΠΉ, ΡΠ»Π΅Π΄ΡΡΡΠ΅Π΅, ΡΡΠΎ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ, ΡΡΠΎ ΡΠΎΠ·Π΄Π°ΡΡ Π½Π°Π±ΠΎΡ Π΄Π°Π½Π½ΡΡ
ΠΈ ΡΠ°Π±Π»ΠΈΡΡ Π² BigQuery. ΠΡΡΡ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΠΏΠΎΡΠΎΠ±ΠΎΠ² ΡΠ΄Π΅Π»Π°ΡΡ ΡΡΠΎ, Π½ΠΎ ΡΠ°ΠΌΡΠΉ ΠΏΡΠΎΡΡΠΎΠΉ β ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΊΠΎΠ½ΡΠΎΠ»Ρ Google Cloud, ΡΠ½Π°ΡΠ°Π»Π° ΡΠΎΠ·Π΄Π°Π² Π½Π°Π±ΠΎΡ Π΄Π°Π½Π½ΡΡ
. ΠΡ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΡ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ, ΡΠΊΠ°Π·Π°Π½Π½ΡΠ΅ ΠΏΠΎ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΉ
Π ΠΈΡΡΠ½ΠΎΠΊ 3. Π‘Ρ
Π΅ΠΌΠ° ΡΠ°Π±Π»ΠΈΡΡ
ΠΡΠ±Π»ΠΈΠΊΠ°ΡΠΈΡ Π΄Π°Π½Π½ΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ Π»ΠΎΠ³Π°
Pub/Sub ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΊΡΠΈΡΠΈΡΠ΅ΡΠΊΠΈ Π²Π°ΠΆΠ½ΡΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠΌ Π½Π°ΡΠ΅Π³ΠΎ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ°, ΠΏΠΎΡΠΊΠΎΠ»ΡΠΊΡ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΠΌ Π½Π΅Π·Π°Π²ΠΈΡΠΈΠΌΡΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡΠΌ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΎΠ²Π°ΡΡ Π΄ΡΡΠ³ Ρ Π΄ΡΡΠ³ΠΎΠΌ. Π ΡΠ°ΡΡΠ½ΠΎΡΡΠΈ, ΠΎΠ½ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΊΠ°ΠΊ ΠΏΠΎΡΡΠ΅Π΄Π½ΠΈΠΊ, ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡΡΠΈΠΉ Π½Π°ΠΌ ΠΎΡΠΏΡΠ°Π²Π»ΡΡΡ ΠΈ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΌΠ΅ΠΆΠ΄Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡΠΌΠΈ. ΠΠ΅ΡΠ²ΠΎΠ΅, ΡΡΠΎ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ, ΡΡΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ΅ΠΌΡ (topic). ΠΠΎΡΡΠ°ΡΠΎΡΠ½ΠΎ ΠΏΡΠΎΡΡΠΎ ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ Π² Pub/Sub Π² ΠΊΠΎΠ½ΡΠΎΠ»ΠΈ ΠΈ Π½Π°ΠΆΠ°ΡΡ CREATE TOPIC.
ΠΡΠΈΠ²Π΅Π΄Π΅Π½Π½ΡΠΉ Π½ΠΈΠΆΠ΅ ΠΊΠΎΠ΄ Π²ΡΠ·ΡΠ²Π°Π΅Ρ Π½Π°Ρ ΡΠΊΡΠΈΠΏΡ Π΄Π»Ρ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΠΈ Π΄Π°Π½Π½ΡΡ
Π»ΠΎΠ³Π°, ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΡ
Π²ΡΡΠ΅, Π° Π·Π°ΡΠ΅ΠΌ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ°Π΅ΡΡΡ ΠΈ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ ΠΆΡΡΠ½Π°Π»Ρ Π² Pub/Sub. ΠΠ΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½ΠΎΠ΅, ΡΡΠΎ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ, β ΡΡΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ PublisherClient, ΡΠΊΠ°Π·Π°ΡΡ ΠΏΡΡΡ ΠΊ ΡΠ΅ΠΌΠ΅ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΠΌΠ΅ΡΠΎΠ΄Π° topic_path
ΠΈ Π²ΡΠ·Π²Π°ΡΡ ΡΡΠ½ΠΊΡΠΈΡ publish
Ρ topic_path
ΠΈ Π΄Π°Π½Π½ΡΠΌΠΈ. ΠΠ±ΡΠ°ΡΠΈΡΠ΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, ΡΡΠΎ ΠΌΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠ΅ΠΌ generate_log_line
ΠΈΠ· Π½Π°ΡΠ΅Π³ΠΎ ΡΠΊΡΠΈΠΏΡΠ° stream_logs
, ΠΏΠΎΡΡΠΎΠΌΡ ΡΠ±Π΅Π΄ΠΈΡΠ΅ΡΡ, ΡΡΠΎ ΡΡΠΈ ΡΠ°ΠΉΠ»Ρ Π½Π°Ρ
ΠΎΠ΄ΡΡΡΡ Π² ΠΎΠ΄Π½ΠΎΠΉ ΠΏΠ°ΠΏΠΊΠ΅, ΠΈΠ½Π°ΡΠ΅ Π²Ρ ΠΏΠΎΠ»ΡΡΠΈΡΠ΅ ΠΎΡΠΈΠ±ΠΊΡ ΠΈΠΌΠΏΠΎΡΡΠ°. ΠΠ°ΡΠ΅ΠΌ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π·Π°ΠΏΡΡΡΠΈΡΡ ΡΡΠΎ ΡΠ΅ΡΠ΅Π· Π½Π°ΡΡ google-ΠΊΠΎΠ½ΡΠΎΠ»Ρ, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ:
python publish.py
from stream_logs import generate_log_line
import logging
from google.cloud import pubsub_v1
import random
import time
PROJECT_ID="user-logs-237110"
TOPIC = "userlogs"
publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path(PROJECT_ID, TOPIC)
def publish(publisher, topic, message):
data = message.encode('utf-8')
return publisher.publish(topic_path, data = data)
def callback(message_future):
# When timeout is unspecified, the exception method waits indefinitely.
if message_future.exception(timeout=30):
print('Publishing message on {} threw an Exception {}.'.format(
topic_name, message_future.exception()))
else:
print(message_future.result())
if __name__ == '__main__':
while True:
line = generate_log_line()
print(line)
message_future = publish(publisher, topic_path, line)
message_future.add_done_callback(callback)
sleep_time = random.choice(range(1, 3, 1))
time.sleep(sleep_time)
ΠΠ°ΠΊ ΡΠΎΠ»ΡΠΊΠΎ ΡΠ°ΠΉΠ» Π·Π°ΠΏΡΡΡΠΈΡΡΡ, ΠΌΡ ΡΠΌΠΎΠΆΠ΅ΠΌ Π½Π°Π±Π»ΡΠ΄Π°ΡΡ Π²ΡΠ²ΠΎΠ΄ Π΄Π°Π½Π½ΡΡ Π»ΠΎΠ³Π° Π½Π° ΠΊΠΎΠ½ΡΠΎΠ»Ρ, ΠΊΠ°ΠΊ ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΎ Π½Π° ΡΠΈΡΡΠ½ΠΊΠ΅ Π½ΠΈΠΆΠ΅. ΠΡΠΎΡ ΡΠΊΡΠΈΠΏΡ Π±ΡΠ΄Π΅Ρ ΡΠ°Π±ΠΎΡΠ°ΡΡ Π΄ΠΎ ΡΠ΅Ρ ΠΏΠΎΡ, ΠΏΠΎΠΊΠ° ΠΌΡ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ CTRL+C, ΡΡΠΎΠ±Ρ Π·Π°Π²Π΅ΡΡΠΈΡΡ Π΅Π³ΠΎ.
Π ΠΈΡΡΠ½ΠΎΠΊ 4. ΠΡΠ²ΠΎΠ΄ publish_logs.py
ΠΠ°ΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΊΠΎΠ΄Π° Π½Π°ΡΠ΅Π³ΠΎ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ°
Π’Π΅ΠΏΠ΅ΡΡ, ΠΊΠΎΠ³Π΄Π° ΠΌΡ Π²ΡΠ΅ ΠΏΠΎΠ΄Π³ΠΎΡΠΎΠ²ΠΈΠ»ΠΈ, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΈΡΡΡΠΏΠΈΡΡ ΠΊ ΡΠ°ΠΌΠΎΠΉ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΠΎΠΉ ΡΠ°ΡΡΠΈ β Π½Π°ΠΏΠΈΡΠ°Π½ΠΈΡ ΠΊΠΎΠ΄Π° Π½Π°ΡΠ΅Π³ΠΎ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ°, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ Beam ΠΈ Python. Π§ΡΠΎΠ±Ρ ΡΠΎΠ·Π΄Π°ΡΡ Beam-ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ, Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ° (p). ΠΠΎΡΠ»Π΅ ΡΠΎΠ³ΠΎ ΠΊΠ°ΠΊ ΠΌΡ ΡΠΎΠ·Π΄Π°Π»ΠΈ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ°, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΡΡ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΡΠ½ΠΊΡΠΈΠΉ ΠΎΠ΄Π½Ρ Π·Π° Π΄ΡΡΠ³ΠΎΠΉ, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡ pipe (|)
. Π ΠΎΠ±ΡΠ΅ΠΌ, ΡΠ°Π±ΠΎΡΠΈΠΉ ΠΏΡΠΎΡΠ΅ΡΡ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΠΊΠ°ΠΊ Π½Π° ΡΠΈΡΡΠ½ΠΊΠ΅ Π½ΠΈΠΆΠ΅.
[Final Output PCollection] = ([Initial Input PCollection] | [First Transform]
| [Second Transform]
| [Third Transform])
Π Π½Π°ΡΠ΅ΠΌ ΠΊΠΎΠ΄Π΅ ΠΌΡ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ Π΄Π²Π΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ. Π€ΡΠ½ΠΊΡΠΈΡ regex_clean
, ΠΊΠΎΡΠΎΡΠ°Ρ ΡΠΊΠ°Π½ΠΈΡΡΠ΅Ρ Π΄Π°Π½Π½ΡΠ΅ ΠΈ ΠΈΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΡΡ ΡΡΡΠΎΠΊΡ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΡΠΏΠΈΡΠΊΠ° PATTERNS, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ ΡΡΠ½ΠΊΡΠΈΡ re.search
. Π€ΡΠ½ΠΊΡΠΈΡ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠ°Π·Π΄Π΅Π»Π΅Π½Π½ΡΡ Π·Π°ΠΏΡΡΡΠΌΠΈ ΡΡΡΠΎΠΊΡ. ΠΡΠ»ΠΈ Π²Ρ Π½Π΅ ΡΠ²Π»ΡΠ΅ΡΠ΅ΡΡ ΡΠΊΡΠΏΠ΅ΡΡΠΎΠΌ ΠΏΠΎ ΡΠ΅Π³ΡΠ»ΡΡΠ½ΡΠΌ Π²ΡΡΠ°ΠΆΠ΅Π½ΠΈΡΠΌ, Ρ ΡΠ΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΡ ΠΎΠ·Π½Π°ΠΊΠΎΠΌΠΈΡΡΡ Ρ ΡΡΠΈΠΌ datetime
Π²Π½ΡΡΡΠΈ ΡΡΠ½ΠΊΡΠΈΠΈ, ΡΡΠΎΠ±Ρ ΠΎΠ½Π° ΡΠ°Π±ΠΎΡΠ°Π»Π°. Π― ΠΏΠΎΠ»ΡΡΠ°Π» ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΠΎΠ± ΠΎΡΠΈΠ±ΠΊΠ΅ ΠΏΡΠΈ ΠΈΠΌΠΏΠΎΡΡΠ΅ Π² Π½Π°ΡΠ°Π»Π΅ ΡΠ°ΠΉΠ»Π°, ΡΡΠΎ Π±ΡΠ»ΠΎ ΡΡΡΠ°Π½Π½ΠΎ. ΠΡΠΎΡ ΡΠΏΠΈΡΠΎΠΊ Π·Π°ΡΠ΅ΠΌ ΠΏΠ΅ΡΠ΅Π΄Π°Π΅ΡΡΡ Π² ΡΡΠ½ΠΊΡΠΈΡ WriteToBigQuery, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΏΡΠΎΡΡΠΎ Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅Ρ Π½Π°ΡΠΈ Π΄Π°Π½Π½ΡΠ΅ Π² ΡΠ°Π±Π»ΠΈΡΡ. ΠΠΎΠ΄ Π΄Π»Ρ Batch DataFlow Job ΠΈ Streaming DataFlow Job ΠΏΡΠΈΠ²Π΅Π΄Π΅Π½ Π½ΠΈΠΆΠ΅. ΠΠ΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½ΠΎΠ΅ ΠΎΡΠ»ΠΈΡΠΈΠ΅ ΠΌΠ΅ΠΆΠ΄Ρ ΠΏΠ°ΠΊΠ΅ΡΠ½ΡΠΌ ΠΈ ΠΏΠΎΡΠΎΠΊΠΎΠ²ΡΠΌ ΠΊΠΎΠ΄ΠΎΠΌ Π·Π°ΠΊΠ»ΡΡΠ°Π΅ΡΡΡ Π² ΡΠΎΠΌ, ΡΡΠΎ Π² ΠΏΠ°ΠΊΠ΅ΡΠ½ΠΎΠΉ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ΅ ΠΌΡ ΡΠΈΡΠ°Π΅ΠΌ CSV ΠΈΠ· src_path
, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ ΡΡΠ½ΠΊΡΠΈΡ ReadFromText
ΠΈΠ· Beam.
Batch DataFlow Job (ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΏΠ°ΠΊΠ΅ΡΠΎΠ²)
import apache_beam as beam
from apache_beam.options.pipeline_options import PipelineOptions
from google.cloud import bigquery
import re
import logging
import sys
PROJECT='user-logs-237110'
schema = 'remote_addr:STRING, timelocal:STRING, request_type:STRING, status:STRING, body_bytes_sent:STRING, http_referer:STRING, http_user_agent:STRING'
src_path = "user_log_fileC.txt"
def regex_clean(data):
PATTERNS = [r'(^S+.[S+.]+S+)s',r'(?<=[).+?(?=])',
r'"(S+)s(S+)s*(S*)"',r's(d+)s',r"(?<=[).d+(?=])",
r'"[A-Z][a-z]+', r'"(http|https)://[a-z]+.[a-z]+.[a-z]+']
result = []
for match in PATTERNS:
try:
reg_match = re.search(match, data).group()
if reg_match:
result.append(reg_match)
else:
result.append(" ")
except:
print("There was an error with the regex search")
result = [x.strip() for x in result]
result = [x.replace('"', "") for x in result]
res = ','.join(result)
return res
class Split(beam.DoFn):
def process(self, element):
from datetime import datetime
element = element.split(",")
d = datetime.strptime(element[1], "%d/%b/%Y:%H:%M:%S")
date_string = d.strftime("%Y-%m-%d %H:%M:%S")
return [{
'remote_addr': element[0],
'timelocal': date_string,
'request_type': element[2],
'status': element[3],
'body_bytes_sent': element[4],
'http_referer': element[5],
'http_user_agent': element[6]
}]
def main():
p = beam.Pipeline(options=PipelineOptions())
(p
| 'ReadData' >> beam.io.textio.ReadFromText(src_path)
| "clean address" >> beam.Map(regex_clean)
| 'ParseCSV' >> beam.ParDo(Split())
| 'WriteToBigQuery' >> beam.io.WriteToBigQuery('{0}:userlogs.logdata'.format(PROJECT), schema=schema,
write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND)
)
p.run()
if __name__ == '__main__':
logger = logging.getLogger().setLevel(logging.INFO)
main()
Streaming DataFlow Job (ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΏΠΎΡΠΎΠΊΠ°)
from apache_beam.options.pipeline_options import PipelineOptions
from google.cloud import pubsub_v1
from google.cloud import bigquery
import apache_beam as beam
import logging
import argparse
import sys
import re
PROJECT="user-logs-237110"
schema = 'remote_addr:STRING, timelocal:STRING, request_type:STRING, status:STRING, body_bytes_sent:STRING, http_referer:STRING, http_user_agent:STRING'
TOPIC = "projects/user-logs-237110/topics/userlogs"
def regex_clean(data):
PATTERNS = [r'(^S+.[S+.]+S+)s',r'(?<=[).+?(?=])',
r'"(S+)s(S+)s*(S*)"',r's(d+)s',r"(?<=[).d+(?=])",
r'"[A-Z][a-z]+', r'"(http|https)://[a-z]+.[a-z]+.[a-z]+']
result = []
for match in PATTERNS:
try:
reg_match = re.search(match, data).group()
if reg_match:
result.append(reg_match)
else:
result.append(" ")
except:
print("There was an error with the regex search")
result = [x.strip() for x in result]
result = [x.replace('"', "") for x in result]
res = ','.join(result)
return res
class Split(beam.DoFn):
def process(self, element):
from datetime import datetime
element = element.split(",")
d = datetime.strptime(element[1], "%d/%b/%Y:%H:%M:%S")
date_string = d.strftime("%Y-%m-%d %H:%M:%S")
return [{
'remote_addr': element[0],
'timelocal': date_string,
'request_type': element[2],
'body_bytes_sent': element[3],
'status': element[4],
'http_referer': element[5],
'http_user_agent': element[6]
}]
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument("--input_topic")
parser.add_argument("--output")
known_args = parser.parse_known_args(argv)
p = beam.Pipeline(options=PipelineOptions())
(p
| 'ReadData' >> beam.io.ReadFromPubSub(topic=TOPIC).with_output_types(bytes)
| "Decode" >> beam.Map(lambda x: x.decode('utf-8'))
| "Clean Data" >> beam.Map(regex_clean)
| 'ParseCSV' >> beam.ParDo(Split())
| 'WriteToBigQuery' >> beam.io.WriteToBigQuery('{0}:userlogs.logdata'.format(PROJECT), schema=schema,
write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND)
)
result = p.run()
result.wait_until_finish()
if __name__ == '__main__':
logger = logging.getLogger().setLevel(logging.INFO)
main()
ΠΠ°ΠΏΡΡΠΊ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ°
ΠΡ ΠΌΠΎΠΆΠ΅ΠΌ Π·Π°ΠΏΡΡΡΠΈΡΡ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΠΌΠΈ ΡΠ°Π·Π»ΠΈΡΠ½ΡΠΌΠΈ ΡΠΏΠΎΡΠΎΠ±Π°ΠΌΠΈ. ΠΡΠ»ΠΈ Π±Ρ ΠΌΡ Π·Π°Ρ ΠΎΡΠ΅Π»ΠΈ, ΠΌΡ ΠΌΠΎΠ³Π»ΠΈ Π±Ρ ΠΏΡΠΎΡΡΠΎ Π·Π°ΠΏΡΡΡΠΈΡΡ Π΅Π³ΠΎ Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎ Ρ ΡΠ΅ΡΠΌΠΈΠ½Π°Π»Π°, ΡΠ΄Π°Π»Π΅Π½Π½ΠΎ Π²ΠΎΠΉΠ΄Ρ Π² GCP.
python -m main_pipeline_stream.py
--input_topic "projects/user-logs-237110/topics/userlogs"
--streaming
ΠΠ΄Π½Π°ΠΊΠΎ ΠΌΡ ΡΠΎΠ±ΠΈΡΠ°Π΅ΠΌΡΡ Π·Π°ΠΏΡΡΡΠΈΡΡ Π΅Π³ΠΎ Ρ ΠΏΠΎΠΌΠΎΡΡΡ DataFlow. ΠΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠ΄Π΅Π»Π°ΡΡ ΡΡΠΎ Ρ ΠΏΠΎΠΌΠΎΡΡΡ Π½ΠΈΠΆΠ΅ΠΏΡΠΈΠ²Π΅Π΄Π΅Π½Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ, ΡΡΡΠ°Π½ΠΎΠ²ΠΈΠ² ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ.
project
β ID Π²Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΎΠ΅ΠΊΡΠ° GCP.runner
β ΡΡΠ΅Π΄ΡΡΠ²ΠΎ Π·Π°ΠΏΡΡΠΊΠ° ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ°, ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΏΡΠΎΠ°Π½Π°Π»ΠΈΠ·ΠΈΡΡΠ΅Ρ Π²Π°ΡΡ ΠΏΡΠΎΠ³ΡΠ°ΠΌΠΌΡ ΠΈ ΡΠΊΠΎΠ½ΡΡΡΡΠΈΡΡΠ΅Ρ Π²Π°Ρ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ. ΠΠ»Ρ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ Π² ΠΎΠ±Π»Π°ΠΊΠ΅ Π²Ρ Π΄ΠΎΠ»ΠΆΠ½Ρ ΡΠΊΠ°Π·Π°ΡΡ DataflowRunner.staging_location
β ΠΏΡΡΡ ΠΊ ΠΎΠ±Π»Π°ΡΠ½ΠΎΠΌΡ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΡ Cloud Dataflow Π΄Π»Ρ ΠΈΠ½Π΄Π΅ΠΊΡΠΈΡΠΎΠ²Π°Π½ΠΈ ΠΏΠ°ΠΊΠ΅ΡΠΎΠ² ΠΊΠΎΠ΄Π°, Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΡΡ ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊΠ°ΠΌ, Π²ΡΠΏΠΎΠ»Π½ΡΡΡΠΈΠΌ ΡΠ°Π±ΠΎΡΡ.temp_location
β ΠΏΡΡΡ ΠΊ ΠΎΠ±Π»Π°ΡΠ½ΠΎΠΌΡ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΡ Cloud Dataflow Π΄Π»Ρ ΡΠ°Π·ΠΌΠ΅ΡΠ΅Π½ΠΈΡ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΡΠ°ΠΉΠ»ΠΎΠ² Π·Π°Π΄Π°Π½ΠΈΠΉ, ΡΠΎΠ·Π΄Π°Π½Π½ΡΡ Π²ΠΎ Π²ΡΠ΅ΠΌΡ ΡΠ°Π±ΠΎΡΡ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ°.streaming
python main_pipeline_stream.py
--runner DataFlow
--project $PROJECT
--temp_location $BUCKET/tmp
--staging_location $BUCKET/staging
--streaming
ΠΠΎΠΊΠ° ΡΡΠ° ΠΊΠΎΠΌΠ°Π½Π΄Π° Π²ΡΠΏΠΎΠ»Π½ΡΠ΅ΡΡΡ, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ Π½Π° Π²ΠΊΠ»Π°Π΄ΠΊΡ DataFlow Π² google-ΠΊΠΎΠ½ΡΠΎΠ»ΠΈ ΠΈ ΠΏΡΠΎΡΠΌΠΎΡΡΠ΅ΡΡ Π½Π°Ρ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ. ΠΠ»ΠΈΠΊΠ½ΡΠ² ΠΏΠΎ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΡ, ΠΌΡ Π΄ΠΎΠ»ΠΆΠ½Ρ ΡΠ²ΠΈΠ΄Π΅ΡΡ ΡΡΠΎ-ΡΠΎ ΠΏΠΎΡ ΠΎΠΆΠ΅Π΅ Π½Π° ΡΠΈΡΡΠ½ΠΎΠΊ 4. Π ΡΠ΅Π»ΡΡ ΠΎΡΠ»Π°Π΄ΠΊΠΈ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΠΎΡΠ΅Π½Ρ ΠΏΠΎΠ»Π΅Π·Π½ΠΎ ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ Π² Π»ΠΎΠ³ΠΈ, Π° Π·Π°ΡΠ΅ΠΌ Π² Stackdriver Π΄Π»Ρ ΠΏΡΠΎΡΠΌΠΎΡΡΠ° ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΡΡ Π»ΠΎΠ³ΠΎΠ². ΠΡΠΎ ΠΏΠΎΠΌΠΎΠ³Π»ΠΎ ΠΌΠ½Π΅ ΡΠ°Π·ΡΠ΅ΡΠΈΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ Ρ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠΎΠΌ Π² ΡΡΠ΄Π΅ ΡΠ»ΡΡΠ°Π΅Π².
Π ΠΈΡΡΠ½ΠΎΠΊ 4: Beam-ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ
ΠΠΎΡΡΡΠΏ ΠΊ Π½Π°ΡΠΈΠΌ Π΄Π°Π½Π½ΡΠΌ Π² BigQuery
ΠΡΠ°ΠΊ, Ρ Π½Π°Ρ ΡΠΆΠ΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ Π·Π°ΠΏΡΡΠ΅Π½ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ Ρ Π΄Π°Π½Π½ΡΠΌΠΈ, ΠΏΠΎΡΡΡΠΏΠ°ΡΡΠΈΠΌΠΈ Π² Π½Π°ΡΡ ΡΠ°Π±Π»ΠΈΡΡ. Π§ΡΠΎΠ±Ρ ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΡΡΠΎ, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ ΠΊ BigQuery ΠΈ ΠΏΡΠΎΡΠΌΠΎΡΡΠ΅ΡΡ Π΄Π°Π½Π½ΡΠ΅. ΠΠΎΡΠ»Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Π½ΠΈΠΆΠ΅ Π²Ρ Π΄ΠΎΠ»ΠΆΠ½Ρ ΡΠ²ΠΈΠ΄Π΅ΡΡ ΠΏΠ΅ΡΠ²ΡΠ΅ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΡΡΠΎΠΊ Π½Π°Π±ΠΎΡΠ° Π΄Π°Π½Π½ΡΡ . Π’Π΅ΠΏΠ΅ΡΡ, ΠΊΠΎΠ³Π΄Π° Ρ Π½Π°Ρ Π΅ΡΡΡ Π΄Π°Π½Π½ΡΠ΅, Ρ ΡΠ°Π½ΡΡΠΈΠ΅ΡΡ Π² BigQuery, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΎΠ²Π΅ΡΡΠΈ Π΄Π°Π»ΡΠ½Π΅ΠΉΡΠΈΠΉ Π°Π½Π°Π»ΠΈΠ·, Π° ΡΠ°ΠΊΠΆΠ΅ ΠΏΠΎΠ΄Π΅Π»ΠΈΡΡΡΡ Π΄Π°Π½Π½ΡΠΌΠΈ Ρ ΠΊΠΎΠ»Π»Π΅Π³Π°ΠΌΠΈ ΠΈ Π½Π°ΡΠ°ΡΡ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π½Π° Π±ΠΈΠ·Π½Π΅Ρ-Π²ΠΎΠΏΡΠΎΡΡ.
SELECT * FROM `user-logs-237110.userlogs.logdata` LIMIT 10;
Π ΠΈΡΡΠ½ΠΎΠΊ 5: BigQuery
ΠΠ°ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅
ΠΠ°Π΄Π΅Π΅ΠΌΡΡ, ΡΡΠΎ ΡΡΠΎΡ ΠΏΠΎΡΡ ΠΏΠΎΡΠ»ΡΠΆΠΈΡ ΠΏΠΎΠ»Π΅Π·Π½ΡΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠΎΠΌ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΏΠΎΡΠΎΠΊΠΎΠ²ΠΎΠ³ΠΎ ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ° Π΄Π°Π½Π½ΡΡ , Π° ΡΠ°ΠΊΠΆΠ΅ ΠΏΠΎΠΈΡΠΊΠ° ΡΠΏΠΎΡΠΎΠ±ΠΎΠ² ΡΠ΄Π΅Π»Π°ΡΡ Π΄Π°Π½Π½ΡΠ΅ Π±ΠΎΠ»Π΅Π΅ Π΄ΠΎΡΡΡΠΏΠ½ΡΠΌΠΈ. Π₯ΡΠ°Π½Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΡ Π² ΡΠ°ΠΊΠΎΠΌ ΡΠΎΡΠΌΠ°ΡΠ΅ Π΄Π°Π΅Ρ Π½Π°ΠΌ ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΡΠ΅ΠΈΠΌΡΡΠ΅ΡΡΠ². Π’Π΅ΠΏΠ΅ΡΡ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π½Π°ΡΠ°ΡΡ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π½Π° Π²Π°ΠΆΠ½ΡΠ΅ Π²ΠΎΠΏΡΠΎΡΡ, Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, ΡΠΊΠΎΠ»ΡΠΊΠΎ Π»ΡΠ΄Π΅ΠΉ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡ Π½Π°Ρ ΠΏΡΠΎΠ΄ΡΠΊΡ? Π Π°ΡΡΠ΅Ρ Π»ΠΈ ΡΠΎ Π²ΡΠ΅ΠΌΠ΅Π½Π΅ΠΌ Π±Π°Π·Π° ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ? Π‘ ΠΊΠ°ΠΊΠΈΠΌΠΈ Π°ΡΠΏΠ΅ΠΊΡΠ°ΠΌΠΈ ΠΏΡΠΎΠ΄ΡΠΊΡΠ° Π»ΡΠ΄ΠΈ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΡΡΡ Π±ΠΎΠ»ΡΡΠ΅ Π²ΡΠ΅Π³ΠΎ? Π Π΅ΡΡΡ Π»ΠΈ ΠΎΡΠΈΠ±ΠΊΠΈ, ΡΠ°ΠΌ Π³Π΄Π΅ ΠΈΡ Π±ΡΡΡ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ? ΠΡΠΎ ΡΠ΅ Π²ΠΎΠΏΡΠΎΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½Ρ Π΄Π»Ρ ΠΎΡΠ³Π°Π½ΠΈΠ·Π°ΡΠΈΠΈ. ΠΠ° ΠΎΡΠ½ΠΎΠ²Π΅ ΠΈΠ΄Π΅ΠΉ, Π²ΡΡΠ΅ΠΊΠ°ΡΡΠΈΡ ΠΈΠ· ΠΎΡΠ²Π΅ΡΠΎΠ² Π½Π° ΡΡΠΈ Π²ΠΎΠΏΡΠΎΡΡ, ΠΌΡ ΡΠΌΠΎΠΆΠ΅ΠΌ ΡΡΠΎΠ²Π΅ΡΡΠ΅Π½ΡΡΠ²ΠΎΠ²Π°ΡΡ ΠΏΡΠΎΠ΄ΡΠΊΡ ΠΈ ΠΏΠΎΠ²ΡΡΠΈΡΡ Π·Π°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΎΠ²Π°Π½Π½ΠΎΡΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ.
Beam Π΄Π΅ΠΉΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½ΠΎ ΠΏΠΎΠ»Π΅Π·Π΅Π½ Π΄Π»Ρ ΡΠ°ΠΊΠΎΠ³ΠΎ ΡΠΈΠΏΠ° ΡΠΏΡΠ°ΠΆΠ½Π΅Π½ΠΈΠΉ, Π° ΡΠ°ΠΊΠΆΠ΅ ΠΈΠΌΠ΅Π΅Ρ ΡΡΠ΄ Π΄ΡΡΠ³ΠΈΡ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΡΡ ΡΠ»ΡΡΠ°Π΅Π² ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ. ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π°Π½Π°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ Π΄Π°Π½Π½ΡΠ΅ ΠΏΠΎ Π±ΠΈΡΠΆΠ΅Π²ΡΠΌ ΡΠΈΠΊΠ°ΠΌ Π² ΡΠ΅ΠΆΠΈΠΌΠ΅ ΡΠ΅Π°Π»ΡΠ½ΠΎΠ³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ ΠΈ ΡΠΎΠ²Π΅ΡΡΠ°ΡΡ ΡΠ΄Π΅Π»ΠΊΠΈ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ Π°Π½Π°Π»ΠΈΠ·Π°, Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, Ρ Π²Π°Ρ Π΅ΡΡΡ Π΄Π°Π½Π½ΡΠ΅ Π΄Π°ΡΡΠΈΠΊΠΎΠ², ΠΏΠΎΡΡΡΠΏΠ°ΡΡΠΈΠ΅ Ρ ΡΡΠ°Π½ΡΠΏΠΎΡΡΠ½ΡΡ ΡΡΠ΅Π΄ΡΡΠ², ΠΈ Π²Ρ Ρ ΠΎΡΠΈΡΠ΅ Π²ΡΡΠΈΡΠ»ΠΈΡΡ ΡΠ°ΡΡΠ΅Ρ ΡΡΠΎΠ²Π½Ρ ΡΡΠ°ΡΠΈΠΊΠ°. ΠΡ ΡΠ°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ΅ΡΠ΅, Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, Π±ΡΡΡ ΠΈΠ³ΡΠΎΠ²ΠΎΠΉ ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠ΅ΠΉ, ΡΠΎΠ±ΠΈΡΠ°ΡΡΠ΅ΠΉ Π΄Π°Π½Π½ΡΠ΅ ΠΎ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡ ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡΠ΅ΠΉ Π΅Π΅ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΎΠ½Π½ΡΡ ΠΏΠ°Π½Π΅Π»Π΅ΠΉ Π΄Π»Ρ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΡ ΠΊΠ»ΡΡΠ΅Π²ΡΡ ΠΏΠΎΠΊΠ°Π·Π°ΡΠ΅Π»Π΅ΠΉ. ΠΠ°Π΄Π½ΠΎ, Π³ΠΎΡΠΏΠΎΠ΄Π°, ΡΡΠΎ ΡΠ΅ΠΌΠ° ΡΠΆΠ΅ Π΄Π»Ρ Π΄ΡΡΠ³ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ°, ΡΠΏΠ°ΡΠΈΠ±ΠΎ Π·Π° ΡΡΠ΅Π½ΠΈΠ΅, Π° Π΄Π»Ρ ΡΠ΅Ρ , ΠΊΡΠΎ Ρ ΠΎΡΠ΅Ρ ΡΠ²ΠΈΠ΄Π΅ΡΡ ΠΏΠΎΠ»Π½ΡΠΉ ΠΊΠΎΠ΄, Π½ΠΈΠΆΠ΅ ΡΡΡΠ»ΠΊΠ° Π½Π° ΠΌΠΎΠΉ GitHub.
ΠΠ° ΡΡΠΎΠΌ Π²ΡΠ΅.
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com