mysql中的数据存储选择

2018年5月3日 没有评论

MySQl中有多种表示日期和时间的数据类型。其中YEAR表示年份,DATE表示日期,TIME表示时间,DATETIME和TIMESTAMP表示日期和实践。它们的对比如下

YEAR ,字节数为1,取值范围为“1901——2155”
DATE,字节数为4,取值范围为“1000-01-01——9999-12-31”
TIME,字节数为3,取值范围为“-838:59:59——838:59:59”
DATETIME,字节数为8,取值范围为“1000-01-01 00:00:00——9999-12-31 23:59:59”
TIMESTAMP,字节数为4,取值范围为“19700101080001——20380119111407”.

以前经常看到有人说时间存储为int类型的时候查询会比timestamp类型快很多,到底有多大的差别哪?我动手测试了一下,给大家分享一下测试结果。

一、表结构

test1表为时间存储为int类型,test2表为存储为timestamp类型。

CREATE TABLE `test1` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL DEFAULT '',
  `address` varchar(100) NOT NULL DEFAULT '',
  `email` varchar(50) NOT NULL DEFAULT '',
  `created_at` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_createdat` (`created_at`),
  KEY `idx_name_time` (`name`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `test2` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL DEFAULT '',
  `address` varchar(100) NOT NULL DEFAULT '',
  `email` varchar(50) NOT NULL DEFAULT '',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_createdat` (`created_at`),
  KEY `idx_name_time` (`name`,`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

 二、生成测试数据

我是自己写的python脚本,利用了python的faker库。

import faker
import time
import pymysql

def timestamp_datetime(value):
    format = '%Y-%m-%d %H:%M:%S'
    # value为传入的值为时间戳(整形),如:1332888820
    value = time.localtime(value)
    ## 经过localtime转换后变成
    ## time.struct_time(tm_year=2012, tm_mon=3, tm_mday=28, tm_hour=6, tm_min=53, tm_sec=40, tm_wday=2, tm_yday=88, tm_isdst=0)
    # 最后再经过strftime函数转换为正常日期格式。
    dt = time.strftime(format, value)
    return dt

def gen_sql(max=1000):
    sqlData = []
    i=0
    while i < max :
        name = generator.name()
        email = generator.email()
        unix_time=generator.unix_time()
        address = generator.address()
        time=timestamp_datetime(unix_time)
        sql = "INSERT INTO test1 (`name`,`address`,`email`,`created_at`) VALUE ('%s','%s','%s',%d);" % (name,address,email,unix_time)
        sql2 = "INSERT INTO test2 (`name`,`address`,`email`,`created_at`) VALUE ('%s','%s','%s','%s');" % (name,address,email,time)
        i=i+1
        sqlData.append(sql)
        sqlData.append(sql2)
    return sqlData

start_time = time.time()

generator = faker.Faker(locale="zh-cn")
total = 1000000*2
one_time=1000
times=total/one_time
print("start generating data")

conn = pymysql.connect(
    host = "127.0.0.1",
    user = "root",
    password = "123456",
    database = "sql_test",
    charset = 'utf8',
    cursorclass = pymysql.cursors.DictCursor)


cursor = conn.cursor()

i=0
while i < times:
    sql_data = gen_sql(one_time)
    for sql in sql_data:
        cursor.execute(sql)
    #commit以后更新才会提交到数据库
    conn.commit()
    i=i+1
    print("add records : %d" % (i*one_time))

cursor.close()
conn.close()
use_time=time.time()-start_time
print("done,use time:%f" % (use_time))

 三、占用空间

由于innodb的show table status from db中的值不准确,所以这里我们可以当做相等。

四、查询性能

#43ms
 select SQL_NO_CACHE * from test1 where created_at > 1025340225 limit 10; 
# 82ms
 select SQL_NO_CACHE * from test2 where created_at > '2002-06-29 16:43:45' limit 10;

单一索引查询,从结果来看差距是很大的,几乎有一倍了,但是就看这点损耗你可以接受吗?

 

分类: Mysql 标签:

nginx反向代理解决跨域问题

2018年3月28日 没有评论

现在随着前端技术的发展,前端渲染模板越来成熟,前后端的分离就变得越来越普遍了。其中一个很目标的问题就是跨域问题,这里就不展开跨域问题了。以前常见的解决办法是在返回的header部分增加”‘Access-Control-Allow-*”系列header来解决问题。以前我也写过一篇类似的文章《ajax跨域解决方案-Cross Origin Resource Sharing》,感兴趣的话可以去看看一下。今天要说的是利用nginx的反向代理来解决这个问题。

现在我们有2个站点,front.aaa.com和api.aaa.com。假设这里我们2个站点都用nginx搭建,front.aaa.com的nginx配置文件为front.conf,api.aaa.com的nginx的配置文件为api.conf。当front站点的页面ajax访问api站点的时候就会起因这个跨域问题。我们在front站点配置1个针对api的反向代理,配置如下:

server {
     ....
       location /api/ {
            rewrite ^/api/(.*) /$1 break;
            proxy_pass http://api.aaa.com;
        }
        ....

}

如此一来所有访问api的接口我们可以同构http://front.aaa.com/api/{realApi}来实现对后端接口的访问。

分类: Linux, nginx, Web 标签:

yum安装elasticsearch

2018年3月13日 没有评论

centos环境下面安装elasticsearch(后面用es代称)有多重方法,可以yum、rpm包或者源码。今天就简单说一下yum安装es的过程,而且也推荐这么做,可以方便的通过systemd系列命令管理es。

安装依赖

这里面真正需要的是java,但是如果你不安装java其实也可以安装成功,不过安装插件时会有问题,所以我们先安装java。

yum install java

 配置官方yum源

我们可以通过官方提供的源来安装,所以我们先配置官方源。

[elasticsearch-6.x]
name=Elasticsearch repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

安装Elasticsearch

然后更新本地yum缓存,安装es。

yum makecache
yum install elasticsearch

 安装中文分词插件

这里的中文分词插件选择analysis-ik,我们通过elasticsearch-plugin命令来安装。首先我们需要知道命令的位置,默认是在”/usr/share/elasticsearch/bin/elasticsearch-plugin”.

cd /usr/share/elasticsearch
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.2/elasticsearch-analysis-ik-6.2.2.zip

  启动ES

下面我们启动es,查看是否安装成功。

 systemctl start elasticsearch

用curl访问9200端口,查看服务是否正常。

curl http://127.0.0.1:9200
#看到下面内容,说明es启动正常
{
  "name" : "id7JTQQ",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "0JLalDUYS_y1FFEsLFsqlg",
  "version" : {
    "number" : "6.2.2",
    "build_hash" : "10b1edd",
    "build_date" : "2018-02-16T19:01:30.685723Z",
    "build_snapshot" : false,
    "lucene_version" : "7.2.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

 查看ES安装插件

访问“http://127.0.0.1:9200/_cat/plugins”可以查看es安装的插件。

curl http://127.0.0.1:9200/_cat/plugins

至此,我们的es安装初步完成了,开始使用它吧!

分类: Linux 标签:

Brew安装指定版本软件

2018年2月28日 没有评论

今天想安装一下elasticsearch,顺便安装一下ik中文分词,可惜用brew搞了好久也没装好。用brew装的elasticsearch是最新版的6.2.2,但是ik插件目前缺只支持6.2.1,就想着用brew安装指定版本的elasticsearch。但是目前网上的中文教程貌似都失效了,幸亏找到一篇英文的文章帮忙解决了问题。

想要通过brew安装指定版本的软件,我们可以利用brew install远程url方式来实现这个目的。brew允许的url格式是什么样子哪?类似以下这种:

brew install ${BREWURL}/{HASH}/Formula/{NAME}.rb
BREWURL=https://raw.githubusercontent.com/Homebrew/homebrew-core

上面命令有3个变量,不过BREWURL算是1个常量,已经给出了,目前来看是不变的,但是我们想要完成上面的目的,还有2个变量要找到,就是HASH和NAME了。NAME比较简单,一般就是软件名字,例如我这里就是elasticsearch,那么接下来只要找到HASH就可以了。
找HASH有2中方式,一种是通过brew log本地查找,另一种方式就是在github上面找了。这里面2中方式都提一下。

brew本地的方式,可以通过一下命令查看git log。

brew log -p NAME

里面有commit记录,找到对应版本的commit id,前7位就是我们需要的hash了。

分类: Mac 标签:

php不定参数函数

2017年12月21日 没有评论

php是可以像C语言一样使用不定参数函数的,系统函数常见的有sprintf、array_merge等,当然用户自定义的函数也可以实现。在 PHP 5.6 及以上的版本中,由 … 语法实现;在 PHP 5.5 及更早版本中,使用函数 func_num_args(),func_get_arg(),和 func_get_args() 。

当然5.6里面新增…这种方法相比以前的利用func_*_arg系列函数相关,参数的定制化和可控制性更强了。

首先先给出这两种方式不定参数函数的例子。

php5.6以前版本func_*_arg系列

function func2()
{
      echo "this is func2". PHP_EOL;
      $arg_num = func_num_args();
      $params = func_get_args();
      var_dump($arg_num, $params);
}
func2('luke', 'like', 'you');

php5.6以后…方式

function func1($author,...$params)
{
     echo "this is func1". PHP_EOL;
     var_dump($params);
}
func1('luke', 'like', 'you');

注意:这里的3个点是在参数名称前面的,所以如果你放到后面是会报错的。

分类: PHP 标签:

php-memcached扩展安装

2017年11月3日 没有评论

由于服务器上的php-memcached的序列化处理使用的是igbinary,而本地使用的是php默认的,在本地跑线上环境代码的时候就遇到问题了。具体是查看错误日志看到如下信息:

'ErrorException' with message 'Memcached::get(): could not unserialize value, no 
igbinary support'

查看memcached的igbinary support发现值是”no”,由于以前没自己动手搞过这块的东西。看了官方配置手册以后,以为只需要在配置文件中把”memcached.serializer”的配置设置为igbinary就可以了那。但是修改以后发现并没有作用。

接下来查看了一下扩展的编译参数,发现有个选项是”–enable-memcached-igbinary”,另外2个类似的参数分别是”–enable-memcached-json” 和”–enable-memcached-msgpack”.现在就找到了问题所在,需要重新重新编译memcached扩展以支持igbinary。

开始编译,编译参数如下:

./configure --enable-memcached --enable-memcached-igbinary --with-php-config=
/usr/local/bin/php-config

但是很不幸,并没有如期待的那样编译成功,遇到如下错误:

"./php_memcached.h:23:10: fatal error: 'Zend/zend_smart_str.h' file not found
#include "Zend/zend_smart_str.h""

搜索了整个电脑硬盘,也没有找到这个头文件。打开php-memcached在github上的项目地址,发现它在readme里面说3.x版本是支持php7的,而php5.2-5.6需要使用2.x版本,所以就从github上的release历史里面下载了php-memcached2.2版本,当然从php官方地址也是可以下载到相应的版本的。我特地下载了php7.1.11的源码查看了一下,果然有缺失的头文件。

接下来安装上面的编译参数再次进行编译,就成功了。

分类: Linux, PHP 标签:

redis批量删除key

2017年10月23日 没有评论

最近生产环境的redis服务器由于key过期不及时,现在发现时key的个数已经暴增到5000多万了。然后运维同学那边就报警了,最大内存12G,已经用了9G多了,正好下面快要双11了,让我们快些解决。

redis服务器里面堆积大量的队列状态相关的key,其实这些key可以设置有效期,或者任务完成以后删除或者过期,但是由于我们使用类库的问题,这些key既没有删除也没有过期,堆积到redis里面去了,现在我们要做的就是删除这些无用key。在删除这些keys的过程中,走了不少弯路,这里说一下我最终采用的方案。

redis的del函数可以删除单个key,也可以删除多个key,del函数官方文档可以看这里。在google之后看到目前网络上很多文章的思路是使用keys匹配返回要删除的key,然后调用del函数去删除。这种方案在数据量较小时无可厚非,但如果像我这样面临的处理的数据有5千W时,keys的阻塞问题可能会给线上生产环境带来致命的问题。所以我们需要对这种方案作出一些修改。

可喜的是自从2.8.0以后redis提供scan来遍历key,而且这个过程是非阻塞,不会影响线上生产环境。最终经过修改的方案是用scan遍历要删除的key,然后调用del删除。

下面是我用python写的用来删除key的脚本。

import sys,redis

r = redis.Redis(host="127.0.0.1", port=6379,db=0)

if  len(sys.argv) <= 1:
    print("必须指明匹配key字符串")
    exit(1)
pattern = sys.argv[1]

cursor = 0
num = 1
while 1 :
    resut = r.scan(cursor, pattern, 10000)
    del_keys = []
    for i in resut[1]:
        key = i.decode()
        del_keys.append(key)
    #print("del keys len :%d" % len(result))
    if len(del_keys) == 0:
        break
    r.delete(*del_keys)
    cursor = resut[0]
    print("delete keys num : %dw" % (num))
    num +=1

print("done\n")

如何利用我这个脚本删除符合某个规则的key哪,如以king开头的key?

下面的命令即可完成上面的问题。

python3 main.py "king*"

期间我看到网上利用keys+del的lua脚本的方案,花了一段时间把scan+del改成lua脚本来删除。但是可惜的是目前redis并不支持这么做,由于scan返回的结果是不确定的,所以禁止在其后直接调用del操作。

参考资料:《How to atomically delete keys matching a pattern using Redis》

分类: Linux, Python 标签:

centos7防火墙firewalld

2017年10月19日 没有评论

centos7防火墙管理工具由我们以前熟知的iptables转变为了firewall。那么firewall是什么哪?firewall的服务是firewalld (防火墙守护)服务,引入了一个信任级别的概念来管理与之相关联的连接与接口。它支持 ipv4 与 ipv6,并支持网桥,采用 firewall-cmd (command) 或 firewall-config (gui) 来动态的管理 kernel netfilter 的临时或永久的接口规则,并实时生效而无需重启服务。

区域(zone)

Firewall 能将不同的网络连接归类到不同的信任级别.

区域 默认规则策略
trusted 允许所有的数据包。
home 拒绝流入的数据包,除非与输出流量数据包相关或是ssh,mdns,ipp-client,samba-client与dhcpv6-client服务则允许。
internal 等同于home区域
work 拒绝流入的数据包,除非与输出流量数据包相关或是ssh,ipp-client与dhcpv6-client服务则允许。
public 拒绝流入的数据包,除非与输出流量数据包相关或是ssh,dhcpv6-client服务则允许。
external 拒绝流入的数据包,除非与输出流量数据包相关或是ssh服务则允许。
dmz 拒绝流入的数据包,除非与输出流量数据包相关或是ssh服务则允许。
block 拒绝流入的数据包,除非与输出流量数据包相关。
drop 拒绝流入的数据包,除非与输出流量数据包相关。

过滤规则

source 根据源地址过滤
interface 根据网卡过滤
service 根据服务名过滤
port 根据端口过滤
icmp-block icmp 报文过滤,按照 icmp 类型配置
masquerade ip 地址伪装
forward-port 端口转发
rule 自定义规则

管理firewalld服务

# systemctl start firewalld         # 启动,
# systemctl enable firewalld        # 开机启动
# systemctl stop firewalld          # 关闭
# systemctl disable firewalld       # 取消开机启动

接下来我们会重点来看一下firewall-cmd命令,它是我们配置firewall的核心命令。

firewall-cmd

我们可以通过”firewall-cmd –help”来查看完整的命令帮助文档。

参数 作用
–get-default-zone 查询默认的区域名称。
–set-default-zone=<区域名称> 设置默认的区域,永久生效。
–get-zones 显示可用的区域。
–get-services 显示预先定义的服务。
–get-active-zones 显示当前正在使用的区域与网卡名称。
–add-source= 将来源于此IP或子网的流量导向指定的区域。
–remove-source= 不再将此IP或子网的流量导向某个指定区域。
–add-interface=<网卡名称> 将来自于该网卡的所有流量都导向某个指定区域。
–change-interface=<网卡名称> 将某个网卡与区域做关联。
–list-all 显示当前区域的网卡配置参数,资源,端口以及服务等信息。
–list-all-zones 显示所有区域的网卡配置参数,资源,端口以及服务等信息。
–add-service=<服务名> 设置默认区域允许该服务的流量。
–add-port=<端口号/协议> 允许默认区域允许该端口的流量。
–remove-service=<服务名> 设置默认区域不再允许该服务的流量。
–remove-port=<端口号/协议> 允许默认区域不再允许该端口的流量。
–reload 让“永久生效”的配置规则立即生效,覆盖当前的。

查看运行状态:

[root:~]# firewall-cmd –state
running

查看当前的区域:

[root:~]# firewall-cmd –get-default-zone
public

查看已被激活的区域信息:

[root:~]# firewall-cmd –get-active-zones
public
interfaces: eth0

查询eth0网卡的区域:

[root:~]# firewall-cmd –get-zone-of-interface=eth0
public

在public中分别查询ssh与http服务是否被允许:


[root:~]# firewall-cmd --zone=public --query-service=ssh
yes
[root:~]# firewall-cmd --zone=public --query-service=http
no

查看指定级别的接口:

[root:~]# firewall-cmd –zone=public –list-interfaces
ens33

查看指定级别的所有信息:


[root:~]# firewall-cmd --zone=public --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: ens33
sources:
services: dhcpv6-client ssh
ports:
protocols:
masquerade: no
forward-ports:
sourceports:
icmp-blocks:
rich rules:

查看所有级别被允许的信息:

[root:~]# firewall-cmd –get-service

RH-Satellite-6 amanda-client … xmpp-server

管理规则:


# firewall-cmd --panic-on # 丢弃
# firewall-cmd --panic-off # 取消丢弃
# firewall-cmd --query-panic # 查看丢弃状态
# firewall-cmd --reload # 更新规则,不重启服务
# firewall-cmd --complete-reload # 更新规则,重启服务

添加某接口至某信任等级,譬如添加 eth0 至 public,永久修改:

# firewall-cmd –zone=public –add-interface=eth0 –permanent

设置默认的信任级别:

[root:~]# firewall-cmd –set-default-zone=public
success

允许https服务流量通过public区域,要求立即生效且永久有效:

[root:~]# firewall-cmd –zone=public –add-service=https –permanent
success

不再允许http服务流量通过public区域,要求立即生效且永久生效:

[root:~]# firewall-cmd –zone=public –remove-service=http –permanent
success

允许 tcp 端口 8080 至 dmz 级别:

[root:~]# firewall-cmd –zone=dmz –add-port=8080/tcp
success

列出 dmz 级别的被允许的进入端口:

[root:~]# firewall-cmd –zone=dmz –list-ports

8080/tcp

允许某范围的 udp 端口至 public 级别,并永久生效:

[root:~]# firewall-cmd –zone=public –add-port=5000-6000/udp –permanent
success

改变ens33接口的区域

[root:~]# firewall-cmd –zone=work –change-interface=ens33 –permanent
success

删除 public zone 中的 eth0:

[root:~]# firewall-cmd –zone=public –remove-interface=eth0 –permanent
success

打开ip伪装:

[root:~]# firewall-cmd –zone=external –add-masquerade
success

关闭ip伪装:

[root:~]# firewall-cmd –zone=external –remove-masquerade

打开端口转发:

[root:~]# firewall-cmd –zone=public –add-masquerade

转发 tcp 22 端口至 3753

[root:~]#firewall-cmd –zone=public –add-forward-port=port=22:proto=tcp:toport=3753

转发22 端口数据至另一个 ip 的相同端口上:

[root:~]#firewall-cmd –zone=public –add-forward-port=port=22:proto=tcp:toaddr=192.168.1.100

转发 22 端口数据至另一 ip 的 2055 端口上:

[root:~]#firewall-cmd –zone=public –add-forward-port=port=22:proto=tcp:toport=2055:toaddr=192.168.1.100

封禁 ip:


[root:~]#firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='222.222.222.222' reject"

通过 ipset 来封禁 ip:


[root:~]#firewall-cmd --permanent --zone=public --new-ipset=blacklist --type=hash:ip

[root:~]#firewall-cmd --permanent --zone=public --ipset=blacklist --add-entry=222.222.222.222

参考文章:

《CentOS 7 下使用 Firewall》

《CentOS7-firewall防火墙使用》

分类: Linux 标签: ,

log4cpp简明教程

2017年10月13日 没有评论

log4cpp是一款c++开源的日志库,我也是刚刚使用,所以对它的了解不是很多。这是第一次在项目中使用log4cpp,当然我在c++方面的项目经验也是屈指可数。这篇文章会是一个简明的教程,方便类似我这样的新手可以快速入门,不会有太高深的内容。log4cpp相关的背景和特点这里也不再多说,感兴趣的可自行查找资料。

基本步骤

1、实例化1个appender对象

2、实例化1个layout对象,并将layout绑定到appender

3、获得Category并设置优先级和添加apprender

4、调用Category写日志

参考代码

这是1个根据日期划分日志文件的简单样例。main.cpp代码

#include <iostream>
#include <log4cpp/Category.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/DailyRollingFileAppender.hh>

using namespace std;
using namespace log4cpp;

int main() {
     DailyRollingFileAppender *appender = new DailyRollingFileAppender(
         string("default"),
         string("/Users/king/c_project/logger/build/log/data.log")
     );
     string patter = "%d[%p] %m%n";
     PatternLayout *layout = new PatternLayout();
     layout->setConversionPattern(patter);
     appender->setLayout(layout);
     Category& root = Category::getRoot();
     root.addAppender(appender);
     root.info("This is test.");
     root.error("sql error.");

     cout << "done!" << endl;
     return 0;
}

这里保存目录如果要指定日志保存目录,请使用绝对路径。

编译链接


c++ main.cpp -o logger -llog4cpp

./logger

分类: C/C++, Linux 标签:

php-fpm配置unixsock引发问题

2017年10月11日 没有评论

今天把博客从原来的阿里云迁移出来了,在新环境搭建环境时,nginx和php-fpm都已经配好的时候(其实还是有问题的),访问首页提示403错误。nginx和fpm的用户都配置为www-data,按道理说同一用户不应该没有访问文件的权限。以前nginx和fpm一直都是使用socket通信的方式,第一次使用unixsock方式配置,有点不知所措。

查看nginx的错误日志,发现最新的错误日志信息是类似这样的:

connect() to unix:/var/run/php-fpm.sock failed (13: Permission denied) while connecting to upstream

也就说出问题的地方是/var/run/php-fpm.sock文件,不是站点目录下文件的权限。在google上面搜索了一下,在stackoverflow上面发现一篇“nginx error connect to php5-fpm.sock failed (13: Permission denied)”,也是unixsock方式配置nginx和php-fpm的问题。问题里面给说的方案是需要设置一下unixsock中listen.user、listen.group、listen.mode这几个参数,按照默认的值将其前面的注释去掉即可,类似如下。


listen.owner = www-data
listen.group = www-data
listen.mode = 0660

然后重启php-fpm,重新访问页面,一切正常了。

分类: Linux, PHP 标签: