记一次微信小程序页面加载慢的排查过程

记一次微信小程序页面加载慢的排查过程,第1张

公司新上线了一个微信小程序,在测试环境以及小程序体验版上测试一切正常,但上线之后,页面加载尤其慢。

经过运维排查,所有的请求到达服务器后均在1s内处理完成并响应,偶尔有2-3s的请求,极少。

既然服务端处理请求没有问题,那么,加载可能出现在小程序本身或网络延迟,但后者可能性较低。于是,使用fiddler抓包,其中一个加载较慢的请求结果如下:

关键时间节点如下:

· 客户端与服务器完成tcp链接时间是11:31:35(时分秒)

· 客户端开始向服务端发送请求的时间是11:31:36(时分秒)

· 服务端接收到请求的时间是11:31:36(时分秒)

· 服务端开始响应的时间是11:31:46(时分秒)

也就是说,从服务器接收到请求到开始响应耗时10s,可这跟运维查看的日志结果不符!

鉴于上面的抓包结果和生产日志结果相悖,决定使用curl对耗时较长的http请求进行分析。

运行结果如下

对应的结果含义如下:

· time_namelookup :DNS 域名解析的时候,就是把 https://zhihu.com 转换成 ip 地址的过程

· time_connect :TCP 连接建立的时间,就是三次握手的时间

· time_appconnect :SSL/SSH 等上层协议建立连接的时间,比如 connect/handshake 的时间

· time_redirect :从开始到最后一个请求事务的时间

· time_pretransfer :从请求开始到响应开始传输的时间

· time_starttransfer :从请求开始到第一个字节将要传输的时间

· time_total :这次请求花费的全部时间

那么对应的时间点应该敬者是:

· DNS解析耗时:0.005s

· TCP建立连接的耗时:0.035-0.005=0.03s

· SSL握手完成耗时:3.8-0.034=3.7s

· server处理数据的时间:3.8402-3.8401=0.0001s

· 总体的耗时:14.5s

emmm,按照这个结果来看,从服务器开始响应到传输完成一共耗时14.5-3.8=10.7s。

那么这里问题又来了,这结果跟fiddler的结果完全相反,到底是在哪个环节出了问题?

fiddler的结果显示从服务器接收到请求到开始响应耗时10s,是服务器处理请求消耗了10s;而curl显示服务器处理请求只用了0.0001s,响应开始到结束耗时10.7s。到底哪个是对的,难道是根据本身有问题?

于是在跟运维同事一波交流之后,得到请求流转过程如下:

客户端<---------->CDN服务器<---------->Nginx代理<---------->应用服务器<---------->DB

弄清请求流转过程之后,手动发送请求,实时查看Nginx和应用服务器日志,发现请求发出后,间隔一段时间(10s左右)Nginx日志才有输出,接着很快App Server日志也有输出(包括卜梁请求和响应)。事实就摆在眼前,是CDN服务器<---------->Nginx代理之间出现了问题,具体是为什么目前还不清楚。

那么fiddler和curl抓包的结果如何解释?

fiddler:从服务器接收到请求到开始响应耗时10s,是正确的。

curl:服务器处理请求只用了0.0001s,响应开始到结束耗时10.7s。这里就有问题了,要想解释得通,只能说time_pretransfer和time_starttransfer是CDN服务器的响应时间,由于配置了CND,在向小程序应用服务器发送请求时,会先请求到CDN服务型稿运器此时CDN服务器很快就响应了客户端的请求,但CDN服务器在转发请求到Nginx代理时出现了延迟,因此在curl的请求结果中才会看起来像是响应开始到响应结束耗时较长。

至于为什么请求从CND服务器到应用服务器会出现延迟,目前还没有结论。待更新

http://blog.51cto.com/h2ofly/1957171

http://developer.51cto.com/art/201704/536923.htm?foxhandler=RssReadRenderProcessHandler

研究了好几天,坑也遇到了,也百度了很久现在终于做完了,给大家分享出来,

我这个也是参考别人写的。有不明白的朋友可以问我

public function unifiedorder($order_no, $openid, $total_fee, $attach, $order_id, $user_id){

    // 当前时间

    $time = time()

    // 生成随机字符串

    $nonceStr = md5($time . $openid)

    // API参数

    $params = [

        'appid' =>$this->appid,                                  //微信分配的小程序id

        'attach' =>$attach,                                      //附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。

        'body' =>'会员卡',                                      //募捐描述

        'mch_id' =>$this->mchid,                        //微信支付分配的商户号

        'nonce_str' =>$nonceStr,                                  //随机字符串,32位以内

        'notify_url' =>$this->notify_url,                    //            base_url() . 'notice.php?s=/task/notify/order/wxapp_id/'.$wxapp_id, // 异步通知地址

        'openid' =>$openid,                                      //用户标识;trade_type=JSAPI,此参数必传,用户在桥枯商户appid下的唯一标识。

        'out_trade_no' =>$order_no,                       槐消码       //商户账单号

        'spbill_create_ip' =>\request()->ip(),                    //终端IP;支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP

        'total_fee' =>(int)$total_fee * 100, // 价格:单位分              // 价格:单位分

        'trade_type' =>'JSAPI',                                  //交易类型

    ]

    // 生成签名

    $params['sign'] = $this->makeSign($params)  //这个地方最坑,需要的是配置 1、appid和商户号必须是绑定的状态

    // 请求API

    $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'

    $result = $this->post($url, $this->toXml($params))

    $prepay = $this->fromXml($result)

    //添加preapay_id

    $data = [

        'user_id' =>$user_id,

        'order_id'铅哪 =>$order_id,

        'attach' =>json_encode($attach),

        'prepay_id' =>$prepay['prepay_id'],

    ]

    (new AppleWxPrepay())->addInfo($data)

    // 请求失败

    if ($prepay['return_code'] === 'FAIL') {

        return [API_CODE_NAME =>2000004, API_MSG_NAME =>$prepay['return_msg']]

    }

    if ($prepay['result_code'] === 'FAIL') {

        return [API_CODE_NAME =>2000004, API_MSG_NAME =>$prepay['err_code_des']]

    }

    // 生成 nonce_str 供前端使用

    $paySign = $this->makePaySign($params['nonce_str'], $prepay['prepay_id'], $time)

    return [

        'prepay_id' =>$prepay['prepay_id'],

        'nonceStr' =>$nonceStr,

        'timeStamp' =>(string)$time,

        'paySign' =>$paySign

    ]

}

/**

* 生成签名

* @param $values

* @return string 本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值

*/

private function makeSign($values)

{

    //签名步骤一:按字典序排序参数

    ksort($values)

    $string = $this->toUrlParams($values)

    //签名步骤二:在string后加入KEY

    $string = $string . '&key=' . $this->apikey

    //签名步骤三:MD5加密

    $string = md5($string)

    //签名步骤四:所有字符转为大写

    $result = strtoupper($string)

    return $result

}

/**

* 格式化参数格式化成url参数

* @param $values

* @return string

*/

private function toUrlParams($values)

{

    $buff = ''

    foreach ($values as $k =>$v) {

        if ($k != 'sign' &&$v != '' &&!is_array($v)) {

            $buff .= $k . '=' . $v . '&'

        }

}

    return trim($buff, '&')

}

/**

* 模拟POST请求

* @param $url

* @param array $data

* @param bool $useCert

* @param array $sslCert

* @return mixed

*/

public function post($url, $data = [], $useCert = false, $sslCert = [])

{

    $header = [

        'Content-type: application/jsoncharset=UTF8'

    ]

    $curl = curl_init()

    curl_setopt($curl, CURLOPT_URL, $url)

    curl_setopt($curl, CURLOPT_HTTPHEADER, $header)

    curl_setopt($curl, CURLOPT_HEADER, false)

    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1)

    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false)

    curl_setopt($curl, CURLOPT_POST, TRUE)

    curl_setopt($curl, CURLOPT_POSTFIELDS, $data)

    if ($useCert == true) {

        // 设置证书:cert 与 key 分别属于两个.pem文件

        curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM')

        curl_setopt($curl, CURLOPT_SSLCERT, $sslCert['certPem'])

        curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM')

        curl_setopt($curl, CURLOPT_SSLKEY, $sslCert['keyPem'])

    }

    $result = curl_exec($curl)

    curl_close($curl)

    return $result

}

/**

* 输出xml字符

* @param $values

* @return bool|string

*/

private function toXml($values)

{

    if (!is_array($values) || count($values) <= 0) {

        return false

    }

    $xml = "<xml>"

    foreach ($values as $key =>$val) {

        if (is_numeric($val)) {

            $xml .= "<" . $key . ">" . $val . "</" . $key . ">"

        } else {

            $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">"

        }

}

    $xml .= "</xml>"

    return $xml

}

/**

* 将xml转为array

* @param $xml

* @return mixed

*/

private function fromXml($xml)

{

    // 禁止引用外部xml实体

    libxml_disable_entity_loader(true)

    return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true)

}

/**

* 生成paySign

* @param $nonceStr

* @param $prepay_id

* @param $timeStamp

* @return string

*/

private function makePaySign($nonceStr, $prepay_id, $timeStamp)

{

    $data = [

        'appId' =>$this->appid,

        'nonceStr' =>$nonceStr,

        'package' =>'prepay_id=' . $prepay_id,

        'signType' =>'MD5',

        'timeStamp' =>$timeStamp,

    ]

    // 签名步骤一:按字典序排序参数

    ksort($data)

    $string = $this->toUrlParams($data)

    // 签名步骤二:在string后加入KEY

    $string = $string . '&key=' . $this->apikey

    // 签名步骤三:MD5加密

    $string = md5($string)

    // 签名步骤四:所有字符转为大写

    $result = strtoupper($string)

    return $result

}

/*********************************微信回调**********************/

public function getNotify()

{

    if (!$xml = file_get_contents('php://input')) {

        $this->returnCode(50000001, 'Not found DATA')

    }

    // 将服务器返回的XML数据转化为数组

    $data = $this->fromXml($xml)

    $payLog = new ApplePayLog()

    // 记录日志

    $payLog->addInfo(['content'=>json_encode($xml)])

    $payLog->addInfo(['content'=>json_encode($data)])

    // 实例化账单模型

    $OrderModel = new AppleOrder()

    // 账单信息

    $orderInfo = $OrderModel->getInfo(['id'=>$data['attach']],'*')

    if (empty($orderInfo)) {

        $this->returnCode(50000001, '账单不存在')

    }

    if($orderInfo['pay_status'] != 1 || !empty($orderInfo['pay_time'])){

        $this->returnCode(50000001,'订单已支付,请勿再次支付')

    }

    // 保存微信服务器返回的签名sign

    $dataSign = $data['sign']

    $return_code = $data['return_code']

    $result_code = $data['result_code']

    $data['body'] = '会员卡'

    $data['spbill_create_ip'] = \request()->ip()

    $data['notify_url'] = $this->notify_url

    // sign 与 s 参数 不参与签名算法

    unset($data['sign'])

    unset($data['transaction_id'])

    unset($data['coupon_id'])

    unset($data['coupon_type'])

    unset($data['coupon_count'])

    unset($data['coupon_fee'])

    unset($data['time_end'])

    unset($data['return_code'])

    unset($data['result_code'])

    unset($data['is_subscribe'])

    unset($data['fee_type'])

    unset($data['bank_type'])

    unset($data['bank_type'])

    // 生成签名

    $sign = $this->makeSign($data)

    // 判断签名是否正确  判断支付状态

    if (($sign === $dataSign) &&($return_code == 'SUCCESS') &&($result_code == 'SUCCESS')) {

        $OrderModel->startTrans()

        try {

            // 账单支付成功业务处理

            $appleOrderInfo = $OrderModel->where(['id'=>$orderInfo['id']])->lock(true)->find()

            $result = $appleOrderInfo->addInfo(['pay_status'=>2,'pay_time'=>time()],['id'=>$orderInfo['id']])

            if(!$result){

                $OrderModel->rollback()

                $this->returnCode(5000003, '修改订单失败,失败原因:'.$OrderModel->getError())

            }

            $appleUserModel = new AppleUser()

            $appleUserInfo =  $appleUserModel->where(['openid'=>$orderInfo['openid']])->lock(true)->find()

            $appleUser = $appleUserInfo->where(['openid'=>$orderInfo['openid']])->setInc('moxibustion',$orderInfo['moxibustion'])

            if(!$appleUser){

                $OrderModel->rollback()

                $this->returnCode(5000003, '添加会员针灸次数失败,失败原因:'.$appleUserModel->getError())

            }

        }catch (\Exception $exception){

            $OrderModel->rollback()

            $this->returnCode(5000003, ' *** 作失败,失败原因:'.$exception->getMessage())

        }

            $OrderModel->commit()

        // 返回状态

        die(json(['code'=>0,'支付成功']))

    }

    // 返回状态

    $this->returnCode(2000003, '签名失败')

}

给button绑定冒泡事件,也就是我们说的点击事件

这里wx.getUserProfile方法可以从底部d框提示用蚂段户授权,success是用户确认授猜物指权回调方法,

可以console.log(result)查看以下数据,可以在userInfo中获取用户的一些基本信息,例如头像,昵称。通过wx.login()获取微信平台提供给我们的code(5分钟内有效,每次请求code都不一样),有了code我们就可以通过wx.request()向后端发起请求了;

通过code和自己开发平台的appid和appSecret调用微信平台提供给我们的穗配url获取openid和session_key,这里我们可以用file_get_contents()或者curl都可。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12520620.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-26
下一篇 2023-05-26

发表评论

登录后才能评论

评论列表(0条)

保存