立一个flag

想起双十一低价买入的阿里云,一直想搭建一个自己的网站。在这2019年的最后一个周末,终于把这个事情提上提上了议程。
至于网站的内容,我还没有想太多,前期就是搭建一个博客吧,有一个自己对外的窗口,也支持各路朋友注册使用。
前端用react 后端暂时用python3吧。
这个过程中应该会踩坑不少,特别是自己不熟悉的后端和运维相关的知识点。
这恰好是个很好的实践机会,应该可以输出一系列的填坑文章。

pre coding之创建git项目

由于家里的网络对github不够友好,考虑到之前对coding的印象还不错,就在coding上创建了一个项目。
说句题外话,coding应该是被腾讯收了,有了金主爸爸,对普通用户也放开了诸多权限。
coding毫无疑问也是支持ssh协议来访问git仓库的,这样就不用每次连接都输入账号和密码。一劳永逸的事情,我最喜欢
假设当前系统登陆用户是codinggirl,要生成该用户的rsa公钥和私钥, 按如下操作进行。这个操作,之后也会用到。

ssh-keygen -t rsa -b 4096 -C your_email@example.com
# 然后一路回车 
# 最后默认情况下,会在~/.ssh/ 这个目录下出现两个文件 私钥:id_rsa  公钥:id_rsa.pub

id_rsa.pub的内容复制到coding新增加公钥页面中的表单中,保存后,就可以在的自己的电脑上用codinggirl用户和coding进行愉快地进行代码交流了。 考虑到未来会持续迭代这个网站,所以考虑到了发布更新的问题。我不能每次都手动去发布,行业里也有很多成熟的解决方案,比如我接下来要说的jenkins 关于什么是jenkins,我就不多说了。用过的人,应该很清楚。

pre coding之jenkins安装配置

之前都是在使用jenkins,没有安装配置过。今日在这个jenkins上也折腾了半天。。-_-
先说安装,当然是怎么简单,怎么来, 不过要先安装jdksudo apt install openjdk

  1. 简单安装 a. sudo apt install jenkins
    b. 按照提示操作,比如输入 /var/lib/jenkins/secret.key 的内容,安装常规插件
    c. 安装coding插件
    安装过程有点慢,可以在这个过程中去做别的事情
  2. 配置 a. add credentials 把当前用户(codinggirl)的ssh private_key 添加到jenkins 中,这样jenkins就可以正常从coding拉取代码到 /var/lib/jenkins/workspace/ 下面
    b. 生成jenkins用户的ssh public_key, 并添加到远程服务器authorized_keys下
    ⅰ. sudo su jenkins
    ⅱ. cd ~ # /var/lib/jenkins
    ⅲ. ssh-keygen 一路回车
    ⅳ. ssh-copy-id -i .ssh/id_rsa.pub www@blog或者直接复制id_rsa.pub的内容到服务器www用户的authorized_keys中 这样jenkins执行shell命令如rsync,就可以同步数据到远程服务器
    c. 创建一个Freestyle的project,项目名不要随便改动
    d. Source Code Management 选择git, 然后填入项目的仓库url,如git@git.coding.net:xxxx/xxx.git, Credentials就选择步骤a创建的credentials
    e. 上面这步成功后,就可以选择分支了,一般会选者默认的master分支, */master
    f. build Execute Shell rsync -rv --exclude=.git/ --exclude=.git* $WORKSPACE www@blog:~

猜过的坑

  1. build后执行的shell命令,在命令行可以正常执行,但是jenkins在构建后,提示
    Host key verification failed.
    rsync: connection unexpectedly closed (0 bytes received so far) [sender]
    

    原因:jenkins在构建时,执行rsync命令的用户是jenkins,不是codinggirl。所以需要给jenkins这个用户创建rsa的公钥和私钥,并将公钥加入到阿里云服务器的authorized_keys中

这样我就只要每次push代码,然后在jenkins中构建,就可以把代码同步到服务器了。当然前端和后端我会分为两个项目,到时候在分享前后端部署的问题。

周六爬了梧桐山,山顶依旧有”演唱会”, 主唱依旧蒙着脸,带着墨镜,与两年前不同,不再是一个人,而是一个乐队—-纵声乐队。唱完了一首歌,主唱说,他坚持来山顶唱歌,是想通过这种方式锻炼自己….,后来,他开始卖自己的演唱会的票,好像只要20块。希望他们实现自己的音乐梦想,走向更大的舞台。

最近打算下定决心学习使用erlang, 不过我有点不习惯在erlang shell和vim之间来回切换。
想了想,干脆优化一下这个重复的过程吧。就像我配置了python的执行快捷键一样,每次只要按下F6, 就会执行当前编辑的python脚本,并新开一个terminal,输出程序正常打印的信息或错误信息

编写Hello world 脚本

-module(hello).
-export([say/0, main/0, hi/1]).


hi(Name) ->
    io:format("hi ~s~n",Name).

say() ->
    io:format("hello world~n").

main()->
    io:format("main~n"),
    say().

Erlang执行脚本的方法

  1. erlang shell中执行 erl 进入erlang shell
    $ erl
    Erlang/OTP 22 [erts-10.5] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
    Eshell V10.5 (abort with ^G)
    1> c(hello).
    {ok,hello}
    2> hello:say().
    hello world
    ok

  2. 在shell中执行 $ erlc hello.erl
    $ erl -noshell -s hello say -s init stop erlc从命令行和启动了erlang编译器,编译器编译了hello.erl
    并生成一个名为hello.beam的目标代码文件 erl -noshell … 加载了hello模块并执行了hello:say()函数,最后,执行init:stop(),终止了erlang会话

这两种都比较麻烦,有没有更简单一点的方法呢?作为一个偷懒的程序猿,我肯定不喜欢这样重复的

我的解决方法

  1. /usr/local/bin/下创建一个可执行的bash脚本,假设名称是exec_erl,内容如下
#!/bin/bash

erl_file_name=$1
shift
fun=$1
shift
if [ -z "$fun" ]; then
    fun=main
fi
erl_mode="${erl_file_name%.*}"
erlc ${erl_file_name}
erl -noshell -s $erl_mode $fun $@ -s init stop

  1. 修改vim配置文件
augroup filetype_erlang
    autocmd!
    autocmd Filetype erlang nnoremap <buffer> <F6> :w<CR>:ter   exec_erl %  <CR>
augroup END

小结

在编写一个elrang脚本后,想快速验证,只需要再多写一个main函数并把他export出来,然后再main中调用你的测试方法, 按下F6就可以运行了。

可以愉快地debug脚本了. 其他需要编译的语言,也可以用这个类似的方法解决,不过,应该都比erlang的简单一些

还有一个好处,如果想验证某个方法,可以直接在执行exec_erl filename.erl functionname param1 param2
例如,我想执行hello.erl中say()
exec_erl hello.erl say
如果想执行hi
exec_erl hello.erl hi zhangsan

今天想了解一下,如何用python连接到redis。当然,可以用redis-py这个现成的包,只需简单的pip install redis就可以很愉快地用操作redis了 比如:

>>> import redis
>>> r = redis.Redis(host='localhost', port=6379, db=0)
>>> r.set('foo', 'bar')
True
>>> r.get('foo')
'bar'

然而,作为一个喜欢折腾的程序员 我肯定不满足使用工具,我想了解一下这是如何实现的。 翻了一下redis-py的源码,其实就是socket,于是就想造个简单的轮子—直接通过socket连接到redis 顺代了解一下socket编成的基本知识和redis-py的源码

socket的历史

套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯或IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。

基于文件类型的套接字家族 - 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族 - 套接字家族的名字:AF_INET (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

套接字把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的

简直是一把利器(书架上的《Unix网络编程》卷一:套接字联网API在召唤我)

socket基础使用方法

服务端

import socket
import sys

# 创建 socket 对象
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

# 获取本地主机名
host = socket.gethostname()
port = 9999
# 绑定端口号
serversocket.bind((host, port))
# 设置最大连接数,超过后排队
serversocket.listen(5)
while True:
    # 建立客户端连接
    clientsocket,addr = serversocket.accept()      
    print("连接地址: %s" % str(addr))
    msg='欢迎关注匿名程序媛!'+ "\r\n"
    clientsocket.send(msg.encode('utf-8'))
    clientsocket.close()

客户端

import socket
import sys
# 创建 socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
# 获取本地主机名
host = socket.gethostname() 
# 设置端口号
port = 9999
# 连接服务,指定主机和端口
s.connect((host, port))
# 接收小于 1024 字节的数据
msg = s.recv(1024)
s.close()
print (msg.decode('utf-8'))

服务端套接字函数

s.bind() #绑定(主机,端口号)到套接字
s.listen() #开始TCP监听
s.accept() #被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数

s.connect() #主动初始化TCP服务器连接
s.connect_ex() #connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数(客户端和服务端都能使用)

s.recv() #接收TCP数据
s.send() #发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() #发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() #接收UDP数据
s.sendto() #发送UDP数据
s.getpeername() #连接到当前套接字的远端的地址
s.getsockname() #当前套接字的地址
s.getsockopt() #返回指定套接字的参数
s.setsockopt() #设置指定套接字的参数
s.close() #关闭套接字

所以?如何连接到redis呢?

my-socket-connect-to-redis

#!/usr/bin/python3

import socket
'''
python3 连接redis
'''

host = '127.0.0.1'
port = 6379
buf_size = 1
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect((host, port))

cmd = 'SELECT 2\n'.encode('utf-8')
r = conn.sendall(cmd)


cmd = 'PING\n'.encode('utf-8')
conn.sendall(cmd)
while True:
    res = conn.recv(buf_size)
    print(res)
    if not res:
        break
conn.close()

执行一下python3 ./redis_client.py,就可以得到如下返回。

b'+'
b'O'
b'K'
b'\r'
b'\n'
b'+'
b'P'
b'O'
b'N'
b'G'
b'\r'
b'\n'

我们连接到第二个数据库,然后ping了一下,看到redis返回了PONG ^_^

作为读写性能出众的K-V类型数据库—Redis几乎在现在所有的项目中都会使用。常常用它来共享Session,缓存数据,或者是用它来实现一个简单的锁。 昨天简单记录了一下redis的单机版安装。之后再考虑学习一下reids集群的搭建。今天再写点简单的,算是一个笔记。

数据类型

redis作为key-value的非关系型数据库,我们常用的5种数据类型(string,list,set,orderd set,hash table),都是指的是value的数据类型。 当然还有其他类型的value,比如bitmap,hyperloglog,geo,stream。后面这些我平时用的少。回头再研究研究。

对redis来说,所有的key其实都是一个字符串,实际项目中我们常常会在key的写法上做一些约束,比如规定前缀,规定单词之间的分割符号

set name zhangsan    
get name # 返回zhangsan
hset student:zhangsan  math  59     # 学生张三的考试成绩就记录在 student:zhangsan 这个hash表中。 
hset student:zhangsan  music  61     # 
hget student:zhangsan  math  # 返回 59
hgetall student:zhangsan   # 返回 math  59  music 61
hdel student:zhangsan math  # 删除数学成绩

lpush  mylist 11   # mylist就是一个列表的名称,从左边添加一个元素到列表mylist中
lpush  mylist 12   # 
lpop  mylist  # 从列表mylist的左侧弹出第一个元素,返回12

sadd  myset  1  # 往集合中添加一个元素
redis可以设置key的缓存时间,默认单位是秒。redis可以给整个hash表设置缓存时间,但是不能给hash表中的某个key单独设置缓存时间
关于redis缓存过期的处理,这也可以单独记录一篇文章了。先挖个坑,以后再填吧

expire mykey 12 # 缓存12秒 ttl mykey # 可以查看剩余缓存时间


### 其他命令

https://redis.io/commands ```

Lua

据说现在不会Lua都不好意思称自己会redis.这也是一个大坑,我以后再填吧

今天分享一个简单的知识点,僵尸进程—zobmie 当然,网上有了很多这类的知识讲解,本文就算是自己的一个笔记吧。

什么是僵尸进程?

当子进程在父进程之前终止,子进程不会完全消失,否则父进程在最终检查子进程是否终止时是无法获取它的终止状态的。 内核为每一个终止的子进程保存了一些信息,所以当父进程调用wait或waitpid时, 可以得到这些信息。 这些信息至少有进程ID、该进程的终止状态、该进程使用的CPU时间总量

在UNIX中,一个已经终止,但是父进程尚未对其进行处理(获取子进程的有关信息、释放它们占用的资源)的进程被称为僵尸进程zobmie

当然,还有一种情况,当父进程先中终止了,它的子进程们会怎样呢?答案是,这些子进程的父进程会变为init进程(进程ID为1)。当这些子进程再终止时,init进程会调用一个wait获取其终止状态,从而不会产生僵尸进程

如何发现僵尸进程?

几个方法:
1.top命令, `zombie` 前面的数字就是僵尸进程的数量
```
top - 23:48:32 up 14 days,  1:53,  6 users,  load average: 0.28, 0.34, 0.42
Tasks: 304 total,   1 running, 254 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.2 us,  0.7 sy,  0.0 ni, 98.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  3729440 total,   187164 free,  2335108 used,  1207168 buff/cache
KiB Swap:  2097148 total,   948220 free,  1148928 used.   730932 avail Mem

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
31730 mm        20   0  931404 241468 108284 S   3.3  6.5   1:38.39 chrome
2364 mm        20   0 3959488 237336  70132 S   2.0  6.4 131:42.83 gnome-shell
1422 gdm       20   0 3488680  48252  19776 S   0.7  1.3   5:45.24 gnome-shell
```
2. `ps -ef | grep defunct`

如何手动产生一个僵尸进程?

如下所示,子进程终止后,父进程并未wait子进程,而是直接sleep了。这个时候,就会出现zombia进程了。

from multiprocessing import Process
import time, os

def run():
    print("son",os.getpid())


if __name__ == '__main__':
    p = Process(target=run)
    p.start()

    print("father",os.getpid())
    time.sleep(1000)

输出如下:

father, 5607
son, 5608

此时top命令上会出现1 zombie, ps命令如下

UID        PID  PPID  C STIME TTY          TIME CMD
mm       5608 5607  0 00:03 pts/2    00:00:00 [python] <defunct>

如何干掉一个僵尸进程?

一般情况下,kill -9 pid是不能干掉僵尸进程。要找到僵尸进程的父进程,分析产生僵尸进程的原因,从根本上杜绝僵尸进程的原因. 可以通过kill掉僵尸进程的父进程,来阻止其他僵尸进程产生.

查看一下这个僵尸进程的详细信息

cat /proc/5608/status 当然可用这个命令查看其他进程的信息.只要替换一下进程id就好了

Name: python3 State: Z (zombie) Tgid: 5608 Ngid: 0 Pid: 5608 PPid: 5607 TracerPid: 0 Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000 FDSize: 0 Groups: 4 24 27 30 46 116 126 1000 NStgid: 5608 NSpid: 5608 NSpgid: 5607 NSsid: 11104 Threads: 1 SigQ: 0/14395 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000001001000 SigCgt: 0000000180000002 CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 0000003fffffffff CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: thread vulnerable Cpus_allowed: ff Cpus_allowed_list: 0-7 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 1 nonvoluntary_ctxt_switches: 0

部分字段解释

VmPeak: 代表当前进程运行过程中占用内存的峰值.
VmSize: 代表进程现在正在占用的内存
VmLck: 代表进程已经锁住的物理内存的大小.锁住的物理内存不能交换到硬盘.
VmHWM: 是程序得到分配到物理内存的峰值.
VmRSS: 是程序现在使用的物理内存.
VmData: 表示进程数据段的大小.
VmStk: 表示进程堆栈段的大小.
VmExe: 表示进程代码的大小.
VmLib: 表示进程所使用LIB库的大小.
VmPTE: 占用的页表的大小.
VmSwap: 进程占用Swap的大小.
Threads: 表示当前进程组的线程数量.
SigPnd: 屏蔽位,存储了该线程的待处理信号,等同于线程的PENDING信号.
ShnPnd: 屏蔽位,存储了该线程组的待处理信号.等同于进程组的PENDING信号.
SigBlk: 存放被阻塞的信号,等同于BLOCKED信号.
SigIgn: 存放被忽略的信号,等同于IGNORED信号.
SigCgt: 存放捕获的信号,等同于CAUGHT信号.
CapEff: 当一个进程要进行某个特权操作时,操作系统会检查cap_effective的对应位是否有效,而不再是检查进程的有效UID是否为0.
CapPrm: 表示进程能够使用的能力,在cap_permitted中可以包含cap_effective中没有的能力,这些能力是被进程自己临时放弃的,也可以说cap_effective是cap_permitted的一个子集.
CapInh: 表示能够被当前进程执行的程序继承的能力.
CapBnd: 是系统的边界能力,我们无法改变它.
Cpus_allowed: 指出该进程可以使用CPU的亲和性掩码,如果是两块CPU,所以这里就是3,如果该进程指定为4个CPU(如果有话),这里就是F(1111).
Cpus_allowed_list: 0-7指出该进程可以使用CPU的列表,这里是0-7.
voluntary_ctxt_switches: 表示进程主动切换的次数.
nonvoluntary_ctxt_switches: 表示进程被动切换的次数.