发布之前对文章中的一些代码做了重命名和删除。你懂的。姑且之前的接口叫做old-share-api ,新接口叫做new-share-api。
写本文,只是想通过一个比较简单的接口,来记录下优化的想法。这个简单的接口,大概做的事情就是页面的分享,生成一个图片,分享到朋友圈或者发给好友。
重构
重构测试
线上环境:
old-share-api(lumen框架,php7) 4核8g
new-share-api (yaf,php7) 2核4g
测试代码片段(循环发送请求):
$uid = 790729;while($start < $end) {// 发送一百个请求$start++; $uid++; $params = ['uid'=>$uid,'link'=>'https://m.yanshinian.com/index.html?jsonData=/act_html/cache/data/7f690e132491_43656.json','pid'=>14,'request_token'=>'000092659614c41548570563', ]; $result = $client->post($url, ['form_params' => $params])->data();echo \GuzzleHttp\json_encode($result); }
代码说明:
页面4365 的分享模块,没有加ad_id,所以只是简单的,拿shareKey,生成二维码。
为什么$uid 累计发送请求呢?因为用同一个uid 第二次请求的时候会走缓存。old从数据库拿之前分享的。new-share从redis拿。
返回结果值:
{"status":0,"msg":"分享成功","data":{"shareImage":"https://img-cdn.yanshinian.com/o_1cs8fvits1o2nlim1rhs1uqg5v.jpg?watermark/1/image/aHR0cHM6Ly9pbWFnZS1fd3VkwuandvMTY2/dissolve/100/gravity/NorthWest/dx/545/dy/950/ws/0%7cimageView2/2/q/80/format/jpg/quality/75!%7cimageslim","pageUrl":"https://m.yanshinian.com/index.html?jsonData=/act_html/cache/data/7f690e132491_43656.json","secne":"ye32d72L","ad_material_info":[ ] } }
线上测试结果(尽可能量化)
下面是不走缓存的情况下(走缓存的没测,一个取redis,一个取mysql,就不测了)
old-share
统计了23次请求,平均 1.3秒多。
cat /home/www/logs/nginx/old-share.yanshinian.com.access.log | grep "690e132491_4365" | awk -F'\001' '{sum+=$7;lineCount+=1} END {print "lineCount= " lineCount; print "sum= " sum; print "average = " sum/NR}'lineCount= 23 sum= 30.427 average = 1.32291
new-share 50次请求 0.4秒(当然抽出23次还是快)
cat /home/www/logs/nginx/new-share.yanshinian.com.access.log | grep "690e132491_4365" | awk -F'\001' '{sum+=$7;lineCount+=1} END {print "lineCount= " lineCount; print "sum= " sum; print "average = " sum/NR}'lineCount= 50sum= 20.908average = 0.41816
重构前的思考
xmind 梳理图 仅供参考
old-share-api重构.png
思考如下:
1.接口影响范围
比如,这个接口调用的点有多少个,列出来,每个调用点可能传的参数不一样。
如果参数不一样,那么做的事情不一样,能否拆成几个接口。
2.梳理逻辑
把没用的逻辑拿掉。
把函数瘦身。
梳理出耗时的点。
3.重构的预期(要做测试)
afp分享慢,重构就是为了快。重构之后要做测试。
回顾下代码
old 片段
// 查询当前活动页是否已被分享$pageShareRecord = DB::connection($this->db)->table('share_record') ->select('id', 'module', 'image', 'page_url', 'updated_at', 'add_data', 'scene') ->where(['page_id' => $page_id, 'ad_uid' => $uid]) ->orderBy('id', 'desc')->first();/** * 对比module_sort,区分当前活动页是都变动,发生变动返回新的连接 * 一期上线之后,后续添加trackId用来统计,trackId格式变动,用来处理旧的分享trackId错误的情况:没有trackId、错误trackId */if ( strnatcmp($pageInfo['module_sort'], $pageShareRecord['module']) != 0 || strpos($pageShareRecord['page_url'], '&trackId') === false || strpos($pageShareRecord['page_url'], ':_:') !== false) {// 获取 shareKey$shareKey = $this->getShareKey($uid);// 此路径不再走 静态页中 lua 环节$uri = str_replace("cache/data/", "", strstr(strstr($link, 'act_html'), '.json', true) . '.json');// 因json内容过大,curl get 可能获取到的数据不完整,使用 guzzle 代替$client = new \GuzzleHttp\Client(); $host = env('ADMIN_HOST'); $response = $client->request('GET', $host . '/' . $uri . '?request_token=' . $request_token);if ($response->getStatusCode() != 200) {throw new \Exception("guzzle request file content error"); } $originJsonData = $response->getBody()->getContents();// 获取活动页中商品数据,使用 trackId,组装商品链接$pageProductInfo = json_decode($originJsonData)), true);if (!empty($pageProductInfo['module']) && is_array($pageProductInfo['module'])) {foreach ($pageProductInfo['module'] as $k => &$v) {if ($v['type_id'] == 16 && $is_promotion) { // 这里取出 分享模块的数据,有分享图$image = $v['value']['qr_bg_img'];if (isset($v['value']['ad_id']) && $v['value']['ad_id']) { $productList = $this->getProductListByApi([$v['value']['ad_id']], $uid, $trackId); $v['value']['ad_material_info']['ad_sales_url'] = $productList['data']['data'][$v['value']['ad_id']]; $addData = $v['value']['ad_material_info']; } } } } else {throw new \Exception("module parameter does not exist [curl afp result] : " . json_encode($pageInfo, JSON_UNESCAPED_UNICODE)); } $link = str_replace('/data', '/page/share', $link) . '&trackId=' . $trackId . '&shareKey=' . $shareKey; //替换完路径,nginx lua走其他处理$pid = $request->input('pid', 0); $hostConfig = config('host');if (isset($hostConfig[$pid])) { $tempArr = explode('.com', $link); $link = $hostConfig[$pid] . $tempArr[1]; }if ($is_promotion) { $scene = '';if ($res = self::wxaCodeImge($link, $image, $uid, $pid)) {list($imageUrl, $scene) = $res; } else { $imageUrl = self::_promotionImge($link, $image, $uid); } $image = $imageUrl;
new-share 片段
// 2 根据用户id 拿去数据库的缓存$userShareInfoKey = sprintf(RedisKey::USER_SHARE_INFO, $uid, $pageId); $userShareInfo = json_decode(RedisHelper::get($userShareInfoKey), true);// 3 根据页面id 拿取 页面分享模块的 缓存$pageShareInfo = json_decode(RedisHelper::get(sprintf(RedisKey::PAGE_INFO, $pageId)), true);// 4 比较签名 这个签名 是分享模块缓存数据的签名,用户分享后的缓存数据也需要 带上,为的是以后对比判断if (isset($userShareInfo["sign"]) && $userShareInfo["sign"] == $pageShareInfo["sign"]) { // 签名相等,说明分享模块数据没有变更过,直接返回分享的数据$this->success($userShareInfo, "分享成功"); }// 如果签名不同 走分享流程// 获取shareKey$shareKey = UserModel::getShareKey($uid);if ($shareKey == "") {throw new Exception("shareKey异常"); } $link = str_replace('/data', '/page/share', $link) . '&trackId=' . $trackId . '&shareKey=' . $shareKey; //替换完路径,nginx lua走其他处理// 1 拿到背景图// 2 生成 小程序码 或 二维码$scene = '';if ($pid == 14) { $xcxCodeResult = Image::getXcxcode($link, $shareKey); $image = $xcxCodeResult['imgUrl'].'&imageView2/2/w/166'; $scene = $xcxCodeResult['sence']; } else { $image = Image::getQrCode($link); } $shareImage = Image::getSharePageImage($pageShareInfo['share_module']['qr_bg_img'], $image);// 水印图$result = ['shareImage'=>$shareImage,'pageUrl'=>$link,'secne'=> $scene,'ad_material_info'=>[], ];// 把分享模块的sign 值附上$result['sign'] = $pageShareInfo['sign']; // 设置签名RedisHelper::setex($userShareInfoKey, ($pageShareInfo['end_time']), json_encode($result));$this->success($result, '分享成功');
具体优化了哪些呢?
1.减少了一次网络请求
$client->request('GET', $host . '/' . $uri . '?request_token=' . $request_token);
拿页面的json数据,最初这么做两个目的
主要是拿通过request_token 获取动态的数据,然后生成新的页面(后来复用一个页面,生成不同页面的需求就废了)
其次拿页面中分享模块的数据
优化后:
分享模块数据存入redis。从redis拿。
2.减少了两次mysql查询操作
DB::connection($this->db)->table('page')->select('module_sort', 'start_time')->where('id', $page_id)->first();
DB::connection($this->db)->table('share_record') ->select('id', 'module', 'image', 'page_url', 'updated_at', 'add_data', 'scene') ->where(['page_id' => $page_id, 'ad_uid' => $uid]) ->orderBy('id', 'desc')->first();
if ( strnatcmp($pageInfo['module_sort'], $pageShareRecord['module']) != 0 ||strpos($pageShareRecord['page_url'], '&trackId') === false ||strpos($pageShareRecord['page_url'], ':_:') !== false)
这样做的目的,是为了比较页面是否变更过。如果页面变更了(strnatcmp($pageInfo['module_sort'], $pageShareRecord['module']) != 0
)。用户下一次分享,不从缓存中(mysql)拿数据,重新生成一次分享数据。
优化之后:
缓存用了redis。对比通过sign字段。页面分享数据,在发布的时候缓存到redis,对数据加密保存一个sign字段。用户在手机分享后,把这个sign也放到自己缓存中。下次做sign对比。
虽然mysql少了。redis对应的就增加了。
代码拆分
这个就不说了。
文摘
1.《系统运维之为什么每个团队存在大量的烂代码》
优化性能的2个观点:
优化主要部分,把一次网络I/O改为内存计算带来的收益远大于我们捯饬编译器优化之类的东西。
性能优化之后要有量化数据,明确说出优化后哪个指标提升了多少。
具体优化措施,无外乎以下几类:
让计算靠近存储
优化算法的时间复杂度
减少无用的操作
并行计算
作者:言十年
链接:https://www.jianshu.com/p/a5d064066155
共同学习,写下你的评论
评论加载中...
作者其他优质文章