Writing in the front

This paper aims to provide some basic ideas and common scenarios for the front-end students in nodeJS server project architecture design. The development of Node service essentially belongs to the category of server-side development. However, due to the popularization of nodeJS development applications and the extension of front-end tool chain to the server-side, the requirements for front-end students’ full-stack development ability are also increasing day by day, so I write this article. Because server development itself is a very large topic, this article will cover it quickly with some easy-to-understand examples. At the end of the article, I will take my company’s recent distributed transformation of unified front-end packaging service and multi-node deployment as examples to describe some practices

Distributed, clustered

distributed

What is distributed?

A part of the system is divided into a separate service, the internal services of the system can be called each other, the system external still form a whole

A typical scenario

Server-side development requires some data storage, often we would use the mysql database, rather than storing the data in the application of node, this is actually a simple distributed systems, database application through the network protocol, speaking, reading and writing, and at the same time the application and the database with external form a service as a whole

Architecture diagram

The cluster

What is a cluster?

In the distribution just mentioned, the application and database obviously provide different functions in the system, and when we deploy multiple same application nodes, these application nodes form an application cluster, so that the cluster is a whole formed by multiple nodes providing the same function in the system

A typical scenario

In the concept of cluster is already mentioned a typical scenario, is that we in the deployment node applications, especially in the production environment, usually deployed at least more than two nodes, to provide more business processing capabilities, while reducing downtime due to some nodes the effects on the system as a whole, thus forming a cluster

Architecture diagram

Single point of failure and high availability

A single point of failure

What is a single point of failure

The so-called single point of failure is that there is only one node for a certain service in the system, such as our Node application. At this time, if the program crashes or the server goes down, the system as a whole is unavailable to the outside world, thus forming a single point of failure

The solution

When the same service is deployed on multiple machines and one machine or service fails, the overall system performance is still available, so the application server cluster mentioned earlier can solve this problem

High availability

What is high availability

The system can maintain normal external services for a long time

The solution

In fact, the previous single point of failure is a typical problem that causes the system to fail to achieve high availability. Therefore, deploying multiple application nodes to form a cluster is still a basic solution for the system to achieve high availability

A smooth release

What is smooth publishing

When node services are published, they usually need to be stopped and restarted with new codes. During this period, if the system can still maintain normal external services, it is called smooth publishing

The solution

If the system has multiple application nodes, the basic conditions for smooth release are met. For example, if two application nodes A and B are deployed in the system, you only need to stop the services of node A and release node A before releasing node B

Load balancing

What is load balancing

As mentioned above, assuming that multiple application nodes have been deployed in the system, the client request needs to be distributed to each application node by a service according to a certain policy, so that the multiple nodes can provide services externally. In this case, the client request is called load for the system, and the so-called balance. This is to use a distribution strategy so that multiple nodes can distribute requests to clients relatively evenly

The solution

As a common Web server, Nginx has the ability of load balancing. We can use one Nginx as the pre-server of the application cluster. Nginx can distribute requests to multiple application nodes randomly

Architecture diagram

Persistent storage, caching, and distributed caching

What is persistent storage

A service that can store data for a long time, such as a database such as mysql. For persistent storage, data is usually stored as a file on disk

What is caching

Cache for front-end students should not strange, generally called access fast storage cache, and access speed bottleneck often depends on a storage medium, such as persistent storage, generally in the form of a disk file save data, and disk file I/O speed obviously is far lower than the memory I/O, so usually cache in memory as a storage medium, Redis, or even storing data directly into a JS object in a Node application, is an easy way to cache

Distributed cache

What is distributed caching

As mentioned above, it is obvious that a distributed cache is a cache that is independent of a single service, such as Redis, etc

Why do you need a distributed cache

Suppose there are multiple application nodes in our system. The client sends a request to store some data, and the load balance distributes the request to an application node. At this time, if the distributed cache is not used, the node will cache the data in the memory of its own node process. In this case, the load balancer still randomly distributes the request to an application node. If the node receiving the request is different from the node storing the request before, the node does not have corresponding data and data fails to be pulled. We need a centralized storage of application clusters to solve such problems

The solution

After receiving the data storage request, any node stores the data to the distributed cache, such as REDis. When the client pulls the data, the application node still obtains the corresponding data from REDis and responds to the client

Architecture diagram

The message queue

What is a message queue

The so-called message, in fact, is a specific structure of data, obviously, message queue is a specific structure of data formed by a FIFO data structure, usually with Redis or other more professional MQ (Message Queue) to achieve the message queue

Usage scenarios

See the asynchronous tasks section that follows below

Asynchronous tasks and scheduled tasks

Asynchronous tasks

A common scenario is that a Node application provides HTTP-based services externally. The system receives an HTTP request, executes some service logic, and then responds to the client through HTTP. Now assume that the business scenario is user registration. The service logic needs to perform a series of operations, such as creating a user, saving the user to the database, and sending an email to the user’s mailbox. At this time, if all operations are completed synchronously, the response to the successful creation of the client user may take a long time. System response speed is an important indicator of user experience or system performance. So the parts can be time consuming and success has little effect on the main business business logic (such as email here) to process the data sent to the first preserved in a message queue, then immediately to the client in response to the user to create success, then the asynchronous email user data was obtained from the message queue and carry out the operation, The FIFO feature of message queues allows these tasks to be processed in the order of HTTP requests

Architecture diagram

Timing task

Scheduled tasks are easy to understand. For example, in our Node service, some processes can be started to perform some tasks in a certain period of time, such as calculating the average age of users accessing the system once a day

Long connections and Websockets

What is a long connection

Simply put, when a client establishes a network connection with a server for a long time, it is called a long connection. The common HTTP protocol is based on TCP. Although TCP itself does not limit the connection duration, due to the design concept of HTTP itself as an application layer protocol, the connection can be disconnected after the completion of a request response model. However, when we use Websocket in Node service to communicate with the client for a long time and multiple round-trip duplex, It can be called a long connection

Usage scenarios

As mentioned above, when the business to be processed is not suitable for a simple request response model, but the client and server need to carry out a long time and multi-frequency two-way data interaction, a long connection, such as Websocket, can be considered

High concurrency

Finally, high concurrency is a scenario where the system has to handle a large number of client requests at the same time. Our NodeJS uses a single-threaded, non-blocking I/O and event-driven underlying model, which has a natural (but not absolute) advantage in handling high concurrency requests. Well, this article will not discuss the high concurrency of NodeJS in depth. If you are interested, please refer to my previous article juejin.cn/post/684490…

The actual case

Unified front-end packaging service distributed transformation, multi-node deployment

background

At the bottom of the company’s front-end publishing system, a unified packaging service provides the packaging function of the front-end project, which is technically a Node project using EggJS. The basic business logic is that the client initiates the packaging request, the server accepts the request, downloads the project code from the code repository, installs dependencies, executes the packaging script, pushes the log generated during the packaging process to the client through websocket, and uploads the final packaging result to the server after the packaging. Refer to the flow chart below

Because each packaging task has a certain consumption of system resources, such as CPU, memory, and so on, and there is an upper limit of system resources, so a task queue is maintained in the program to ensure that the number of packaging tasks processed at the same time is not more than a certain number, and the extra tasks are queued in FIFO

Problems before transformation

As the application server is deployed on a single node, basic high availability and smooth release cannot be achieved. In addition, the number of packaged tasks processed at the same time depends on the upper limit of system resources. With the increasing release requirements of front-end teams in the whole company, queuing phenomenon becomes more and more serious, affecting the release efficiency of front-end projects in the whole company

A possible solution or problem

Since system resources are the bottleneck, can you upgrade the hardware resources of the system directly? The answer is yes, but not a good, easy to implement, scalable solution. Adding hardware resources directly first is sometimes not an easy thing, although some virtualization technology can be used to solve; Secondly, if a certain amount of system resources are added according to the existing demand, the hardware resources need to be expanded again after a period of time when the demand increases, which is obviously not an easy to implement and good scalability scheme. At the same time, application single-node deployment cannot achieve basic high availability and smooth release, which also affects system stability and user experience to a certain extent

Multi-node deployment of the solution

First, multi-node deployment is easy, just deploy the same application to multiple servers, and nginx does the request distribution in front of you. But if it’s just that, it raises some serious questions

Problem 1. Assuming that one server can handle four packaging tasks simultaneously, two servers should be able to handle eight tasks simultaneously. Consider a scenario where five packaging requests come in, assuming that they all happen to be randomly distributed to the same application node by Nginx. The application node processed the first four tasks, while the fifth task was set to pending state by the application because it exceeded the maximum number of tasks that the node could process at the same time, that is, it entered the queuing logic. As for the system as a whole, the maximum number of tasks that can be processed at the same time is 8, but queues are created when the fifth task enters, which is obviously not in line with our expectations

Problem 2. As mentioned earlier, the packaging service needs to push the generated log to the client through the Websocket during the packaging process. Consider A scenario where there are two application servers A and B. The client initiates A packet request, and Nginx sends the request to node A. At the same time, because the client needs to receive the packet log through websocket, the client initiates A webSocket long connection, and nginx performs another packet distribution. What happens if you make a long connection to node B? Since no packing is performed on node B and no logs are generated, naturally the client cannot receive the desired data over this long connection

Distributed transformation of solutions

Can be seen from the above scenario, the essence of the two questions are actually out in some application state is the application of a node in the separate maintenance, bring foreign unable to form an effective whole system, so the solution is to external to the application cluster state distribution, formed an independent and public services. For the first problem, I use Redis as a distributed cache to maintain a task queue. Each application node sends the task to the task queue after receiving the request, and then all nodes pull the task according to their own task processing situation. In this way, a production/consumption model is also formed. Each application node produces tasks to the task queue as a producer, and pulls tasks from the task queue as a consumer for consumption. In this way, the tasks handled by each node at the same time do not exceed their limits, and the overall external service logic of the system is normal

Ok, let’s look at the second problem. Essentially, the problem is that nginx does not distribute the application node that handles the packaging task. As a result, the client cannot obtain the packaging log. Similarly, redis is used to send the log data generated by all nodes to Redis first. By using the publish/subscribe function of Redis, all nodes subscribe to the topic published by log in advance. When a node publishes new log data to Redis, all nodes can obtain the corresponding log data from Redis. At this point, all nodes in the application cluster have the ability to push logs generated by all the tasks being packaged in the system

summary

So far, the system architecture of distributed task queue, production and consumption, release and subscription of log data and application cluster has completed the unified multi-node deployment of packaged services, and realized the basic high availability and smooth release of the system. Such architecture plays a more important role, that is, it gives the system the ability to scale horizontally, that is, we can now smoothly deploy new application nodes in the cluster when the overall service capacity of the system is insufficient again. (The so-called horizontal extension is the vertical direction in the previous architecture diagram. Usually, the extension direction of the system from the client to the server is called the vertical direction of the system architecture, and a certain level of the vertical direction is the horizontal direction, such as the application cluster above.)

conclusion

Actually either the server or client architecture design, is a process of gradual, gradually improve, often does not need to come up to use a most complex architecture, so that may appear in all aspects of resources waste, has promoted the problem such as the complexity of the system, a lot of time a can meet the requirements, simple and effective architecture is a good architecture. On the other hand, the system designer also needs to be able to anticipate the future direction of the system or user needs, so as to design a more scalable architecture