Perf_events简称perf是 Linux 系统原生提供的性能分析工具,会返回 CPU 正在执行的函数名以及调用栈(stack)。通常,它的执行频率是 99Hz(每秒99次),如果99次都返回同一个函数名,那就说明 CPU 这一秒钟都在执行同一个函数,可能存在性能问题。
perf 在 Linux 的工具包中,安装
apt install linux-tools
由于 perf 的分析往往会结合火焰图,需要补充安装 perf_data_converter ,这个能够将 perf 生成的文件进行转化,转为火焰图工具能够识别的格式,具体可参考:https://github.com/google/perf_data_converter,这边以 Debian 为例
方法1:源安装 bazel
apt install apt-transport-https curl gnupgcurl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpgsudo mv bazel.gpg /etc/apt/trusted.gpg.d/echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.listsudo apt update && sudo apt install bazelbazel --version
方法2:二进制包安装 bazel
wget https://github.com/bazelbuild/bazel/releases/download/5.1.1/bazel-5.1.1-linux-x86_64chmod +x bazel-<version>-installer-linux-x86_64.sh./bazel-<version>-installer-linux-x86_64.sh --user
git clone https://github.com/google/perf_data_converter.gitcd perf_data_converterbazel build src:perf_to_profile
编译完之后将 perf_to_profile 文件移动到 /usr/local/bin
目录下
项目地址: https://github.com/jlfwong/speedscope
服务部署可以通过 nginx 代理的方式,采用 docker-compose 完成
docker-compose.yaml
version: '3.1'services: custom-nginx-proxy: image: nginx:1.10.1 volumes: - ${STATIC_FILE_PATH}:/usr/share/nginx/html:ro - ${NGINX_CONFIG_PATH}:/etc/nginx/conf.d/default.conf:ro ports: - ${PROXY_PORT:-8080}:80networks: default: driver: bridge driver_opts: com.docker.network.driver.mtu: 1400
.env
STATIC_FILE_PATH=/home/nginx/file_storeNGINX_CONFIG_PATH=/home/nginx/default.confPROXY_PORT=80
default.conf
server { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/nginx/log/host.access.log main; location / { root /usr/share/nginx/html; index index.html index.htm; autoindex on; autoindex_exact_size on; autoindex_localtime on; charset utf-8; } #error_page 404 /404.html;}
将 speedscope 的资源文件下载到目录 /home/nginx/file_store
目录下
cd /home/nginx/file_storewget https://github.com/jlfwong/speedscope/releases/download/v1.13.0/speedscope-1.13.0.zipunzip speedscope-1.13.0.zip
接来下就可以通过部署服务的机器访问:http://localhost/speedscope
项目地址:https://github.com/brendangregg/FlameGraph
直接下载即可:https://github.com/brendangregg/FlameGraph/releases
工具已就绪,直接按照需要执行下面的命令
例如要排查一个服务进程的资源使用情况,假定进程名称为:go-proxy
则先获取进程的 ID
TARGET_PID=$(ps -ef | grep go-proxy | grep -v grep | awk -F ' ' '{print $2}')
使用 perf 获取对应的性能指标记录
perf record -F 99 -p ${TARGET_PID} -g -- sleep 60
上面的命令中,perf record 表示记录,-F 99表示每秒 99 次,-p xxx 是进程号,即对哪个进程进行分析,也可以对线程进行分析,-g表示记录调用栈,sleep 60 则是持续 60 秒。运行后会产生一个庞大的文本文件 perf.data
为了能够利用火焰图进行观察,需要进行下列两个步骤获得符合火焰图格式的 profile 文件
perf script -i perf.data &> perf.unfold
FlameGraph-1.0/stackcollapse-perf.pl perf.unfold &> perf.folded
利用上面下载的 FlameGraph 工具导出火焰图
FlameGraph-1.0/flamegraph.pl perf.folded > perf.svg
例如:
同时也可以将 perf.folded
文件放到 speedscope 中分析
perf.folded
文件放置于 /home/nginx/file_store
目录下上面的格式只要遵循:#profileURL=[URL-encoded profile URL]&title=[URL-encoded custom title]
即可,不过要注意 profileUrl 需要是可跨域的,这边示例是放在同一个域名下,不会有跨域问题
火焰图最直观的就是每隔一个方法调用在整个 CPU 采样中的占比,移动鼠标到对应的方法栈上面能够显示 CPU 使用的占比,进而分析哪一个方法的资源耗费最大。
]]>127.0.0.1:6379> INFO commandstats# Commandstatscmdstat_get:calls=78,usec=608,usec_per_call=7.79cmdstat_setex:calls=5,usec=71,usec_per_call=14.20cmdstat_keys:calls=2,usec=42,usec_per_call=21.00cmdstat_info:calls=10,usec=1931,usec_per_call=193.10
]]>传送门:《50 Tips and Tricks for MongoDB Developers》
规范化架构
{ "_id" : productId, "name" : name, "price" : price, "desc" : description}{ "_id" : orderId, "user" : userInfo, "items" : [ productId1, productId2, productId3 ]}
非规范化架构
{ "_id" : productId, "name" : name, "price" : price, "desc" : description}{ "_id" : orderId, "user" : userInfo, "items" : [ { "_id" : productId1, "name" : name1, "price" : price1 }, { "_id" : productId2, "name" : name2, "price" : price2 }, { "_id" : productId3, "name" : name3, "price" : price3 } ]}
数据示例
{ "_id": ObjectId('xxx'), "threadId": 123, "data": ISODate("2022-04-13")}
查询语句
db.posts.find({"threadId" : id}).sort({"date" : 1}).limit(20)
业务经常需要这种数据分页式查询,则可以建立索引
db.posts.createIndex({'threadId' : 1, 'date' : 1}, {'background': true})
例如某一条记录是用于记录一天内固定的6个小时的访问情况
{ "_id" : pageId, "start" : time, "visits" : { "minutes" : [ [num0, num1, ..., num59], [num0, num1, ..., num59], [num0, num1, ..., num59], [num0, num1, ..., num59], [num0, num1, ..., num59], [num0, num1, ..., num59] ], "hours" : [num0, ..., num5] }}
则对于那些仍未发生的内容,可以用缺省值先填充
{ "_id" : pageId, "start" : someTime, "visits" : { "minutes" : [ [0, 0, ..., 0], [0, 0, ..., 0], [0, 0, ..., 0], [0, 0, ..., 0], [0, 0, ..., 0], [0, 0, ..., 0] ], "hours" : [0, 0, 0, 0, 0, 0] }}
MongoDB不需要为新内容寻找空间,它只是更新已经输入的值,这样会快很多。
例如,在小时开始时,程序可能会执行以下操作:
> db.pages.update({"_id" : pageId, "start" : thisHour}, ... {"$inc" : {"visits.0.0" : 3}})
例:提前把 total 值算好,MongoDB 是很笨重的数据库,对简单的检索效率很高,但是在数据量大的情况下做很复杂的聚合,性能会随着复杂度提升而降低。
> db.food.update(criteria, {"$inc" : {"apples" : 10, "oranges" : -2, "total" : 8}})> db.food.findOne(){ "_id" : 123, "apples" : 20, "oranges" : 3, "total" : 23}
MongoDB提供了以下Read Preference Mode:
在一些业务下,会频繁请求后端并读取 Mongo 数据。对于这种业务切忌增加耗时的操作
def get_method(): # 这边通过 Mongo 获取数据 data = self.mongo_service.get() # 这边有一个耗时的数据处理,例如从别的系统获取数据 self.combine_data_from_http(data) return data
]]>以下主要以 Linux 系统为例,Darwin 类似。
这个服务负责签名,是 Bee 节点的前置依赖之一。
Ubuntu / Debian / Raspbian
执行命令,通过 deb 包安装
wget https://github.com/ethersphere/bee-clef/releases/download/v0.4.9/bee-clef_0.4.9_amd64.debsudo dpkg -i bee-clef_0.4.9_amd64.deb
CentOS
wget https://github.com/ethersphere/bee-clef/releases/download/v0.4.9/bee-clef_0.4.9_amd64.rpmsudo rpm -i bee-clef_0.4.9_amd64.rpm
MacOS
brew tap ethersphere/tapbrew install swarm-clef
运行服务
brew services start swarm-clef
Bee 节点是分布式存储服务的主体服务。
Ubuntu / Debian / Raspbian
执行命令,通过 deb 包安装
wget https://github.com/ethersphere/bee/releases/download/v0.5.3/bee_0.5.3_amd64.debsudo dpkg -i bee_0.5.3_amd64.deb
CentOS
wget https://github.com/ethersphere/bee/releases/download/v0.5.3/bee_0.5.3_amd64.rpmsudo rpm -i bee_0.5.3_amd64.rpm
MacOS
brew tap ethersphere/tapbrew install swarm-bee
运行服务
brew services start swarm-bee
安装好工具之后,可以使用默认的配置运行 bee 节点,但是默认 bee 节点连接的以太坊测试网络的 endpoint 为:https://rpc.slock.it/goerli, 这个入口很容易被堵塞(发送的交易请求过多),进而导致 bee 节点无法正常工作,报错如下:
Error: get chain id: Post "https://rpc.slock.it/goerli": dial tcp 87.117.121.163:443: i/o timeout
为此安装属于个人的 Goerli endpoint,进入:https://infura.io/,创建项目
创建之后选择测试网络 Goerli,记录保存 endpoint 地址:
将地址替换配置文件
vim /etc/bee/bee.yaml...# swap-endpoint: https://rpc.slock.it/goerli 注释默认测试网络swap-endpoint: https://goerli.infura.io/v3/6a542820bd61406e98c3a682312eb9ed...
运行 bee 节点准备存储工作(挖矿)。
]]>Echarts提供了非常强大的可视化功能,在平时的开发过程中难免用到echarts来助力数据展示。
那么在Angularjs中该如何使用echarts呢?下面撸一个例子方便后面参考。
AngularJS中的指令是一种特有的处理DOM节点的方式,它可以操作以及渲染可重用的UI组件。例如想要用AngularJS处理echarts的一个柱状图📊,则可以通过下面的方式操作
angular.module('demo') .directive('myBarChart', function($window) { //定义柱状图指令 return { restrict: 'EA', // 以属性或者tag的形式调用指令 link: function($scope, element, attrs) { //attrs 是DOM元素的属性集合 var myBarChart = echarts.init(element[0]); // element是一个jqlite对象,如果JQuery再AngularJS之前引入,则是一个Jquery对象,可以使用Jquery所有的方法 $scope.$watch(attrs.eData, function(newVal, oldVal, scope) {//监听属性e-data的值,当数据发生改变时执行作为第二个参数的函数 var xData = [], sData = [], data = newVal, totalCount = 0; angular.forEach(data, function(val) { xData.push(val.name); sData.push(val.value); totalCount += val.value; }); var option = { title: { text: '用户访问来源统计', subtext: '总计值:' + totalCount, x: 'center', }, color: ['#333642'], tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: [{ type: 'category', data: xData, axisTick: { alignWithLabel: true } }], yAxis: [{ type: 'value' }], series: [{ name: '直接访问', type: 'bar', barWidth: '60%', data: sData }] }; myBarChart.setOption(option); }, true); $window.onresize = function() { myBarChart.resize(); } } } });
通过指令处理好了组件之后,便可以在之后的开发中使用组件。
JavaScript
var app = angular.module("demo", []); //定义一个模块angular.module("demo").controller("myCtrl", function($scope, $http, $interval) { //定义控制器 $interval(function() { //调用$interval服务执行循环任务,3秒自动更新一次数据 $http .get("/demo_get") //调用$http服务获取数据 .success(function(data) { if (data.status == "0") { $scope.data = data.data; //将数据放在model中 } }) .error(function() { $scope.data[0].value = Math.floor(Math.random() * 2000); //模拟数据改变测试 }); }, 5000); //假设获取到的数据如下: var fakeData = { status: 0, data: [ { value: 335, name: "直接访问" }, { value: 310, name: "邮件营销" }, { value: 234, name: "微信跳转" }, { value: 135, name: "视频广告" }, { value: 748, name: "搜索引擎" } ] }; $scope.data = fakeData.data;});
HTML
<div ng-app="demo" ng-controller="myCtrl" id="demo"> <div style="height:320px;" my-bar-chart e-data="data"></div></div>
整体效果可以参考:https://codepen.io/zhilingsomnus/pen/mdyjqKX?editors=1010
]]>当输入输出源的性能已经达到上限,那么性能瓶颈不在Logstash,应优先对输入输出源的性能进行调优。
1、如果JVM堆大小设置过小会导致GC频繁,从而导致CPU使用率过高 2、快速验证这个问题的方法是double堆大小,看性能是否有提升。注意要给系统至少预留1GB的空间。 3、为了精确查找问题可以使用jmap或VisualVM。[参考](https://www.elastic.co/guide/en/logstash/current/tuning-logstash.html#profiling-the-heap)4、设置Xms和Xmx为相同值,防止堆大小在运行时调整,这个过程非常消耗性能。
4)Logstash worker设置:AngularJS通过指令的新属性来扩展HTML,其内置了许多指令来为应用添加功能,最常见的指令如ng-app
、ng-model
、ng-bind
等等,同时AngularJS还提供了用户自定义指令功能。
通过derective实现自定义focus聚焦
为了能够让input标签具有自动获取焦点的能力,可以通过自定义一个指令来实现。具体的实现如下
var app = angular.module('plunker', []);var MyCtrl = function ($scope, $timeout) {};app.directive('customFocus', function($timeout) { return { scope: { trigger: '=customFocus' }, link: function(scope, element) { scope.$watch('trigger', function(value) { if(value === true) { $timeout(function() { element[0].focus(); scope.trigger = false; }); } }); } };});
之后在html的input标签中可以这么使用
<html ng-app="plunker"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.js"></script> <script src="example.js"></script> <link href="https://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/css/bootstrap-combined.min.css" rel="stylesheet"> </head> <body><div ng-controller="MyCtrl"> <button class="btn" ng-click="showForm=true;focusInput=true">show form and focus input</button> <div ng-show="showForm"> <input type="text" custom-focus="focusInput"> <button class="btn" ng-click="showForm=false">hide form</button> </div></div> </body></html>
]]>bash <(curl -L -s https://install.direct/go.sh)systemctl status v2ray
具体的做法和另一篇文章《搭建v2ray》一样,只是其中的配置文件替换为用websocket,如下
{ "log" : { "access": "/var/log/v2ray/access.log", "error": "/var/log/v2ray/error.log", "loglevel": "warning" }, "inbound": { "port": 9000, "listen": "127.0.0.1", "protocol": "vmess", "settings": { "clients": [ { "id": "8d837310-8120-ca48-748e-830359e454b9", "level": 1, "alterId": 64 } ] }, "streamSettings":{ "network": "ws", "wsSettings": { "path": "/v2ray" } } }, "outbound": { "protocol": "freedom", "settings": {} }, "outboundDetour": [ { "protocol": "blackhole", "settings": {}, "tag": "blocked" } ], "routing": { "strategy": "rules", "settings": { "rules": [ { "type": "field", "ip": [ "0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.2.0/24", "192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "::1/128", "fc00::/7", "fe80::/10" ], "outboundTag": "blocked" } ] } }}
安装
apt install nginx
配置/etc/nginx/conf.d/v2ray.conf
,如下
/etc/nginx/conf.d# cat v2ray.conf server { listen 443; server_name ip.address.of.your.vps; location /v2ray { proxy_redirect off; proxy_pass http://127.0.0.1:9000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; }}
为了允许访问者访问nginx站点,您需要打开端口80和443:
apt install -y firewalldfirewall-cmd --permanent --zone=public --add-service=http firewall-cmd --permanent --zone=public --add-service=httpsfirewall-cmd --reload
启动v2rayx之后会有这样一个图标
点击Configure进入配置
接下来点击transport settings进入配置
配置websocket
配置http/2
配置tls
在开启服务端V2ray和nginx服务后,Google it!
Rsync作为文件同步工具,其在许多场景下都提供了便捷。为了实现文件传输,用户会使用scp
工具,🤠scp
工具是基于ssh协议来设计的,其在安全性上面优势明显,但是如果存在如下场景,scp
无疑是一种比较浪费资源并且比较低效的做法:
当传输的文件经常面临修改或者发生变更,例如代码,使用scp会全量进行覆盖,每一次都会进行所有文件的复制,并且覆盖。
这个时候通常会采用rsync
的方式来同步文件,实现增量式传输文件,这样能够极大提升文件的传输效率。
1. 对比默认参数下, 两种方式消耗的系统资源情况
2. 在服务器端存在对应服务的条件下
3. scp和rsync的具体适用场景
rsync 命令在大部分的Unix或者Linux系统上面都预装了,如果没有安装,则可以通过下面的命令来安装。
在CentOS & RHEL系统上执行
yum install rsync -y
在Debian系操作系统中(Ubuntu & Linux Mint)执行
apt install rsync -y
rsync的命令参数主要包括如下
rsync -a 归档模式,表示以递归方式传输文件,并保持所有属性 -r 对于目录以递归模式处理,主要针对目录,传输的是目录必须加-r -v 打印一些信息出来,比如速率,文件数量等。 -l 保留软连链 -L 向对待常规文件一样处理软链接,如果是src(源机)中有软链接文件,刚加上该选项后会把软连接指向的目标文件拷贝到dst(目标机) -p 保持文件权限 -o 保持文件属主信息 -g 保持文件属组信息 -D 保持 设备文件信息 -t 保持 文件时间信息 --delete 删除那些dst中src没有的文件 --exclude=PATTERN指定排除不需要传输的文件,等号后面跟文件名,可以是万用字符模式(如*.txt) PATTERN路径是相对弄要同步的路径如(rsync -avPz --exclude=zabbix /opt/sh 10.8.64.99::backup/tmp/ #排除的是/opt/sh/zabbix) --progress或-P 在同步的过程中可以看到同步的过程状态,比如统计要同步的文件数量,同步的文件传输速度等等。。。 --bwlimit=10 (限制传输速度) -u 加上这个选项后将会把DST中比SRC还新的文件排除掉,不会覆盖 -z 压缩 传输的过程中会压缩,我们并不会感知。 文件到了目标机器上我们看到的是一样的。 (工作中常用的几个 -a -v --delete --exclude)
使用rsync传输文件有两种模式,一种是通过ssh
隧道来传输,另一种是通过连接服务端的rsync daemon
来传输。
一下举一些例子来说明两种传输模式。
rsync同步ssh隧道方式:#后面的目录是目标地址 例1:rsync -avPz 192.168.183.109:/tmp/1.txt /tmp/ 拉文件:远程到本机 例2:rsync -avPz /tmp/1.txt 192.168.183.109:/tmp/ 推文件:本机到远程 例3:rsync -avPz -e "ssh -p 10022" /tmp/1.txt 192.168.183.109:/tmp/ 推文件:本机到远程,端口不是22的情况rsync同步daemon方式 例1:不需要密码 学ssh免密码登陆 rsync -auvPz --bwlimit=10 (限制传输速度) tmp.txt test@<ip>::test --password-file=~/.rsync.password 例2:查询rsyncd可用模块 (list参数,yes会显示,no不会显示) rsync -list --port 8873 192.168.186.118::
rsync通过daemon的方式启动一个服务端,让客户端连接服务端完成文件传输。daemon的可以分不同模块来处理不同的rsync请求。
为了能够启动一个服务端rsync daemon,需要按照如下例子配置
创建/etc/rsyncd.conf
配置文件,内容如下
port=8873log file=/var/log/rsync.logpid file=/var/run/rsyncd.pidaddress=192.168.0.11 # 本机IP地址[mkdocs]path=/home/banban/mkdocsuse chroot=truemax connections=4read only=nolist=trueuid=banbangid=banbanauth users=chenzhilingsecrets file=/etc/rsyncd.passwd# pre-xfer exec=/home/banban/deploy.shpost-xfer exec=/home/banban/deploy.shhosts allow=192.168.0.1/32
解释一下每一个参数的含义
port:说明启动rsyncd服务的端口号,默认是873。log file:日志文件位置。pid file:服务文件。address:启动rsyncd服务的本机IP地址[]:rsync的模块path:rsync需要同步的目录位置,这里指明为/home/banban/mkdocsuse chroot true|false:是否需要root权限来同步max connections:指定最大的连接数。list:当用户查询该服务器上的可用模块时,是否列出这个模块。uid/gid:banbanauth users:banbansecrets file:指定密码文件,该参数连同上面的参数如果不指定,则不使用密码验证。注意该密码文件的权限一定要是600。hosts allow:表示被允许连接该模块的主机,其中前面两个IP是作业给出的另外两台机器的IP,最后一个是通过在办公网下使用dig -x 反解得到的gitlab的ip
具体关于
rsyncd.conf
的配置可以参考: https://download.samba.org/pub/rsync/rsyncd.conf.html
创建密码文件/etc/rsyncd.passwd
echo "chenzhiling:xxxxx" > /etc/rsyncd.passwdchmod 600 /etc/rsyncd.passwd
注意:这个文件的权限一定要设置为600,文件内容格式[rsync-user:password]
启动服务
rsync --daemon --config=/etc/rsyncd.conf
为了方便rsync的服务管理,可以使用下面这个脚本
#!/bin/bash # this script for start|stop rsync daemon service status1=$(ps -ef | egrep "rsync --daemon" | grep -v 'grep') pidfile="/var/run/rsyncd.pid" start_rsync="rsync --daemon --config=/etc/rsyncd.conf" function rsyncstart() { if [ "${status1}X" == "X" ];then rm -f $pidfile ${start_rsync} status2=$(ps -ef | egrep "rsync --daemon.*rsyncd.conf" | grep -v 'grep') if [ "${status2}X" != "X" ];then echo "rsync service start.......OK" fi else echo "rsync service is running !" fi } function rsyncstop() { if [ "${status1}X" != "X" ];then kill -9 $(cat $pidfile) status2=$(ps -ef | egrep "rsync --daemon" | grep -v 'grep') if [ "${statusw2}X" == "X" ];then echo "rsync service stop.......OK" fi else echo "rsync service is not running !" fi } function rsyncstatus() { if [ "${status1}X" != "X" ];then echo "rsync service is running !" else echo "rsync service is not running !" fi } function rsyncrestart() { if [ "${status1}X" == "X" ];then echo "rsync service is not running..." rsyncstart else rsyncstop rsyncstart fi } case $1 in "start") rsyncstart ;; "stop") rsyncstop ;; "status") rsyncstatus ;; "restart") rsyncrestart ;; *) echo echo "Usage: `basename $0` start|stop|restart|status" echo esac
启动了服务之后,便可以通过客户端传输文件
rsync -avz --port 8873 ./ chenzhiling@<ip>::mkdocs/
]]>autojump
是一个类似于cd
命令的工具,它可以快速定位到目录或者文件,其实现的基本原理是由于autojump
维护了一个目录访问历史表,如果出现目录名同名的情况,autojump
会根据不同目录的访问频率来设置对应的权重,权重高的优先进入。
开源地址:https://github.com/wting/autojump
autojump
的安装主要在Linux和Mac系统下面,目前在Windows下面还没有直接的支持。
安装
apt install -y autojump
在Debian系系统中,需要手动激活autojump
,为了暂时激活 autojump 应用,即直到你关闭当前会话或打开一个新的会话之前让 autojump 均有效,你需要以常规用户身份运行下面的命令:
source /usr/share/autojump/autojump.sh on startup
为了使得 autojump 在 BASH shell 中永久有效,你需要运行下面的命令。
echo '. /usr/share/autojump/autojump.sh' >> ~/.bashrc
关于autojump的文档放在
cat /usr/share/doc/autojump/README.Debian
安装
yum install epel-releaseyum install autojump或dnf install autojump
对于特别的shell例如zsh或者fish,可以使用不同的autojump版本,在zsh下使用 autojump-zsh
,在fish下使用autojump-fish
。
直接通过brew进行安装
brew install autojump
默认安装的位置是
/usr/local/Cellar/autojump/
不同版本都放在这个目录下面。
在安装过程中可能会询问一些配置的步骤,根据提示配置即可。
安装好之后,如果是使用默认的bash,执行
bash /usr/local/Cellar/autojump/22.5.3/share/autojump/autojump.bash
同时将下列配置添加到~/.bash_profile
中
# Set autojump env. /usr/local/Cellar/autojump/22.5.3/share/autojump/autojump.bash
如果是oh-my-zsh
作为用户shell,则配置plugin,添加如下配置内容到~/.zshrc
中
plugins=(... autojump)
其中省略号表示别的plugin。
auto的使用很简单,通过-h可以看到其主要的功能
chenzhiling@banban:~ ➜ j -h usage: autojump [-h] [-a DIRECTORY] [-i [WEIGHT]] [-d [WEIGHT]] [--complete] [--purge] [-s] [-v] [DIRECTORY [DIRECTORY ...]]Automatically jump to directory passed as an argument.positional arguments: DIRECTORY directory to jump tooptional arguments: -h, --help show this help message and exit -a DIRECTORY, --add DIRECTORY add path -i [WEIGHT], --increase [WEIGHT] increase current directory weight -d [WEIGHT], --decrease [WEIGHT] decrease current directory weight --complete used for tab completion --purge remove non-existent paths from database -s, --stat show database entries and their key weights -v, --version show version informationPlease see autojump(1) man pages for full documentation.
autojump
的别名是j
,很简约
chenzhiling@banban:~ ➜ j -s________________________________________0: total key weight0: stored directories0.00: current directory weightdata: /Users/chenzhiling/Library/autojump/autojump.txt
刚安装的时候,autojump没有记录任何信息,因此总权重为0。
在使用了一段时间cd
命令之后,autojump自然而然会记录一些目录访问记录,如下
chenzhiling@banban:~ ➜ j -s...65.6: /Users/chenzhiling/dev/nodejs-workspace67.8: /Users/chenzhiling/dev/nodejs-workspace/devteam/x-ui67.8: /Users/chenzhiling/dev/nodejs-workspace/devteam81.2: /Users/chenzhiling/dev/python-workspace/devteam/x-server________________________________________2806: total weight168: number of entries0.00: current directory weightdata: /Users/chenzhiling/Library/autojump/autojump.txt
这样权重最高的会优先被遍历,例如想要快速跳转到x-server
这个目录下面
j x-server
便可以直接跳转到/Users/chenzhiling/dev/python-workspace/devteam/x-server
这个目录下面,省去了很长的目录输入。
如果autojump
还没有记录的那些目录你想要直接跳转,是不行的,至少需要使用cd
进入到对应的目录一次,才有可能用autojump
,否则会直接跳转到.
。
Enjoy it!
]]>Elasticsearch是一个高可扩展的开源全文检索和分析引擎。它提供了快速实时进行存储、查询和分析海量数据的功能。这个工具通常用于那些需要复杂查询功能和需求的场景,我们将Elasticsearch作为一个底层引擎技术来驱动顶层应用。
以下是一些简单的使用Elasticsearch的用例
Elasticsearch是Elastic Stack(简称ES)的核心分布式检索和分析组件,Logstash和Beats主要用于将数据进行采集、聚合以及处理数据,然后将数据存储到Elasticsearch,Kibana则将处理好的数据进行可视化,并且对数据进行一些操作与监控。而那些索引、检索以及分析的核心功能都是在Elasticsearch中完成的。
Elasticsearch提供了实时检索与分析数据的能力,不管是结构化的数据还是非结构化的数据、数字化数据、地理数据等,Elasticsearch都能够高效地进行存储并建立索引,同时实现高效检索能力。用户可以远远超出简单的检索和聚合信息,以发现数据中的趋势和模式,达到数据分析的目的。随着数据和查询量的增长,Elasticsearch的分布式特性使得部署能够随之无缝增长,实现动态扩展的能力。
在现实业务中,显然不是每一个问题都是搜索问题,但是Elasticsearch提供了处理各种数据的速度和灵活性,以下是一些用例:
在Elasticsearch中有一些核心概念,掌握这些概念有助于对Elasticsearch的理解。
Elasticsearch是一个近实时的搜索平台,也就是说Elasticsearch在对某一个文档建立好index之后到进入可搜索阶段会有一个时延(通常是一秒钟)。
集群通常指的是由一台或者多台服务节点组成的节点,这些节点存储了完整的数据内容,并且对这些数据提供了联合索引,用户可以通过所有的节点进行数据检索。一个集群通常会由一个独一的名称进行标识(默认使用“elasticsearch”),这个名称很重要,一个节点在加入了某一个名称的集群之后,只能属于某一个集群。
所以在一些环境下,要确保针对不同的集群没有使用相同的名称,否则可能就会出现一些节点加入错误的情况。例如,用户可以定义名为logging-dev
、 logging-stage
或者logging-prod
的名字来区分不同的es集群。
需要注意的是,一个集群是完全支持只存在一个节点的情况的。
一个节点就是一个集群的一部分,节点存储了数据,参与到一个集群中提供其索引和搜索的能力。同集群有点类似,一个节点由一个UUID来标识,如果不想使用UUID,用户可以自顶一个名称来标识节点。
一个节点可以通过配置加入到某一个特定的集群中,默认一个节点直接加入到名为”elasticsearch“集群中。假设在一个分布式环境下,有一些es节点启动并且能够相互发现,他们会自组织形成一个名为elasticsearch
的集群,并加入到这个集群中。
在一个集群下,可以容纳任意多的节点。
Index是一系列有相同或者相似特征的文档,例如对于用户数据,可以为其定义一个customer index
,对于商品类目,可以定义另一个index为catagory
,一个index由一个名称来表示,而这个index会在后续的索引、检索、更新以及删除文档等操作的时候使用。
在一个单独的集群中,可以为文档定义任意多的index。
在一个index下面的逻辑分区,能够将不同类型的文档归类到同一个index下面。例如,一个文档类型是users
的类型,另一类的文档类型是blog posts
类型,这两个类型可以放在同一个index的不同分区下面,也就是不同的type下面。
这个概念在后续的es版本中已经弃用了。具体可以参考Removal of mapping types。
在前面有提到文档的概念,一个文档是一个能够被index的最小单元,例如可以为一个单独的顾客定义一个文档,也可以为一个单独的商品定义一个文档。这个文档通常是用JSON来表示。
在一个index或者type下,可以存储任意多的文档,需要注意的是,一个文档虽然物理位置上面来说是在一个index下面的,但是一个文档必须详细分配到index下面的某一个type下。
在一个index下面可能会存储非常大量的文档(数据),这些数据有可能会超出一个节点的硬件限制。例如,在一个单独的index下面有超过1TB的文档,这个可能会超过一个节点的磁盘容量或者可能会导致查询的效率非常的慢。
为了解决这个问题,Elasticsearch将index进行再分割为一个一个shards(分片),当定义了一个index的时候,用户可以指定对应的shards(分片)数,每个分片本身都是一个功能齐全且独立的“索引”,可以托管在集群中的任何节点上。
分片之所以很重要有两个很重要的原因:
在一个复制的分布式环境或者云环境中,不可预期的一些故障是可能出现的,为此强烈建议使用Elasticsearch提出的故障转移机制,以防止某一个节点因为宕机或者其它一些原因不可用而导致数据丢失。Elasticsearch允许对每一个分片都进行副本复制,称为分片副本。
副本很重要的原因主要有两个:
总而言之,每一个index(索引)的都可以划分为多个分片,同时针对每一个索引都可以为其分片进行副本复制,复制之后,每个索引都将具有朱分片和副本分片。在创建index的时候可以为每一个index定义分片和副本的数目,创建索引之后,用户可以动态地更改副本数,但是不能修改分片数目。
默认情况下,Elasticsearch中的每个索引都会有5个主分片和一个副本,这就因为这在集群中至少需要两个节点(副本要和主分片在不同节点),即索引会包含5个主分片和5个副本分片,总计每个索引10个分片。
本人在进行实验的时候Elasticsearch的最新版本是7.3.2
,视情况而定下载对应的镜像,具体镜像在www.docker.elastic.co。
设置镜像的版本号
export ELK_VERSION=7.3.2
用docker方式来安装会比较快捷方便,接下来主要通过docker run
的方式来部署一个ES集群。
下载镜像
docker pull docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION}
使用docker run的方式启动
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -it -d --name es-node docker.elastic.co/elasticsearch/elasticsearch:7.3.2
Kibana是ELK的dashboard,可以通过Kibana实现数据的可视化,对数据进一步分析。
拉取Kibana的镜像
docker pull docker.elastic.co/kibana/kibana:${ELK_VERSION}
启动Kibana服务的方式也有两种,一种是通过docker run的方式,另一种是通过docker-compose的方式。
首先介绍docker run的方式。
docker run --link YOUR_ELASTICSEARCH_CONTAINER_NAME_OR_ID:elasticsearch -p 5601:5601 docker.elastic.co/kibana/kibana:${ELK_VERSION}
这里运行成功的前提是elasticsearch容器启动的时候网络用的是default,否则需要制定对应的网络。
在运行的过程中,由于Kibana服务的启动过程中有很多参数可以设置,为此不建议通过环境变量一个一个写,可以通过挂载的方式将kibana.yaml
的配置文件挂载过去,kibana.yaml
配置文件(7.3版本)的具体配置要求在这个链接
则启动方式添加参数
docker run --link YOUR_ELASTICSEARCH_CONTAINER_NAME_OR_ID:elasticsearch -p 5601:5601 -v ./kibana.yml:/usr/share/kibana/config/kibana.yml docker.elastic.co/kibana/kibana:${ELK_VERSION}
可以通过docker-compose的方式启动,docker-compose的yaml文件如下
elasticsearch.yaml
version: '3'services: es01: image: docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION} container_name: es01 environment: - node.name=es01 - discovery.seed_hosts=es02 - cluster.initial_master_nodes=es01,es02 - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - esdata01:/usr/share/elasticsearch/data ports: - 9200:9200 networks: - esnet es02: image: docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION} container_name: es02 environment: - node.name=es02 - discovery.seed_hosts=es01 - cluster.initial_master_nodes=es01,es02 - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - esdata02:/usr/share/elasticsearch/data networks: - esnetvolumes: esdata01: driver: local esdata02: driver: localnetworks: esnet:
另外将镜像的版本添加到.env
文件中(和elasticsearch.yaml文件同一个目录)
.env
ELK_VERSION=7.3.2
然后直接执行
docker-compose -f elasticsearch.yaml up -d
创建好之后查看docker容器运行情况
# docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES4b8cc899cb3f docker.elastic.co/elasticsearch/elasticsearch:7.3.2 "/usr/local/bin/do..." 6 minutes ago Up 6 minutes 9200/tcp, 9300/tcp es02992ed17bfeb9 docker.elastic.co/elasticsearch/elasticsearch:7.3.2 "/usr/local/bin/do..." 6 minutes ago Up 6 minutes 0.0.0.0:9200->9200/tcp, 9300/tcp es01
访问Elasticsearch的接口
$ curl http://127.0.0.1:9200/_cat/health1568624275 08:57:55 docker-cluster green 2 2 0 0 0 0 0 0 - 100.0%
安装成功
结合之前的elasticsearch容器启动,追加内容得到
elasticsearch.yaml
version: '3'services: es01: image: docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION} container_name: es01 environment: - node.name=es01 - discovery.seed_hosts=es02 - cluster.initial_master_nodes=es01,es02 - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - esdata01:/usr/share/elasticsearch/data ports: - 9200:9200 networks: - esnet es02: image: docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION} container_name: es02 environment: - node.name=es02 - discovery.seed_hosts=es01 - cluster.initial_master_nodes=es01,es02 - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - esdata02:/usr/share/elasticsearch/data networks: - esnet kibana: image: docker.elastic.co/kibana/kibana:${ELK_VERSION} container_name: kibana environment: SERVER_NAME: testing-chenzhiling.loghub.netease.com ELASTICSEARCH_HOSTS: http://es01:9200 ports: - 5601:5601 links: - 'es01:es01' networks: - esnetvolumes: esdata01: driver: local esdata02: driver: localnetworks: esnet:
然后执行
docker-compose -f elacticsearch.yaml up -d
启动成功之后访问 http://127.0.0.1:5601 可以进入Kibana的界面
什么是空洞文件(hole file)?💁♂️在Linux中,lseek的系统调用是可以改变在文件上面的偏移量的,而且还允许其超出文件的长度。偏移量一旦超出了文件的长度,下一次进行文件IO写入操作文件的时候便会延续偏移量的位置继续写入,进而在文件中间产生了空洞的部分,这部分会以”\0”填充,而从原来的文件结尾到新写入数据间的这段空间就被称为“文件空洞”。
在Linux中,EOF(文件结束符)并不是一个字符,而是在读取到文件末尾的时候返回的一个信号值,也就是-1。
文件空洞部分实际上是不会占用任何的物理空间的,直到在某个时刻对空洞部分进行写入文件内容的时候才会为它分配对应的空间。但是在空洞文件形成的时候,逻辑上面的文件大小是分配了空洞部分的大小的。
👯♀️👯♂️👯♀️👯♂️👯♀️👯♂️👯♀️👯♂️
接下来可以通过一个实验来验证空洞文件的形成,并且使用cat和cp两种方式对空洞文件进行操作,看看他们对应的不同效果。
首先使用dd命令产生一个空洞文件,具体可以查看dd的使用方法。
接下来从/dev/urandom
中读取内容,写入到hole.file
文件中,在写入之前,对hole.file
文件设置了999的偏移量,bs设置了4096的大小,同时设置写入的block数量为1,这样将会产生一个逻辑长度为1000数据块的文件。
~$ dd if=/dev/urandom of=hole.file bs=4096 seek=999 count=11+0 records in1+0 records out4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000449329 s, 9.1 MB/s
产生之后,使用ls命令查看文件大小是4.0MB
~$ ls -lh hole.file-rw-r--r-- 1 chenzhiling01 root 4.0M 8月 21 15:12 hole.file
使用du命令查看文件大小是4.0KB
~$ du -h hole.file4.0K hole.file
相差如此悬殊!!🧐
使用od命令来查看hole.file
文件的二进制内容
~$ od -c hole.file0000000 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0...
会看到其实整个hole.file
文件的开始是用许多的”\0”来填充的,由于上面设置了偏移量比较大,可以设置小一点的seek来观察。
接下来使用cat来重定向文件到一个新的文件
~$ cat hole.file > hole.cat
再使用cp命令将文件拷贝一份出来
~$ cp hole.file hole.cp
使用ls命令查看两个新生文件的大小
~$ ls -lh hole.cat hole.cp-rw-r--r-- 1 chenzhiling01 root 4.0M 8月 21 15:39 hole.cat-rw-r--r-- 1 chenzhiling01 root 4.0M 8月 21 15:40 hole.cp
再使用du查看两个新生文件的大小
~$ du -h hole.cat hole.cp4.0M hole.cat4.0K hole.cp
会看到使用cat重定向得到的文件的大小使用ls和du都是4.0MB,而使用cp命令得到的文件大小信息和原来的文件是一样的。
为什么ls和du命令得到的文件大小会相差如此巨大呢?是因为ls获取得到的是文件的逻辑大小,而du获取得到的是文件的实际占用物理块的大小。也就是说,当产生空洞文件的时候,文件系统并不会将空洞文件部分对应也分配好空间,那样是相当浪费的,而且还会被一些黑客利用,在系统中产生大量的空洞文件来耗尽系统的存储资源。
🦁一个小插曲,我发现ls -sh列出来的大小和du出来的大小是一样的,所以可以认为ls -s所获取得到的是文件实际占用的存储块的大小。
因此在文件系统上面观察,对应的文件看上去是和普通文件一样,该多大就多大,但是实际上并不会马上为文件分配对应大小的存储。
而我们使用cat和cp命令得到的文件大小竟然是不一样的,也就是说cp命令和重定向的过程是进行了不一样的操作的。
cat命令重定向内容到新的文件的时候,其遇到了空洞部分,会用0来填充,这样空洞部分其实也就有了内容了,对应需要为他分配物理存储block,这样文件的真是大小其实就是4MB,但是cp命令在遇到空洞部分的时候,会模拟源文件的空洞调用seek,进行偏移之后再进行内容拷贝,这样其实生成的新文件和源文件是一样的,空洞部分都是不被分配真是存储空间的。
空洞文件看上去好像是一个不太靠谱不太安全的操作,但其实在很多情况都很有用:
🤓🤓🤓
我们知道用find命令可以使用一些正则表达式来寻找某一些文件,但是却不能够用来寻找文件内是否包含某一些内容。
grep可以帮我们实现这种效果。
grep的中文全称是“全面搜索正则表达式并把行打印出来”,它能使用正则表达式来搜索文本并且将匹配的行打出来。
首先用grep直接搜索某个文件很简单
$ grep "import" setup.pyimport ioimport refrom setuptools import setup
这种输出是最快捷方便的
如果想再一个目录下搜索所有的包含“import”内容的文件呢?可以使用-r或者-R,当然他们有一点点不一样
首先在一个独立的测试目录下面创建a、b、c、d文件,这写文件的内容分别如下
$ for file in {a..d}; do echo "$file:";cat $file; donea:importb:import testc:import test importimportd:IMPORTimport
现在分别对d文件创建一个硬链接和软链接
$ ln d e$ ln -s d f
于是目录下的文件结构如下
$ ls -alktotal 20drwxr-xr-x 2 chenzhiling01 root 60 8月 15 17:28 .drwxr-xr-x 6 chenzhiling01 chenzhiling01 93 8月 15 17:15 ..-rw-r--r-- 1 chenzhiling01 root 7 8月 15 16:55 a-rw-r--r-- 1 chenzhiling01 root 12 8月 15 17:27 b-rw-r--r-- 1 chenzhiling01 root 26 8月 15 17:28 c-rw-r--r-- 2 chenzhiling01 root 14 8月 15 17:19 d-rw-r--r-- 2 chenzhiling01 root 14 8月 15 17:19 elrwxrwxrwx 1 chenzhiling01 root 1 8月 15 16:57 f -> d
先来看
$ grep -r 'import' ././a:import./b:import test./d:import./e:import./c:import test import./c:import
再使用-R
$ grep -R 'import' ././a:import./b:import test./d:import./e:import./f:import./c:import test import./c:import
会看到-R得到的结果会比-r多一个文件,这个文件就是f,这个文件是软链接到d文件的,也就是说-R会递归寻找,同时不会忽略掉软链接的文件。
如果只想看看对应某一个文件内容出现的具体行,而不像要看到对应的文件名,则可以使用-h,而-H则是输出文件名
$ grep -r -h "import" ./importimport testimportimportimport test importimport
$ grep -r -H "import" ././a:import./b:import test./d:import./e:import./c:import test import./c:import
所以如果想值看到文件名,可以结合管道和cut
$ grep -r -H "import" ./ | cut -d: -f1./a./b./d./e./c./c
在一些情况下可能会想找多个内容,则可以使用egrep
egrep -w -R 'word1|word2' ./
在一些时候可能会出现权限问题或者文件不存在等问题,但是grep默认是不会处理这个错误信息的,可以将错误信息通过重定向或者抑制(-s)的方式
grep -r -H "import" ./ 2>/dev/nullgrep -r -H -s "import" ./
grep很强大,我们可以用来过滤自己想要的信息,很多人最常用的就是ps -aux | grep xxx
,在日常工作中可以尽可能挖掘他的功能。
grep的常用参数
-r – 递归搜索-R – 在每一个目录下面递归搜索,同时会搜索链接的文件。-n – 打印出搜索到的内容在文件中的行-s – 抑制错误的信息,包括文件不存在或者不可读的文件-w – 只根据整个搜索词汇的值来搜索,而不是以正则表达式匹配的形式-i – 忽略大小写去搜索-a - 将 binary 文件以 text 文件的方式搜寻数据-c - 计算找到 '搜寻字符串' 的次数-v - 反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行-E - 使用正则表达式-A - 后面跟数字n,表示显示搜索得到的结果以及前n行-B - 后面跟数字n,表示显示搜索得到的结果以及后n行-C - 后面跟数字n,表示显示搜索得到的结果以及前后n行--color - 对grep出的结果进行颜色区分
]]>GNU Find Utilities是一个在GNU操作系统下基本的文件目录搜索工具集合,这些程式通常用于与其他的一些程式结合一起使用,进而实现模块化的强大的文件目录搜索能力。
Find Utilities工具主要包含了如下:
find工具在一个目录树下去寻找一系列的文件,它会搜寻对应的目录树并且处理得到那些符合用户预设好条件的文件。
locate工具会搜索一个或者多个文件名的数据库,并且将符合条件的文件名打印出来,通常locate命令会结合updatedb命令使用,updatedb会更新整个系统的文件信息,之后locate命令可以定位到对应的文件内容。locate的查询效率是非常高的,只要对应的database更新了,就能快速定位到文件。
updatedb程序就是维护一个文件名数据库,这个数据库会被locate程序使用,这个文件名数据库会维护一系列的目录树组成的文件列表,updatedb命令执行的时候会更新对应的目录树,这个程序可以交给cron来做定时更新,每天跟新以此即可。如果再平时使用的时候想要用最新的数据库,手动执行updatedb即可。
xargs 程序从标准输入流中获取得到一些列内容作为参数,并提供给后续的命令或程序使用,例如一个非常常见的例子就是讲find命令得到的文件名收集在一起作为参数,提供后续的删除操作等等:
find . -type f | xargs ls -lkh
具体的FileUtils功能可以查看Findutils
接下来介绍find命令的使用,同时会根据其提供的参数举例子。
find命令的基础用法是
find [paths] [expression] [actions]
find命令允许用户传入多个paths,其会根据不同的path递归的去在每一个path下面寻找用户想要的文件,知道全部找完。默认find命令会把paths下面的所有paths都走一遍因为为了能够寻找对应满足一定要求的path下面的文件,可以使用正则表达式来表示对应的path。
同时find命令也可以对寻找得到的结果进行一些处理,默认是直接输出找到的所有文件,为了能够根据一定的要求来寻找对应的文件,用户可以使用对应的一些action来过滤或者处理文件。
接下来根据几个例子来加强对find命令的理解
有时候用户想要将某一个目录下面的所有文件和目录都显示出来,find命令默认能实现这个功能,例如将/usr/bin下面的所有文件找出来
$ find /usr/bin//usr/bin//usr/bin/dpkg/usr/bin/dpkg-deb/usr/bin/dpkg-divert/usr/bin/dpkg-maintscript-helper/usr/bin/dpkg-query/usr/bin/dpkg-split/usr/bin/dpkg-statoverride/usr/bin/dpkg-trigger/usr/bin/update-alternatives/usr/bin/debconf/usr/bin/debconf-apt-progress/usr/bin/debconf-communicate/usr/bin/debconf-copydb/usr/bin/debconf-escape/usr/bin/debconf-set-selections/usr/bin/debconf-show/usr/bin/deb-systemd-helper/usr/bin/deb-systemd-invoke/usr/bin/ischroot...
会看到这个文件列表非常大,因为它把/usr/bin
目录下面的所有文件和目录都显示出来了,包括/usr/bin
目录本身。
如果想要从一堆的文件目录中寻找出所有的文件,则可以在find后面追加不同的path
find /usr/share /bin /usr/lib
如果想要把当前目录的所有文件都找出来
find .
或者
find
有时候我们只想根据文件的名字来寻找对应的文件,则可以使用-name
的action来实现
find . -name hello.txt
这种方式是根据文件的全名来寻找的,也就是说-name
的默认行为就是根据文件的全名来寻找。
如果说用户不能确定文件的大小写,可以用-iname
来寻找
find /usr -iname hello.txt
如果用户连文件的完整名字也无法确定,只想通过模糊匹配的形式来寻找文件,则可以使用正则表达式来寻找
find /usr -name '*.txt'
如果想根据文件名的长度来寻找文件,例如想寻找路径下文件的字符数目是4的文件
find /usr -name '????'
在一些情况下,用户想要寻找某一个特定的目录下面的符合要求的文件,而不是将整个目录中的所有符合要求的文件都显示出来,则可以使用-path
。例如一个目录下面有很多目录,现在想要在tmp目录下面寻找出文件名字符数为3的文件或目录,则可以执行
find . -path '*tmp/???'
同时也有一个-ipath
的action来忽略掉大小写的操作
在Linux系统下面,所有的东西都是以文件的形式存在。
在一些情况下,用户不想找出目录,只想找出对应路径下满足条件的文件,这个时候就可以使用-type
的action来寻找。
-type
的类型比较常见的有
例如,找出tmp目录下面出了目录的文件
find tmp -type f
自然,用户可组合不同的actions来寻找文件,例如只找出txt后缀的文件
find tmp -type f -name '*.txt'
find命令支持寻找空的文件或者空的目录
find /tmp -empty
find命令可以根据action的否定来处理寻找的文件结果。
例如可以寻找出了后缀为.txt
的文件
find . ! -name '*.txt'
或者寻找出了空文件或者空目录以外的文件
find . ! -empty
find命令可以根据文件的属主来寻找。例如想要寻找文件属主为chenzhiling的文件
find . -username chenzhiling
-username
接受的参数值可以使username或者UID,想要获取对应用户的UID可以通过
id -u chenzhiling
同时也可以根据用户所在的组或者GID来寻找对应的文件
find . -group root
如果想要使用GID来寻找,可以通过下面的命令获取对应的GID
id -g chenzhiling
文件在创建之后就会有三个时间跟随着文件。着三个时间分别是mtime、atime、ctime。他们分别表示
为此我们可以使用-mtime
、-atime
、-ctime
的action来寻找对应的文件。
例如寻找在五天内被修改的文件
find . -mtime -5 -type f
寻找在五天以前被修改的文件
find . -mtime +5 -type f
寻找文件在五天前(就在第五天前)被修改的文件
find . -mtime 5 -type f
以此类推另外两个action的用法类似。
如果觉得使用天来寻找太长了, find还提供了使用mmin
、amin
、cmin
这种分钟级别的action来寻找,例如寻找在5分钟以内被访问的文件
find . -type f -amin -5
在一些时候,用户想要寻找一些大于某个大小或者小于某一个大小的文件,这个时候可以使用-size
来寻找
find . -type f -size -10M
以上命令把目录下面小于10MB的文件都找出来。
文件的大小主要分为了4类
根据文件权限来寻找文件使用-perm
,他有两种方式来寻找,一种是根据权限的标识,另一种用权限的数字。
我们知道在Linux系统中,文件的权限主要分为了User、Group和Other级别的权限。如下图所示
例如要寻找文件权限为rwxr-xr-x
的文件,
find . -type f -perm u=rwx,g=rx,o=x
同时为了寻找对于所有的用户权限都是一样的情况,可以使用a来表示,例如寻找文件权限为r-xr-xr-x
的文件,这个文件对于所有的用户的权限都是一样的,则可以使用
find . -type f -perm a=rx
为了寻找某一类文件,只想关心它有执行权限x
,其他的权限可以设置,也可以没有,这个时候不能只是简单的设置-perm a=x
,因为这表示的是要寻找的文件对于所有的用户都只有执行权限,即--x--x--x
,这时候可以用-perm /a=x
的形式来寻找那些包含了执行权限的文件,但是别的权限为可以是有或者无
find . -type f -perm /a=x
使用权限数字来寻找文件,则直接在-perm
后面追加数字为参数即可。例如某一个文件的权限是rwxr-xr-x
,这个权限对应的数字表示模式为644,则寻找文件的时候可以
find . -type f -perm 644
同样的,在只想关心某一个权限位的时候,find也有一个表示的方式。例如上面的图中,我们想要找一系列文件,值关心这个文件是否有执行权限x
,那么每个权限位置的x位置都设置为1,其它不关心的设置为0,也就是111。这时候在111前面加上-
就可以实现只处理权限的子集了。
find . -type f -perm -111
那么如果说使用
find . -type f -perm -000
则其实-perm的效果就失效了。
在上面学会了使用数字模式的权限寻找文件了,那么如果想要寻找具有SUID权限的文件,其实就是一样的做法,只关心SUID的权限位置,其余权限都不管
find . -type f -perm -4000
同样的,先刚查看有setuid的权限文件的同时想查看具有执行权限的文件
find . -type f -perm -4111
查看设置了setgid的文件
find . -type -f -perm -2000
查看设置了粘滞位的文件
find . -type -f -perm -1000
这个sticky位比较神奇,有兴趣可以Google一下。
查看设置了setuid和sticky的该怎么找呢,当然这个属于比较少见到的文件了,权限的格式类似于--s-----t
,find命令只要求将这里两个权限数字模式相加即可,也就是5000
find . -type f -perm -5000
如果想要使用权限标识来表示,则可以
find . -type f -perm /u=sfind . -type f -perm /g=sfind . -type f -perm /o=t
前面提到了find寻找文件是通过路径去递归查询的,那么有时候有些目录下面的文件深度非常深,则会导致搜索的结果非常庞大。这时候可以使用-maxdepth
来限制搜索的深度
find . -type f -maxdepth 3
反过来也可以设置最小的搜索深度
find . -type f -mindepth 3
例 1:列出所有数据库
mysql -h host_name -P3306 -u user_name -p'password' -se "show databases;"
例 2:列出 database 下的所有表
mysql -h host_name -P3306 -u user_name -p'password' -D database -se "show tables;"
]]>在局域网环境下,主机之间是通过ARP协议获取对应机器的MAC地址的,ARP交换是在二层网络上面的协议。IP数据包在以太网中传递,但是对于以太网设备是不能识别32位的IP地址的,它只能识别48的物理MAC地址,为此在每一台主机上面都会维护一张从IP地址到物理地址的映射表,而不断更新这个映射表就是ARP(地址解析协议)所需要做的事情,ARP协议位于TCP-IP协议的底层。
ARP协议的主要流程是
举一个例子,如下图
ping 114.114.114.114
的操作114.114.114.114
,目的地址是192.168.10.3
,因为路由器需要查看自己本地的arp表去查看是否有对应的目的地址的匹配表,如果有则将回显包发送到对应的mac地址的机器A,如果没有,就需要在局域网中发送一个arp request的请求,询问A的mac地址。在这个过程中,会看到假设说主机A或者路由器在自己本地的arp表中如果没有对应的记录的时候,都需要去询问对应的mac地址并且更新arp表,也就是说arp表示会一致刷新的。
那么在linux系统中,我们的arp表到了什么情况会被清理呢?
在linux系统中,arp表是缓存在内存中的,因此在到了一定的量之后,会有对应的垃圾回收器来回收对应的arp表。对应的配置在一下文件中可以找到
Debian 9/proc/sys/net/ipv4/neigh/default/gc_thresh1 /proc/sys/net/ipv4/neigh/default/gc_thresh2 /proc/sys/net/ipv4/neigh/default/gc_thresh3
默认来说他们的值分别为128、512、1024,第一个值的垃圾回收器运行的最低要求,也就是说在arp数目达到了这个值之后才会启动对应的垃圾回收进程,第二个值是一个开始回收的阈值,当arp表的数目超过这个值一定时间(5秒)之后,垃圾回收器就会对arp表进行回收,而第三个值则是一个触发阈值,就是说一旦超过这个值垃圾回收器马上就进行arp的清理,当然在垃圾回收的时候,并不是全部都刷掉的,而是会根据一定的策略进行回收。
其实在ARP的整个工作流程中不难看出,其实是存在一定的可乘之机的。也就是说在图中的B主机是可以加入到整个arp工作流程来进行arp欺骗的。
具体的做法如下
mac:xx:xx:xx:yy:xx ip:192.168.10.1
,以此来伪装自己就是网关,让A将数据发给自己max: xx:xx:xx:yy:xx ip: 192.168.10.3
,这样主机B就达到了数据包嗅探的能力下面可以通过linux来模拟这么一个实验
首先安装一个arp攻击的模拟工具包
apt install dsniff
同时在攻击的机器上面打开ip转发
echo 1 > /proc/sys/net/ipv4/ip_forward
然后使用 arpspoof 命令进行欺骗, 命令使用方法如下:
arpspoof -i <网卡名> -t <欺骗的目标> <我是谁>
分别开两个终端:
终端1, 欺骗主机 A 我是网关
arpspoof -i eth0 -t 192.168.10.3 192.168.10.1
终端2, 欺骗网关我是主机 A
arpspoof -i eth0 -t 192.168.10.1 192.168.10.3
这样之后,在主机B上面就可以执行tcpdump或者urlsnarf来侦听数据了
apt install tcpdump snarf
使用tcpdump
tcpdump -nntvvv -i eth0 port 8080
使用urlsnarf
urlsnarf -i eth0
arp欺骗的形式有很多,例如有:中间人攻击、被动式数据嗅探。
如何防御 ARP 欺骗攻击?
1. 下载iTerm2
安装完成后,在/bin目录下会多出一个zsh的文件。
Mac系统默认使用dash作为终端,可以使用命令修改默认使用zsh:
chsh -s /bin/zsh
zsh完美代替bash,具体区别可查看:《Zsh和Bash区别》
iterm2的原始界面
2. 替换背景图片
打开路径:iterm2 -> Preferences -> Profiles -> window -> Background Image
选择一张自己喜欢的壁纸即可
可以通过Blending调节壁纸的透明度: 透明度为0的时候,背景变为纯色(黑色)
我个人比较喜欢扁平化的壁纸,喜欢的朋友可以来这里看看:
《有哪些优雅的 Windows 10 壁纸?》
3. 安装Oh my zsh
zsh的功能极其强大,只是配置过于复杂,通过Oh my zsh可以很快配置zsh。
这里只做简单的配置,如需要深入了解,可以查看:《oh-my-zsh,让你的终端从未这么爽过》
安装方法有两种,可以使用curl或wget,看自己环境或喜好:
# curl 安装方式sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
# wget 安装方式sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"oh-my-zsh开源地址:《oh-my-zsh》
4. 安装PowerLine
安装powerline:
pip install powerline-status --user
5. 安装PowerFonts
在常用的位置新建一个文件夹,如:~/Desktop/OpenSource/
在OpenSource文件夹下下载PorweFonts:
# git clonegit clone https://github.com/powerline/fonts.git --depth=1# cd to foldercd fonts# run install shell./install.sh
执行结果如下:
安装好字体库之后,我们来设置iTerm2的字体,具体的操作是:
iTerm2 -> Preferences -> Profiles -> Text
在Font区域选中Change Font,然后找到Meslo LG字体。
6. 安装配色方案(可跳过)
在OpenSource目录下执行git clone
命令:
git clone https://github.com/altercation/solarizedcd solarized/iterm2-colors-solarized/open .
在打开的finder窗口中,双击Solarized Dark.itermcolors和Solarized Light.itermcolors即可安装明暗两种配色:
再次进入iTerm2 -> Preferences -> Profiles -> Colors -> Color Presets
中根据个人喜好选择.
7. 安装主题
在OpenSource目录下执行git clone命令:
git clone https://github.com/fcamblor/oh-my-zsh-agnoster-fcamblor.gitcd oh-my-zsh-agnoster-fcamblor/./install
执行上面的命令会将主题拷贝到oh my zsh
的themes.
执行命令打开zshrc
配置文件,将ZSH_THEME后面的字段改为agnoster
vi ~/.zshrc
此时command+Q或source配置文件后,iTerm2变了模样:
8. 安装高亮插件
这是oh my zsh的一个插件,安装方式与theme大同小异:
cd ~/.oh-my-zsh/custom/plugins/git clone https://github.com/zsh-users/zsh-syntax-highlighting.gitvi ~/.zshrc
这时我们再次打开zshrc文件进行编辑。找到plugins,此时plugins中应该已经有了git,我们需要把高亮插件也加上:
请务必保证插件顺序,zsh-syntax-highlighting必须在最后一个。
然后在文件的最后一行添加:
source ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
执行命令使刚才的修改生效:
source ~/.zshrc
9. 可选择、命令补全
跟代码高亮的安装方式一样,这也是一个zsh的插件,叫做zsh-autosuggestion
,用于命令建议和补全。
cd ~/.oh-my-zsh/custom/plugins/git clone https://github.com/zsh-users/zsh-autosuggestionsvi ~/.zshrc
找到plugins,加上这个插件即可:
插件效果:
有同学说补全命令的字体不太清晰,与背景颜色太过相近,其实可以自己调整一下字体颜色。
Preferences -> Profiles -> Colors
中有Foreground是标准字体颜色,ANSI Colors中Bright的第一个是补全的字体颜色。
为了能够美化终端,通常会给自己的终端加上一个logo,并且这个logo还会打印一些系统的基本信息。效果如下
以下介绍几种终端Logo
安装方法很简单,直接执行
brew install screenfetch neofetch archey
安装好之后分别执行
chenzhiling@banban:blog master ✗ 10d ▲ △ ◒ ➜ screenfetch -/+:. chenzhiling@banban :++++. OS: 64bit Mac OS X 10.14 18A391 /+++/. Kernel: x86_64 Darwin 18.0.0 .:-::- .+/:-``.::- Uptime: 24d 6h 20m .:/++++++/::::/++++++/:` Packages: 171 .:///////////////////////:` Shell: zsh 5.3 ////////////////////////` Resolution: 2560x1600 ,1920x1080 -+++++++++++++++++++++++` DE: Aqua /++++++++++++++++++++++/ WM: Quartz Compositor /sssssssssssssssssssssss. WM Theme: Blue :ssssssssssssssssssssssss- Disk: 128G / 251G (53%) osssssssssssssssssssssssso/` CPU: Intel Core i5-8259U @ 2.30GHz `syyyyyyyyyyyyyyyyyyyyyyyy+` GPU: Intel Iris Plus Graphics 655 `ossssssssssssssssssssss/ RAM: 4622MiB / 8192MiB :ooooooooooooooooooo+. `:+oo+/:-..-:/+o+/-
neofetch
chenzhiling@banban:blog master ✗ 10d ▲ △ ◒ ➜ neofetch 'c. chenzhiling@banban ,xNMM. ------------------ .OMMMMo OS: macOS Mojave 10.14 18A391 x86_64 OMMM0, Host: MacBookPro15,2 .;loddo:' loolloddol;. Kernel: 18.0.0 cKMMMMMMMMMMNWMMMMMMMMMM0: Uptime: 24 days, 6 hours, 21 mins .KMMMMMMMMMMMMMMMMMMMMMMMWd. Packages: 103 (brew) XMMMMMMMMMMMMMMMMMMMMMMMX. Shell: zsh 5.3 ;MMMMMMMMMMMMMMMMMMMMMMMM: Resolution: 1440x900@2x, 1920x1080@2x :MMMMMMMMMMMMMMMMMMMMMMMM: DE: Aqua .MMMMMMMMMMMMMMMMMMMMMMMMX. WM: Quartz Compositor kMMMMMMMMMMMMMMMMMMMMMMMMWd. WM Theme: Blue (Light) .XMMMMMMMMMMMMMMMMMMMMMMMMMMk Terminal: vscode .XMMMMMMMMMMMMMMMMMMMMMMMMK. CPU: Intel i5-8259U (8) @ 2.30GHz kMMMMMMMMMMMMMMMMMMMMMMd GPU: Intel Iris Plus Graphics 655 ;KMMMMMMMWXXWMMMMMMMk. Memory: 6238MiB / 8192MiB .cooc,. .,coo:.
archey
chenzhiling@banban:blog master ✗ 10d ▲ △ ◒ ➜ archey ### User: chenzhiling #### Hostname: banban ### Distro: OS X 10.14 ####### ####### Kernel: Darwin ###################### Uptime: 24 days ##################### Shell: /bin/zsh #################### Terminal: xterm-256color vscode #################### CPU: Intel Core i5-8259U CPU @ 2.30GHz ##################### Memory: 8 GB ###################### Disk: 53% #################### Battery: 100% ################ IP Address: 218.107.55.252 #### #####
同样的,也可以在Linux系统上安装上面几种Logo,效果如下
如果对Spring Security和Oauth2比较熟悉,可以直接跳过第1、2部分,直接到第三部分。
Spring Security是一个强大的高度可定制化的身份认证与访问控制框架,它用于为Spring框架开发的应用提供标准的安全保障。Spring Security这个框架专注于为应用提供授权和认证服务,它的强大在于用户可以非常灵活地自定义权限访问。
Spring Security的特性
Oauth2很显然是Oauth协议的版本2,Oauth也可以称为是框架,其允许用户授权第三方的应用接入到某一个应用中访问自己的信息,并授权一定的HTTP服务访问能力,而不需要将自己的用户名和密码提供给第三方应用或者分享他们数据的全部内容。Oauth2则是Oauth的改进版本,其简化了Oauth的一些机制,让不同应用之间的交互更加安全方便。Oauth2.0主要关注客户端开发者的简易性,要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。
Oauth2目前来说在各个厂商的各大应用中获得了比较好的效果,例如Google和Facebook。
Oauth2包含了4个角色
Tokens 是一系列由认证服务器生成的随机字符,每当客户端请求获取对应的用户的token时认证服务器会下发对应的Token给客户端,通常又两种Token类型
scope是一个对客户端访问资源的权限限制的参数,授权服务器Authorization Server会定义一个scope列表,客户端在请求获取Token的时候需要将scope作为一个参数提交,这个scope参数的内容越多,那么获得的资源访问能力就越多,这个通常都是用户授权给客户端的。
由于客户端想要通过Oauth2来获得资源服务器中用户的资源内容,因此客户端需要向认证服务器注册一个client,注册client的参数包含如下几个
资源服务器收到client的注册内容之后返回的内容如下
Oauth2定义了4中授权类型,这4中授权类型需要根据不同的场景进行使用,下面分别介绍
授权码适合在Client是一个web服务器的时候,这个方式允许web保持一个长时间的Access Token因为可以通过refresh Token来刷新服务器(只要授权服务器允许这种行为)。
例子
场景
用户在这个过程中不会看到Access Token,网站会自行存储这个Access Token,Google 认证服务器在下发Access Token的同事一般还会下发Refresh Token
时序图如下
当网页通过一些脚本语言例如JavaScript作为Client客户端的时候,这种情况不允许脚本获取到Refresh Token。
例子
场景
也许你会很好奇为什么Facebook允许客户端直接通过添加Access Token而不会被跨域规则所拦截下来。这是可能的也是允许的,因为有CORS(Access-Control-Allow-Origin)的帮助。具体可以查看Access-Control-Allow-Origin
Attention! This type of authorization should only be used if no other type of authorization is available. Indeed, it is the least secure because the access token is exposed (and therefore vulnerable) on the client side.
时序图如下
这种认证方式比较特别,认证的信息(也就是密码)是先传给客户端网站,客户端网站拿着密码去和认证服务器通讯获得对应的Access Token,因此说这两个应用都需要有同等的信任关系,所以一般来说这种方式用于两个应用属于同一个单位或者企业的应用之间使用,例如有一个网站属于example.com
,现在它需要授权访问api.example.com
下面所保护的资源,那么用户自然会愿意将自己的密码授权给客户端网站,因为他的账号密码就是从example.com
所获得的。
例子
acme.com
网站(属于Acme公司)注册了账号api.acme.com
暴露 APIacme.com
网站场景
时序图
当客户端Client自己本身就是Resource Owner,那么意味着不用用户为之授权。
例子
场景
在这里,网站的使用者就没有必要给网站授权自己的资源内容访问权限给网站。
时序图
本人使用的开发工具是Spring Tool Suite 4,构建项目的时候选择:Web、JPA、Thymeleaf、Security、Oauth2 Resource Server,如下
创建好项目之后,对应的pom.xml
文件内容如下
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>org.scut.storic</groupId> <artifactId>storic-rest</artifactId> <version>1.0.0</version> <name>storic-rest</name> <description>manager for storest based on fabric</description> <properties> <java.version>1.8</java.version> <skipTests>true</skipTests> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.jasypt</groupId> <artifactId>jasypt</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>24.0-jre</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
首先配置Spring Security
CustomSecurityConfiguration.java
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.password.PasswordEncoder;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(securedEnabled = true)public class CustomSecurityConfiguration extends WebSecurityConfigurerAdapter{ @Bean @Override protected UserDetailsService userDetailsService(){ //采用一个自定义的实现UserDetailsService接口的类 return new CustomUserDetailsService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(new NullAuthenticationProvider()); super.configure(auth); } /** * Spring Boot 2 配置,这里要bean 注入 */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { AuthenticationManager manager = super.authenticationManagerBean(); return manager; } @Bean public PasswordEncoder passwordEncoder() { //return PasswordEncoderFactories.createDelegatingPasswordEncoder(); return new CustomPasswordEncoder(); }}
CustomUserDetailsService.java
import java.util.ArrayList;import java.util.List;import org.scut.storic.persistent.entity.Account;import org.scut.storic.persistent.repository.AccountRepository;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;public class CustomUserDetailsService implements UserDetailsService { private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class); @Autowired private AccountRepository accountRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("Load UserDetails by username: " + username); Account account = accountRepository.findByName(username); if (account == null) throw new UsernameNotFoundException(username); List<GrantedAuthority> authorities = new ArrayList<>(); for (String permission : account.getPermission().split(" ")) { if (permission.isEmpty()) continue; authorities.add(new SimpleGrantedAuthority(permission)); } return new CustomUser(username, account.getPassword(), true, true, true, account.isVerified(), authorities); }}
其中AccountRepository
是为了从Mysql数据库中获取对应账户的权限内容,不再详细描述。
CustomUser.java
import java.util.Collection;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;public class CustomUser extends User { /** * */ private static final long serialVersionUID = 1527936124593374191L; /** * Construct the <code>User</code> with the details required by * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}. * * @param username the username presented to the * <code>DaoAuthenticationProvider</code> * @param password the password that should be presented to the * <code>DaoAuthenticationProvider</code> * @param enabled set to <code>true</code> if the user is enabled * @param accountNonExpired set to <code>true</code> if the account has not expired * @param credentialsNonExpired set to <code>true</code> if the credentials have not * expired * @param accountNonLocked set to <code>true</code> if the account is not locked * @param authorities the authorities that should be granted to the caller if they * presented the correct username and password and the user is enabled. Not null. * * @throws IllegalArgumentException if a <code>null</code> value was passed either as * a parameter or as an element in the <code>GrantedAuthority</code> collection */ public CustomUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); } @Override public boolean isCredentialsNonExpired() { return super.isCredentialsNonExpired(); } @Override public boolean isAccountNonExpired() { return super.isAccountNonExpired(); } @Override public boolean isAccountNonLocked() { return super.isAccountNonLocked(); } public boolean isManager() { return this.getAuthorities() .contains(new SimpleGrantedAuthority("ROLE_ADMIN")); } public boolean isSystemManager() { return this.getAuthorities() .contains(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); }}
CustomPasswordEncoder.java
import org.springframework.security.crypto.password.PasswordEncoder;public class CustomPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { return PasswordHelper.encryptPassword(rawPassword.toString()); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return PasswordHelper.checkPassword(rawPassword.toString(), encodedPassword.toString()); }}
PasswordHelper.java
import java.util.HashMap;import java.util.Map;import org.jasypt.digest.StandardStringDigester;import org.jasypt.util.password.PasswordEncryptor;public class PasswordHelper { public enum Type{ SHA256_BASE64("SHA-256", "base64"), SHA256_HEX("SHA-256", "hexadecimal"), MD5_BASE64("MD5", "base64"), MD5_HEX("MD5", "hexadecimal"); private String algorithm; private String outputType; private Type(String algorithm, String value) { this.algorithm = algorithm; this.outputType = value; } public String getAlgorithm() { return algorithm; } public String getOutputType() { return outputType; } } private final static class MyPasswordEncryptor implements PasswordEncryptor { private final StandardStringDigester digester; public MyPasswordEncryptor(String algorithm) { this.digester = new StandardStringDigester(); this.digester.setAlgorithm(algorithm); this.digester.setIterations(1000); this.digester.setSaltSizeBytes(8); } public void setStringOutputType(final String stringOutputType) { this.digester.setStringOutputType(stringOutputType); } public String encryptPassword(final String password) { return this.digester.digest(password); } public boolean checkPassword(final String plainPassword, final String encryptedPassword) { return this.digester.matches(plainPassword, encryptedPassword); } } private static Map<Type, MyPasswordEncryptor> passwordEncryptors = new HashMap<Type, MyPasswordEncryptor>(); private PasswordHelper(){} private static PasswordEncryptor getPasswordEncryptor(final Type type) { if (passwordEncryptors.get(type) == null) { synchronized (passwordEncryptors) { if (passwordEncryptors.get(type) == null) { MyPasswordEncryptor passwordEncryptor = new MyPasswordEncryptor(type.getAlgorithm()); passwordEncryptor.setStringOutputType(type.getOutputType()); passwordEncryptors.put(type, passwordEncryptor); } } } return passwordEncryptors.get(type); } public static String encryptPassword(final String password) { return getPasswordEncryptor(Type.SHA256_BASE64).encryptPassword(password); } public static String encryptPassword(final String password, final Type type) { return getPasswordEncryptor(type).encryptPassword(password); } public static boolean checkPassword(final String plainPassword, final String encryptedPassword) { return getPasswordEncryptor(Type.SHA256_BASE64).checkPassword(plainPassword, encryptedPassword); } public static boolean checkPassword(final String plainPassword, final String encryptedPassword, final Type type) { return getPasswordEncryptor(type).checkPassword(plainPassword, encryptedPassword); }}
NullAuthenticationProvider.java
import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;public class NullAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return authentication; } @Override public boolean supports(Class<?> authentication) { return (NullAuthenticationToken.class.isAssignableFrom(authentication)); }}
NullAuthenticationToken.java
import org.springframework.security.authentication.AbstractAuthenticationToken;import org.springframework.security.core.userdetails.User;public class NullAuthenticationToken extends AbstractAuthenticationToken { /** * */ private static final long serialVersionUID = 4544894451246623482L; private final Object principal; public NullAuthenticationToken(User principal) { super(principal.getAuthorities()); this.principal = principal; setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return principal; }}
Oauth2需要配置一下三项:AuthorizationServer、ClientDetailsService、ResourceServer
CustomAuthServerConfiguration.java
import java.util.concurrent.TimeUnit;import org.scut.storic.utils.PasswordHelper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;@Configuration@EnableAuthorizationServerpublic class CustomAuthServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Autowired UserDetailsService userDetailsService; private static final String DEMO_RESOURCE_ID = "storicid"; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //Oauth2 默认使用BCryptPasswordEncoder作为加密方法,需要在加密之后的字符前加{bcrypt} //String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("storic-secret"); String finalSecret = PasswordHelper.encryptPassword("storic-secret"); //这里通过实现 ClientDetailsService接口 clients.withClientDetails(new BaseClientDetailService()); //配置客户端,一个用于password认证一个用于client认证 clients .inMemory() .withClient("client_app") .resourceIds(DEMO_RESOURCE_ID) .authorizedGrantTypes("client_credentials", "refresh_token", "password") .scopes("select") .authorities("oauth2") .secret(finalSecret) .accessTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)) .refreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)) .and() .withClient("client_web") .resourceIds(DEMO_RESOURCE_ID) .authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token", "password", "implicit") .scopes("read", "write", "trust") .authorities("oauth2") .secret(finalSecret) .accessTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)) .refreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(new InMemoryTokenStore()) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) // 不添加无法正常使用refresh_token .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { //允许表单认证 //这里增加拦截器到安全认证链中,实现自定义认证,包括图片验证,短信验证,微信小程序,第三方系统,CAS单点登录 //addTokenEndpointAuthenticationFilter(IntegrationAuthenticationFilter()) //IntegrationAuthenticationFilter 采用 @Component 注入 oauthServer .realm(DEMO_RESOURCE_ID) .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients(); }}
这里ClientDetailService基于内存的方式,是最简单的一种方式,在真是环境中,一般建议结合JDBC或者Redis。为此可以自定义这个ClientDetailService
CustomClientDetailService.java (可选)
import java.util.Arrays;import java.util.HashSet;import java.util.Set;import java.util.concurrent.TimeUnit;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.oauth2.provider.ClientDetails;import org.springframework.security.oauth2.provider.ClientDetailsService;import org.springframework.security.oauth2.provider.ClientRegistrationException;import org.springframework.security.oauth2.provider.NoSuchClientException;import org.springframework.security.oauth2.provider.client.BaseClientDetails;/** * 自定义ClientDetailsService,可以灵活定义auth2拦截的接口、权限集合等 * @author chenzhiling * */public class CustomClientDetailService implements ClientDetailsService { private static final Logger log = LoggerFactory.getLogger(CustomClientDetailService.class); @Override public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { BaseClientDetails client = null; //这里可以改为查询数据库 if("client_app".equals(clientId)) { log.info("Create a app client detail."); client = new BaseClientDetails(); client.setClientId(clientId); client.setClientSecret("{noop}123456"); client.setResourceIds(Arrays.asList("order")); client.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "client_credentials", "refresh_token", "password", "implicit")); //不同的client可以通过一个scope 对应权限集 client.setScope(Arrays.asList("all", "select")); client.setAuthorities(AuthorityUtils.createAuthorityList("ADMIN_ROLE")); client.setAccessTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)); //1天 client.setRefreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)); //1天 Set<String> uris = new HashSet<>(); uris.add("/login"); client.setRegisteredRedirectUri(uris); } if(client == null) { throw new NoSuchClientException("No client width requested id: " + clientId); } return client; }}
CustomResServerConfiguration.java
import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;/** * Spring Security 中的ResourceServerConfigurerAdapter配置会覆盖WebSecurityConfigurerAdapter * protected void configure(HttpSecurity http) 中的配置会以ResourceServerConfigurerAdapter为准 * @author chenzhiling * */@Configuration@EnableResourceServerpublic class CustomResServerConfiguration extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ID = "storicid"; @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(RESOURCE_ID).stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { // Since we want the protected resources to be accessible in the UI as well we need // session creation to be allowed (it's disabled by default in 2.0.6) http .httpBasic() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .authorizeRequests() .antMatchers("/") .permitAll() .and() .logout() .permitAll() .and() .authorizeRequests() .antMatchers("/oauth/*") .permitAll() .and() .authorizeRequests() .antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**", "/**/*.js", "/**/*.css", "/**/*.png") .permitAll() .and() .authorizeRequests() .regexMatchers(HttpMethod.POST, "/user") .permitAll() .and() .authorizeRequests() .antMatchers("/admin/**") .access("#oauth2.hasScope('read')") .antMatchers("/admin/**") .hasAnyAuthority("ROLE_ADMIN") .and() .authorizeRequests() .anyRequest() .authenticated() .and() //关闭默认的csrf认证 .csrf() .disable(); }}
对于http的Security拦截可以灵活根据不同的需求定义,这里只是极尽可能列出不同情况。