隐私政策

本应用(Falling Bally)尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务,本应用会按照本隐私权政策的规定使用和披露您的个人信息。但本应用将以高度的勤勉、审慎义务对待这些信息。除本隐私权政策另有规定外,在未征得您事先许可的情况下,本应用不会将这些信息对外披露或向第三方提供。本应用会不时更新本隐私权政策。 您在同意本应用服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本应用服务使用协议不可分割的一部分。 

1. 适用范围 

a) 在您使用本应用网络服务时,本应用自动接收并记录包括但不限于您的IP地址、浏览器的类型、使用的语言、访问日期和时间、软硬件特征信息及您需求的网页记录等数据; 

b) 本应用通过合法途径从商业伙伴处取得的用户个人数据。 

2. 信息使用 

a) 本应用不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可,或该第三方和本应用(含本应用关联公司)单独或共同为您提供服务,且在该服务结束后,其将被禁止访问包括其以前能够访问的所有这些资料。 

b) 为服务用户的目的,本应用可能通过使用您的个人信息,向您提供您感兴趣的信息,包括但不限于向您发出产品和服务信息,或者与本应用合作伙伴共享信息以便他们向您发送有关其产品和服务的信息(后者需要您的事先同意)。 

3. 信息披露 

在如下情况下,本应用将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息: 

a) 经您事先同意,向第三方披露; 

b) 为提供您所要求的产品和服务,而必须和第三方分享您的个人信息; 

c) 根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;

d) 如您出现违反中国有关法律、法规或者本应用服务协议或相关规则的情况,需要向第三方披露;  

e) 如您是适格的知识产权投诉人并已提起投诉,应被投诉人要求,向被投诉人披露,以便双方处理可能的权利纠纷;

f) 在本应用平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出信息披露请求的,本应用有权决定向该用户提供其交易对方的联络方式等必要信息,以促成交易的完成或纠纷的解决。  

g) 其它本应用根据法律、法规或者网站政策认为合适的披露。  

4. 信息存储和交换  

本应用收集的有关您的信息和资料将不会被保存更不可能被使用。  

5. 信息安全  

在使用本应用网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对方披露自己的个人信息,如联络方式或者邮政地址。请您妥善保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息泄密,尤其是本应用用户名及密码发生泄露,请您立即联络本应用客服,以便本应用采取相应措施。


【长连接】基于workerman + PHP实现聊天及服务端主动推送消息

序言:
workerman是一个高性能的PHP socket 服务器框架,workerman基于PHP多进程以及libevent事件轮询库,PHP开发者只要实现一两个接口,便可以开发出自己的网络应用。
workerman的目标是让PHP开发者更容易的开发出基于socket的高性能的应用服务,而不用去了解PHP socket以及PHP多进程细节。 
workerman本身是一个PHP多进程服务器框架,具有PHP进程管理以及socket通信的模块,所以不依赖php-fpm、nginx或者apache等这些容器便可以独立运行。


百度云链接:https://pan.baidu.com/s/13_qgYDRgNVvEWCK6mYASsg
密码:0hvt


关于WorkerMan依赖的扩展
运行 php -m 会列出命令行 PHP CLI 已经安装的扩展,结果类似如下:

1531275977997082.png


1、pcntl扩展
pcntl扩展是PHP在Linux环境下进程控制的重要扩展,WorkerMan用到了其进程创建、信号控制、定时器、进程状态监控等特性。此扩展win平台不支持。

2、posix扩展
posix扩展使得PHP在Linux环境可以调用系统通过POSIX标准提供的接口。WorkerMan主要使用了其相关的接口实现了守护进程化、用户组控制等功能。此扩展win平台不支持。

3、libevent扩展 或者 Event扩展
libevent扩展(或者event扩展)使得PHP可以使用系统Epoll、Kqueue等高级事件处理机制,能够显著提高WorkerMan在高并发连接时CPU利用率。在高并发长连接相关应用中非常重要。libevent扩展(或者event扩展)不是必须的,如果没安装,则默认使用PHP原生Select事件处理机制。


启动与停止

这里以workerman-chat为例,它的启动入口为start.php。

启动
以debug(调试)方式启动
php start.php start

以daemon(守护进程)方式启动
php start.php start -d

停止
php start.php stop

重启
php start.php restart

平滑重启
php start.php reload

查看状态
php start.php status


业务开发只需要关注 Applications/项目/Events.php一个文件即可。
业务开发只需要关注 Applications/项目/Events.php一个文件即可。
业务开发只需要关注 Applications/项目/Events.php一个文件即可。


将使用到的端口打开,保证没有占用
下面是start_gateway.php的配置

1531277162969828.png


进入workerman目录,启动GatewayWorker

1531277352693611.png

1531277602993115.png


打开前端页面,可以看到已经接收到服务端workerman推送的心跳检测数据了

1531277799251234.png


如果只是做聊天功能,不涉及到服务端主动推送的话,到这里基本就结束了

如果要做服务端主动发起推送,需要在workerman-chat/Applications/Chat/下加一个start_text_gateway.php文件

其实就是开启一个内部Gateway端口,用于推送数据

start_text_gateway.php文件内容如下:

1531277978797888.png


在项目中就可以直接用PHP socket 使用文本协议调用
具体调用方法如下:

1531278179113993.png


在项目API接口返回之前,调用一下上面socket类进行推送,只要前端用户在ws中能接收到消息,那就ok了
如果前端接收不到,可以在workerman-chat/Applications/Chat/Events.php里面的onMessage类打印一下socket收到的消息,再进行一步步调错,在调试的时候,记得不要使用 -d 开启服务,使用守护进程模式的话,无法打印结果!!!

1531278603421001.png


***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************


客官,点击下方打赏一个呗~

【性能】MySQL使用自增ID和自建ID区别

题记:整改公司以前的商城数据,打开数据库,看到原项目中所有的表主键都是uuid,并且所有表都没有注释,我表示一脸懵逼。由于该表是领导以前找的一个人做的,好吧,联系人去。

由主键引发的“血案”就这么开始了,这个框架是之前的人自己写的,里面主键全是uuid,我跟他建议说MySQL最好使用自增主键,效率更高,innoDB的索引特性导致了自增id做主键是效率最好的,为了拿实际的案例来说服他,所以准备做个详细的测试



一:准备表及数据
USER,自增ID为主键,表结构类似如下:
CREATE TABLE `USER` (
   `ID` bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
   `USER_NAME` varchar(100) DEFAULT NULL COMMENT '用户名',
   `USER_PWD` varchar(200) DEFAULT NULL COMMENT '密码',
   `USER_ICON` varchar(500) DEFAULT NULL COMMENT '头像图片',
   `SEX` char(1) DEFAULT NULL COMMENT '性别, 1:男,2:女,3:保密',
   `STAT` varchar(10) DEFAULT NULL COMMENT '用户状态,01:正常,02:冻结',
   `LAST_LOGIN_DATE` datetime DEFAULT NULL COMMENT '最后登录时间',
   `LAST_LOGIN_IP` varchar(100) DEFAULT NULL COMMENT '最后登录IP',
   `SRC_OPEN_USER_ID` bigint(20) DEFAULT NULL COMMENT '来源的联合登录',
   `EMAIL` varchar(200) DEFAULT NULL COMMENT '邮箱',
   `MOBILE` varchar(50) DEFAULT NULL COMMENT '手机',
   `IS_DEL` char(1) DEFAULT '0' COMMENT '是否删除',
   `IS_EMAIL_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定邮箱',
   `IS_PHONE_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定手机',
   `CREATER` bigint(20) DEFAULT NULL COMMENT '创建人',
   `CREATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
   `UPDATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期',
   `PWD_INTENSITY` char(1) DEFAULT NULL COMMENT '密码强度',
   `ACTIVATE` char(1) DEFAULT '1' COMMENT '激活,1:激活,0:未激活',
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'
USER_BACK表,字符串ID为主键,采用uuid:
CREATE TABLE `USER` (
   `UUID` VARCHAT(36) NOT NULL COMMENT '主键',
   `USER_NAME` varchar(100) DEFAULT NULL COMMENT '用户名',
   `USER_PWD` varchar(200) DEFAULT NULL COMMENT '密码',
   `USER_ICON` varchar(500) DEFAULT NULL COMMENT '头像图片',
   `SEX` char(1) DEFAULT NULL COMMENT '性别, 1:男,2:女,3:保密',
   `STAT` varchar(10) DEFAULT NULL COMMENT '用户状态,01:正常,02:冻结',
   `LAST_LOGIN_DATE` datetime DEFAULT NULL COMMENT '最后登录时间',
   `LAST_LOGIN_IP` varchar(100) DEFAULT NULL COMMENT '最后登录IP',
   `SRC_OPEN_USER_ID` bigint(20) DEFAULT NULL COMMENT '来源的联合登录',
   `EMAIL` varchar(200) DEFAULT NULL COMMENT '邮箱',
   `MOBILE` varchar(50) DEFAULT NULL COMMENT '手机',
   `IS_DEL` char(1) DEFAULT '0' COMMENT '是否删除',
   `IS_EMAIL_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定邮箱',
   `IS_PHONE_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定手机',
   `CREATER` bigint(20) DEFAULT NULL COMMENT '创建人',
   `CREATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
   `UPDATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期',
   `PWD_INTENSITY` char(1) DEFAULT NULL COMMENT '密码强度',
   `ACTIVATE` char(1) DEFAULT '1' COMMENT '激活,1:激活,0:未激活',
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'


二:1000万数据开始测试
2.1)确认数据量
自增id为主键的表:
mysql> select count(*) from USER;
+----------+
| count(*) |
+----------+
| 10698102 |
+----------+
1 row in set (27.42 sec)

uuid为主键的表:
mysql> select count(*) from USER_BACK;
+----------+
| count(*) |
+----------+
| 10698102 |
+----------+
1 row in set (0.00 sec)

占据的空间容量来看,自增ID比UUID小一半左右:

自增ID: -rw-rw---- 1 mysql mysql 4.2G Aug 20  23:08 USER.ibd        4.2 G 

UUID:   -rw-rw---- 1 mysql mysql 8.8G Aug 20  18:20 USER_BACK.ibd    8.8 G


2.2)单个数据走索引查询,自增id和 uuid效率比约为:(2~3):1
单条记录查询 
自增ID:	SELECT SQL_NO_CACHE t.* FROM  test.`UC_USER_1` t WHERE t.`MOBILE` ='14782121512'; 0.069 
UUID:	SELECT SQL_NO_CACHE t.* FROM  test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE` ='14782121512'; 0.274 

小范围查询 
自增ID:	SELECT SQL_NO_CACHE t.* FROM  test.`UC_USER_1` t WHERE t.`MOBILE` IN( '14782121512','13761460105'); 0.050 
UUID:	SELECT SQL_NO_CACHE t.* FROM  test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE`  IN('14782121512','13761460105'); 0.151 

根据日期查询 
自增ID:	SELECT SQL_NO_CACHE t.* FROM  test.`UC_USER_1` t WHERE t.`CREATE_DATE`='2013-11-24 10:26:36' ; 0.269 
UUID:	SELECT SQL_NO_CACHE t.* FROM  test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE`='2013-11-24 10:26:43' ; 0.810


2.3)范围like查询,自增ID性能优于UUID,比值约为:(1.5~2):1
(1)模糊范围查询1000条数据,自增ID性能要好于UUID 
自增ID:    SELECT SQL_NO_CACHE t.* FROM  test.`USER` t WHERE t.`MOBILE` LIKE '147%' LIMIT 1000; 2.398 
UUID:    SELECT  SQL_NO_CACHE t.* FROM test.`USER_BACK` t WHERE t.`MOBILE` LIKE  '147%' LIMIT 1000; 5.872 

(2)日期范围查询20条数据,自增ID稍微弱于UUID 
自增ID:    SELECT SQL_NO_CACHE t.* FROM  test.`USER` t WHERE t.`CREATE_DATE` > '2016-08-01 10:26:36' ORDER BY  t.`UPDATE_DATE` DESC LIMIT 20; 0.765 
UUID:    SELECT SQL_NO_CACHE t.* FROM  test.`USER_BACK` t WHERE t.`CREATE_DATE` > '2016-08-01  10:26:36' ORDER BY t.`UPDATE_DATE` DESC LIMIT 20; 1.090 

(3)范围查询200条数据,自增ID性能要好于UUID 
自增ID:    SELECT SQL_NO_CACHE t.* FROM  test.`USER` t WHERE t.`CREATE_DATE` > '2016-07-01 10:26:36' ORDER BY  t.`UPDATE_DATE` DESC LIMIT 200; 1.569 
UUID:    SELECT  SQL_NO_CACHE t.* FROM test.`USER_BACK` t WHERE t.`CREATE_DATE`  > '2016-07-01 10:26:36' ORDER BY t.`UPDATE_DATE` DESC LIMIT 200; 2.597 

范围查询总数量,自增ID要好于UUID 
自增ID:    SELECT SQL_NO_CACHE COUNT(1) FROM  test.`USER` t WHERE t.`CREATE_DATE` > '2016-07-01 10:26:36'  ; 1.129 
UUID:    SELECT  SQL_NO_CACHE COUNT(1) FROM test.`USER_BACK` t WHERE  t.`CREATE_DATE` > '2016-07-01 10:26:36'   ;



2.4)写入测试,自增ID比UUID效率高,比值约为:(3~10):1
修改一天的记录 
自增ID:	UPDATE test.`UC_USER_1` t SET  t.`MOBILE_TGC`='T2' WHERE t.`CREATE_DATE` > '2016-05-03 10:26:36' AND  t.`CREATE_DATE` <'2016-05-04 00:00:00'   ; 2.685 
UUID:	UPDATE test.`UC_USER_PK_VARCHAR_1` t SET  t.`MOBILE_TGC`='T2' WHERE t.`CREATE_DATE` > '2016-05-03 10:26:36' AND  t.`CREATE_DATE` <'2016-05-04 00:00:00'   ; 26.521 

录入数据 
自增ID:	INSERT INTO test.`UC_USER_1`(   ID,    `USER_NAME`,   `USER_PWD`,   `BIRTHDAY`,   `NAME`,    `USER_ICON`,   `SEX`,   `NICKNAME`,   `STAT`,    `USER_MALL`,    `LAST_LOGIN_DATE`,   `LAST_LOGIN_IP`,   `SRC_OPEN_USER_ID`,   `EMAIL`,    `MOBILE`,   `IS_DEL`,   `IS_EMAIL_CONFIRMED`,   `IS_PHONE_CONFIRMED`,   `CREATER`,   `CREATE_DATE`,   `UPDATE_DATE`,   `PWD_INTENSITY`,   `MOBILE_TGC`,   `MAC`,    `SOURCE`,   `ACTIVATE`,   `ACTIVATE_TYPE` ) SELECT       NULL,    CONCAT('110',`USER_NAME`,8),   `USER_PWD`,   `BIRTHDAY`,   `NAME`,    `USER_ICON`,   `SEX`,   `NICKNAME`,   `STAT`,     `USER_MALL`,    `LAST_LOGIN_DATE`,    `LAST_LOGIN_IP`,     `SRC_OPEN_USER_ID`,   `EMAIL`,  CONCAT('110',TRIM(`MOBILE`)),   `IS_DEL`,   `IS_EMAIL_CONFIRMED`,   `IS_PHONE_CONFIRMED`,   `CREATER`,   `CREATE_DATE`,   `UPDATE_DATE`,   `PWD_INTENSITY`,   `MOBILE_TGC`,   `MAC`,    `SOURCE`,   `ACTIVATE`,   `ACTIVATE_TYPE` FROM `test`.`UC_USER_1`  LIMIT 100; 0.534 
UUID:	INSERT INTO test.`UC_USER_PK_VARCHAR_1`(    ID,    `USER_NAME`,   `USER_PWD`,   `BIRTHDAY`,   `NAME`,    `USER_ICON`,   `SEX`,   `NICKNAME`,   `STAT`,    `USER_MALL`,    `LAST_LOGIN_DATE`,     `LAST_LOGIN_IP`,    `SRC_OPEN_USER_ID`,   `EMAIL`,   `MOBILE`,     `IS_DEL`,   `IS_EMAIL_CONFIRMED`,   `IS_PHONE_CONFIRMED`,   `CREATER`,   `CREATE_DATE`,   `UPDATE_DATE`,   `PWD_INTENSITY`,   `MOBILE_TGC`,   `MAC`,    `SOURCE`,   `ACTIVATE`,   `ACTIVATE_TYPE` ) SELECT         UUID(),   CONCAT('110',`USER_NAME`,8),   `USER_PWD`,   `BIRTHDAY`,   `NAME`,    `USER_ICON`,   `SEX`,   `NICKNAME`,   `STAT`,     `USER_MALL`,    `LAST_LOGIN_DATE`,    `LAST_LOGIN_IP`,     `SRC_OPEN_USER_ID`,   `EMAIL`,  CONCAT('110',TRIM(`MOBILE`)),     `IS_DEL`,    `IS_EMAIL_CONFIR



2.3)备份和恢复,自增ID性能优于UUID
Mysqldump备份

自增ID:	time mysqldump -utim -ptimgood -h192.168.121.63  test USER> USER_BACK.sql	0m50.548s
UUID:	time mysqldump -utim -ptimgood  -h192.168.121.63 test USER> USER_BACK.sql	0m58.590s
 
MySQL恢复

自增ID:	time mysql -utim -ptimgood  -h192.168.121.63 test < USER.sql	        17m30.822s
UUID:	time mysql -utim -ptimgood  -h192.168.121.63 test < USER_BACK.sql        23m6.360s


小结

在1000W记录表的测试下:

(1)普通单条或者20条左右的记录检索,自增主键效率是uuid主键的2到3倍;

(2)但是范围查询特别是上百成千条的记录查询,自增id的效率要大于uuid;

(3)在范围查询做统计汇总的时候,自增id主键的效率是uuid主键1.5到2倍;

(4)在存储上面,自增id所占的存储空间是uuid的1/2;

(5)在写入上面,自增ID主键的效率是UUID主键的3到10倍,相差比较明显,特别是update小范围之内的数据上面。

(6)在备份恢复上,自增ID主键稍微优于UUID。



三:MySQL分布式架构的取舍

分布式架构,意味着需要多个实例中保持一个表的主键的唯一性。这个时候普通的单表自增ID主键就不太合适,因为多个mysql实例上会遇到主键全局唯一性问题。


3.1)自增ID主键+步长,适合中等规模的分布式场景

在每个集群节点组的master上面,设置(auto_increment_increment),让目前每个集群的起始点错开 1,步长选择大于将来基本不可能达到的切分集群数,达到将 ID 相对分段的效果来满足全局唯一的效果。


优点是:实现简单,后期维护简单,对应用透明。

缺点是:第一次设置相对较为复杂,因为要针对未来业务的发展而计算好足够的步长;

规划:

比如计划总共N个节点组,那么第i个节点组的my.cnf的配置为:

auto_increment_offset  i

auto_increment_increment  N

假如规划48个节点组,N为48,现在配置第8个节点组,这个i为8,第8个节点组的my.cnf里面的配置为:

auto_increment_offset  8

auto_increment_increment  48


3.2)UUID,适合小规模的分布式环境

对于InnoDB这种聚集主键类型的引擎来说,数据会按照主键进行排序,由于UUID的无序性,InnoDB会产生巨大的IO压力,而且由于索引和数据存储在一起,字符串做主键会造成存储空间增大一倍。


在存储和检索的时候,innodb会对主键进行物理排序,这对auto_increment_int是个好消息,因为后一次插入的主键位置总是在最后。但是对uuid来说,这却是个坏消息,因为uuid是杂乱无章的,每次插入的主键位置是不确定的,可能在开头,也可能在中间,在进行主键物理排序的时候,势必会造成大量的 IO操作影响效率,在数据量不停增长的时候,特别是数据量上了千万记录的时候,读写性能下降的非常厉害。


优点:搭建比较简单,不需要为主键唯一性的处理。

缺点:占用两倍的存储空间(在云上光存储一块就要多花2倍的钱),后期读写性能下降厉害。



总结

(1):单实例或者单节点组3.2

经过500W、1000W的单机表测试,自增ID相对UUID来说,自增ID主键性能高于UUID,磁盘存储费用比UUID节省一半的钱。所以在单实例上或者单节点组上,使用自增ID作为首选主键。


(2)分布式架构场景

20个节点组下的小型规模的分布式场景,为了快速实现部署,可以采用多花存储费用、牺牲部分性能而使用UUID主键快速部署;

20到200个节点组的中等规模的分布式场景,可以采用自增ID+步长的较快速方案。

200以上节点组的大数据下的分布式场景,可以借鉴类似twitter雪花算法构造的全局自增ID作为主键。






***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************


客官,点击下方打赏一个呗~


【并发抢购】PHP+Redis抢购队列防止超卖

题记:2017过去了,公司各门店的会员一年消费所产生的积分也不少,一直在问这积分什么时候可以兑换呀?
领导前几天说要把积分兑换优惠券开放了!啊哈?积分兑换。。。?抢购优惠券???
有抢购,并发超卖的现象势必就会跟着出现,咋办捏。。锁表不现实,并发量大的情况下,锁表后MySQL的事务会对效率有很大的影响,看来只能上队列了。。。

微信图片_20180108130953.png


1.1)下单
/**
 * 会员兑换优惠券
 */
public function vipAddCoupon(){
	$openid = i('post.openid','','trim');
	$vid = i('post.vid','','trim');
	$couid = i('post.couid','','trim');
	$mid = i('post.merc','','trim');
	
	if($vid == '' || $couid == '' || $openid == '' || $mid == ''){
		$this -> error = '400004';
		return FALSE;
	}
	
	$vipData = M('vips') -> where(['vid'=>$vid]) -> where(['mid'=>$mid]) -> find();		//会员数据
	$couData = M('coupon') -> where(['couid'=>$couid]) -> find();						//优惠券数据
	
	if($couData['is_recovery'] == 1){				//优惠券停用
		$this -> error = '500031';
		return FALSE;
	}
	
	if($couData['surplus_num'] < 1){				//优惠券可换数量不足
		$this -> error = '500033';
		return FALSE;
	}
	
	if($vipData['integra'] < $couData['integra']){	//会员积分不足
		$this -> error = '500029';
		return FALSE;
	}
	
	$queueObj = A("Vipuser/Queue");					//实例化
	
	$res = $queueObj -> enqueue($couData,$vid);		//入队
	if($res['code'] != '000000'){
		$this -> error = $res['code'];
		return FALSE;
	}
	return TRUE;
}

1.png

如果进入队列返回失败(code不等于000000),直接返回抢购失败
1.2)入队处理
/**
 * 判断队满
 * @param $couData : 优惠券数据
 */
private function qIsFull($couData){
	$queueName = 'vip_coupon_queue|'.$couData['couid'];				//队列名称
    $queueSize = self::$redis -> lSize($queueName);					//获取队列长度
	if($queueSize != 0 && $queueSize >= $couData['surplus_num']){	//队列长度大于或等于可抢商品数,返出错误
		return FALSE;
	}else{
		return TRUE;
	}
}

/**
 * 队尾入队
 * @param $couData : 优惠券数据
 * @param $vid : 会员id
 */
public function enqueue($couData=null,$vid=null){
	if(is_null($couData) || is_null($vid)){
		$out_arr['code'] = '400004';
	}else{
		$res = $this -> qIsFull($couData);							//判断队列是否已满
		if(!$res){
			$out_arr['code'] = '500033';
		}else{
			$queueName = 'vip_coupon_queue|'.$couData['couid'];		//队列名称
			$re = self::$redis -> rPush($queueName,$vid);			//将会员id从队尾压入队列
			$out_arr['code'] = '000000';
		}
	}
    return $out_arr;
}

2.png


2.1)出队接口
/**
 * 队头出队
 * @param $queueName : 队列名称
 */
public function deQueue($queueName = null){
	
	if(is_null($queueName)) return FALSE;
	
	$data = $this -> getFrontData($queueName);								//获取队列长度
	if($data['code'] != '000000'){
		$out_arr['code'] = $data['code'];
	}else{
		$vid = $data['data'];
		$vipData = M('vips') -> where(['vid'=>$vid]) -> find();				//会员数据
		
		$couid = explode('|', $queueName);
		$couid = $couid[1];
		$couData = M('coupon') -> where(['couid'=>$couid]) -> find();		//优惠券数据
		
		if($couData['surplus_num'] < 1){									//优惠券可抢数不足
			$out_arr['code'] = '500033';
		}else{
			
			$addData['couid'] = $couid;
			$addData['vid'] = $vid;
			
			$obj = M('coupon_vip');											//实例化
			$obj -> startTrans();											//开启事务
			
			$addRe = M('coupon_vip') -> add($addData);						//写入会员优惠券表
			
			$save['integra'] = $vipData['integra'] - $couData['integra'];
			$re = M('vips') -> where(['vid'=>$vid]) -> save($save);			//扣减会员积分
			
			$integraData['integra'] = $couData['integra'];
			$integraData['vid'] = $vid;
			$res = M('vip_integral') -> add($integraData);					//写入会员积分变更表
			
			$couSave['surplus_num'] = $couData['surplus_num'] - 1;
			$r = M('coupon') -> where(['couid'=>$couid]) -> save($couSave);	//扣减优惠券表可用数量
			if(!$addRe || !$re || !$res || !$r){
				$obj -> rollback();											//事务回滚
				$out_arr['code'] = '999997';
				$this -> enqueue($couData,$vid);							//重新从队尾入队
			}else{
				$obj -> commit();											//提交事务
				$out_arr['code'] = '000000';
			}
		}
	}
	return json_encode($out_arr,JSON_UNESCAPED_UNICODE);
}

4.png


2.2)扫描所有优惠券、进行出队处理
/**
 * 定时任务(优惠券队列出队)
 */
public function queueSetinterval(){
	
	while (TRUE) {
		$data = M('coupon') -> where(['is_recovery'=>0]) -> where("endtime > ".time()) -> select();	//已启用、未到期的优惠券数据
		if(!empty($data)){
			foreach ($data as $k => $v) {						//因为每个优惠券的可抢数不一样,所以入队时定义的是每个优惠券分一个队列,所以这里需要循环处理
				$queueName = 'vip_coupon_queue|'.$v['couid'];	//队列名称
				$res = $this -> deQueue($queueName);			//队头出队
			}
		}else{
			sleep(5);
		}
	}
	
	die;
}

3.png


2.3)创建Linux定时任务调用优惠券出队(1秒执行一次)

5.png


3.1)创建一张优惠券
兑换规则:一共放出10000张优惠券,1积分兑换1张,每人限购80张,当前剩余20张

6.png


3.2)创建一个会员,拥有18积分

7.png


3.3)创建100个线程调用接口同时抢购,最终只有18个线程抢购成功

8.png9.png


以上就是关于抢购商品的思路及源码了

ps:感觉比小程序的实时通讯简单了N倍啊


***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************


客官,点击下方打赏一个呗~

PHP SWOOLE的webSocket建立长连接,实现服务端主动向客户端推送消息

最近被webSocket长连接坑哭了。。。

公司的小程序需要使用到服务器端主动向客户端推送消息,一开始朋友推荐使用workerman,废了九牛二虎之力,配置成功并且在web端也实现了服务端主动向页面推送消息。。。workerman需要引入封装好的io,打算挪到小程序的时候,一看...我的tian!!小程序不能引入外部在线js。。。

好吧,只能重新弄了,这次选择使用PHP的swoole扩展,下面进入正题


(https://gitee.com/swoole/swoole/tree/v1.9.21)
wget https://github.com/swoole/swoole-src/archive/v1.9.8.zip
 

tar zxvf v1.9.8.zip


cd swoole-src-1.9.8



(=号后面是php-config的完整路径)
./configure --with-php-config=/www/server/php/55/bin/php-config 


make  && make install



extension=/www/server/php/55/lib/php/extensions/no-debug-non-zts-20121212/swoole.so


二:服务端代码
/**
 * 启动webSocket服务类
 */
class websocket_server_start{
	
	public static $ws = null;
	
	public function __construct(){
		
		if(is_null(self::$ws)){
			
			/**
			 * @param 1 : IP
			 * @param 2 : 端口
			 * @param 3 : swoole运行模式
			 * @param 4 : SSL隧道加密TCP-Server
			 * 			  SWOOLE_SOCK_TCP | SWOOLE_SSL 表示此端口启用加密
			 */
			self::$ws = new swoole_websocket_server("0.0.0.0",9502,SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL);
		}
		
//		SSL文件路径
		self::$ws -> set(array(
			'ssl_cert_file'=>'/www/wwwroot/Public/ssl/214257688760806.pem',
			'ssl_key_file'=>'/www/wwwroot/Public/ssl/214257688760806.key',
		));
		
//		客户端建立连接,返回
		self::$ws -> on('open',function(swoole_websocket_server $ws,$request){
			var_dump($request);
		  	$ws -> push($request->fd,"websocket连接建立成功! \n");
		});
		
//		消息回复
		self::$ws -> on('message',function(swoole_websocket_server $ws,$request){
		  	self::$ws -> push($request->fd,json_encode($request,JSON_UNESCAPED_UNICODE));
		});
		
//		连接关闭
		self::$ws -> on('close',function(swoole_websocket_server $ws,$request){
			echo "cloes\n";
		});
		
		self::$ws -> start();
	}
	
}
new websocket_server_start();

1.png


三:客户端代码
<!DOCTYPE html>
<html>
	<head>
      <meta charset="UTF-8"/>
		<meta charset="{CHARSET}">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			
//			实例化webSocket
			var webSocket = new WebSocket("wss://www.xxxx.com:9502");
			
//			建立socket连接
			webSocket.onopen = function(evt){
				console.log("连接成功");
			}
			
//			连接关闭
			webSocket.onclose = function(evt){
				console.log("连接关闭");
			}
			
//			接收webSocket发来的消息
			webSocket.onmessage = function(evt){
				console.log(evt.data);
			}
			
//			错误函数
			webSocket.onerror = function(evt,e){
				console.log(evt);
			}
		</script>
	</body>
</html>

5.png


四:在cli模式下启动webSocket
进入到放置服务端代码的目录,输入php websocket_server_start.php

2.png

检查一下线程,看看是否已经启动 ps -ajft

3.png


五:建立长连接

4.png

这里报了HTTP的400错误,打开上面写的客户端的文件

6.png

http模式下,webSocket已经建立,由于小程序只能在wss(https)下运行,再试试https,看看能不能链接成功

7.png

大功告成!http和https都没问题
我们在服务端代码open函数打印了客户端向服务端传递的数据,在Xshell下可以查看一下

8.png


***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************


客官,点击下方打赏一个呗~

MySQL悲观锁总结和实践

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
 
使用场景举例:以MySQL InnoDB为例
商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。
 

//1.查询出商品信息
select status from ceshi_lock where id=1;
//2.根据商品信息生成订单
insert into ceshi_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update ceshi_lock set status=2;
 

前面已经提到,只有当goods status为1时才能对该商品下单,上面第一步操作中,查询出来的商品status为1。但是当我们执行第三步Update操作的时候,有可能出现其他人先一步对商品下单把goods status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。
 
2使用悲观锁来实现:
在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。
 

 
我们可以使用命令设置MySQL为非autocommit模式:
set autocommit=0;
 
设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from ceshi_lock where id=1 ;
//2.根据商品信息生成订单
insert into ceshi_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update ceshi_lock set status=2;
//4.提交事务
commit;/commit work;
 

 
上面的第一步我们执行了一次查询操作:select status from ceshi_lock where id=1 for update;
与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在ceshi_lock表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。
创建表ceshi_lock,包括id,status,name三个字段,id为主键,数据库中记录如下;

1.png


为了测试数据库锁,使用两个console来模拟不同的事务操作,分别用console1、console2来表示。


console1:查询出结果,并且把该条数据锁定了

6.png

console2:查询被阻塞

7.png


二:明确指定主键,若查无此数据,无lock

5.png


console1:查询出结果,并且把该条数据锁定了

2.png

console2:查询被阻塞

3.png


以上就是关于数据库主键对MySQL锁级别的影响实例,需要注意的是,除了主键外,使用索引也会影响数据库的锁定级别

有不对的地方欢迎拍砖,下一次会带来数据库乐观锁的总结和实践


客官,点击下方打赏一个呗~

【SSE】实现服务端数据推送方式

老的http协议是请求-响应式的,对于某些实时性要求比较高的需求(例如微博消息推送),实现起来是比较麻烦

html5标准中,新增了一个SSE(server-sent event,服务端推送事件)允许服务器端向客户端推送新数据(简称数据推送)的HTML5技术

当数据源有新数据时,服务器端能立刻发送给一个或多个客户端,而不用等客户端来请求,这些新数据可能是突发新闻、最新股票、上线朋友的聊天信息、新的天气预报、策略游戏中的下一步等。

SSE适用于更新频繁、低延迟并且数据都是从服务端到客户端。它和WebSocket的区别:

1)便利,不需要添加任何新组件,用任何习惯的后端语言和框架就能继续使用,不用为新建虚拟机弄一个新的IP或新的端口号而劳神。

2)服务器端的简洁。因为SSE能在现有的HTTP/HTTPS协议上运作,所以它能够直接运行于现有的代理服务器和认证技术。

WebSocket相较SSE最大的优势在于它是双向交流的,这意味着服务器发送数据就像从服务器接受数据一样简单,而SSE一般通过一个独立的Ajax请求从客户端向服务端传送数据,因此相对于WebSocket使用Ajax会增加开销。因此,如果需要以每秒一次或者更快的频率向服务端传输数据,就应该用WebSocket。

sse是直接建立在当前http连接上的,本质上是保持一个http长连接,但是和comet不同的是:comet是每次服务端返回数据后,连接关闭然后客户端马上再次发起连接。而sse是保持长连接常驻。

而客户端对数据的通信是通过js的EventSource来进行的,EventSource提供了三个事件:
1、open:当成功建立连接时产生
2、message:当接收到消息时产生
3、error:当出现错误时产生
直接使用即可。
<html>
    <head>
        <meta charset="UTF-8">
        <title>basic SSE test</title>
    </head>
    <body>
        <pre id = "x">initializting...</pre>
        <!--之所以使用pre标签而不是p或者div是为了确保数据能以它被接受时的格式呈现,而不会修改或格式化-->
    </body>
    <script>
        var es = new EventSource("./basic_sse.php");
        es.addEventListener("message",function(e){
            //e.data
            document.getElementById("x").innerHTML += "\n"+e.data;
        },false);//使用false表示在冒泡阶段处理事件,而不是捕获阶段。
    </script>
</html>
<!doctype html>
<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    $time = date('Y-m-d H:i:s');

    echo 'retry: 1000'.PHP_EOL;
    echo 'data: The server time is: '.$time.PHP_EOL.PHP_EOL;
?>


空白:表示该行是注释,会在处理时被忽略。
data:表示该行包含的是数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
event:表示该行用来声明事件的类型。浏览器在收到数据时,会产生对应类型的事件。
id:表示该行用来声明事件的标识符。
retry,表示该行用来声明浏览器在连接断开之后进行再次连接之前的等待时间。

jdfw.gif


***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************

PHP与客户端交互使用OpenSSL RSA加解密数据

众所周知,任何数据在互联网上传输,是极其不安全的,所以一些私密数据就需要通过加密后再传输
应用场景:PHP与客户端交互
一:下载OpenSSL
wget  http://www.openssl.org/source/openssl-1.1.0e.tar.gz


二:解压OpenSSL到当前目录,得到openssl-openssl-1.1.0e文件夹
tar -xzf openssl-1.1.0e.tar.gz


三:进入解压的目录
cd openssl-1.1.0e


四:设定Openssl 安装( --prefix )参数为要安装的目录,也就是安装后的档案会出现在该目录下
./config --prefix=/usr/local/openssl


五:执行命令./config -t


六:执行make,编译Openssl
make install


七:进入Openssl目录
cd usr/local/openssl/bin/

1.png

文件已经存在,安装完成



生成原始 RSA私钥文件:openssl genrsa -out rsa_private_key.pem 1024
加密长度是1024位。加密长度是指理论上最大允许“被加密的信息”长度的限制,也就是明文的长度限制。随着这个参数的增大(比方说2048),允许的明文长度也会增加,但同时也会造成计算复杂度的极速增长。

将原始 RSA私钥转换为 pkcs8格式:openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem


根据私钥生成RSA公钥:openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

进入目录查看生成的公钥、私钥文件,cd usr/local/bin/

从结果中可以看出,文件已经生成成功!

QQ截图20170816102503.png

==========华丽的分割线==========
九:PHP使用RSA私钥加密、公钥解密
1)将上面生成的公钥文件和私钥文件复制到PHP项目中
cp -r ./rsa_public_key.pem /www/wwwroot/rsa/
cp -r ./rsa_private_key.pem /www/wwwroot/rsa/


2)下图可以看出,已经成功的把公钥、私钥文件复制进来

5.png


3)执行php代码
/**
 * rsa加解密
 */
public function rsa(){
	
	$private_key = file_get_contents("http://".$_SERVER['SERVER_NAME']."/rsa/rsa_private_key.pem");		//私钥
	$public_key = file_get_contents("http://".$_SERVER['SERVER_NAME']."/rsa/rsa_public_key.pem");		//公钥
	
	$pi_key =  openssl_pkey_get_private($private_key);						//可用返回资源id
	$pu_key = openssl_pkey_get_public($public_key);
	
//	加密数据
	$data = [
	    'id' => '1234567890',
	    'name' => '小明',
	    'mobile' => '123456',
	];
	$data = json_encode($data,JSON_UNESCAPED_UNICODE);						//转json格式


	$encrypted = '';		//加密后的结果
	$decrypted = '';		//解密后的结果

	openssl_public_encrypt($data, $encrypted, $pu_key);						//公钥加密
//	参数:1.数据源,2.加密后的结果,3.资源id
	$encrypted = base64_encode($encrypted);									//转base64传输
	

	openssl_private_decrypt(base64_decode($encrypted), $decrypted, $pi_key);//私钥解密
//	参数:1.数据源(先解base64),2.解密后的结果,3.资源id
	
//	打印结果
	p('数据源:'.$data);
	p('加密后的数据:'.$encrypted);
	p('解密后的数据:'.$decrypted);
}

6.png

7.png


使用OpenSSL生成RSA文件,PHP使用RSA加解密至此完成~~


客官,点击下方打赏一个呗~


MySQL之按年月分区

都说程序猿是靠爬坑积累起来的,对于这话深感认同。。。

最近公司新运行的项目,数据量已经过百万级了,为公司带来业绩、利润的同时,程序一些操作的耗时也越来越大,好吧,我承认这是前期着急上线,对于数据量的考量没有做到位,现在开始补救。。(开启填坑模式o(╯□╰)o)

在索引、字段类型已经建立好的情况下,将大表拆分
要对表的时间字段(类型:datetime)基于月进行分区。于是遍历MySQL官方文档分区章节,总结如下:

 
主要是以下几种:

1. RANGE分区
 
2. LIST分区
 
3. HASH分区

4. KEY分区

测试的维度主要从两个方面进行
 

 
针对特定的查询,是否能进行分区剪裁(即只查询相关的分区,而不是所有分区)
 
一、分区剪裁 
针对特定的查询,是否能进行分区剪裁(即只查询相关的分区,而不是所有分区)
 
二、查询时间
 
鉴于该批测试数据是静止的(即没有并发进行的insert,update和delete操作),数据量也不太大,从这个维度来考量貌似意义也不是很大。
 
因此,重点测试第一个维度。
 
基于RANGE的分区方案
 
在这里,选用了TO_DAYS函数


1、创建表并且设置按年月分区

CREATE TABLE bdm_range_datetime(
    id INT,
    hiredate DATETIME,
    time INT
)
PARTITION BY RANGE (TO_DAYS(hiredate) ) (
    PARTITION p1 VALUES LESS THAN ( TO_DAYS('20170101') ),
    PARTITION p2 VALUES LESS THAN ( TO_DAYS('20170201') ),
    PARTITION p3 VALUES LESS THAN ( TO_DAYS('20170301') ),
    PARTITION p4 VALUES LESS THAN ( TO_DAYS('20170401') ),
    PARTITION p5 VALUES LESS THAN ( TO_DAYS('20170501') ),
    PARTITION p6 VALUES LESS THAN ( TO_DAYS('20170601') ),
    PARTITION p7 VALUES LESS THAN ( TO_DAYS('20170701') ),
    PARTITION p8 VALUES LESS THAN ( TO_DAYS('20170801') ),
    PARTITION p9 VALUES LESS THAN ( TO_DAYS('20170901') ),
    PARTITION p10 VALUES LESS THAN ( TO_DAYS('20171001') )
);

1.png

2、批量插入10W条数据

public function ceshi(){
	for($i=0;$i<99999;$i++){
		$time = rand(1488297600, 1501430399);			//3月1日-7月30日
		$data['hiredate'] = date("Y-m-d H:i:s",$time);
		$data['time'] = time();
		$data['id'] = $i;
		M('range_datetime') -> add($data);
	}
}

2.png

3、查看各分区数据条数

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_range_datetime';

4.png

4、查看特定查询语句的执行计划

explain partitions select * from bdm_range_datetime where hiredate > '20170501001000' and hiredate<='20170630235959';

3.png

从执行结果可以看出,查询2017年5月1日 - 2017年6月30日的数据,查询39623行,确实是走的所属分区,但是为什么会多走了p1这个分区呢??百思不得其解,查询了很多资料,还是没能找到答案,有知道原因的小伙伴可以在下方给我留言噢


==========华丽的分割线==========


TO_DAYS函数出现的这个p1分区没有解决的情况下,换了一种思路

基于RANGE COLUMNS的分区方案

RANGE COLUMNS可以直接基于列,而无需像上述RANGE那种,分区的对象只能为整数。


1、创建表并且设置按年月分区

CREATE TABLE bdm_range_datetime(
    id INT,
    hiredate DATETIME,
    time INT
)
PARTITION BY RANGE COLUMNS(hiredate) (
    PARTITION p1 VALUES LESS THAN ( '20170101' ),
    PARTITION p2 VALUES LESS THAN ( '20170201' ),
    PARTITION p3 VALUES LESS THAN ( '20170301' ),
    PARTITION p4 VALUES LESS THAN ( '20170401' ),
    PARTITION p5 VALUES LESS THAN ( '20170501' ),
    PARTITION p6 VALUES LESS THAN ( '20170601' ),
    PARTITION p7 VALUES LESS THAN ( '20170701' ),
    PARTITION p8 VALUES LESS THAN ( '20170801' ),
    PARTITION p9 VALUES LESS THAN ( '20170901' ),
    PARTITION p10 VALUES LESS THAN ('20171001' )
);

5.png

2、批量插入10W条数据

public function ceshi(){
	for($i=0;$i<99999;$i++){
		$time = rand(1488297600, 1501430399);			//3月1日-7月30日
		$data['hiredate'] = date("Y-m-d H:i:s",$time);
		$data['time'] = time();
		$data['id'] = $i;
		M('range_datetime') -> add($data);
	}
}

2.png

3、查看各分区数据条数

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_range_datetime';

6.png

4、查看特定查询语句的执行计划

explain partitions select * from bdm_range_datetime where hiredate > '20170501001000' and hiredate<='20170630235959';

7.png


总结: 
1. 经过对比,个人倾向于第二种方案,即基于RANGE COLUMNS的分区实现。
 
2. 在5.7版本之前,对于DATA和DATETIME类型的列,如果要实现分区裁剪,只能使用YEAR() 和TO_DAYS()函数,在5.7版本中,又新增了TO_SECONDS()函数。
 
3. 其实LIST也能实现基于天的分区方案,但在这个需求上,相比于RANGE,还是显得很鸡肋。
 
4. TIMESTAMP类型的列,只能基于UNIX_TIMESTAMP函数进行分区,切记!


==========华丽的分割线==========

普通表转分区表

1、查看原表结构

desc bdm_order;

1.png

这是一张普通表,oid为主键


2、新增字段(datetime类型,用于分区)

2.png


3、修改原表结构(关联主键、索引)

3.png

4.png

设置关联主键,关联索引


4、将原普通表更改为分区表

alter table bdm_order partition by RANGE COLUMNS(hiredate) (
    PARTITION p201612 VALUES LESS THAN ( '20170101' ),
    PARTITION p201701 VALUES LESS THAN ( '20170201' ),
    PARTITION p201702 VALUES LESS THAN ( '20170301' ),
    PARTITION p201703 VALUES LESS THAN ( '20170401' ),
    PARTITION p201704 VALUES LESS THAN ( '20170501' ),
    PARTITION p201705 VALUES LESS THAN ( '20170601' ),
    PARTITION p201706 VALUES LESS THAN ( '20170701' ),
    PARTITION p201707 VALUES LESS THAN ( '20170801' ),
    PARTITION p201708 VALUES LESS THAN ( '20170901' ),
    PARTITION p201709 VALUES LESS THAN ( '20171001' ),
    PARTITION p201710 VALUES LESS THAN ( '20171101' ),
    PARTITION p201711 VALUES LESS THAN ( '20171201' ),
    PARTITION p201712 VALUES LESS THAN ( '20180101' ),
    PARTITION p201801 VALUES LESS THAN ( '20180201' ),
);

5.png


5、查看分区数据情况

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_order';

6.png

从结果中看出,分区裁剪已经成功,所有数据都存在于2017年之前的分区中


6、将原time类型时间戳转换为可视化时间写入新增的字段中

UPDATE bdm_order SET hiredate=FROM_UNIXTIME(orderTime, '%Y-%m-%d %H:%i:%S') WHERE sid!=0;

7.png

8.png

从结果中看出,时间戳已经转换为可视化时间写到新字段中了


7、再次查看分区数据情况和查看特定查询语句的执行计划

SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'bdm_order';

9.png

explain partitions select * from bdm_order where hiredate > '20170501001000' and hiredate<='20170630235959';

10.png

至此填坑完毕!



客官,点击下方打赏一个呗~

Thinkphp关闭系统日志

题记:一大早正在吃早餐,工作群里有人发了张截图(如下图),说网站打不开了,马上丢下碗筷去处理。。。


1.jpg


原来是服务器磁盘空间不够了,可一想不对啊。。这只放框架文件,40个G怎么就不够了!?

2.png


好吧,连接Xshell,排查是哪些文件占用的系统磁盘空间!困...

命令:du -sh /*


3.png

4.png


一路排查下来,10个多G!!!原来是系统日志文件搞的鬼,果断删除!!!

删除之后,网站立刻恢复正常,整个世界都清净了!赶紧滚去继续吃早餐。。


事情到了这里还没完,既然这个日志文件是自动生成的,果断给他关闭掉

在index.php入口文件注释掉下面这句话

//define('APP_DEBUG',TRUE);

 5.png


然后去配置项那里增加一行参数

/*隐藏index.php主入口*/
'URL_CASE_INSENSITIVE'  =>	FALSE,

6.png


如果不设置这个配置项,本地运行没问题,但是到服务器会出现报错哟。。。


点击下方打赏一个呗~