【轻知识】记录一次代码优化以及想法

发布之前对文章中的一些代码做了重命名和删除。你懂的。姑且之前的接口叫做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= 50
sum= 20.908
average = 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. 代码拆分

这个就不说了。

文摘

1.《系统运维之为什么每个团队存在大量的烂代码》

优化性能的2个观点:

  • 优化主要部分,把一次网络I/O改为内存计算带来的收益远大于我们捯饬编译器优化之类的东西。
  • 性能优化之后要有量化数据,明确说出优化后哪个指标提升了多少。

具体优化措施,无外乎以下几类:

  • 让计算靠近存储
  • 优化算法的时间复杂度
  • 减少无用的操作
  • 并行计算

参考资料:

  • 书《高可用架构(第一卷)》中秦迪的三篇文章《系统运维之为什么每个团队存在大量的烂代码》、《系统运维之评价烂代码优劣的方法》、《系统运维之如何应对烂代码》 文章还不错。那本书上有他四篇,另一篇《微博对大规模、高负载系统问题的排查方法》
  • 《优秀程序设计的18大原则》https://m.imooc.com/article/1836
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354