博客

  • 本地https快速解决方案 mkcert

    mkcert 是一个使用 go 语言编写的生成本地自签证书的小程序,具有跨平台,使用简单,支持多域名,自动信任 CA 等一系列方便的特性可供本地开发时快速创建 https 环境使用。

    mac 安装

    brew install mkcert

    使用步骤

    1. # mkcert -install

    Created a new local CA 💥
    Sudo password:
    The local CA is now installed in the system trust store! ⚡️


    1. # mkcert xxx.com "*.xxx.com" localhost 127.0.0.1 ::1

    Created a new certificate valid for the following names 📜
    “xxx.com”
    “*.xxx.com”
    “localhost”
    “127.0.0.1”
    “::1”
    Reminder: X.509 wildcards only go one level deep, so this won’t match a.b.xxx.com ℹ️
    The certificate is at “./xxx.com+4.pem” and the key at “./xxx.com+4-key.pem” ✅
    It will expire on 30 November 2025

  • 分布式锁

    1. 为什么需要分布式锁

    随着业务的发展,一个应用可能部署到好几台服务器上,此时若多台机器需要同步访问同一个资源,就需要使用到分布式锁

    2. 锁的实现

    2.1 基于数据库实现

    通过增加递增的版本号字段实现乐观锁:

    线程1: amount=10, version=123

    select amount,version from bank where id = 1

    线程2: amount=10, version=123

    select amount,version from bank where id = 1

    线程1: update=1,更新成功,更新后version为124

    update bank set version = 124, amount = amount-10 where id = 1 and version = 123

    线程2: 由于当前的version为124,update=0,更新失败

    update bank set version = 124, amount = amount-10 where id = 1 and version = 123

    2.2 基于redis实现

    2.2.1 setnx

    SET lockKey randomValue NX PX 30000

    实现思路:

    1. 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID
    2. 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则释放锁。
    try {
        lock = redisTemplate.opsForValue().setIfAbsent(lockKey, UUID);
        if (lock) {
            //成功 设置过期时间
            redisTemplate.expire(lockKey,1, TimeUnit.MINUTES); 
            // TODO
        }else {
            //没有获取到锁
        }
    } finally {
        if(lock){   
            //任务结束,释放锁  
            redisTemplate.delete(lockKey);
        }
    }
    

    要点

    1. 为什么锁要添加一个超时时间?如果A获取了锁,宕机了没有释放锁,会造成死锁。
    2. 锁的value值为UUID,是为了避免这种情况:假设A获取了锁,过期时间10s,此时15s之后,锁已经自动释放了,A去释放锁,但是此时可能B获取了锁。A就不能删除B的锁了。

    还有一些可以完善的地方:如果在第一步setnx执行成功后,在expire()命令执行成功前,发生了宕机的现象,那么就会出现死锁的问题

    如果考虑redis的部署问题

    • 单机模式:只要redis故障了,就不能加锁
    • master-slave + sentinel选举模式:如果master节点故障了,发生主从切换,就有可能出现锁丢失的问题。
    • redis cluster模式

    2.2.2 RedLock

    redis的作者也考虑到上面的问题,提出了一个RedLock的算法:假设redis的部署模式是redis cluster,总共有5个master节点,通过以下步骤获取一把锁:

    • 获取当前时间戳,单位是毫秒
    • 轮流尝试在每个master节点上创建锁,过期时间设置较短,一般就几十毫秒
    • 尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2 +1)
    • 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了
    • 要是锁建立失败了,那么就依次删除这个锁
    • 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁

    但是这样的这种算法还是颇具争议的,可能还会存在不少的问题,无法保证加锁的过程一定正确。

    争议

    1. 官方的推荐 https://redis.io/topics/distlock

    2. Martin Kleppmann 关于 Readlock 的评价。
      https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

    3. redis 作者的回复。
      http://antirez.com/news/101

    2.2.3 Redisson

    Redisson是一个企业级的开源Redis Client,也提供了分布式锁的支持。

    实现1设置了超时时间,但是超过时间都还没有完成业务逻辑的情况下,key会过期,其他线程有可能会获取到锁。这样一来的话,第一个线程还没执行完业务逻辑,第二个线程进来了也会出现线程安全问题。所以我们还需要额外的去维护这个过期时间,Redisson帮我们做了这些。

    实现细节:
    Redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行
    Redisson设置一个key的默认过期时间为30s,如果某个客户端持有一个锁超过了30s,Redisson中有一个watchdog的概念,翻译过来叫看门狗,它会在你获取锁之后,每隔10秒帮你把key的超时时间设为30s。这样就算一直持有锁也不会出现key过期,其他线程获取到锁的问题了。
    – “看门狗”逻辑保证了没有死锁发生。如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期,其他线程可以获取到锁

    另外,Redisson还提供了对redlock算法的支持

    2.3 基于zookeeper实现

    zookeeper是一个为分布式应用提供一致性服务的开源组件, 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案。它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名,利用这个特性来实现锁:

    ​ 让多个客户端同时创建一个临时节点,创建成功的就说明获取到了锁 。然后没有获取到锁的客户端创建一个 watcher 进行节点状态的监听,如果这个互斥锁被释放了,可以调用回调函数重新获得锁。zk 中不需要向 redis 那样考虑锁得不到释放的问题,因为当客户端挂了,节点也挂了,锁也释放了。

    如何同时实现 共享锁和独占锁 ?创建有序的临时节点。

    1. 当读请求(获取共享锁),如果 没有比自己更小的节点,或比自己小的节点都是读请求 ,则可以获取到读锁。如果比自己小的节点中有写请求 ,则只能等待前面的写请求完成。
    2. 当写请求(获取独占锁),如果 没有比自己更小的节点 ,则表示当前客户端可以直接获取到写锁。如果 有比自己更小的节点,无论是读操作还是写操作,当前客户端都无法获取到写锁 ,等待所有前面的操作完成。

    当然还有优化的地方,比如当一个锁得到释放它会通知所有等待的客户端从而造成 惊群效应 。此时你可以通过让等待的节点只监听他们前面的节点,让 读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点

    2.4 锁对比

    实现 优点 缺点
    数据库 简单,易于理解,系统依赖少 1. 性能较差,有锁表的风险 2. 非阻塞,需要轮询,占用CPU资源
    redis 性能很高,可以支撑高并发的获取、释放锁操作 1. 数据并不是强一致性的,在某些极端情况下,可能会出现问题 2. 锁删除失败,过期时间不好控制 2. 非阻塞,需要轮询,占用CPU资源
    zk 1. 简单易用,有较好的性能和可靠性 2. 不用一直轮询,性能消耗较小 3.可解决失效死锁问题 1. 性能不如redis实现,需要动态创建、销毁临时节点,且只能通过 Leader 服务器来执行,然后将数据同步到所有的 Follower 机器

    3. 分布式锁的要点

    3.1 AP 还是 CP

    3.1.1 AP模型

    ​ 例子:Redis的主备集群做例子

    好处:

    ​ 在接受事务请求(增删改数据)的时候,主Master节点只需要确保自己写入即可立即返回给客户端,复制的过程由于是异步的,客户端延时性上来说影响并不大,相比于CP模型的确保半数提交成功,AP模型的延时性是比较低的Redis本身的定位就是要快,所以这相当符合Redis的设计初衷,如果集群有三个节点,他可以容许宕机两个节点,可以看出来,可用性的容错节点是N-1个,相比于CP模型他的可用性会更高

    Redis作为分布式锁的话有可能会造成数据的不一致,如果你使用分布式锁的场景是为了更好的利用系统资源(CPU、内存),让多节点不做一些重复的工作,并行互斥执行不同的任务,那么不妨将任务做成幂等,这样就算两个节点做同一个任务,任务被执行了两次但是它们是幂等的,其结果也不会被影响

    3.1.2 CP模型

    ​ 例子:zookeeperZab协议)

    存在的问题:

    ​ 半数以上的节点上确认,延时性就取决于最快的那半数节点的写入性能,而且多加了网络通信来回的开销。微服务链路调用都需要注册中心获取服务的IP地址,并发量大,但IP地址这种东西小概率存在不一致(服务刚上线,但注册中心没有这个服务的IP地址,使得不被访问到)其实是可以接受的。在注册中心的场景下延时性才最重要,这也就是为什么Nacos的注册中心会选用AP模型,他的延时性相对是要好的。

    3.1.3 总结

    • 在延迟性要求高、客户端响应不能太慢、性能要求高的场景下,允许牺牲小部分时间的锁失效来换取好的性能,那么建议使用AP模型来实现分布式锁,某些场景可以通过幂等弥补小部分锁失效带来的负面影响
    • 在延迟性要求不高、主要保证锁不能失效、高一致性的场景下,允许牺牲一点性能来换取一致性,那么建议使用CP模型来实现分布式锁。选型时注意考虑到并发量极高的情况下可能有问题

    3.2 宕机锁释放问题

    分布式锁拿到锁的节点意外宕机,拿到锁而不释放锁,从而死锁,这就是一个宕机锁释放问题。

    3.2.1 Redis宕机锁释放问题

    redis中我们解决宕机锁释放问题通常会在设置锁的同时给他设置一个超时时间,这就有一个问题了,这个超时时间要设置多长?如果这个超时时间太长,那节点宕机没释放锁就只能等待锁超时,死锁时间会变长(服务至少有一段时间的不可用),这是不容许的,那如果超时时间太短,又会造成如果有什么做了很久的业务操作,这边还没执行完,另一个节点却也能获取到锁,造成的锁失效。

    Redisson其实解决了超时时间过短,锁失效的问题,虽然有续租,但是不建议超时时间太长,如果超时时间太长还是会造成死锁的时间(如果超时时间设置1小时。。那还是会有1小时锁无法获取的情况),也不建议太短,万一JVM进行GC,整个代码进行停顿,后台线程因此有几秒时间无法续租,锁也会失效被其他节点获取,所以这里建议超时时间设置的不大偏小,3、5分钟左右这样子

    3.2.2 zookeeper的宕机锁释放问题

    使用zookeeper作为分布式锁,客户端会在zk上创建一个临时节点,获取到锁的客户端会与zk维持一个心跳连接,如果zk收不到客户端的心跳就说明客户端宕机了,此时临时节点会自动释放,相当于自动释放了锁

    3.3 锁等待问题

    A节点获取到锁执行锁区块的业务逻辑,B节点获取不到锁,那么B节点怎么才能知道自己需要阻塞等待多久?这就需要一个通知机制,在锁释放的时候中间件需要通知等待中的节点来获取锁。

    3.3.1 redis中的锁等待问题

    Redisson利用了PubSub模式完成了一个锁释放的通知机制

    1. 利用redisPubSub模式订阅一个LockName关联的channel(一把锁对应一个channel
    2. 设置一个监听器,监听PubSub中名称为刚刚的那个LockNamechannel发出通知(有锁释放),动作为调用Semaphorerelease方法释放信号量
    3. 当前获取不到锁的线程调用Semaphoreacquire方法尝试获取信号量,若没有信号量则阻塞ttl个时间
    4. 等待超过ttl个时间或者有锁释放通知之后线程唤醒,继续尝试获取锁
    5. 若获取不到锁,继续调用Semaphore#acquire方法阻塞然后获取锁,无限循环直到获取到锁

    3.3.2 zookeeper中的锁等待问题

    同样使用通知机制(观察者模式)会比较好解决,在zookeeper中方案就是Watch机制,监听一个节点是否产生变化,若变化会收到一个通知,当获取不到锁之后监听锁的那个临时节点即可。也可以按顺序来,获取锁失败之后注册一个顺序节点,按照自己的顺序,向前一个节点注册Watch,这样一个个来可解决惊群效应。

    3.4 误释放锁

    redis作为分布式锁误释放锁

    1. A节点获取到锁之后,redis挂了,重新选举一个从节点的redis后由于是AP模型,锁信息不在这个从节点上,B节点此时来获取锁成功,B开始执行业务逻辑,A执行完业务逻辑之后来释放锁,就会把B的锁释放掉了…然后C又来获取锁,B执行完又把C的锁释放掉…以此类推

    2. A节点获取到锁之后,因为某种原因(GC停顿或者…)没有续租过期时间,锁不小心释放掉了但是业务逻辑还在跑,B节点此时来获取锁成功,B也在跑业务逻辑,A执行完逻辑之后释放锁,把B的锁也给释放掉了…然后C又来获取锁,B执行完又把C的锁释放掉…以此类推

    zookeeper有误释放锁的情况

    1. 假设A节点获取到锁,此时GC停顿,后台线程无法给zookeeper发送心跳,zk以为A节点宕机,把临时节点给删了,这样其他节点也会乘虚而入,然后就会出现上面说的循环释放别人锁的情况。

    3.4.1 解决方案

    借鉴Redisson的方案,在获取锁的时候将一个唯一标识设置为value(UUID+threadId),设置一个threadId还有一个好处,就是可以做可重入锁,当同一个线程再次获取锁的时候就可以以当前threadId作为依据判断是否是重入情况。

  • 威联通配置开机启动项

    添加启动项目

    挂载特殊分区

    mkdir /tmp/config
    mount $(/sbin/hal\_app --get\_boot\_pd port\_id=0)6 /tmp/config
    

    新增启动脚本

    vi /tmp/config/autorun.sh
    

    加入开机时需要执行的命令,比如启动ZeroTier加入网络的配置

    cd /share/CACHEDEV1_DATA/.qpkg/ZeroTier/ && zerotier-cli join xxx 
    

    增加可执行权限

    chmod +x /tmp/config/autorun.sh
    

    取消挂载

    umount /tmp/config
    

    打开自启动开关

    自启动开关位于:控制台 -> 系统 -> 硬件 -> 一般设置 -> 勾选 启动时运行用户定义的进程

    参考资料

    https://wiki.qnap.com/wiki/Running_Your_Own_Application_at_Startup

  • Linux下安装Go环境

    Golang官网下载地址:https://golang.org/dl/
    1. 打开官网下载地址选择对应的系统版本, 复制下载链接
    wget https://dl.google.com/go/go1.15.6.linux-amd64.tar.gz
    2. tar解压到/usr/loacl目录下,得到go文件夹
    tar -C /usr/local -zxvf go1.15.6.linux-amd64.tar.gz
    3. 添加/usr/loacl/go/bin目录到PATH变量中。添加到/etc/profile$HOME/.profile都可以
    vi /etc/profile
    // 在最后一行添加
    export GOROOT=/usr/local/go
    export PATH=$PATH:$GOROOT/bin
    // :wq保存退出后source一下
    source /etc/profile
    4. 执行go version,如果显示版本号,则安装成功。

  • REST API 设计最佳实践

    一 前言

    作为一名后端程序员,照着产品需求设计好了模型,设计好了关联关系,设计API时候问题来了:一旦 API 进入前端 APP 代码,或者是被你的顾客广泛使用的话,再来大改就非常麻烦了。比如说,如果 APP 版本 1.0 用了一个接口 A,这个接口 A 如果要进行大改,那么必须将 A 维持至所有用户升级过 APP 1.0 后。那么怎么样避免 API 发布之后大改呢?有没有一些提前可以注意到的设计准则可以帮我们避开 API 设计中的各种坑?

    二 REST API 是什么

    REST API 有一套 API 设计的准则,它规范了 API 设计的框架,使得服务间、程序员之间有一个通用的沟通语言。

    三 REST API 内具体规定了什么

    REST API 规范了 API 设计的两大核心原则

    1. API应该作用于 Resource(资源)上
    2. 对资源的操作应使用对应语义的几种操作,包括: GET, POST, PUT, PATCH, DELETE

    什么是Resource(资源)

    这里的资源是可操作的逻辑对象,允许调用者进行操作,比如用户注册,那么 API 类似于POST /users,资源即为 users。在很多情况下,API 中的资源与你的数据模型是一一对应的。当然也有例外情况,比如说你的数据库中存有用户,但是你现在想要让调用者可以创建“管理员”,那么 API 可能是POST /admins表中并没有 admins 这个表,可能 admin 是 Users 表中的一个属性,比如 role=admin
    REST API中的资源一定需要是名词,即一定是一个实在存在的概念,比如 用户, 帐号, 车票等,或一个抽象的概念,比如 权限 等。
    如果你需要提供一个创建某种资源的API接口,POST /indexesPOST /accountsPOST /docs等等。
    对于资源的命名,建议统一命名为为英文的复数。比如说 users 而不是 user。更重要的是保持一致性,在所有地方用一样的复数。

    什么是操作

    一旦定义了资源,接下来需要定义允许调用者在这些资源上做什么操作。
    比如说,以抢车票为例,我们可能允许调用者进行以下操作

    • GET /tickets – 列出所有车票
    • GET /tickets/9839 – 列出 id 为 9839 这张车票的信息
    • POST /tickets – 创建一张车票
    • PUT /tickets/9839 – 更新 9839 这张车票的信息
    • PATCH /tickets/9839 – 部分修改 983 这张车票的信息,比如只修改车票价格
    • DELETE /tickets/9839 – 删掉 9839 这张车票

    以上可以总结出来REST的大致设计思路了。它由两部分组成,第一部分是 操作,第二部分是可操作的 资源。比如上文中的 GET /tickets,操作是 GET,可操作的资源是车票。
    如果严格遵循了REST的设计准则,调用者也了解 REST 的准则的话,那么对于很多 API 调用,不用再参考互相写的文档了。如果需要调用一张车票的信息,调用者自然会知道应该用GET去查看一个车票资源的信息,即 GET /tickets/:ticketId,这样就极大降低了沟通成本和出错成本,提升效率。

    如何在 API 中表示实体(数据库表)间关系

    在后端设计中,有的资源逻辑上无法独立存在。比如说,有索引存在,用GET /indexes/index_abc/docs/1来表达获取索引 index_abc 中编号为 1 的文档。因此,对于所有资源需要依赖于另一个资源存在时,我们就按顺序在端点中将资源列出来。索引和文档的关系,可以有以下接口

    • GET /indexes/index_abc/docs/1 – 获取index id为 index_abc 下的id为 1 的文档
    • GET /indexes/index_abc/docs – 获取index id为 index_abc 下的所有文档
    • POST /indexes/index_abc/docs – 在index id为 index_abc 的索引中,添加文档 …

    如果一个资源可以独立于另一个资源存在,那么可以考虑直接提供子端点。比如说,如果一个宠物店主人和宠物信息分别都常常被同时调用,那么可以考虑

    GET /owners/  – 获取所有主人信息
    GET /owners/1/pets/ 获取 id 为 1 的主人的所有宠物

    GET /pets/ – 获取所有宠物信息(宠物店所有宠物)

    GET /pets/13 – 直接获取 id 为 13 的宠物

    REST API中如何表示一个动作

    有时候表达一些接口时,会发现REST的准则很难直接应用。比如用户登录POST /users/signin,这里的 signin是个动词。采用REST准则时可以考虑有三个选择

    1. 严格地遵循 REST 原则,找一个替代动词的名词。 signin 可以替换为login。或者,以 token 密钥的方式登录的话,可以改为 POST /users/token,创建一个 user token(也就是登录了)
    2. 在某些实在困难的地方,放弃严格的REST原则
    3. 参考一些成功的 REST API 并寻找类似的 API,参考他们的命名设计,如 github 的 API,较为规范,覆盖了很多 API 调用的情景,可以找到个类似的命名参考。

    比如说,在 github 上,如果让你来设计加星这个操作,你会把端点被设计成什么样?
    Github把加星端点设计为 PUT /gists/:id/star,把取消加星设计为 DELETE /gitsts/:id/star。这样就完美地遵循了 REST 名词作为资源的准则,把动词”加星”完美地用 PUT/DELETE 两个操作清晰地表达出来。

    REST API 设计常见问题和建议

    如何区分版本

    比如说,如果在大致将 v1 开发完毕后,v1 前缀的 API 就应该稳定下来,所有的改动进入 v2。同时开始通知所有使用 v1 的用户,帮助他们平滑迁移到 v2。带有版本前缀的 API 示例如下

    GET /v1/indexes/
    GET /v1/indexes/abc/
    POST /v1/indexes/

    也可以用子域名表示

    http://api.example.com/v1
    http://apiv1.example.com

    或者添加自定义请求头部

    Accept-version: v1
    Accept-version: v2

    过滤、排序、字段选择和分页

    过滤:为所有字段或者查询语句提供独立的查询参数。

    GET /cars?color=red 返回红色车的列表
    GET /cars?seats<=2 返回两座车的列表

    排序:允许跨越多字段的正序或者倒序排列。

    GET /cars?sort=-manufactorer,+model 返回排序的车辆列表

    字段选择:只列出需要的字段。一些情况下,只需要在列表中查询几个有标识意义的字段,并不需要从服务端把所有字段的值都请求出来,因此需要API支持选择查询字段的能力。这样也可以在一定程度上提高网络传输的性能与响应速度。

    GET /cars?fields=manufacturer,model,id,color 返回需要的车辆信息字段的列表

    分页:使用offset和limit来获取固定数量的资源结果,当其中一个参数没有出现时,应该提供各自的默认值,比如默认取第一页,或者默认取20条数据。

    GET /cars?offset=10&limit=5 返回第10页的5条车辆记录列表
    GET /cars?&limit=5 返回前5条车辆记录列表
    GET /cars?&offset=5 返回第5页的车辆记录列表(默认单页数量)

    应该返回什么

    建议 REST API 永远返回 JSON 格式的结果。
    因为这样才能返回标准的结构化数据。所以,服务器回应的 HTTP 头的Content-Type属性要设为application/json。客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的ACCEPT属性也要设成application/json

    HTTP 状态码

    • 1xx:相关信息
    • 2xx:操作成功
    • 3xx:重定向
    • 4xx:客户端错误
    • 5xx:服务器错误

    这五大类总共包含100多种状态码,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。
    2xx 状态码
    200状态码表示操作成功,但是不同的方法可以返回更精确的状态码。

    GET: 200 OK
    POST: 201 Created
    PUT: 200 OK
    PATCH: 200 OK
    DELETE: 204 No Content

    上面代码中,POST返回201状态码,表示生成了新的资源;DELETE返回204状态码,表示资源已经不存在。
    此外,202 Accepted状态码表示服务器已经收到请求,但还未进行处理,会在未来再处理,通常用于异步操作。

    参考

    1. https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/
    2. https://hackernoon.com/restful-api-designing-guidelines-the-best-practices-60e1d954e7c9
    3. https://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
    4. Azure ( https://docs.microsoft.com/zh-cn/azure/architecture/best-practices/api-design
    5. Google Cloud ( https://cloud.google.com/apis/design/
    6. Zalendo : https://opensource.zalando.com/restful-api-guidelines/ ,其中有非常多的实践是可以参考的,也像 RFC 一样规范了 MUST 、SHOULD 、MAY 的遵守分级。
  • Windows客户端frp设置开机自启动

    新建文件frps.vbs,编辑

    Set ws = CreateObject("Wscript.Shell") ws.run "cmd /c d:\frp\frpc.exe -c d:\frp\frpc.ini",vbhide
    

    将 frps.vbs 文件放到启动目录

  • linux磁盘清理后不释放空间还是100%

    通过df -h查看磁盘满了 ,删除文件有,空间没有被立即释放, 通过查阅相关资料,了解到被删除文件被执行rm命令时,如果有进程操作该文件,该文件不会被立马删除,而是被标记为deleted;直到操作该文件的所有进程都结束,该文件才会被删除。 delete状态下的文件不可见,使用ll命令时也看不到,但实实在在占用了磁盘空间。可以通过执行下列命令查看被标记为delete的文件清单:

    lsof | grep deleted

    解决办法:kill -9 PID   把进程删掉就能释放空间。

  • restful API

    说明

    1. 在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词一般与数据库的表格名对应。

    2.对于资源的具体操作类型,由HTTP动词表示。常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

    GET(SELECT):从服务器取出资源(一项或多项)。

    POST(CREATE):在服务器新建一个资源。

    PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

    PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

    DELETE(DELETE):从服务器删除资源。

    3. 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。

    GET /collection:返回资源对象的列表(数组)

    GET /collection/resource:返回单个资源对象

    POST /collection:返回新生成的资源对象

    PUT /collection/resource:返回完整的资源对象

    PATCH /collection/resource:返回完整的资源对象

    DELETE /collection/resource:返回一个空文档

    实现(示例)

    请求路径方法对应方法名如下

    请求方法 请求路径 访问控制器方法 说明

    GET /user index() 返回用户列表

    GET /user/123 show($id) 返回$id=123的用户信息

    POST /user store() 新建一个用户

    PUT / PATCH /user/123 update($id) 修改$id=123的用户信息

    DELETE /user/123 delete($id) 删除$id=123的用户

    POST /user/foo foo() 自定义方法

    其他

    前端请求方法为PUT / PATCH/DELETE 时,属于非简单访问,会产生跨域问题,会在正式通信之前,增加一次HTTP查询请求,预请求用的请求方法是OPTIONS。
    服务器收到”预检”请求以后,检查Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段,确认允许跨源请求,就可以做出回应:

    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
    Access-Control-Allow-Headers: Origin,X-requested-with,content-Type,Accept

  • Nginx 设置wordpress 伪静态

    nginx 配置

    location / {
        if (-f $request_filename/index.html){
            rewrite (.*) $1/index.html break;
        }
        if (-f $request_filename/index.php){
            rewrite (.*) $1/index.php;
        }
        if (!-f $request_filename){
            rewrite (.*) /index.php;
        }
    }
    

    固定链接设置参数:

    1. %year% 基于文章发布年份,比如2007;
    2. %monthnum% 基于文章发布月份,比如05;
    3. %day% 基于文章发布当日,比如28;
    4. %hour% 基于文章发布小时数,比如15;
    5. %minute% 基于文章发布分钟数,比如43
    6. %second% 基于文章发布秒数,比如33
    7. %postname% 基于文章的postname,其值为撰写时指定的缩略名,不指定缩略名时是文章标题;
    8. %post_id% 基于文章post_id,比如423;
    9. %category% 基于文章分类,子分类会处理成“分类/子分类”这种形式;
    10. %author% 基于文章作者名。

    将上述参数进行组合,即可得到wordpress的固定链接形式。
    网上常见的几种设置方法:

    1. /%year%/%monthnum%/%day%/%postname%/
    2. /%year%/%monthnum%/%postname%/
    3. /%year%/%monthnum%/%day%/%postname%.html
    4. /%year%/%monthnum%/%postname%.html
    5. /%category%/%postname%.html
    6. /%category%/%post_id%
    7. /%postname%.html
    8. /%post_id%.html 一般使用这个方式比较好。
  • axios 参数为payload的解决方法

    1. 添加头部headers

      // 新创建 axios 实例配置
      const $axios = axios.create({
      baseURL: ‘http://domain.com’,
      timeout: 5000,
      headers: {
      ‘Content-Type’: ‘application/x-www-form-urlencoded’,
      ‘sessionId’: Lockr.get(“sessionId”),
      ‘authKey’: Lockr.get(“authKey”),
      }
      });

    2. 参数序列化

      var qs = require(‘qs’);
      axios.post(‘/foo’, qs.stringify({ ‘foo’: ‘bar’ });

    见 https://github.com/mzabriskie/axios/blob/master/README.md#using-applicationx-www-form-urlencoded-format