CentOS7 jenkins配置.Net Core持续部署

jenkins安装

Jenkins官网

1.创建Dockerfile

1
2
3
# 使用touch创建空文件
sudo touch Dockerfile
sudo vim Dockerfile

2.填充一下内容

注:提前更新Docker镜像源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM jenkins

USER root
#更新源并安装缺少的包
RUN apt-get update && apt-get install -y libltdl7 && apt-get update

# 使 jenkins 运行 docker 不需要 sudo
RUN groupadd -o -g 999 docker && usermod -aG docker jenkins
ARG dockerGid=999

RUN echo "docker:x:${dockerGid}:jenkins" >> /etc/group

# 解决Docker时区问题
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone

USER jenkins
# 解决时区问题
ENV JAVA_OPTS -Duser.timezone=Asia/Shanghai

USER root
# 安装 docker-compose 因为等下构建环境的需要
RUN curl -L https://github.com/docker/compose/releases/download/1.21.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

RUN chmod +x /usr/local/bin/docker-compose

注:可能出现

1
2
3
4
5
[root@localhost Desktop]# curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
curl: (35) Peer reports incompatible or unsupported protocol version.

需要运行以下命令更新:

1
yum update nss curl nss-util nspr

等待时间可能有点长,请耐心等待

运行结果:

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
[root@localhost Desktop]# docker build . -t auto-jenkins
Sending build context to Docker daemon 2.56kB
Step 1/8 : FROM jenkins
---> 7b210b6c238a
Step 2/8 : USER root
---> Using cache
---> 9ce15bde1415
Step 3/8 : RUN apt-get update && apt-get install -y libltdl7 && apt-get update
---> Using cache
---> 3d428282a736
Step 4/8 : RUN groupadd -o -g 999 docker && usermod -aG docker jenkins
---> Using cache
---> 94f6440d5fae
Step 5/8 : ARG dockerGid=999
---> Using cache
---> 3cce8ba121fe
Step 6/8 : RUN echo "docker:x:${dockerGid}:jenkins" >> /etc/group
---> Using cache
---> a0f4fdd6cf3f
Step 7/8 : RUN curl -L https://github.com/docker/compose/releases/download/1.21.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
---> Running in fcdb1deaf0bf
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 617 0 617 0 0 226 0 --:--:-- 0:00:02 --:--:-- 226
100 10.3M 100 10.3M 0 0 591k 0 0:00:17 0:00:17 --:--:-- 1622k
Removing intermediate container fcdb1deaf0bf
---> 1909e02c39da
Step 8/8 : RUN chmod +x /usr/local/bin/docker-compose
---> Running in 409184886de5
Removing intermediate container 409184886de5
---> b0f1c2c53301
Successfully built b0f1c2c53301
Successfully tagged auto-jenkins:latest

出现以上 Successfully 内容代表安装Jenkins成功

启动Jenkins

需要先创建一个Jenkins的配置目录,并且挂载到docker 里的Jenkins目录下

1
mkdir -p /var/jenkins_home

运行

1
2
3
4
5
docker run --name jenkins -p 8080:8080 -p 50000:50000 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(which docker):/bin/docker \
-v /var/jenkins_home:/var/jenkins_home \
-d auto-jenkins

第一行构建一个名称为jenkins的容器,需要使用的 8080 跟 50000 端口,-p 是容器运行开放端口

第二行将宿主机上的 docker.sock 挂载到容器中的相应位置,使得容器中的 docker cli 能跟宿主机的 docker 通信

第三行将宿主机上面的 docker 命令行工具挂载到容器中,使 jenkins 用户能够执行 docker 命令

第四行挂载我们之前创建的配置文件存放目录到 jenkins 用户的 home(对的,jenkins 用户的 home 目录在 /var 下面),建立宿主机的配置目录,挂载进docker容器里,这样容器里的Jenkins配置目录文件就会映射出来

第五行:使用auto-jenkins Image 并且后台启动

运行完这条指令后,出现一串很长的字符串以后,我们的jenkins已经成功启动,可以访问本机的 8080 端口来登录 Jenkins

通过命令docker ps查看运行的镜像

1
2
3
[root@localhost Desktop]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0c63f3ce79b5 auto-jenkins "/bin/tini -- /usr/l…" 12 seconds ago Up 5 seconds 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp jenkins

删除docker

1
docker rm -f jenkins

配置jenkins

进入容器内

1
docker exec -it jenkins /bin/bash

查看密码:

1
cat /var/jenkins_home/secrets/initialAdminPassword
1
2
3
4
5
oot@localhost Desktop]# docker exec -it jenkins /bin/bash
root@0c63f3ce79b5:/# cat /var/jenkins_home/secrets/initialAdminPassword
fc648ef222a54ac690bec031834885f2
root@0c63f3ce79b5:/# exit
exit

复制输出的内容,粘贴到Administrator password,输入 exit 退出容器,此时进行下一步你会看到此界面,点击 Install suggested plugins

重启:

1
docker restart jenkins

最后,安装推荐插件

忘记管理员密码

1
2
3
4
5
6
7
[root@localhost Desktop]# cd /var/jenkins_home/users/admin/
[root@localhost admin]# ls
config.xml
[root@localhost admin]# vim config.xml
[root@localhost admin]# docker restart jenkins
jenkins
[root@localhost admin]#

<passwordHash>节点的内容(图中黑色的那一串)换成

#jbcrypt:$2a$10$DdaWzN64JgUtLdvxWIflcuQu2fgrrMSAMabF5TSrGK5nXitqK9ZMS

重启,默认密码为:111111

参考网址:

http://www.cnblogs.com/LongJiangXie/p/7517909.html

http://www.cnblogs.com/stulzq/p/8627360.html

http://www.cnblogs.com/JacZhu/p/6814848.html

单机安装

1、首先去官网下载zookeeper的包 zookeeper-3.4.10.tar.gz

2、用FTP文上传到/usr/local下

3、解压文件 tar -zxvf zookeeper-3.4.10.tar.gz

4、在conf文件夹下新建zoo.cfg文件,或者使用里面自带的zoo_sample.cfg,重新拷贝

cp zoo_sample.cfg zoo.cfg

zoo.cfg文件内容:

1
2
3
4
tickTime=2000
dataDir=/usr/local/zookeeper/zookeeper-3.4.10/data
dataLogDir=/usr/local/zookeeper/zookeeper-3.4.10/logs
clientPort=4180

5、运行server脚本

需要在root账户下启动:

1
[root@localhost zookeeper-3.4.10]# ./bin/zkServer.sh start

6、Server启动之后, 就可以启动client连接server了, 执行脚本:

1
[root@localhost zookeeper-3.4.10]# ./bin/zkCli.sh -server localhost:4180

7,检查状态

1
[root@localhost zookeeper-3.4.10]# ./bin/zkServer.sh start

8,停止

1
[root@localhost zookeeper-3.4.10]# ./bin/zkServer.sh stop

伪集群模式

所谓伪集群, 是指在单台机器中启动多个zookeeper进程, 并组成一个集群. 以启动3个zookeeper进程为例

  • 复制三份zookeeper,(权限需要使用root帐号)
1
2
3
4
5
[toor@localhost zookeeper]$ su root
Password:
[root@localhost zookeeper]# cp -r zookeeper-3.4.10 zookeeper-3.4.10-1
[root@localhost zookeeper]# cp -r zookeeper-3.4.10 zookeeper-3.4.10-2
[root@localhost zookeeper]# cp -r zookeeper-3.4.10 zookeeper-3.4.10-3
  • 配置每个zookeeper文件夹中对应的配置文件

1,zookeeper-3.4.10-1中的zoo.cfg文件

1
2
3
4
5
6
7
8
9
10
tickTime=2000
initLimit=5
syncLimit=2
tickTime=2000
dataDir=/usr/local/zookeeper/zookeeper-3.4.10-1/data
dataLogDir=/usr/local/zookeeper/zookeeper-3.4.10-1/logs
clientPort=4180
server.0=127.0.0.1:8880:7770
server.1=127.0.0.1:8881:7771
server.2=127.0.0.1:8882:7772

2,zookeeper-3.4.10-2中的zoo.cfg文件

1
2
3
4
5
6
7
8
9
10
tickTime=2000
initLimit=5
syncLimit=2
tickTime=2000
dataDir=/usr/local/zookeeper/zookeeper-3.4.10-2/data
dataLogDir=/usr/local/zookeeper/zookeeper-3.4.10-2/logs
clientPort=4180
server.0=127.0.0.1:8880:7770
server.1=127.0.0.1:8881:7771
server.2=127.0.0.1:8882:7772

3,zookeeper-3.4.10-3中的zoo.cfg文件

1
2
3
4
5
6
7
8
9
10
tickTime=2000
initLimit=5
syncLimit=2
tickTime=2000
dataDir=/usr/local/zookeeper/zookeeper-3.4.10-3/data
dataLogDir=/usr/local/zookeeper/zookeeper-3.4.10-3/logs
clientPort=4180
server.0=127.0.0.1:8880:7770
server.1=127.0.0.1:8881:7771
server.2=127.0.0.1:8882:7772

发现只有dataDir和dataLogDir还有clientPort这三个参数不一致,其他参数完全一致。

  • 配置myid

    在这三个datadir配置的路径下/Users/zookeeper0、1、2上增加myid文件,里面依次填上0、1、2。

    数字0、1、2和每个conf/zoo.cfg中的server.0、server.1、server.2的数字一一对应,让zookeeper知道你是哪个server

  • 依次启动zookeeper节点服务

1
./bin/zkServer.sh start

检查状态

1
./bin/zkServer.sh status

客户端连接

1
./bin/zkCli.sh -server localhost:4180

看到输出显示有下列信息,表示启动,配置成功!

1
Welcome to ZooKeeper!

集群模式

集群模式, 各server部署在不同的机器上, 因此各server的conf/zoo.cfg文件可以完全一样(是所有都一样)

他们的zookeeper的conf下的zoo.cfg文件为:

1
2
3
4
5
6
7
8
9
tickTime=2000
initLimit=5
syncLimit=2
dataDir=/home/zookeeper/data
dataLogDir=/home/zookeeper/logs
clientPort=4180
server.1=192.168.1.130:2888:3888
server.2=192.168.1.131:2888:3888
server.3=192.168.1.132:2888:3888

部署了3台zookeeper server, 分别部署在192.168.1.130, 192.168.1.131, 192.168.1.132上。

各server的dataDir目录下的myid文件中的数字必须不同。

  192.168.1.130 server的myid为1

  192.168.1.131 server的myid为2

  192.168.1.132 server的myid为3

至此,所有的安装与部署就都搞定了。

ZooKeeper服务命令:

在准备好相应的配置之后,可以直接通过zkServer.sh 这个脚本进行服务的相关操作

  1. 启动ZK服务: sh bin/zkServer.sh start
  2. 查看ZK服务状态: sh bin/zkServer.sh status
  3. 停止ZK服务: sh bin/zkServer.sh stop
  4. 重启ZK服务: sh bin/zkServer.sh restart

zoo.cfg配置详解:

tickTime:Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,每个tickTime 时间就会发送一个心跳。

dataDir:Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。

clientPort:客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。

initLimit:Leader和Follower初始化连接时最长能忍受多少个心跳时间间隔数。总的时间长度就是 5*2000=10 秒。

syncLimit:Leader 与 Follower之间发送消息,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000=4 秒。

server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

myid文件:

除了修改 zoo.cfg 配置文件,集群模式下还要配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面就有一个数据就是 A 的值,Zookeeper 启动时会读取这个文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是哪个server。

server.1=10.1.39.43:2888:3888,很多人不理解为啥后面有两个端口?解释一下:

2888:标识这个服务器与集群中的leader服务器交换信息的端口

3888:leader挂掉时专门用来进行选举leader所用的端口

参考:

http://www.cnblogs.com/hzg110/p/6921886.html

使用ab进行站点压力测试

压力测试相关概念

  • 吞吐率(Requests per second)

    概念:服务器并发处理能力的量化描述,单位是reqs/s,指的是某个并发用户数下单位时间内处理的请求数。某个并发用户数下单位时间内能处理的最大请求数,称之为最大吞吐率。
    计算公式:总请求数 / 处理完成这些请求数所花费的时间,即
    Request per second = Complete requests / Time taken for tests

  • 并发连接数(The number of concurrent connections)

    概念:某个时刻服务器所接受的请求数目,简单的讲,就是一个会话。

    并发用户数(The number of concurrent users,Concurrency Level)

    概念:要注意区分这个概念和并发连接数之间的区别,一个用户可能同时会产生多个会话,也即连接数。

  • 用户平均请求等待时间(Time per request)

    计算公式:处理完成所有请求数所花费的时间/ (总请求数 / 并发用户数),即
    Time per request = Time taken for tests /( Complete requests / Concurrency Level)

  • 服务器平均请求等待时间(Time per request: across all concurrent requests)

    计算公式:处理完成所有请求数所花费的时间 / 总请求数,即
    Time taken for / testsComplete requests
    可以看到,它是吞吐率的倒数。
    同时,它也=用户平均请求等待时间/并发用户数,即
    Time per request / Concurrency Level

下载工具

官网下载网址

解压压缩包,进入到httpd-2.4.25-win64-VC14\Apache24\bin目录下,
打开命令行,输入命令

运行测试

1
ab -n 100 -c 10 http://baidu.com/

其中-n表示请求数,-c表示并发数

测试结果

测试结果

完整测试报告

服务器信息

显示服务器为apache,域名,端口;
1
2
3
Server Software:        Apache
Server Hostname: baidu.com
Server Port: 80

文档信息

所在位置“/”,文档的大小为338436 bytes(此为http响应的正文长度)
1
2
Document Path:          /
Document Length: 81 bytes

重要信息

1
2
3
4
5
6
7
8
9
10
Concurrency Level:      10
Time taken for tests: 2.584 seconds
Complete requests: 100
Failed requests: 0
Total transferred: 38100 bytes
HTML transferred: 8100 bytes
Requests per second: 38.70 [#/sec] (mean)
Time per request: 258.374 [ms] (mean)
Time per request: 25.837 [ms] (mean, across all concurrent requests)
Transfer rate: 14.40 [Kbytes/sec] received
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
//并发请求数
Concurrency Level: 10

//整个测试持续的时间
Time taken for tests: 2.584 seconds

//完成的请求数
Complete requests: 100

//失败的请求数
Failed requests: 0

//整个场景中的网络传输量
Total transferred: 13701482 bytes

//整个场景中的HTML内容传输量
HTML transferred: 13197000 bytes

//吞吐率,大家最关心的指标之一,相当于 LR 中的每秒事务数,后面括号中的 mean 表示这是一个平均值
Requests per second: 38.70 [#/sec] (mean)

//用户平均请求等待时间,大家最关心的指标之二,相当于 LR 中的平均事务响应时间,后面括号中的 mean 表示这是一个平均值
Time per request: 258.374 [ms] (mean)

//服务器平均请求处理时间,大家最关心的指标之三
Time per request: 25.837 [ms] (mean, across all concurrent requests)

//平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题
Transfer rate: 14.40 [Kbytes/sec] received

网络上消耗的时间的分解

1
2
3
4
5
6
Connection Times (ms)
min mean[+/-sd] median max
Connect: 12 25 38.9 20 408
Processing: 8 223 120.3 196 591
Waiting: 8 142 124.9 120 574
Total: 29 248 125.7 217 609

网络消耗时间

这段是每个请求处理时间的分布情况,50%的处理时间在217ms内,66%的处理时间在224ms内…,重要的是看90%的处理时间

1
2
3
4
5
6
7
8
9
10
Percentage of the requests served within a certain time (ms)
50% 217
66% 224
75% 246
80% 249
90% 595
95% 604
98% 609
99% 609
100% 609 (longest request)

登录的问题

有时候进行压力测试需要用户登录,怎么办?
请参考以下步骤:

先用账户和密码登录后,用开发者工具找到标识这个会话的Cookie值(Session ID)记下来
如果只用到一个Cookie,那么只需键入命令:
ab -n 100 -C key=value http://baidu.com/

如果需要多个Cookie,就直接设Header:
ab -n 100 -H “Cookie: Key1=Value1; Key2=Value2” http://baidu.com/

同类型的压力测试工具还有:webbench、siege、http_load等

参考:

http://www.cnblogs.com/lpfuture/p/5740831.html

win10安装openssl

下载安装

openssl官网:https://www.openssl.org/

openssl下载地址:http://slproweb.com/products/Win32OpenSSL.html

下载Win64 OpenSSL v1.1.0e(win10 64位),点击依次点击下一步,即可完成安装;

设置环境变量

找到“系统->高级系统设置->环境变量”窗口里面的系统变量;

添加变量名为“OPENSSL_HOME”,变量值:“C:\OpenSSL-Win64\bin”(根据本机openssl安装目录配置),然后在变量名为“Path”中添加“%OPENSSL_HOME%”

如图:

配置1

配置2

注意:设置完环境变量后的cmd窗口必须重新打开,设置的环境变量才会起作用,否则依然提示命令不可用。

使用WinSCP软件在windows和Linux中进行文件传输

官方的解释:WinSCP 是一个 Windows 环境下使用 SSH 的开源图形化 SFTP 客户端。同时支持 SCP 协议。它的主要功能就是在本地与远程计算机间安全的复制文件等。

官网网站:http://winscp.net/eng/docs/lang:chs

下载地址:http://winscp.net/eng/download.php

Portable executables是绿色版,不需要安装。
下载完成之后打开可执行文件,填写登录信息,选择协议之后,就可以进行图形化管理了。

配置

我们只需要填写3个地方:1. host name 2.user name 3.password。hostname是虚拟机的IP地址。
最好是填写root用户时的用户名和密码。点击登陆就进入到Linux系统了
界面中,左边属于windows操作系统的目录,右边属于Linux(CentOS)操作系统的目录。可以用鼠标直接把文件拖过来拖过去的。

参考:

http://www.cnblogs.com/shanyou/archive/2012/08/25/2656753.html

CentOS7使用Jexus部署.NetCore应用

安装Jexus

安装 Jexus 直接使用一下命令即可(需要在root身份下执行):

1
curl https://jexus.org/release/x64/install.sh|sh

安装成功后会提示:OK, Jexus has been installed in /usr/jexus.

发布程序到CentOS7服务器

/usr/local建立www文件夹,将应用程序包放在www文件夹下

如果www不存在,则使用命令创建:

1
mkdir /usr/local/www

将发布好的程序,拷贝到linux系统/usr/local/www文件夹中

参考使用

WinSCP配置

配置Jexus

1
2
3
cd /usr/jexus/siteconf #切换到jexus配置文件目录
cp default test #复制默认配置文件test
vim test #编辑

参考配置:

1
2
3
4
5
6
7
8
9
port=82  # 外部访问的端口号,可以改成你想要的端口号,外部访问通过 ip/域名:端口号 即可访问
roott=/ /usr/local/www/SessionTest/ # 应用程序的工作根目录(全路径)
hosts=* #OR your.com,*.your.com 如果为服务器设置了DNS解析,则可以填写解析到服务器的域名,如:www.myweb.com

AppHost={
cmd=dotnet SessionTest.dll; # 命令,启动Asp.Net Core应用要执行的命令
root=/usr/local/www/SessionTest/; # Asp.Net Core应用程序所在的全路径
port=0;
}
1
2
3
4
5
6
port=0;
# Asp.Net Core应用程序所使用的端口号,如果在程序中使用了UsrUrls自定义端口则使用UsrUrls中填写的端口

(不建议使用UsrUrls自定义端口), 在没有使用UsrUrls自定义端口的情况下端口号设置为 0,Jexus会在运行时与

Asp.Net Core进行"协商"具体使用的端口号,避免多个应用分配端口的麻烦和冲突的风险。

反向代理

1
reproxy=/ http://192.168.1.132:5100,http://192.168.1.130:5100

启动/重启 Jexus

当配置文件编辑完成后使用以下命令对Jexus进行 启动/重启

1
2
3
4
5
# 如果已启动 Jexus:
sh /usr/jexus/jws restart

# 如果未启动 Jexus:
sh /usr/jexus/jws start

开启远程访问端口

开放jexus配置的82端口,以供外部访问:

1
firewall-cmd --zone=public --add-port=82/tcp --permanent

命令含义:

1
2
3
4
5
6
7
--zone #作用域

--add-port=82/tcp #添加端口,格式为:端口/通讯协议

--permanent #永久生效,没有此参数重启后失效

firewall-cmd --reload #重启防火墙

参考:

http://www.cnblogs.com/staneee/p/6852559.html

https://www.jexus.org/

https://www.linuxdot.net/bbsfile-3084

Nginx安装

Yum安装方式

1
2
3
4
5
[root@localhost /]# yum install epel-release
[root@localhost /]# yum install nginx
[root@localhost /]# systemctl start nginx #启动
[root@localhost /]# systemctl enable nginx #可用
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.

将nginx 设置为启动系统自动启动nginx

1
[root@localhost /]# echo "/usr/local/nginx/sbin/nginx" >> /etc/rc.local

nginx.conf配置文件

1
2
[root@localhost /]# cd /etc/nginx #定位到nginx安装目录
[root@localhost nginx]# vim nginx.conf #通过vim打开nginx.conf配置文件进行配置

在http节点中,添加配置

1
2
3
4
5
6
7
#设定负载均衡的服务器列表
upstream load_balance_server {
#weigth参数表示权值,权值越高被分配到的几率越大
server 192.168.1.131:5100 weight=5;
server 192.168.1.132:5100 weight=1;
server 192.168.1.130:5100 weight=6;
}

在http->server节点中添加配置:

1
2
3
4
5
6
7
8
9
10
location / {
root html;
index index.aspx index.html index.htm;
#其中load_balance_server 对应着upstream设置的集群名称
proxy_pass http://load_balance_server;
#设置主机头和客户端真实地址,以便服务器获取客户端真实IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

报错

nginx 启动失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost nginx]# service nginx restart
Redirecting to /bin/systemctl restart nginx.service
Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalctl -xe" for details.
[root@localhost nginx]# systemctl status nginx -l
● nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Thu 2017-05-18 09:34:41 CST; 6s ago
Process: 121270 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=1/FAILURE)
Process: 121265 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
Main PID: 117382 (code=exited, status=0/SUCCESS)

May 18 09:34:41 localhost.localdomain systemd[1]: Starting The nginx HTTP and reverse proxy server...
May 18 09:34:41 localhost.localdomain nginx[121270]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
May 18 09:34:41 localhost.localdomain nginx[121270]: nginx: [emerg] bind() to 0.0.0.0:5100 failed (13: Permission denied)
May 18 09:34:41 localhost.localdomain nginx[121270]: nginx: configuration file /etc/nginx/nginx.conf test failed
May 18 09:34:41 localhost.localdomain systemd[1]: nginx.service: control process exited, code=exited status=1
May 18 09:34:41 localhost.localdomain systemd[1]: Failed to start The nginx HTTP and reverse proxy server.
May 18 09:34:41 localhost.localdomain systemd[1]: Unit nginx.service entered failed state.
May 18 09:34:41 localhost.localdomain systemd[1]: nginx.service failed.

权限拒绝,经检查发现是开启selinux 导致的。 直接关闭

getenforce 这个命令可以查看当前是否开启了selinux 如果输出 disabled 或 permissive 那就是关闭了

如果输出 enforcing 那就是开启了 selinux

1、临时关闭selinux

1
2
setenforce 0    ##设置SELinux 成为permissive模式
setenforce 1 ##设置SELinux 成为enforcing模式

2、永久关闭selinux,

修改 /etc/selinux/config 文件

SELINUX=enforcing 改为 SELINUX=disabled

重启机器即可

重启nginx服务

1
service nginx restart

参考:

http://www.cnblogs.com/Andon_liu/p/5353522.html

http://www.cnblogs.com/jingmoxukong/p/5945200.html

安装

官网下载

下载地址:redis-3.2.8.tar.gz

依赖包

1
yum install gcc

编译安装

1
2
3
4
5
6
wget http://download.redis.io/releases/redis-3.2.8.tar.gz # 获取包
tar -zxvf redis-3.2.8.tar.gz
cd redis-3.2.8
cd src
make distclean
make && make install

移动文件夹

移动文件,便于管理:(所有源代码安装的软件都安装在/usr/local下)

创建两个文件夹,bin用于存放命令,etc拥有存放配置文件。

1
2
mkdir -p /usr/local/redis/etc
mkdir -p /usr/local/redis/bin

-p是递归创建。

接下来,将redis-3.2.8文件夹下的redis.conf复制到/usr/local/redis/etc/

并将src目录下的6个命令文件(绿色的),移动到/usr/local/redis/bin/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost src]# mkdir -p /usr/local/redis/etc
[root@localhost src]# mkdir -p /usr/local/redis/bin
[root@localhost src]# cd ..
[root@localhost redis-3.2.8]# ls
00-RELEASENOTES COPYING Makefile redis.conf runtest-sentinel tests
BUGS deps MANIFESTO runtest sentinel.conf utils
CONTRIBUTING INSTALL README.md runtest-cluster src
[root@localhost redis-3.2.8]# mv ./redis.conf /usr/local/redis/etc
[root@localhost redis-3.2.8]# ls
00-RELEASENOTES COPYING Makefile runtest sentinel.conf utils
BUGS deps MANIFESTO runtest-cluster src
CONTRIBUTING INSTALL README.md runtest-sentinel tests
[root@localhost redis-3.2.8]# cd src
[root@localhost src]# mv mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-sentinel redis-server /usr/local/redis/bin/

启动redis服务

1
2
[toor@localhost redis]$ cd bin
[toor@localhost bin]$ ./redis-server

显示结果:

结果

但是,这样做的话,我们并没有使用etc的下的配置文件进行启动(图中红线部分)。

如果希望通过指定的配置文件启动,需要在启动时指定配置文件:

使用ctrl+C终止服务(显示错误,可以重试几次),然后查看redis服务是否终止干净了,之后通过设置配置文件来启动服务

运行:pstree -p | grep redis 发现redis服务已经被终止干净

现在我们带上配置文件 /usr/local/etc/redis.conf 运行redis

1
[toor@localhost bin]$  ./redis-server /usr/local/redis/etc/redis.conf

启动结果:
启动结果

这样启动redis仍然在前台运行;

redis后台运行需要将daemonize由no改为yes

首先在超级权限下打开redis.conf。

1
[root@localhost bin]# vim /usr/local/redis/etc/redis.conf

保存退出,然后再次使用配置文件启动redis-server

1
2
3
4
5
6
7
[root@localhost bin]# ./redis-server /usr/local/redis/etc/redis.conf
[root@localhost bin]# ps -ef|grep redis
root 101598 1 0 17:04 ? 00:00:00 ./redis-server 127.0.0.1:6379
root 101607 101338 0 17:04 pts/0 00:00:00 grep --color=auto redis
[root@localhost bin]# pstree -p|grep redis
|-redis-server(101598)-+-{redis-server}(101601)
| `-{redis-server}(101602)

详细参数说明:

daemonize 如果需要在后台运行,把该项改为yes

pidfile 配置多个pid的地址 默认在/var/run/redis.pid

bind 绑定ip,设置后只接受来自该ip的请求

port 监听端口,默认是6379

loglevel 分为4个等级:debug verbose notice warning

logfile 用于配置log文件地址

databases 设置数据库个数,默认使用的数据库为0

save 设置redis进行数据库镜像的频率。

rdbcompression 在进行镜像备份时,是否进行压缩

dbfilename 镜像备份文件的文件名

Dir 数据库镜像备份的文件放置路径

Slaveof 设置数据库为其他数据库的从数据库

Masterauth 主数据库连接需要的密码验证

Requriepass 设置 登陆时需要使用密码

Maxclients 限制同时使用的客户数量

Maxmemory 设置redis能够使用的最大内存

Appendonly 开启append only模式

Appendfsync 设置对appendonly.aof文件同步的频率(对数据进行备份的第二种方式)

vm-enabled 是否开启虚拟内存支持 (vm开头的参数都是配置虚拟内存的)

vm-swap-file 设置虚拟内存的交换文件路径

vm-max-memory 设置redis使用的最大物理内存大小

vm-page-size 设置虚拟内存的页大小

vm-pages 设置交换文件的总的page数量

vm-max-threads 设置VM IO同时使用的线程数量

Glueoutputbuf 把小的输出缓存存放在一起

hash-max-zipmap-entries 设置hash的临界值

Activerehashing 重新hash

Redis服务端默认连接端口是6379.

mysql 服务端默认连接端口是3306

Mogodb 服务端默认连接端口是27017,还有28017。

在平时,我们往往需要查看6379端口是否被占用。可以用以下命令:

1
2
3
4
5
6
7
8
9
10
11
[toor@localhost bin]$ netstat -tunpl | grep 6379
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN -
[toor@localhost bin]$ sudo netstat -tunpl | grep 6379
[sudo] password for toor:
toor is not in the sudoers file. This incident will be reported.
[toor@localhost bin]$ su root
Password:
[root@localhost bin]# netstat -tunpl | grep 6379
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN 101598/./redis-serv

注意,redis服务需要su权限才能查看,不然只能检查到6379被某个进程占用,但是看不到进程名称。

至此,redis服务已经按照配置文件启动成功!!

客户端登陆

进入 /usr/local/redis/bin/

1
2
3
4
5
6
[root@localhost bin]# redis-cli
127.0.0.1:6379>

[root@localhost bin]# redis-cli -h 192.168.1.131 -p 6379 # 指定主机,端口 -a password #指定密码
192.168.1.131:6379>
[root@localhost bin]# redis-cli -h 192.168.1.131 -p 6379 shutdown # 关闭redis服务

关闭Redis服务

1
2
3
4
5
6
7
[root@localhost bin]# pkill redis-server
[root@localhost bin]# netstat -tunpl | grep 6379
[root@localhost bin]# pstree -p | grep redis
[root@localhost bin]# redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected>

也可以使用/usr/local/redis/bin/redis-cli shutdown,这种方法使用客户端命令redis-cli

1
2
3
4
5
6
7
[root@localhost bin]# ./redis-server /usr/local/redis/etc/redis.conf
[root@localhost bin]# pstree -p | grep redis
|-redis-server(101927)-+-{redis-server}(101929)
| `-{redis-server}(101930)
[root@localhost bin]# /usr/local/redis/bin/redis-cli shutdown
[root@localhost bin]# pstree -p | grep redis
[root@localhost bin]# sudo netstat -tunpl | grep 6379

开启远程访问端口

开启端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
firewall-cmd --zone=public --add-port=6379/tcp --permanent
firewall-cmd --reload #重启防火墙

## 重新查询端口是否开放
firewall-cmd --query-port=6379/tcp

## 查看监听的端口
netstat -lnpt

#关闭6379端口
firewall-cmd --zone=public --remove-port=6379/tcp --permanent

## 查看防火墙所有开放的端口
firewall-cmd --zone=public --list-ports

命令含义:

1
2
3
--zone #作用域
--add-port=6379/tcp #添加端口,格式为:端口/通讯协议
--permanent #永久生效,没有此参数重启后失效

配置密码认证

找到/usr/local/redis/etc/redis.conf文件中

1
requirepass foobared

去掉注释,并且设置密码

重启redis

1
2
3
4
[root@localhost bin]# ./redis-server /usr/local/redis/etc/redis.conf
[root@localhost bin]# redis-cli -h 192.168.1.131 -p 6379 -a toor
192.168.1.131:6379> keys *
(empty list or set)
1
2
3
4
5
6
7
8
[root@localhost bin]# ./redis-server /usr/local/redis/etc/redis.conf
[root@localhost bin]# redis-cli -h 192.168.1.131 -p 6379
192.168.1.131:6379> keys *
(error) NOAUTH Authentication required.
192.168.1.131:6379> auth toor
OK
192.168.1.131:6379> keys *
(empty list or set)

master配置了密码,slave如何配置

若master配置了密码则slave也要配置相应的密码参数否则无法进行正常复制的。

slave中配置文件内找到如下行,移除注释,修改密码即可

1
#masterauth  mstpassword

参考:

http://www.open-open.com/lib/view/open1426468117367.html

centos7/rhel7: 如何重启/停止/启动网络服务

重启网络服务

1
2
3
systemctl retart network.service

systemctl restart network

启动网络服务

1
2
3
systemctl start network.service

systemctl start network

停止网络服务

1
2
3
systemctl stop network.service

systemctl stop network

参考:http://www.osetc.com/archives/1191.html

流程

下载

官网下载网址

找到CentOS7最新下载包,

mongodb-linux-x86_64-rhel70-3.4.4.tgz

官方手册

解压

在终端输入命令,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 解压
tar xzvf mongodb-linux-x86_64-rhel70-3.4.4.tgz

# 移动
mv mongodb-linux-x86_64-rhel70-3.4.4 mongodb-3.4.4

# 创建目录
cd mongodb-3.4.4
mkdir logs
mkdir db

# 创建配置文件
cd bin
vim mongodb.conf

配置文件如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# idae - MongoDB config start - 2017-05-10

# 设置数据文件的存放目录
dbpath = /usr/local/mongodb/mongodb-3.4.4/db

# 设置日志文件的存放目录及其日志文件名
logpath = /usr/local/mongodb/mongodb-3.4.4/logs/mongodb.log

# 设置端口号(默认的端口号是 27017)
port = 27017

# 设置为以守护进程的方式运行,即在后台运行
fork = true

# nohttpinterface = true
nohttpinterface = true
# idae - MongoDB config end - 2016-05-10

参数解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--dbpath 数据库路径(数据文件)
--logpath 日志文件路径
--master 指定为主机器
--slave 指定为从机器
--source 指定主机器的IP地址
--pologSize 指定日志文件大小不超过64M.因为resync是非常操作量大且耗时,最好通过设置一个足够大的oplogSize来避免resync(默认的 oplog大小是空闲磁盘大小的5%)。
--logappend 日志文件末尾添加,即使用追加的方式写日志
--journal 启用日志
--port 启用端口号
--fork 在后台运行
--only 指定只复制哪一个数据库
--slavedelay 指从复制检测的时间间隔
--auth 是否需要验证权限登录(用户名和密码)
--syncdelay 数据写入硬盘的时间(秒),0是不等待,直接写入
--notablescan 不允许表扫描
--maxConns 最大的并发连接数,默认2000
--pidfilepath 指定进程文件,不指定则不产生进程文件
--bind_ip 绑定IP,绑定后只能绑定的IP访问服务(服务器本地IP,非访问服务器客户端IP)

启动mongodb服务

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
# 进入mongodb-3.4.4/bin目录
# 自定义的 mongodb 配置文件方式启动
./mongod --config mongodb.conf

# 停止:
./mongod --config mongodb.conf --shutdown

# 修复模式启动 mongodb
./mongod --repair -f mongodb.conf

# 以参数方式启动(在mongodb-3.4.4/bin目录下):
./mongod --dbpath=/usr/local/mongodb/mongodb-3.4.4/db --logpath=/usr/local/mongodb/mongodb-3.4.4/logs/mongodb.log --fork

# 停止:添加 --shutdown
./mongod --dbpath=/usr/local/mongodb/mongodb-3.4.4/db --logpath=/usr/local/mongodb/mongodb-3.4.4/logs/mongodb.log --fork --shutdown

# 添加认证: --auth
# 连接
./mongo 192.168.1.131:27017/admin -u root1 -p root1

# 如果报如下错误:

ERROR: child process failed, exited with error number 1
# 很可能是 mongodb.conf 中配置的路径不一致问题;

# 如果报如下错误:

ERROR: child process failed, exited with error number 100
# 很可能是没有正常关闭导致的,那么可以删除 mongod.lock 文件
# number 100 :端口问题

查看 mongodb 进程:

ps aux | grep mongodb

查看 mongodb 服务的运行日志:

tail -200f /usr/local/mongodb/mongodb-3.4.4/logs/mongodb.log

检查端口是否已被启动:

netstat -lanp | grep 27017

杀死 mongodb 进程,即可关闭 mongodb 服务:(禁止使用,容易出现问题)

1
kill -15 PID

连接错误

1.若数据库出现如上不能连接的原因,可能是data目录下的mongod.lock文件问题,可以用如下命令修复:
./bin/mongod --repair

2.直接删除mongod.lock
rm -f /usr/local/mongodb/db/mongod.lock

3.然后再启动 mongodb 服务:
./mongod --config mongodb.conf

4.如果以上两部依然解决不掉,则是路径文件,我们可以删除 /usr/local/mongodb/mongodb-3.4.4/db 目录及其子目录,并采用绝对路径的方式:

1
./mongod /usr/local/mongodb/mongodb-3.4.4/bin/mongod --dbpath=/usr/local/mongodb/mongodb3.4.4/db --logpath=/usr/local/mongodb/mongodb-3.4.4/logs/mongodb.log --fork

将 mongodb 服务加入到自启动文件中:

vi /etc/rc.local

在文件末尾追加如下命令:

/usr/local/mongodb/mongodb-3.4.4/bin/mongod --config mongodb.conf

保存并退出:
:wq!

/usr/local/mongodb/mongodb3.4.4/bin/ 目录中,键入如下命令,打开一个 mongodb 的客户端程序,即打开一个 mongodb 的 shell 客户端,这个 shell 客户端同时也是一个 JavaScript 编辑器,即可用输入任何的 JavaScript 脚本:

./mongo

在浏览器中输入 IP:27017,如:

http://127.0.0.1:27017/

开启远程访问端口

开启端口:

firewall-cmd --zone=public --add-port=27017/tcp --permanent

命令含义:
–zone #作用域
–add-port=27017/tcp #添加端口,格式为:端口/通讯协议
–permanent #永久生效,没有此参数重启后失效

重启防火墙:

firewall-cmd --reload

安全和认证

限制特定IP地址访问(MongoDB服务器IP地址)

1
./mongod --bind_ip 192.168.1.131 --port 27017 --dbpath /usr/local/mongodb/mongodb-3.4.4/db --logpath /usr/local/mongodb/mongodb-3.4.4/logs/mongodb.log --fork

设置监听端口,并且需要在防火墙配置

1
--port 27017

设置登录用户名和口令

启用mongodb授权认证的方法:

1、以–auth 启动mongod

2、在配置文件mongod.conf 中加入 auth = true

添加用户:

1
2
3
4
5
6
7
db.createUser(
... {
... user: "root1",
... pwd: "root1",
... roles: [ { role: "__system", db: "admin" } ]
... }
... )

重新登录:
./mongo 192.168.1.131:27017 -u root1 -p root1

展示角色:
show roles

获取用户:
db.getUsers();

参考网址:

http://www.linuxidc.com/Linux/2016-06/132675.htm

CSharp lock锁深入理解

定义

临界区(Critical Section)

临界区是一段在同一时候只被一个线程进入/执行的代码块。

Lock关键字

将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。此语句的形式如下:

1
2
3
4
5
6
Object thisLock = new Object();

lock (thisLock)
{
// Critical code section
}
  • lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

Lock使用准则

Lock 调用块开始位置的 Enter 和块结束位置的 Exit。

通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock (“myLock”) 违反此准则:

  • 如果实例可以被公共访问,将出现 lock (this) 问题。

  • 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。

  • 由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现lock(“myLock”) 问题。

最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。

针对上述三点原则我们用具体的代码来加深理解

为什么不要Lock值类型

为什么不能lock值类型,比如lock(1)呢?让我们对上面的实例代码进行修改,将lock(thisLock)改为Lock(1),编译器时会出现int不是Lock语句要求的引用对象

编译失败

lock本质上Monitor.Enter,Monitor.Enter会使值类型装箱,每次lock的是装箱后的对象。lock其实是类似编译器的语法糖,因此编译器直接限制住不能lock值类型。

不要使用lock((object)1)

经lock(thisLock)改为Lock((object)1)编译可以成功,说明代码块并没有锁.究其原因是因为Lock(对象)中,对象为引用类型,其是通过判断object.ReferenceEquals((object)1, (object)1)始终返回false(因为每次装箱后都是不同对象),也就是说每次都会判断成未申请互斥锁,这样在同一时间,别的线程照样能够访问里面的代码,达不到同步的效果。

不要使用Lock(this)

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace TestLock
{
public class TestLockThis
{
private bool deadlocked = true;
private static readonly object lockobject = new object();

//这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
public void LockMethod(object o)
{
//lock (this)
lock (lockobject)
{
while (deadlocked)
{
deadlocked = (bool)o;
Console.WriteLine("I am locked ");
Thread.Sleep(500);
}
}
}

//所有线程都可以同时访问的方法
public void NotLockMethod()
{
Console.WriteLine("I am not locked ");
}

}
}
static void Main(string[] args)
{
TestLockThis TestLockThis = new TestLockThis();

//在t1线程中调用LockMe,并将deadlock设为true(将出现死锁)
Thread t1 = new Thread(TestLockThis.LockMethod);
t1.Start(true);
Thread.Sleep(100);

//在主线程中lock c1
lock (TestLockThis)
{
//调用没有被lock的方法
TestLockThis.NotLockMethod();
//调用被lock的方法,并试图将deadlock解除
TestLockThis.LockMethod(false);
}

Console.Read();
}

在t1线程中,LockMethod调用了lock(this), 也就是Main函数中的c1,这时候在主线程中调用lock(TestLockThis)时,因为TestLockThis已经被锁定,所以必须要等待t1中的lock块执行完毕之后才能访问锁定的代码,即lock块中的所有操作都无法完成,于是我们看到连TestLockThis.NotLockMethod()都没有执行。

Lock为什么不要Lock(null对象)

使用Lock(null),会抛出异常

Lock 本质

Lock 关键字就是用Monitor 类来实现的。例如:

1
2
3
4
lock(x)
{
DoSomething();
}

这等效于:

1
2
3
4
5
6
7
8
9
10
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
DoSomething();
}
finally
{
System.Threading.Monitor.Exit(obj);
}

使用 lock 关键字通常比直接使用 Monitor 类更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放监视器。这是通过 finally 关键字来实现的,无论是否引发异常它都执行关联的代码块。

Lock 对象推荐为只读静态对象

1
private static readonly object obj = new object();

为什么要设置成只读的呢?这是因为如果在lock代码段中改变obj的值,其它线程就畅通无阻了,因为互斥锁的对象变了,object.ReferenceEquals必然返回false。

更准确的说,如果lock(object),object是静态的。那么说要该类的实例请求的都是一个object,所有实例执行的lock的时候只有一个能running。其他都必须等等。
lock(this)则表示锁住当前实例,实例内的所有线程必须等待,同一类的其他实例则可以有一个线程runing.

简单说lock(static objcet)在所有实例里锁住这段代码。
lock(this)锁住当前实例的这段代码。

参考:

http://www.cnblogs.com/chengqscjh/archive/2010/12/12/1903784.html

CSharp中equal与==的区别

C#中,判断相等有两种方式,一种是传统的==操作,一种是object提供的Equals方法。

二者的区别在于:

  • 一、==操作符判断的是堆栈中的值,Equlas判断的是堆中的值。

    C#提供值类型和引用类型,值类型存储在栈上,故用==判断是直接判断其值是否相等,因为值类型不存在堆中的数据,因此值类型的Equals也是判断数据。
    即,对于值类型而言,==与Equals相同,均是判断其值是否相等。

    对于引用类型而言,其栈中存储的是对象的地址,那么==就是比较两个地址是否相等,即是否指向同一个对象;Equals函数则是比较两个对象在堆中的数据
    是否一样,即两个引用类型是否是对同一个对象的引用。

  • 二、String类型特殊

    String类型虽然是引用类型,但是对String对象的赋值却按照值类型操作。

    例如:

    1
    2
    String s1="hello";
    String s2="hello";

    对s2初始化的时候,并没有重新开辟内存,而是直接将其地址指向s1的内容“hello”。这样一来,string类型虽然是引用类型,但是其==操作和Equals操作
    都是一样的,均比较值是否相等。

  • 三、与GetHashCode()的关系

    若两对象Equals相等,那么其GetHashCode()必定相等;但是反过来,若GetHashCode()相等,那么这两个对象Equals方法比较结果不一定相同。

    (为了获取最佳性能,hash函数为对象内容生成的数字是随机分布的,这就意味着,内容不同的对象,有可能生成的数字是一样的,但可以认为这种概率非常小)。

    下面示例说明:

    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
    57
    58
    59
    60
    61
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace ConsoleApplication1
    {
    class People
    {
    private string name;

    public string Name
    {
    get { return name; }
    set { name = value; }
    }

    public People(string name)
    {
    this.name = name;
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    string a = "hello";
    string b = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
    Console.WriteLine(a == b);
    Console.WriteLine(a.Equals(b));
    Console.WriteLine("\n");

    Int32 m = 3;
    Int32 n = 3;
    Console.WriteLine(n == m);
    Console.WriteLine(n.Equals(m));
    Console.WriteLine("\n");

    object g = a;
    object h = b;
    Console.WriteLine(g == h);
    Console.WriteLine(g.Equals(h));
    Console.WriteLine(g.GetHashCode() + " " + h.GetHashCode());
    Console.WriteLine("\n");

    People p1 = new People("Jimmy");
    People p2 = new People("Jimmy");
    Console.WriteLine(p1 == p2);
    Console.WriteLine(p1.Equals(p2));
    Console.WriteLine(p1.GetHashCode() + " " + p2.GetHashCode());
    Console.WriteLine("\n");

    People p3 = new People("Jimmy");
    People p4 = p3;
    Console.WriteLine(p3 == p4);
    Console.WriteLine(p3.Equals(p4));

    }
    }
    }

    运行结果:
    运行结果

hexo文档

命令

  • hexo:查看帮助
  • hexo server :本地启动命令,可以在本地浏览器浏览
    • Ctrl+C 停止Server
  • hexo new “My New Post” : 新建文档
  • hexo clean :清除旧文档
  • hexo generate :生成
  • hexo deploy :部署

部署配置

生成 SSH KEY

1
2
3
git config --global user.name " GitHub 用户名 "
git config --global user.email " GitHub 邮箱 "
ssh-keygen -t rsa -C " 邮箱地址 "

1,SSH KEY 生成之后会默认保存在 C:/Users/电脑名用户名/.ssh 目录中,打开这个目录,打开 id_rsa.pub 文件,复制全部内容,即复制密钥

2,打开 GitHub ,依次点击 头像–>Settings–>SSH and GPG keys–>Add SSH key,将复制的密钥粘贴到 key 输入框,最后点击 Add Key ,SSH KEY 配置成功

添加图片

  • 使用本地路径:在hexo/source目录下新建一个img文件夹,将图片放入该文件夹下,插入图片时链接即为/img/图片名称
    • 例如:![配置](/img/mysql-workbench.png)

站内文章链接

官方有标签插件实现站内文章链接,如下:

post_link 官方文档

1
{% post_link 'hexo blog' %}

其它

首页隐藏文章详细内容:

1
<!--more-->

段落缩进两个字符:

1
&emsp;&emsp;

ILGenerator

ILGenerator:生成IL代码

获取ILGenerator类的实例方法:

ConstructorBuilder.GetILGenerator方法
DynamicMethod.GetILGenerator 方法
MethodBuilder.GetILGenerator 方法

上面涉及到了在Emit中能够动态生成方法的三种途径,

ConstructorBuilder类用来配置的构造函数,构造函数内部的IL要使用它的

GetILGenerator方法返回的ILGenerator类发出。

DynamicMethod类,是在当前运行上下文环境中动态生成方法的类,使用该类不必

事先创建程序集、模块和类型,同样发出其内部的IL使用DynamicMethod.GetILGenerator

方法返回的ILGenerator类实例.

最终实现的调用效果:

1
2
3
4
5
6
7
8
Mock<IAssessmentAopAdviceProvider> mocker = new Mock<IAssessmentAopAdviceProvider>();

mocker.Setup(t => t.Before(3)).Returns("Hello World!");

Console.WriteLine(mocker.Obj.Before(2));

Console.Read();

接收一个接口,初始化一个Mock类的实例,然后通过Setup和Returns扩展方法设定实现

该接口的实例在指定方法上的返回值。这里我们先不考虑对不同参数的处理逻辑。

Mock类定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Mock<T> where T : IAssessmentAopAdviceProvider
{
public T Obj
{
get; set;
}

public SetupContext Contex { get; set; }

public Mock()
{
}
}

Mock类的Obj属性是特定接口的实例。Contex属性是上下文信息

Contex类定义如下:

1
2
3
4
public class SetupContext
{
public MethodInfo MethodInfo { get; set; }
}

只包含一个MethodInfo属性

扩展方法:

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
public static class MockExtention
{
public static Mock<T> Setup<T>(this Mock<T> mocker, Expression<Action<T>> expression) where T : IAssessmentAopAdviceProvider
{
mocker.Contex = new SetupContext();

mocker.Contex.MethodInfo = expression.ToMethodInfo();

return mocker;
}

public static void Returns<T>(this Mock<T> mocker, object returnValue) where T : IAssessmentAopAdviceProvider
{
if (mocker.Contex != null && mocker.Contex.MethodInfo != null)
{
//这里为简单起见,只考虑IAssessmentAopAdviceProvider接口
mocker.Obj = (T)AdviceProviderFactory.GetProvider(mocker.Contex.MethodInfo.Name, (string)returnValue);
}
}

public static MethodInfo ToMethodInfo(this LambdaExpression expression)
{
var memberExpression = expression.Body as System.Linq.Expressions.MethodCallExpression;

if (memberExpression != null)

{
return memberExpression.Method;
}

return null;
}

}

Setup是Mock类的扩展方法,配置要Mock的方法信息;

Returns扩展方法则调去对应的工厂获取接口的实例。

ToMethodInfo是LambdaExpression扩展方法,该方法从Lambda表达式中获取MethodInfo.

对应的工厂方法直接返回IAssessmentAopAdviceProvider接口实例。

在构造方法中初始化assemblyBuilder和moduleBuilder
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static readonly AssemblyName assemblyName = new AssemblyName("EmitTest.MvcAdviceProvider");

private static AssemblyBuilder assemblyBuilder;

private static ModuleBuilder moduleBuilder;

static AdviceProviderFactory()
{
assemblyName.Version = new Version("1.0.0.0");

assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProviderModule", "test.dll", true);
}

CreateInstance方法负责创建类型和方法的实现:

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
private static IAssessmentAopAdviceProvider CreateInstance(string instanceName, string methodName, string returnValue)
{
TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider.MvcAdviceProviderType", TypeAttributes.Public, typeof(object), new Type[] { typeof(IAssessmentAopAdviceProvider) });

// typeBuilder.AddInterfaceImplementation(typeof(IAssessmentAopAdviceProvider));

MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Virtual, typeof(string), new Type[] { typeof(int) });

beforeMethodBuilder.DefineParameter(1, ParameterAttributes.None, "value");

ILGenerator generator1 = beforeMethodBuilder.GetILGenerator();

LocalBuilder local1 = generator1.DeclareLocal(typeof(string));

local1.SetLocalSymInfo("param1");

generator1.Emit(OpCodes.Nop);

generator1.Emit(OpCodes.Ldstr, returnValue);

generator1.Emit(OpCodes.Stloc_0);

generator1.Emit(OpCodes.Ldloc_0);

generator1.Emit(OpCodes.Ret);

Type providerType = typeBuilder.CreateType();

assemblyBuilder.Save("test.dll");

IAssessmentAopAdviceProvider provider = Activator.CreateInstance(providerType) as IAssessmentAopAdviceProvider;

return provider;
}

在CreateInstance方法中,首先定义类型:

1
TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider.MvcAdviceProviderType", TypeAttributes.Public, typeof(object), new Type[] { typeof(IAssessmentAopAdviceProvider) });

第三个和第四个参数,分别是该类型大的基类型和实现的接口列表。

1
MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Virtual, typeof(string), new Type[] { typeof(int) });

DefineMethod方法中,传人的第一个参数是方法的名称,第二个参数是访问类型,第三个参数是修饰符,第四个参数是方法的参数类型列表。这里需要注意第二个参数,也就是方法的修饰符,因为接口中的方法定义都是virtual的,所以在实现接口的时候,方法也必须声明为MethodAttributes.Virtual。

定义方法的参数,使用MethodBuilder.DefineParameter方法。

1
beforeMethodBuilder.DefineParameter(1, ParameterAttributes.None, "value");

DefineParameter方法的第一参数指定当前定义的参数在方法参数列表中的顺序,从1开始,如果设置为0则代表方法的返回参数。第二个参数是设置参数的特性,如输入参数,输出参数等等。第三个参数是指定该参数的名称。

方法定义完毕,接下来就是发出Opcode,返回一个指定的字符串。先获取ILGenerator实例,如下:

1
ILGenerator generator1 = beforeMethodBuilder.GetILGenerator();

我们若想返回一个字符串,必须先为该字符串声明一个局部变量,可以使用LocalBuilder.DeclareLocal方法,如下:

1
2
3
LocalBuilder local1 = generator1.DeclareLocal(typeof(string));

local1.SetLocalSymInfo("param1");

ocal1.SetLocalSymInfo(“param1”)指定局部变量的名称。

需要注意,如果模块定义时不允许发出符号信息,是无法使用SetLocalSymInfo方法的,AdviceProviderFactory的构造函数中,我们定义模块的代码

1
moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProviderModule", "test.dll",true);

最后一个参数是指定是否允许发出符号信息的。

发出的Opcode:

1
2
3
4
5
6
7
8
9
generator1.Emit(OpCodes.Nop);

generator1.Emit(OpCodes.Ldstr, returnValue);

generator1.Emit(OpCodes.Stloc_0);

generator1.Emit(OpCodes.Ldloc_0);

generator1.Emit(OpCodes.Ret);

第一条指令不执行任何操作。

第二条指令加载一个字符串到计算堆栈中。

第三条指令赋值计算堆栈顶部的数据到局部变量列表中的第一个变量。

第四条指令加载局部变量列表中的第一个变量的数据引用到计算堆栈。

第五条指令方法返回。

整个Emit的过程结束了,接下来要创建实例:

1
2
3
4
5
Type providerType = typeBuilder.CreateType();

assemblyBuilder.Save("test.dll");

IAssessmentAopAdviceProvider provider = Activator.CreateInstance(providerType) as IAssessmentAopAdviceProvider;

控制台调用:

1
2
3
4
5
6
7
Mock<IAssessmentAopAdviceProvider> mocker = new Mock<IAssessmentAopAdviceProvider>();

mocker.Setup(t => t.Before(3)).Returns("Hello World!");

Console.WriteLine(mocker.Obj.Before(2));

Console.Read();

代码

参考:

http://www.cnblogs.com/xuanhun/archive/2012/06/22/2558698.html

动态创建完整的程序集

创建程序集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace EmitTest
{
internal class Program
{
private static void Main(string[] args)
{
AssemblyName assemblyName = new AssemblyName("EmitTest.MvcAdviceProvider");

AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
}
}
}

AppDomain.CurrentDomain.DefineDynamicAssembly方法返回一个AssemblyBuilder实例。

其中,第一个参数是AssemblyName实例,是程序集的唯一标识;第二个参数AssemblyBuilderAccess.Run表明该程序集只能用来执行代码,

不能被持久保存。AssemblyBuilderAccess还有如下选项:

AssemblyBuilderAccess.ReflectionOnly:程序集只能在反射上下文中执行。

AssemblyBuilderAccess.RunAndCollect:程序集可以运行和垃圾回收。

AssemblyBuilderAccess.RunAndSave:程序集可以执行代码而且被持久保存。

AssemblyBuilderAccess.Save:程序集是持久化的,保存之前不可以执行代码。

创建了程序集之后,我们继续向程序集中添加模块。

向程序集添加模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace EmitTest
{
internal class Program
{
private static void Main(string[] args)
{
AssemblyName assemblyName = new AssemblyName("EmitTest.MvcAdviceProvider");

AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProvider");
}
}
}

AssemblyBuilder.DefineDynamicModule方法来创建模块,该方法共有三个重载,如下表所示:

DefineDynamicModule(String)定义指定名称的模块。

DefineDynamicModule(String, Boolean)定义指定名称的模块,并指定是否发出符号信息。

DefineDynamicModule(String, String)定义持久模块。用给定名称定义将保存到指定文件路径的模块。不发出符号信息。

DefineDynamicModule(String, String, Boolean)定义持久模块,并指定模块名称、用于保存模块的文件名,同时指定是否使用默认符号编写器发出符号信息。

定义类型

我们要定义的类型必须继承并实现IAssessmentAopAdviceProvider接口

1
2
TypeBuilder typeBuilder = moduleBuilder.DefineType("EmitTest.MvcAdviceProvider", TypeAttributes.Public,
typeof(object), new Type[] { typeof(IAssessmentAopAdviceProvider) });

上述代码中mb.DefineType方法返回一个TypeBuilder实例,该方法有6个重载方法,这里采用的方法有四个参数,

第一个参数是类型名称,第二个参数的TypeAttributes枚举是类型的访问级别和类型类别等其他信息,第三个参数

是类型继承的基类,第四个参数是类型实现的接口。其他重载函数的说明如下(引自MSDN):

DefineType(String)在此模块中用指定的名称为私有类型构造 TypeBuilder。

DefineType(String, TypeAttributes)在给定类型名称和类型特性的情况下,构造 TypeBuilder。

DefineType(String, TypeAttributes, Type)在给定类型名称、类型特性和已定义类型扩展的类型的情况下,构造 TypeBuilder。

DefineType(String, TypeAttributes, Type, Int32)在给定类型名称、特性、已定义类型扩展的类型和类型的总大小的情况下,构造 TypeBuilder。

DefineType(String, TypeAttributes, Type, PackingSize)在给定类型名称、特性、已定义类型扩展的类型和类型的封装大小的情况下,构造 TypeBuilder。

DefineType(String, TypeAttributes, Type, Type[])在给定类型名称、特性、已定义类型扩展的类型和已定义类型实现的接口的情况下,构造 TypeBuilder。

DefineType(String, TypeAttributes, Type, PackingSize, Int32)在给定类型名称、特性、已定义类型扩展的类型,已定义类型的封装

通过TypeBuilder,可以使用TypeBuilder.DefineField来定义字段,使用TypeBuilder.DefineConstructor来定义构造函数,

使用TypeBuilder.DefineMethod来定义方法,并使用TypeBuilder.DefineEvent来定义事件等,总之可以定义类型里的任何成员。

这里我们只需要定义方法

1
2
3
MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod("Before", MethodAttributes.Public, typeof(object), new Type[] { typeof(object) });

MethodBuilder afterMethodBuilder = typeBuilder.DefineMethod("After", MethodAttributes.Public, typeof(object), new Type[] { typeof(object), typeof(object) });

在上面的代码中,使用TypeBuilder.DefineMethod 方法来创建MethodBuilder对象。该方法有5个重载,如下表(引自MSDN):

DefineMethod(String, MethodAttributes)使用指定的名称和方法特性向类型中添加新方法。

DefineMethod(String, MethodAttributes, CallingConventions)使用指定名称、方法特性和调用约定向类型中添加新方法。

DefineMethod(String, MethodAttributes, Type, Type[])使用指定的名称、方法特性和调用约定向类型中添加新方法。

DefineMethod(String, MethodAttributes, CallingConventions, Type, Type[])使用指定的名称、方法特性、调用约定和方法签名向类型中添加新方法。

DefineMethod(String, MethodAttributes, CallingConventions, Type, Type[], Type[], Type[], Type[][], Type[][])使用指定的名称、方法特性、调用约定、方法签名和自定义修饰符向类型中添加新方法。

完整代码:

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
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace EmitTest
{
internal class Program
{
private static void Main(string[] args)
{
AssemblyName assemblyName = new AssemblyName("EmitTest.MvcAdviceProvider");

AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProvider");

TypeBuilder typeBuilder = moduleBuilder.DefineType("EmitTest.MvcAdviceProvider", TypeAttributes.Public,

typeof(object), new Type[] { typeof(IAssessmentAopAdviceProvider) });

MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod("Before", MethodAttributes.Public, typeof(object), new Type[] { typeof(object) });

MethodBuilder afterMethodBuilder = typeBuilder.DefineMethod("After", MethodAttributes.Public, typeof(object), new Type[] { typeof(object), typeof(object) });

TestType(typeBuilder);
}

private static void TestType(TypeBuilder typeBuilder)
{
Console.WriteLine(typeBuilder.Assembly.FullName);

Console.WriteLine(typeBuilder.Module.Name);

Console.WriteLine(typeBuilder.Namespace);

Console.WriteLine(typeBuilder.Name);

Console.Read();
}
}
}

参考:

http://www.cnblogs.com/xuanhun/archive/2012/06/03/2532922.html