Node.js Worker Threads – על קצה המזלג

כידוע, Async ו Await הם לא פתרונות קסם, ולעיתים, אנו נדרשים לבצע מטלה שעלולה "לתקוע" את הEvent Loop, כמו חישוב מורכב , ליטרציה כבדה של מערכים ועוד.
אם יש לכם פונקציה שעושה חישוב מורכב, להוסיף בתחילתה async לא ייפתור את הבעיה והיא עדיין תתקע את הEvent Loop.
להזכירכם , NODE עובד בצורה של Event Loop שהוא Single Threaded למעשה.

(ליתר דיוק, הוא דווקא כן עושה שימוש בTHREADS , דהיינו הlibuv מייצר POOL של 4 THREADS כברירת מחדל מה שמאפשר לEVENT LOOP להעביר "משימות כבדות" לPOOL בצורה אוטומטית, בעיקר משימות שקשורות למערכת הקבצים, הצפנות, דחיסות וכו. אבל זה כבר נושא אחר).
בכל מיקרה, זאת האחריות שלנו כמפתחים לכתוב קוד שעושה שימוש בEvent Loop בצורה הנכונה ביותר.

נו, אז מה הפיתרון?

פיתרון אחד לבעיה שאומנם לא מתאים תמיד מבחינת ארכיטקטורה נכונה לכל המקרים, אבל לעיתים דווקא כן יכול להתאים הוא שימוש בWorker Threads, הדגשים הם:

  • לכל THREAD יש Process אחד ואת הEVENT LOOP שלו. – מה שרץ בWorker Thread אחד לא משפיע על השני מבחינת חסימה (None Blocking)

  • ניתן לחלוק זיכרון (לדוגמה באמצעות SharedArrayBuffer) וניתן להעביר מידע לThread שלנו.

  • לכל THREAD יש את הINSTANCE של הV8 וlubuv שלו (ISOLATED, וכן, הם צורכים משאבים)
  • מומלץ בעיקר למשימות שדורשות משאבי CPU

בואו נתחיל ונכתוב את הקוד הבא בParent שלנו (יכול להיות גם בApp.js שלכם):

//Parent Code:
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js');

בשורה 3 אנו טוענים לWORKER את הקובץ שאנחנו רוצים שהWORKER יריץ, במקרה שלנו worker.js.

נוסיף לקובץ את השורות הבאות:

//Subscribing for message on our Parent.
worker.on('message', message => console.log(message))

//Sending Message to our Worker
worker.postMessage('Hello');

בשורה 2, אנו "נרשמים" (Subscribe) להודעה מהWORKER שלנו, משמע כאשר הWORKER שלנו ישלח לנו הודעה במקרה שלנו הPARENT יריץ console.log(message).

בשורה 5, אנו שולחים לWORKER שלנו message עם הערך 'Hello', עד כאן, לא קרה בעצם יותר מדי, יצרנו WORKER ועשינו Subscribe לMessage ממנו, שלחנו לו Message עם הערך Hello. אבל הParent שלנו עדיין לא ידפיס לconsole את הmessage והסיבה לכך היא שהWorker שלנו כרגע לא שולח לParent
Message בחזרה.

בworker.js נכתוב את הקוד הבא:

const { parentPort } = require('worker_threads');
parentPort.on('message', message => 
    parentPort.postMessage({ hello: message })
);

בשורה 2 אנו עושים Subscribe מהWorker שלנו לParent, דהיינו, כאשר הWorker שלנו יקבל Message הוא יריץ את הפקודה
parentPort.postMessage({ hello: message });
מה שישלח למעשה לParent שלנו .

עד כאן למעשה יצרנו סינכרון מלא בין הParent שלנו לבין הWorker Thread שלו. הנה הקוד המלא:

//Parent Code:
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js');

//Subscribing for message on our Parent.
worker.on('message', message => console.log(message));

//Sending Message to our Worker
worker.postMessage('Hello');
const { parentPort } = require('worker_threads');
parentPort.on('message', message => 
    parentPort.postMessage({ hello: message });
)

אני ממליץ כמעט תמיד ליצור קובץ נפרד לThread שלנו (במקרה של הדוגמא כאן הוא .worker.js).
אבל במידה ולמקרה הספציפי, קובץ נפרד פחות מתאים נוכל להשתמש בטכניקה הבאה של שימוש בisMainThread כדי להכניס את כל הקוד שלנו לקובץ אחד:

const {Worker, isMainThread} = require('worker_threads');

//האם אנו רצים מה
//Parent?
if(isMainThread) {
//יוצרים Worker 
// חדש ושולחים אליו את הקובץ הנוכחי
 const worker = new Worker(__filename);
} else {
  //קוד זה רץ ב
  //Worker
 console.log('Hi from your worker!');
}

במקרה הנל אנו עושים שימוש בisMainThread שמאפשר לנו לדעת האם אנחנו רצים כרגע מתוך הWorker Thread או מתוך הParent שלנו ולעשות הפרדה בין הCode שירוץ.


איך מעבירים מידע ראשוני לWorker שלנו?

כדי להעביר DATA לWorker , נוכל לעשות שימוש באפשרות workerData, לדוגמה:

const { worker, inMainThread, workerData} = require('worker_threads');

if(isMainThread) {
 const worker = new Worker(__filename, { workerData: 'Hi!' })
} else {
 console.log(workerData); // will print 'Hi!'
}

יש עוד דברים מעניין שניתן לעשות בהם שימוש כמו SHARE_ENV ,eval, ממליץ לכם לתת הצצה בתיעוד ולגלות דברים מעניינים.

אופציה נוספת שאני חושב ששווה לתת דגש עליה היא resourceLimit:

const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js', { resourceLimits: {maxOldGenerationSizeMb: 10} })

במקרה הנל אנו מגבילים את הגודל של הHEAP (זיכרון) ל10MB. אם הWorker יגיע למגבלה הזאת הוא יושמד עם שגיאה.

אילו Events קיימים בWorker?

  • message , לדוגמא parentPort.postMessage()
  • exit – כשהworker שלנו הפסיק.
  • online – כאשר הWorker שלנו התחיל להריץ את הקוד שלו
  • error – כאשר נזרקה מהworker שלנו uncaught exception.

הערה לגבי השימוש בpostMessage:


בדוגמאות הנל עשינו שימוש ב postMessage כדי לשלוח מידע/הודעות בין הPARENT לThread. חשוב מאוד לדעת שכאשר אנו עושים שימוש בPostMessage, הדאטא שאנו מעבירים בעצם "משוכפל" – Cloned עם כל החסרונות / ייתרונות בכך, דהיינו, שיכפול של דאטא מורכב יכול לעלות לנו בהרבה כח CPU, ככל שהדאטא יותר מורכב/מסובך/עמוק יותר כך הוא יידרוש יותר כח מחשוב.

שלא נדבר על הRAM הכפול בעצם שאנו צורכים כדי לשמור את הדאטא הנוסף הזה בזיכרון.
מבחינת ארכיטקטורה, מאוד חשוב לשים לב לזה. כדי ללמוד יותר על הנושא ניתן להריץ חיפוש בגוגל עם המונח is postmessage slow, וגם להכיר את האפשרות של TransferList ועוד. (אולי בהזדמנות אכתוב על זה פוסט נפרד).

מצאתם טעות? הערות? שאלות? הסתדרתם? נתקעתם? כתבו לי בתגובות!


התקנת קוברנטיס

לשם ההדגמה נעבוד עם 3 מכונות וירטואליות, לראשון נקרא k8node1, לשני k8node2 ולשלישי שהוא ישמש כמסטר שלנו נקרא k8master.
3 המכונות האלו מריצות CentOs7.

את הפקודות הבאות נריץ בכל אחת מהמכונותם שלנו. הפקודה הראשונה תהיה

sudo su

על מנת לקבל הרשאות ROOT.

לשם ההדגמה , אנו נבטל את SELinux – שימו לב, לא לעשות את זה בפרודקשיין, זה בשביל להקל על ההדגמה בלבד.

setenforce 0
sed -i --follow-symlinks 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/sysconfig/selinux

בשלב הבא אנו נפעיל את br_netfilter כדי לאפשר תקשורת בין הקלסטרים שלנו, הפקודה בעצם מאפשרת לקוברנטיס לבצע שינויים בIPTABLES.

modprobe br_netfilter
echo '1' > /proc/sys/net/bridge/bridge-nf-call-iptables

בנוסף, אנו נבטל את הSWAP, הסיבה שאנו רוצים לעשות את זה היא על מנת שנקבל דיווח אמין על כמות הזיכרון שאנו משתמשים בו בפרודקשיין, כשאנו מאפשרים SWAP, הדיווח עלול להיות שגוי.

swapoff -a

בנוסף, אנחנו צריכים לערוך את הקובץ fstab, כצעד ראשון נבצע "גיבוי" לקובץ המקורי על ידי הפקודה:

cp /etc/fstab /etc/fstab.orig


ואז נריץ את הפקודה

nano /etc/fstab

ונוסיף # לפני השורה הבאה:

/root/swap swap swap sw 0 0

כך שנקבל את השורה הבאה במקום

#/root/swap swap swap sw 0 0

עכשיו נתקין את החבילות הדרושות לדוקר

 yum install -y yum-utils device-mapper-persistent-data lvm2

 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

 yum install -y docker-ce

בCENTOS אנו צריכים גם לוודא שדוקר וקוברנטיס ירוצו על אותו Cgroup Driver (הוא משמש לדיווח הסטטיסטיקה כמו זיכרון שאנו משתמשים בו וכו)
כמו כן, אנו נירצה שדוקר יופעל כל פעם שהמערכת מופעלת.

 sed -i '/^ExecStart/ s/$/ --exec-opt native.cgroupdriver=systemd/' /usr/lib/systemd/system/docker.service 

 systemctl daemon-reload

 systemctl enable docker --now

אחרי שסיימנו, נוספיף את הריפו של קוברנטיס על ידי הרצה של הפקודה הבאה:

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
   https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

ומייד לאחר מכן נתקין

 yum install -y kubelet kubeadm kubectl

וכמובן לא נשכח להפעיל אותו

systemctl enable kubelet
systemctl start docker.service

שימו לב! את הפקודות הבאות נריץ רק בשרת המסטר שלנו

kubeadm init --pod-network-cidr=10.244.0.0/16

לאחר שנריץ את הפקודה הנל במסטר, נקבל בחזרה את הפקודה אותה נצטרך להריץ במכונות האחרות שלנו על מנת שהם יוכלו להצטרף לקלסטר שהמסטר שלנו מנהל, במיקרה שלי הפלט שהתקבל הוא:

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.31.37.95:6443 --token 9mplpv.oxlntc0gz9iukdm3 \
    --discovery-token-ca-cert-hash sha256:429959040fbf2743d9dc2ed67045548057ee2ae4781b5253143d8f93dca3bd0d 

והפקודה שחשוב שנשמור אותה היא למעשה הפקודה שמכילה את הטוקן שאנו צריכים כדי ששאר המכונות שלנו יוכלו להתחבר לקלסטר שיצרנו (למסטר)

kubeadm join 172.31.37.95:6443 --token 9mplpv.oxlntc0gz9iukdm3 \
    --discovery-token-ca-cert-hash sha256:429959040fbf2743d9dc2ed67045548057ee2ae4781b5253143d8f93dca3bd0d

לאחר שסיימנו נקיש exit כדי לצאת ממצב ROOT ונריץ את הפקודות הבאות:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

בנוסף נעשה דפלוי לפנל שלנו:

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

ונבדוק את הסטטוס של הקלסטר על ידי הפקודה

kubectl get pods --all-namespaces

את הפקודות הנל אנו צריכים להריץ בכל המכונות שלנו (למעט הפקודות שקשורות למסטר בלבד). לאחר מכן בכל מכונה שהיא לא המסטר נריץ את הפקודה שמכילה את הטוקן שלנו (במקרה שלי)

kubeadm join 172.31.37.95:6443 --token 9mplpv.oxlntc0gz9iukdm3 \
    --discovery-token-ca-cert-hash sha256:429959040fbf2743d9dc2ed67045548057ee2ae4781b5253143d8f93dca3bd0d 

ולאחר מכן כשנריץ את הפקודה kubectk get nodes במסטר, אנו אמורים לראות את הנודים האחרים שלנו.

קצת על Kubernetes Deployments

Kubernetes Deployments הוא "כלי" שמאפשר לנו להפוך את תהליך יצירת הPODS שלנו לאוטומטי, אנו מגדירים את המצב (STATE) שאנו מעוניינים שהPOD שלנו יהיה בו, והקלסטר יידאג ליצור, לנהל ולשמור את המצב (STATE) שביקשנו.

דוגמאות לשימוש בDeployment

Scaling
מאפשר לבנו לבחור את מספר הרפליקציות שאנו מעוניינים שהDeployment ייצור לאפלקציה ספציפית. בנוסף ליצירת מס הרפלקציות שביקשנו, הDeployment יוודא שהרפלקציות שלנו יהיו מחולקות בצורה מאוזנת בין הNodes שלנו בשביל זמינות (Availability)

Self-Healing
כשאחד מהPODS שלנו נהרס,או נמחק בטעות, הDeployment ירים אחד חדש במקומו מיידית. אם הגדרתי שאני מעוניין ב6 רפליקציות למערכת שלי, ומחקתי בטעות רפליקה – הDeployment יידאג להרים רפליקה חדשה במקום.

Rolling Updates
בעזרת Deployment, אנו יכולים להחליף/לשנות IMAGE של קונטיינר, הDeployment יחליף קונטיינרים קיימים בגירסה החדשה בשלבים, הסיבה שאנו מעוניינים בכך היא שאם אנו נעשה דפלוי לאפלקציה החדשה שלנו בבת אחת – יהיה זמן מסויים של אי זמינות של המערכת, כאשר אנו עושים את הדפלוי בשלבים – החלפה של IMAGE בזה אחר זה אין לנו מצב של אי זמינות. כשיש לנו גירסה חדשה לאפלקציה שלנו ואנו רוצים לעשות לה דפלוי – אנו נעשה שימוש בDeployment.


הקובץ YAML הבא (נקרא לו example.yaml) למשל, מציין שאנו מעוניינים ב6 רפליקות, ובקונטיינר (במקרה שלנו קונטיינר אחד בלבד) בשם nginx שיריץ …nginx (במקרה שלנו 1.15.4).

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 6
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.15.4
        ports:
        - containerPort: 80

כדי להפעיל את הדפלוימנט שיצרנו נריץ את הפקודה הבאה

kubectl create -f example.yaml

ונקבל בחזרה שורה שאומרת:
deployment.apps/nginx-deployment created

אחרי שהפעלנו את הדפלוימנט הזה נריץ את הפקודה הבאה כדי לראות את רשימת הדפלוימנט שלנו ונשים לב שנקבל פלט שמכיל את שם הדפלוימנט שלנו (nginx-deployment), את כמות הרפלקציות שביקשנו (Desired) שבמקרה שלנו הוא 6, ופרטים נוספים.

kubectl get deployments

ונקבל במיקרה שלי:

NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deployment 6 6 6 6 30s

כדי לקבל מידע נוסף על דפלוימנט ספציפי נריץ את הפקודה הבאה , שימו לב שהפקודה צריכה להכיל את שם הדפלוימנט שאנו מעוניינים לקבל מידע נוסף לגביו, במיקרה שלנו nginx-deployment

kubectl describe deployment nginx-deployment

ועכשיו אנו רואים פרטים מלאים, כמו פורטים, סוג הIMAGE וכו.

אגב, אם נריץ את הפקודה

kubectl get pods

במיקרה שלנו אנו ניראה שיש 6 PODS שרצים (כיון שביקשנו 6 רפליקות).

NAME READY STATUS RESTARTS AGE
nginx-deployment-d55b94fd-8qbrs 1/1 Running 0 100s
nginx-deployment-d55b94fd-9rc4t 1/1 Running 0 100s
nginx-deployment-d55b94fd-dj4lx 1/1 Running 0 100s
nginx-deployment-d55b94fd-g278x 1/1 Running 0 100s
nginx-deployment-d55b94fd-kp4v6 1/1 Running 0 100s
nginx-deployment-d55b94fd-pvq87 1/1 Running 0 100s


אם נמחוק אחד מהPOD שלנו , על ידי הפקודה
kubectl delete nginx-deployment-d55b94fd-8qbrs למשל
אנו ניראה שPOD חדש נוצר במקומו כמעט מיידית כיון שדרשנו שיהיו לנו 6 רפליקות.
ונוכיח את זה על ידי הרצת הפקודה kubectl get pods שוב, ובמיקרה שלי הפלט שהתקבל הוא

nginx-deployment-d55b94fd-9rc4t 1/1 Running 0 2m15s
nginx-deployment-d55b94fd-dj4lx 1/1 Running 0 2m15s
nginx-deployment-d55b94fd-g278x 1/1 Running 0 2m15s
nginx-deployment-d55b94fd-kp4v6 1/1 Running 0 2m15s
nginx-deployment-d55b94fd-pvq87 1/1 Running 0 2m15s
nginx-deployment-d55b94fd-rnnjz 1/1 Running 0 9s

כאשר ניתן לראות שהPOD האחרון נוצר לפני 9 שניות על מנת להחליף את הPOD שמחקתי, לעומת האחרים שנוצרו לפני 2 דקות.

אסטרטגיות Cache בשימוש ב ElastiCache

ElasticCache הוא שירות מנוהל של MemCached או Redis (ניתן לבחור איזה מהמנועים אנו מעוניינים להריץ) ומיועד לשפר את ביצועי מסד הנתונים שלנו על ידי שמירת שאילתות בCache.


הייתרונות בשירות מנוהל ברורים – חוסך לנו את הזמן הדרוש להתעסק בתחזוקה ובניהול השירות , ובעיקר את המומחיות הדרושה שלא תמיד זמינה לנו, ומאפשר לנו להיות מפוקסים בפיתוח המוצר שלנו.

אסטרטגיות פופולאריות:
Lazy Loading:
בשיטה זו אנו בעצם כותבים לCache כאשר ניסינו לקבל מידע ממנו והוא לא קיים.
הייתרון בשיטה זו הוא שאנו טוענים רק מידע שבעצם יש לנו צורך בו.
בדרך כלל אנו נשתמש בשיטה זו בצירוף שיטות אחרות.

Write Through

בשיטה זו, בכל פעם שאנו מבצעים כתיבה למסד הנתונים שלנו אנו מעדכנים את הCache בהתאם.
הייתרון בשיטה זו שהקאש תמיד מעודכן במידע האחרון
החיסרון העיקרי שאנו בעצם מגדילים את הזמן הדרוש לאפלקציה שלנו לבצע את העדכון במסד הנתונים כיון שבשיטה זו בנוסף לעדכון מסד הנתונים אנו מעדכנים גם את הקאש.
– שימו לב, בשיטה זו חשוב מאוד לקבוע TTL לקאש שלנו – אחרת הקאש שלנו יהיה מלא בדאטא שייתכן שאנו לא צריכים אותו.

TTL – Time To Live
מאפשר לנו להקטין את החסרונות הקיימים באסטרטגיות Write Through ו Lazy Loading
TTL זה הזמן שאנו מעוניינים שהמפתח Cache שלנו יהיה קיים – או יותר נכון, מתי אנו מעוניינים שהוא יימחק.
חשוב לזכור שTTL לכשעצמו לא מבטיח לנו שהמידע שיש לנו בקאש יהיה העדכני ביותר, הוא רק מבטיח לנו שהקאש שלנו לא יהיה במלא במידע שאנו לא צריכים אותו.
TTL בשיתוף עם Lazy Loading בעצם יאפשר לנו לאכול את העוגה ולהשאיר אותה שלמה – אנו גם כותבים לקאש רק מידע שאנו צריכים וגם שומרים אותו עדכני על ידי שימוש בTTL – החיסרון הוא שבפעם הראשונה שאנו נצטרך את הדאטא – אנו בעצם לא נהנים מהייתרונות של הCache.

הגדרה וקונפיגורציה של CodeDeploy

שימו לב! המאמר עדיין לא גמור ורק חלק מהשלבים הרלוונטים מפורטים ומוסברים בו.

  1. אנו צריכים ליצור משתמש IAM (משתמש Non-admin) עם פוליסת CodeDeploy על מנת לאפשר לו לנהל את כל מה שקשור לCodeDeploy. לחילופין אנו יכולים לשדך את הפוליסה ל"קבוצה" ולשייך את המשתמש שיצרנו לאותה קבוצה.
  2. יצירת Instance Profile – מאפשר לנו לתת גישה לקוד רפוסיטי שלנו לEC2.
  3. יצירת Service Role – יאפשר לCodeDeploy להתקשר עם שירותים נוספים של AWS
  4. התקנת AWS CLI (מומלץ)

בשלב הראשון ניצור משתמש IAM חדש בשם MyCodeDeployUser

על מנת ליצור פוליסה מתאימה, נלחץ על AMI ואז של Policies.שם נלחץ על Create Policy

ואז נבחר באופציה CodeDeploy ונבחר את הפוליסה בשם "AWSCodeDeployFullAccess" או פוליסה אחרת שמתאימה לנו בהתאם לצורך.

לאחר מכן אנו צריכים להצמיד את הפוליסה למשתמש שאנו מעוניינים שיהיו לו את כל ההרשאות הקשורות לניהול CodeDeploy. נעשה זאת על ידי בחירת המשתמש MyCodeDeployUser תחת משתמשי הIAM שלנו ואז נלחץ על הוספת הרשאות. (בתמונה שם הפוליסה הוא MyCodeDeployPolicy אבל אצלכם שם הפוליסה יהיה AwsCodeDeployFullAccess

הגדרת Service Role מתאים לEC2

ניגש לIAM ואז לRoles, ואז נלחץ על Create Role.
ברשימת השירותים נבחר את השירות CodeDeploy וב Use Case נבחר "CodeDeploy"


ניתן שם לRole ונקרא לו CodeDeployServiceRole

מה זה CodeDeploy

CodeDeploy הוא שירות של AWS שמאפשר לנו להפוך את תהליך הDeployment שלנו לאוטומטי.

ייתרונות:
* Scale – ניתן לעשות דפלוי לשרת אחד או לאלפי שרתים ביחד
* שליטה – מאפשר לנו לקבל עדכונים ודוחות עם מידע לגבי מתי ומה דפלויד למערכת.
* צימצום זמן הDown Time שלנו – מאפשר שיטות דפלוי שונות כמו Rolling Updates ועוד.
* ניתן לעשות שימוש בCodeDeploy בכל שפה או טכנולוגיה – בתנאי שהקוד מנוהל במנוע מסוג GIT כמו CodeCommit לדוגמה או שהקוד שלנו יושב בS3
* ניתן לעשות דפלוי לשרתי EC2, לקונטיינרים או לSERVERLESS

למה אנחנו יכולים לעשות דפלוי?
* כמובן לקוד שלנו
* פונקציות Lambada
* סקריפטים
* קבצי מולטימדיה
ועוד…

פריימווארקים פופולארים לWEB בNodeJS – על קצה המזלג

Nodejs מגיעה עם המון מודלים מובנים, אבל בהרבה מהמקרים, אנו נצטרך לעשות שימוש בפריימוורקים על מנת שיענו לנו על צורך מסויים, ובהרבה מהמקים – פשוט להקל עלינו את העבודה מול המודולים המובנים.
בנוסף, שימוש בפרייווארק בדרכ עושה לנו סדר בקוד, ומקל על התחזוקה שלו.

הפופולארית ביותר – Express

התקנה באמצעות NPM

npm install express

קוד דוגמה ליצירת שרת באקספרס:

const express = require('express');
const myServer = express();

myServer.get('/', (req, res) => {
 res.send('Welcome to our website');
});

myServer.get('/products', (req, res) => {
 res.send('Products list');
});

myServer.listen(4153, () => {
 console.log('Hello from express server');
});

כאשר משתמש יכנס לכתובת הראשית של הURL שלנו, הוא יקבל בדפדפן את ההודעה
Welcome to our website.
כאשר הגולש ייכנס לכתובת /products הוא יקבל בדפדפן את ההודעה Products list.

לאתר אקספרס – https://expressjs.com/


פריימוארקים פופולאריים נוספים:
1. KOAJS – לאתר ליחצו כאן
2. Sails – דומה מאוד לRails, היא MVC, עם אפשרויות מיוחדות לREST API's. ליחצו כאן לאתר
3. Meteor – עולם ומלואו, לאתר ליחצו כאן
4. וכמובן האהובה עלי ביותר: NEST, לאתר ליחצו כאן

שימוש בNodemon בסביבות הפיתוח שלנו

NodeMon מאפשר לנו לאתחל את שרת הNode שלנו בכל פעם שמתבצע שינוי בקובץ, יעיל מאוד והכרחי כאשר אנו מפתחים בNode.

על מנת להתקין את nodemon נריץ את הפקודה הבאה:

npm i -g nodemon

נניח והקובץ הראשי שלנו הוא main.js, כדי להריץ אותו באמצעות nodemon נריץ את הפקודה הבאה:

nodemon main.js

וזאת במקום הפקודה הרגילה שהיא:

node main.js

ועכשיו בכל פעם שנשנה את הקובץ main.js שלנו – nodemon יפעיל את עצמו מחדש ואנו נוכל לראות את השינויים שעשינו מיידית ללא צורך לאתחל מחדש את השרת.

יצירת שרת HTTP בNodeJS באמצעות הAPI http – על קצה המזלג

http – הוא בילטאין בNodeJS, לכן, לא נדרשת התקנה של שום ספרייה. בExpress למשל אנו נצטרך כמובן להתקין אותה באמצעות NPM.

//השורה הבאה מחזירה את ה
// API של 
// http
const myHttpAPI = require('http');
const myRequestListener = (req, res) => {
 res.end('Hello from NodeJS !!!');
}

//הפקודה הבאה יוצרת את השרת שלנו למעשה, אך לא מפעילה אותו
const myServer = myHttpAPI.createServer(myRequestListener);

//נאזין לקריאות לשרת שיצרנו מקודם בפורט 80 או כל פורט אחר שנבחר
myServer.listen(80, () => {
 console.log('YAY! The server is ready!');
});

זוכרים שדיברנו על Events?
בכל פעם שמתקבלת בקשה – Request לשרת שלנו, משודר למעשה EVENT בשם 'request'.
הפונקציה myRequestListener שלנו בעצם "מאזינה" לEvent 'request' ומופעלת בכל פעם שמגיע אלינו request חדש.

חשוב להדגיש שהפונקציה myServer.listen היא פונקציה אסינכרונית, דהיינו, הקוד שבתוך פונקצית הcallback שלה יופעל רק כאשר הmyServer שלנו יהיה מוכן.


מצאתם טעות? הערות? שאלות? הסתדרתם? נתקעתם? כתבו לי בתגובות!

קלסטרים בNodeJS – הקדמה.

בסביבות פרודקשין, חשוב לעשות שימוש בNode Clusters, לדוגמה אם למעבד שלנו יש כמה ליבות – כל Cluster יעשה שימוש בליבה וכך נוכל לנצל את מלוא היכולת של המעבד שלנו. אגב, גם אם למעבד לשנו יש רק ליבה אחת – עדיין כדאי לעשות שימוש בCluster כיון שיש לו יכולת לנטר את הפרוסס הקיים ולהתחיל אחד חדש כאשר הקיים נכשל/נסגר.

ניתן להריץ קלסטרים של נוד באמצעות מודל הCluster שהוא בילט-אין בNode. לחילופין ניתן ליצור קלסטר בשימוש בכלים כמו PM2 שיכול לעשות שימוש בכל הליבות של השרת שלנו בצורה אוטומטית (עם Load Balancer מובנה) ולהפעיל מחדש את האפלקציה שלנו ללא Downtime.

גישה נוספת כמובן לשימוש בקלסטר היא שימוש בקוברנטיס – בשימוש בקוברנטיס אף אחד לא יכול להבטיח לנו שכאשר נבקש תהליך חדש – הוא ירוץ על ליבה נפרדת, בPM2 לעומת זאת – כאשר אנו מבקשים תהליך חדש – הוא ירוץ על ליבה נוספת.