精华内容
下载资源
问答
  • react 元素延迟加载As developers, when we build apps for users on the internet, it is very important to ensure that the app is served ... 作为开发人员,当我们为互联网上的用户构建应用程序时,确保以最快...

    react 元素延迟加载

    As developers, when we build apps for users on the internet, it is very important to ensure that the app is served to the user in the fastest way possible.

    作为开发人员,当我们为互联网上的用户构建应用程序时,确保以最快的方式为用户提供应用程序非常重要。

    When building React applications, it's very common to see the size of the app grow largely due to the number of dependencies in use. This happens when a section of the app (or route) might import a large number of components that are not necessary when it first loads. This can reduce the initial load time of the app.

    在构建React应用程序时,通常会看到应用程序的大小在很大程度上由于所使用的依赖项而增长。 当应用程序(或路由)的某个部分可能会导入大量首次加载时不需要的组件时,就会发生这种情况。 这样可以减少应用程序的初始加载时间。

    So how do we approach this? How do we make sure that the app only loads what is needed thereby avoiding bloat in the code?

    那么我们该如何处理呢? 我们如何确保应用程序仅加载所需的内容,从而避免代码中出现膨胀?

    We can do that by utilizing Lazy Loading. Lazy loading is a great way to optimize your site, and it does that by splitting your code at logical breakpoints, and then loading it once the user has done something that requires, or will require, a new block of code. This speeds up the initial load of the application and lightens its overall weight as some blocks may never even be loaded.

    我们可以利用延迟加载来做到这一点。 延迟加载是优化站点的一种好方法,它可以通过在逻辑断点处拆分代码,然后在用户完成需要或将需要新代码块的操作后加载,来进行加载。 这会加快应用程序的初始加载速度,并减轻其整体重量,因为某些块甚至可能从未加载过。

    In React, we can lazy load components and routes by code splitting using Webpack. By splitting our app into chunks we can load & evaluate only the code that is required for the page rendered.

    在React中,我们可以使用Webpack通过代码拆分来延迟加载组件和路由。 通过将我们的应用程序分成多个块,我们可以仅加载和评估呈现页面所需的代码。

    With all of that being mentioned, let's go ahead and create a basic React app and demonstrate how we can lazy load routes.

    提到了所有这些之后,让我们继续创建一个基本的React应用程序,并演示如何可以延迟加载路由。

    使用create-react-app引导React应用 ( Bootstrap a React app with create-react-app )

    We'll be using the create-react-app CLI to bootstrap a new React app. The CLI, which was built by Facebook helps developers by creating a structured React app that works out of the box; no build configuration is needed.

    我们将使用create-react-app CLI引导新的React应用程序。 由Facebook构建的CLI通过创建一个开箱即用的结构化React应用程序来帮助开发人员。 无需构建配置。

    Install the create-react-app tool with this command:

    使用以下命令安装create-react-app工具:

    npm install -g create-react-app

    Once the installation process has been completed, you can now create a new React app by using the command create-react-app lazy-loading-routes.

    安装过程完成后,您现在可以使用命令create-react-app lazy-loading-routes创建一个新的React应用。

    This generates a new folder with all the files required to run the React app. You can now run any of the following commands:

    这将生成一个新文件夹,其中包含运行React应用程序所需的所有文件。 现在,您可以运行以下任何命令:

    npm start
    npm run build
    npm test

    The npm start command runs the app in development mode, the npm run build command builds the app for production to the build folder, and the npm test command runs the test watcher in an interactive mode.

    npm start命令以开发模式运行该应用程序, npm run build命令将要生产的应用程序构建到build文件夹, npm test命令以交互模式运行测试观察程序。

    The basic idea of the React app we are building is to have routes/components that use one or two React plugins. Without code splitting, all the React code and plugins in use will be bundled into one JavaScript file, but with code splitting, only the component/plugin needed would be loaded.

    我们正在构建的React应用程序的基本思想是拥有使用一个或两个React插件的路由/组件。 如果没有代码拆分,所有使用中的React代码和插件将被捆绑到一个JavaScript文件中,但是通过代码拆分,仅会加载所需的组件/插件。

    This isn’t a concern early on when our app is quite small, but it becomes an issue down the road when the app becomes quite large.

    当我们的应用程序很小时,这不是一个早期的问题,但是当应用程序变得很大时,这将成为一个问题。

    Let's get back to building the app. The create-react-app CLI generates a working React app for us as mentioned above and that means we can starting building immediately.

    让我们回到构建应用程序。 如上所述, create-react-app CLI为我们生成了一个可运行的React应用,这意味着我们可以立即开始构建。

    First of all, let's set up the basic routes we'll be needing for the React app. For routing, we'll be using react-router. You can add the react-router package to the app by running npm install react-router-dom in the terminal.

    首先,让我们设置React应用程序所需的基本路线。 对于路由,我们将使用react-router 。 您可以通过在终端中运行npm install react-router-dom来将react-router软件包添加到应用程序中。

    Once the installation is complete, we can now begin to create the components that will serve as the routes. For this app, we'll be using four routes; Home, Maps, Blog, and a catch-all route that serves as a 404 page NotFound.

    安装完成后,我们现在可以开始创建将用作路线的组件。 对于这个应用程序,我们将使用四个路线; HomeMapsBlog以及用作404页NotFound路线。

    Navigate to the src folder inside the project directory and run the following commands:

    导航到项目目录内的src文件夹,然后运行以下命令:

    mkdir Home Maps Blog NotFound

    That will create a folder for the different components to be used. It's basically a way to compartmentalize the React app.

    这将为要使用的不同组件创建一个文件夹。 基本上,这是划分React应用程序的一种方法。

    Before we create the individual components to be used, let's edit the App.js file and set up the basic routes. Open up the App.js file and edit with the following code:

    在创建要使用的各个组件之前,让我们编辑App.js文件并设置基本路线。 打开App.js文件,并使用以下代码进行编辑:

    // Import React and Component
    import React, { Component } from 'react';
    import {
        BrowserRouter as Router,
        Route,
        Switch,
        Link
    } from 'react-router-dom'
    
    // Import the Home component to be used below
    import Home from './Home/Home'
    // Import the Maps component to be used below
    import Maps from './Maps/Maps'
    // Import the Blogs component to be used below
    import Blog from './Blog/Blog'
    // Import the NotFound component to be used below
    import NotFound from './NotFound/NotFound'
     // Import CSS from App.css
    import './App.css';
    import createBrowserHistory from 'history/createBrowserHistory';
    
    const history = createBrowserHistory();
    class App extends Component {
        render () {
            return (
                <Router history={history}>
                    <div>
                        <header className="header">
                            <nav className="navbar container">
                                <div className="navbar-brand">
                                    <Link to="/">
                                        <span className="navbar-item">Lazy Loading Routes</span>
                                    </Link>
                                </div>
    
                                <div className="navbar-end">
                                    <Link to="/maps">
                                        <span className="navbar-item">Maps</span>
                                    </Link>
                                    <Link to="/blog">
                                        <span className="navbar-item">Blog</span>
                                    </Link>
                                </div>
                            </nav>
                        </header>
                        <section className="content">
                            <Switch>
                                <Route exact path="/" component={Home} />
                                <Route path="/maps" component={Maps} />
                                <Route path="/blog" component={Blog} />
                                <Route path="*" component={NotFound} />
                            </Switch>
                        </section>
                    </div>
                </Router>
            )
        }
    }
    
    export default App;

    In the code block above, we imported React and its Component module using ES6 import, and we also imported BrowserRouter, Route, Switch and Link from react-router. Inside the render() function, we first created a view which the user can use to navigate the different routes and then the <Switch> component holds the different routes and the components that responds to them.

    在上面的代码块中,我们使用ES6 import导入了React及其组件模块,还从react-router导入了BrowserRouterRouteSwitchLink 。 在render()函数内部,我们首先创建了一个视图,用户可以使用该视图导航不同的路由,然后<Switch>组件保存不同的路由以及对其做出响应的组件。

    The App.css file should be edited with the following code:

    应该使用以下代码编辑App.css文件:

    .header {
      background-color: #000;
      padding-top: 15px;
    }
    .navbar {
      background-color: #000;
    }
    .navbar-item {
      color: white !important;
    }
    .content {
      margin-top: 50px;
    }

    Let's continue with the individual components, navigate into the Home folder and create the following files: Home.js and Home.css. Open up the Home.js file and edit with the following code:

    让我们继续各个组件,导航到Home文件夹并创建以下文件: Home.jsHome.css 。 打开Home.js文件,并使用以下代码进行编辑:

    import React, { Component } from 'react'
    import './Home.css'
    import Button from '../NavButton/NavButton'
    
    class Home extends Component {
        render () {
            return (
                <div className="container">
                    <section className="section">
                        <div className="container">
                            <h1 className="title">Lazy Loading</h1>
                            <h2 className="subtitle">
                                A simple app to demonstrate how lazy loading routes in React works.
                            </h2>
                            <section className="bottom">
                                <Button name="Go to About page" link="/about" />
                                <Button name="Go to Blog page" link="/blog" />
                            </section>
                        </div>
                    </section>
    
                </div>
            )
        }
    }
    
    export default Home

    In the code block above, we basically just created the view for the Home component. A Button component is used, although it is yet to be created in this tutorial, it takes a prop of name and link. We also imported styles from the Home.css file. Let's write the CSS for that file.

    在上面的代码块中,我们基本上只为Home组件创建了视图。 使用了Button组件,尽管本教程中尚未创建它,但是它采用了namelinkprop 。 我们还从Home.css文件中导入了样式。 让我们为该文件编写CSS。

    .subtitle {
        margin-top: 10px !important;
    }
    .bottom {
        margin-top: 300px;
    }
    .bottom a {
        margin-right: 10px;
    }

    Next up is the Maps route, The maps page simply shows a Google map of a location using the google-map-react React plugin. Navigate to the Maps folder and create the following files: Maps.js and Maps.css. Open up the Maps.js file and edit with the following code:

    接下来是地图路线,“地图”页面仅使用google-map-react React插件显示位置的Google地图 。 导航到Maps文件夹并创建以下文件: Maps.jsMaps.css 。 打开Maps.js文件,并使用以下代码进行编辑:

    import React, { Component } from 'react'
    import './Maps.css'
    import GoogleMapReact from 'google-map-react';
    
    const MapsView = ({ text }) => (
        <div style={{
            position: 'relative', color: 'white', background: 'red',
            height: 40, width: 60, top: -20, left: -30, textAlign: 'center', paddingTop: '5px'
        }}>
            {text}
        </div>
    );
    
    class Maps extends Component {
        static defaultProps = {
            center: {lat: 6.5244, lng: 3.3792},
            zoom: 11
        };
    
        render () {
            return (
                <div className="container">
                    <p>This page is simply a page that shows a Google Map view of a location. Play around with the coordinates to get a different view</p>
                    <div className="map-container">
                        <GoogleMapReact
                            defaultCenter={this.props.center}
                            defaultZoom={this.props.zoom}
                        >
                            <MapsView
                                lat={6.5244}
                                lng={3.3792}
                                text={'Your Location'}
                            />
                        </GoogleMapReact>
                    </div>
                </div>
            )
        }
    }
    
    export default Maps

    In the code block above, we first imported React, and it's Component module. The google-map-react plugin is also imported. The MapsView() function takes in a parameter of text and puts that text in a div.

    在上面的代码块中,我们首先导入了React ,它是Component模块。 google-map-react插件也已导入。 MapsView()函数接受一个text参数,并将该text放入div

    The next thing is the ES6 class named Maps that extends the component module from react. Inside the Maps component, we set some default props value by using the defaultProps object and the render() function contains the view and the GoogleMapReact component. If you'd like to read more on the google-map-react plugin, you can do that here.

    接下来的事情是名为Maps的ES6类,该类扩展了react模块。 在Maps组件内部,我们使用defaultProps对象设置了一些默认的props值,并且render()函数包含视图和GoogleMapReact组件。 如果您想了解更多有关google-map-react插件的信息,可以在此处进行

    Let's write the CSS for the Maps.css file. Open up the file and type in the following:

    让我们为Maps.css文件编写CSS。 打开文件并输入以下内容:

    .map-container {
        height: 400px;
    }

    The next component is the Blog component, which uses a React plugin called react-markdown to render markdown into pure React components. Navigate to the Blog folder and create the Blog.js file. Open up the Blog.js file and edit with the following code:

    下一个组件是Blog组件,它使用称为React -markdown的React插件将markdown渲染为纯React组件。 导航到Blog文件夹并创建Blog.js文件。 打开Blog.js文件,并使用以下代码进行编辑:

    import React, { Component } from 'react'
    import ReactMarkdown from 'react-markdown'
    
    class Blog extends Component {
        constructor(props) {
            super(props);
    
            this.state = {
                markdownSrc: [
                    '# Lazy Loading Routes with React\n\nWhy do we need to lazy load routes?.\n\n* Reduce code bloat\n* Avoid loading all components at the same time ',
                    '\n* React app loads faster',
                    '\n* Load only the component that is needed and preload the others\n',
                    '\n## A quote\n\n<blockquote>\n    A man who splits his code ',
                    'is a wise man.\n</blockquote>\n\n## How about some code?\n',
                    '```js\nimport React, { Component } from \'react\';\nimport asyncComponent from \'./AsyncComponent\'',
                    '\n\nimport {\n' +
                    '    BrowserRouter as Router,\n' +
                    '    Route,\n' +
                    '    Switch,\n' +
                    '    Link\n' +
                    '} from \'react-router-dom\'\n```\n\n\n'
                ].join(''),
    
                htmlMode: 'raw'
            };
        }
        render () {
            return (
                <div className="container">
                    <ReactMarkdown source={this.state.markdownSrc} />
                </div>
            )
        }
    }
    
    export default Blog

    In the code block above, we imported the react-markdown plugin and used it to render the markdown in the state.markdownSrc into a pure React component in the render() function.

    在上面的代码块中,我们导入了react-markdown插件,并使用它在state.markdownSrc中将markdown渲染为render()函数中的纯React组件。

    The last component is the catch-all route, the NotFound route, navigate into the NotFound folder and create the NotFound.js file. Open up the NotFound.js file and edit with the following code:

    最后一个组件是全部路由,即NotFound路由,导航到NotFound文件夹并创建NotFound.js文件。 打开NotFound.js文件,并使用以下代码进行编辑:

    import React, { Component } from 'react'
    
    class NotFound extends Component {
        render () {
            return (
                <div className="container">
                    <p>404</p>
                    <p>Page not found - </p>
                </div>
            )
        }
    }
    
    export default NotFound

    We used a Button component in the Home route above, let's create the component now. In the src folder, create a folder titled NavButton and then create a file named NavButton.js inside the newly created folder. Open up the NavButton.js file and edit with the following code:

    我们在上面的Home路线中使用了Button组件,现在让我们创建该组件。 在src文件夹中,创建一个名为NavButton的文件夹,然后在新创建的文件夹中创建一个名为NavButton.js的文件。 打开NavButton.js文件,并使用以下代码进行编辑:

    import React from 'react';
    import { Link } from 'react-router-dom'
    
    const button = {
        backgroundColor: 'white',
        padding: 10,
        color: 'black',
        borderRadius: 1,
        borderColor: 'black',
        borderWidth: 2,
        borderStyle: 'solid'
    }
    
    const Button = (props) => {
        return (
            <Link to={props.link}>
                <span style={button}>{props.name}</span>
            </Link>
        )
    }
    
    export default Button

    In the code block above, we created a functional stateless component that builds a view for a button. The component is a button that helps with navigation in the React app, and that is achieved by using react-router which is imported at the top of the file. The Button component takes in two props; link and name. The link prop is used to determine what route to navigate to, and the name prop is used to display a text within the button.

    在上面的代码块中,我们创建了一个功能性的无状态组件,该组件为按钮构建视图。 该组件是一个按钮,可帮助在React应用程序中导航,可通过使用在文件顶部导入的react-router来实现。 Button组件带有两个propslinknamelink属性用于确定导航到的路线, name属性用于显示按钮内的文本。

    Right now, you can run the app to see the progress. To see the app in development mode, run the command npm start in your terminal and you should see a homepage similar to the one below.

    现在,您可以运行该应用程序以查看进度。 要在开发模式下查看该应用程序,请在终端中运行命令npm start ,您将看到一个类似于以下内容的主页。

    Now that we know the app works fine, let's do some analysis and see how the app loads all the JavaScript code we've written. Run the command npm run build in your terminal to build the app for production.

    现在我们知道该应用程序可以正常工作,让我们进行一些分析,看看该应用程序如何加载我们编写的所有JavaScript代码。 在终端中运行命令npm run build来构建用于生产的应用程序。

    As you can see from above, all the JavaScript code is bundled into one file main.....js with a relatively small size. This is quite okay but can prove problematic if the size of the JavaScript file becomes too large. Let's see how we can fix this with code splitting.

    从上面可以看到,所有JavaScript代码都打包到一个相对较小的main.....js文件中。 没关系,但是如果JavaScript文件的大小太大,可能会出现问题。 让我们看看如何通过代码拆分解决此问题。

    代码分割 ( Code Splitting )

    The reason why we are here. How do we implement code splitting in a React app? This can be done by using Webpack and since create-react-app already ships with Webpack, there will be no need for extra config or ejecting create-react-app.

    我们之所以在这里。 我们如何在React应用程序中实现代码拆分? 这可以通过使用Webpack来完成,并且由于Webpack已经附带了create-react-app ,因此不需要额外的配置或弹出create-react-app

    Let's take a look at the routes setup we defined up there:

    让我们看一下我们在那里定义的路由设置:

    import Home from './Home/Home'
    import Maps from './Maps/Maps'
    import Blog from './Blog/Blog'
    
    <Switch>
        <Route exact path="/" component={HomeComponent} />
        <Route path="/maps" component={MapsComponent} />
        <Route path="/blog" component={BlogComponent} />
        <Route path="*" component={NotFound} />
    </Switch>

    With the current setup above, the Switch component renders the route that matches the path that users navigate to by using the components above. Because we import all the components at the top, this means that all components are loaded when a user goes to a particular route, even though the rest is not needed at all.

    使用上面的当前设置, Switch组件通过使用上面的组件来呈现与用户导航到的路径匹配的路由。 因为我们在顶部导入了所有组件,所以这意味着当用户转到特定路线时,即使根本不需要其余组件,也会加载所有组件。

    This is where code splitting comes in. Code splitting helps to dynamically import components and only load them whenever they are needed thereby removing unnecessary JavaScript code that's not needed. So how do we go about code splitting?

    这就是代码拆分的地方。代码拆分有助于动态导入组件,并仅在需要它们时才加载它们,从而删除不需要JavaScript代码。 那么我们如何进行代码拆分呢?

    Create a file titled AsyncComponent.js in the src folder and edit with the following:

    src文件夹中创建一个名为AsyncComponent.js的文件,并使用以下内容进行编辑:

    import React, { Component } from "react";
    export default function asyncComponent(getComponent) {
        class AsyncComponent extends Component {
            static Component = null;
            state = { Component: AsyncComponent.Component };
    
            componentWillMount() {
                if (!this.state.Component) {
                    getComponent().then(Component => {
                        AsyncComponent.Component = Component
                        this.setState({ Component })
                    })
                }
            }
            render() {
                const { Component } = this.state
                if (Component) {
                    return <Component {...this.props} />
                }
                return null
            }
        }
        return AsyncComponent;
    }

    The asyncComponent() function takes a parameter, getComponent which is a function that will dynamically import a given component. It will not be called until the first mount. On componentWillMount, we simply call the getComponent function that is passed in and save the dynamically loaded component in the state. Finally, we conditionally render the component if it has completed loading, if not we simply render null.

    asyncComponent()函数采用参数getComponent ,该参数将动态导入给定组件。 在第一次安装之前,不会调用它。 在componentWillMount ,我们只需调用传入的getComponent函数,并将动态加载的组件保存在状态中。 最后,如果组件已完成加载,则有条件地渲染该组件,否则,我们仅渲染null。

    Now that we have the asyncComponent down, let's change how we import the components in the App.js file by importing them dynamically with the asyncComponent() function. The code block below in the App.js file should be replaced with the second code block.

    现在我们已经关闭了asyncComponent ,让我们通过使用asyncComponent()函数动态导入组件来更改在App.js文件中导入组件的App.js 。 App.js文件中下面的代码块应替换为第二个代码块。

    import Home from './Home/Home'
    import Maps from './Maps/Maps'
    import Blog from './Blog/Blog'
    // Dynamically imported components
    const Home = asyncComponent(() =>
        import('./Home/Home').then(module => module.default)
    )
    
    const Maps = asyncComponent(() =>
        import('./Maps/Maps').then(module => module.default)
    )
    
    const Blog = asyncComponent(() =>
        import('./Blog/Blog').then(module => module.default)
    )

    Your final App.js should look like the code block below:

    最终的App.js应该看起来像下面的代码块:

    import React, { Component } from 'react';
    import {
        BrowserRouter as Router,
        Route,
        Switch,
        Link
    } from 'react-router-dom'
    
    import NotFound from './NotFound/NotFound'
    import './App.css';
    import createBrowserHistory from 'history/createBrowserHistory';
    import asyncComponent from './AsyncComponent'
    
    const Home = asyncComponent(() =>
        import('./Home/Home').then(module => module.default)
    )
    
    const Maps = asyncComponent(() =>
        import('./Maps/Maps').then(module => module.default)
    )
    
    const Blog = asyncComponent(() =>
        import('./Blog/Blog').then(module => module.default)
    )
    
    const history = createBrowserHistory();
    
    class App extends Component {
        render () {
            return (
                <Router history={history}>
                    <div>
                        <header className="header">
                            <nav className="navbar container">
                                <div className="navbar-brand">
                                    <Link to="/">
                                        <span className="navbar-item">Lazy Loading Routes</span>
                                    </Link>
                                </div>
    
                                <div className="navbar-end">
                                    <Link to="/maps">
                                        <span className="navbar-item">Maps</span>
                                    </Link>
                                    <Link to="/blog">
                                        <span className="navbar-item">Blog</span>
                                    </Link>
                                </div>
                            </nav>
                        </header>
    
                        <section className="content">
                            <Switch>
                                <Route exact path="/" component={Home} />
                                <Route path="/maps" component={Maps} />
                                <Route path="/blog" component={Blog} />
                                <Route path="*" component={NotFound} />
                            </Switch>
                        </section>
                    </div>
                </Router>
            )
        }
    }
    
    export default App;
    

    Now that we've implemented code splitting, let's do some analysis and see how the app bundles the JavaScript code now. Run the command npm run build in your terminal to build the app for production.

    现在,我们已经实现了代码拆分,让我们做一些分析,看看应用程序现在如何捆绑JavaScript代码。 在终端中运行命令npm run build来构建用于生产的应用程序。

    As you can see from above, our code is now being separated into different chunks, and the React will now load only the component needed for a path as opposed to loading everything. That's the power of code splitting.

    从上面可以看到,我们的代码现在被分成不同的块,并且React现在将仅加载路径所需的组件,而不是加载所有组件。 这就是代码拆分的力量。

    结论 ( Conclusion )

    In this tutorial, we saw how to avoid code bloat by implementing code splitting. We wrote code to dynamically import components and only load them when needed.

    在本教程中,我们了解了如何通过实现代码拆分来避免代码膨胀。 我们编写了代码来动态导入组件,并仅在需要时加载它们。

    I should mention that if you'd want to avoid all this hassle, you can use the react-loadable plugin. It is a higher order component that takes care of loading components with promises.

    我应该提到,如果您想避免所有这些麻烦,可以使用react-loadable插件。 它是一个高阶组件,负责按需加载组件。

    If you'd like to see the complete React app and play around with it, you can check it out here.

    如果您想查看完整的React应用并进行试用,可以在此处查看

    翻译自: https://scotch.io/tutorials/lazy-loading-routes-in-react

    react 元素延迟加载

    展开全文
  • 人类历史的超级工程有什么?金字塔、万里长城、迪拜世界岛、三峡大坝……其实,我们还有个很特别的超级工程:人们看不到它的形态,却无时无刻不在感知它的存在。天南海北的货物因它向世界各地集散,各行各业因它...

    人类历史上的超级工程有什么?金字塔、万里长城、迪拜世界岛、三峡大坝……

    其实,我们还有个很特别的超级工程:人们看不到它的形态,却无时无刻不在感知它的存在。天南海北的货物因它向世界各地集散,各行各业因它联成一体,这就是双11,一个属于互联网技术的超级工程。

    图片描述

    不夸张地说,最近十年阿里的重大技术演进,几乎都是被双11逼出来的。从第一个双11的错误丛生,到最近一年“丝般顺滑”的用户购物体验,凝结的是阿里技术九年来的不断进步。

    究竟阿里工程师是如何做到的?

    这次,阿里工程师将至今以来的经验与心得,凝聚在《九年双11:互联网技术超级工程》,首次对外免费公开。近300页、毫无保留的分享,为你展示阿里技术的崭新面貌与实力。

    图片描述

    为什么这本书值得反复阅读?

    2017天猫双11,史上最大规模的人机协同、前沿的智能技术保障、强大的互联网基础设施,创造了交易创建峰值 32.5 万笔/秒,支付成功峰值 25.6 万笔/秒的历史记录。这是人类商业史上最大规模的一次协同,也是互联网史上最大规模的一次流量并发。

    图片描述

    125个国家和地区、超过上亿的用户同时在这一天相互联系在一起。人类商业史上可能还从来没有过在同一天,那么多的人,与商家、物流、银行、海关等各个环节发生的联动,背后支撑这些的是阿里九年来的技术进步。

    从这本书中,你可以看到,数据智能、机器智能如何融入到阿里系统的每一个方面,整个商品推荐、整个交易链路、包括整个决策都是用机器智能来实现。这些经历实践检验的宝贵经验沉淀,相信定会对你的工作有所启发。

    此书有哪些重点需要关注的内容?

    这本书分为三大板块——“新智能”“新基础”“新体验”,下面我们一起来梳理各个板块的重点内容:

    图片描述

    书籍部分目录

    2017年双11,实现了人类科技史上大规模的一次机器智能应用,比如:

    机器智能推荐系统,如何生成个性化页面,实现人见人爱的“千人千面”?

    阿里小蜜在过去一年如何蜕变,承担海量客服咨询量,带来传统服务行业模式的变化与体验提升?

    智能写手问世,如何生成上亿个不同个性的短文案,以及图文型清单?

    此书也为你讲述了双11背后不为人知的强大基础保障技术。比如:

    阿里基础网络,具备自愈能力,实时故障发现和收敛,并自动恢复,全面保障双11业务稳定运行;

    混部技术,基于全网pouch容器化和Sigma调度技术,双11期间混部集群资源利用率大幅提升,IT资产新增成本显著削减;

    基础中间件,掌控超千亿规模消息推送和分布式数据库调用,支撑双11洪峰消息;

    实时计算平台Blink,支撑阿里绝大部分实时计算任务,双11当天毫秒级延迟。

    图片描述

    此外,这本书同样展示了新体验背后的“黑科技”,比如:

    区块链技术在天猫国际商品溯源中的应用

    如何把范冰冰“送”到你家?双 11 晚会神秘技术公开

    直击Weex在优酷双11猫晚直播的应用

    双十一安全技术:目标检测在淘宝直播中的应用

    阿里新供应链体系架构及功能解读

    9年时间,足够一个人从象牙塔学生蜕变为技术骨干,从单纯懵懂走向成熟稳重,从毛头小子变成家庭支柱。阿里技术也伴随着双11成长起来,强壮起来,自信起来,成为互联网技术史上实至名归的超级工程。

    希望这本书,能为你打开一扇窗户,去看更大的世界;成为一个小支点,帮你撬动更大的进步。

    在技术创新的道路上,让我们一路同行,在一起、战一起。

    《九年双11:互联网技术超级工程》免费下载:

    http://techforum-img.cn-hangzhou.oss-pub.aliyun-inc.com/1516614343703/AliDouble11.pdf

    温馨提醒:

    1、本书约25M,需要一定下载时间,请耐心等待哦。

    2、流量不足的童鞋,建议将地址复制到PC端浏览器后打开下载。

    此外,阿里技术的菜单栏里还藏着不少技术电子好书,等待你去发现~ 在技术成长的路上,我们一起遇见更好的自己。

    展开全文
  • 作者:Kaito,90 后,坐标北京,6 年+工作经验,就职于一家移动互联网公司,目前从事基础架构和数据库中间件研发。 Redis 介绍 Redis作为内存数据库,拥有非常高的性能,单个实例的QPS能够达到10W左右。但我们在使用...

    来源:http://kaito-kidd.com/
    作者:Kaito,90 后,坐标北京,6 年+工作经验,就职于一家移动互联网公司,目前从事基础架构和数据库中间件研发。

    Redis 介绍

    Redis作为内存数据库,拥有非常高的性能,单个实例的QPS能够达到10W左右。但我们在使用Redis时,经常时不时会出现访问延迟很大的情况,如果你不知道Redis的内部实现原理,在排查问题时就会一头雾水。

    很多时候,Redis出现访问延迟变大,都与我们的使用不当或运维不合理导致的。

    Redis变慢了?常见延迟问题定位与分析

    下面我们就来分析一下Redis在使用过程中,经常会遇到的延迟问题以及如何定位和分析。

    使用复杂度高的命令

    如果在使用Redis时,发现访问延迟突然增大,如何进行排查?

    首先,第一步,建议你去查看一下Redis的慢日志。Redis提供了慢日志命令的统计功能,我们通过以下设置,就可以查看有哪些命令在执行时延迟比较大。

    首先设置Redis的慢日志阈值,只有超过阈值的命令才会被记录,这里的单位是微妙,例如设置慢日志的阈值为5毫秒,同时设置只保留最近1000条慢日志记录:

    # 命令执行超过5毫秒记录慢日志
    CONFIG SET slowlog-log-slower-than 5000
    
    # 只保留最近1000条慢日志
    CONFIG SET slowlog-max-len 1000
    

    设置完成之后,所有执行的命令如果延迟大于5毫秒,都会被Redis记录下来,我们执行SLOWLOG get 5查询最近5条慢日志:

    127.0.0.1:6379> SLOWLOG get 5
    
    1) 1) (integer) 32693       # 慢日志ID
    
       2) (integer) 1593763337  # 执行时间
    
       3) (integer) 5299        # 执行耗时(微妙)
    
       4) 1) "LRANGE"           # 具体执行的命令和参数
    
          2) "user_list_2000"
    
          3) "0"
    
          4) "-1"
    
    2) 1) (integer) 32692
    
       2) (integer) 1593763337
    
       3) (integer) 5044
    
       4) 1) "GET"
    
          2) "book_price_1000"
    

    通过查看慢日志记录,我们就可以知道在什么时间执行哪些命令比较耗时,如果你的业务经常使用O(n)以上复杂度的命令,例如sort、sunion、zunionstore,或者在执行O(n)命令时操作的数据量比较大,这些情况下Redis处理数据时就会很耗时。

    如果你的服务请求量并不大,但Redis实例的CPU使用率很高,很有可能是使用了复杂度高的命令导致的。

    解决方案就是,不使用这些复杂度较高的命令,并且一次不要获取太多的数据,每次尽量操作少量的数据,让Redis可以及时处理返回。

    存储大key

    如果查询慢日志发现,并不是复杂度较高的命令导致的,例如都是SET、DELETE操作出现在慢日志记录中,那么你就要怀疑是否存在Redis写入了大key的情况。

    Redis在写入数据时,需要为新的数据分配内存,当从Redis中删除数据时,它会释放对应的内存空间。

    如果一个key写入的数据非常大,Redis在分配内存时也会比较耗时。同样的,当删除这个key的数据时,释放内存也会耗时比较久。

    你需要检查你的业务代码,是否存在写入大key的情况,需要评估写入数据量的大小,业务层应该避免一个key存入过大的数据量。

    那么有没有什么办法可以扫描现在Redis中是否存在大key的数据吗?

    Redis也提供了扫描大key的方法:

    redis-cli -h $host -p $port --bigkeys -i 0.01
    

    使用上面的命令就可以扫描出整个实例key大小的分布情况,它是以类型维度来展示的。

    需要注意的是当我们在线上实例进行大key扫描时,Redis的QPS会突增,为了降低扫描过程中对Redis的影响,我们需要控制扫描的频率,使用-i参数控制即可,它表示扫描过程中每次扫描的时间间隔,单位是秒。

    使用这个命令的原理,其实就是Redis在内部执行scan命令,遍历所有key,然后针对不同类型的key执行strlen、llen、hlen、scard、zcard来获取字符串的长度以及容器类型(list/dict/set/zset)的元素个数。

    而对于容器类型的key,只能扫描出元素最多的key,但元素最多的key不一定占用内存最多,这一点需要我们注意下。不过使用这个命令一般我们是可以对整个实例中key的分布情况有比较清晰的了解。

    针对大key的问题,Redis官方在4.0版本推出了lazy-free的机制,用于异步释放大key的内存,降低对Redis性能的影响。即使这样,我们也不建议使用大key,大key在集群的迁移过程中,也会影响到迁移的性能,这个后面在介绍集群相关的文章时,会再详细介绍到。

    集中过期

    有时你会发现,平时在使用Redis时没有延时比较大的情况,但在某个时间点突然出现一波延时,而且报慢的时间点很有规律,例如某个整点,或者间隔多久就会发生一次。

    如果出现这种情况,就需要考虑是否存在大量key集中过期的情况。

    如果有大量的key在某个固定时间点集中过期,在这个时间点访问Redis时,就有可能导致延迟增加。

    Redis的过期策略采用主动过期+懒惰过期两种策略:

    • 主动过期:Redis内部维护一个定时任务,默认每隔100毫秒会从过期字典中随机取出20个key,删除过期的key,如果过期key的比例超过了25%,则继续获取20个key,删除过期的key,循环往复,直到过期key的比例下降到25%或者这次任务的执行耗时超过了25毫秒,才会退出循环;
    • 懒惰过期:只有当访问某个key时,才判断这个key是否已过期,如果已经过期,则从实例中删除。

    注意,Redis的主动过期的定时任务,也是在Redis主线程中执行的,也就是说如果在执行主动过期的过程中,出现了需要大量删除过期key的情况,那么在业务访问时,必须等这个过期任务执行结束,才可以处理业务请求。此时就会出现,业务访问延时增大的问题,最大延迟为25毫秒。

    而且这个访问延迟的情况,不会记录在慢日志里。慢日志中只记录真正执行某个命令的耗时,Redis主动过期策略执行在操作命令之前,如果操作命令耗时达不到慢日志阈值,它是不会计算在慢日志统计中的,但我们的业务却感到了延迟增大。

    此时你需要检查你的业务,是否真的存在集中过期的代码,一般集中过期使用的命令是expireat或pexpireat命令,在代码中搜索这个关键字就可以了。

    如果你的业务确实需要集中过期掉某些key,又不想导致Redis发生抖动,有什么优化方案?

    解决方案是,在集中过期时增加一个随机时间,把这些需要过期的key的时间打散即可。

    伪代码可以这么写:

    # 在过期时间点之后的5分钟内随机过期掉
    
    redis.expireat(key, expire_time + random(300))
    

    这样Redis在处理过期时,不会因为集中删除key导致压力过大,阻塞主线程。

    另外,除了业务使用需要注意此问题之外,还可以通过运维手段来及时发现这种情况。

    做法是我们需要把Redis的各项运行数据监控起来,执行info可以拿到所有的运行数据,在这里我们需要重点关注expired_keys这一项,它代表整个实例到目前为止,累计删除过期key的数量。

    我们需要对这个指标监控,当在很短时间内这个指标出现突增时,需要及时报警出来,然后与业务报慢的时间点对比分析,确认时间是否一致,如果一致,则可以认为确实是因为这个原因导致的延迟增大。

    实例内存达到上限

    有时我们把Redis当做纯缓存使用,就会给实例设置一个内存上限maxmemory,然后开启LRU淘汰策略。

    当实例的内存达到了maxmemory后,你会发现之后的每次写入新的数据,有可能变慢了。

    导致变慢的原因是,当Redis内存达到maxmemory后,每次写入新的数据之前,必须先踢出一部分数据,让内存维持在maxmemory之下。

    这个踢出旧数据的逻辑也是需要消耗时间的,而具体耗时的长短,要取决于配置的淘汰策略:
    allkeys-lru:不管key是否设置了过期,淘汰最近最少访问的key;
    volatile-lru:只淘汰最近最少访问并设置过期的key;
    allkeys-random:不管key是否设置了过期,随机淘汰;
    volatile-random:只随机淘汰有设置过期的key;
    allkeys-ttl:不管key是否设置了过期,淘汰即将过期的key;
    noeviction:不淘汰任何key,满容后再写入直接报错;
    allkeys-lfu:不管key是否设置了过期,淘汰访问频率最低的key(4.0+支持);
    volatile-lfu:只淘汰访问频率最低的过期key(4.0+支持)。

    具体使用哪种策略,需要根据业务场景来决定。

    我们最常使用的一般是allkeys-lru或volatile-lru策略,它们的处理逻辑是,每次从实例中随机取出一批key(可配置),然后淘汰一个最少访问的key,之后把剩下的key暂存到一个池子中,继续随机取出一批key,并与之前池子中的key比较,再淘汰一个最少访问的key。以此循环,直到内存降到maxmemory之下。

    如果使用的是allkeys-random或volatile-random策略,那么就会快很多,因为是随机淘汰,那么就少了比较key访问频率时间的消耗了,随机拿出一批key后直接淘汰即可,因此这个策略要比上面的LRU策略执行快一些。

    但以上这些逻辑都是在访问Redis时,真正命令执行之前执行的,也就是它会影响我们访问Redis时执行的命令。

    另外,如果此时Redis实例中有存储大key,那么在淘汰大key释放内存时,这个耗时会更加久,延迟更大,这需要我们格外注意。

    如果你的业务访问量非常大,并且必须设置maxmemory限制实例的内存上限,同时面临淘汰key导致延迟增大的的情况,要想缓解这种情况,除了上面说的避免存储大key、使用随机淘汰策略之外,也可以考虑拆分实例的方法来缓解,拆分实例可以把一个实例淘汰key的压力分摊到多个实例上,可以在一定程度降低延迟。

    fork耗时严重

    如果你的Redis开启了自动生成RDB和AOF重写功能,那么有可能在后台生成RDB和AOF重写时导致Redis的访问延迟增大,而等这些任务执行完毕后,延迟情况消失。

    遇到这种情况,一般就是执行生成RDB和AOF重写任务导致的。

    生成RDB和AOF都需要父进程fork出一个子进程进行数据的持久化,在fork执行过程中,父进程需要拷贝内存页表给子进程,如果整个实例内存占用很大,那么需要拷贝的内存页表会比较耗时,此过程会消耗大量的CPU资源,在完成fork之前,整个实例会被阻塞住,无法处理任何请求,如果此时CPU资源紧张,那么fork的时间会更长,甚至达到秒级。这会严重影响Redis的性能。

    具体原理也可以参考我之前写的文章:Redis持久化是如何做的?RDB和AOF对比分析。

    我们可以执行info命令,查看最后一次fork执行的耗时latest_fork_usec,单位微妙。这个时间就是整个实例阻塞无法处理请求的时间。

    除了因为备份的原因生成RDB之外,在主从节点第一次建立数据同步时,主节点也会生成RDB文件给从节点进行一次全量同步,这时也会对Redis产生性能影响。

    要想避免这种情况,我们需要规划好数据备份的周期,建议在从节点上执行备份,而且最好放在低峰期执行。如果对于丢失数据不敏感的业务,那么不建议开启AOF和AOF重写功能。

    另外,fork的耗时也与系统有关,如果把Redis部署在虚拟机上,那么这个时间也会增大。所以使用Redis时建议部署在物理机上,降低fork的影响。

    绑定CPU

    很多时候,我们在部署服务时,为了提高性能,降低程序在使用多个CPU时上下文切换的性能损耗,一般会采用进程绑定CPU的操作。

    但在使用Redis时,我们不建议这么干,原因如下。

    绑定CPU的Redis,在进行数据持久化时,fork出的子进程,子进程会继承父进程的CPU使用偏好,而此时子进程会消耗大量的CPU资源进行数据持久化,子进程会与主进程发生CPU争抢,这也会导致主进程的CPU资源不足访问延迟增大。

    所以在部署Redis进程时,如果需要开启RDB和AOF重写机制,一定不能进行CPU绑定操作!

    开启AOF

    上面提到了,当执行AOF文件重写时会因为fork执行耗时导致Redis延迟增大,除了这个之外,如果开启AOF机制,设置的策略不合理,也会导致性能问题。

    开启AOF后,Redis会把写入的命令实时写入到文件中,但写入文件的过程是先写入内存,等内存中的数据超过一定阈值或达到一定时间后,内存中的内容才会被真正写入到磁盘中。

    AOF为了保证文件写入磁盘的安全性,提供了3种刷盘机制:

    • appendfsync always:每次写入都刷盘,对性能影响最大,占用磁盘IO比较高,数据安全性最高;
    • appendfsync everysec:1秒刷一次盘,对性能影响相对较小,节点宕机时最多丢失1秒的数据;
    • appendfsync no:按照操作系统的机制刷盘,对性能影响最小,数据安全性低,节点宕机丢失数据取决于操作系统刷盘机制。
      当使用第一种机制appendfsync always时,Redis每处理一次写命令,都会把这个命令写入磁盘,而且这个操作是在主线程中执行的。

    内存中的的数据写入磁盘,这个会加重磁盘的IO负担,操作磁盘成本要比操作内存的代价大得多。如果写入量很大,那么每次更新都会写入磁盘,此时机器的磁盘IO就会非常高,拖慢Redis的性能,因此我们不建议使用这种机制。

    与第一种机制对比,appendfsync everysec会每隔1秒刷盘,而appendfsync no取决于操作系统的刷盘时间,安全性不高。因此我们推荐使用appendfsync everysec这种方式,在最坏的情况下,只会丢失1秒的数据,但它能保持较好的访问性能。

    当然,对于有些业务场景,对丢失数据并不敏感,也可以不开启AOF。

    使用Swap

    如果你发现Redis突然变得非常慢,每次访问的耗时都达到了几百毫秒甚至秒级,那此时就检查Redis是否使用到了Swap,这种情况下Redis基本上已经无法提供高性能的服务。

    我们知道,操作系统提供了Swap机制,目的是为了当内存不足时,可以把一部分内存中的数据换到磁盘上,以达到对内存使用的缓冲。

    但当内存中的数据被换到磁盘上后,访问这些数据就需要从磁盘中读取,这个速度要比内存慢太多!

    尤其是针对Redis这种高性能的内存数据库来说,如果Redis中的内存被换到磁盘上,对于Redis这种性能极其敏感的数据库,这个操作时间是无法接受的。

    我们需要检查机器的内存使用情况,确认是否确实是因为内存不足导致使用到了Swap。

    如果确实使用到了Swap,要及时整理内存空间,释放出足够的内存供Redis使用,然后释放Redis的Swap,让Redis重新使用内存。

    释放Redis的Swap过程通常要重启实例,为了避免重启实例对业务的影响,一般先进行主从切换,然后释放旧主节点的Swap,重新启动服务,待数据同步完成后,再切换回主节点即可。

    可见,当Redis使用到Swap后,此时的Redis的高性能基本被废掉,所以我们需要提前预防这种情况。

    我们需要对Redis机器的内存和Swap使用情况进行监控,在内存不足和使用到Swap时及时报警出来,及时进行相应的处理。

    网卡负载过高

    如果以上产生性能问题的场景,你都规避掉了,而且Redis也稳定运行了很长时间,但在某个时间点之后开始,访问Redis开始变慢了,而且一直持续到现在,这种情况是什么原因导致的?

    之前我们就遇到这种问题,特点就是从某个时间点之后就开始变慢,并且一直持续。这时你需要检查一下机器的网卡流量,是否存在网卡流量被跑满的情况。

    网卡负载过高,在网络层和TCP层就会出现数据发送延迟、数据丢包等情况。Redis的高性能除了内存之外,就在于网络IO,请求量突增会导致网卡负载变高。

    如果出现这种情况,你需要排查这个机器上的哪个Redis实例的流量过大占满了网络带宽,然后确认流量突增是否属于业务正常情况,如果属于那就需要及时扩容或迁移实例,避免这个机器的其他实例受到影响。

    运维层面,我们需要对机器的各项指标增加监控,包括网络流量,在达到阈值时提前报警,及时与业务确认并扩容。

    以上我们总结了Redis中常见的可能导致延迟增大甚至阻塞的场景,这其中既涉及到了业务的使用问题,也涉及到Redis的运维问题。

    可见,要想保证Redis高性能的运行,其中涉及到CPU、内存、网络,甚至磁盘的方方面面,其中还包括操作系统的相关特性的使用。

    作为开发人员,我们需要了解Redis的运行机制,例如各个命令的执行时间复杂度、数据过期策略、数据淘汰策略等,使用合理的命令,并结合业务场景进行优化。

    作为DBA运维人员,需要了解数据持久化、操作系统fork原理、Swap机制等,并对Redis的容量进行合理规划,预留足够的机器资源,对机器做好完善的监控,才能保证Redis的稳定运行。

    Redis的最佳实践方式:业务层面和运维层面

    在上文中,主要讲解了 Redis 常见的导致变慢的场景以及问题定位和分析,主要是由业务使用不合理和运维不当导致的。

    我们在了解了导致Redis变慢的原因之后,针对性地优化,就可以让Redis稳定发挥出更高性能。

    接着就来总结一下,在使用Redis时的最佳实践方式,主要包含两个层面:业务层面、运维层面。

    由于我之前写过很多UGC后端服务,在大量场景下用到了Redis,这个过程中也踩过很多坑,所以在使用过程中也总结了一套合理的使用方法。

    后来做基础架构,开发Codis、Redis相关的中间件,在这个阶段关注领域从使用层面下沉到Redis的开发和运维,更多聚焦在Redis的内部实现和运维过程中产生的各种问题,在这块也积累了一些经验。

    下面就针对这两块,分享一下我认为比较合理的Redis使用和运维方法,不一定最全面,也可能与你使用Redis的方法不同,但以下这些方法都是我在踩坑之后总结的实际经验,供你参考。

    业务层面

    业务层面主要是开发人员需要关注,也就是开发人员在写业务代码时,如何合理地使用Redis。开发人员需要对Redis有基本的了解,才能在合适的业务场景使用Redis,从而避免业务层面导致的延迟问题。

    在开发过程中,业务层面的优化建议如下:

    • key的长度尽量要短,在数据量非常大时,过长的key名会占用更多的内存;
    • 一定避免存储过大的数据(大value),过大的数据在分配内存和释放内存时耗时严重,会阻塞主线程;
    • Redis 4.0以上建议开启lazy-free机制,释放大value时异步操作,不阻塞主线程;
      建议设置过期时间,把Redis当做缓存使用,尤其在数量很大的时,不设置过期时间会导致内存的无限增长;
    • 不使用复杂度过高的命令,例如SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE,使用这些命令耗时较久,会阻塞主线程;
    • 查询数据时,一次尽量获取较少的数据,在不确定容器元素个数的情况下,避免使用LRANGE key 0 -1,ZRANGE key 0
      -1这类操作,应该设置具体查询的元素个数,推荐一次查询100个以下元素;
    • 写入数据时,一次尽量写入较少的数据,例如HSET key value1 value2
      value3…,控制一次写入元素的数量,推荐在100以下,大数据量分多个批次写入;
    • 批量操作数据时,用MGET/MSET替换GET/SET、HMGET/MHSET替换HGET/HSET,减少请求来回的网络IO次数,降低延迟,对于没有批量操作的命令,推荐使用pipeline,一次性发送多个命令到服务端;
    • 禁止使用KEYS命令,需要扫描实例时,建议使用SCAN,线上操作一定要控制扫描的频率,避免对Redis产生性能抖动
    • 避免某个时间点集中过期大量的key,集中过期时推荐增加一个随机时间,把过期时间打散,降低集中过期key时Redis的压力,避免阻塞主线程;
    • 根据业务场景,选择合适的淘汰策略,通常随机过期要比LRU过期淘汰数据更快;
    • 使用连接池访问Redis,并配置合理的连接池参数,避免短连接,TCP三次握手和四次挥手的耗时也很高;
    • 只使用db0,不推荐使用多个db,使用多个db会增加Redis的负担,每次访问不同的db都需要执行SELECT命令,如果业务线不同,建议拆分多个实例,还能提高单个实例的性能;
    • 读的请求量很大时,推荐使用读写分离,前提是可以容忍从节数据更新不及时的问题;
    • 写请求量很大时,推荐使用集群,部署多个实例分摊写压力。

    运维层面

    运维层面主要是DBA需要关注的,目的是合理规划Redis的部署和保障Redis的稳定运行,主要优化如下:

    • 不同业务线部署不同的实例,各自独立,避免混用,推荐不同业务线使用不同的机器,根据业务重要程度划分不同的分组来部署,避免某一个业务线出现问题影响其他业务线;
    • 保证机器有足够的CPU、内存、带宽、磁盘资源,防止负载过高影响Redis性能;
      以master-slave集群方式部署实例,并分布在不同机器上,避免单点,slave必须设置为readonly;
      master和slave节点所在机器,各自独立,不要交叉部署实例,通常备份工作会在slave上做,做备份时会消耗机器资源,交叉部署会影响到master的性能;
    • 推荐部署哨兵节点增加可用性,节点数量至少3个,并分布在不同机器上,实现故障自动故障转移;
    • 提前做好容量规划,一台机器部署实例的内存上限,最好是机器内存的一半,主从全量同步时会占用最多额外一倍的内存空间,防止网络大面积故障引发所有master-slave的全量同步导致机器内存被吃光;
    • 做好机器的CPU、内存、带宽、磁盘监控,在资源不足时及时报警处理,Redis使用Swap后性能急剧下降,网络带宽负载过高访问延迟明显增大,磁盘IO过高时开启AOF会拖慢Redis的性能;
    • 设置最大连接数上限,防止过多的客户端连接导致服务负载过高;
    • 单个实例的使用内存建议控制在10G以下,过大的实例会导致备份时间久、资源消耗多,主从全量同步数据时间阻塞时间更长;
    • 设置合理的slowlog阈值,推荐10毫秒,并对其进行监控,产生过多的慢日志需要及时报警;
    • 设置合理的复制缓冲区repl-backlog大小,适当调大repl-backlog可以降低主从全量复制的概率;
    • 设置合理的slave节点client-output-buffer-limit大小,对于写入量很大的实例,适当调大可以避免主从复制中断问题;
    • 备份时推荐在slave节点上做,不影响master性能;
    • 不开启AOF或开启AOF配置为每秒刷盘,避免磁盘IO消耗降低Redis性能;
    • 当实例设置了内存上限,需要调大内存上限时,先调整slave再调整master,否则会导致主从节点数据不一致;
    • 对Redis增加监控,监控采集info信息时,使用长连接,频繁的短连接也会影响Redis性能;
    • 线上扫描整个实例数时,记得设置休眠时间,避免扫描时QPS突增对Redis产生性能抖动;
    • 做好Redis的运行时监控,尤其是expired_keys、evicted_keys、latest_fork_usec指标,短时间内这些指标值突增可能会阻塞整个实例,引发性能问题。

    以上就是我在使用Redis和开发Redis相关中间件时,总结出来Redis推荐的实践方法,以上提出的这些方面,都或多或少在实际使用中遇到过。

    可见,要想稳定发挥Redis的高性能,需要在各个方面做好工作,但凡某一个方面出现问题,必然会影响到Redis的性能,这对我们使用和运维提出了更高的要求。

    展开全文
  • 2018半年互联网DDoS攻击趋势分析

    千次阅读 2018-06-13 12:02:07
    2018年半年DDoS攻防仍如火如荼发展,以IoT设备为反射点的SSDP反射放大尚未平息,Memcached DDoS又异军突起,以最高可达5万的反射放大倍数、峰值可达1.7Tbps的攻击流量成为安全界关注的新焦点。DDoS这一互联网公敌...

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~

    2018年上半年DDoS攻防仍如火如荼发展,以IoT设备为反射点的SSDP反射放大尚未平息,Memcached DDoS又异军突起,以最高可达5万的反射放大倍数、峰值可达1.7Tbps的攻击流量成为安全界关注的新焦点。DDoS这一互联网公敌,在各种防御设备围追堵截的情况下,攻击者夜以继日地钻研对抗方法、研究新的攻击方式;而且往平台化、自动化的方向发展,不断增强攻击能力。这里我们从2018年上半年DDoS攻击情况的全局统计、DDoS黑色产业链条中的人员分工与自动化操作演进两个方面进行分析阐述。

    一、全局统计分析

    1. 2013~2018年DDoS流量峰值情况

    DDoS攻击流量峰值每年都不断地被超越,今年3月份针对某游戏攻击的Memcached DDoS,其峰值1.7Tbps达到了一个新的高度。虽然已经关闭了大量的Memcached的UDP端口,但其5万的反射放大倍数,仍使攻击者可利用少量未关停UDP端口的Memcached反射点,打出大流量攻击。所以短短的三个月,Memcached DDoS已成为反射放大的一股主要力量。

    img

    2. DDoS攻击行业分类情况

    随着各行各业的互联网化,DDoS的攻击面也越来越多,这里我们列出了14种主要行业。游戏行业因其日流水量最大、变现快,一直站在利益的风口浪尖上,当仁不让地成为DDoS首选的攻击目标,也是上半年各行业中遭受攻击最多的行业。值得关注的是在医疗、物联网、教育等传统行业互联网化后,也遭受到了不同程度的攻击,且呈上升的趋势。

    在游戏行业当中,手机游戏已超过了PC客户端游戏成为了DDoS攻击的主要目标。H5游戏的崛起,也成为了DDoS的关注点,占整体攻击的1.4%。这里我们首次把游戏的第三方服务归结到游戏中,游戏的飞速发展催生了大量的第三方服务商,包括但不限于游戏虚拟财产买卖平台、数据分析、电竞、美术/音乐外包、游戏云服务、游戏资讯等各个环节。

    img

    3. DDoS攻击的类型占比统计

    在攻击类型中,反射放大占比最多,约为55.8%。Memcached作为今年三月以来的新兴反射放大力量,迅速被DDoS黑产界利用,其在整体的占比中也相当大。反射放大占比如此之多的一个原因是DDoS黑产的自动平台化,即无需人工干预,完全自动流程可完成攻击的所有操作。

    SYN Flood 排名第二,其一直是DDoS的主要攻击手法。随着DDoS黑产的平台化,SYN Flood的载体也发生了改变,由海量的肉鸡渐渐转移到了发包机上(以伪造源IP的SYN Flood为主)。

    HTTP Flood作为7层攻击的主要方式,因为要建立完整的TCP连接,不能够伪造源IP,所以还是以肉鸡侧发动攻击为主。但云鼎实验室监测发现,HTTP Flood也开始向代理服务器和发包机发展。在互联网上获取海量的代理服务器相比肉鸡的抓取容易很多,以代理服务器频繁地变换真实的IP,再加上交互的模拟,可以使HTTP Flood很难被发现。而发包机的方式,虽不能变换IP,但可以频繁变换UserAgent的内容,以突破针对HTTP Flood的防御。

    img

    下图给出了几种反射放大的反射源地域分布情况,从抽样数据统计可见,LDAP、NTP、Memchached为主的反射源Top 10的国家重合度很高,以美国、中国、欧洲国家为主。SSDP反射源因IoT设备的问题,导致其地域分布有所不同。

    img

    4. DDoS所对应的C2地域分布

    近年来国内的互联网安全措施持续加强,通过监控发现,在国内的C2渐渐有外迁的现象。还有一些持有高性能肉鸡的黑客,看到了虚拟货币的逐利远远大于DDoS攻击,将一部分高性能肉鸡转去挖矿。鉴于以上原因针对用于DDoS的C2监控难度越来越大。

    img

    5. 家族情况

    通过对攻击家族的监控,主要以Xorddos,Billgates,Mayday,Dofloo,Nitol,Darkshell等家族为主。Xorddos是发起攻击最多的家族,甚至每天多达上万次的攻击;攻击类型多以SYN Flood为主、其他攻击类型为辅的组合攻击。Nitol家族是发起HTTP Flood攻击最多的家族,还会输出SYNFlood, ICMP Flood, TCP Flood等攻击。以上家族攻击的统计中,针对各个行业的攻击都有涉猎,游戏行业无疑是攻击的首选。

    6. 被攻击IP的地域情况

    DDoS攻击目标按地域分布统计中,国外受攻击最多的国家是美国,其次是韩国、欧洲国家为主,DDoS攻击的主要目标还是聚集在互联网发达的国家中。

    img

    在国内各省的统计来看,受到DDoS攻击较多的省份在长三角、珠三角和京津片区,即中国的互联网发达省份,其中以江浙两省最多。

    img

    7. 每月百G以上攻击流量情况

    以每月超过百Gbps的攻击次数统计来看,百Gbps流量分层占比相差不多。100-200Gbps占比最大,基本都在75%以上,而超过300Gbps的流量攻击次数较少。

    img

    8. 攻击流量带宽分布情况

    在攻击流量的分层统计上,1-5G的攻击次数最多,占比约38%。通过统计可得到,大多数的攻击为100Gbps以下的流量攻击,超过百G的攻击累计占总攻击次数不到5%。整体的攻击流量的平均峰值约在5.2Gbps左右。

    img

    9. 攻击时长分布占比情况

    在攻击时长来看,占比最多是1min以下的攻击,约占38.7%。其主要攻击方式是瞬时攻击,以极大的流量直接瘫痪掉攻击的服务,导致大量用户掉线、延迟、抖动等。5-10min也占相当大比例,约28.7%。抽样统计得出,平均攻击时长约1h,单次攻击最长时长约54天。

    img

    二、DDoS黑色产业链条演进

    我们从黑产中的人员分工与自动化操作两个方面进行DDoS发展的阐述。

    1. 传统DDoS攻击

    早期的DDoS一般是黑客一个人的游戏,从工具开发、bot传播、接单、攻击等都独自完成。随着互联网经济的飞速发展,网络攻击获利越来越多,催生了DDoS攻击的大量需求,例如竞品的攻击、DDoS勒索等。高额的利益便会催生对应工作的精细化分工,DDoS的黑产也不例外,我们针对传统DDoS攻击的专业化人员分工进行分析:

    • 发单人:也可以称为金主,是DDoS攻击后的直接获利者,提出攻击需求。
    • 担保商:也可以称为中间人,是DDoS黑产中较出名的人物,在各个不同分工人员间做“信任”担保,与交易环节的资金中转工作。担保商也会自己架设接发单平台或即时通讯工具群等形式来扩大自己的知名度,带来更多的DDoS攻击业务。
    • 接单人:也可以称为攻击手,通过操作C2服务器或发包机直接发起DDoS攻击。
    • 流量商:通过担保商或直接将国外购买的流量服务器,售卖给攻击手。
    • 肉鸡商:手头拥有大量的肉鸡资源,通过担保商或直接将肉鸡售卖/出租给攻击手。
    • 黑客软件作者:开发botnet程序,反射放大程序等各种DDoS工具。

    这样的多种分工,使DDoS在技术难度上被拆解,技术门槛降低,部署更容易。同时给互联网安全人员的分析与溯源带来更大的困难。在分析中我们发现,有一些人员也可能同时担当多个角色。

    虽然这种比较早期的DDoS攻击分工已十分成熟,但还是存在一定的不足之处:

    • 成单难以保障:担保商、接单人都具有不确定性,发单人付费后,可能会存在针对目标的攻击没有效果或根本没有发起攻击的情况,给发单人造成经济损失。
    • 响应周期较长:从发单人提出需求到真正达到攻击效果,需要发单人、担保商(或其搭建的各种对接平台/即时通讯工具群等)、接单人等几个环节,时间上需要几小时到几天不等。
    • 攻击效果不能保证:攻击手一般手动远程操作C2服务器或发包机针对目标服务器进行攻击,攻击手所掌握的botnet或发包机规模不同,攻击的流量达不到保证。

    img

    2. 目前DDoS攻击

    鉴于传统DDoS攻击的不足,促使了DDoS的多个环节的自动化发展,页端DDoS攻击平台便是发展的结果之一。其高度集成管理,在成单率、响应时长、攻击效果方面都得到了可行的解决。在人员分工上,有了新的发展:

    担保商淡出DDoS黑产圈,发单人可直接在页端DDoS攻击平台下单、支付费用;且可以根据自己的攻击目标的情况选择攻击方式与流量大小。保障了百分之百的成单率。

    攻击手已被自动化的攻击平台取代,不需要手动操作攻击。从发起攻击命令,到真正开始攻击,一般延时在10s左右,再也不用等几小时或几天了。

    发包机提供人替代了流量商角色,且完成发包机的程序部署,测试,最终给出发包机的攻击类型、稳定流量、峰值流量等各种定量且稳定的攻击能力。稳定的攻击流量,保障了最终的攻击效果。

    站长成为了页端DDoS攻击平台的核心人员,进行平台的综合管理、部署、运维工作。例如:DDoS攻击套餐管理、注册用户(金主)管理、攻击效果与流量稳定保障、及后续的升级等。

    img

    不同的页端DDoS攻击平台也有不同的实现,但其操作流程、核心攻能都很相似,下图给出了其技术的解读。从此图中可见,在用户注册、套餐付费、攻击发起,在用户侧都可以完成,不需要其他人员参与。相对比传统DDoS攻击来看,已完成了全自动的无人值守攻击方式。在图中的调用传统肉鸡的攻击形式很少,主要是调用发包机的攻击方式。发包机中主要配置的是反射放大的各种程序和其对应的反射源列表,偶尔会有伪造源IP的SYN Flood、动态变化UserAgent的HTTP Flood (如goldeneye工具)。

    img

    三、总结与趋势展望

    综上所述,上半年的DDoS攻击,无论从流量还是次数的角度,都上升了一个新的高度。

    DDoS黑色产业链的人员与技术的演进降低了整体DDoS入门的门槛,在溯源监控中,有的DDoS黑产团伙平均年龄 20 岁左右,甚至有未满 16 周岁的学生也是其中的一员。

    在DDoS的整体防御上,建议用户采用具备大带宽储备和BGP资源的云服务商防御方案。如腾讯云大禹拥有30线BGP IP接入资源,丰富的场景化防护方案。

    随着智能AI设备与物联网的飞速发展,DDoS的新宿主平台不断出现,DDoS攻防战会越来越激烈。可以预期,2018年下半年DDoS会呈现出多样化的发展:

    • 类似于Memcached DDoS的新反射放大方式会不断的被曝光与利用;
    • 智能设备的发展会催生出新平台下的botnet产生,且这些平台基本防护措施薄弱,更成了DDoS的温床;
    • 随着打击DDoS力度的不断加大,P2P式僵尸网络或半去中心化变种方式有望重回风口,让DDoS难于监控与溯源分析;
    • 基于暗网的DDoS平台将逐渐替代目前流行的页端DDoS攻击平台,让其平台的存活时间更长。

    问答
    如何防范DDos攻击?
    相关阅读
    游戏场景下的DDOS风险分析及防护
    基于TCP反射DDoS攻击分析
    初识常见DDoS攻击手段

    此文已由作者授权腾讯云+社区发布,原文链接:https://cloud.tencent.com/developer/article/1146994?fromSource=waitui
    欢迎大家前往腾讯云+社区或关注云加社区微信公众号(QcloudCommunity),第一时间获取更多海量技术实践干货哦~

    展开全文
  • 一:引进国际专线接入国际专线接入是指建立专门的网络隧道以便于直接访问国际互联网,这样的方式也能大大降低访问速度的延迟问题。 二:全优化中国大陆方向的带宽资源从中国与世界各国的实际距离来看,不同国家的...
  • 以短视频、直播为代表的音视频互动,正成为互联网主流的交互方式。拿直播举例,它从一种娱乐形式,逐渐融合于教育、娱乐、电商、旅游等多种生态中。未来,直播还将成为像水、电一样的基础设施。 然而,仅仅可进行...
  • 互联网最初是一个工具,后来变成了一个国家...其实这个观点是错的,只有在互联网上、与互联网连着的数据才是真正意义的大数据。如果纯粹从量的角度看,欧洲做基本物理学研究的地方可能是世界数据最多的一个地方,但
  • (一)5G网络超低延迟背后的黑科技

    千次阅读 2020-06-16 12:08:49
    题图来自视觉中国,本文转自知乎作者“见微”的回答《5G 的网络延迟时间 1 毫秒是怎么做到的?》 一、网络延迟时间的定义 #单向延迟 单向延迟指的是信息从发送方传到接收方的所花费的时间。 单向时间延迟 #双向时间...
  • 发送端发送的最后一个数据包是3063号包,从3063号包中可以看到in flight的大小(见前面计算方法),数据包里显示的in flight只有不到5个数据包,而这是几乎不可能的,我只是在千兆网络模拟了一个300ms的延迟而已。...
  • GitLab网站的运营工作由GitLab基础设施团队负责,同时这也是GitLab目前最大的实例:拥有约300万用户和近700万个项目,是互联网上最大的单租户开源SaaS站点之一。PostgreSQL是GitLab网站基础设施的关键组成部分,我们...
  • 互联网数字营销广告管理平台应用

    千次阅读 2017-12-13 11:22:25
    项目背景及挑战由于数字营销发展迅猛,数据源和处理方式多种多样,要求越来越高,根据AdMaster公司实际应用场景经过10多年的打磨,构建了一套互联网数字营销广告管理平台。公司要为数千家客户提供广告监控、舆情分析...
  • 耳返功能又称耳机返听,耳机采集监听,在设备插入耳机(普通耳机或蓝牙耳机),能从耳机侧听到麦克风采集的声音。 该功能可以使主播在K歌直播、K歌歌曲录制、个人清唱,朗诵等场景下实时监听自己的声音,...
  • 延迟对于普通用户来说是一个新词。我们通常以Mbps为单位来表示链路质量。 如果我们希望在拥有许多设备的家庭或办公室中建立良好的连接,那么根据日常使用情况,使用高Mbps值的连接,例如30、50或100Mbps。 速度测试...
  • 最近由于公司业务关系,需要一个在公网能实时互动超清视频的架构和技术方案。众所周知,视频直播用 CDN + RTMP 就可以满足绝大部分视频直播业务,我们也接触了和测试了几家 CDN 提供的方案,单人直播没有问题,...
  • 复工延迟,给你一份远程办公指南

    千次阅读 2020-02-09 13:16:39
    由于疫情拐点依然没有出现,原本推迟到2.10复工的公司,不得不再次调整复工时间,下图是一些互联网公司最新的复工时间。可以看到,很多企业都将上班时间,推迟了一周。外地返回的员工,需要自行隔...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,832
精华内容 4,332
关键字:

互联网延迟上300