[转]向facebook学习,通过协程实现mysql查询的异步化

FROM : 通过协程实现mysql查询的异步化

前言

最近学习了赵海平的演讲,了解到facebook的mysql查询可以进行异步化,从而提高性能。由于facebook实现的比较早,他们不得不对php进行hack才得以实现。现在的php5.5,已经无需hack就可以实现了。
对于一个web网站的性能来说,瓶颈多半是来自于数据库。一般数据库查询会在某个请求的整体耗时中占很大比例。如果能提高数据库查询的效率,网站的整体响应时间会有很大的下降。如果能实现mysql查询的异步化,就可以实现多条sql语句同时执行。这样就可以大大缩短mysql查询的耗时。

异步为啥比同步快?

与异步查询相反的时同步查询。通常情况下mysql的query查询都是同步方式。下面我们对两种方式做下对比。对比的例子是,请求两次select sleep(1)。这条语句在mysql服务器端大概耗时1000ms。

同步方式的执行流程:

第一步,向mysql服务器端发送第一次查询请求。大概耗时 1ms
第二步,mysql服务器端返回第一次查询的结果。大概耗时 1000ms
第三步,向mysql服务器再次发送请求。大概耗时 1ms
第四步,mysql服务器端返回第二次查询的结果。大概耗时 1000ms
同步的方式执行两次select sleep(1),大概耗时 2002ms。

异步方式的执行流程:

第一步,向mysql服务器端发送第一次查询请求。大概耗时1ms
第二步,在等待第一次请求返回数据的同时,向服务器端发送第二次查询请求。大概耗时 1ms
第三步,接受mysql服务器端返回的两次查询请求。大概耗时 1000ms。

对比分析
异步查询比同步查询速度快,是因为多条查询语句在服务器端同时执行,大大缩短了服务器端的响应时间。并行一般情况下总比串行快嘛。sql语句执行时间越长,效果越明显。

如何实现mysql的异步查询?

要实现异步查询的关键是能把发送请求和接受返回数据分开。正好mysqlnd中提供了这个特性。
在mysqlnd中对应的方法是:
mysqlnd_async_query 发送查询请求
mysqlnd_reap_async_query 获取查询结果
mysqli扩展针对mysqlnd的这个特性做了封装,在调用query方法时,传入MYSQLI_ASYNC即可。

具体代码实现可以查看博文 php中mysql数据库异步查询实现

为啥使用协程?
查看了博文中的代码实现,是不是感觉写法和平时不一样?一般在项目当中,我们都是以function的形式去相互调用,function中包含了数据库查询。为了保持这个习惯,方便大家使用,因此引入了协程。在php5.5中正好提供了yield和generator,方便我们实现协程。示例代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

<?php

function f1(){

    $db = new db();

    $obj = $db->async_query(‘select sleep(1)‘);

    echo "f1 async_query \n";

    yield $obj;

    $row = $db->fetch();

    echo "f1 fetch\n";

    yield $row;

}

function f2(){

    $db = new db();

    $obj = $db->async_query(‘select sleep(1)‘);

    echo "f2 async_query\n";

    yield $obj;

    $row = $db->fetch();

    echo "f2 fetch\n";

    yield $row;

}

$gen1 = f1();

$gen2 = f2();

$gen1->current();

$gen2->current();

$gen1->next();

$gen2->next();

$ret1 = $gen1->current();

$ret2 = $gen2->current();

var_dump($ret1);

var_dump($ret2);

class db{

    static $links;

    private $obj;

    function getConn(){

        $host = ‘127.0.0.1‘;

        $user = ‘demo‘;

        $password = ‘demo‘;

        $database = ‘demo‘;

        $this->obj = new mysqli($host, $user, $password, $database);

        self::$links[spl_object_hash($this->obj)] = $this->obj;

        return self::$links[spl_object_hash($this->obj)];

    }

   function async_query($sql){

        $link = $this->getConn();

        $link->query($sql, MYSQLI_ASYNC);

        return $link;

    }

    function fetch(){

        for($i = 1; $i <= 5; $i++){

            $read = $errors = $reject = self::$links;

            $re = mysqli_poll($read, $errors, $reject, 1);

            foreach($read as $obj){

                if($this->obj === $obj){

                    $sql_result = $obj->reap_async_query();

                    $sql_result_array = $sql_result->fetch_array(MYSQLI_ASSOC);//只有一行

                    $sql_result->free();

                    return $sql_result_array;

                }

            }

        }

    }

}

?>

在终端命令行方式执行结果如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

$time php ./async.php

f1 async_query

f2 async_query

f1 fetch

f2 fetch

array(1) {

  ["sleep(1)"]=>

  string(1) "0"

}

array(1) {

  ["sleep(1)"]=>

  string(1) "0"

}

real    0m1.016s

user    0m0.007s

从结果上我们可以看出执行流程是,先发了两次mysql查询,然后在接受数据库的返回数据。正常情况下,至少需要2000ms才能执行完毕。但是,real 0m1.016s,说明两次查询的耗时只有1016ms。
tips:以上代码只是示例代码,还有一些需要完善的地方。

注意

需要注意的是,如果mysql服务器本身负载很大,这种并行执行的方式就不一定是好的解决方法。因为,mysql服务端会为每个链接创建一个单独的线程进行处理。如果创建的线程数过多,会给系统造成负担。

参考资料

Facebook Asynchronous MySQL How Facebook Queries Databases

时间: 2024-08-06 20:03:52

[转]向facebook学习,通过协程实现mysql查询的异步化的相关文章

用python3的多进程和协程处理MySQL的数据

本文介绍用python3的多进程 + 协程处理MySQL的数据,主要逻辑是拉取MySQL的数据,然后使用flashtext匹配关键字,在存回MySQL,代码如下(async_mysql.py): import time import asyncio import random from concurrent.futures import ProcessPoolExecutor as Pool import aiomysql from flashtext import KeywordProcess

python学习笔记 协程

在学习异步IO模型前,先来了解协程 协程又叫做微线程,Coroutine 子程序或者成为函数,在所有语言中都是层级调用,比如a调用b,b调用c.c执行完毕返回,b执行完毕返回,最后a执行完毕返回 所以子程序是通过栈来实现的,一个线程就是执行一个子程序 子程序调用总是一个入口一次返回,调用顺序是明确的,而协程的调用和子程序是不同的. 协程看上去是子程序,但在执行过程中可以在子程序内部中断,转而执行别的子程序,在适当的时候返回执行 注意在一个子程序中断去执行其他子程序不是函数调用,类似于CPU的中断

Python 学习笔记 - 协程

Python里面控制并发,除了多线程和多进程,还可以使用协程(CoRoutine).他是在一个线程内通过程序员人为的指定来进行切换 例1:通过switch()可以人为来回切换到另外一个函数:当所有函数执行完毕,最后输出print(10) #!/usr/bin/env python # -*- coding:utf-8 -*- # Author:Alex Li from greenlet import greenlet def test1():     print(12)     gr2.swit

协程、 Select\Poll\Epoll异步IO、事件驱动 介绍

一.协程介绍

day11 协程 数据库 MYSQL

协程 '''并发的解决方案1:    多进程    多线程总结两点:    什么叫并发:看起来同时运行,    如何实现并发*    进程线程都是由操作系统调度的并发的解决方案2:    协程:单线程下实现的并发,应用程序级别的切换,操作系统无法感知    找到一种解决方案:        1.在多个任务直接切换+保存状态        2.检测应用程序里的IO,实现遇到IO操作时才切换'''#串行执行import timedef consumer(res):    '''任务1:接收数据,处理

day10-python并发编程之多线程协程及MySQL

第1章 python并发编程之多线程 1.1 死锁现象与递归锁 1.1.1 死锁概念 进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁 1.1.2 博客实例 from threading import Thread,Lock import time mutexA=L

玩转python(7)python多协程,多线程的比较

前段时间在做一个项目,项目本身没什么难度,只是数据存在一个数据接口服务商那儿,这就意味着,前端获取数据需要至少两次http请求,第一次是前端到后端的请求,第二次是后端到数据接口的请求.有时,后端接收到前端的一次请求后,可能需要对多个接口进行请求,按照传统串行执行请求的方法,用户体验肯定是非常糟糕了,而且对计算资源也是极大的浪费,正好前段时间学习了协程和线程的知识,所以我花了一些时间,对几种可行方案进行了测试对比.一开始我使用真正的网络io进行测试,发现这种方法受网络环境影响比较大,为了公平起见,

Python与协程从Python2—Python3

协程,又称微线程.纤程,英文名Coroutine:用一句话说明什么是线程的话:协程是一种用户态的轻量级线程. Python对于协程的支持在python2中还比较简单,但是也有可以使用的第三方库,在python3中开始全面支持,也成为python3的一个核心功能,很值得学习. 协程介绍 协程,又称微线程.纤程,英文名Coroutine:用一句话说明什么是线程的话:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先

爬虫速度太慢?来试试用异步协程提速吧!

1. 前言 在执行一些 IO 密集型任务的时候,程序常常会因为等待 IO 而阻塞.比如在网络爬虫中,如果我们使用 requests 库来进行请求的话,如果网站响应速度过慢,程序一直在等待网站响应,最后导致其爬取效率是非常非常低的. 为了解决这类问题,本文就来探讨一下 Python 中异步协程来加速的方法,此种方法对于 IO 密集型任务非常有效.如将其应用到网络爬虫中,爬取效率甚至可以成百倍地提升. 注:本文协程使用 async/await 来实现,需要 Python 3.5 及以上版本. 2.