Jenkins的使用(一)—安装

Jenkins是什么

Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。

功能

持续的软件版本发布/测试项目。
监控外部调用执行的工作。

在CentOS7.0中安装Jenkins

安装Jenkins最新版本

添加Jenkins的yum源到系统中,并安装

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
yum install jenkins

安装jenkins标准版本

添加Jenkins的标准版yum源到系统中,并安装

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
yum install jenkins

安装JAVA

java环境采用openjdk1.7版本以上

yum install java

Jenkins的启动停止

service jenkins start/stop/restart
chkconfig jenkins on

NOTE: 如果有如下错误信息,请确认JAVA是否安装

Starting jenkins (via systemctl): Job for jenkins.service failed. See ‘systemctl status jenkins.service’ and ‘journalctl -xn’ for details. 
[FAILED]

该安装包都做了哪些工作

  • Jenkins做为守护进程在后台启动,启动脚本路径 /etc/init.d/jenkins
  • 创建了‘jenkins’用户用来运行该服务,如果更改该用户为别的用户,则必须更改/var/log/jenkins, /var/lib/jenkins, and /var/cache/jenkins的授权
  • 日志文件路径/var/log/jenkins/jenkins.log
  • 启动时的配置参数文件/etc/sysconfig/jenkins
  • 默认监听端口 8080
  • Jenkins RPM Repository /etc/yum.repos.d/jenkins.repo

开启防火墙规则

firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --reload
firewall-cmd --list-all

禁用SELinux

setenforce 0
vi /etc/sysconfig/selinux 
SELINUX=disabled

访问URL

http://ServerIP:8080/

两种方法解决Linux wget报错-bash: wget command not found

今天使用DigitalOcean的vps服务器,wget 时提示 -bash:wget command not found.

估计是安装的Linux系统, CentOS+7.4+x64wget软件包没有默认被安装。

可以通过以下两种方法来安装:

1、rpm 安装

rpm 下载源地址:http://mirrors.163.com/centos/6.5/os/x86_64/Packages/

下载wget的RPM包:http://mirrors.163.com/centos/6.5/os/x86_64/Packages/wget-1.12-1.4.el6.x86_64.rpm

rpm ivh wget-1.12-1.4.el6.x86_64.rpm 安装即可。

如果客户端用的是SecureCRT,linux下没装rzsz 包时,rz无法上传文件怎么办?

网上看到一个办法,安装另一个SSH客户端:SSH Secure Shell。然后传到服务器上安装,这个比较费劲,所以推荐用第二种方法。

2、yum安装

yum -y install wget

第二种方法明显更简单一些。不过如果yum包也没有安装的话,那就只能用第一种方法了。

顺便分享2个中国大陆开源镜像站,有需要的时候应该能提供不少方便:

搜狐开源镜像站:http://mirrors.sohu.com/

网易开源镜像站:http://mirrors.163.com/

Linux screen报错-bash: screen: command not found 解决

如果linux系统中没有安装screen。

会出现:-bash: screen: command not found

可以自行安装:

如果你使用yum软件包管理工具:yum install screen
如果你使用APT软件包管理工具:apt-get install screen

你也可以到Screen的官方网站去下载软件包,地址是:
http://www.gnu.org/software/screen/

剖析Linux系统调用的执行路径

在什么是操作系统这篇文章中,介绍过操作系统像是一个代理一样,为我们去管理计算机的众多硬件,我们需要计算机的一些计算服务、数据管理的服务,都由操作系统提供接口来完成。这样做的好处是让一般的计算机使用者不用关心硬件的细节。

1. 什么是操作系统的接口

既然使用者是通过操作系统接口来使用计算机的,那到底是什么是操作系统提供的接口呢?

接口(interface)这个词来源于电气工程学科,指的是插座与插头的连接口,起到将电与电器连接起为的功能。后来延伸到软件工程里指软件包向外提供的功能模块的函数接口。所以接口是用来连接两个东西、信号转换和屏蔽细节。

那对于操作系统来说:操作系统通过接口的方式,建立了用户与计算机硬件的沟通方式。用户通过调用操作系统的接口来使用计算机的各种计算服务。为了用户友好性,操作系统一般会提供两个重要的接口来满足用户的一些一般性的使用需求:

  • 命令行:实际是一个叫bash/sh的端终程序提供的功能,该程序底层的实质还是调用一些操作系统提供的函数。

  • 窗口界面:窗口界面通过编写的窗口程序接收来自操作系统消息队列的一些鼠标、键盘动作,进而做出一些响应。

对于非一般性使用需求,操作系统提供了一系列的函数调用给软件开发者,由软件开发者来实现一些用户需要的功能。这些函数调用由于是操作系统内核提供的,为了有别于一般的函数调用,被称为系统调用。比如我们使用C语言进行软件开发时,经常用的printf函数,它的内部实际就是通过write这个系统调用,让操作系统内核为我们把字符打印在屏幕上的。

为了规范操作系统提供的系统调用,IEEE制定了一个标准接口族,被称为POSIX(Portable Operating System Interface of Unix)。一些我们熟悉的接口比如:fork、pthread_create、open等。

2. 用户模式与内核模式

计算机硬件资源都是操作系统内核进行管理的,那我们可以直接用内核中的一些功能模块来操作硬件资源吗?可以直接访问内核中维护的一些数据结构吗? 当然不行!有人会说,为什么不行呢?我买的电脑,内核代码在内存中,那内存不都是我自己买的吗?,我自己不能访问吗?
现在我们运行的操作系统都是一个多任务、多用户的操作系统。如果每个用户进程都可以随便访问操作系统内核的模块,改变状态,那整个操作系统的稳定性、安全性都大大降低了。

为了将内核程序与用户程序隔离开,在硬件层面上提供了一次机制,将程序执行的状态分为了不同的级别,从0到3,数字越小,访问级别越高。0代表内核态,在该特权级别下,所有内存上的数据都是可见的,可访问的。3代表用户态,在这个特权级下,程序只能访问一部分的内存区域,只能执行一些限定的指令。

操作系统在建立GTD表的时候,将GTD的每个表项中的2位(4种特权级别)设置为特权位(DPL),然后操作系统将整个内存分为不同的段,不同的段,在GDT对应的表项中的DPL位是不同的。比如内核内存段的所有特权位都为00。而用户程序访存时,在保护模式下都是通过段寄存器+IP寄存器来访问的,而段寄存器里则用两位表示当前进程的级别(CPL),是位于内核态还是用户态。

既然如此,那我们还有什么办法可以调用操作系统的内核代码呢?操作系统为了实现系统调用,提供了一个主动进入内核的惟一方式:中断指令int。int指令会将GDT表中的DPL改为3,让我们可以访问内核中的函数。所以所有的系统调用都必须通过调用int指令来实现,大致的过程如下:

  • 用户程序中包含一段包含int指令的代码
  • 操作系统写中断处理,获取相调程序的编号
  • 操作系统根据编号执行相应的代码

3. 剖析printf函数

下面我们以printf函数的调用为例,说明该函数是如何一步一步最终落在内核函数上去的。

未分类
图1:应用程序、库函数和内核系统调用之间的关系

printf函数是C语言的一个库函数,它并不是真正的系统调用,在Unix下,它是通过调用write函数来完成功能的。

write函数内部就是调用了int中断。一般的系统调用都是调用0x80号中断。而操作系统中一般不会的显式的写出write的实现代码,而是通过_syscall3宏展开的实现。_syscall3是专门用来处理有3个参数的系统调用的函数的实现。同理还有_syscall0、_syscall1和_syscall2等,目前最大支持的参数个数为3个,这三个参数是通过ebx, ecx,edx传递的。如果有系统调用的参数超过了3个,那么可以通过一个参数结构体来进行传递。

// linux/lib/write.c
#define __LIBRARY__
#include <unistd.h>
// 
_syscall3(int,write,int,fd,const char *,buf,off_t,count)
// linux/include/unistd.h
#define _syscall3(type,name,atype,a,btype,b,ctype,c) 
type name(atype a,btype b,ctype c) 
{ 
long __res; 
__asm__ volatile ("int $0x80" 
    : "=a" (__res) 
    : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); 
if (__res>=0) 
    return (type) __res; 
errno=-__res; 
return -1; 
}

所以宏展开后,write函数的实现实现为:

int write(int fd, const char *buf, off_t count)
{ 
    long __res; 
    __asm__ volatile ("int $0x80" 
        : "=a" (__res) 
        : "0" (__NR_write),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); 
    if (__res>=0) 
        return (type) __res; 
    errno=-__res; 
    return -1; 
}

我们看到实际函数内部并没有做太多的事情,主要就是调用int 0x80,将把相关的参数传递给一些通用寄存器,调用的结果通过eax返回。其中一个很重要的调用参数是__NR_write这个也是一个宏,就是wirte的系统调用号,在linux/include/unistd.h中被定义为4,同样还有很多其他系统调用号。因为所有的系统调用都是通过int 0x80,那怎么知道具体需要什么功能呢,只能通过系统调用号来识别。

下面我们来看看int 0x80是如何执行的。这是一个系统中断,操作系统对于中断处理流程一般为:

  • 关中断:CPU关闭中段响应,即不再接受其它外部中断请求
  • 保存断点:将发生中断处的指令地址压入堆栈,以使中断处理完后能正确地返回。
  • 识别中断源:CPU识别中断的来源,确定中断类型号,从而找到相应的中断服务程序的入口地址。
  • 保护现场所:将发生中断处理有关寄存器(中断服务程序中要使用的寄存器)以及标志寄存器的内存压入堆栈。
  • 执行中断服务程序:转到中断服务程序入口开始执行,可在适当时刻重新开放中断,以便允许响应较高优先级的外部中断。
  • 恢复现场并返回:把“保护现场”时压入堆栈的信息弹回原寄存器,然后执行中断返回指令(IRET),从而返回主程序继续运行。

前3项通常由处理中断的硬件电路完成,后3项通常由软件(中断服务程序)完成。

未分类
图2:系统调用中断处理流程

那0x80号中断的处理程序是什么呢,我们可以看一下操作系统是如何设置这个中断向量表的。在操作系统初始化时shecd_init函数里,调用了

set_system_gate(0x80, &system_call);

我们深入看一下set_system_gate函数做了什么

#define _set_gate(gate_addr,type,dpl,addr) 
__asm__ ("movw %%dx,%%axnt" 
    "movw %0,%%dxnt" 
    "movl %%eax,%1nt" 
    "movl %%edx,%2" 
    : 
    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), 
    "o" (*((char *) (gate_addr))), 
    "o" (*(4+(char *) (gate_addr))), 
    "d" ((char *) (addr)),"a" (0x00080000))

#define set_system_gate(n,addr) 
    _set_gate(&idt[n],15,3,addr)

通过上面的代码,我们可以看出,set_system_gate把第0x80中断表的表项中中断处理程序入口地址设置为&system_call。并且把那一项IDT表中的DPL设置了为3, 方便用户程序可以去访问这个地址。

所以init 0x80最终会被system_call这个函数地址处的代码来实际处理。让我们看下system_call做了什么事情。

# linux/kernel/system_call.s
nr_system_calls=72 # 最大的系统调用个数
.globl _system_call

system_call:
    cmpl $nr_system_calls-1,%eax    # eax中放的系统调用号,在write的调用过程中为__NR_write = 4
    ja bad_sys_call
    push %ds        # 下面是一些寄存器保护,后面还要弹出
    push %es
    push %fs
    pushl %edx
    pushl %ecx              # push %ebx,%ecx,%edx as parameters
    pushl %ebx              # to the system call
    movl $0x10,%edx         # set up ds,es to kernel space
    mov %dx,%ds             # 把ds的段标号设置为0001 0000(最后2位是特权级),所以段号为4,内核态数据段
    mov %dx,%es
    movl $0x17,%edx     # 把fs的段标号设置为0001 0111(最后2位是特权级),所以段号为5,用户态数据段
    mov %dx,%fs
    call sys_call_table(,%eax,4)        # 实际的系统调用
    pushl %eax
    movl current,%eax
    cmpl $0,state(%eax)     # state 检测是否为就绪状态
    jne reschedule                        # 进入调度程序
    cmpl $0,counter(%eax)       # counter 查看信号状态
    je reschedule
ret_from_sys_call:
    movl current,%eax       # task[0] cannot have signals
    cmpl task,%eax
    je 3f
    cmpw $0x0f,CS(%esp)     # was old code segment supervisor ?
    jne 3f
    cmpw $0x17,OLDSS(%esp)      # was stack segment = 0x17 ?
    jne 3f
    movl signal(%eax),%ebx
    movl blocked(%eax),%ecx
    notl %ecx
    andl %ebx,%ecx
    bsfl %ecx,%ecx
    je 3f
    btrl %ecx,%ebx
    movl %ebx,signal(%eax)
    incl %ecx
    pushl %ecx
    call do_signal
    popl %eax
3:  popl %eax
    popl %ebx
    popl %ecx
    popl %edx
    pop %fs
    pop %es
    pop %ds
    iret

我们可以发现,上面代码中大部分代码是寄存器状态保存与恢复,堆栈段的切换。核心代码为call sys_call_table(,%eax,4),它是一个函数调用,函数的地址为sys_call_table(,%eax,4) = sys_call_table + 4*%eax说明sys_call_table为一个数组入口,数组中的元素长度都为4个字节,我们要访问数组中的第%eax个元素。而%eax即为系统调用号。sys_call_table就是所有系统调用的函数指针数组。

// 定义在 linux/include/linux/sys.h
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };

到这里,我们找到了最终真正的执行核心函数地址sys_write,这个是操作实现的内核代码,所有的屏幕打印就是由该函数最终实现。它里面涉及IO的一些硬件驱动函数,我们在这里就不再继续深入了。

到此,我们已经通过printf这样一个上层的函数接口,清楚操作系统是如何一步步为了我们提供了一个内核调用的方法。如此的精细控制,让人感叹。

4. 我们如何为操作系统添加一个系统调用

下面简单说明一下,如何在操作系统源码中添加两个我们自己的系统调用whoami和iam

  • iam系统调用把我们指定的一个字符串保存在内核中。
  • whoami把内核中的通过iam设置的那个字符串读取出来。

下面是具体的操作步骤。

  • 在linux/kernel文件夹加入一个自定义的文件who.c
  • 在who.c中实现sys_iam和sys_whoami,需要注意的实现这两个函数时,需要用于用户栈数据与内核栈数据拷贝。
  • 在linux/include/linux/sys.h中的sys_call_table中添加两个数组项。
  • 修改linux/kernel/system_call.s中的系统调用个数nr_system_calls。
  • 用int 0x80实现iam和whoami函数。
  • 编写用户程序调用上面两个函数。

要注意的是:在系统调用的过程中,段寄存器ds和es指向内核数据空间,而fs被设置指向用户数据空间。因此在实际数据块信息传递过程中Linux内核就可以利用fs寄存器来执行内核数据空间与用户数据空间之间的数据复制工作,并且在复制过程中内核程序不需要对数据边界范围作任何检查操作。边界检查操作由CPU自动完成。内核程序的实际数据传送工作可以使用get_fs_byte()和puts_fs_bypte()等函数进行。

php+crontab+shell方案实现的秒级定时发起异步请求回调方案

方案介绍

该方案出来的场景:一天有一个业务需求,需要把我方的一些信息或订单状态等异步发起请求同步给第三方,这里就会出现定时时间和延迟时间消息的处理,考虑过很多消息队列方案(如:rabbitmq、云消息服务等)。

不过最后公司定了因为该业务流量很小,不用做那么麻烦。所以就直接出了这个方案

该方案在50条消息/s,应该压力不大,量大了就会出现一个消息延迟问题,如果不注重这个业务时间准确性,该方案承载的秒级处理在1000内应该问题也不大

方案架构图

未分类

mysql的任务队列表

CREATE TABLE `open_queue` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '任务类型,可用于后续读取不同任务失败次数区分',
  `order_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '业务绑定的订单id等,根据自己自行设计业务id',
  `event_identity` varchar(30) NOT NULL DEFAULT '' COMMENT '事件表示分类,可不用,同type',
  `data` text NOT NULL COMMENT '需要处理的数据,json格式化',
  `try` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '特定任务需要当时就处理的次数,而不是发起回调请求的任务',
  `again` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0不是重试记录 1重试记录,失败后发起任务未1,否者未0',
  `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否被执行 1是 0否',
  `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '任务创建的时间戳',
  `at_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '任务时间的时间戳',
  `task_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '执行结果 -1重复消息系统主动取消 0未执行 1执行成功 2执行失败',
  `task_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '执行时间',
  PRIMARY KEY (`id`),
  KEY `IDX_EVENT` (`event_identity`),
  KEY `IDX_AT_TIME` (`at_time`) USING BTREE,
  KEY `IDX_ORDER` (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

方案实现源码

下方代码都是伪代码,大家自行根据思路自己设计业务

redis同步到mysql

该步骤可以省略,因为我方当时redis是单机部署,也没做持久化,

$redisTypeLock; //lock类型
if (!IS_WIN) {
    $file = fopen($path . "/lock/{$redisTypeLock}.lock","w+");
    if (!flock($file, LOCK_EX | LOCK_NB)) {
        flock($file,LOCK_UN);
        fclose($file);
        exit;
    }
}

//成功获得锁 开始业务执行

$count = get_redis_lLen('msgevent:' . $redisType);
if (!$count) {
    //无需要入数据库订单队列,直接返回
    return false;
}

$successCount = 0;
for ($i = 0; $i < $loopLen; $i++) {
    $data = get_redis_lPop('msgevent:' . $redisType);
    //把redis数据格式化存入到数据库持久化
    $result = $this->saveToMysql($data);
    if (!$result) {
        //如果执行失败,重新推入redis队列
        get_redis_lPush('msgevent:' . $redisType, $data);
    }

    $successCount++;
}

if (!IS_WIN) {
    flock($file, LOCK_UN);
    fclose($file);
}

定时执行任务的的入口

这个入口是定时秒级处理脚本和处理小于当前时间的脚本调用的入口

public function task_run()
{
    $timeType = $_GET['tt'] ? : 'now';
    //获取当前秒需要处理的所有消息, 依次路由后执行
    //如果你redis无需存入mysql,你可以redis的list结构实现, 一次全部取出数据集合,并删除list
    //如果接下去业务里发现发起请求失败了,重新把任务分配给redis建立恢复list,list名为t+需要什么时间执行的时间戳
    $lists = $this->getTaskLists($timeType);
    foreach ($lists as $item) {
        $this->task('event:' . $item['event_type']);
        //$this->task('event:demo1');
        //$this->task('event:demo2');
        //$this->task('event:demo3');
    }
}

实际处理业务(发起通知请求)

# 模拟一个发起的请求事件demo
private function pushDemo1Event($info)
{
    $sendInfoData = $this->getSendInfoFromInfo($info);

    //发起请求,这里做的真正的发起给对方的请求,你可以根据$sendInfo拼接各种事件或消息分类给对方
    $isSuccess = $this->sendInfo($sendInfoData);

    $data = array(
        'status'        => 1,
        'task_status'   => $isSuccess ? 1 : 2,
        'task_time'     => NOW_TIME
    );

    $db->save($data);

    //如果执行失败,重新插入一条记录,并把at_time生成下次执行的时间戳,这样定时器根据at_time字段可以取出判断执行到当时的秒级
    if (!$isSuccess) {

        $tryCount = $db->where(...)->count();

        if ($tryCount >= 5) {
            return false;
        }

        $this->newTask($info, $tryCount, 30);
    }
}

private function newTask($info, $tryCount, $timeout = 30)
{
    $newTime = $this->nextTaskTime($info, $timeout);
    $data = array(
        ...
        'at_time' => newTime //执行的时间(时间戳)
    );
    $db->add($data);
}

定时消费方

$timeType = $_GET['time_type'] ? : 'now';
$lists = $this->getMsgAll($timeType);
if (!$lists) {
    return false;
}

foreach ($lists as $item) {
    if ($item['type'] == '1') {
        //根据业务生成我方真实订单
        call_user_func_array(array($this, "add_real_order"), array($item));
    }
    if (in_array($item['type'], array(2,3))) {
        $this->push('push:toengineer', $item);  //指派或取消工程师
    }
    if ($item['type'] == '5') {
        $this->push('push:edit', $item);  //编辑
    }
    if ($item['type'] == '6') {
        $this->push('push:cancel', $item);  //取消
    }
    if ($item['type'] == '7' || $item['type'] == '8' || $item['type'] == '9') {
        $this->push('push:status', $item);  //简单订单状态
    }
}

借助crontab+shell外力实现秒级执行

因为crontab是最小单位是分钟,所以需要借助shell脚本来实现秒级执行

读取redis任务队列到mysql存储

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    #执行php直接返回,不会阻塞 不要忘记最后面的 &
    $(/usr/bin/php index.php redis2mysql > /dev/null 2>&1 &)
    sleep 1
done

exit 0

处理秒级时间sh脚本

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    $(/usr/bin/php index.php task_run > /dev/null 2>&1 &)
    sleep 1
done

exit 0

处理秒级小于当前时间sh脚本

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    $(/usr/bin/php index.php task_run/tt/lt > /dev/null 2>&1 &)
    sleep 1
done

exit 0

linux下的crontab

* * * * * sh /web/project/src/shell/repair_build_order.sh
* * * * * sh /web/project/src/shell/repair_sync_second.sh
* * * * * sh /web/project/src/shell/repair_sync_lt_time.sh

php模拟linux crontab实现定时计划任务的方法

本文分享一下php模拟linux crontab实现定时计划任务的方法。

PHP定时计划任务需要两个文件。

1、crontab.conf.php (配置文件)

<?php
//当为0时,关闭计划任务
return 1;

2、crontab.php (主体文件)

<?php
ignore_user_abort();//关掉浏览器,PHP脚本也可以继续执行.
set_time_limit(0);// 通过set_time_limit(0)可以让程序无限制的执行下去

$interval = 60 * 3;// 每隔3分钟运行
$i = 0;

do {
    $nowTime = date("Y-m-d H:m:s");
    $run = include 'crontab.conf.php';

    if (!$run) {
        file_put_contents("crontab_task.log", "==计划任务已结束 $nowTime==rn", FILE_APPEND);
        die('Job has ended.');
    }

    //此处放要执行的代码
    sleep($interval);// 等待3分钟
    $data = "$i>计划任务正在运行中...(运行状态:$run)  $nowTimern";
    file_put_contents("tasktest.txt", $data, FILE_APPEND);
    $i++;
} while (true);

主要改变 crontab.conf.php 中 return 0 即可实现控制这个计划任务的关闭。

注意:当php服务关掉之后该计划任务脚本也会停掉并且不会自动重启。

为Docker Swarm群集配置Nutanix持久存储为Docker Swarm群集配置Nutanix持久存储

本文介绍如何用Docker卷插件的方式,给Docker Swarm的群集挂载Nutanix存储。Nutanix Container Volume Plug-in 简称DVP,可以给容器提供数据持久化的功能。

本文使用ownCloud网盘应用做功能测试。测试的过程如下,安装部署Docker Datacenter,配置好群集,在UCP的界面里调用DVP插件建持久的数据卷,建立ownCloud服务,部署和测试该服务。

Nutanix DVP (Docker Volume Plug-in)安装和配置

这一部分描述DVP的安装部署过程,需要连接互联网;安装调试完毕之后,作虚拟机的镜像模板使用。这样Docker Swarm的其它节点也都不需要重复这个步骤了。

本文使用的是Docker社区文档稳定版 17.03.1-ce ;本文使用的OS是CentOS 7.3。所Docker安装的版本如下:

[root@centos7-temp]# docker version
Client:
 Version:      17.03.1-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:05:44 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.1-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Mon Mar 27 17:05:44 2017
 OS/Arch:      linux/amd64
 Experimental: false

[root@centos7-temp]# rpm -qa|grep docker
docker-ce-17.03.1.ce-1.el7.centos.x86_64
docker-ce-selinux-17.03.1.ce-1.el7.centos.noarch

本文使用的Docker 安装yum源如下:

[root@centos7-temp]# cat /etc/yum.repos.d/docker-ce.repo
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://download.docker.com/linux/centos/7/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-stable-debuginfo]
name=Docker CE Stable - Debuginfo $basearch
baseurl=https://download.docker.com/linux/centos/7/debug-$basearch/stable
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-stable-source]
name=Docker CE Stable - Sources
baseurl=https://download.docker.com/linux/centos/7/source/stable
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-edge]
name=Docker CE Edge - $basearch
baseurl=https://download.docker.com/linux/centos/7/$basearch/edge
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-edge-debuginfo]
name=Docker CE Edge - Debuginfo $basearch
baseurl=https://download.docker.com/linux/centos/7/debug-$basearch/edge
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-edge-source]
name=Docker CE Edge - Sources
baseurl=https://download.docker.com/linux/centos/7/source/edge
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-test]
name=Docker CE Test - $basearch
baseurl=https://download.docker.com/linux/centos/7/$basearch/test
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-test-debuginfo]
name=Docker CE Test - Debuginfo $basearch
baseurl=https://download.docker.com/linux/centos/7/debug-$basearch/test
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

[docker-ce-test-source]
name=Docker CE Test - Sources
baseurl=https://download.docker.com/linux/centos/7/source/test
enabled=0
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg

本机所使用的所有安装源如下:

[root@centos7-temp]# yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * epel: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
repo id                                   repo name                                                         status
base/7/x86_64                             CentOS-7 - Base - mirrors.aliyun.com                               9,363
docker-ce-stable/x86_64                   Docker CE Stable - x86_64                                              4
epel/x86_64                               Extra Packages for Enterprise Linux 7 - x86_64                    11,808
extras/7/x86_64                           CentOS-7 - Extras - mirrors.aliyun.com                               381
updates/7/x86_64                          CentOS-7 - Updates - mirrors.aliyun.com                            1,859
repolist: 23,415

安装docker引擎,并启动服务,并校验服务状态。安装过程参考如下:

[root@centos7-temp]# yum install -y docker-ce
[root@centos7-temp]# systemctl enable docker
[root@centos7-temp]# systemctl start docker
[root@centos7-temp]# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2017-06-20 20:30:49 CST; 19min ago
     Docs: https://docs.docker.com
 Main PID: 875 (dockerd)
   CGroup: /system.slice/docker.service
           ├─ 875 /usr/bin/dockerd
           ├─ 942 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-in...
           ├─2008 docker-containerd-shim 0ca2346b6126de702fb4dda5f807c0a69a402eb643f15c142730277d0eb7bbcb /var/...
           └─0ca2346b6126de702fb4dda5f807c0a69a402eb643f15c142730277d0eb7bbcb
             └─2038 /usr/bin/python /code/main.py --prism-ip 10.68.69.22 --dataservices-ip 10.68.69.23 --prism-...

到目前为止,Docker安装配置完成。

下面开始安装DVP,安装和配置过程参考页面。

https://store.docker.com/plugins/nutanix-dvp-docker-volume-plug-in

下面是给操作系统安装iscsi initiator服务的参考步骤:

yum install -y iscsi-initiator-utils 
systemd-tmpfiles --create
systemctl start iscsid
systemctl enable iscsid
systemctl status iscsid
● iscsid.service - Open-iSCSI
   Loaded: loaded (/usr/lib/systemd/system/iscsid.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2017-06-20 20:30:46 CST; 24min ago
     Docs: man:iscsid(8)
           man:iscsiadm(8)
 Main PID: 888 (iscsid)
   CGroup: /system.slice/iscsid.service
           ├─882 /usr/sbin/iscsid
           └─888 /usr/sbin/iscsid

Jun 20 20:30:46 centos7-temp.zenlab.local systemd[1]: Starting Open-iSCSI...
Jun 20 20:30:46 centos7-temp.zenlab.local iscsid[878]: iSCSI logger with pid=882 started!
Jun 20 20:30:46 centos7-temp.zenlab.local systemd[1]: Failed to read PID from file /var/run/iscsid.pid: Inva...ent
Jun 20 20:30:46 centos7-temp.zenlab.local systemd[1]: Started Open-iSCSI.
Jun 20 20:30:47 centos7-temp.zenlab.local iscsid[882]: iSCSI daemon with pid=888 started!
Hint: Some lines were ellipsized, use -l to show in full.

解释一下DVP的工作原理是,它是让Docker主机通过iSCSI协议连接Nutanix的存储服务。DVP插件的配置里包含了连接存储服务和存储容器(这个容器是Nutanix的存储术语,非Docker说的容器)的相关信息。这样Docker主机上用该卷插件建立的数据卷都会指向Nutanix后台的存储容器中;数据通过iSCSI协议连接Nutanix存储服务的时候,就可以利用到Nutanix群集提供的负载均衡能力;当数据块写入Nutanix存储池的过程中和之后,就可以利用到到Nutanix存储容器所具备的其它重要特性:数据块2~3副本的高可靠性、冷热数据分成、压缩、去重、纠删码等;而且存储空间对于容器或者Docker Swarm里的服务都是透明和无限容量的。

现在做一些安装DVP的准备工作,询问Nutanix系统管理员下面信息:

  • 获得Prism 的IP
  • 获得Nutanix群集数据服务的IP,这个IP是群集上的虚拟服务IP
  • 获得群集的用户名和密码
  • 新建一个测试存储容器,获得容器名

参考下面的DVP安装命令:

docker plugin install ntnx/nutanix_volume_plugin PRISM_IP="10.68.69.22" DATASERVICES_IP="10.68.69.23" PRISM_PASSWORD="nutanix/4u" PRISM_USERNAME="admin" DEFAULT_CONTAINER="ddc-sc1" --alias nutanix

以上的命令执行结果如下:

[root@centos7-temp]# docker plugin install ntnx/nutanix_volume_plugin PRISM_IP="10.68.69.22" DATASERVICES_IP="10.68.69.23" PRISM_PASSWORD="nutanix/4u" PRISM_USERNAME="admin" DEFAULT_CONTAINER="ddc-sc1" --alias nutanix
Plugin "ntnx/nutanix_volume_plugin" is requesting the following privileges:
 - network: [host]
 - mount: [/dev]
 - mount: [/lib/modules]
 - mount: [/etc/iscsi]
 - mount: [/var/lock/iscsi]
 - mount: [/proc]
 - allow-all-devices: [true]
 - capabilities: [CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_IPC_LOCK CAP_IPC_OWNER CAP_NET_ADMIN CAP_MKNOD CAP_SYS_MODULE]
Do you grant the above permissions? [y/N] y (输入y,按回车)
latest: Pulling from ntnx/nutanix_volume_plugin
be892c8cb64d: Download complete
Digest: sha256:5a3730ffae077eb6ddc0c125620283d56852528b686cbe42f2f58696eab82c0d
Status: Downloaded newer image for ntnx/nutanix_volume_plugin:latest
Installed plugin ntnx/nutanix_volume_plugin

确认VDP安装结果,这个插件应该是最新版、启动的状态,如下:

[root@centos7-temp]# docker plugin ls
ID                  NAME                DESCRIPTION                        ENABLED
f0e38fbc11b3        nutanix:latest      Nutanix volume plugin for docker   true

执行下面的测试,确认DVP工作正常。

[root@centos7-temp]# docker volume create testvol -d nutanix:latest
testvol
[root@centos7-temp]# docker volume ls
DRIVER              VOLUME NAME
nutanix:latest      testvol
[root@centos7-temp]#

回到Nutanix的Prisum界面(主要的群集管理图形化界面)中查看Storage –> table –> Volume Group,应该能看到这个命令所创建的名为testvol的数据卷。如下图所示:

未分类

在命令行删除这个测试的卷。

[root@centos7-temp]# docker volume rm testvol
testvol
[root@centos7-temp]# docker volume ls
DRIVER              VOLUME NAME
[root@centos7-temp]#

在回到Prisum界面中查看刚才看到的那个卷应该就消失了。到此为止所有节点的DVP部署配置工作就完毕了,并且确认docker服务和DVP功能都很正常。用 sys-unconfig 命令关机,把这个虚拟机在Prisum里面做一个快照备用,也可以在Nutanix的acli命令行里面把它做成一个基础镜像。

我们已经理解和熟悉了DVP的基本操作,配置和部署,下面开始安装Docker Datacenter;Docker Datacenter的架构图如下所示:

未分类

本文安装的架构是:

  • 一个 UCP manager 节点
  • 一个 DTR 节点
  • 两个 worker node 节点

在Nutanix的Prisum中用刚才制作的那个快照或者镜像模板,克隆/新建4个虚拟机。虚拟机的参考配置如下:

  • 2 vCPU
  • 4GB RAM
  • 50GB Disk

安装UCP(Docker Universal Control Plane)节点

在Nutanix的Prisum中从刚才新建的四个虚拟机中选择一个,Power on开机;ssh登录到操作系统内之后,设定主机名和IP地址。

安装配置参考文档:https://docs.docker.com/datacenter/ucp/2.1/guides/admin/install/install-offline/#download-the-offline-package

注意事项,提前下载好安装包,这个tar包里面包含了UCP需要的所有镜像,可以一次性导入到UCP的节点上。

wget https://packages.docker.com/caas/ucp_images_2.1.4.tar.gz -O docker-datacenter.tar.gz

docker load < docker-datacenter.tar.gz

载入完毕后,可以看到如下镜像。

[root@ucp-master ~]# docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
docker/ucp-metrics      2.1.4               e3e24ef156bd        3 weeks ago         92.2 MB
docker/ucp-swarm        2.1.4               d8b51d6801e5        3 weeks ago         21 MB
docker/ucp-hrm          2.1.4               38a19323327d        3 weeks ago         14.8 MB
docker/ucp-etcd         2.1.4               9aa382502e19        3 weeks ago         38.5 MB
docker/ucp-controller   2.1.4               5a852aa3039e        3 weeks ago         28 MB
docker/ucp-dsinfo       2.1.4               66ee9368796a        3 weeks ago         159 MB
docker/ucp              2.1.4               7a28dbfc44e4        3 weeks ago         19.1 MB
docker/ucp              latest              7a28dbfc44e4        3 weeks ago         19.1 MB
docker/ucp-cfssl        2.1.4               acdc1f147711        3 weeks ago         15.1 MB
docker/ucp-compose      2.1.4               25775e989077        3 weeks ago         32.9 MB
docker/ucp-auth-store   2.1.4               f27ad13dee6c        3 weeks ago         58.7 MB
docker/ucp-agent        2.1.4               d716a096c331        3 weeks ago         22.5 MB
docker/ucp-auth         2.1.4               1f4739cd3c08        3 weeks ago         25.1 MB
[root@ucp-master ~]#

安装UCP的命令参考如下:

docker run --rm -it --name ucp 
  -v /var/run/docker.sock:/var/run/docker.sock 
  docker/ucp:2.1.4 install 
  --host-address 10.68.69.12 
  --interactive

以上命令中10.68.69.12是UCP主机的ip地址,建议UCP使用固定IP。以上命令完毕后用浏览器访问这个IP。

参考以下文档,完成UCP的安装步骤,其中需要到Docker网站获得30天的试用版许可证文件。

https://docs.docker.com/datacenter/ucp/2.1/guides/admin/install/

能够正常登陆访问UCP之后,在首页下方点击 【Add Node】按钮,获得加其它节点到群集里的命令,参考命令如下:

docker swarm join 
  --token SWMTKN-1-1310ah7gzj9e7bk6a5yobo2qyiwf93ybrd29flkved1zqydd6i-7pir0884sag5pjofwzjq5o1um 
  10.68.69.12:2377

把以上命令记录在写字板中备用。

加3个节点到群集里

把剩下的三个虚拟机开机,进入操作系统后设定主机名和IP。其中的一个安装DTR(Docker镜像仓库)的节点建议使用固定IP。

在每个操作系统里面用docker命令确认DVP是否正常。

  • docker plugin ls
  • docker volume ls
  • systemctl status iscsid

下面就可以把上一步所记录命令在命令行里面执行以下,完毕之后回到UCP的界面中查看是否它们已经添加成功。如下图所示:

未分类

安装DTR-Docker镜像仓库

在UCP首页的下方,找到并点击 【Install DTR】的按钮,取得安装命令(记得从清单中选择固定IP地址的DTR主机);在登录DTR主机的控制台里面输入这个命令,命令如下:

docker run -it --rm 
  docker/dtr:2.2.5 install 
  --ucp-node 10.68.69.12 
  --ucp-insecure-tls

DTR节点没有离线安装的整合包,它需要联网下载很多相关镜像,如果网络速度不是很快的话,下载和安装的过程需要至少半个小时,过程中还需要输入UCP的管理员,用户名和密码。

参考文档如: https://docs.docker.com/datacenter/dtr/2.2/guides/admin/install/#step-3-install-dtr

DTR正常工作了以后,登录建立一个名为owncloud的镜像库,点击【New Rrepository】输入owncloud。 在一个节点上下载owncloud镜像,添加新的tag,上传到这个镜像到镜像库里备用。参考命令如下:

docker login dtr.zenlab.local
docker pull owncloud
docker tag owncloud:latest dtr.zenlab.local/admin/owncloud:latest
docker push dtr.zenlab.local/admin/owncloud:latest
Screen Shot 2017-06-20 at 10.10.13 P

未分类

注意:如果你的环境中没有DNS,就把dtr.zenlab.local换成DTR的IP地址。

以上这个步骤主要是方便以后,反复使用和测试这个镜像的可能性,如果所有的节点都有高速的互联网链接,可以忽略以上步骤。

Docker Swarm群集中使用DVP

这里使用UCP的图形化界面,在一个所有节点都配置和部署了VDP的群集上,给群集挂载外部Nutanix的数据卷。

登录UCP主页,点击Resource,点击Volumes,点击 【Create Volume】,输入相关参数,如下图所示。图中的sizeMb=500000这个参数是制定VolumeGroup的大小,不设定这个参数的话,默认是10GB。

未分类

在到Nutanix的Prism里面查看这个Volume Group是否存在。应该如下图所示:

未分类

部署OwnCloud网盘服务

登录UCP主页,点击 Service , 点击 【Create a Service】按钮;开始建立这个服务。输入服务名,镜像名;点击 【Next】按钮。

未分类

点击 【Next】按钮。进入 Resource页面,这里需要配置端口和数据卷。

未分类

未分类

最后点击【Deploy Now】按钮。 部署完毕之后,显示这个服务的状态为正常。

未分类

未分类

点击这个服务,到这个页面的最下方,找到右下角的发布端口的链接,点击后,就可以看到ownCloud的初始化配置页面了。

未分类

输入管理员的用户名和密码,进入之后,上传一些图片,测试一下功能是否正常。

未分类

尝试一些Docker Datacenter的高级功能,如服务的高可用性;同时Nutanix的DVP在底层保障了数据的持久性和完全性。测试步骤如下:

  • 找到运行owncloud的容器,删除这个容器
  • 在服务页面查看owncloud服务的状态变化
  • 等ownCloud的状态恢复正常之后
  • 再次登录ownCloud页面
  • 查看和确认刚才上传的文件是否还在

总结

Nutanix是一种融合和了计算、存储和虚拟化(内置KVM)的超融合平台。Nutanix DVP (Docker Volume Plug-in)可以让平台里的容器用上持久化存储服务。DVP不仅可以给单独虚拟机里的容器提供持久卷服务,还能给类似于Docker Swarm的其它容器编排平台提供持久化数据服务功能。我后续的文章还会分享路测试Kubernetes等其它平台。

使用virtualenv搭建Python下的Flask开发环境,ubu测试有效

Flask 依赖两个外部库:Werkzeug 和 Jinja2 。不过使用virtualenv就可以搞定这一切。

下面重点介绍一下环境搭建的步骤:

如果你在 Mac OS X 或 Linux 下,下面两条命令可能会适用:

$ sudo easy_install virtualenv

或更好的:

$ sudo pip install virtualenv

上述的命令会在你的系统中安装 virtualenv。它甚至可能会存在于包管理器中, 如果你用的是 Ubuntu,可以尝试:

$ sudo apt-get install python-virtualenv

virtualenv 安装完毕后,执行:

$ virtualenv venv
New python executable in venv/bin/python
Installing distribute............done.

现在,无论何时你想在某个项目上工作,只需要激活相应的环境。在 OS X 和 Linux 上,执行如下操作:

$ . venv/bin/activate

下面的操作适用 Windows:

$ venvscriptsactivate

无论通过哪种方式,你现在应该已经激活了 virtualenv(注意你的 shell 提示符显示的是当前活动的环境)。

现在你只需要键入以下的命令来激活 virtualenv 中的 Flask:

$ pip install Flask

几秒钟后,一切都搞定了。

然后deactivate退出当前环境。

sqlalchemy多表查询

user_modules.py

from datetime import datetime
from sqlalchemy import Column,Integer,String,Boolean,DateTime,ForeignKey
from sqlalchemy.orm import  relationship
from .connect import Base,session

class User(Base):
    __tablename__='user'
    id=Column(Integer,primary_key=True,autoincrement=True)
    username=Column(String(20),nullable=False)
    passwd=Column(String(50),nullable=False)
    createtime=Column(DateTime,default=datetime.now)
    _locked=Column(Boolean,default=False,nullable=False)
    #在modules中写好查询条件,使用时直接调用
    @classmethod
    def all(cls):
        return session.query(cls).all()
    @classmethod
    def by_name(cls,username):
        return session.query(cls).filter_by(username=username).all()
    @property
    def locked(self):
        return self._locked

    def __repr__(self):
        return '<User(id=%s,username=%s,passwd=%s,createtime=%s,_locked=%s)>'%(
          self.id,
          self.username,
          self.passwd,
          self.createtime,
          self._locked
        )
class UserDetails(Base):
     __tablename__='user_details'
     id=Column(Integer,primary_key=True,autoincrement=True)
     id_card=Column(Integer,nullable=True,unique=True)
     last_login=Column(DateTime)
     login_num=Column(Integer,default=0)
     user_id=Column(Integer,ForeignKey('user.id'))
#bakcref建立反向索引,
     userdetails_for_foreignkey=relationship('User',backref='details',uselist=False,cascade='all')
     def __self__(self):
         return '<UserDetails(id=%s,id_card=%s,last_login=%s,login_num=%s,user_id=%s)>'%(
             self.id,
             self.id_card,
             self.last_login,
             self.login_num,
             self.user_id
         )
if __name__=='__main__':
    Base.metadata.create_all()

query.py

from data.user_modules import User,session,UserDetails
#带条件查询
raw=session.query(User).filter_by(username='nanian').all()
raw=session.query(User).filter_by(username='nanian') #去掉.all()原生sql
raw=session.query(User).filter(User.username  =='nanian').all()
raw=session.query(User.username).filter(User.username  !='nanian').all()
raw=session.query(User.username).filter(User.username  !='nanian').first()
raw=session.query(User.username).filter(User.username  !='nanian').one() #如果前面查出的是多条数据则报错
print(session.query(User).get(2)) #根据主键查,会自己找主键
print(raw)

#限制查询结果数
print(session.query(User).filter(User.username!='nanian').limit(3).all())#前三行
print(session.query(User).filter(User.username!='nanian').offset(3).all())#第三行以后
print(session.query(User).filter(User.username!='nanian').slice(1,3).all())#2,3行


#排序
from sqlalchemy import desc
raw=session.query(User).filter(User.username  !='nanian').order_by(User.username).all()
raw=session.query(User).filter(User.username  !='nanian').order_by(desc(User.username).all()#逆序

#模糊查询 尽量少用模糊查询,效率低
from sqlalchemy import or_
raw=session.query(User).filter(User.username!='nanian').all()
raw=session.query(User).filter(User.username.like('n%').all()
raw=session.query(User).filter(User.username.notlike('n%').all()
raw=session.query(User).filter(User.username.in_(['nanian','a']).all()) #加下划线表示和python关键字作区分
raw=session.query(User).filter(User.username.isnot(None),User.passwd=='123').all()) #多条件
raw=session.query(User).filter(or_(User.username.isnot(None),User.passwd=='123')).all()) #或

raw=session.query(User).filter(User.username==None).all())

#聚合函数
from sqlalchemy import func,extract
print(session.query(User.passwd,func.count(User.id)).group_by(User.passwd).all())
print(session.query(User.passwd,func.count(User.id)).group_by(User.passwd).
      having(func.count(User.id)>1) all())
print( session.query(extract('minute',User.createtime).label('minute'),
                     func.count(User.id)).group_by('minute').all() )  #提取分钟,按分钟分组

#多表查询
raw=session.query(User,UserDetails).all()
raw=session.query(User,UserDetails).filter(UserDetails.id==User.id) all()# cross join
raw=session.query(User.username,UserDetails.last_login).
    join(UserDetails, UserDetails.id==User.id) all()# inner join
raw=session.query(User.username,UserDetails.last_login).
    outerjoin(UserDetails, UserDetails.id==User.id) all()
# outer join代表left join 左连接,右连接将表反过来(sqlalchemy没有rightjoin),小表左连接右表效率高

q1=session.query(User.id)
q2=session.query(UserDetails.id)
raw=q1.union(q2).all()

from sqlalchemy import  all_,any_
sql_0=session.query(UserDetails.last_login).subquery() #声明子表
raw=session.query(User).filter(User.createtime >all_(sql_0)).all()
raw=session.query(User).filter(User.createtime >any_(sql_0)).all()

#原生sql
sql_1='''
select * from `user`
'''
raw=session.execute(sql_1)
#print(raw,dir(raw))
#print(raw.fetchone())
#print(raw.fetchmany())
#print(raw.fetchall())

for i in raw:
    print(i)

Flask 与 Tornado 中的路由定义

Flask 和 Tornado 是我现在最“熟悉”的两个 Python web 框架,各自都具备一些别具一格的特性, 在很多实现上都走了截然不同的道路。它们的路由实现分别代表了 Python web 框架的两大风格:

Flask 使用函数作为 web 请求的 Handler,这一点和早期的 Django 是一致的, 不过 Django 似乎是 1.7 以后推荐使用 class 作为 Handler。和 Django 不同的是, Flask 使用装饰器注册路由,同类型的框架还有 Bottle。

# flaskapp.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

说到 class handler,web.py 就是其中很有名的 一个,而 Tornado 的最初的灵感就是来自 web.py,Tornado 和 web.py 在路由方面都是 使用路由元组做配置。

# tornadoapp.py
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

(上面两段代码分别来自 Flask 和 Tornado 官方的示例教程。)

当然,并不是说 Flask 和 Tornado 就不能使用对方的路由配置模式了, 这里提供两个简单的实现抛砖引玉:

使用路由元组的 Flask

Flask 在路由方面支持惰性加载, 提供了 Flask.add_url_rule() 函数用于手动注册路由:

# flaskapp.py
import flask

class Flask(flask.Flask):
    def add_url_rules(self, url_rules):
        for url_rule in url_rules:
            self.add_url_rule(url_rule[0], view_func=url_rule[1])

def hello():
    return "Hello World!"

app = Flask(__name__)
app.add_url_rules([("/", hello)])

if __name__ == "__main__":
    app.run()

使用装饰器配置路由的 Tornado

上面的 “Hello World” 修改如下:

# tornadoapp.py
import tornado.ioloop
import tornado.web

class Application(tornado.web.Application):
    def route(self, pattern):
        def _(handler):
            handler_pattern = [(pattern, handler)]
            self.add_handlers(".*$", handler_pattern)
            return handler
        return _

app = Application()

@app.route(r"/")
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

if __name__ == "__main__":
    app.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

使用python下的Flask应用

Flask是一个使用 Python 编写的轻量级 Web 应用框架。在学习过程中进行一些总结:

1. 一个最小的Flask应用 flask_test1.py:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()

执行python flask_test1.py即可,在浏览器中使用127.0.0.1:5000即可访问

配置外部可访问,将脚本中的app.run()改写成app.run(host=’0.0.0.0′),如此就可以在外面使用运行脚本的服务器地址访问了,如192.168.2.22:5000

2. Flask的路由 flask_test2.py:

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

if __name__ == '__main__':
    app.run(host='0.0.0.0')

运行脚本后,使用127.0.0.1:5000/projects 和127.0.0.1:5000/about即可得到不同的消息处理方式

也可以使用通配的方式书写路由,如下

@app.route('/user/<username>')
def show_user_profile(username):
    return 'User %s' % username

运行脚本后,使用127.0.0.1:5000/user/projects 和127.0.0.1:5000/user/about都会进入到show_user_profile()这个函数里面进行处理

3. Flask的Http方法:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

运行脚本后可以使用get和post方法分别向login这个路由下发送消息。