Tencent news gold rush of people active node isomorphic straight out summary rendering scheme

Our business in the process unfolded, the front-end rendering mode is mainly experienced three stages: the server-side rendering, the current front-end rendering and homogeneous straight out of the rendering scheme.

The main features of the service side rendering is not separated front and rear ends, the front end of the finished page style and structure, and then to the rear cover page data, and then finally with the FBI. At the same time the front end of the release is also dependent on the back end of the students; but the advantages are obvious: faster page rendering speed, while good SEO results.

In order to solve the problem is not isolated front and rear ends, and later appeared in this mode front-end rendering, routing and page rendering, all on the front-end. Front and rear end through an interface to interact, each side can focus more on their business, while also publishing an independent publication. But the disadvantage is slow page rendering, heavily dependent on loading speed js file js file is loaded when the failure or CDN fluctuations, the page will hang directly. Most of our business is the front end before rendering mode, some user feedback pages long loading time, page rendering speed is slow, especially on older Android models to this problem is more obvious.

node同构直出渲染方案And the front end server to avoid rendering the rendering shortcomings, while the front and rear ends with js are written, the data can be achieved, components, tools and other methods to achieve the shared front and rear ends.

1. Effect

First, look at the result of the statistics can be seen from the front end of the switching mode rendering to the node isomorphic straight out rendering mode, loading an entire page to Processed reduced from about 2100 ms 3500ms, the overall loading speed increased by nearly 40% .

1- homogeneous linear optimization results out scheme - mosquito distal blog

But this data is not the final data, because it was time to catch on the line, a lot of things yet to come and optimization, after subsequent optimization is complete, you can see a whole load of time-consuming and dropped to around 1600ms, fall again about 500ms.

2- homogeneous linear optimization results out scheme - mosquito distal blog

Reduced from 3500ms to 1600ms, 1900ms full speed up the loading speed of enhancing the overall 54%. Means optimized later also to explain.

2. challenges encountered

Carrying out homogeneous linear rendering scheme, also existing art, in combination with its own technology stack, the overall architecture of the comb.

Selection of Technology - a homogeneous linear program - mosquito distal blog

The next important and difficult to tease out the presence of:

  1. How to keep homogeneous data, routing state, the common base components? How to distinguish between client and server?
  2. How to make data requests, whether cross-domain requests exist? How are the data requested in the server, browser and news client, what their characteristics are, if you can package it?
  3. Engineering: how to distinguish between the development environment, test environment, the environment and the official pre-release environment? How to unit test execution? You can automates the release?
  4. What are the characteristics of the project page, pages, data interfaces, components, etc. Can cache? How caching? Whether personalized data exist?
  5. How logging, performance data reporting projects, such as the volume of requests, the front page of the entire page load time-consuming, error rates, and other time-consuming back-end data? How exception occurred in the service node (such as the load is too high, memory leaks) an alarm?
  6. How to deal with disaster recovery, when an exception occurs how to downgrade and inform developers to quickly repair!
  7. node is a single thread, how to take advantage of multicore?
  8. Performance optimization: pre-loading, lazy loading images, use service worker, lazy loading js, IntersectionObserver lazy loading components, etc.

For our initial project planning, the problems that may occur one by one to solve, and ultimately our projects can be achieved Cha Buli, some of the larger modules I might need to separate out to write an article summarized.

3. functions to achieve

The front and rear ends of the isomorphic 3.1

Use node server pointed isomorphic rendering scheme, the most important is the data structure can be achieved 前后端的同构共享.

Homogeneous aspect is to achieve: a data homogeneous, homogeneous state, and routing components isomorphism isomorphism like.

Data isomorphic: For the same virtual DOM element, use renderToNodeStream the rendering results in the form of "flow" upon their response objects on the server side, so do not wait until all rendered html to the browser in order to return the results, "flow" role is how much content to how much content can be further improved, "the first significant rendering time." Meanwhile, in the browser, use the virtual hydrate dom rendered as real DOM elements. If the number of components in the browser-side comparison of services rendered, if inconsistencies occur, it is no longer a direct throw away all of the content, but local rendering. Therefore, in the rendering process uses the server side, the front and rear end components to ensure the consistency of the data. Here the server request data, js is inserted into the global variable, together with the rendering html to the browser (dehydration); this is the browser, the data can be dehydrated to get the initialization component, and the like add interactivity etc. (water).

State isomorphic: We used here mobxto create a global state management for each user, so that data can be unified management, and clothing Cen layer without passing between components.

Isomorphic components: the preparation of the basic components or other components may be able to use the service and client while using typeof window==='undefined'or process.browserto determine whether the current client or server, in order to shield a terminal does not support the operation.

Route unity: the client to use BrowserRouterthe service end use StaticRouter.

In the process of isomorphic in the very beginning not quite understand this concept, in the coding phase encountered such a problem. For example, we have a small carousel, the carousel is displayed random array upset, and I will end after the service request data upset rendered to the page, the result of an error message in the debug output window (here we use a sample data instead):

const list = ['勋章', '答题卡', '达人榜', '红包', '公告'];

In the render()random output:

{
    list.sort(() => (Math.random() < 0.5 ? 1 : -1)).map(item => (
        <p key={item}>{item}</p>
    ));
}

Results console output warning information, and ultimately out of the display information is not disrupted sort:

Warning: Text content did not match Server:. "Red envelope" Client: "answer sheet"

A warning message is output because the client found that current data are inconsistent with the server, the client re-rendering, and gives a warning message. We only array upset when rendering order, the server is rendered in accordance with the data out of order, but passed to the client's data or raw data, resulting in inconsistent data problems end.

If you really want a random order, you can obtain the data after the server directly sorted first, and then rendering the data so that the server and client will remain the same. In nextjs it is in getInitialPropsoperation.

3.2 How data request

Based on the characteristics of our project is mainly run in the news client, we have to consider the way a variety of data requests: within the server, browser, news client, whether cross-domain characteristics, and then form a complete unified multi terminal data request system.

  • Server: http modules, or third-party components axios http request initiating the like, and transparent transmission and ua cookie to the interface;
  • News client: use news provided by the client jsapi initiate interface requests, request pay attention to the differences in the way different APP iOS and Android;
  • Browser cross-domain requests: create a script tag initiate interface request and set the timeout;
  • Browser sympatric request: precedence fetch, then XMLHttpRequestinitiate interface request.

Here the data are encapsulated multi-terminal external call to provide a unified and stable way, without concern for the business layer from which the current request initiated by the terminal.

// 发起接口请求
// @params {string} url 请求的地址
// @params {object} opts 请求的参数
const request = (url: string, opts: any): Promise<any> => {};

Meanwhile, we also added the request interface method of monitoring the process, such as information request amount monitoring interface, time consuming, failure rate, so that the detailed information is recorded, and the corresponding rapid positioning.

3.3 engineering

Engineering is a great concept, we are here only be described from a few small points.

Our projects are currently deployed on skte, by setting different environment variable to distinguish the current test environment, the environment and the official pre-release environment.

At the same time, because our business is mainly accessible features in the news client, lots of unit tests can not fully cover only part of the unit testing, to ensure the normal operation of the basic functions.

Test Unit - a homogeneous linear program - mosquito distal blog

Now, after access to a fully automated CI (continuous integration) / CD (continuous deployment), publishing built on git branch mode, when a developer to complete the coding work, push to test / pre / master branch, unit testing check, it will automatically integrate and deploy adoption.

3.4 Cache

Cache advantage Needless to say:

  • Speeds up browser page loads;
  • Reducing the redundant data transmission, save network traffic and bandwidth;
  • Reduce the burden on the server, greatly improving site performance.

But at the same time increase the cache, the complexity of the overall project will increase, we need to assess whether the project fit under caching, caching mechanism which applies to, what to do if a cache miss.

Caching mechanisms are:

  1. Strong browser cache or nginx cache: Cache fixed length of time, for example 30ms time, read data in the cache during this time of 30ms, the cache is disadvantage of this data can not be updated in time, you must wait until after the time to update the cache ;
  2. Or global cache status register: This is applicable to handover between routing multiple data caches, or individual users, only valid during a single accessible;
  3. Memory cache: cache stored in memory, no additional I / O overhead, reading and writing speed; but the drawback is prone to failure data, the cache is lost once the program directly abnormal, but can not reach the shared cache memory between processes. Here, when we use the browser's cache negotiations that produced based on the content generated ETagvalue, if etag value is the same as using the cache, or data requests to the server, which will result in cache data between different processes may be different, and more etag times of failure problem. Memory cache with particular attention to the problem of memory leaks
  4. Distributed caching: caches use an independent third party, such as Redis or Memcached, when the benefits can be shared among multiple processes, while reducing the processing of the project itself out of the cache algorithm

Different projects or different pages with different caching strategies.

  • Infrequently updated data, such as home page, leaderboards and other pages, you can use the browser cache or interfaces strong buffer;
  • User avatar, nickname, personalized data such as the use of state management;
  • You can use a third-party interface data cache

When the data cache to the interface, in particular, should be noted that when the interface returns to normal, only cached data, or to deal with the business layer.

At the same time, the use of cache in the process, but also pay attention to the problem of cache invalidation.

Cache invalidation meaning solution
Cache avalanche All cache at the same time fail Set random cache time
Cache penetration Does not exist in the cache, the database does not exist A blank value in the cache, and the cache time shorter
Random request key Maliciously using a random key request, not result in a cache hit Bloom filter data in the filter is not in direct interception
The key for the cache The cache database but there is no After the success of the request, cache data, and returns data

3.5 Logging

Detailed logging enables us to easily understand the effect of the project and troubleshoot problems. Manifestations front and rear ends are not the same, we have to distinguish between the front and rear ends of the logs to be reported.

The front page of the major reporting performance information, the server reported major abnormalities, CPU and memory usage and other programs.

In the front, we can use window.performancesimple calculation performance data to web:

  • First loaded consuming: domLoading - fetchStart;
  • Full page time-consuming: loadEventEnd - fetchStart;
  • Error rate: the amount of the error log / request amount;
  • DNS time-consuming: domainLookupEnd - domainLookupStart;
  • TCP time-consuming: connectEnd - connectStart;
  • Back-end time-consuming: responseStart - requestStart;
  • html consuming: responseEnd - responseStart;
  • DOM consuming: domContentLoadedEventEnd - responseEnd;

We also need to capture some of the front-end code error:

  1. Global capture, error:
window.addEventListener(
    'error',
    (message, filename, lineNo, colNo, stackError) => {
        console.log(message); // 错误信息的描述
        console.log(filename); // 错误所在的文件
        console.log(lineNo); // 错误所在的行号
        console.log(colNo); // 错误所在的列号
        console.log(stackError); // 错误的堆栈信息
    }
);
  1. Global capture, unhandledrejection:

When Promise is reject and do not reject the processor when the trigger unhandledrejection event; this may occur in the window, but may also occur in the Worker. This rollback error handling is very useful for debugging.

window.addEventListener('unhandledrejection', event => {
    console.log(event);
});
  1. Interface asynchronous request

Here can fetchand XMLHttpRequestre-packaging, will not affect the normal business logic, error may be reported.

XMLHttpRequest package:

const xmlhttp = window.XMLHttpRequest;
const _oldSend = xmlhttp.prototype.send;

xmlhttp.prototype.send = function() {
    if (this['addEventListener']) {
        this['addEventListener']('error', _handleEvent);
        this['addEventListener']('load', _handleEvent);
        this['addEventListener']('abort', _handleEvent);
    } else {
        var _oldStateChange = this['onreadystatechange'];
        this['onreadystatechange'] = function(event) {
            if (this.readyState === 4) {
                _handleEvent(event);
            }
            _oldStateChange && _oldStateChange.apply(this, arguments);
        };
    }
    return _oldSend.apply(this, arguments);
};

fetch packages:

const oldFetch = window.fetch;
window.fetch = function() {
    return _oldFetch
        .apply(this, arguments)
        .then(res => {
            if (!res.ok) {
                // True if status is HTTP 2xx
                // 上报错误
            }
            return res;
        })
        .catch(error => {
            // 上报错误
            throw error;
        });
};

Server log based on the severity, can be divided into the following main categories:

  1. error: error, unexpected problems;
  2. warning: warning, there has been an exception in the expected, but the program can run normally, as a whole controllable;
  3. info: conventional, normal recording information;
  4. silly: of unknown causes;

We focused on the degree of abnormality may appear different categories (level) reporting, where we adopted two strategies recorded, respectively, using web logs bossand local logs winstonwere recorded. boss log records relatively simple information, easily and quickly carried out the investigation through a browser; winston record detailed local log, when you can not locate a simple log information, use local logs more detailed investigation.

Use winstonwere reported to the server logs by date classification, the main message of the report are: the current time, server process ID, message, stack trace etc:

// https://github.com/winstonjs/winston
logger = createLogger({
    level: 'info',
    format: combine(label({ label: 'right meow!' }), timestamp(), myFormat), // winston.format.json(),
    defaultMeta: { service: 'user-service' },
    transports: [
        new transports.File({
            filename: `/data/log/question/answer.error.${date.getFullYear()}-${date.getMonth() +
                1}-${date.getDate()}.log`,
            level: 'error'
        })
    ]
});

Meanwhile nodejs service itself also take advantage of the monitoring mechanism, including, for example http status code, memory usage (process.memoryUsage) and so on.

Statistical process logs added alarm mechanism, the alarm when the number or value exceeds a certain range, and alarm information sent to device developers and microcells mailbox. For example, wherein an alarm rule is: when the page load time is less than 10ms or more than 6000ms send an alarm message , hang up the page description, the server might be larger than 6000ms abnormal, resulting in long time to load resources is less than 10ms.

But also timely attention to user feedback platform, if a user generates a feedback, there must be such a problem more users.

3.6 disaster recovery process

Behavior arising after logging and alarm are all accidents, how should we ensure that this period of time before we see the log information and fix the problem, the service can still be running at least, not black and white or a 5xx and other information. Here we have to do is deal with disaster recovery services online.

potential problem Disaster recovery measures
Abnormal backend interface Use the default data, and promptly inform the interface side
High instantaneous flow rate is too high CPU load Automatic expansion, and alarm
service node abnormalities, such as 4xx, 5xx, etc. The number of nginx service will automatically turn static pages, and alarm forwarding
Static resources caused by abnormal style The first Style Home screen or embedded into the page

Disaster recovery process and record log information, to protect our online program can run normally.

3.7 cluster module

nodejs as a single-threaded, single-process program running, if simply using words ( node app.js), there are some problems as follows:

  • Can not take full advantage of multi-core cpu machines,
  • Service instability, an unhandled exception will cause the entire program to exit
  • No sophisticated log management solutions,
  • No service / process monitoring mechanism

Fortunately, nodejs provides us with clusterthe module, what is the Cluster :

simply put,

  • Start multiple processes on the server.
  • Each process in both running the same copy of the source code (like putting work before a process of give multiple processes to do it).
  • Even more amazing is that these processes can simultaneously monitor a port ( Cluster implementation principle ).

among them:

  • Responsible for starting the process to other processes called the Master, he is like a "contractor", do not do specific work, is only responsible for starting other processes.
  • Other call Worker process started, as the name suggests is the work of "workers." They receive requests to provide services.
  • The number of Worker process is generally based on the number of CPU core servers to be, so that you can take advantage of multicore perfect resource.

cluster module can create shared server port of the child. Here's an official known cases:

const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isMaster) {
    // 当前为主进程
    console.log(`主进程 ${process.pid} 正在运行`);

    // 启动子进程
    for (let i = 0, len = os.cpus().length; i < len; i++) {
        cluster.fork();
    }

    cluster.on('exit', worker => {
        console.log(`子进程 ${worker.process.pid} 已退出`);
    });
} else {
    http.createServer((req, res) => {
        res.writeHead(200);
        res.end('hello world\n');
    }).listen(8000);

    console.log(`子进程 ${process.pid} 已启动`);
}

When a process exits, it will trigger exitevents, such as when we kill off the process 69030:

> kill -9 69030

子进程 69030 已退出

We try to kill off a process, we found that the child is not automatically re-created, and here I can modify the next exitevent, re-create a child process is triggered when this event:

cluster.on('exit', worker => {
    console.log(`子进程 ${worker.process.pid} 已退出`);
    // log日志记录

    cluster.fork();
});

Communication between the main processes and sub-processes: between each process is independent of each other, but each process can communicate with the primary process. This will put a lot of questions needs of each child processes need to be addressed, into the main process in the process, such as logging, caching. We also section 3.4 cache stresses "shared memory cache can not be reached between the process", but we can improve the cache to the main process for caching.

if (cluster.isMaster) {
    Object.values(cluster.workers).forEach(worker => {
        // 向所有的进程都发布一条消息
        worker.send({ timestamp: Date.now() });

        // 接收当前worker发送的消息
        worker.on('message', msg => {
            console.log(
                `主进程接收到 ${worker.process.pid} 的消息:` +
                    JSON.stringify(msg)
            );
        });
    });
} else {
    process.on('message', msg => {
        console.log(`子进程 ${process.pid} 获取信息:${JSON.stringify(msg)}`);
        process.send({
            timestamp: msg.timestamp,
            random: Math.random()
        });
    });
}

However, if online production environments, we need to give this code to add a lot of logic. Here you can use pm2to maintain our project node, while pm2 also enable cluster mode.

pm2 official website is http://pm2.keymetrics.io , GitHub is https://github.com/Unitech/pm2 . The main features are:

  • Native clustering support (using Node cluster cluster module)
  • Record the number and time of restart of the application
  • Background daemon mode
  • 0 seconds off overloaded, the program is ideal for upgrade
  • Stop unstable process (to avoid infinite loop)
  • Monitoring Console
  • Real-time centralized log processing
  • Robust API, includes a remote control and real-time interface API (Nodejs module allows administrators to interact and PM2 process)
  • Automatically kill the process on exit
  • Built-in support for start-up (support of many linux distributions and macos)

Work nodejs services can be hosted to pm2 process.

pm2 the current maximum number of CPU cluster startup mode:

pm2 start server.js -i max

However, our project uses configuration files to start, ecosystem.config.js:

module.exports = {
    apps: [
        {
            name: 'question',
            script: 'server.js',
            instances: 'max',
            exec_mode: 'cluster',
            autorestart: true,
            watch: false,
            max_memory_restart: '1G',
            env_test: {
                NEXT_APP_ENV: 'testing'
            },
            env_pre: {
                NEXT_APP_ENV: 'pre'
            },
            env: {
                NEXT_APP_ENV: 'production'
            }
        }
    ]
};

Then you can start:

pm2 start ecosystem.config.js

On the use of cluster node to write mode, or use pm2 to start cluster mode, still need to look at the project. When using the node write, you can control the communication between the various processes, allowing each process to do their own thing; and pm2 to start, then some of the better overall robustness.

3.8 Performance Optimization

We should first ensure load Home and above the fold, one is required to form the first screen embedded directly into the page to load, then one is the first screen and second screen data separately loaded. Our data on the first page of the main ways to load a waterfall, stream and waterfall is required js calculated, so here we loaded first few data to ensure that there is a first screen of data, then the next data should be on using js computing which position.

Another is to use service worker to a local cache css and js resources, more specifically, provide access to application service worker in a red envelope press activities .

Here we use IntersectionObserver package versatile components lazy loading scheme, since the use of scroll events, we may also need to manually throttling and anti-shake, at the same time, because of the speed of image loading, resulting in the need to obtain offsetTop value of the element multiple times. The IntersectionObserver perfectly able to avoid these problems, and we can see, this property in high version of the browser is also supported in the lower version browser, I can use polyfill manner compatible processing;

IntersectionObserver- a homogeneous linear program - mosquito distal blog

I will be packaged as a component of this function, external monitor provides several methods, will need lazy loading of resources as a component or sub-assembly, wrapped, at the same time, we recommend here also suggest users to use the default skeleton prop up the screen element is not when page rendering. Because when direct rendering using lazy loading, if you do not use the skeleton screen, the user is to see black and white, and then suddenly rendered content to the page the user feel a strong shake. When the real component in the final really show up, take some time and space and time are loaded from resources to render completion takes time; and space refers to the page layout needs to real components aside some problems, one is to avoid page after a re-use skeleton screen:

  1. To enhance the user's sensory experiences
  2. Ensure consistency switch
  3. Providing visibility observation target object, the component is performed to ensure the visibility of the lazy loading area

General lazy loading components implemented here, foreign provides several callback methods: onInPage, onOutPage, onInited and so on.

This generic component lazy loading scheme can be used in the following scenarios:

  1. Lazy loading vary particle size as large as a component or several components, can be as small as a picture;
  2. Page data reporting exposure module, such a hopper can be calculated from the exposure module to the data involved;
  3. Infinite scrolling long lists: we can listen to a transparent element at the bottom of the page, when the transparent element is about to be seen, to load and render data on the next page.

Of course, infinite scrolling long lists of priority, visibility is not limited to use in place of a scroll event, there are other optimization methods.

4. Summary

Although verbose a lot, but this is our isomorphic straight out to start rendering scheme, we still have a long way to go. Difficulties in the application of technologies not overcome the technical issue, but that it can continue to combine their product experience, discovery experience these problems exist, continue to use better technology solutions to optimize the user experience, contribute to the entire product development.

node isomorphic straight out of the rendering scheme - the mosquito front-end blog

Front-end blog links mosquitoes: https://www.xiabingbao.com .

I welcome attention to the micro-channel public number: wenzichel
Public micro-channel number - the mosquito front-end blog

Guess you like

Origin www.cnblogs.com/xumengxuan/p/11718006.html