laravel/lumen —— 软删除

在项目中,总是会碰到一些需要删除的数据。但是数据理论上是不能够物理删除(硬删除)的,只能进行逻辑删除(软删除)。

laravel/lumenEloquent ORM为我们提供了方便的软删除操作。

单表软删除

我们先创建user表。结构比较简单,三个字段:自增ID、用户名和密码。

并且为其插入一些数据。

正常情况下,我们进行删除操作。就是将数据重数据库中完全删除了。除了重二进制日志中进行回复,其他已无法恢复数据了。

接下来我们进行软删除配置:

1、 在Model中引入SoftDeletes

2、 为我们的数据增加几个字段created_atupdated_atdeleted_at

进入项目根目录执行命令,我们使用迁移来实现添加字段。

随后就会在database\migrations目录下找到2016_03_27_043709_alter_user_deleted_at这个文件。我们修改up方法如下。

随后执行php artisan migrate命令,数据库中字段就以添加。

3、 实现软删除

我们可以看到 deleted_at字段就会被填入删除时间点。

4、 查询

当我们去再去查询的时候,就会发现已经查询不到了。

如果说我们一定要查到被软删除的数据我们可以使用withTrashed

查询到后我们也可以将其恢复。

5、 原理

为什么引入SoftDeletes后就可以现象软删除呢?

当我们创建Eloquent对象的时候。会对引入的SoftDeletes Trait进行初始化。

__construct -> bootIfNotBooted -> boot -> bootTraits

bootTraits方法中会去进行初始化SoftDeletes Trait

SoftDeletes Trait中的bootSoftDeletes就是被初始化的方法。意思是添加一个软删除的作用于。

作用域的详情可以看到这里:http://laravelacademy.org/post/2995.html#ipt_kb_toc_2995_19

SoftDeletes Trait 主要作用的就是通过不同手段(重写方法、添加作用域等)来实现软删除,以及查询是自动添加deleted_at is null

多表软删除

我们再创建一个contentcomment表。并且重复上面的步骤添加上软删除。

形成了一个如下关系

接下来进行操作

1、 查询

我们可以被软删除的id=3content数据并没有查出来。

结合withTrashed来查询,就可以查出来被软删除的数据。

关联查询

软删除是Eloquent提供的功能,如果直接使用门面DB来操作或者使用leftJoin等操作是没办法自动实现的,需要我们手动来控制。

乱想阿陈 —— 缓存那些事

“乱想阿陈”系列文章是我未经实践的胡乱意淫。不保证正确,不保证有用。
仅作为自己平时思考一些东西后的记录、抑或是一些突发奇想地东西。


缓存是一个很好的东西,它可以轻易地帮我们抗住以读为主的大流量高并发。

那如何用好缓存?如何设计缓存呢?

1、缓存机制

1.1、 主动更新

当源数据发生变化之后主动来更新缓存。

适合场景:实时性要求高
优点:数据事实
缺点:复杂,难以治理

1.2、 被动更新

一般指的是例如redis设置过去时间的缓存。当缓存过期后,重新访问数据库获取数据然后进行缓存。

适合场景:不经常修改的数据
优点:简单,依赖缓存产品(redis等)的自身过期机制。
缺点:当cache失效的瞬间,正好遇上高并发的话,就可能会发生雪崩情况。

不过这个缺点可以通过巧妙地设计来弥补,但是程序也少许增加了复杂度。当然封装好了,程序员可以无感知的调用。

1.3、 缓存主动RELOAD

缓存永远不过期或者有效期很长,然后后台启动一个程序后者服务,定时生成缓存。

适合场景:并发量大,允许一定延迟。如:首页、频道页等热门访问页面
优点:相对简单
缺点:不是很实时,但是有自己相应的应用场景。

1.4、 缓存被动RELOAD

缓存永远不过期或者有效期很长,依靠访问来刷新缓存。

当访问页面时,通过主流程获取数据并返回。同时异步(队列、线程等)去检测缓存是否过期,如果过期重新reload

适合场景:并发量一般,访问速度要求快,允许一定延迟。
优点:对于冷数据,无人访问的数据,不浪费资源去处理它。
缺点:容易在多次访问的时候,多次异步去reload缓存。

2、 事件驱动

在上面4种缓存机制中,我觉得1.21.31.4并没有什么难度。
1.1主动更新缓存才是重中之重,我们应该如何去设计它呢?

我暂时想到的比较好的方法,就是事件驱动

比如,订单状态的刷新。我们可以在订单状态改动的程序附件触发一个OrderUpdateEvent的事件,然后写一个ReloadOrderCacheListener的监听器挂载在上面。

这样做既解耦了业务流程代码和缓存代码,又可以实现缓存代码的重用。

但是,当事件变得越来越多,不仅有缓存的事件,也有业务逻辑的事件,久而久之也会发现难以管理。

那么我们应该如何去管理这些事件?

3、 服务化

我们可以将所以缓存相关的事件集中起来,做成一个服务。

它独立于其他系统服务,并且提供出相应的事件监听器,相关服务可以在启动时向缓存服务发送请求注册自己关注事件以及关联的监听器

业务中监听事件

服务化的好处,我们可以起一个有一个的服务,将代码彻底的解耦。然后无线水平扩展(当然是理论上的- -)。

elasticsearch —— 基本操作命令

一直以来没有时间细细的看elasticsearch的命令操作。最近公司需要做一个直播聊天系统。我就想把聊天内容放到elasticsearch里面。乘这个机会来整理一下基本操作命令。

注: 命令语法是基于kibana的插件sense的格式。

  • 基本命令
    • 索引操作
    • 类型mapping添加
  • curd
  • 搜索、过滤
  • 其他

基本命令

1、 查看elasticsearch信息

会返回一些elasticsearch的基本信息。
比如说elasticsearch会自动帮助本节点取的名字,以及集群名字,版本信息,基于lucene的版本信息,以及口号You Know, for Search

索引操作

2、创建索引

重复创建会报错。

创建时还可以初始化一些设置。

number_of_shards表示分片数,初始化后不能修改。
number_of_replicas表示副本数,初始化后可修改。

副本是为了保障数据的可靠性,当一个节点的主分片丢失,elasticSearch可以把任意一个可用的分片副本推举为主分片。所以elasticSearch会默认创建一个副本。

分片则是为了提高查询的吞吐量。一个索引会分成多个分片存储。分片会带来额外的分割和合并的损耗,理论上分片数越少,搜索的效率越高

分片数量推荐: 分片数*副本数=集群数量

所以如果是单机的话,可以设置为1个分片,1个副本。

3、 修改索引设置

4、查看索引

会返回一写索引的基本信息。如: 别名、mapping、设置信息。

5、 删除索引

重复删除与会报错。

类型操作

6、 创建type并且插入数据

当然,同一个索引下可以创建多个不同的类型。这写类型的mapping可以不一致。

7、 创建type并且插入数据

上面2个创建都是通过直接插入数据,自动映射出mapping。我们也可以手动设置mapping。以及关闭自动映射mapping

8、 创建type但是不插入数据

通过设置配置文件./config/elasticsearch.yml 来关闭自动映射mapping。如果没有的话直接添加即可。

9、 修改类型

注: elasticsearch2.x版本后取消了直接删除类型的操作。

curd

10、 添加数据

11、 根据ID获取数据

12、 更新整个文档

13、 局部更新

14、 删除文档

15、 批量获取

也可以通过ID来获取,如果不存在,该IDfoundfalse

16、 批量操作

bulk API允许我们使用单一请求来实现多个文档的createindexupdatedelete

这种格式类似于用”\n”符号连接起来的一行一行的JSON文档流
格式如下:

注意: 由于delete操作没有报文,所以报文不需要填写。具体如下:

搜索和过滤

为了方便测试我在type_3中添加了4条测试数据。

17、 查询数据所有数据

18、 分页搜索

19、 排序查询

20、 多字段排序

按年龄升序,在年龄相同的情况下,按手机号倒序。

条件查询

21、空查询,即无条件查询

可以简化为

22、 按谈条件查询

22、多条件搜索

mysql相比较的话,
must 类似 and xxx = xxx
must not 类似 and xxx != xxx
should 类似 or

23、 and 条件 age = 19 and name 包含 4

24、 or 条件 age = 19 or name 包含 John

25、 (name 包含 4 and age = 19) or name 包含 3

过滤查询

过滤查询与条件查询的语法类型,只是把query替换成了filter

那么 filterquery 有什么区别么?

主要的区别:
1、 filter的结果将会被缓存,而query的结果。
2、 filter出来的记过_score字段全部为1,而query则计算相关性,为每个结果打分。
3、运用场景,如果你的查询是范围和类型比较粗大的,用filter ! 如果是那种精准的,就用query来查询。

26、 filter基本操作

27、 范围查询

28、 查询test索引里存在age字段的文档

29、查询test索引里不存在age字段的文档

30、term过滤

term主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed的字符串

31、 多个字段匹配过滤同一个关键词

搜索过滤结合使用

32、 filtered查询

filtered查询的有点事:借助于filter的速度可以快速过滤出文档,然后再由query根据条件来匹配。推荐使用。

其他操作

33、 获取文档条数

当然查询颗粒度可以更粗或更细。

34、 查看所有基本信息

35、 查看mapping

finite state machine

  • 状态模式
  • 状态间切换的控制
  • 基于事件

状态模式

状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

主要角色

  • 抽象状态角色: 定义一个接口,用以封装环境对象的一个特定的状态所对应的行为
  • 具体状态角色: 每一个具体状态类都实现了环境的一个状态所对应的行为
  • 环境角色: 定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态

为什么要用状态模式

为了解耦各个状态间的工作,让人能够清晰的看懂某个状态下做些什么事情。

代码实现

接下来就是进行实践:

状态间切换的控制

状态切换

举个人类跑步的例子:

人的最大体力和当前体力为5,
需要跑步13KM,每1KM消耗1HP,
当体力不够是需要停下来休息,回复到最大体力后,继续跑,
跑完13KM,结束。

最后运行:

状态控制

虽然这个时候代码结构已经很清晰。但是还有一个问题就是无法控制状态之间的转换。

比如,征程流程准备->跑步->休息->跑步->结束,但是这个时候的代码可以跑步->准备之间切换。这个很显然违反的我们的规则。

这个时候就需要引入规律机制。

首先,增加一个简单的过程类

其次,Context变量中增加,过程对象

调用后,返回内容和上面一致。

但是当出现不允许调用情况是会抛出异常。

基于事件

我们还可以为进入状态,离开状态添加事件,以便代码更清晰,更容易解耦。

事件的底层其实就是观察模式。为了方便我就使用illuminate/events包。

1、为Context类添加event变量

2、设置事件

3、设置监听点以及调用

kibana4.4 – node0.12 内存泄漏解决

在公司里搭建了一个elk日志监控系统。

但是每天早上到公司都会发现,kibana挂了,后来观察了一下,发现原来kibana进程会无限制上升,我那台服务器内存本来就不多,还要运行elasticsearchlogstach,那怎么行- -!

后来到github上去逛了一圈发现,大家似乎都碰到了这个问题。然后在issue里面找到了解决方案。

修改kibana下的bin/kibana命令文件,

参考:
https://github.com/elastic/kibana/pull/3564
https://github.com/elastic/kibana/pull/5451
https://github.com/stayup-io/kibana/commit/50fc3a0e908bf5696ad9f576f7bbd604b185eadf

swoole —— process

  • 创建进程
  • 进程间通信
    • 管道
    • 队列
    • 信号
  • 其他
    • 执行系统命令
    • 回收
    • 守护进程
    • 命名进程
    • CPU亲和性

swoole_process

swoole的进程对比pcntl扩展进程的优势:

  • pcntl无法用在fpm/apache
  • pcntl没有提供进程间通信的功能
  • pcntl不支持重定向标准输入和输出
  • pcntl只提供了fork这样原始的接口,容易使用错误
  • swoole_process提供了比pcntl更强大的功能,更易用的API,使PHP在多进程编程方面更加轻松。

创建进程

原型:

参数:

  • function: 子进程创建成功后要执行的函数
  • redirect_stdin_stdout: 重定向子进程的标准输入和输出。 启用此选项后,在进程内echo将不是打印屏幕,而是写入到管道。读取键盘输入将变为从管道中读取数据。 默认为阻塞读取。
  • create_pipe: 是否创建管道,启用redirect_stdin_stdout后,此选项将忽略用户参数,强制为true 如果子进程内没有进程间通信,可以设置为false

返回值:

swoole_process对象,数据结构包括了管道描述符和回调函数。

进程间通信

管道

当创建进程时,进行了输入输出重定向,swoole就会帮我们自动创建管道。

这里运行程序并不会输出process: echo 1。因为输出意见被输入到管道中去了。

这里我们关闭输出输入重定向,并且开启管道。进行一个简单的案例说明:

队列

信号

父进程中注册信号(子进程被kill触发),自进程中注册信号(本身被kill触发)。

开启子进程后,父进程立即杀死子进程,触发子进程信号事件。子进程关闭后,触发父进程信号事件。

其他

执行系统命令

会输出 ps -ef执行后内容。

回收

子进程结束必须要执行wait进行回收,否则子进程会变成僵尸进程

守护进程

使当前进程脱变为一个守护进程。

命名进程

CPU亲和性

设置CPU亲和性,可以将进程绑定到特定的CPU核上。