Restful Api 风格规范研究

状态码用http status code 还是 在response body 中自定义code

列举总结一下各大厂的API的设计风格

总结

  • 国内资讯类大厂出于兼容性考虑 以及杀毒软件和安全浏览器 拦截404 页面 等并未采用REST API
  • 业务复杂度高的开放平台也未采用Rest API 如支付宝,微信,微博
  • 国外API多采用REST API 包括很多框架的设计(PHP Laravel)

百度手机站

jsonp http status code 200

百度PC站

jsonp ,收集搜索数据接口 http status code 200

今日头条PC

json,使用message区分状态 http status code 200

今日头条手机站

json http status code 200 has_more 区分

新浪微博PC

json http status code 200 直接返回html

微博手机站

Google手机站

restful api

极光推送

RestFul API HTTP STATUS CODE + Response Body 自定义CODE

GitHub

Restful API https://developer.github.com/v3/#client-errors

个人结论

遵守RFC规范,遵守业内标准,融入生态,工业技术是要标准化的。

更详细的论述看左耳朵耗子的文章 “一把梭: REST API 全用 POST”

应用案例

laravel框架默认的错误信息返回风格

去掉最外层data数据包裹 文档

参考

Linux网络调试 常用命令和分析工具 排查方法

查看/配置网络

ifconfig 查看IP地址

https://wangchujiang.com/linux-command/c/ifconfig.html

enx00e04cf0a10c: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.5  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 2408:8207:305c:1c30:b48f:56c7:81bb:bb9  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::2c1b:1b85:35cd:9642  prefixlen 64  scopeid 0x20<link>
        inet6 2408:8207:305c:1c30:e49b:195a:eeaa:1e44  prefixlen 64  scopeid 0x0<global>
        ether 00:e0:4c:f0:a1:0c  txqueuelen 1000  (Ethernet)
        RX packets 29715  bytes 31484642 (31.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 22107  bytes 3794564 (3.7 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 4003  bytes 2920205 (2.9 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4003  bytes 2920205 (2.9 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
 
wlp4s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.111  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::6e92:514f:2d68:1da1  prefixlen 64  scopeid 0x20<link>
        ether 14:4f:8a:60:f2:2a  txqueuelen 1000  (Ethernet)
        RX packets 106  bytes 9074 (9.0 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 129  bytes 19965 (19.9 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

e开头的一般是有线网卡 例eth

lo 全称是 loopback,又称环回接口,往往会被分配到 127.0.0.1 这个地址。这个地址用于本机通信,经过内核处理后直接返回,不会在任何网络中出现

wlp开头表示无线网卡

flags = 4163 没有搜到什么意思

UP表示接口启用

BROADCAST 表示主机支持广播

RUNNING 表示接口正在工作

MULTICAST 主机支持多播

MTU:1500 最大传输单元 1500字节 百科

inet 网卡的IP地址

netmask 网络掩码

broadcast 广播地址

inet6 fe80::2aa:bbff:fecc:ddee prefixlen 64 scopeid 0x20<link> IPv6地址

ether 00:e0:4c:f0:a1:0c 连接类型:Ethernet (以太网) HWaddr (硬件mac地址)

txqueuelen (网卡设置的传送队列长度)

RX packets 接收时,正确的数据包数

RX bytes 接收的数据量

RX errors 接收时,产生错误的数据包数

RX overruns 接收时,由于速度过快而丢失的数据包数

RX dropped 接收时,丢弃的数据包数

RX frame 接收时,发生frame错误而丢失的数据包数

TX carrier 发送时,发生carrier错误而丢失的数据包数

TX collisions 冲突信息包的数目

mii-tool 配置网络设备协商方式的工具

https://wangchujiang.com/linux-command/c/mii-tool.html

是用于查看、管理介质的网络接口的状态,有时网卡需要配置协商方式,比如10/100/1000M的网卡半双工、全双工、自动协商的配置。但大多数的网络设备是不用我们来修改协商,因为大多数网络设置接入的时候,都采用自动协商来解决相互通信的问题。不过自动协商也不是万能的,有时也会出现错误,比如丢包率比较高,这时就要我们来指定网卡的协商方式。mii-tool就是能指定网卡的协商方式。下面我们说一说mii-tool的用法。

sudo mii-tool -v enx00e04cf0a10c
enx00e04cf0a10c: negotiated 100baseTx-FD, link ok
product info: vendor 00:07:32, model 0 rev 0
basic mode: autonegotiation enabled
basic status: autonegotiation complete, link ok
capabilities: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD
advertising: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD flow-control
link partner: 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD

link ok 表示网线连接ok

basic mode: autonegotiation enabled 启用自动协商

route 查看网关

https://wangchujiang.com/linux-command/c/route.html

显示并设置Linux中静态路由表

常用方式 route -n 不执行DNS反向查找,直接显示数字形式的IP地址

route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.1.1     0.0.0.0         UG    100    0        0 enx00e04cf0a10c
0.0.0.0         192.168.0.1     0.0.0.0         UG    600    0        0 wlp4s0
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 wlp4s0
192.168.0.0     0.0.0.0         255.255.255.0   U     600    0        0 wlp4s0
192.168.1.0     0.0.0.0         255.255.255.0   U     100    0        0 enx00e04cf0a10c

ip 网络配置工具

https://wangchujiang.com/linux-command/c/ip.html

用来显示或操纵Linux主机的路由、网络设备、策略路由和隧道

支持信息的object

OBJECT := { link | address | addrlabel | route | rule | neigh | ntable | tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm | netns | l2tp | macsec | tcp_metrics | token }

nc 全称netcat

用于设置路由器。它能通过 TCP 和 UDP 在网络中读写数据。通过与其他工具结合和重定向,你可以在脚本中以多种方式使用它。

https://wangchujiang.com/linux-command/c/nc.html

查看端口实例

nc -vz xiaoyuapi.zcoming.com 443 -w2
Connection to xiaoyuapi.zcoming.com 443 port [tcp/https] succeeded!

故障排除

ping 测试主机之间网络的连通性

https://wangchujiang.com/linux-command/c/ping.html

执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常

traceroute 显示数据包到主机间的路径

https://wangchujiang.com/linux-command/c/traceroute.html

用于追踪数据包在网络上的传输时的全部路径,它默认发送的数据包大小是40字节。

通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。

traceroute通过发送小的数据包到目的设备直到其返回,来测量其需要多长时间。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称(如有的话)及其ip地址。

traceroute www.58.com
traceroute to www.58.com (211.151.111.30), 30 hops max, 40 byte packets
 1  unknown (192.168.2.1)  3.453 ms  3.801 ms  3.937 ms
 2  221.6.45.33 (221.6.45.33)  7.768 ms  7.816 ms  7.840 ms
 3  221.6.0.233 (221.6.0.233)  13.784 ms  13.827 ms 221.6.9.81 (221.6.9.81)  9.758 ms
 4  221.6.2.169 (221.6.2.169)  11.777 ms 122.96.66.13 (122.96.66.13)  34.952 ms 221.6.2.53 (221.6.2.53)  41.372 ms
 5  219.158.96.149 (219.158.96.149)  39.167 ms  39.210 ms  39.238 ms
 6  123.126.0.194 (123.126.0.194)  37.270 ms 123.126.0.66 (123.126.0.66)  37.163 ms  37.441 ms
 7  124.65.57.26 (124.65.57.26)  42.787 ms  42.799 ms  42.809 ms
 8  61.148.146.210 (61.148.146.210)  30.176 ms 61.148.154.98 (61.148.154.98)  32.613 ms  32.675 ms
 9  202.106.42.102 (202.106.42.102)  44.563 ms  44.600 ms  44.627 ms
10  210.77.139.150 (210.77.139.150)  53.302 ms  53.233 ms  53.032 ms
11  211.151.104.6 (211.151.104.6)  39.585 ms  39.502 ms  39.598 ms
12  211.151.111.30 (211.151.111.30)  35.161 ms  35.938 ms  36.005 ms

记录按序列号从1开始,每个纪录就是一跳 ,每跳表示一个网关,我们看到每行有三个时间,单位是ms,其实就是-q的默认参数。探测数据包向每个网关发送三个数据包后,网关响应后返回的时间;如果用traceroute -q 4 www.58.com,表示向每个网关发送4个数据包。

有时我们traceroute一台主机时,会看到有一些行是以星号表示的。出现这样的情况,可能是防火墙封掉了ICMP的返回信息,所以我们得不到什么相关的数据包返回数据。

有时我们在某一网关处延时比较长,有可能是某台网关比较阻塞,也可能是物理设备本身的原因。当然如果某台DNS出现问题时,不能解析主机名、域名时,也会 有延时长的现象;您可以加-n参数来避免DNS解析,以IP格式输出数据。

如果在局域网中的不同网段之间,我们可以通过traceroute 来排查问题所在,是主机的问题还是网关的问题。如果我们通过远程来访问某台服务器遇到问题时,我们用到traceroute 追踪数据包所经过的网关,提交IDC服务商,也有助于解决问题;但目前看来在国内解决这样的问题是比较困难的,就是我们发现问题所在,IDC服务商也不可能帮助我们解决。

mtr My traceroute

https://www.cnblogs.com/alexyuyu/articles/2811346.html

Linux 网络诊断工具MTR(My traceroute) 结合了ping跟tracert的一个工具

首先看最后一行,目标服务器有没有丢包,有的话再往前分析。

测试指定端口实例

sudo mtr -P 443 -i 0.5 -rwc 50 xiaoyuapi.zcoming.com

nslookup

是常用域名查询工具,就是查DNS信息用的命令。

https://wangchujiang.com/linux-command/c/nslookup.html

非交互模式直接加域名

nslookup xiaoyuapi.zcoming.com
Server:		127.0.0.53
Address:	127.0.0.53#53

Non-authoritative answer:
Name:	xiaoyuapi.zcoming.com
Address: 47.92.203.163

telnet 登录远程主机和管理(测试ip端口是否连通)

https://wangchujiang.com/linux-command/c/telnet.html

telnet因为采用明文传送报文,安全性不好,很多Linux服务器都不开放telnet服务,而改用更安全的ssh方式了。但仍然有很多别的系统可能采用了telnet方式来提供远程登录,因此弄清楚telnet客户端的使用方式仍是很有必要的

检测端口,如下表示端口正常

telnet xiaoyuapi.zcoming.com 443
Trying 47.92.203.163...
Connected to xiaoyuapi.zcoming.com.
Escape character is '^]'.

tcpdump

https://wangchujiang.com/linux-command/c/tcpdump.html

 是一款抓包,嗅探器工具,它可以打印所有经过网络接口的数据包的头信息,也可以使用-w选项将数据包保存到文件中,方便以后分析

netstat 查看Linux中网络系统状态信息

https://wangchujiang.com/linux-command/c/netstat.html

可以查看网络协议tcp/udp ip地址监听状态 程序进程id等

netstat -ltnp //查看监听中的进程

ss socket统计信息

https://wangchujiang.com/linux-command/c/ss.html

比 netstat 好用的socket统计信息,iproute2 包附带的另一个工具,允许你查询 socket 的有关统计信息

命令安装

有些发行版可能没有这些命令需要自己安装

net-tools包含以下常用的命令:apt-get install net-tools

  • ifconfig:显示和配置网络接口的信息。
  • netstat:显示网络连接、路由表和网络统计信息。
  • arp:显示和操作ARP缓存。
  • route:显示和操作IP路由表。
  • hostname:显示或设置系统的主机名。

iproute2包含以下常用的命令: apt-get install iproute2

  • ip:用于配置和管理网络接口、路由表、策略路由、隧道、桥接等各种网络参数。
  • ss:显示套接字统计信息,包括网络连接、监听端口、进程关联的套接字等。
  • bridge:用于配置和管理网络桥接。
  • tc:配置和管理流量控制策略。
  • ipset:管理iptables使用的IP集合。
  • nstat:显示网络统计信息。
  • rtacct:显示路由表统计信息。

traceroute命令需要 apt-get install traceroute

网络监控工具

nethogs https://wangchujiang.com/linux-command/c/nethogs.html

//安装
sudo apt install nethogs
sudo yum install nethogs

//使用
sudo nethogs -d 1
sudo nethogs eth0 -d 1
GUI

网络问题排查思路

1.查看服务器服务是否正常,你的网络能否正常访问

当用户反馈APP或网站无法访问或卡顿的时候,首先用你的网络测试一下能否正常访问,

然后排查服务器是否有问题CPU,内存,网络,指标是否正常,进程数量,TCP连接数量 平均负载

各种日志,负载均衡日志,nginx日志,程序日志

2.客户端排查,收集详细的用户反馈

先让用户访问一些常用的网站和app,比如百度等,排除是否是用户的网络问题

收集问题用户数据,手机型号, 操作系统,网络类型(3G,4G,wifi) 运营商(移动,联通,电信),点击哪个模块哪个功能出现的网络故障提示,什么时间点,最好能录屏或者截个图。

或者在程序中增加功能,网络不好的时候弹窗提示用户反馈信息,用户点击反馈按钮收集客户端的ip操作系统型号,上报给服务器并记录下来,通过ip可以获取网络运营商的信息。

3.通过第三方测试平台,模拟用户的网络环境

站长之家测速工具

云服务提供商也用类似站点监控的工具 通过以上工具测试否为运营商问题

4.APP(移动测试)远程真机调试

参考

手机远程真机调试平台汇总
有哪些好用的App云测试平台

目前对移动App的测试主要指的是下面几部分:

兼容性测试——App对不同手机、操作系统版本的兼容性测试,包括安装、启动、卸载等。
功能测试——遍历应用的每一个角落,查看应用的功能、逻辑是否正常,完整。
性能测试——应用的性能怎样,如启动时间、反应时间、CPU占用率,内存占用率等。
稳定性测试——在一定时间内对App进行持续地测试,测试App运行的稳定性。
网络场景测试——测试不同网络环境中App的运行状况,如2G&3G,弱网络等。

Testin:Testin云测|全球领先的App及手游测试平台

腾讯优测:优测网-让测试更简单

贯众云测试:贯众云测试

百度MTC:百度移动云测试中心

阿里MQC:MQC 阿里移动质量中心

参考


ORM扣库存导致的BUG总结

前一段时间,以前一个项目的积分兑换功能出现了库存超扣现象,排查问题时,一看更新时没有加锁,于是以为是锁的问题,加锁之后没有仔细推敲就汇报bug修复完成.

//初始代码
DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存
 $goods_model->stock-=$request->number;
 $goods_model->save();
 ...
});

//第一次修复BUG,增加悲观锁 for update
DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::lockForUpdate()->find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存
 $goods_model->stock-=$request->number;
 $goods_model->save();
 ...
});

昨天接的报告,库存超扣BUG又出现了,立即查看Binlog 日志排查sql执行记录

看日志,同一时刻,有两个线程(386609669,370064926)更新库存,线程386609669 先将库存更新为2 ,线程370064926随后又将库存更新为9 ,并且响应时间760ms ,猜想是数据库响应问题,正考虑其它解决方案,队列或限流等.

看日志nginx,发现在抢购兑换时间内只有几十的并发,看了一下RDS的资源统计使用率不高,感觉悲观锁可以应付的来,不是性能问题.

继续看binlog 日志,发现库存被 线程370064926更新为9之后,又被其它线程386850322更新为3,继续往后看发现同一时间内有好多线程在同时更新库存而且更新库存数量不相同.

百思不得其解,网上Google Mysql高并发扣库存方案,其中有一只乐观锁方案,提到在事物中更新库存时,要比对程序中的库存数量和mysql中的库存数量,突然间恍然大悟,仔细一看代码.果然,犯了一个相当愚蠢的错误.

DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::lockForUpdate()->find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存
 $goods_model->stock-=$request->number; 标注1
 $goods_model->save();
 
 ...
});

问题分析

标注1

更新使用的是程序模型中的库存数量 – 兑换数量

假设stock为 9 number 为 2
上述写法生成的sql 为 update goods set stock = 7 where id = ?
在高并发场景下这么写显然是非常错误的. 假设商品的初始库存为10,ABC三个用户同一时刻兑换商品,ABC读取到程序模型中的库存数量 $goods_model->stock 都是10 ,由于用了悲观锁for update(这不是重点,不用悲观锁也会出现这种情况) 更新语句变为阻塞执行,用户A将库存改为9,用户BC同样将库存改为9,最后库存剩余还是9,这显然与预期结果不服,正确的剩余库存应该为7

扣库存应该采用 Update goods set stock = stock -1 where id = ? 这种语句,更新时读取的是当前记录的stock的值.最后修正结果代码如下:

//最后修正
DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::lockForUpdate()->find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存 正确的操作 或者使用原生update
  $goods_model->decrement('stock');
 ...
});

decrement 方法生成的语句为update goods set stock = stock -1 where id = ?

总结

  • 外包项目写多了忽略了程序在高并发场景下可能发生的情况.高并发场景要考虑进程线程之间数据一致性和以及程序和数据库(包含nosql) 的数据一致性问题
  • 遇到问题要反复仔细验证,不要轻易下结论

疑问

使用错误的的写法用JMeter模拟并发兑换 20threads/1s 200threads/1s 都没有出现库存超扣现象,由于生产环境不允许压测,所以在本机测试 .猜测可能本机的配置高于服务器,处理能力高于服务器所有没有复现,以后验证成功回头再补充

本机(8核16G) 服务器(4核8G)而且跑了很多项目

解决方案引申

乐观锁

//乐观锁
    public function caseOne(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                Rule::exists('goods', 'id')->where(function ($query) use ($request) {
                    $query->where('stock', '>=', $request->number);
                }),
            ]
        ]);
        DB::transaction(function () use ($request) {
            $goods = Goods::find($request->goods_id);
            $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
            if (!$result) {
                return response()->json(['message' => '失败']);
            }
            Orders::create([
                'goods_id' => $goods->id,
                'goods_name' => $goods->name,
                'number' => $request->number,
            ]);
        });

        return response()->json();
    }

悲观锁

//悲观锁
    public function caseTwo(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                Rule::exists('goods', 'id')->where(function ($query) use ($request) {
                    $query->where('stock', '>=', $request->number);
                }),
            ]
        ]);
        DB::transaction(function () use ($request) {
            $goods = Goods::lockForUpdate()->find($request->goods_id);
            $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
            if (!$result) {
                return response()->json(['message' => '失败']);
            }
            Orders::create([
                'goods_id' => $goods->id,
                'goods_name' => $goods->name,
                'number' => $request->number,
            ]);
        });

        return response()->json();
    }

不用事务

//不用事务
    public function caseThree(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                Rule::exists('goods', 'id')->where(function ($query) use ($request) {
                    $query->where('stock', '>=', $request->number);
                }),
            ]
        ]);
        $goods = Goods::find($request->goods_id);
        $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
        if (!$result) {
            return response()->json(['message' => '失败']);
        }
        Orders::create([
            'goods_id' => $goods->id,
            'goods_name' => $goods->name,
            'number' => $request->number,
        ]);
    }

redis库存

//redis库存
    public function caseFour(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                function ($attributes, $value, $fail) use ($request) {
                    $stock = Redis::get('goods_id_' . $value);
                    if (is_null($stock)) {
                        return $fail('商品不存在');
                    }
                    if ($stock < $request->number) {
                        return $fail('库存不足');
                    }
                }
            ]
        ]);
        $goods = Goods::find($request->goods_id);
        $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
        if (!$result) {
            return response()->json(['message' => '失败']);
        }
        Orders::create([
            'goods_id' => $goods->id,
            'goods_name' => $goods->name,
            'number' => $request->number,
        ]);
        Redis::decr('goods_id_' . $request->goods_id);

        return response()->json();
    }

队列

public function caseFive(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                function ($attributes, $value, $fail) use ($request) {
                    $stock = Redis::get('goods_id_' . $value);
                    if (is_null($stock)) {
                        return $fail('商品不存在');
                    }
                    if ($stock < $request->number) {
                        return $fail('库存不足');
                    }
                }
            ]
        ]);
        $goods = Goods::find($request->goods_id);
        SecKill::dispatch($goods, $request->all());

        return response()->json();
    }

https://github.com/yangliuan/shop-demo 有待完善

微博删除脚本

// ==UserScript==

// @name Weibored.js

// @namespace http://vito.sdf.org

// @version 0.2.0

// @description 删除所有微博

// @author Vito Van

// @match http://weibo.com/p/*

// @grant none

// ==/UserScript==

'use strict';

var s = document.createElement('script');

s.setAttribute(

'src',

'https://lib.sinaapp.com/js/jquery/2.0.3/jquery-2.0.3.min.js'

);

s.onload = function() {

setInterval(function() {

if (!$('a[action-type="feed_list_delete"]')) {

$('a.next').click();

} else {

$('a[action-type="feed_list_delete"]')[0].click();

$('a[action-type="ok"]')[0].click();

}

// scroll bottom let auto load

$('html, body').animate({ scrollTop: $(document).height() }, 'slow');

}, 800);

};

document.head.appendChild(s);

打开微博个人首页.F12打开控制台,输入代码回车

Laravel Config 缓存原理

参考

加载过程

  • laravel的所有配置文件都在config目录下
  • 启动时加载所有config目录下的所有配置文件
  • 通过Dotenv 类库加载.env文件中的配置项到预定义全局变量$_ENV中
  • env函数中使用getenv()来获取环境变量$_ENV中的值
  • config文件中使用env()加载配置值
  • config()函数调用config文件中的配置项

使用配置文件时要注意严格遵守约定,在config文件中调用env()函数

在路由,控制器和模型以及自定义类文件中必须使用config()函数获取配置项的值

Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;
 /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        //此处判断是否开启缓存配置,开启缓存配置直接返回缓存中的配置
        //因此开启缓存配置后,env()函数会失效
        if ($app->configurationIsCached()) {
            return;
        }

        $this->checkForSpecificEnvironmentFile($app);
        //此处加载.env中的文件
        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
        } catch (InvalidPathException $e) {
            //
        }
    }
/**
     * Gets the value of an environment variable.
     *
     * @param  string  $key
     * @param  mixed   $default
     * @return mixed
     */
    function env($key, $default = null)
    {
        $value = getenv($key);

        if ($value === false) {
            return value($default);
        }

        switch (strtolower($value)) {
            case 'true':
            case '(true)':
                return true;
            case 'false':
            case '(false)':
                return false;
            case 'empty':
            case '(empty)':
                return '';
            case 'null':
            case '(null)':
                return;
        }

        if (strlen($value) > 1 && Str::startsWith($value, '"') && Str::endsWith($value, '"')) {
            return substr($value, 1, -1);
        }

        return $value;
    }
/**
     * Get / set the specified configuration value.
     *
     * If an array is passed as the key, we will assume you want to set an array of values.
     *
     * @param  array|string  $key
     * @param  mixed  $default
     * @return mixed|\Illuminate\Config\Repository
     */
    function config($key = null, $default = null)
    {
        if (is_null($key)) {
            return app('config');
        }

        if (is_array($key)) {
            return app('config')->set($key);
        }

        return app('config')->get($key, $default);
    }
//app('config) 生成的是 Illuminate\Config\Repository 实例

开启缓存配置

php artisan config:cache

/**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {  
        //调用清楚缓存命令
        $this->call('config:clear');
       //加载所有配置项
        $config = $this->getFreshConfiguration();
       //使用var_export导出php可以执行的数组
       //文件系统使用file_put_contents 生成 配文件 config.php
       //配置文件缓存目录 app/bootstrap/cache/config.php
        $this->files->put(
            $this->laravel->getCachedConfigPath(),
            '<?php return ' . var_export($config, true) . ';' . PHP_EOL
        );

        $this->info('Configuration cached successfully!');
    }

php artisan config:clear

/**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {   
        //laravel文件系统使用 unlink 删除 缓存的config.php文件
        $this->files->delete($this->laravel->getCachedConfigPath());

        $this->info('Configuration cache cleared!');
    }

总结

处理思想就是把多个配置文件合并成一个配置文件,减少I/0操作提升性能.

UbuntuServer 18.04以上安装教程

1.请选择首选语言

2.请在下面选择您的键盘布局,或选择“识别键盘”以自动检测您的布局

3.欢迎使用Ubuntu ,世界上最受欢迎的 云,集群,和令人惊叹的物联网平台.这是ubuntu在服务器和网络设备上的安装

  • 1.安装ubuntu
  • 2.3 云平台安装 MAAS https://maas.io

4.配置网卡

5.配置代理

不需要代理直接 选择Done

选择镜像地址

推荐使用国内镜像,国外镜像由于墙的问题可能安装失败

https://mirrors.aliyun.com/ubuntu/

文件系统安装
  • 使用整个磁盘
  • 使用真个磁盘并且换装LVM(推荐)
  • 手动安装
选择磁盘安装
磁盘选项

采用默认配置

资料设置
选择安装openssh server

选择软件跳过,进入安装

重启进入登录界面,输入用户名密码

安装增强功能配置并共享文件夹

1.更新系统软件

sudo apt update && sudo apt upgrade

2.安装增强功能

设备>>安装增强功能

点击右下角光驱选择 /usr/share/virtualbox/VBoxGuestAdditions.iso

3.安装工具 dkms build-essential

sudo apt-get install dkms 
sudo apt-get install build-essential

4.挂在光驱

 sudo mount /dev/cdrom /mnt/
挂在成功

5.安装增加功能包

sudo /mnt/VBoxLinuxAdditions.run

6.配置共享文件夹(点击虚拟机设置或右下角文件夹图标)

挂载点名称

7.挂载磁盘 sudo mount -t vboxsf [挂在点名称] [挂载后的本地路径]

sudo mount -t vboxsf downloads /home/www/share

开启root登录

#设置root密码
sudo passwd root

vim /etc/ssh/sshd_config
添加#注释掉PermitRootLogin Prohibit-password
添加:PermitRootLogin yes

#重启sshd
serivce sshd restart

Laravel框架读写分离测试

#配置修改
 'mysql' => [
            'read' => [
                'host' => '192.168.31.194',
            ],
            'write' => [
                'host' => '127.0.0.1',
            ],
            #同一请求周期内使用相同链接获取数据
            'sticky'    => true,
            'driver'    => 'mysql',
            'database'  => 'lara',
            'username'  => 'root',
            'password'  => '123456',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix'    => '',
  ],

测试代码

public function create()
    {
        dump(Order::count());
        $res = Order::create(['sn' => date('YmdHis') . mt_rand(0000, 9999)]);
        dump(Order::find($res->id), Order::count());
    }

stop slave; 关闭从库同步

sticky 为 false时

sticky 为 true时

写入之后的读取的count数为47 插入的id模型也打印成功,说明laravel在写入操作之后使用的是写连接.

源码分析

Illuminate\Database\Connection.php

/**
     * Get the current PDO connection used for reading.
     *
     * @return \PDO
     */
    public function getReadPdo()
    {
        if ($this->transactions > 0) {
            return $this->getPdo();
        }

        if ($this->getConfig('sticky') && $this->recordsModified) {
            return $this->getPdo();
        }

        if ($this->readPdo instanceof Closure) {
            return $this->readPdo = call_user_func($this->readPdo);
        }

        return $this->readPdo ?: $this->getPdo();
    }

代码判断配置sticky 为true 并且数据修改成功返回当当前链接

Mysql 复制 Replication

参考

简介

复制是mysql自带数据同步功能,可用于构建大规模高可用Mysql集群.复制解决的基本问题就是将一台数据库上的数据同步到另一台数据库上.

解决的问题(应用场景)

1.数据分布,备份

在不同的数据中心存储备份数据,即使在不稳定的网络环境中,复制可以正常工作

作为数据备份的补充技术手段

2.负载均衡

解决读密集型业务,将读取请求分发到多台数据库服务器上

3.高可用和故障切换

避免单点问题一台数据库服务器宕机还有其它从库可用

4.mysql升级测试

升级更高版本的mysql时,使用一台备库升级测试,不影响正常业务使用.

工作原理和复制的方式

1. master(主实例)将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events)

2. slave(从实例)将master的binary log events拷贝到它的中继日志(relay log);

3.slave重做中继日志中的事件,更新数据,完成数据同步

复制方式

https://dev.mysql.com/doc/refman/5.7/en/replication-sbr-rbr.html#replication-sbr-rbr-sbr-disadvantages


复制的方式
基于语句的复制(SBR)基于行的复制(RBR)
简介复制的执行Sql语句(逻辑复制)复制数据
优点1.成熟的技术
2.写入日志文件的数据较少(快地完成从备份中获取和恢复) 传输效率更高
3.日志文件包含进行任何更改的所有语句,因此可用于审计数据库(云数据库的sql审计应该是这样实现的?)
1.可以复制所有更改,安全考高
2.执行效率高,因为复制的是数据,没有消耗系统资源的Sql操作

缺点 安全性差一点,有些特殊语句可能会报错
消耗系统资源,主库的操作需要从库要做一遍,
日志文件大传输效率低,I/O操作耗时高

Mysql支持混合复制模式,动态切换语句复制和行复制

复制拓扑及配置

基本原则

  • Mysql一个备库实例只能有一个主库(只能从一个主库读取binlog)(5.7以后支持多源复制)
  • 每一个备库必须有一个唯一服务器ID(同一网络内)
  • 一个主库实例可以有多个备库(一个备库可以有多个兄弟备库)
  • 开启log_slave_updates 选项 备库可以把它从主库获取的数据变化传递给其它备库(slave 此备库的其它备库)

学习拓扑图是,可以把复制的slave关系理解为面向对象中继承关系

一主多从

常用读写分离,适用于多读少写

双主复制

用于多写场景时,需要更改主键自增策略

其它形式的复制,请参考高性能<<Mysql>>

配置(主从实例)

1.创建复制账号

#创建用户
CREATE USER 'tongbu'@'192.168.%.%' IDENTIFIED BY '123456';
#配置账号同步权限
GRANT REPLICATION SLAVE ON *.* TO 'tongbu'@'192.168.%.%';
#刷新权限
flush privileges

需要在主库和从库 执行上述操作

2更改配置文件

配置

#服务器id(保证主从的id都是唯一的)
server-id = 1
#开启binlog日志
log_bin = mysql-bin
#混合复制模式,支持语句复制和行复制
binlog_format = mixed
#备库记录更新
log-slave-updates = 1

查看主库日志和位置 show master status\G

MySQL [(none)]> show master status\G;
*************************** 1. row ***************************
File: mysql-bin.000320
Position: 154
Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 2d8be082-e379-11e6-9e2c-d89d672bc9d4:1-759145,
48496646-e379-11e6-9e2d-008cfaf6f158:1-4,
7c64616c-23a4-11e7-809c-7cd30abda0c4:1-9629173,
98d692c9-07a7-11e7-8a1b-7cd30abd9f90:1-6919876
1 row in set (0.00 sec)

配置从库监听主库

change master to master_host='192.168.31.106',master_port=3306,master_user='tongbu',master_password='123456',master_log_file='mysql-bin.000320',master_log_pos=154;

master_log_file 和 master_log_pos 要根据主库的信息来设置

 #开启从库
 start slave;
 #查看从库状态
 show slave status\G; 
 #状态为yes表示成功 
 Slave_IO_Running: Yes
 Slave_SQL_Running: Yes

开启从库root用户远程访问(方便测试时用户端查看数据)

use mysql;
GRANT ALL ON *.* TO root@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
flush privileges;

测试

创建测试数据库 sync_test 刷新从库, 同步成功

注意

开启start slave 之前的数据不会同步,需要手动在从库创建并导入