派大星的博客

很多事情不是会了才能做,而是做了才能学会


  • 首页

  • 标签

  • 分类

  • 关于

  • 搜索

Java使用Jwt加密信息生成token

发表于 2019-05-21 | 分类于 Java,java工具类,SpringBoot技能大全

1. 添加依赖

1
2
3
4
5
6
<!-- jwt加密工具类 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

2. java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import io.jsonwebtoken.*;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
* @author pibigstar
* @create 2018-12-13 15:32
* @desc token工具类
**/
public class TokenUtil {

private static final String SECRET = "rgsnsm#ldyh*ws%l&hdpmnmw@xyhndes";//私密key
private static final Long TTL_EXPIRATION = 1000L * 60 * 30; //过期时间30分钟
private static final String ISSUER = "pibigstar";//发行人

/**
* 加密信息,生成token
*/
public static String creatToken(Map<String,Object> params) {
SignatureAlgorithm signature = SignatureAlgorithm.HS256;

byte[] secretBytes = DatatypeConverter.parseBase64Binary(SECRET);
Key secretKey = new SecretKeySpec(secretBytes, signature.getJcaName());
Long expiration = System.currentTimeMillis() + TTL_EXPIRATION;

JwtBuilder builder = Jwts.builder()
.setIssuedAt(new Date())
.setExpiration(new Date(expiration))
.setIssuer(ISSUER)
.setClaims(params)
.signWith(signature,secretKey);

return builder.compact();
}

/**
* 解析token
*/
public static Map<String, Object> parseToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))
.parseClaimsJws(token).getBody();
} catch (SignatureException | MalformedJwtException e){
System.out.println("token解析失败");
} catch (ExpiredJwtException e) {
System.out.println("token已过期");
}
return claims;
}
}

3. 测试

1
2
3
4
5
6
7
8
public static void main(String[] args){
Map<String,Object> map = new HashMap<>();
map.put("pibigstar","测试");
String token = creatToken(map);
System.out.println("token:" + token);
Map<String, Object> result = parseToken(token);
System.out.println(result.get("pibigstar"));
}

数据库批量备份与还原

发表于 2019-05-18 | 分类于 mysql

备份某个数据库

备份test数据库

1
mysqldump -uroot -p123456 -P3306 test -t > test.sql

备份此链接下所有数据库

使用 -A 参数,表示备份所有数据库(结构和数据)

1
mysqldump -uroot -p123456 -A > all.sql

备份所有数据库(仅结构)

加入-d 参数表示只备份结构

1
mysqldump -uroot -p123456 -P3306 -A -d > all.sql

还原数据库

先登录进数据库中

1
mysql  -uroot -p123456

然后执行

1
source all.sql

使用NetData对服务器性能进行监控

发表于 2019-05-18 | 分类于 Linux

@[TOC]
服务器为:CentOS

1. 安装NetData

1.安装Netdata需要的基本编译环境安装:

1
yum install zlib-devel gcc make git autoconf autogen automake pkgconfig
  1. 下载NetData

    1
    wget http://firehol.org/download/netdata/releases/v1.0.0/netdata-1.0.0.tar.gz
  2. 解压

    1
    tar -xf netdata-1.0.0.tar.gz
  3. 进入到netdata

    1
    cd netdata-1.0.0
  4. 执行安装

    1
    ./netdata-installer.sh

2. 查看界面

浏览器打开:http://你的IP:19999

3. 更改配置

1
vim /etc/netdata/netdata.conf

4. 启动与关闭

  1. 启动netdata服务

    1
    /usr/sbin/netdata
  2. 关闭netdata服务

    1
    killall netdata

使用python做爬虫总结

发表于 2019-05-10 | 分类于 爬虫相关,pyhton

[TOC]

1 最基本的抓站

1
2
import urllib2 
content = urllib2.urlopen('http://XXXX').read()

2 使用代理服务器

这在某些情况下比较有用,比如IP被封了,或者比如IP访问的次数受到限制等等。

1
2
3
4
5
6
7
8
9
import urllib2 

proxy_support = urllib2.ProxyHandler({'http':'http://XX.XX.XX.XX:XXXX'})

opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)

urllib2.install_opener(opener)

content = urllib2.urlopen('http://XXXX').read()

3 需要登录的情况

登录的情况比较麻烦我把问题拆分一下:

3.1 cookie的处理

1
2
3
4
5
import urllib2, cookielib 
cookie_support= urllib2.HTTPCookieProcessor(cookielib.CookieJar())
opener = urllib2.build_opener(cookie_support, urllib2.HTTPHandler)
urllib2.install_opener(opener)
content = urllib2.urlopen('http://XXXX').read()

是的没错,如果想同时用代理和cookie,那就加入proxy_support然后operner改为

1
opener = urllib2.build_opener(proxy_support, cookie_support, urllib2.HTTPHandler)

3.2 表单的处理

登录必要填表,表单怎么填?首先利用工具截取所要填表的内容。
比如我一般用firefox+httpfox插件来看看自己到底发送了些什么包
这个我就举个例子好了,以verycd为例,先找到自己发的POST请求,以及POST表单项:

可以看到verycd的话需要填username,password,continueURI,fk,login_submit这几项,其中fk是随机生成的(其实不太随机,看上去像是把epoch时间经过简单的编码生成的),需要从网页获取,也就是说得先访问一次网页,用正则表达式等工具截取返回数据中的fk项。continueURI顾名思义可以随便写,login_submit是固定的,这从源码可以看出。还有username,password那就很显然了。
好的,有了要填写的数据,我们就要生成postdata

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import urllib 
postdata=urllib.urlencode({
'username':'XXXXX',
'password':'XXXXX',
'continueURI':'http://www.verycd.com/',
'fk':fk,
'login_submit':'登录'
})
# 然后生成http请求,再发送请求:
req = urllib2.Request(
url = 'http://secure.verycd.com/signin/*/http://www.verycd.com/',
data = postdata
)

result = urllib2.urlopen(req).read()

3.3 伪装成浏览器访问

某些网站反感爬虫的到访,于是对爬虫一律拒绝请求。这时候我们需要伪装成浏览器,这可以通过修改http包中的header来实现:

1
2
3
4
5
6
7
8
headers = { 
'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
}
req = urllib2.Request(
url = 'http://secure.verycd.com/signin/*/http://www.verycd.com/',
data = postdata,
headers = headers
)

3.4 反 ”反盗链”

某些站点有所谓的反盗链设置,其实说穿了很简单,就是检查你发送请求的header里面,referer站点是不是他自己,所以我们只需要像3.3一样,把headers的referer改成该网站即可,以黑幕著称地cnbeta为例:

1
2
3
headers = { 
'Referer':'http://www.cnbeta.com/articles'
}

headers是一个dict数据结构,你可以放入任何想要的header,来做一些伪装。例如,有些自作聪明的网站总喜欢窥人隐私,别人通过代理访问,他偏偏要读取header中的X-Forwarded-For来看看人家的真实IP,没话说,那就直接把X-Forwarde-For改了吧,可以改成随便什么好玩的东东来欺负欺负他,呵呵。

3.5 终极绝招

有时候即使做了3.1-3.4,访问还是会被据,那么没办法,老老实实把httpfox中看到的headers全都写上,那一般也就行了。 再不行,那就只能用终极绝招了,selenium直接控制浏览器来进行访问,只要浏览器可以做到的,那么它也可以做到。类似的还有pamie,watir,等等等等。

4 多线程并发抓取

单线程太慢的话,就需要多线程了,这里给个简单的线程池模板 这个程序只是简单地打印了1-10,但是可以看出是并发地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from threading import Thread 
from Queue import Queue
from time import sleep

#q是任务队列
q = Queue()
#NUM是并发线程总数
NUM = 2
#JOBS是有多少任务
JOBS = 10
#具体的处理函数,负责处理单个任务
def do_somthing_using(arguments):
print arguments
#这个是工作进程,负责不断从队列取数据并处理
def working():
while True:
arguments = q.get()
do_somthing_using(arguments)
sleep(1)
q.task_done()
#fork NUM个线程等待队列
for i in range(NUM):
t = Thread(target=working)
t.setDaemon(True)
t.start()
#把JOBS排入队列
for i in range(JOBS):
q.put(i)
#等待所有JOBS完成
q.join()

5 验证码的处理

碰到验证码咋办?这里分两种情况处理:

  • google那种验证码,凉拌
  • 简单的验证码:字符个数有限,只使用了简单的平移或旋转加噪音而没有扭曲的,这种还是有可能可以处理的,一般思路是旋转的转回来,噪音去掉,然后划分单个字符,划分好了以后再通过特征提取的方法(例如PCA)降维并生成特征库,然后把验证码和特征库进行比较。这个比较复杂,一篇博文是说不完的,这里就不展开了,具体做法请弄本相关教科书好好研究一下。
  • 事实上有些验证码还是很弱的,这里就不点名了,反正我通过2的方法提取过准确度非常高的验证码,所以2事实上是可行的。

    6 gzip/deflate支持

    现在的网页普遍支持gzip压缩,这往往可以解决大量传输时间,以VeryCD的主页为例,未压缩版本247K,压缩了以后45K,为原来的1/5。这就意味着抓取速度会快5倍。
    然而python的urllib/urllib2默认都不支持压缩,要返回压缩格式,必须在request的header里面写明’accept-encoding’,然后读取response后更要检查header查看是否有’content-encoding’一项来判断是否需要解码,很繁琐琐碎。如何让urllib2自动支持gzip, defalte呢?
    其实可以继承BaseHanlder类,然后build_opener的方式来处理:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    import urllib2 

    from gzip import GzipFile
    from StringIO import StringIO

    class ContentEncodingProcessor(urllib2.BaseHandler):

    # add headers to requests
    def http_request(self, req):
    req.add_header("Accept-Encoding", "gzip, deflate")
    return req

    # decode
    def http_response(self, req, resp):
    old_resp = resp
    # gzip
    if resp.headers.get("content-encoding") == "gzip":
    gz = GzipFile(
    fileobj=StringIO(resp.read()),
    mode="r"
    )
    resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
    resp.msg = old_resp.msg
    # deflate
    if resp.headers.get("content-encoding") == "deflate":
    gz = StringIO( deflate(resp.read()) )
    resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
    resp.msg = old_resp.msg
    return resp


    import zlib

    def deflate(data):

    try:
    return zlib.decompress(data, -zlib.MAX_WBITS)
    except zlib.error:
    return zlib.decompress(data)
    # 然后就简单了,
    encoding_support = ContentEncodingProcessor
    opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )

    #直接用opener打开网页,如果服务器支持gzip/defalte则自动解压缩
    content = opener.open(url).read()

7 更方便地多线程

总结一文的确提及了一个简单的多线程模板,但是那个东东真正应用到程序里面去只会让程序变得支离破碎,不堪入目。在怎么更方便地进行多线程方面我也动了一番脑筋。先想想怎么进行多线程调用最方便呢?

7.1 用twisted进行异步I/O抓取

事实上更高1效的抓取并非一定要用多线程,也可以使用异步I/O法:直接用twisted的getPage方法,然后分别加上异步I/O结束时的callback和errback方法即可。例如可以这么干:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from twisted.web.client import getPage 
from twisted.internet import reactor

links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]

def parse_page(data,url):

print len(data),url

def fetch_error(error,url):
print error.getErrorMessage(),url

# 批量抓取链接

for url in links:
getPage(url,timeout=5)
.addCallback(parse_page,url) #成功则调用parse_page方法
.addErrback(fetch_error,url) #失败则调用fetch_error方法

reactor.callLater(5, reactor.stop) #5秒钟后通知reactor结束程序

reactor.run()

twisted人如其名,写的代码实在是太扭曲了,非正常人所能接受,虽然这个简单的例子看上去还好;每次写twisted的程序整个人都扭曲了,累得不得了,文档等于没有,必须得看源码才知道怎么整,唉不提了。
如果要支持gzip/deflate,甚至做一些登陆的扩展,就得为twisted写个新的HTTPClientFactory类诸如此类,我这眉头真是大皱,遂放弃。有毅力者请自行尝试。
这篇讲怎么用twisted来进行批量网址处理的文章不错,由浅入深,深入浅出,可以一看。

7.2 设计一个简单的多线程抓取类

还是觉得在urllib之类python“本土”的东东里面折腾起来更舒服。试想一下,如果有个Fetcher类,你可以这么调用

1
2
3
4
5
6
7
8
f = Fetcher(threads=10) #设定下载线程数为10 

for url in urls:
f.push(url) #把所有url推入下载队列

while f.taskleft(): #若还有未完成下载的线程
content = f.pop() #从下载完成队列中取出结果
do_with(content) # 处理content内容

这么个多线程调用简单明了,那么就这么设计吧,首先要有两个队列,用Queue搞定,多线程的基本架构也和“技巧总结”一文类似,push方法和pop方法都比较好处理,都是直接用Queue的方法,taskleft则是如果有“正在运行的任务”或者”队列中的任务”则为是,也好办,于是代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import urllib2 
from threading import Thread,Lock
from Queue import Queue
import time

class Fetcher:
def __init__(self,threads):
self.opener = urllib2.build_opener(urllib2.HTTPHandler)
self.lock = Lock() #线程锁
self.q_req = Queue() #任务队列
self.q_ans = Queue() #完成队列
self.threads = threads
for i in range(threads):
t = Thread(target=self.threadget)
t.setDaemon(True)
t.start()
self.running = 0

def __del__(self): #解构时需等待两个队列完成
time.sleep(0.5)
self.q_req.join()
self.q_ans.join()

def taskleft(self):
return self.q_req.qsize()+self.q_ans.qsize()+self.running

def push(self,req):
self.q_req.put(req)

def pop(self):
return self.q_ans.get()

def threadget(self):
while True:
req = self.q_req.get()
with self.lock: #要保证该操作的原子性,进入critical area
self.running += 1
try:
ans = self.opener.open(req).read()
except Exception, what:
ans = ''
print what
self.q_ans.put((req,ans))
with self.lock:
self.running -= 1
self.q_req.task_done()
time.sleep(0.1) # don't spam

if __name__ == "__main__":
links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]
f = Fetcher(threads=10)
for url in links:
f.push(url)
while f.taskleft():
url,content = f.pop()
print url,len(content)

8 一些琐碎的经验

8.1 连接池:

opener.open和urllib2.urlopen一样,都会新建一个http请求。通常情况下这不是什么问题,因为线性环境下,一秒钟可能也就新生成一个请求;然而在多线程环境下,每秒钟可以是几十上百个请求,这么干只要几分钟,正常的有理智的服务器一定会封禁你的。
然而在正常的html请求时,保持同时和服务器几十个连接又是很正常的一件事,所以完全可以手动维护一个HttpConnection的池,然后每次抓取时从连接池里面选连接进行连接即可。
这里有一个取巧的方法,就是利用squid做代理服务器来进行抓取,则squid会自动为你维护连接池,还附带数据缓存功能,而且squid本来就是我每个服务器上面必装的东东,何必再自找麻烦写连接池呢。

8.2 设定线程的栈大小

栈大小的设定将非常显著地影响python的内存占用,python多线程不设置这个值会导致程序占用大量内存,这对openvz的vps来说非常致命。stack_size必须大于32768,实际上应该总要32768*2以上

1
2
3
from threading import stack_size 

stack_size(32768*16)

8.3 设置失败后自动重试

1
2
3
4
5
6
7
8
9
10
11
12
def get(self,req,retries=3): 
try:
response = self.opener.open(req)
data = response.read()
except Exception , what:
print what,req
if retries&gt;0:
return self.get(req,retries-1)
else:
print 'GET Failed',req
return ''
return data

8.4 设置超时

1
2
3
import socket 

socket.setdefaulttimeout(10) #设置10秒后连接超时

8.5 登陆

登陆更加简化了,首先build_opener中要加入cookie支持,参考“总结”一文;如要登陆VeryCD,给Fetcher新增一个空方法login,并在init()中调用,然后继承Fetcher类并override login方法:

1
2
3
4
5
6
7
8
9
def login(self,username,password): 
import urllib
data=urllib.urlencode({'username':username,
'password':password,
'continue':'http://www.verycd.com/',
'login_submit':u'登录'.encode('utf-8'),
'save_cookie':1,})
url = 'http://www.verycd.com/signin'
self.opener.open(url,data).read()

于是在Fetcher初始化时便会自动登录VeryCD网站。

Google搜索技巧

发表于 2019-05-10 | 分类于 工具使用
  • inurl:搜索URL网址中包含指定字符串

    1
    inurl:login.php
  • intitle:标题中包含指定的字符串

    1
    intitle:登录
  • intext: 内容中包含指定的字符串

    1
    intext:登录
  • site:指定网站,只会在指定网站中查找关键字

    1
    深入理解JVM虚拟机 site:baidu.com
  • filetype:指定文件类型

    1
    深入理解JVM虚拟机 filetype:pdf

Linux远程连接Mysql配置

发表于 2019-05-05 | 分类于 数据库,Linux

Mysql默认root用户只能本地访问,不能远程连接管理mysql数据库,Linux如何开启mysql远程连接?设置步骤如下:

创建远程链接授权用户

1
2
# 进入mysql操作命令
mysql -h localhost -u root -p

增加pibigstar用户授权访问,密码pibigstar

1
GRANT ALL PRIVILEGES ON *.* TO pibigstar@localhost IDENTIFIED BY 'pibigstar' WITH GRANT OPTION;

授与pibigstar用户从任何其它主机发起的访问(通配符%)

1
GRANT ALL PRIVILEGES ON *.* TO pibigstar@"%" IDENTIFIED BY 'pibigstar' WITH GRANT OPTION;

上面的执性完成之后,基本上就可以通过用户名 pibigstar 密码 pibigstar 访问了,如果还是不行,查看下面方式来解决

设置防火墙允许3306端口

1
vi /etc/sysconfig/IPtables

添加

1
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 3306 -j ACCEPT

注意:
添加在-A RH-Firewall-1-INPUT -j REJECT –reject-with icmp-host-prohibited之前,否则可能导致规则不生效

重启防火墙

1
service iptables restart

远程访问mysql速度很慢的解决方法

修改/etc/my.cnf或my.ini
[mysqld]下添加

1
2
skip-name-resolve
skip-grant-tables

docker高级使用

发表于 2019-05-05 | 分类于 docker

PS: 你可以把镜像理解为一个Java类,而容器则是这个类创建的一个实例。

查询镜像

1
2
# 搜索跟centos相关的镜像
docker search centos

拉取镜像

1
docker pull centos

启动容器

1
docker run -dit -name 容器名 镜像名 /bin/bash

端口映射

1
2
 #本地端口:容器端口 
docker run -dit -p 8080:8080 --name 容器名 镜像名 /bin/bash

进入容器

1
docker exec -it 容器名 /bin/bash

查看容器日志

1
docker logs -f 容器名

查看容器的IP地址

1
docker inspect --format='{{.NetworkSettings.IPAddress}}' 容器名

推送镜像

1
2
# username为你docker账号的用户名
docker push username/ubuntu:17.10

查看容器信息

1
docker inspect 容器名

删除全部容器

1
docker stop $(docker ps -q) & docker rm $(docker ps -aq)

删除镜像

1
docker rmi 镜像id

创建一个docker网络

1
docker network create -d bridge my-net

运行一个容器并连接到新建的网络

1
docker run -it --name 容器名 --network my-net 镜像名 /bin/bash

挂载本地目录

1
2
# 将当前目录,挂载到容器的/app/demo目录中 
docker run -it -d --name 容器名 -v ./:/app/demo 镜像名 /bin/bash

保存镜像

1
docker save alpine | gzip > alpine-latest.tar.gz

加载保存的镜像

1
docker load -i alpine-latest.tar.gz

导出容器

1
docker export 7691a814370e > ubuntu.tar

从容器快照中导入为镜像

1
cat ubuntu.tar | docker import - test/ubuntu:v1.0

清理所有处于终止状态的容器

1
docker prune

以生活的例子来解释编程中的名词

发表于 2019-05-02 | 分类于 Java

死锁

死锁就是两个或两个以上的线程争夺同一资源而形成的一种僵持状态,这种状态在没有外力的作用下将一直持续下去

例子:比如馍夹菜这道菜,需要馒头和菜,A线程抢到了馒头,而B线程抢到了菜,这时A需要B的菜,而B需要A的馒头,他们都不愿放弃手中的资源而一直僵持下去,这种就叫做死锁

活锁

活锁简单来说就是同时释放,又同时拿,还是上面那个例子,A放弃了馒头,B也放弃了菜,可他们又同时拿了资源,又会造成僵持

例子: 在路上走路的时候,迎面来了一个人,这时你想给他让路,所以你向左边移动,而他也想给你让路,所以他也向左边移动,此时你俩又是无法通过,又彼此让路,又无法通过。。。。就这样一直持续下去,这种就是活锁。

IOC

IOC(Inversion of Control) 控制反转,你要明白是什么被反转了,是对象的获取过程被反转了,之前是我们手动去new一个对象,不需要的时候在销毁对象,手动去管理对象之间的依赖关系,而现在是将对象托管给容器管理。。它有两种实现方式,一种是DI(Dependency Injection)依赖注入,也是最常见的方式,另一种是DL(Dependency Lookup)依赖查找

例子:你去餐馆吃饭,服务员只需要把菜单给你,你点菜就可以了,而这里菜就是对象,你不需要知道这个菜是怎们做的,材料和菜谱什么的你都不需要关心,这种方式就是IOC,你无需关心

HTTPS

https是一种加密的网络传输协议,它不同于http,http是无状态的,并且信息是可篡改的。客户端在第一次是通过非对称加密拿到秘钥,客户端访问https服务器时会发送公钥,https服务器通过CA颁发的证书利用公钥对信息(秘钥)进行加密,客户端可以利用私钥拿到这个信息(秘钥),以后通信就可以用这个秘钥进行对称加密

例子: http 就好比飞鸽传输,飞鸽把信息直接绑在腿上,任何人都可以截获这个飞鸽并且把信息更换掉,可见这种方式是极其不安全的,那怎么办了?我们可以把信息加密不就好了,比如字母每个都+1,这样 abc就变成了 bcd ,服务端在收到这个信息之后再把字母-1 就又变成了abc,可这个 +1 的信息怎么通知服务端呢,如果别人拿到了这个信息不是就可以轻易破解我们的信息并篡改他们了吗,这时就需要用到了非对称加密,通过公钥对信息加密,再通过私钥进行加密。而 https 就好比 客户端在飞鸽上绑了一个开着锁的盒子发给服务端,服务端将 加密秘钥 (+1信息)放到盒子里并锁上锁发给客户端,这样客户端就可以通过钥匙打开锁,并拿到这个秘钥,以后都通过这个秘钥对信息加密。在这里CA证书就充当了盒子的身份。

以生活的例子说明单线程和多线程

发表于 2019-04-27 | 分类于 Java

以生活例子说明单线程与多线程

转载自:书圈

1. 程序设计的目标

在我看来单从程序的角度来看,一个好的程序的目标应该是性能与用户体验的平衡。当然一个程序是否能够满足用户的需求暂且不谈,这是业务层面的问题,我们仅仅讨论程序本身。围绕两点来展开,性能与用户体验。

性能:高性能的程序应该可以等同于CPU的利用率,CPU的利用率越高(一直在工作,没有闲下来的时候),程序的性能越高。
体验:这里的体验不只是界面多么漂亮,功能多么顺手,这里的体验指程序的响应速度,响应速度越快,用户体验越好。

下面我们就这两点进行各种模型的讨论。

2. 单线程多任务无阻塞

以生活中食堂打饭的场景作为比喻,假设有这样的场景,小A,小B,小C 在窗口依次排队打饭。 假设窗口负责打饭的阿姨打一个菜需要耗时1秒。如果小A需要2个菜,小B需要3个菜,小C需要2个菜。如下:

阿姨(CPU):打一个菜需要1秒
小A:2个菜
小B:3个菜
小C:2个菜

那么在这种模型下将所有服Wu做完阿姨需要耗时 2 + 3 + 2 = 7秒
阿姨 = CPU
小A,小B,小C = 任务(这里是以任务为概念,表示需要做一些事情)
这种模型下CPU是满负荷不间断运转的,没有空闲,用户体验还不错。这种程序中每个任务的耗时都比较小,是非常理想的状态,一般情况下基本不太可能存在。

3. 单线程多任务IO阻塞

将上面的场景稍微做改动:
阿姨:打一个菜需要1秒
小A:2个菜,但是忘记带钱了,要找同学送过来,估计需要等5分钟可以送到(可以理解为磁盘IO)
小B:3个菜
小C:2个菜

这种情况下小A这里发生了阻塞,实际上小A这里耗费了5分钟也就是 300秒+ 2个菜的时间,也就是302秒,而CPU则空闲了300秒,实际上工作2秒。

所有服Wu做完花费 302 + 3 + 2 = 307秒 CPU实际工作7秒,等待300秒。 极大浪费了CPU的时钟周期。 用户体验很差,因为小A阻塞的时候,后面的所有人都等着,而实际上此时CPU空闲。所以单线程中不要有阻塞出现。

4. 单线程多任务异步IO

还是上面的模型,加入一个角色:值日生小哥,他负责事先询问每一个人是否带钱了,如果带钱了则允许打菜,否则把钱准备好了再说。

<1> 值日生小哥问小A准备好打菜了吗,小A说忘带钱了,值日生小哥说,你把钱准备好了再说,小A开始准备(需要300秒,从此刻开始记时)。
<2> 值日生小哥问小B准备好打菜了吗,小B说可以了,阿姨服务小B,耗时3秒
<3> 值日生小哥问小C准备好打菜了吗,小C说可以了,阿姨服务小C,耗时2秒
<4> 值日生小哥问小A准备好了没有,小A说还要等一会,阿姨由于没有人过来服务,处于空闲状态
<5> 300秒之后,小A准备好了,阿姨服务小A,耗时2秒

整个过程做完耗时 300 + 2 = 302秒 CPU工作7秒,空闲295秒

值日生小哥相当于select模型中的select功能,负责轮询任务是否可以工作,如果可以则直接工作,否则继续轮询。在小A阻塞的300秒里面,阿姨(CPU)没有傻等,而是在服务后面的人,也就是小B和小C,所以这里与模型3不同的是,这里有5秒CPU是工作的。 如果打饭的人越多,这种模型CPU的利用率越高,例如如果有小D,小E,小F…… 等需要服务,CPU可以在小A阻塞的300秒期间内继续服务其他人。实际上值日生小哥轮询也会耗时,这个耗时是很少的,几乎可以忽略不计,但是如果任务非常多,这个轮询还是会影响性能的,但是epoll模型已经不使用轮询的方式,相当于A,B,C会主动跟值日生小哥报告,说我准备好了,可以直接打菜了。

这种模式下用户体验好,CPU利用率高(任务越多利用率越高)

5. 单线程多任务,有耗时计算

回到最开始的模型,如下:
阿姨:打一个菜需要1秒
小A:200个菜
小B:3个菜
小C:2个菜

顺序做完所有任务,需要耗时 200 + 3 + 2 = 205秒, CPU无空闲,但是用户体验却不是很好,因为显然后面的 B,C 需要等待小A 200秒的时间,这种情况下是没有IO阻塞的,但是任务A本身太耗CPU了,所以说如果单线程◇◇现了耗时的操作,一定会影响体验(IO操作或者是耗时的计算都属于耗时的操作,都会导致阻塞,但是这两种导致阻塞的性质是不一样的)。在所有的单线程模型中都不允许出现阻塞的情况,如果出现,那么用户体验是极差的,例如在UI编程中(QT,C# Winform)是不允许在UI线程中做耗时的操作的,否则会导致UI界面无响应。 编写Nodejs程序的时候,我们所写的代码实际上是在一个线程中执行的,所以也不允许有阻塞的操作(当然整个Nodejs框架实现异步,一定不止一个线程)。

出现阻塞的情况一般有2种,一种是IO阻塞,例如典型的如磁盘操作,这种情况下的阻塞会导致CPU空闲等待(当然现代操作系统中如果IO阻塞,操作系统一定会将导致IO阻塞的线程挂起)。这种阻塞的情况,可以通过异步IO的方法避免,这样就避免程序中仅有的单线程被操作系统挂起。另一种情况下是确实有非常多的计算操作,例如一个复杂的加密算法,确实需要消耗非常多的CPU时间,这种情况下CPU并不是空闲的,反而是全负荷工作的。这种CPU密集的工作不适合放在单线程中,虽然CPU的利用率很高,但是用户体验并不是很好。这种情况下使用多线程反而会更好,例如如果3个任务,每个任务都在一个线程中,也就是有3个线程,A任务在ThreadA中,B任务在ThreadB中,C任务在ThreadC中,那么即使A任务的计算量比较大,B,C两个任务所在的线程也不必等待A任务完成之后再工作,他们也有机会得到调度,这是由操作系统来完成的。这样就不会因为某一个任务计算量大,而导致阻塞其他任务而影响体验了。

6. 多线程程序

我们将上面的模型改造成多线程的模型是怎样的呢,我们在模型5的基础上添加一个角色,管理员大叔(操作系统的角色):
阿姨:打一个菜需要1秒
小A:200个菜
小B:3个菜
小C:2个菜

加入管理员大叔之后变成这样的了,小A打两个菜之后,大叔说,你打的菜太多了,不能因为你要打200个菜,让后面的同学都没有机会打菜,你打两个菜之后等一会,让后面的同学也有机会。

大叔让小B打两个菜,然后让小C打两个菜(小C完成),然后再让小A打两个菜(完成之后小A总共就有4个菜了),再让小B打1个菜(此时小B总共打3个菜,完成),然后小A打剩下的196个菜。

CPU的利用率:很高,阿姨在不断的工作

用户体验:不错,即使小A要打200个菜,小B,小C也有机会。 当然如果小A说我是帮校长打菜,要快一点(线程优先级高),那也只能先把小A服务完

总耗时: 200 + 3 + 2 + (大叔指挥安排所消耗的时间,包括从小C切换回小A的时候,大叔要知道小A上次打的菜是哪两个,这次应该接着打什么菜,这相当于线程上下文切换的开销以及线程环境的保存与恢复),所以并不是线程越多越好,线程非常多的时候大叔估计会焦头烂额吧,要记住这么状态,切换来切换去也耗时间。

这种模型下实际上是将小A的耗时任务,分成多份去执行而不是集中执行,所以小A要完成他的任务,可能需要更多的时间(期间他也需要等别人,阿姨不会一直为他一个人服务,但是阿姨为他服务的时间是没有变化的),这种其实有点以时间换取用户体验(小B和小C的体验,小A的体验可能就不会那么好了,但是小A本来也非常耗时,所以多等一会是不是也没关系)

那么IO阻塞和CPU计算耗时阻塞这两者有什么区别呢? 区别在于IO阻塞是不使用CPU的,而CPU计算耗时导致的阻塞是会使用CPU的。 例如上面的例子中,小A说忘记带钱了需要同学送钱,于是小A等着同学送钱过来,这个过程中阿姨并没有为小A提供服务,这个过程中为小A提供服务的是他的同学(送钱过来),实际上小A的同学相当于现代计算机系统中的DMA(直接内存操作),小A同学送钱的过程相当于DMA从磁盘读取数据到内存的过程,这个过程基本不需要CPU干预。

当然在DMA技术还没有出现的年代,从磁盘读取文件也是需要CPU发送指令去读取的,也就是说需要CPU的计算,应用到这里的场景中,就是阿姨亲自跑一趟帮小A把钱拿过来。

7. 多CPU

多CPU是一个更加复杂的问题,多CPU如何调度? 小A在第一个窗口打两个菜,又跑到第二个窗口打两个菜这种情况如何处理。小A在第一个窗口,小B在第二个窗口他们要同一个菜,但是这个菜只够一个人,那么两个窗口阿姨如何分配这种需求(实际上应该是由操作系统也就是管理员大叔来决定如何分配,也就是多核下的线程同步与互斥)?

多核CPU情况下,多线程的调度,互斥,锁与同步相对来讲更加复杂,多核情况下是真正的并行,同一时刻有多个线程在同时运行,他们的竞争怎么处理,多个CPU之间如何同步(多CPU之间的缓存状态一致性)等等一系列的问题。

8. 多线程与多进程

上面描述的多线程实际上是讨论的是多线程的调度问题,这里我们说一说多线程与多进程与资源的分配问题。什么意思呢,一群人(多个线程)在一个桌子(进程)上吃饭,他们会涉及到一些问题,比如多个人可能会夹一个菜(竞争),A和B同时看到盘子里面有一块肉,同时伸出筷子去夹,A先夹走,B迟了一点伸到盘子的时候已经没了,只能缩回来(临界资源,互斥),有一个点心需要用馍夹肉一起吃。A夹了肉,B夹了馍,A需要B的馍,B需要A的肉,他们僵持不下谁都不让步(死锁)。

多线程之间的资源共享是非常方便的,因为他们共用进程的资源空间(在一个桌子上),但是需要注意一系列的问题,竞争,死锁,同步等。如果在旁边再开一个桌子(进程)。 那么桌子之间讲话,递东西又不方便(进程间通信),而开一个桌子的开销比在一个桌子上多加一个人的开销要大。另外一个桌子上的人数不可能无限制增加,桌子的容量有限也坐不下这么多人(进程的线程句柄是有限制的)。一个桌子坏了不会影响到另一个桌子上面人的就餐情况(进程间相互DuLi,一个进程崩溃不会影响另一个),而一个桌子上的某人喝挂了需要送医院,估计这一桌人都要散了(线程挂掉会导致整个进程也挂掉)。所以多线程与多进程是各有优缺点,不能一概而论。

说明:多线程桌子的比喻受到知乎用户[pansz]的启发,但是该比喻似乎说明不了线程同步的情况。

9. 总结

单线程程序:适合IO异步,不能阻塞,不能有大量耗CPU的计算。典型如Nodejs,还有一些网络程序

Go语言入门(5)安装第三方库

发表于 2019-04-24 | 分类于 go

通过go get 去获取github上的第三方库是没什么问题的,但是要获取google的就不行了,没办法,
所以我们通过gopm这个工具来获取Google的第三方库

1. 安装gopm

1
go get github.com/gpmgo/gopm

2. 通过gopm安装

我们安装一个go 的 import 工具

1
gopm get -g -v golang.org/x/tools/cmd/goimports

-g 参数是将包下载到 GOPATH 路径下
-v 参数是显示下载过程

3. 安装下载的库

1
go install golang.org/x/tools/cmd/goimports

这个命令可以生成一个可执行文件到GOPATH/bin 路径下

1…789…14
派大星

派大星

137 日志
64 分类
230 标签
GitHub CSDN
© 2019 派大星