The Wandering Isle

Jackie's blog

0%

PO(Persistant Object)持久对象

  • PO用于表示数据库中一条记录映射成的Java对象
  • PO通常遵循Java bean规范,带有getter/setter, 仅用于表示数据,没有任何数据操作

BO(Bussiness Object)业务对象

  • 封装对象、复杂对象,里面可能包含多个类
  • 业务对象的主要作用是把业务逻辑封装成一个对象。这个对象可以包含一个或者多个其他对象。
  • 用于表示一个业务对象。BO 包括了业务逻辑,常常封装了对 DAO、RPC 等的调用,可以进行 PO 与 VO/DTO 之间的转换。BO 通常位于业务层,要区别于直接对外提供服务的服务层:BO 提供了基本业务单元的基本业务操作,在设计上属于被服务层业务流程调用的对象,一个业务流程可能需要调用多个 BO 来完成。

VO(Value Object)表现对象

  • 前端界面展示;value object值对象;ViewObject表现层对象;主要对应界面显示的数据对象。对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值;对于Android而言即是activity或view中的数据元素。
  • 用于表示一个与前端进行交互的 java 对象。有的朋友也许有疑问,这里可不可以使用 PO 传递数据?实际上,这里的 VO 只包含前端需要展示的数据即可,对于前端不需要的数据,比如数据创建和修改的时间等字段,出于减少传输数据量大小和保护数据库结构不外泄的目的,不应该在 VO 中体现出来。通常遵守 Java Bean 的规范,拥有 getter/setter 方法。

DTO(Data Transfer Object)数据传输对象

  • 前端调用时传输;也可理解成“上层”调用时传输;比如我们一张表有100个字段,那么对应的PO就有100个属性。但是我们界面上只要显示10个字段,客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO.
  • 用于表示一个数据传输对象。DTO通常用于不同服务或服务不同分层之间的数据传输。DTO与VO概念相似,并且通常情况下字段也基本一致。但DTO与VO又有一些不同,这个不同主要是设计理念上的,比如 API 服务需要使用的 DTO就可能与 VO 存在差异。通常遵守 Java Bean 的规范,拥有 getter/setter 方法

DAO(Data Access Object)数据访问对象

  • 这个大家最熟悉,和上面几个O区别最大,基本没有互相转化的可能性和必要.,主要用来封装对数据库的访问。通过它可以把POJO持久化为PO,用PO组装出来VO、DTO;
  • 用于表示一个数据访问对象。使用 DAO 访问数据库,包括插入、更新、删除、查询等操作,与 PO 一起使用。DAO 一般在持久层,完全封装数据库操作,对外暴露的方法使得上层应用不需要关注数据库相关的任何信息。

POJO(Plain Ordinary Java Object)简单Java对象

#1 关于跨越十三年的一封信

2008年,当时的初中班主任在班上组织了一次活动,给十年后的自己写一封信,我清晰记得当时留下的收信地址,但是却对于写了什么完全没有印象。如今已经过去13年,3年以前我一度以为这封信已经石沉大海,因为过去十多年了,中间发生了很多事情,无论学校、老师还是我自己都有了不小的变化。然而就在前不久,很意外地收到了当时自己给自己写的信,从头至尾看完,发现当年的自己对于未来并没有非常明确的规划,只是希望自己在未来能够正视自己的缺点,努力为自己争取一个能让自己满意的生活状态。当然,现在看来,在生活中中要让自己满意也越来越难,反而只能逐渐去接受现状,然后努力去一点点突破。

#2 关于健身

7月份把年初的私教课续了半年,一次性进行了一笔金额不小的消费。从2021年1月1日办卡开始到现在半年多的时间,身边已经有人说能够看到我的训练成果了,说明系统性地健身(虽然会被各种琐事打断)还是比自己徒手锻炼效果要好一点。春节时给自己定的年初计划当中有一条,2021年至少减肥到75kg,我现在的体重正好在这个数字附近徘徊,这也是我下定决心消费的原因,一个是有专业人员指导,健身减肥的效果会更好,同时自己钱花出去了,相当于给自己心里面留了个念头,每当有懈怠的时候便能给自己鞭策,况且,对自己的投资是最好的投资,我相信这样的消费以后能带来的回报是完全值得的。

#3 关于看书学习

说到年初计划,其中还包含了今年的阅读计划:在2021年读三本专业书与非专业书。目前在读的书有讲述日本泡沫经济时期普通人生活的《饱食穷民》、Java开发相关的《springboot实战》、以及摄影相关的《D7000摄影实战》,算是两本专业书与一本非专业书。这几本书的整个阅读过程是断断续续的,我能够明显感受到,在现在移动网络发达,抖音、B站等碎片化内容冲击的环境下,要静下心来读书是一件困难的事情,同时大脑需要花费更多精力去消化这些长内容。因此,我也决定对自己的阅读过程做一些调整:首先,制定明确的阅读计划,通过时间节点来限制自己去完成阅读,例如,计划在8月底之前完成《springboot实战》的阅读;然后,在阅读过程中,记录自己的阅读、学习心得,通过对内容的复盘,来巩固阅读效果。本篇文章写于2021年8月12日,8月份还剩下不到三分之二的时间,我决定从即日起开始执行改进后的阅读计划。

2021年已经过去一半了,今年比较明确地给自己立下了几个目标,有一部分已经完成,有一部分还在进行当中。半年时间经历了很多事情,生活看起来像是不平淡的样子,但是也就这样过去了。

因为一些机缘在时隔很久之后又打开了这个blog,看到最近的更新竟然停滞在2019年,内心感慨:很多事情一旦放下很有可能就会被长期忘却了。当年的自己迈着缓慢步伐探索的内容,以今天的眼光来看虽然都是一些小儿科的东西,但是相比现在停滞不前的自己,当年也是真正跟随本心,一点点摸出道路来的。今天的我也许在经历、经验上得到了扩充,但是很难说,自己是否还有那颗想要不断扩充自己的初心呢?我一直觉得,身边优秀的人太多,优秀的他们跟我做一样的事情,而且大多都做得更好,这样的优秀让人胆怯,我的懦弱反而让我无法走出自己的那一步,想要在普通的人当中做得好一点,这种感觉都已经让人窒息了。

增增减减写了这些流水账一般的话,发现自己文字水平已经减退到难以表达自己真正的想法了,总的来说感觉自己现在的状态还算可以,只是自己错过了很多“本可以更好”的机会。但是,大家不都说:种一棵树,最好的时间是十年前,其次是现在。鞭策自己继续去做,即使步履蹒跚,继续往前走,应该是会更好的吧。

之前写的两篇爬虫体验基本上涵盖了一般的Html页面提取场景,但是有些时候,如果目标页面不是纯静态的页面,而是使用js动态渲染的页面(比如one),之前的爬虫就不好使了,这种时候就要借助一些其他工具来进行实现。

一般爬取动态页面的思路是通过软件模拟浏览器行为获取到渲染后的页面镜像,然后再对渲染后的页面进行分析,常用的工具有selenium,phantomJs,puppeteer等,通过对项目维护程度、对PHP友好度的对比,我选用的是puppeteer。

根据官方介绍,谷歌在2017年开发了自家Chrome浏览器的Headless特性,puppeteer便是这个时候诞生的,它的原理是通过调用Chrome DevTools开放的接口与Chrome通信,将浏览器开放接口进行封装,方便用户调用,可以很容易地实现浏览器行为的模拟。

尝试一下puppeteer,安装起来其实非常简单:

1
npm i puppeteer

 根据官方API写了example.js进行测试:

1
2
3
4
5
6
7
8
9
10
11
async function start(){
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://wufazhuce.com');
return page.content();
};

(async () => {
const a = await start();
process.stdout.write(a);
})();

执行node example.js便可以看到控制台输出了渲染完成之后的页面Html,这个时候便能够使用php的fopen读取stdout获取到html文本进行下一步处理了。

在github上面查找相关支持,发现有spatie/browsershot这个项目直接把操作步骤封装好了,这样便可以使用puppeteer进行动态生成html内容的获取,然后继续使用dom-crawler来获取想要抓取的内容了:

1
2
3
4
5
6
7
8
$this->crawler = new Crawler();
$html = Browsershot::url($this->url)
->setOption('args', [
'--no-sandbox',
'--disable-setuid-sandbox'
])
->bodyHtml();
$this->crawler->addHtmlContent($html);

 

 

主要思路是通过软链将npm添加到usr/local/bin下面:

1
2
sudo ln -s "$NVM_DIR/versions/node/$(nvm version)/bin/node" "/usr/local/bin/node"
sudo ln -s "$NVM_DIR/versions/node/$(nvm version)/bin/npm" "/usr/local/bin/npm"

 

自从使用hexo在github page更新博客之后,我每次在cnblog上发布文章,需要手动再更新hexo。hexo使用markdown格式来写文章,手动更新需要对文章本身内容进行转化,做成md文件再进行上传,后来就想到,本身爬虫就可以对页面中的各种元素进行提取,同时markdown使用的是标记语法,那么使用爬虫分析文章元素,提取主要内容并且根据模板自动生成对应的md文件理论上是可行的。

由于我的hexo博客使用的是默认布局,所以在hexo目录下直接执行:

1
hexo new model

这样就生成了一个model.md文件,接下来就是把这个文件改装成所需的模板,打开model.md,能看到默认布局hexo文章只有一个简单的front-matter区域用来进行文章变量的指定:

1
2
3
4
5
---
title: model
date: 2018-11-22 09:49:22
tags:
---

按照官方文档,front-matter用于文章变量的指定,本身不会作为markdown被解析,所以模板布局可以分为两个部分:

1
2
3
4
---
{{front-matter}}
---
{{markdown}}

接下来就是对文章元素的提取并且转化成相应的hexo文章内容,并且拼接填充至模板当中了。

这里我简单封装了一个MarkdownGenerator类用来把文章转换成markdown文件:

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
namespace Root;

use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

Class MarkdownGenerator
{
private $client;

private $crawler;

private $url;

private $assetPath;

private $contentsArray = [];

private $categories = [];

private $tags = [];

private $title;

private $dateString;

private $documentName;

/** 初始化,会在同目录下生成一个和文档同名的文件夹用来装静态资源
* MarkdownGenerator constructor.
* @param $documentName
*/
public function __construct($documentName)
{
$this->documentName = $documentName;
$this->assetPath = __DIR__ . "/{$this->documentName}/";
if(!is_dir($this->assetPath)){
mkdir($this->assetPath, 755);
}
$headers = [
'user-agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
];
$this->client = new Client([
'timeout' => 20,
'headers' => $headers
]);
$this->crawler = new Crawler();
}

/** 设置文章url
* @param string $url
*/
public function setUrl($url = '')
{
$this->url = $url;
}

/** 设置文章tags
* @param array $tags
*/
public function setTags($tags = [])
{
$this->tags = $tags;
}

/** 设置文章categories
* @param array $categories
*/
public function setCategories($categories = [])
{
$this->categories = $categories;
}

/**
* 生成markdown文件
*/
public function generate()
{
$responseContent = $this->client->request('GET', $this->url)->getBody()->getContents();
$this->crawler->addHtmlContent($responseContent);
try{
//获取文章title
$this->title = trim($this->crawler->filterXPath('//h1[@class="postTitle"]')->text());
//获取文章发布时间
$this->dateString = trim($this->crawler->filterXPath('//span[@id="post-date"]')->text());
//处理文章正文部分
$this->crawler->filterXPath('//div[@id="cnblogs_post_body"]')->children()->each(function(Crawler $node) {
$this->contentsArray[] = $this->parseParagraph($node);
});
//组装markdown正文部分
$markdownContent = $this->makeContent();
$frontMatter = $this->makeFrontMatter();
$tmplate = file_get_contents('model.md');
$content = str_replace(['{{front-matter}}','{{markdown}}'],[$frontMatter, $markdownContent], $tmplate);
file_put_contents($this->documentName.'.md', $content);
}catch (\Throwable $e){
print_r($e->getMessage());
}
}

/** 处理段落
* @param Crawler $node
* @return mixed|string
*/
private function parseParagraph(Crawler $node)
{
$res = $node->html();
//替换a标签,替换成markdown当中的格式
$linkPattern = '#<a\b[^>]+\bhref=\"([^\"]*)\"[^>]*>([\s\S]*?)<\/a>#';
preg_match_all($linkPattern, $res, $links);
if(!empty($links[0])){
foreach ($links[2] as $k => $link){
$l = "[{$link}]({$links[1][$k]})";
$res = str_replace($links[0][$k], $l, $res);
}
}

//处理图片,图片这里使用hexo的图片标签,格式为{% asset_img name.format alt %}, 对应图片放在和post目录下和md文件同名文件夹中
$imgPattern = '#<img\b[^>]+\bsrc="([^"]*)"[^>]+\balt="([^"]*)"[^>]*>#';
preg_match_all($imgPattern, $res, $imgs);
if(!empty($imgs[0])){
foreach ($imgs[2] as $k => $img){
$imageUrl = $imgs[1][$k];
//下载图片并放入对应的文件夹内
$imageName = pathinfo($imageUrl)['basename'];
$fileName = $this->assetPath . $imageName;
$image = $this->client->get($imageUrl)->getBody()->getContents();
file_put_contents($fileName, $image);
$i = "{% asset_img {$imageName} {$imgs[2][$k]} %}";
$res = str_replace($imgs[0][$k], $i, $res);
}
}
//处理内嵌代码
if($node->attr('class') === 'cnblogs_code'){
$plainCodes = trim($node->text());
$res = htmlspecialchars("```") ."\n{$plainCodes}\n" . htmlspecialchars("```");
}
return $res;
}

/** 组装markdown正文部分
* @return string
*/
private function makeContent()
{
return (implode($this->contentsArray, "\n\n"));
}

/** 组装frontMatter部分
* @return string
*/
private function makeFrontMatter()
{
$res = <<<FM
title: {$this->title}
date: {$this->dateString}
FM;
if(!empty($this->categories)){
$res .= "\ncategories:\n- " . implode($this->categories, "\n- ");
}
if(!empty($this->tags)){
$res .= "\ntags:\n- " . implode($this->tags, "\n- ");
}
return $res;
}
}

接下来只用实例化generator类,然后设置各项属性,调用generate方法就能够抓取生成markdown文件了:

1
2
3
4
5
$generator = new MarkdownGenerator('test');
$generator->setUrl('https://www.cnblogs.com/jackiebao/p/8466232.html');
$generator->setTags(['test1','test2']);
$generator->setCategories(['test_cat']);
$generator->generate();

 P.S.本篇文章hexo版本即为该脚本生成。

我们在PHP开发当中难免会遇到这种情况,在用composer做包管理工具的时候,项目依赖的某个开源组件的部分代码需要根据整个项目的需求进行修改,这种时候可以通过修改vendor包里面的组件源码来实现,然而修改vendor包容易导致一个问题,那就是版本不容易进行管理,如果进行composer update操作很容易就把修改过的代码给覆盖了。将composer的repository管理设置为vcs源可以很好地解决这个问题。

VCS全称Version Control System,意为版本管理系统,根据composer官方文档,现在composer支持GitSubversionMercurialFossil等版本管理系统,其中如果使用Github的git源,Bitbucket的git和mercurial源,composer是可以直接通过API获取到zip包的,如果是其他源,则需要本地有对应的客户端支持。

假设有这么个场景,在开发的过程中使用了authorAprojectA包,然后我需要对包里面某个部分的代码进行一些跟本地项目环境更加适配的更改,就可以先将projectA的项目代码fork到自己的github目录下,这样就可以对项目源码进行修改了(请遵循相应的开源协议),然后在工程目录的composer.json当中只需要加入这几行代码:

1
2
3
4
5
6
7
8
9
10
11
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/myAcount/projectA"
}
],
"require": {
"authorA/projectA": "~x.x"
}
}

将本地修改后的代码push到自己的仓库中,注意要打tag,然后在项目目录下执行composer update authorA/projectA 命令就可以使用用自己仓库做源的projectA包了

网络爬虫在大数据时代可以非常高效地自动进行数据的收集处理,而传统爬虫最简单也是最基本的功能实现原理即是下载网页,然后通过抽取页面元素来达到收集信息的目的。

PHP作为一门灵活易用的脚本语言,实现这些功能自然是不在话下的。

这里实现爬虫基于两个组件:

guzzle:最好用的PHP HTTP客户端,用来进行爬取页面的请求,异步请求和并发请求功能可以用来实现一些后期的扩展功能。

dom-crawler:symphony的Dom分析组件,可以用来分析HTML页面Dom元素和XML文件,用来进行页面分析。

两个组件在项目中都可以很方便地使用composer进行安装,这里以博客园的文章为例,使用这两个组件实现最简单的页面抓取,抓取我个人博客园首页的文章摘要和链接。

代码如下:

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
require_once __DIR__ . '/vendor/autoload.php';

use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

run();
function run()
{
//要爬取的页面地址为我的博客园主页
$url = "http://www.cnblogs.com/jackiebao/";
//伪造浏览器UA
$headers = [
'user-agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
];
$client = new Client([
'timeout' => 20,
'headers' => $headers
]);
//发送请求获取页面内容
$response = $client->request('GET', $url)->getBody()->getContents();

$data = [];
$crawler = new Crawler();
$crawler->addHtmlContent($response);

//使用crawler进行页面内容分析
try{
//这里使用的是xpath语法,轮询forFlow子类day中的元素,既页面上每一篇文章的块状元素,并且进行内容获取
$crawler->filterXPath('//div[contains(@class, "forFlow")]/div[contains(@class, "day")]')->each(function(Crawler $node, $i) use (&$data){
$item = [
'date' => $node->filterXPath('//div[contains(@class, "dayTitle")]/a')->text(),
'title' => $node->filterXPath('//div[contains(@class, "postTitle")]/a')->text(),
'abstract' => $node->filterXPath('//div[contains(@class, "postCon")]/div')->text(),
'url' => $node->filterXPath('//div[contains(@class, "postCon")]/div/a')->attr('href'),
];
$data[] = $item;
});
}catch (\Exception $e){
echo $e->getMessage() . PHP_EOL;
}
//打印结果
print_r($data);
}

打印出来的结果为:

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
73
74
75
Array
(
[0] => Array
(
[date] => 2018年4月27日
[title] => windows环境下给PHP增加rdkafka扩展
[abstract] => 摘要: 因为工作需要kafka作为消息中间件,所以在本地开发环境进行测试的时候需要给PHP添加rdkafka扩展,使用PHP作为producer或者cosumer,在此纪录一下rdkafka的安装过程。 扩展下载地址:http://pecl.php.net/package/rdkafka 根据自身PHP版本阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/8962804.html
)

[1] => Array
(
[date] => 2018年2月24日
[title] => 在亚马逊aws服务器上添加Google BBR支持
[abstract] => 摘要: 参考文章: https://51.ruyo.net/2783.html http://blog.csdn.net/VgFengYe/article/details/78609040 官方 quick start文档:https://github.com/google/bbr/blob/master/阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/8466232.html
)

[2] => Array
(
[date] => 2018年2月22日
[title] => PHP static关键字和self关键字的区别
[abstract] => 摘要: 在PHP的一个类中,带有static关键字的方法和属性被称为静态方法和静态属性,这样的方法和属性可以通过类直接访问,而不需要通过类对应的实例来进行访问,在类中访问静态变量以及静态属性的时候,可以使用self关键字和static关键字,两种访问方式看起来似乎没有区别,但是实际上还是不一样的 运行之后的阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/8459899.html
)

[3] => Array
(
[date] => 2018年2月6日
[title] => linux系统mysql忘记密码处理
[abstract] => 摘要: 最近开始重新拾掇自己优惠时贪便宜买的一台京东云主机,然而早已经将当年集成环境一键安装时设置的mysql密码给忘了。 于是度娘了解决办法,大致分为以下步骤: 结果执行之后报这个错误 “Unknown column 'password' in 'field list'”。 后查询得知mysql在5.7版阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/8424672.html
)

[4] => Array
(
[date] => 2018年1月25日
[title] => PHP7 新增加的两种运算符
[abstract] => 摘要: 太空舱运算符: 空合并运算符:阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/8352383.html
)

[5] => Array
(
[date] => 2017年4月25日
[title] => PHP trait 特性
[abstract] => 摘要: trait是PHP自5.4版本之后加入的一种新的代码复用机制,是一种细粒度代码复用的方法。官方文档对于trait给出的解释是: 自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。 Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/6763388.html
)

[6] => Array
(
[date] => 2017年1月23日
[title] => PHP 字符串拆分函数
[abstract] => 摘要: function str_split_utf8($str) { $split = 1; $array = array(); for ($i = 0; $i 127) { if ($value >= 192 && $value = 224 && $value = 240 && $value <= 247) { ...阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/6344173.html
)

[7] => Array
(
[date] => 2016年10月25日
[title] => Windows 环境下php安装openssl证书
[abstract] => 摘要: 新的电脑安装了PHP、设置好环境变量之后安装了composer,想要通过composer安装Yii2,结果出现了如下报错: 检查发现php.ini里面的extension=php_openssl.dll已开启,如提示所说问题的原因是证书认证失败。 解决方法: http://curl.haxx.se/阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/5996770.html
)

[8] => Array
(
[date] => 2016年7月21日
[title] => PHP 按照多个键值给数组分组合并
[abstract] => 摘要: 简介: $array 为一堆数组,各数组键值为固定 $keys为分组依据,在$array中按照$keys所指定的键值将数组分组,并且将除$keys指定键值对应的值以外的值合并 输出:阅读全文
[url] => https://www.cnblogs.com/jackiebao/p/5691094.html
)

)

得到这样格式化的数据就很方便进行进一步的处理了,而最基础的爬虫功能也就实现了,实际上是非常简单的。

因为工作需要kafka作为消息中间件,所以在本地开发环境进行测试的时候需要给PHP添加rdkafka扩展,使用PHP作为producer或者cosumer,在此纪录一下rdkafka的安装过程。

扩展下载地址

根据自身PHP版本选择相应的包,使用phpinfo()函数可以很方便的查看自己PHP版本:

我的是7.1.5 vc14 x64 nts版本的php 所以选择相应的扩展包进行下载:

windows版的扩展包下载下来之后是一个压缩文件,解压之后得到一堆文件

将其中rdkafka.dll放入php目录下的ext文件夹内,librdkafka.dll放入php目录下,然后修改php.ini,添加:

1
extension=php_rdkafka.dll

然后重启服务器,再通过phpinfo查看,便能看到rdkafka扩展已经成功安装

参考文章1

参考文章2

官方 quick start文档

1、获取root权限:

1
sudo -s

2、根据官方文档,TCP BBR需要linux的内核版本为4.9或者更高版本,因此需要先检查系统内核:

1
2
#查看当前系统全部信息
uname -a

显示系统内核为4.4.0,所以需要升级一下内核

这里查看最新内核,根据系统架构下载最新的.deb文件

1
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.16-rc2/linux-image-4.16.0-041600rc2-generic_4.16.0-041600rc2.201802190311_amd64.deb

安装内核:

1
dpkg -i linux-image-4.16.0-041600rc2-generic_4.16.0-041600rc2.201802190311_amd64.deb

更新引导文件并且重启系统:

1
2
3
4
#更新grub系统引导文件
update-grub
#重启系统
reboot

重启后执行uname -r可以看到系统内核已经切换

开启bbr:

1
2
3
4
echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
#保存
sysctl -p

提示permission denied,sudo -s获取root权限,再次执行操作,成功。

执行

1
sysctl net.ipv4.tcp_available_congestion_control

看到net.ipv4.tcp_available_congestion_control = reno cubic bbr

再执行

1
lsmod | grep bbr

看到tcp_bbr 20480 1

说明操作成功bbr已经启动