最近记性不太好,做个记录,防止下次忘了。
若果有遗漏,之后再补充。
更新
1 | yum update && yum upgrade |
安装 php7.4 的 yum 源
1 | yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm |
安装 php 7.4
1 | yum-config-manager --enable remi-php74 |
安装新版 mariadb
先加个源:
1 | vim /etc/yum.repos.d/mariadb.repo |
安装服务:
1 | yum clean all |
初始化密码:
敲回车的时候注意别把用户远程登录给禁了!!!
1 | mysql_secure_installation |
安装 nginx
1 | yum install nginx |
启动服务
1 | systemctl start nginx |
文章作者: m-finder
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 M-finder!
相关推荐

2019-04-12
laravel 广播系统学习
看到广播系统,先想起了曾经虐过我的即时通讯。 虽然都是对 websocket 的应用,但是好像又有点区别,这里好好学习一下。 laravel 的广播与事件紧密相关,广播即对事件进行广播,因此在学习广播之前,要先阅读事件和监听器的相关文档。 配置老规矩,先来看配置文件 config/broadcasting.php 里边的配置选项: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960<?phpreturn [ /* |-------------------------------------------------------------------------- | Default Broadcaster |-------------------------------------------------------------------------- | | This option controls the default broadcaster that will be used by the | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | | Supported: "pusher", "redis", "log", "null" | */ 'default' => env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- | Broadcast Connections |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used | to broadcast events to other systems or over websockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'encrypted' => true, ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], 'log' => [ 'driver' => 'log', ], 'null' => [ 'driver' => 'null', ], ],]; 默认情况下,laravel 提供了以上几种开箱即用的广播驱动器程序。 env 配置文件中,默认的驱动为 log,意味着客户端不会受到任何信息,只是会把要广播的消息写入 log 文件中,跟学习目标不符,就先以 pusher 展开学习吧。 我们就以发布新文章后推送给所有用户为例。 前期准备开始之前,必须要先注册 App\Providers\BroadcastServiceProvider,在 config/app.php 配置文件中的 providers 数组中取消对提供者的注释。 注册: [ pusher ] 然后把相关参数配置到 .env 文件。 安装组件:12composer require pusher/pusher-php-servernpm install --save laravel-echo pusher-js 添加文章模块,包含 migrate,controller,model,view 和 router 等内容。 新建事件:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051php artisan make:event NewArticleNotificationEvent// 事件内容:<?phpnamespace App\Events;use Illuminate\Queue\SerializesModels;use Illuminate\Broadcasting\Channel;use Illuminate\Broadcasting\PrivateChannel;use Illuminate\Foundation\Events\Dispatchable;use Illuminate\Broadcasting\InteractsWithSockets;use Illuminate\Contracts\Broadcasting\ShouldBroadcast;use App\Article;class NewArticleNotificationEvent implements ShouldBroadcast{ use Dispatchable, InteractsWithSockets, SerializesModels; private $article; /** * Create a new event instance. * * @return void */ public function __construct(Article $article) { $this->article = $article; } public function broadcastWith() { return [ 'title' => $this->article->title, 'content' => $this->article->content, 'author' => $this->article->user->name ]; } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new Channel('articles'); }} 触发事件在保存文章的控制器中触发事件: 123$data = array_merge($request->only(['title', 'content']), ['uid' => Auth::id()]);$article = Article::create($data);broadcast(new NewArticleNotificationEvent($article)); 前端监听文章列表用了vue组件,在这个组件中进行事件监听。 1234567891011121314151617181920212223242526272829303132333435<template> <div class="container"> <table class="table table-striped"> <tr> <th>ID</th> <th>Author</th> <th>Title</th> <th>Content</th> <th>Created At</th> </tr> <tr v-for="article in articles"> <td>{{article.id}}</td> <td>{{article.user.name}}</td> <td>{{article.title}}</td> <td>{{article.content}}</td> <td>{{article.created_at}}</td> </tr> </table> </div></template><script>export default { props: ['articles'], created() { Echo.channel('articles').listen('NewArticleNotificationEvent', (article) => { console.log(article); }) }}</script><style scoped></style> 写好后要在命令行执行 npm run watch-poll 实时编译文件。 测试写篇文章测试一下: 注意事项 不需要创建 channel 路由 不需要开启队列监听 如果没反应请先强制刷新浏览器

2018-02-08
openssl_pkey_get_private 内存泄漏
从前,有一块使用 swoole_process 做多线程处理的程序,这个程序有一个奇怪的问题:每运行一个星期左右都会因为内存爆表而停掉。 因为爆表的时间关系,开发的小伙伴们都认为是这段使用 swoole_process 的程序有问题,几经更新改版,代码已经趋于完美,平稳的运行了几天后,小伙伴们终于都松了一口气,终于解决了这个问题。 然而,伴随着百年难遇的血月,这块程序还是在一个意想不到的时间又爆了一次,小伙伴们终于意识到,问题的根本并不在这里,想要找出并改掉这个问题,是何等艰难!但是他们知道,真相只有一个! 又经过几天的排查,凶手终于浮出水面! 呐,就是这孙子:openssl_pkey_get_private 抽出的测试代码: 123456789while(true){ $key = file_get_contents('mock_A.pem'); echo $m=memory_get_usage(),PHP_EOL; $getkey = openssl_pkey_get_private($key, ''); openssl_pkey_free($getkey); echo $mm = memory_get_usage(),PHP_EOL; echo "Before unset: ",$mm-$m ,PHP_EOL; sleep(3);} 结果: 审判结果:该阉的就赶紧阉了吧 行刑后长相: 12345678910$key = file_get_contents('mock_A.pem');$getkey = openssl_pkey_get_private($key, '');while(true){ echo $m=memory_get_usage(),PHP_EOL; openssl_pkey_free($getkey); echo $mm = memory_get_usage(),PHP_EOL; echo "Before unset: ",$mm-$m ,PHP_EOL; sleep(3);} 前线专家点评:纯属冤案,哪儿特么这么容易泄漏,都是因为自己写的不规范! 注:生成公私钥是需要消耗内存的,每次脚本执行结束后会释放掉,但是常驻内存的脚本没有释放的机会,所以每次循环调用都会造成内存增加,持续增加到一定量后系统也就崩了。所以解决办法是在循环开始之前就把密钥生成好。

2024-02-28
Laravel Octane 和 Swoole 协程的使用分析
之前在工作中使用 Laravel Octane 的 concurrently 处理并发时,发现在队列和定时任务中不会触发并发效果。经过分析,作了如下猜测:队列定时任务都属于一个独立的进程,与 Octane 服务无关,而 Octane conturrently 恰恰需要在 Octane 环境下才能运行。 后来通过代码进行环境检测和查看 php 的进程,证明猜想成立。 1234info('check env', [ 'served by octane' => isset($_SERVER['LARAVEL_OCTANE']) && ((int)$_SERVER['LARAVEL_OCTANE'] === 1), 'on swoole server' => (extension_loaded('swoole') || extension_loaded('openswoole')) && app()->bound(Server::class)]); 为了能够在任意代码中实现并发,我们研究参考了 Hyperf 框架关于协程的代码,然后抽取了如下两个类: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071<?phpnamespace App\Services;use App\Exceptions\ParallelExecutionException;use Laravel\Octane\Facades\Octane;use Throwable;use Swoole\Coroutine as Co;class Parallel{ protected array $callbacks = []; protected array $results = []; /** * @var Throwable[] */ protected array $throwables = []; public function add(callable $callable, $key = null): void { if (is_null($key)) { $this->callbacks[] = $callable; } else { $this->callbacks[$key] = $callable; } } public function wait(bool $throw = true): array { if (isset($_SERVER['LARAVEL_OCTANE']) && ((int)$_SERVER['LARAVEL_OCTANE'] === 1)) { return Octane::concurrently($this->callbacks, 300000); } app('log')->useLoggingLoopDetection(false); Co\run(function () { foreach ($this->callbacks as $key => $callback) { Co::create(function () use ($callback, $key) { try { $this->results[$key] = $callback(); } catch (Throwable $throwable) { $this->throwables[$key] = $throwable; unset($this->results[$key]); } }); } }); if ($throw && ($throwableCount = count($this->throwables)) > 0) { $message = 'Detecting ' . $throwableCount . ' throwable occurred during parallel execution:' . PHP_EOL . $this->formatThrowAbles($this->throwables); $executionException = new ParallelExecutionException($message); $executionException->setResults($this->results); $executionException->setThrowAbles($this->throwables); unset($this->results, $this->throwables); throw $executionException; } app('log')->useLoggingLoopDetection(true); return $this->results; } private function formatThrowAbles(array $throwables): string { $output = ''; foreach ($throwables as $key => $value) { $output .= sprintf('(%s) %s: %s' . PHP_EOL . '%s' . PHP_EOL, $key, get_class($value), $value->getMessage(), $value->getTraceAsString()); } return $output; }} 1234567891011121314151617181920212223242526272829303132<?phpnamespace App\Exceptions;use RuntimeException;class ParallelExecutionException extends RuntimeException{ protected array $results = []; protected array $throwables = []; public function getResults(): array { return $this->results; } public function setResults(array $results): void { $this->results = $results; } public function getThrowAbles(): array { return $this->throwables; } public function setThrowAbles(array $throwables): array { return $this->throwables = $throwables; }} 其中,第一个类的作用是检测系统是否运行在 Octane 环境下,是则调用Octane concurrently,否则就执行 Swoole 协程代码,使用起来也比较简单: 1234567$parallel = new Parallel();$parallel->add(fn() => $this->analysisStructure(), 'structure');$parallel->add(fn() => $this->analysisHabit(), 'habit');[ 'structure' => $structure, 'habit' => $habit,] = $parallel->wait(); 之所以没有完全使用 Swoole 协程,是因为相比之下,Octane 代码更加优雅,我们在期待着某天更新后,Octane concurrently 也能直接在队列中运行使用。 第二个类的作用比较简单,就是对协程中异常的一个定义。 另外在分析过程中,我们也发现了一个比较有意思的事情: 如图所示,当我在路由中运行检测代码时,Octane 和 Swoole server 都为 true;在控制器中运行检测代码时,又只有 Octane 为true;为什么会有这样的区分?我个人猜测是 Octane 在将框架代码读进内存时,特意跳过了控制器中的代码,以避免数据更新不及时等问题。 有知道具体原因的小伙伴,欢迎留言探讨。

2025-01-22
mac m1 安装 swoole 报错问题记录
今天切本地环境的时候发现 swoole 还没装,装的时候又报了错:pcre2.h file not found,鉴于我这个记性越来越差,还是写条笔记记录一下解决办法。 brew install pcre2,如果本机已经安装,就直接进行第二步 ln -s /opt/homebrew/include/pcre2.h /opt/homebrew/Cellar/php@8.1/8.1.31/include/php/ext/pcre/pcre2.h,正常安装 pcre2 之后,需要软连接对应目录,注意这里要把目录切换成你实际的目录 pecl install swoole,再次运行安装 php -m | grep swoole,运行验证

2019-03-21
laradock 中 php5.6 连接 mysql 报错解决
laradock 中 php 5.6 连接 mysql 报错的解决方法。 之前弄的是默认版本的 php 7.2 和 mysql 8,配置好以后一直都能正常使用。 最近因为需要切换 php 版本,突然发现切换后连接 mysql 时无法识别 host。 emmmm……,最开始还以为是 php 的锅,各种重新 build,无果,最后在 github 上找到别人发出来的解决办法: rm -rf ~/.laradock/data/mysql删除老版本的数据 docker-compose build mysql重新构建 mysql 进入 mysql 容器mysql -uroot -prootALTER USER root IDENTIFIED WITH mysql_native_password BY ‘root’;exit; 到这里也就可以了。 有这个问题是因为 8 和 5.7 的配置不一样,不清掉老数据的话会引起报错,一有请求去连接 mysql 马上就宕机。

2017-12-22
laravel 使用笔记
先从最简单的开始: 安装laravel 的安装需要借助 composer ,百度一下,安装,然后去 GitHub 下载 laravel 切换到项目文件夹 ,在不选中任何文件的前提下按住 shift + 鼠标右键,打开 Powershell 或者 cmd 输入:composer install 将 .env.example 另存为 .env 修改数据库配置信息和邮件系统配置信息 然后在命令行输入:php artisan key:generate 生成密钥 然后配置一个本地域名指向 public 文件夹,然后,就好了。[哈哈] 邮件邮件系统配置示例: 12345678MAIL_DRIVER=smtpMAIL_HOST=smtp.mxhichina.comMAIL_PORT=25//465MAIL_USERNAME=m@m-finderMAIL_PASSWORD=邮箱密码MAIL_FROM_NAME=M-finderMAIL_FROM_ADDRESS=m@m-finderMAIL_ENCRYPTION=null//ssl #如果用465端口的话,需要参数 MAIL_ENCRYPTION=ssl 邮件有 3 种模式(可能更多,暂时只接触到3种):一种用 Mail::send 方法一种用 Mail::raw另外一种则是官方文档中的,新建一个类,然后发送邮件时实例化这个类。3 种方法实现的功能一样。Mail::send 1234Mail::send('admin.email', ['orderPrice' => 'laravel'], function ($message) { $message->to('m@m-finder'); $message->subject('我是自定义标题');}); 这个方法第一个参数为视图文件,视图文件的用法等同于普通视图,第二个参数为视图中所用到的数据Mail::raw 1234567$content = '这是一封来自Laravel的测试邮件.';$toMail = 'm@m-finder'; Mail::raw($content, function ($message) use ($toMail) { $message->subject('[ 测试 ] 测试邮件SendMail - ' . date('Y-m-d H:i:s')); $message->to($toMail);}); 基本等同于Mail::send 第三种方法 12345678910php artisan make:controller MailControllerphp artisan make:mail OrderShipped在 OrderShipped 增加内容return $this->view('admin.email')->with([ 'orderName' => 'test', 'orderPrice' => 1500, ]); 然后在要发送邮件的方法中调用: 1Mail::to('m@m-finder')->send(new OrderShipped()); 开放路由,访问下就可以了。 如果需要自定义邮件标题,可以试一下以下方法(未测试): 在你的类中定义一个subject变量: 1public $subject = '这里是邮件自定义标题'; 或者在你的view后跟一个subject方法: 1view('emails.activate-user')->subject('这里定义邮件标题'); 多视图共享数据在 app\Providers 文件夹下 boot() 方法中写入要共享的数据即可 , 例如 : 123456789101112public function boot() { $links = Link::orderBy('id', 'desc')->get(); $web_info = SysConfig::first(); $menus = Menu::select('id', 'name', 'type', 'seo_title', 'seo_describe', 'link') ->where('pid', '=', 0) ->where('is_show', '=', '2') ->get(); view()->share('links', $links); view()->share('web_info', $web_info); view()->share('menus', $menus);} 这样写完以后,你会发现你的 migrate 挂了,哈哈 ,解决办法是使用闭包,即 composer 方法: 12345678910111213public function boot() { Schema::defaultStringLength(191); //解决数据库版本过低无法执行 migrate view()->composer(['layouts.home', 'layouts.userhome','layouts.admin'], function($view) { $links = Link::orderBy('id', 'desc')->get(); $web_info = SysConfig::first(); $menus = Menu::select('id', 'name', 'type', 'seo_title', 'seo_describe', 'link') ->where('pid', '=', 0) ->where('is_show', '=', '2') ->get(); $view->with(['links' => $links, 'web_info' => $web_info, 'menus' => $menus]); }); } 文件上传config 文件夹下有一个 filesystems.php,里边是默认的上传地址,可以根据自己的需要做修改或者添加 上传的控制器代码: 12345678910111213141516if ($file->isValid()) { if ($file->getClientSize() > 2097152) { return $this->json_response(1, "请上传小于 2 mb 的图片", 0); } $ext = $file->getClientOriginalExtension(); $realPath = $file->getRealPath(); $type = $file->getClientMimeType(); $filename = date('Y-m-d-H-i-s') . '-' . uniqid() . '.' . $ext; $bool = Storage::disk($path)->put($filename, file_get_contents($realPath)); $url = Storage::disk($path)->url($filename); if ($filename) { return ['code' => 0, 'msg' => '', 'src' => $url, 'data'=>['src'=>$url,'title'=>$filename]]; //{"code": 0 ,"msg": "" ,"data": {"src": "图片路径","title": "图片名称"} layui 图片上传接口 }} 访问上传到本地的文件资源,需要先创建一个软连接:php artisan storage:link 一个页面中,如果有 ajax 调取数据的,可以把 ajax 使用的路由和页面的路由名称定义为同一个,不同的是页面是 get ,ajax 是 post 自定义404页面在 app\Exceptions文件夹下的Hander中有个render方法,改造一下: 123456789101112public function render($request, Exception $exception){ if ($exception instanceof ModelNotFoundException) { $exception = new NotFoundHttpException($exception->getMessage(), $exception); } if ( ! config('app.debug')) { return response()->view('errors.500', [], 500); } return parent::render($request, $exception);} 然后在views文件夹新建error文件夹和对应错误代码的blade文件。