Linux文件系统修复

前因

当前的操作系统是openSuSE,系统目录使用的是btrfs文件系统,用户目录使用的是xfs文件系统。由于公司突然间断电,导致系统完全崩溃,开机后直接进入grub修复界面。初步判断是因为,计算机使用的固态硬盘,加上断电时,计算机还在做关键操作,操作完成之后,写文件的校验值checksum和文件不一致。最终导致系统目录无法读取。

处理过程

用户目录挂载盘符为/dev/sdb4
恢复时仅需一行代码:

1
xfs_repair /dev/sdb4 -L

系统目录挂载盘符为/dev/sdb3
恢复时我并没有恢复成功,直接放弃了,反正我也用不到。
但通过查看帮助似乎有下面这种方法:

1
btrfsck --repair --init-csum-tree --init-extent-tree  <device>

但我执行后不成功,于是我直接保留用户目录,然后重新安装了。

缓存的伪随机及定期清理

缓存的伪随机策略

有时候在业务逻辑上,我们需要返回一些随机的数据,越随机越好,这时候缓存较短的时间、不缓存等方式均不理想。缓存一定的时间会导致用户会以为页面没刷新,没取到数据,体验不好。
而若不缓存,大量的请求可能会造成数据库、中间件的巨大压力。
可以通过设置特定的键值+随机数的方式,来人为造成一定数量随机出来的缓存,完美解决随机问题。

1
2
3
4
5
6
7
8
$key = 'key:sample:'.rand(1, 500);
$value = $redis->get($key);
if (!$value) {
$value = self::getData('...');
if ($key) {
$redis->setex($key, rand(600, 1200), $value);
}
}

缓存时间随机+key键值随机,可以产生500种(甚至更多)组合,满足线上的需求。

长期缓存,定期清理

当我们做了缓存之后,有时候缓存的时间不能设置太长,因为总会有一些数据在变更。变更后,如果很长的时间内,缓存一直有效,那么修改的操作等于没有成功。
缓存的时间短,也导致了缓存命中率不高,内存利用率较小,反而增加了多余的两次缓存读写(缓存取不到数据,从数据库/中间件获取,再次写入缓存),性能提升不够明显。
其实对比热数据而言,99%的在库数据,是长时间不会变化的,可以通过检测数据库插入/变更/删除时间,通过特定的调度脚本,及时从缓存种移除。
如果做了这些工作,那么缓存的设置就可以大胆的设置为长期缓存,半年,整月等策略,缓存的命中率能提升一个大台阶。

缓存置换策略的选择

在redis中,当缓存的内存占用量达到峰值maxmemory的时候,这时候就需要用到置换策略了,这个策略不设置或设置错误,都会导致浪费,甚至影响系统正常运行,抛出错误,502网关错误等等。redis一共支持6种策略。

  • noeviction: 不进行置换,表示即使内存达到上限也不进行置换,所有能引起内存增加的命令都会返回error
  • allkeys-lru: 优先删除掉最近最不经常使用的key,用以保存新数据
  • volatile-lru: 只从设置失效(expire set)的key中选择最近最不经常使用的key进行删除,用以保存新数据
  • allkeys-random: 随机从all-keys中选择一些key进行删除,用以保存新数据
  • volatile-random: 只从设置失效(expire set)的key中,选择一些key进行删除,用以保存新数据
  • volatile-ttl: 只从设置失效(expire set)的key中,选出存活时间(TTL)最短的key进行删除,用以保存新数据

缓存与基础业务系统分离

如果基础业务系统与缓存放在同一redis库,那么设置了volatile-lru,就会导致正常用户可能被迫下线。设置了volatile-ttl就会导致一些缓存时间较短的缓存,没有起到作用,频繁被擦除,影响命中率,缓存效果也不理想。
若缓存与基础业务系统分离,既可以优化缓存的命中率等考核条件。同时也可以保证用户正常的使用不受影响,如session、用户登录状态、邮箱/手机验证码等数据。

session

在使用session时,应选择按照volatile-ttl策略,并发量高时,删除长期不在线的用户。

缓存

在缓存方面,通过volatile-lru,近期最少使用算法,讲命中率低的缓存及时删除,把空间让给更需要的缓存。

开启TCP BBR算法

开启BBR

开启之前,需先升级至4.9内核,开启bbr算法,vim /etc/sysctl.conf

1
2
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

执行,使其保存生效

1
sysctl -p

检查是否开启

检查内核是否已开启bbr

1
2
sysctl net.ipv4.tcp_available_congestion_control
lsmod | grep bbr

openSUSE升级Linux内核版本

可以通过官方的一个网站package查找到所需要的包,选择新的版本或者当
前版本,选择对应的版本下载。

安装Linux 4.9

1
rpm -ivh kernel-default-4.9.0-2.1.x86_64.rpm

迁移Redis过程中存在的问题总结

需求的产生

由于公司业务需求,我对较高访问量的页面做了全局随机缓存优化,主要缓存了部分API、RPC接口。特点是代码层无需变化,无需改变原有逻辑,有较高的可控性和可维护性,降低了代码的冗余。同时对缓存做了进一步的封装,便于下一步的维护与变更。
增加随机缓存主要是防止缓存数据穿透问题,就是确保缓存数据不会同时在一个特定的时间全部失效,进而导致RPC、数据库请求剧增。
同时提供了打印请求过程、记录参数、超时告警、请求时间、超时自动结束等功能。便于进一步跟踪代码的执行情况。

产生的问题

由于原先的Memcached使用的是代理机制,1带4的方案,缓存的一天请求数可能接近1亿次左右,老的Memcached可能没有考虑到大规模应用缓存的需求,在高峰时期,有可能会挂掉。
甚至由于Memcached所在的服务器单一IP,Memcached的请求数在某一时刻会达到好几万,巨大的请求数没有及时结束,造成了大量的Time Wait,已经影响到正常的PHP服务。

期间,我们也做过一些尝试,看能否更换机器解决,最终发现还是远远无法满足未来的需求,急需扩容。

Redis的选择

公司原有众多业务也有使用到Redis、Kafka,考虑到Redis支持的类型更多,在扩容的时候,直接切换到Redis似乎是更好的选择。

Redis Cluster

一开始我们定了这个方案,一是他的高可用,二是它能够做到自动扩容,可以把机器当成资源池使用。同时配置了多台主从,理论上应该是性能超佳才是。
但在测试阶段就遇到众多问题,发现在单台服务器连接的时候,就出现了请求数过大、网卡过载、连接超时等问题。主要原因可能是:
1、长连接及连接池,PHP的机制导致了PHP是没有内存这个说法,没有办法建立一个长连接,然后让所有进程来共享使用,同时由于默认自带的参数是使用的长连接的形式,会导致进程结束,但是连接并未结束,一直占用着端口。在使用PHPRedis发现无法解决存在的问题,我还尝试了使用PRedis,发现问题还是一样,巨大的连接数对服务器是一个很大的负担,通过修改系统配置也没有很好的解决。
2、网卡可能不够用了,Redis Cluster的机制我没有特别深入去了解,也不是很明白为什么几千的请求数就能够把千兆网卡打满,这是否说明了Redis Cluster不仅仅把数据放大了2倍,而是n倍?同时还需要以转发的形式,来取得真正的值?这与Redis支持10万请求/秒的理论值相距深远。
3、Redis Cluster可能还不够成熟,大规模应用是否可行、是否经过完全测试?文档不齐全、案例较少、没有一个很好的PHP客户端也是一个大问题。

其中有一个同事提出能否由PHP端来分片,使用短连接,直接写入Redis Cluster,最终我们发现,似乎原有方法不支持这个功能,连接单机时会提示错误。

Redis

最终由于时间有限,我们暂时是选择了Redis单机的模式,经过对Linux系统的配置,降低了实时的请求数占用情况。同时在代码层实现分片功能。

1
2
3
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1

升级OpenSSL支持ALPN

升级OpenSSL

我使用的Linux系统是CentOS7,但是OpenSSL的版本其实还是很低,并不能够及时支持google chrome的新版本。而HTTP/2还是需要服务端的ssl提供支持,这是一个前提,我选择了openssl-1.1.0b。
首先要下载,解压,编译。

1
2
3
4
5
6
7
8
wget https://www.openssl.org/source/openssl-1.1.0b.tar.gz
tar zxf openssl-1.1.0b.tar.gz
cd openssl-1.1.0b
./config --prefix=/usr --shared
make
make depend
make test
make install

先查看当前版本情况,做好备份。

1
2
openssl version
mv /usr/bin/openssl /root/

替换,查看版本,重启nginx即可,若不行,可能还需要重新安装nginx。

1
2
3
ln -s /usr/local/ssl/bin/openssl /usr/bin/openssl
openssl version
lnmp nginx reload

使用swoole搭建socket长连接

Swoole简介

通常基于php扩展使用纯PHP就可以完全实现异步网络服务器和客户端程序。但是想实现一个类似于多IO线程,还是有很多繁琐的编程工作要做,包括如何来管理连接,如何来保证数据的收发原则性,网络协议的处理。
开源项目Swoole使用C语言和PHP结合来完成了这项工作。灵活多变的业务模块使用PHP开发效率高,基础的底层和协议处理部分用C语言实现,保证了高性能。它以扩展的方式加载到了PHP中,提供了一个完整的网络通信的框架,然后PHP的代码去写一些业务。它的模型是基于多线程Reactor+多进程Worker,既支持全异步,也支持半异步半同步。

Swoole的特点

  • Accept线程,解决Accept性能瓶颈和惊群问题
  • 多IO线程,可以更好地利用多核
  • 提供了全异步和半同步半异步2种模式
  • 处理高并发IO的部分用异步模式
  • 复杂的业务逻辑部分用同步模式
  • 底层支持了遍历所有连接、互发数据、自动合并拆分数据包、数据发送原子性。

为何选择Swoole及Swoole性能

包括腾讯企业QQ、聚美优品等众多公司的大规模部署和实践。
我在网站挂载的服务器上做了测试,由于服务器配置可能不高,仅能支持2万并发。具体性能还有待调整提高。

简单部署

选择了8000端口作为socket连接端口,其中server.php代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//创建websocket服务器对象,监听0.0.0.0:9502端口
$ws = new swoole_websocket_server("0.0.0.0", 8000);

//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
var_dump($request->fd, $request->get, $request->server);
$ws->push($request->fd, "hello, welcome\n");
});

//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
echo "Message: {$frame->data}\n";
$ws->push($frame->fd, "server: {$frame->data},this is yatesun");
});

//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
echo "client-{$fd} is closed\n";
});

$ws->start();

执行php server.php,启动swoole服务,监听8000端口,可以在系统中查看到端口已经监听成功,接下来可以通过chrome中的Console,执行js代码,来测试服务是否正常运行。

JS调试

1
2
3
4
5
6
7
var exampleSocket = new WebSocket("ws://119.29.182.88:8000");
exampleSocket.onopen = function (event) {
exampleSocket.send("亲爱的服务器!我连上你啦!");
};
exampleSocket.onmessage = function (event) {
console.log(event.data);
};

接下来执行下面代码,则能收到服务器返回的信息:

1
exampleSocket.send("爱你哦");

server: 爱你哦,this is yatesun

任务调度celery

Celery

Celery是Python开发的分布式任务调度模块,本身不含消息服务,它使用第三方消息服务来传递任务,目前,Celery支持的消息服务有RabbitMQ、Redis甚至是数据库,当然Redis应该是最佳选择。

安装

1
sudo pip install Celery

使用Redis作为Broker时,再安装一个celery-with-redis。
开始编写tasks.py:

1
2
3
4
5
6
7
8
9
10
11
# tasks.py
import time
from celery import Celery

celery = Celery('tasks', broker='redis://localhost:6379/0')

@celery.task
def sendmail(mail):
print('sending mail to %s...' % mail['to'])
time.sleep(2.0)
print('mail sent.')

然后启动Celery处理任务

1
celery -A tasks worker --loglevel=info

上面的命令行实际上启动的是Worker,如果要放到后台运行,可以扔给supervisor。

如何发送任务?非常简单:

1
2
3
>>> from tasks import sendmail
>>> sendmail.delay(dict(to='celery@python.org'))
<AsyncResult: 1a0a9262-7858-4192-9981-b7bf0ea7483b>

Celery默认设置就能满足基本要求。Worker以Pool模式启动,默认大小为CPU核心数量,缺省序列化机制是pickle,但可以指定为json。由于Python调用UNIX/Linux程序实在太容易,所以,用Celery作为异步任务框架非常合适。

自动拉取代码小脚本

GIT半自动部署

有时候,正式环境可能由多个计算机群组组成,可能是几台,也可能是几十台服务器,如果需要一一拉取代码,有些不方便。
因此可以通过一台专门的服务器,给他一个指令,于是他自动去这写服务器群组拉取代码。

1
2
3
4
5
6
7
8
9
10
ips=("172.16.0.1" "172.16.0.2" "172.16.0.3")
wwwroot=("app1" "app2" "api1" "api2")
for ip in ${ips[*]}
do
for www in ${wwwroot[*]}
do
echo $ip-$www;
ssh $ip 'cd /pathto/'$www'/ && git pull'
done
done

数据结构的学习笔记

数据结构的学习

早期:计算机科学是研究算法的科学
近期:计算机科学是研究数据的科学

数据结构是程序设计的基础

程序=算法+数据结构(两大支柱)
通过算法对数据结构进行操作与控制

早期:程序就是软件,软件就是程序
近期:接口、消息的传递、技术文档、模块化、数据类型,软件=程序+文档
现在:只需要学会一门语言,对相应的指导性文件进行翻译即可。

DS主要研究内容

数据的各种逻辑结构和物理结构,以及他们直接按的相应关系
对各种结构定义相适应的各种运算
设计出相应的算法
分析算法的效率

基本术语

数据可以根据不同特性,分解为若干个不同的数据对象。数据对象由包含了若干个不同的个体,称为数据元素。每个数据元素由不同的数据项加以描述,数据项是数据集合中最小的有意义单位。
数据结构:是带有结构的数据元素的集合
结构:数据元素之间的关系,即数据元素之间的运算及运算规则
DS=(D,R)
D是数据元素的集合,R是D上关系的集合
逻辑结构(Logical Structure):指数据元素之间的结构关系
物理结构(Physical Structure):指数据结构在机内的表示
物理结构要满足逻辑结构的定义
算法的基本特性:有穷性、确定性、可行性

程序生命周期:
1、需求调研,得出需求规格说明书,对需求分析,划分子系统、模块、功能,并描述
2、概要分析,做出概要设计说明书
3、通过自然语言设计算法
4、编码实现,将自然语言翻译成为某种程序语言

程序与算法区别:
程序可以是无穷的,算法是有穷的
程序可以是错误的,算法是正确的
程序是用程序设计语言描述,在机器上可以执行
算法还可以用框图、自然语言等方式描述
程序是给机器执行的,算法是给人看的

过程与函数的区别:
过程没有返回值,函数有返回值

衡量算法的三个尺度:
时间
空间
可读性、易调性、健壮性
(时间和空间特性的巨大改进源于更好的数据结构或算法)

Laravel 5.3新改变

前言

laravel5.3提供了全新的通知方式Notification,同时增加了较多的第三方Channels,有一些方法还是发生了变化。

部分文件有内容有差异

首先是app/Providers/RouteServiceProvider.phpapp/Providers/EventServiceProvider.php文件中,如boot()方法发生了变化。
laravel 5.2:

1
2
3
4
public function boot(Router $router)
{
parent::boot($router);
}

laravel 5.3

1
2
3
4
5
6
public function boot()
{
//

parent::boot();
}

laravel 5.3还去掉了Controller中的Illuminate\Foundation\Auth\Access\AuthorizesResources

DB查询构建器

原有的DB操作中有->lists()方法,修改成->pluck()。

中间件设置

由于原来使用的是middlewareGroups,现在改成middleware之后,需要在middleware中增加session支持

1
\Illuminate\Session\Middleware\StartSession::class,

启用CSRF跨站请求伪造保护

Illuminate/Foundation/Http/Middleware/VerifyCsrfToken中的handle()加入CSRF不通过时的策略,同时对所有POST方法增加VerifyCsrfToken。
在中间件VerifyCsrfToken中,也可以加入一些排除的url,如其他服务器的回调,微信的回调等等,可以去掉CSRF保护。

1
2
3
protected $except = [
'*/callback/*',
];