0%

NFS双机热备高可用环境 - 运维笔记 - 散尽浮华 - 博客园

Excerpt

NFS高可用目的 部署NFS双机热备高可用环境,用作K8S容器集群的远程存储,实现K8S数据持久化。 NFS高可用思路 NFS + Keepalived 实现高可用,防止单点故障。 Rsync+Inotify 实现主备间共享数据进行同步。 环境准备 技术要求 两个NFS节点机器的配置要一致 keep


NFS高可用目的

部署NFS双机热备高可用环境,用作K8S容器集群的远程存储,实现K8S数据持久化。

NFS高可用思路

NFS + Keepalived 实现高可用,防止单点故障。

Rsync+Inotify 实现主备间共享数据进行同步。

环境准备

技术要求

  • 两个NFS节点机器的配置要一致
  • keepalived监控nfs进程,master的nfs主进程宕掉无法启动时由slave的nfs接管继续工作。
  • k8s数据备份到slave,同时master和slave数据用rsync+inotify实时同步,保证数据完整性。
  • 生产环境下,最好给NFS共享目录单独挂载一块硬盘或单独的磁盘分区。

关闭两台节点机的防火墙和Selinux

1

2

3

4

5

6

7

8

9

10

11

12

13

14

关闭防火墙

not running

关闭selinux

SELINUX=disabled

Disabled

NFS高可用部署记录

一、安装部署NFS服务(Master和Slave两机器同样操作)

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

1)安装nfs

2)创建nfs共享目录

3)编辑export文件,运行k8s的node节点挂载nfs共享目录

这里可以使用node节点的ip网段进行挂载配置

也可以直接使用node节点的具体ip(一个ip配置一行)进行挂载配置

/data/k8s_storage 172.16.60.0/24(rw,sync,no_root_squash)

4)配置生效

5)查看生效

6)启动rpcbind、nfs服务

7)查看 RPC 服务的注册状况

8)showmount测试

Master节点测试

Export list for 172.16.60.235:

/data/k8s_storage 172.16.60.0/24

Slave节点测试

Export list for 172.16.60.236:

/data/k8s_storage 172.16.60.0/24

或者到ks的任意一个node节点上手动尝试挂载NFS,看是否挂载成功:

[root@k8s-node01 ~]

[root@k8s-node01 ~]

[root@k8s-node01 ~]

[root@k8s-node01 ~]

[root@k8s-node01 ~]

[root@k8s-node01 ~]

二、安装部署keepalived(Master和Slave两机器同样操作)

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

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

1)安装keepalived

2)Master节点的keepalived.conf配置

这里特别需要注意:

一定要设置keepalived为非抢占模式,如果设置成抢占模式会在不断的切换主备时容易造成NFS数据丢失。

! Configuration File for keepalived

global_defs {

    router_id master  

}

vrrp_script chk_nfs {

    script "/etc/keepalived/nfs_check.sh"   

    interval 2

    weight -20  

}

vrrp_instance VI_1 {

    state BACKUP   

    interface eth0 

    virtual_router_id 51

    priority 100   

    advert_int 1

    nopreempt      

    authentication {

        auth_type PASS

        auth_pass 1111

    }  

    track_script {

        chk_nfs

    }

    virtual_ipaddress {

        172.16.60.244     

    }

}

3)Slave节点的keepalived.conf配置

只需将priority参数项修改为80,其他配置的和master节点一样,脚本也一样。

! Configuration File for keepalived

global_defs {

    router_id master  

}

vrrp_script chk_nfs {

    script "/etc/keepalived/nfs_check.sh"   

    interval 2

    weight -20  

}

vrrp_instance VI_1 {

    state BACKUP   

    interface eth0 

    virtual_router_id 51

    priority 80  

    advert_int 1

    nopreempt      

    authentication {

        auth_type PASS

        auth_pass 1111

    }  

    track_script {

        chk_nfs

    }

    virtual_ipaddress {

        172.16.60.244     

    }

}

4)编辑nfs_check.sh监控脚本

#!/bin/bash

A=`ps -C nfsd --no-header | wc -l`

if [ $A -eq 0 ];then

        systemctl restart nfs-server.service

        sleep 2

        if [ `ps -C nfsd --no-header| wc -l` -eq 0 ];then

            pkill keepalived

        fi

fi

设置脚本执行权限

5)启动keepalived服务

查看服务进程是否启动

6)检查vip是否存在

在两台节点机器上执行"ip addr"命令查看vip,其中会在一台机器上产生vip地址。

    inet 172.16.60.244/32 scope global eth0

测试vip地址要能被ping通 

PING 172.16.60.244 (172.16.60.244) 56(84) bytes of data.

64 bytes from 172.16.60.244: icmp_seq=1 ttl=64 time=0.063 ms

64 bytes from 172.16.60.244: icmp_seq=2 ttl=64 time=0.042 ms

64 bytes from 172.16.60.244: icmp_seq=3 ttl=64 time=0.077 ms

7)keepalived故障测试

停掉vip所在的Master节点机器上的keepalived服务后,发现vip会自动飘移到另一台Backup机器上才算测试成功。

当该Master节点的keepalived服务重新启动后,vip不会重新飘移回来。因为keepalived采用了非抢占模式。

如果keepalived设置为抢占模式,vip会在Master节点的keepalived重启恢复后自动飘回去,

但是这样一直来回切换可能会造成NFS数据不完整,因为这里必须设置成非抢占模式。

由于配置了nfs的nfs_check.sh监控脚本,所以当其中一台节点机器上的NFS服务宕停后会自动重启NFS。

如果NFS服务重启失败,则会自动关闭该节点机器上的keepalived服务,如果该节点有vip则会自动飘移到另外一台节点上。

三、安装部署Rsync+Inofity(Master和Slave两机器都要操作)

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

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

1)安装rsync和inotify

2)Master节点机器配置rsyncd.conf

uid = root

gid = root

use chroot = 0

port = 873

hosts allow = 172.16.60.0/24 

max connections = 0

timeout = 300

pid file = /var/run/rsyncd.pid

lock file = /var/run/rsyncd.lock

log file = /var/log/rsyncd.log

log format = %t %a %m %f %b

transfer logging = yes

syslog facility = local3

[master_web]

path = /data/k8s_storage

comment = master_web

ignore errors

read only = no  

list = no

auth users = rsync 

secrets file = /etc/rsyncd.passwd 

编辑密码和用户文件(格式为"用户名:密码"

rsync:123456

编辑同步密码(注意这个文件和上面的密码和用户文件路径不一样)

该文件内容只需要填写从服务器的密码,例如这里从服务器配的用户名密码都是rsync:123456,则主服务器则写123456一个就可以了

123456

设置文件执行权限

启动服务

检查rsync服务进程是否启动

3)Slave节点机器配置rsyncd.conf

就把master主机/etc/rsyncd.conf配置文件里的[master_web]改成[slave_web]

其他都一样,密码文件也设为一样

uid = root

gid = root

use chroot = 0

port = 873

hosts allow = 172.16.60.0/24

max connections = 0

timeout = 300

pid file = /var/run/rsyncd.pid

lock file = /var/run/rsyncd.lock

log file = /var/log/rsyncd.log

log format = %t %a %m %f %b

transfer logging = yes

syslog facility = local3

[slave_web]

path = /data/k8s_storage

comment = master_web

ignore errors

read only = no

list = no

auth users = rsync

secrets file = /etc/rsyncd.passwd

编辑密码和用户文件(格式为"用户名:密码"

rsync:123456

编辑同步密码

123456

设置文件执行权限

启动服务

检查rsync服务进程是否启动

4)手动验证下Master节点NFS数据同步到Slave节点

1

2

3

4

5

6

7

8

9

10

11

12

13

在Master节点的NFS共享目录下创建测试数据

a  b  test

手动同步Master节点的NFS共享目录数据到Slave节点的NFS共享目录下

到Slave节点查看

a  b  test

上面rsync同步命令说明:

  • /data/k8s_storage/ 是同步的NFS共享目录
  • rsync@172.16.60.236::slave_web
  • rsync 是Slave节点服务器的/etc/rsyncd.passwd文件中配置的用户名
  • 172.16.60.236为Slave节点服务ip
  • slave_web 为Slave服务器的rsyncd.conf中配置的同步模块名
  • --password-file=/opt/rsyncd.passwd 是Master节点同步到Slave节点使用的密码文件,文件中配置的是Slave节点服务器的/etc/rsyncd.passwd文件中配置的密码

5)设置Rsync+Inotify自动同步

这里需要注意:不能设置Master和Slave节点同时执行rsync自动同步,即不能同时设置双向同步。因为Master节点将数据同步到Slave节点,如果Slave节点再将数据同步回到Master节点,这个就矛盾了。所以需要确保只有一方在执行自动同步到另一方的操作。方式就是判断当前节点服务器是否存在VIP,如存在VIP则自动同步数据到另一台节点上。如不存在VIP则不执行自动同步操作。

+++++++    Master节点服务器操作    +++++++

编写自动同步脚本/opt/rsync_inotify.sh

1

2

3

4

5

6

7

8

9

10

11

12

13

#!/bin/bash

host=172.16.60.236

src=/data/k8s_storage/

des=slave_web

password=/opt/rsyncd.passwd

user=rsync

inotifywait=/usr/bin/inotifywait

$inotifywait -mrq --timefmt '%Y%m%d %H:%M' --format '%T %w%f%e' -e modify,delete,create,attrib $src \

| while read files ;do

 rsync -avzP --delete --timeout=100 --password-file=${password} $src $user@$host::$des

 echo "${files} was rsynced" >>/tmp/rsync.log 2>&1

done

编写VIP监控脚本/opt/vip_monitor.sh

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

#!/bin/bash

VIP_NUM=`ip addr|grep 244|wc -l`

RSYNC_INOTIRY_NUM=`ps -ef|grep /usr/bin/inotifywait|grep -v grep|wc -l`

if [ ${VIP_NUM} -ne 0 ];then

   echo "VIP在当前NFS节点服务器上" >/dev/null 2>&1

   if [ ${RSYNC_INOTIRY_NUM} -ne 0 ];then

      echo "rsync_inotify.sh脚本已经在后台执行中" >/dev/null 2>&1

   else

      echo "需要在后台执行rsync_inotify.sh脚本" >/dev/null 2>&1

      nohup sh /opt/rsync_inotify.sh &

  fi

else

   echo "VIP不在当前NFS节点服务器上" >/dev/null 2>&1

   if [ ${RSYNC_INOTIRY_NUM} -ne 0 ];then

      echo "需要关闭后台执行的rsync_inotify.sh脚本" >/dev/null 2>&1

      ps -ef|grep rsync_inotify.sh|grep -v grep|awk '{print $2}'|xargs kill -9

      ps -ef|grep inotifywait|grep -v grep|awk '{print $2}'|xargs kill -9

   else

      echo "rsync_inotify.sh脚本当前未执行" >/dev/null 2>&1

   fi

fi

编写持续执行脚本/opt/rsync_monit.sh

1

2

3

4

5

#!/bin/bash

while [ "1" = "1" ]

do

  /bin/bash -x /opt/vip_monitor.sh >/dev/null 2>&1

done

后台运行脚本

设置rsync_monit.sh脚本的开机启动

+++++++    Slave节点服务器操作    +++++++    

脚本名为/opt/rsync_inotify.sh,内容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

#!/bin/bash

host=172.16.60.235

src=/data/k8s_storage/

des=master_web

password=/opt/rsyncd.passwd

user=rsync

inotifywait=/usr/bin/inotifywait

$inotifywait -mrq --timefmt '%Y%m%d %H:%M' --format '%T %w%f%e' -e modify,delete,create,attrib $src \

| while read files ;do

 rsync -avzP --delete --timeout=100 --password-file=${password} $src $user@$host::$des

 echo "${files} was rsynced" >>/tmp/rsync.log 2>&1

done

编写VIP监控脚本/opt/vip_monitor.sh

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

#!/bin/bash

VIP_NUM=`ip addr|grep 244|wc -l`

RSYNC_INOTIRY_NUM=`ps -ef|grep /usr/bin/inotifywait|grep -v grep|wc -l`

if [ ${VIP_NUM} -ne 0 ];then

   echo "VIP在当前NFS节点服务器上" >/dev/null 2>&1

   if [ ${RSYNC_INOTIRY_NUM} -ne 0 ];then

      echo "rsync_inotify.sh脚本已经在后台执行中" >/dev/null 2>&1

   else

      echo "需要在后台执行rsync_inotify.sh脚本" >/dev/null 2>&1

      nohup sh /opt/rsync_inotify.sh &

  fi

else

   echo "VIP不在当前NFS节点服务器上" >/dev/null 2>&1

   if [ ${RSYNC_INOTIRY_NUM} -ne 0 ];then

      echo "需要关闭后台执行的rsync_inotify.sh脚本" >/dev/null 2>&1

      ps -ef|grep rsync_inotify.sh|grep -v grep|awk '{print $2}'|xargs kill -9

      ps -ef|grep inotifywait|grep -v grep|awk '{print $2}'|xargs kill -9

   else

      echo "rsync_inotify.sh脚本当前未执行" >/dev/null 2>&1

   fi

fi

编写持续执行脚本/opt/rsync_monit.sh

1

2

3

4

5

#!/bin/bash

while [ "1" = "1" ]

do

  /bin/bash -x /opt/vip_monitor.sh >/dev/null 2>&1

done

后台运行脚本 (只执行rsync_monit.sh)

设置rsync_monit.sh脚本的开机启动

6)最后验证下自动同步

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

1)比如当前VIP在Master节点,在Master节点创建测试数据,观察是否自动同步到Slave节点

    inet 172.16.60.244/32 scope global eth0

haha

到Slave节点上查看,已自动同步过来

haha

test

2)接着关闭Master节点的keeplived,将VIP飘移到Slave节点

到Slave节点上查看,发现VIP已经飘移过来了

    inet 172.16.60.244/32 scope global eth0

在Slave节点创建测试数据,观察是否自动同步到Master节点

到Master节点查看,发现数据已经同步过来了

cha heihei

3)模拟Master节点和Slave节点关机,观察开机后:

/opt/rsync_monit.sh脚本会实现开机自启动。

按照上面Master和Slave节点的自动同步验证OK。

作者:Jarosław Kowalski <jaak@jkowalski.net>

翻译:CrazyCoder(由衷感谢他的热心!!)

原文:http://www.nlog-project.org/config.html

更多关于NLog的中文文章,请参考《NLog文章系列》。

NLog支持以多种不同方式配置,目前同时支持直接编程和使用配置文件两种方法。本文将对目前支持的各种配置方式作详细描述。

日志配置

通过在启动的时候对一些常用目录的扫描,NLog会尝试使用找到的配置信息进行自动的自我配置。当你运行一个独立的*.exe客户端可执行程序时,NLog将在以下目录搜索配置信息:

  1. 标准的程序配置文件(通常为 程序名_.exe.config_)
  2. 程序目录下的_程序名__.exe.nlog_文件
  3. 程序目录下的_NLog.config_文件
  4. NLog.dll所在目录下的_NLog.dll.nlog_文件
  5. 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件

如果是一个ASP.NET程序,被搜索的目录包括:

  1. 标准的web程序配置文件_web.config_
  2. 和_web.config_在同一目录下的_web.nlog_文件
  3. 程序目录下的_NLog.config_文件
  4. NLog.dll所在目录下的_NLog.dll.nlog_文件
  5. 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件

由于.NET Compact Framework不支持程序配置文件(*.exe.config)和环境变量,因此NLog将只会扫描这些地方:

  1. 程序目录下的_NLog.config_文件
  2. NLog.dll所在目录下的_NLog.dll.nlog_文件
  3. 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件

配置文件格式

 NLog支持两种配置文件格式

  1. 配置信息嵌入在.NET应用程序标准的*.exe.config或者web.config文件里
  2. 保存在独立文件里,也叫单一格式

如果你选择了第一种方式,使用的是标准的configSections这种机制,那么你的配置文件看起来差不多是这个样子:

<configuration>

<configSections>

<section name\="nlog" type\="NLog.Config.ConfigSectionHandler, NLog"/> 

</configSections>

<nlog>

</nlog>

</configuration>

单一格式的配置文件就是一个以为根节点的纯XMl文件。命名空间并不强制使用,如果使用的话我们就可以利用Visual Studio的智能感应。

<nlog xmlns=“http://www.nlog-project.org/schemas/NLog.xsd

  xmlns:xsi\="http://www.w3.org/2001/XMLSchema-instance"\> 

</nlog>

需要注意的是NLog的配置文件总是大小写敏感的,不管是在使用的时候,或者即使你没有使用名字空间。当然只有你的大小写符合要求,智能感应才能正常工作。

配置元素

下面这些元素可以作为的字节点。列表中的前两个元素在所有的NLog配置文件中都必须提供,其余的则可以选择使用,通常用于一些复杂场景。

  1. - 定义日志的目标/输出
  2. - 定义日志的路由规则
  3. - 从*.dll加载NLog扩展
  4. - 导入外部配置文件
  5. - 为配置变量赋值

输出目标

区域定义了日志的目标或者说输出。每一个元素代表一个目标。我们需要为每一个目标设置两个属性:

  1. name - 目标的名称
  2. type - 目标的类型 - 比如“File”,“Database”,“Mail”。如果你使用了名字空间,这个属性会被命名为 xsi:type.

除了这两个属性,通常来说给目标添加一些其它参数,这些属性将会影响你在程序中如何使用诊断跟踪语句(diagnostic traces)。每一个目标都可以有一组不同的参数集合,并且参数都是上下文相关的,你可以在本项目的主页上找到更多关于参数的详细说明。Visual Studio的智能感应对这些参数同样有用。

举个例子 - “File”目标可以使用“fileName”作为参数来定义输出文件名,而“Console”目标可以借助“error”参数的值来判断是否应该把当前进程的diagnostic traces结果输出到标准错误(stderr)还是标准输出(stdout)控制台。

下面这个例子演示了在区域同时定义多个目标:两个files目标,一个network目标和一个OutputDebugString目标:

<targets>

<target name=“f1” xsi:type=“File” fileName=“file1.txt”/>

<target name=“f2” xsi:type=“File” fileName=“file2.txt”/>

<target name=“n1” xsi:type=“Network” address=“tcp://localhost:4001”/>

<target name=“ds” xsi:type=“OutputDebugString”/>

</targets>

NLog提供了许多已经预先定义好的目标。关于这些目标的详细说明请参考本项目的主页。实际上,你也可以很容易的为自己创建目标 - 全部只需大约15-20行代码即可,更多信息请参考项目文档

路由规则

区域定义了日志的路由规则。实际上它是一个简单的路由表,对每一个日志源/记录者的名称和记录等级的组合,定义了一个日志写入目标列表。 表中的规则是被顺序处理的。每当遇到匹配的规则时,日志信息就会被送到规则中定义的一个或多个目标去。如果一个规则被标识为最后一个,那么其后的规则都不会被执行。

每一个路由表项就是一个元素,它的可以接受的属性有:

  1. name - 日志源/记录者的名字 (允许使用通配符*)
  2. minlevel - 该规则所匹配日志范围的最低级别
  3. maxlevel - 该规则所匹配日志范围的最高级别
  4. level - 该规则所匹配的单一日志级别
  5. levels - 该规则所匹配的一系列日志级别,由逗号分隔。
  6. writeTo - 规则匹配时日志应该被写入的一系列目标,由逗号分隔。
  7. final - 标记当前规则为最后一个规则。其后的规则即时匹配也不会被运行。

一些例子:

  1. - 名字空间Name.Space下的Class1这个类的所有级别等于或者高于Debug的日志信息都写入到“f1”这个目标里。
  2. -名字空间Name.Space下的Class1这个类的所有级别等于Debug或Error的日志信息都写入到“f1”这个目标里。
  3. -名字空间Name.Space下所有类的所有级别的日志信息都写入到“f3”和“f4”这两个目标里。
  4. - 名字空间Name.Space下所有类的、级别在Debug和Error之间的(包括Debug,Info,Warn,Error) 日志信息都不会被记录(因为这条规则没有定义writeTo),同时其它后续规则也都会被忽略(因为这里设置了final=”true”)。

最简单的情况下,整个日志的配置信息可以只由一个元素和一个规则构成,就可以吧一定级别的日志信息路由到期望的目标去。随着程序不断的变大,增加新的目标和规则也很简单。

上下文信息

NLog最棒的功能之一就是使用布局(layouts)的能力。布局由被一个美元符号$加左大括弧“${”和一个右大括弧“}”为标记所包围的文本所组成。这个标记也就是所谓的“布局生成器(layout renderers),我们可以用它来把一些上下文相关的信息插入到日志信息中。布局可以应用在许多地方,比如可以被用在控制输出到屏幕或写入文件信息的格式,也可以用在控制文件名。接下来我们会更多的了解布局的强大。

假设我们希望每个输出到控制台的信息都包含一些这些信息:

  • 当前的日期和时间
  • 产生日志信息的类和方法的名字
  • 日志等级
  • 日志内容

利用Layout来实现很简单:

<target name=“c” xsi:type=“Console”

layout=“${longdate} ${callsite} ${level} ${message}”/>

或者我们可以把每一个日志记录者生成的日志信息输出到一个单独的文件里:

<target name=“f” xsi:type=“File” fileName=“${logger}.txt”/>

这里我们看到fileName属性的值被设置为布局生成器${logger},从而使每一条日志信息被写到一个以日志生成者名字命名的一个文件里。上面这个例子将生成如下一系列文件:

  • Name.Space.Class1.txt
  • Name.Space.Class2.txt
  • Name.Space.Class3.txt
  • Other.Name.Space.Class1.txt
  • Other.Name.Space.Class2.txt
  • Other.Name.Space.Class3.txt

有一个常见需求是能够用日期信息来区分日志文件。如果使用${shortdate}布局生成器,这简直太容易了:

<target name=“f” xsi:type=“File” fileName=“${shortdate}.txt”/>

那么可以给每一个职员生成一个日志文件吗?答案就是${windows-identity}布局生成器:

<target name=“f” xsi:type=“File” fileName=“${windows-identity:domain=false}.txt”/>

这样我们就能够给每一个职员生成一个日志文件了:

  1. Administrator.txt
  2. MaryManager.txt
  3. EdwardEmployee.txt

更复杂的场景也能做到。下面这个例子说明了如何为每个人每天生成一个日志文件。每天的日志文件存放在不同的文件夹里:

<target name=“f” xsi:type=“File”

    fileName\="${shortdate}/${windows-identity:domain=false}.txt"/> 

这将创建如下文件:

  1. 2006-01-01/Administrator.txt
  2. 2006-01-01/MaryManager.txt
  3. 2006-01-01/EdwardEmployee.txt
  4. 2006-01-02/Administrator.txt
  5. 2006-01-02/MaryManager.txt
  6. 2006-01-02/EdwardEmployee.txt

NLog提供了许多预先定义好的布局生成器。关于它们的说明都在这个页面:http://www.nlog-project.org/layoutrenderers.html。建立你自己的布局生成器也很容易,大概只需要15-20行的代码而已。详细做法请参考项目文档

包含文件

有时我们希望把配置文件分割为一些比较小的文件。NLog提供了包含文件这一机制来支持这种需求。要包含一个外部文件,你需要做的只是设置fileName这个属性。同时,和其它大多数NLog配置文件的属性一样,fileName也支持用大家都很熟悉的${var}标记引入动态值,这使得我们可以根据环境属性的不同包含不同的文件。在下面这个例子里,我们总是导入基于当前机器名的配置文件。

<nlog>

<include file=“${basedir}/${machinename}.config”/>

</nlog>

变量的使用可以使我们以比较简洁的形式书写复杂或者是重复表达式(如文件名)。定义一个变量的语法是:。变量定义好之后,就可以像使用布局生成器一样 – 通过语法${var}来使用了。下面我们来看一个使用变量的例子:

<nlog>

<variable name=“logDirectory” value=“${basedir}/logs/${shortdate}”/>

<targets>

<target name\="file1" xsi:type\="File" fileName\="${logDirectory}/file1.txt"/> 

<target name\="file2" xsi:type\="File" fileName\="${logDirectory}/file2.txt"/> 

</targets>

</nlog>

自动再配置

配置文件在程序启动时会被自动读取。然而在一些长时间运行的程序中(比如Windows服务或者ASP.NET程序),有时我们希望能够在不中断程序的前提下临时提高日志的级别。NLog可以一直监视日志配置文件的状态,并在它们被修改后重新读取。要激活这一机制,你只需在你的配置文件中设置。注意自动再配置支持引入文件,所以每次如果一个引入文件被修改了,会引起整个配置信息被重新载入。

日志排错

有时候即使你觉得你已经把日志配置的没有任何问题了,你的程序就是不输出任何日志信息。原因可能有很多,最常见的是权限问题,尤其在ASP.NET程序里,aspnet_wp.exe或者w3wp.exe进程可能没有足够的权限访问存放日志文件的目录。NLog被设计为吃掉任何由于记录日志而带来的运行时异常。而下面这些设置可以改变这种行为并/或者重定向这些信息。

  • - 设置throwExceptions属性为“true”可以让NLog不再阻挡这类异常,而是把它们抛给调用者。在部署是这样做可以帮我们快速定位问题。一旦应用程序已经正确配置了,我们建议把throwExceptions的值设为“false”,这样由于日志引发的问题不至于导致应用程序的崩溃。
  • - 设置internalLogFile属性可以让NLog把内部的调试和异常信息都写入指定文件里。
  • - 决定内部日志的级别,级别越高,输出的日志信息越简洁。
  • - 是否把内部日志输出到标准控制台。
  • - 是否把内部日志输出到标准错误控制台 (stderr)。

异步处理,封装和复合目标

NLog提供的封装和复合目标可以修改其它目标的行为,这可以增加一些功能如:

  • 异步处理 (被封装的目标在另一个线程上运行)
  • 自动重试 (retry-on-error)
  • 负载平衡 (round-robin targets)
  • 缓冲 (buffering)
  • 过滤 (filtering)
  • 备份目标 (灾难恢复failover)
  • 更多请参考http://www.nlog-project.org/targets.html

定义一个封装或者复合目标,你只需在一个目标节点里嵌套另一个目标节点即可。你甚至可以封装一个封装目标。嵌套的层数没有任何限制。比如,要给你的配置文件加上异步日志记录的功能,同时异步日志记录可以自动重试,你可以这样做:

<targets>

<target name=“n” xsi:type=“AsyncWrapper”>

<target xsi:type\="RetryingWrapper"\> 

  <target xsi:type\="File" fileName\="${file}.txt"/> 

</target\> 

</target>

</targets>

因为异步处理使用的非常普遍,NLog专门为异步处理设计了一个简化符号,这样所有需要异步处理的目标都不需要显式的定义封装了。你只要设置然后你所有的目标就都具备了异步处理的能力了。

缺省封装

有时我们希望用同一种封装来处理所有的目标,比如说增加缓冲和/或自动重试功能。NLog为此提供了专门的语法:。你只要把这个元素放在区域里,然后所有的目标都会自动加载同一个封装目标。需要注意的是只对当前这个区域有效,而你可以使用多个区域,这样你就可以把目标分组并用不同的封装目标处理。

<nlog>

<targets>

<default-wrapper xsi:type\="BufferingWrapper" bufferSize\="100"/> 

<target name\="f1" xsi:type\="File" fileName\="f1.txt"/> 

<target name\="f2" xsi:type\="File" fileName\="f2.txt"/> 

</targets>

<targets>

<default-wrapper xsi:type\="AsyncWrapper"\> 

  <wrapper xsi:type\="RetryingWrapper"/> 

</default-wrapper\> 

<target name\="n1" xsi:type\="Network" address\="tcp://localhost:4001"/> 

<target name\="n2" xsi:type\="Network" address\="tcp://localhost:4002"/> 

<target name\="n3" xsi:type\="Network" address\="tcp://localhost:4003"/> 

</targets>

</nlog>

上面的例子里我们定义了两个缓冲文件目标和三个异步以及自动重试网络目标。

缺省目标参数

和缺省封装目标类似,NLog也提供了来让你为目标参数设置缺省值。比如,如果你不希望日志文件总是被打开,你既可以通过给每一个目标增加 keepFileOpen=”false”属性来达到这个目的:

<nlog>

<targets>

<target name\="f1" xsi:type\="File" fileName\="f1.txt" keepFileOpen\="false"/> 

<target name\="f2" xsi:type\="File" fileName\="f2.txt" keepFileOpen\="false"/> 

<target name\="f3" xsi:type\="File" fileName\="f3.txt" keepFileOpen\="false"/> 

</targets>

</nlog>

或者,你可以定义一个并把它的值赋给当前区域里所有的目标。缺省参数时依据不同的类型定义的,并且在XML文件的实际属性没有被定义好之前就生效了。

<nlog>

<targets>

<default-target-parameters xsi:type\="File" keepFileOpen\="false"/> 

<target name\="f1" xsi:type\="File" fileName\="f1.txt"/> 

<target name\="f2" xsi:type\="File" fileName\="f2.txt"/> 

<target name\="f3" xsi:type\="File" fileName\="f3.txt"/> 

</targets>

</nlog>

原文:http://www.cnblogs.com/felixnet/p/5498759.html

NLog是一个记录日志组件,和log4net一样被广泛使用,它可以将日志保存到文本文件、CSV、控制台、VS调试窗口、数据库等。最近刚用到这个组件,觉得不错,水一篇。

下载

通过Nuget安装NLog,你也可以同时安装NLog.Config,它会在 项目目录下帮你建立一个配置文件NLog.config,不过不需要,我们直接手动建立一个,你也可以将配置的信息写入到 App.config/Web.config,我比较喜欢独立出来,不与其它配置掺和在一起。

配置

在项目根目录下新建一个NLog.config,基本目录结构:targets下面配置日志输出目标及相关参数,rules下面配置目标输出规则。

1

2

3

4

5

6

7

8

9

10

11

<?``xml version="1.0" ?>

<``nlog``>

<``targets``>

<``target``></``target``>

<``target``></``target``>

</``targets``>

<``rules``>

<``logger``></``logger``>

<``logger``></``logger``>

</``rules``>

</``nlog``>

记得在NLog.config的属性中设置 Copy to Output Directory: Copy always

现在我们要将日志输出到文本文件,数据库,VS调试窗口,完整配置文件如下:

复制代码

复制代码

<nlog xmlns=“http://www.nlog-project.org/schemas/NLog.xsd“ xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“ autoReload=“true”>

<targets>

<!-- Log in a separate thread, possibly queueing up to
    5000 messages. When the queue overflows, discard any
    extra messages\-->

<!-- write logs to file \-->
<target name\="file" xsi:type\="AsyncWrapper" queueLimit\="5000" overflowAction\="Discard"\>
  <target xsi:type\="File" fileName\="${basedir}/logs/${shortdate}.log" layout\="${longdate} ${level:uppercase=true} ${event-context:item=Action} ${message} ${event-context:item=Amount} ${stacktrace}" />      
</target\>

<!-- write log message to database \-->
<target name\="db" xsi:type\="AsyncWrapper" queueLimit\="5000" overflowAction\="Discard"\>
  <target type\="Database" dbProvider\="mssql" connectionString\="Data Source=.\\SQLEXPRESS;Initial Catalog=EFinance;Persist Security Info=True;User ID=sa;Password=123456;"\>

    <commandText\> INSERT INTO Log(Timestamp,Level,Message,Action,Amount,StackTrace) VALUES(@time\_stamp, @level, @message, @action, @amount, @stacktrace);
    </commandText\>

    <!-- database connection parameters \-->
    <parameter name\="@time\_stamp" layout\="${date}" />
    <parameter name\="@level" layout\="${level:uppercase=true}" />
    <parameter name\="@message" layout\="${message}" />
    <parameter name\="@action" layout\="${event-context:item=Action}" />
    <parameter name\="@amount" layout\="${event-context:item=Amount}" />
    <parameter name\="@stacktrace" layout\="${stacktrace}" />
  </target\>
</target\>

<!--write log message to Visual Studio Output\-->
<target name\="debugger" xsi:type\="Debugger" layout\="NLog: ${date:format=HH\\:mm\\:ss} | ${level:uppercase=true:padding=-5} | ${message}" />

</targets>

<rules>

<logger name=“*“ minlevel=“Trace” writeTo=“debugger” />

<logger name=“*“ minlevel=“Info” writeTo=“db” />

<logger name=“*“ minlevel=“Debug” writeTo=“file” />
</rules>
</nlog>

复制代码

复制代码

  • 如在根节点(nlog)配置 internalLogLevel, internalLogFile,可以查看NLog输出日志时的内部信息,比如你配置文件有错误,很有帮助,不过项目发布后还是关闭比较好,以免影响效率;
  • 在target外面罩了一个 并且xsi:type为 AsyncWrapper,即表示这条 target 将异步输出,这里我将文件和数据库日志异步输出;
  • db target内指定了数据库连接字符串 connectionString,SQL语句,SQL参数,还可以指定数据库/表创建和删除的脚本(推荐看NLog源码示例,这里不介绍),同时我们自定义了2个参数 action和amount;
  • target参数里有些是NLog内置参数,比如message,level,date,longdate,exception,stacktrace等,NLog在输出时会自动赋值;
  • layout设置了每条日志的格式;
  • 在rules节点,我们分别指定了三个target输出日志的级别,NLog 用于输出日志的级别包括:Trace,Debug,Info,Warn,Error,Fatal,可以设置 minlevel设置最小级别,也可以用 levels定义你所有需要的级别(多个用逗号分隔)。

封装

简单两句就可以使用NLog了:

NLog.Logger logger = Nlog.LogManager.GetCurrentClassLogger();
logger.Fatal(“发生致命错误”);
logger.Warn(“警告信息”);

但是这样只能记录了NLog的内置字段,我们定义的 Amount, Action都不能写入,接下来我们来封装一个Logger:

复制代码

复制代码

public class Logger
{
NLog.Logger _logger;

    private Logger(NLog.Logger logger)
    {
        \_logger = logger;
    }

    public Logger(string name) : this(LogManager.GetLogger(name))
    {

    }

    public static Logger Default { get; private set; }
    static Logger()
    {
        Default = new Logger(NLog.LogManager.GetCurrentClassLogger());
    }

    #region Debug
    public void Debug(string msg, params object\[\] args)
    {
        \_logger.Debug(msg, args);
    }        

    public void Debug(string msg, Exception err)
    {
        \_logger.Debug(err, msg);
    }        
    #endregion

    #region Info
    public void Info(string msg, params object\[\] args)
    {
        \_logger.Info(msg, args);
    }

    public void Info(string msg, Exception err)
    {
        \_logger.Info(err, msg);
    }
    #endregion

    #region Warn
    public void Warn(string msg, params object\[\] args)
    {
        \_logger.Warn(msg, args);
    }

    public void Warn(string msg, Exception err)
    {
        \_logger.Warn(err, msg);
    }
    #endregion

    #region Trace
    public void Trace(string msg, params object\[\] args)
    {
        \_logger.Trace(msg, args);
    }

    public void Trace(string msg, Exception err)
    {
        \_logger.Trace(err, msg);
    }
    #endregion

    #region Error
    public void Error(string msg, params object\[\] args)
    {
        \_logger.Error(msg, args);
    }

    public void Error(string msg, Exception err)
    {
        \_logger.Error(err, msg);
    }
    #endregion

    #region Fatal
    public void Fatal(string msg, params object\[\] args)
    {
        \_logger.Fatal(msg, args);
    }

    public void Fatal(string msg, Exception err)
    {
        \_logger.Fatal(err, msg);
    }
    #endregion

    #region Custom

    public void Process(Models.Log log)
    {
        var level = LogLevel.Info;
        if (log.Level == Models.EFLogLevel.Trace)
            level = LogLevel.Trace;
        else if (log.Level == Models.EFLogLevel.Debug)
            level = LogLevel.Debug;
        else if (log.Level == Models.EFLogLevel.Info)
            level = LogLevel.Info;
        else if (log.Level == Models.EFLogLevel.Warn)
            level = LogLevel.Warn;
        else if (log.Level == Models.EFLogLevel.Error)
            level = LogLevel.Error;
        else if (log.Level == Models.EFLogLevel.Fatal)
            level = LogLevel.Fatal;

        var ei = new MyLogEventInfo(level, \_logger.Name, log.Message);
        ei.TimeStamp = log.Timestamp;
        ei.Properties\["Action"\] = log.Action;
        ei.Properties\["Amount"\] = log.Amount;

        \_logger.Log(level, ei);
    }

    #endregion

    /// <summary>
    /// Flush any pending log messages (in case of asynchronous targets).
    /// </summary>
    /// <param name="timeoutMilliseconds">Maximum time to allow for the flush. Any messages after that time will be discarded.</param>
    public void Flush(int? timeoutMilliseconds = null)
    {
        if (timeoutMilliseconds != null)
            NLog.LogManager.Flush(timeoutMilliseconds.Value);

        NLog.LogManager.Flush();
    }
}

public class MyLogEventInfo : LogEventInfo
{
    public MyLogEventInfo() { }
    public MyLogEventInfo(LogLevel level, string loggerName, string message) : base(level, loggerName, message)
    { }

    public override string ToString()
    {
        //Message format
        //Log Event: Logger='XXX' Level=Info Message='XXX' SequenceID=5
        return FormattedMessage;
    }
}

复制代码

复制代码

复制代码

复制代码

public class Log : IEntityBase {
public long Id { get; set; }
///


/// 日志级别 Trace|Debug|Info|Warn|Error|Fatal
///

public string Level { get; set; }
public string Message { get; set; }
public string Action { get; set; }
public string Amount { get; set; }
public string StackTrace { get; set; }
public DateTime Timestamp { get; set; }

    private Log() { }
    public Log(string level, string message, string action = null, string amount = null)
    {
        this.Level = level;
        this.Message = message;
        this.Action = action;            
        this.Amount = amount;
    }
}

复制代码

复制代码

  • Models.Log是我们项目里的日志对象,它对应一个数据表Log,NLog将日志数据写入到这个表;
  • Process(Models.Log)是我们处理自定义对象的日志方法,用LogEventInfo来写入;
  • 重写 LogEventInfo.ToString() 是因为 LogEventInfo的Message格式是“_Log Event: Logger=’XXX’ Level=Info Message=’XXX’ SequenceID=5_”,不便于查阅,我们只需要我们设置的Message。

使用:

下面是测试方法,我们一共输出9条日志,这9条日志将输出到哪个目标,由配置文件中的Rules/logger决定

复制代码

复制代码

Logger.Default.Trace(“Hello World! Trace”);
Logger.Default.Info(“Hello World! Info”);
Logger.Default.Warn(“Hello World! Warn”);
Logger.Default.Debug(“Hello World! Debug”);
Logger.Default.Error(“Hello World! Error”);
Logger.Default.Fatal(“Hello World! Fatal”);

Logger.Default.Process(new Models.Log(Models.EFLogLevel.Info, “Hello World! Info”, “TEST”, “100.00”));
Logger.Default.Process(new Models.Log(Models.EFLogLevel.Debug, “Hello World! Debug”, “TEST”, “100.00”));
Logger.Default.Process(new Models.Log(Models.EFLogLevel.Error, “Hello World! Error”, “TEST”, “100.00”));

Logger.Default.Flush();

复制代码

复制代码

因为我们在Target中设置了异步,所以如果我们想当场看到输出结果,就需要使用Flush()方法,实际输出日志时就不需要了。

结果:

查看日志记录:

.Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费);
实现前后Aop切面和INotifyPropertyChanged注入方式。

开源地址: https://git.oschina.net/chejiangyi/BSF.Aop

开源QQ群: .net 开源基础服务  238543768  欢迎交流

描述:

  • 静态织入实现,性能几乎接近原生代码编写。
  • 项目结构扩展自BSF基础架构,但dll方面并不依赖BSF.dll,可以独立开发使用。
  • 项目代码量少(也就几个类文件),核心功能插件化开发,便于第三方扩展,阅读,调试,一起来完善。
  • .net Aop静态织入相关的免费开源项目比较少或暂未听闻,故希望开源这块内容,成为同类型开源项目的起点和借鉴。

使用配置

1)项目引用BSF.Aop.dll,引用Mono.Cecil.dll,引用Mono.Cecil.Pdb.dll。
2)项目启动代码添加AopStartLoader.Start();一句代码即可。
(该代码用于自动注入扫描和vs项目环境自动配置,导出相关exe文件等)

备注:

  • Web项目在Application_Start中添加
  • Winform项目在Pragram.Main中添加

自动化配置(默认推荐)

Build项目,然后直接运行调试项目。

备注:

  • AopStartLoader.Start();这句代码第一次项目运行会报错,因为Aop环境未曾自动搭建;第一次运行并自动搭建成功后,以后运行都是自动注入的。
  • 默认会在项目下生成“packages.BSF.Aop”文件夹,并在vs项目PostBuildEvent事件中注入“aop运行脚本”,具体参考以下“手工配置内容”。

手工配置 (遇到问题,推荐使用)

1)配置Aop注入目录。
vs项目下新建packages.BSF.Aop 目录,里面分别包含BSF.Aop.ILRun.exe,BSF.Aop.dll,Mono.Cecil.Pdb.dll,Mono.Cecil.dll 这几个文件。
2)配置PostBuildEvent 脚本。

winform环境:

打开vs-》项目属性-》PostBuildEvent,配置aop运行脚本。如:
xcopy $(OutDir)BSF.Aop.dll $(ProjectDir)packages.BSF.Aop\ /Y
call “$(ProjectDir)packages.BSF.Aop\BSF.Aop.ILRun.exe” msgbox $(TargetDir)

web环境:

打开vs-》项目属性-》PostBuildEvent,配置aop运行脚本。(OpenApi.Test.Web替换成具体的项目名) 如:
xcopy $(SolutionDir)\OpenApi.Test.Web\bin\BSF.Aop.dll $(SolutionDir)\OpenApi.Test.Web\packages.BSF.Aop\ /Y
call “$(SolutionDir)\OpenApi.Test.Web\packages.BSF.Aop\BSF.Aop.ILRun.exe” msgbox $(SolutionDir)\OpenApi.Test.Web\bin\

使用demo示例

  1. 前后Aop切面示例 (_详细参考BSF.Aop.Test项目_)

public class AroundAopTest
{
[MyAroundAop]
[AttributeInfo(Des = “测试2”)]
public void Method(TempInfo info, out int b,int a=1)
{
a = 222;
b = 3;
System.Console.WriteLine(“Hello world!”+a);
}
}

public static class AroundAopTest2
{
    \[MyAroundAop\]\[AttributeInfo(Des ="测试")\]
    public static void Method2(TempInfo info, int a = 1)
    {
        a = 222;
        System.Console.WriteLine("Hello world!" + a);

    }
}

public class MyAroundAop : Aop.Attributes.Around.AroundAopAttribute
{
    public MyAroundAop()
    {
    }


    public override void Before(AroundInfo info)
    {
        var att = info.Method.CustomAttributes.ToList()\[0\];
        info.Params\["a"\] = 55;
        System.Console.WriteLine("before" + info.Params\["a"\]);
    }

    public override void After(AroundInfo info)
    {
        System.Console.WriteLine("after"+ info.Params\["a"\]);
    }
}

public class TempInfo
{
    public int T1 { get; set; }
}

public class AttributeInfo : System.Attribute
{
    public string Des { get; set; }
}
  1. INotifyPropertyChanged 示例(暂未测试真正使用效果,详细参考BSF.Aop.Test项目

[NotifyPropertyChangedAop]
public class User
{
public string Name { get; set; }

    public int Age { get; set; }

    \[NoAop\]
    public int B { get; set; }
}

by 车江毅

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

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

namespace FytSoa.Common

{

/// <summary>

/// 日志模块

/// </summary>

public class Logger

{

NLog.Logger _logger;

private Logger(NLog.Logger logger)

{

_logger = logger;

}

public Logger(``string name) : this``(LogManager.GetLogger(name))

{

}

/// <summary>

/// 单例

/// </summary>

public static Logger Default { get``; private set``; }

static Logger()

{

Default = new Logger(LogManager.GetCurrentClassLogger());

}

private static string _path = ""``;

/// <summary>

/// 自定义输出目录,初始化

/// </summary>

public void Setting(``string path)

{

if (_path != path)

{

_path = path;

LogManager.Configuration.Variables[``"cuspath"``] = path+``"/"``;

}

}

/// <summary>

/// 自定义写日志路径

/// </summary>

/// <param name="msg">消息</param>

/// <param name="path">写入地址</param>

/// <returns></returns>

public void Process(``string msg, string path=``""``)

{

_logger.Debug(msg);

}

#region Debug

public void Debug(``string msg, params object``[] args)

{

_logger.Debug(msg, args);

}

public void Debug(``string msg, Exception err)

{

_logger.Debug(err, msg);

}

#endregion

#region Info

public void Info(``string msg, params object``[] args)

{

_logger.Info(msg, args);

}

public void Info(``string msg, Exception err)

{

_logger.Info(err, msg);

}

#endregion

#region Warn

public void Warn(``string msg, params object``[] args)

{

_logger.Warn(msg, args);

}

public void Warn(``string msg, Exception err)

{

_logger.Warn(err, msg);

}

#endregion

#region Trace

public void Trace(``string msg, params object``[] args)

{

_logger.Trace(msg, args);

}

public void Trace(``string msg, Exception err)

{

_logger.Trace(err, msg);

}

#endregion

#region Error

public void Error(``string msg, params object``[] args)

{

_logger.Error(msg, args);

}

public void Error(``string msg, Exception err)

{

_logger.Error(err, msg);

}

#endregion

#region Fatal

public void Fatal(``string msg, params object``[] args)

{

_logger.Fatal(msg, args);

}

public void Fatal(``string msg, Exception err)

{

_logger.Fatal(err, msg);

}

#endregion

}

}

NetworkManager设置 - 知乎

Excerpt

通常的linux发行版对于网络的配置方法一般会同时支持network.service(即配置和使用/etc/sysconfig/network-scripts/下的配置文件来配置网络,对于ubuntu是/etc/network/interfaces等等)和NetworkManager.service…


通常的linux发行版对于网络的配置方法一般会同时支持network.service(即配置和使用/etc/sysconfig/network-scripts/下的配置文件来配置网络,对于ubuntu是/etc/network/interfaces等等)和NetworkManager.service(简称NM)。默认情况下,这2个服务都有开启,而且功能上是平行的,可以通过任意一个来配置网络,正常的情况下通过NM来配置网络后它会自动把配置同步到network.service的配置中。

NM能管理各种网络

  1. 物理网卡、虚拟网卡
  2. 有线网卡、无线网卡
  3. 动态ip、静态ip
  4. 以太网、非以太网

使用方法

  1. nmcli:命令行。这是最常用的工具。
  2. nmtui:在shell终端开启文本图形界面。
  3. Freedesktop applet:如GNOME上自带的网络管理工具
  4. nm-connection-editor图形配置工具
  5. cockpit:redhat自带的基于web图形界面的”驾驶舱”工具,具有dashborad和基础管理功能。

nmcli使用方法

nmcli使用方法非常类似linux ip命令、cisco交换机命令,并且支持tab补全,也可在命令最后通过-h、–help、help查看帮助。在nmcli中有2个命令最为常用:

  • nmcli connection

连接,可理解为配置文件,相当于ifcfg-ethX。可以简写为nmcli c

  • nmcli device

这里注意一点:ethernet是设备类型,eth0是设备名称。不要混了,设备类型可以是ethernet代表以太网,还可以是loopback代表环回口等等。

设备,可理解为实际存在的网卡(包括物理网卡和虚拟网卡)。可以简写为nmcli d

在NM里,有2个层级:连接(connection)和设备(device),连接的配置记录在(对于ubuntu为/etc/NetworkManager/system-connections/内),通常NM的管理是以连接为单位的,在连接的配置文件里指明设备名,所以我们在NM的所有配置目录里找不到针对设备的配置文件,只有针对连接的配置文件,就像你的手机wifi可以记住多个无线连接一样,多个连接可以使用同一个设备,但同一时刻,一个设备只能有一个连接活跃。意思就是你的手机wifi在一个时刻只能连接一个wifi。可以通过 nmcli connetion up<连接> 来切换连接。

对于一个device有4种常见状态

  1. connected:已被NM托管,并且当前有活跃的connection
  2. disconnected:已被NM托管,但是当前没有活跃的connection
  3. unmanaged:未被NM托管,就是不让NM动这个设备相关的任何操作
  4. unavailable:不可用,NM无法托管,通常出现于网卡link为down的时候(比如ip link set ethX down)

对于任意设备上的任一connection通常有2种状态

  1. 活跃(带颜色字体):表示当前该connection是正在使用的。
  2. 非活跃(正常字体):表示当前该connection没有连接。

nmcli常用命令一览

对于连接:

1
# 查看ip(类似于ifconfig、ip addr) nmcli # 创建connection,配置静态ip(等同于配置ifcfg,其中BOOTPROTO=none,并ifup启动) nmcli c add type ethernet con-name CNNCT1 ifname ethX ipv4.addr 192.168.1.100/24 ipv4.gateway 192.168.1.1 ipv4.method manual # 创建connection,配置动态ip(等同于配置ifcfg,其中BOOTPROTO=dhcp,并ifup启动) nmcli c add type ethernet con-name CNNCT1 ifname ethX ipv4.method auto # 修改ip(非交互式) nmcli c modify CNNCT1 ipv4.addr '192.168.1.200/24' nmcli c up CNNCT1 # 修改ip(交互式) nmcli c edit CNNCT1 nmcli> goto ipv4.addresses nmcli ipv4.addresses> change Edit 'addresses' value: 192.168.1.200/24 Do you also want to set 'ipv4.method' to 'manual'? [yes]: yes nmcli ipv4> save nmcli ipv4> activate nmcli ipv4> quit # 启用connection(相当于ifup) nmcli c up CNNCT1 # 停止connection(相当于ifdown) nmcli c down # 删除connection(类似于ifdown并删除ifcfg) nmcli c delete CNNCT1 # 查看connection列表 nmcli c show # 查看connection详细信息 nmcli c show CNNCT1 # 重载所有ifcfg或route到connection(不会立即生效) nmcli c reload # 重载指定ifcfg或route到connection(即导入原来的系统配置,不会立即生效) nmcli c load /etc/sysconfig/network-scripts/ifcfg-eth0 //注意eth0是设备名(centos) nmcli c load /etc/sysconfig/network-scripts/route-eth0 //这个是配置的路由(centos) # 立即生效connection,有3种方法 nmcli c up CNNCT1 nmcli d reapply CNNCT1 nmcli d connect CNNCT1

对于设备(device)

1
# 查看device列表 nmcli d # 查看所有device详细信息 nmcli d show # 查看指定device的详细信息 nmcli d show eth0 # 激活网卡 nmcli d connect eth0 # 关闭无线网络(NM默认启用无线网络) nmcli r all off # 查看NM托管状态 nmcli n # 开启NM托管 nmcli n on # 关闭NM托管(谨慎执行) nmcli n off # 监听事件 nmcli m # 查看NM本身状态 nmcli # 检测NM是否在线可用 nm-online

本文提及的ifcfg均指代/etc/sysconfig/network-scripts/ifcfg-ethX及/etc/sysconfig/network-scripts/route-ethX(即原来的配置方法)
NetworkManager自己的配置文件在/etc/NetworkManager/下

1
[root@localhost ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 #网卡设备名称 ONBOOT=yes#启动时是否激活 yes | no BOOTPROTO=static #协议类型 dhcp bootp none IPADDR=192.168.1.90#网络IP地址 NETMASK=255.255.255.0#网络子网地址 GATEWAY=192.168.1.1#网关地址 BROADCAST=192.168.1.255#广播地址 HWADDR=00:0C:29:FE:1A:09#网卡MAC地址 TYPE=Ethernet #网卡类型为以太网 [root@localhost ~]# cat /etc/sysconfig/network-scripts/route-eth0 0.0.0.0/0 via 192.168.3.1 dev eth0 10.211.6.0/24 via 192.168.3.1 dev eth0 # cat /etc/sysconfig/network-scripts/route-eth1 10.0.0.0/8 via 10.212.52.1 dev eth1

nmcli connection重点

nmcli c show

▪ 第一列是connection名字,简称con-name(注意con-name不是网卡名) ▪ 第二列是connection的UUID ▪ 最后一列才是网卡名(标准说法叫device名),可通过nmcil d查看device

对connection做操作时需要指定标识,标识可以是con-name、UUID、如果存在ifcfg文件则也可以用ifcfg的完整路径,即/etc/sysconfig/network-scripts/ifcfg-ethX

1
nmcli c show CNNT1 nmcli c show cae3f1ef-e79a-46c3-8e0c-946b91a65e11 nmcli c show /etc/sysconfig/network-scripts/ifcfg-ethX

nmcli c的con-name

同时对应ifcfg的文件名以及内容中的NAME=,该参数表示连接(connection)的名字,无需和网卡名相同,可以为一个设备(device)创建多个连接,但同一时刻只能有一个连接生效。当有多个连接时候, nmcli cdelete删除当前连接,就会自动选择同一个设备的其他连接来顶替生效。可以通过 nmcli c up来将指定连接切换生效。

注意:通过nmcli c modify修改con-name,只会对应修改ifcfg文件中的NAME,而不会更改ifcfg文件名。

nmcli c的ipv4.method

对应ifcfg文件内容的BOOTPROTO,ipv4.method默认为auto,对应为BOOTPROTO=dhcp,这种时候如果指定ip,就可能导致网卡同时有dhcp分配的ip和静态ip。设置为manual表示BOOTPROTO=none,即只有静态ip。

例子:创建一个连接(connection)

1
nmcli c add type ethernet con-name ethX-test ifname ethX ipv4.addresses '192.168.1.100/24,192.168.1.101/32' ipv4.routes '10.0.0.0/8 192.168.1.10,192.168.0.0/16 192.168.1.11' ipv4.gateway 192.168.1.254 ipv4.dns '8.8.8.8,4.4.4.4' ipv4.method manual

▪ type ethernet:创建连接时候必须指定类型,类型有很多,可以通过 nmcli c add type-h看到,这里指定为ethernet。 ▪ con-name ethX ifname ethX:第一个ethX表示连接(connection)的名字,这个名字可以任意定义,无需和网卡名相同;第二个ethX表示网卡名,这个ethX必须是在 nmcli d里能看到的。 ▪ ipv4.addresses ‘192.168.1.100/24,192.168.1.101/32’:配置2个ip地址,分别为192.168.1.100/24和192.168.1.101/32 ▪ ipv4.gateway 192.168.1.254:网关为192.168.1.254 ▪ ipv4.dns ‘8.8.8.8,4.4.4.4’:dns为8.8.8.8和4.4.4.4 ▪ ipv4.method manual:配置静态IP

对应的ifcfg和dns就是

1
# /etc/sysconfig/network-scripts/ifcfg-eth0-test TYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no BOOTPROTO=none IPADDR=192.168.1.100 PREFIX=24 IPADDR1=192.168.1.101 PREFIX1=32 GATEWAY=192.168.1.254 DNS1=8.8.8.8 DNS2=4.4.4.4 DEFROUTE=yes IPV4_FAILURE_FATAL=no IPV6INIT=yes IPV6_AUTOCONF=yes IPV6_DEFROUTE=yes IPV6_FAILURE_FATAL=no IPV6_ADDR_GEN_MODE=stable-privacy NAME=ethX-test UUID=9a10ad89-437c-4caa-949c-a394a6d28c8d DEVICE=ethX ONBOOT=yes # /etc/resolv.conf nameserver 8.8.8.8 nameserver 4.4.4.4

此时,通过 nmcli c应该可以看到增加了一条连接

注意:如果这是为eth0创建的第一个连接,则自动生效;如果此时已有连接存在,则该连接不会自动生效,可以执行 nmcli c up eth0-test来切换生效

nmcli device重点

nmcli d connect eth0

由NM对指定网卡进行管理,同时刷新该网卡对应的活跃connection(如果之前有修改过connection配置);如果有connection但是都处于非活跃状态,则自动选择一个connection并将其活跃;如果没有connection,则自动生成一个并将其活跃。

nmcli d disconnect eth0

让NM断开指定网卡的连接,此操作不会变更实际网卡的link状态,只会使对应的connection变成非活跃。若重启系统则又会自动connect。另外,如果手工将该网卡的connection全部删掉,该网卡状态也会自动变为disconnected。

nmcli d reapply eth0

专门用于刷新connection,前提是网卡的device处于connected状态,否则会报错。

nmcli d set eth0 autoconnect yes|no managed yes|no

可以设置是否自动连接和是否自动管理,但经测试只能用于当前开机状态, 如果这2个参数都设置为no,然后重启系统,又会自动恢复成connected和managed yes的状态。所以该命令用途不大。注意事项:如果managed设置为no,那么 nmcli c reload会读取配置文件,但是不会立即生效,接着如果执行nmcli c up CNNT1,就会立即生效,同时managed自动变为yes。

重启系统自动恢复成connected和managed yes的状态,这种逻辑并不实用也不够合理,笔者已将此问题提交给redhat,据回复,这么设计是因为目前没有一个有效的手段来证明“我是我”,比如当网卡重新拔插到其他插槽时候,网卡名有很大可能性会发生变化,因此无法确定关机前设置的是对应开机后的哪个网卡,目前暂无办法解决,笔者将持续跟进。

3种网络配置方法

在讲3种配置方法前,需要先明白ifcfg和NM connection的关联:虽然network.service被废弃了,但是redhat为了兼容传统的ifcfg,通过NM进行网络配置时候,会自动将connection同步到ifcfg配置文件中。也可以通过 nmcli c reload或者 nmcli c load/etc/sysconfig/network-scripts/ifcfg-ethX的方式来让NM读取ifcfg配置文件到connection中。因此ifcfg和connection是一对一的关系,另外上面有提到,connection和device是多对一的关系。

在rhel8上,有3种方法进行网络配置

1
1. 手工配置ifcfg,通过NM来生效 ,意思就是导入原来的配置到NM 2. 通过NM自带工具配ip,比如nmcli,意思是从头建立自己的配置 3. 手工配置ifcfg,通过传统network.service来生效

建议:

推荐使用上述第1种网络配置方法(手工配置ifcfg,通过NM生效),因为这样既兼容了传统的ifcfg配置,又能熟悉nmcli。举例:

1
cat > /etc/sysconfig/network-scripts/ifcfg-eth0 <<EOF NAME=eth0 DEVICE=eth0 ONBOOT=yes BOOTPROTO=none TYPE=Ethernet IPADDR=192.168.1.10 NETMASK=255.255.255.0 GATEWAY=192.168.1.1 EOF nmcli c reload # nmcli c up eth0 # 如果之前没有eth0的connection,则上一步reload后就已经自动生效了

这么做有2个好处:

  1. 按官方建议使用NM而不是network.service
  2. 当还不太熟悉nmcli命令时候,这样最稳妥

Tips

1. nmcli命令支持tab补全,但是需要 yum install bash-completion

2. 如果希望NM不要纳管网卡,只有一个办法最彻底最靠谱,就是自己写ifcfg,内容加上 NM_CONTROLLED=no,这样该device的状态就会始终保持unmanaged。nmcli c up、nmcli c reload、nmcil c load都不会对其起任何作用。

3. NM只能对link状态为up的网卡进行操作,如果手动 ip linksetethX down,那么NM就无法对该网卡做任何操作(即使nmcli d connect也没有用)。

4. 可以通过 yum install network-scripts来安装传统的network.service,不过redhat说了,在下一个rhel的大版本里将彻底废除,因此不建议使用network.service。

5. 手工创建新的ifcfg或者在ifcfg里修改ip等配置,NM不会自动读取,需要手工执行 nmcli c reload或者 nmcli c load/etc/sysconfig/network-scripts/ifcfg-ethX。这一点可能和其他系统的NM行为不太一样,但这种做法实则更适合服务器。

6. 不手工配置ifcfg,使用默认的dhcp情况下,网卡的增减是不会自动生成ifcfg,此时nmcli c看到的con-name将类似’System ethX’或者’Wired connection 1’。

7. NetworkManager支持3种获取dhcp的方式:dhclient、dhcpcd、internal,当/etc/NetworkManager/NetworkManager.conf配置文件中的[main]部分没配置 dhcp=时候,默认使用internal(rhel7/centos7默认是dhclient)。internal是NM内部实现的dhcp客户端。

8. 关于手动指定网关ip的方法,经过实测,/etc/sysconfig/network中的GATEWAY仅在3种情况下有效: NM_CONTROLLED=noipv4.method manual从ipv4.method manual第一次转到ipv4.methodauto时候。建议:当NM_CONTROLLED=no时,将网关写在/etc/sysconfig/network(GATEWAY);当使用NM时候,使用nmcli c命令配置网关(比如 nmcli c modify ethX ipv4.gateway192.168.1.1)。

9. NM默认会从dhcp里获取dns信息,并修改/etc/resolv.conf,如果不想让NM管理/etc/resolv.conf,则只需在/etc/NetworkManager/NetworkManager.conf里的[main]里增加 dns=none即可。

10. 如果想让NM不要自动管理新网卡(比如不要给新网卡获取ip地址),则只需在/etc/NetworkManager/NetworkManager.conf里的[main]里增加 no-auto-default=*即可,改完后通过 systemctl restartNetworkManager或者重启系统来生效。除了手工在NetworkManager.conf里加配置,也可以 yum installNetworkManager-config-server,这会生成/usr/lib/NetworkManager/conf.d/00-server.conf,内容为如下截图。建议使用前者方案,因为后者的ingore-carrier是不被推荐的参数。

11. 更多NetworkManager参数详见man NetworkManager.conf

12. nmtui示意图:

13. cockpit示意图:

在rhel8.0 beta时候,必须要先将浏览器语言设置为英语,才可以使用,如果为中文,在登陆后是空白页面。笔者已将该bug提交给redhat,据回复会在RHEL8.0正式版修复,笔者将持续跟进。

本文转自基于RHEL8/CentOS8的网络IP配置详解,作者:小慢哥 略有修改。

.Net中Remoting通信机制简单实例 - JiYF - 博客园

Excerpt

.Net中Remoting通信机制 前言: 本程序例子实现一个简单的Remoting通信案例 本程序采用语言:c# 编译工具:vs2013工程文件 编译环境:.net 4.0 程序模块: Test测试 Talker Server端 Client端 源代码工程文件下载 Test测试程序截图: Talk


.Net中Remoting通信机制

前言:

本程序例子实现一个简单的Remoting通信案例

  本程序采用语言:c#

  编译工具:vs2013工程文件

  编译环境:.net 4.0

程序模块:

  • Test测试
  • Talker
  • Server端
  • Client端
  • 源代码工程文件下载

Test测试程序截图:

Talker类:

复制代码

1
2
3
4
5
6
7
8
<span>1</span> <span>public</span> <span>class</span><span> Talker : MarshalByRefObject
</span><span>2</span> <span> {
</span><span>3</span> <span>public</span> <span>void</span> Talk(<span>string</span><span> word)
</span><span>4</span> <span> {
</span><span>5</span> <span> System.Console.WriteLine(word);
</span><span>6</span> <span> }
</span><span>7</span>
<span>8</span> }

复制代码

Server端:

复制代码

1
2
3
4
5
6
7
8
9
<span>1</span>  <span>//</span><span>注册通道</span>
<span>2</span> TcpServerChannel channel = <span>new</span> TcpServerChannel(<span>"</span><span>TalkChannel</span><span>"</span>,<span>8090</span><span>);
</span><span>3</span> ChannelServices.RegisterChannel(channel,<span>true</span><span>);
</span><span>4</span>
<span>5</span> <span>//</span><span>注册远程对象</span>
<span>6</span> <span> RemotingConfiguration.RegisterWellKnownServiceType(
</span><span>7</span> <span>typeof</span><span>(Talker),
</span><span>8</span> <span>"</span><span>Talker</span><span>"</span><span>,
</span><span>9</span> WellKnownObjectMode.SingleCall);

复制代码

Client端:

复制代码

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
<span> 1</span>   <span>public</span> <span>partial</span> <span>class</span><span> Form1 : Form
</span><span> 2</span> <span> {
</span><span> 3</span> <span>private</span> Talker _talk = <span>null</span><span>;
</span><span> 4</span> <span>public</span><span> Form1()
</span><span> 5</span> <span> {
</span><span> 6</span> <span> InitializeComponent();
</span><span> 7</span> <span> }
</span><span> 8</span>
<span> 9</span> <span>private</span> <span>void</span> btnSend_Click(<span>object</span><span> sender, EventArgs e)
</span><span>10</span> <span> {
</span><span>11</span> <span>if</span> (btnSend.Text.Equals(<span>"</span><span>开始</span><span>"</span><span>))
</span><span>12</span> <span> {
</span><span>13</span> timer1.Enabled = <span>true</span><span>;
</span><span>14</span> btnSend.Text = <span>"</span><span>结束</span><span>"</span><span>;
</span><span>15</span> <span> }
</span><span>16</span> <span>else</span>
<span>17</span> <span> {
</span><span>18</span> timer1.Enabled = <span>false</span><span>;
</span><span>19</span> btnSend.Text = <span>"</span><span>开始</span><span>"</span><span>;
</span><span>20</span> <span> }
</span><span>21</span> <span> }
</span><span>22</span>
<span>23</span> <span>private</span> <span>void</span> sendMsg(<span>string</span><span> msg)
</span><span>24</span> <span> {
</span><span>25</span> <span>try</span>
<span>26</span> <span> {
</span><span>27</span> <span>//</span><span>操作远程对象</span>
<span>28</span> <span> _talk.Talk(msg);
</span><span>29</span> <span>string</span> newline = msg +<span> Environment.NewLine;
</span><span>30</span> txtContent.Text = txtContent.Text.Insert(<span>0</span><span>, newline);
</span><span>31</span> <span> }
</span><span>32</span> <span>catch</span><span> (Exception ex)
</span><span>33</span> <span> {
</span><span>34</span> <span> MessageBox.Show(ex.Message);
</span><span>35</span> <span> }
</span><span>36</span> <span> }
</span><span>37</span>
<span>38</span> <span>private</span> <span>void</span> Form1_Load(<span>object</span><span> sender, EventArgs e)
</span><span>39</span> <span> {
</span><span>40</span> <span>try</span>
<span>41</span> <span> {
</span><span>42</span> timer1.Interval = <span>1000</span><span>;
</span><span>43</span> <span>//</span><span>注册通道</span>
<span>44</span> TcpClientChannel channel = <span>new</span><span> TcpClientChannel();
</span><span>45</span> ChannelServices.RegisterChannel(channel, <span>true</span><span>);
</span><span>46</span> <span>//</span><span>获取远程对象</span>
<span>47</span> _talk = (Talker)Activator.GetObject(<span>typeof</span>(Talker), <span>"</span><span>TCP://localhost:8090/Talker</span><span>"</span><span>);
</span><span>48</span> <span> }
</span><span>49</span> <span>catch</span><span> (Exception ex)
</span><span>50</span> <span> {
</span><span>51</span> <span> MessageBox.Show(ex.Message);
</span><span>52</span> <span> }
</span><span>53</span> <span> }
</span><span>54</span>
<span>55</span> <span>private</span> <span>void</span> timer1_Tick(<span>object</span><span> sender, EventArgs e)
</span><span>56</span> <span> {
</span><span>57</span> <span> sendMsg(txtWord.Text.Trim());
</span><span>58</span> }

复制代码

源代码工程文件下载:

  源代码工程文件下载 https://files.cnblogs.com/files/JiYF/RemotingSolution.rar

Net分布式系统之三:Keepalived+LVS+Nginx负载均衡之高可用 - 刘蔡涛 - 博客园

Excerpt

上一篇写了nginx负载均衡,此篇实现高可用(HA)。系统整体设计是采用Nginx做负载均衡,若出现Nginx单机故障,则导致整个系统无法正常运行。针对系统架构设计的高可用要求,我们需要解决Nginx负载均衡出现单机故障时,系统正常运行的需求。所以系统架构引入Keepalived组件,实现系统高可用


  上一篇写了nginx负载均衡,此篇实现高可用(HA)。系统整体设计是采用Nginx做负载均衡,若出现Nginx单机故障,则导致整个系统无法正常运行。针对系统架构设计的高可用要求,我们需要解决Nginx负载均衡出现单机故障时,系统正常运行的需求。所以系统架构引入Keepalived组件,实现系统高可用。

  一、Keepalived介绍

   Keepalived是分布式部署系统解决系统高可用的软件,结合LVS(Linux Virtual Server)使用,其功能类似于heartbeat,解决单机宕机的问题。


  二、Keepalived技术原理

   keepalived是以VRRP协议为实现基础的,VRRP全称Virtual Router Redundancy Protocol,即虚拟路由冗余协议。通过VRRP协议结合LVS,对组群服务器监控情况,若master出现宕机情况,则将VIP漂移到backup机上。实现了分布式系统高可用。可以理解为:keepalived是LVS的管理软件,根据监控情况,将宕机服务器从ipvsadm移除掉。


  三、Keepalived+LVS+Nginx实现系统高可用

  

 
服务器IP地址说明
虚拟IP192.168.1.120:80 
主机192.168.1.104:80 
备机192.168.1.103:80 
Web站点A192.168.1.101:8081不同端口
Web站点B192.168.1.101:8082不同端口

  1、安装ipvsadm,CentOS7自带安装包,通过yum进行安装。实现系统支持LVS

  2、安装Keepalived软件,并将keepalived设置开机启动

1

systemctl enable keepalived

  3、进行Keepalived.conf配置,如果是MASTER机,将state BACKUP改为state MASTER。

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

62

63

64

65

vim /etc/keepalived/keepalived.conf

#配置的内容

! Configuration File for keepalived

global_defs {

   notification_email {

     xxx@126.com #收到通知的邮件地址

   }

   notification_email_from XX@126.com

   smtp_server 127.0.0.1

   smtp_connect_timeout 30

   router_id LVS_DEVEL

}

vrrp_script monitor_nginx{

   script "/usr/local/etc/keepalived/script/monitor_nginx.sh"

   interval 1

   weight -15

}

vrrp_instance VI_1 {

    state BACKUP

    interface eno16777736

    virtual_router_id 51

    priority 80

    advert_int 1

    authentication {

        auth_type PASS

        auth_pass 1111

    }

    virtual_ipaddress {

        192.168.1.120

    }

    track_script {

        monitor_nginx

    }

}

virtual_server 192.168.1.120 80 {

    delay_loop 6

    lb_algo wrr

    lb_kind DR

    persistence_timeout 50

    protocol TCP

    real_server 192.168.1.103 80 {

        weight 1

        TCP_CHECK {

            connect_timeout 10

            nb_get_retry 3

            delay_before_retry 3

            connect_port 80

        }

    }

    real_server 192.168.1.104 80 {

        weight 5

        TCP_CHECK {

            connect_timeout 10

            nb_get_retry 3

            delay_before_retry 3

            connect_port 80

        }

    }

}

  4、配置监控shell脚本

1

2

3

4

5

6

7

8

9

10

(1)创建:vim /usr/local/etc/keepalived/script/monitor_nginx.sh<br><br>(2)SHELL文件内容<br>#!/bin/bash 

if [ "$(ps -ef | grep "nginx: master process"| grep -v grep )" == "" ]

then 

    systemclt start nginx.service

    sleep 5   

  if [ "$(ps -ef | grep "nginx: master process"| grep -v grep )" == ""

  then  

    killall keepalived 

  fi 

fi

  以上完成相关配置,nginx和web服务以上一篇博客内容一致。如下对功能进行验证测试。


  四、实现测试展示

   1、访问系统情况:通过VIP(192.168.1.120)访问系统页面。因为设置了轮询调度,所以刷新页面访问不同站点。

        

  2、将 MASTER(192.168.1.104)关机前后,查看相关VLS情况:

  (1)关机前:

  (2)关机后:

  我们看到将104服务器从 LVS移除掉。此时则将后续请求转发到103服务器。

  3、关机后,BACKUP服务器 keepalived日志显示无法连接104,并移除掉

  

  5、开机后,将自动检测到服务器正常,并加入LVS中。

  

作者:andon
出处:http://www.cnblogs.com/Andon_liu
关于作者:专注于微软平台项目架构、管理。熟悉设计模式、领域驱动、架构设计、敏捷开发和项目管理。现主要从事ASP.NET MVC、WCF/Web API、SOA、MSSQL、redis方面的项目开发、架构、管理工作。 如有问题或建议,请一起学习讨论!
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
如有问题,可以邮件:568773262@qq.com 联系我,谢谢。

一、下载

oracle java驱动下载地址:http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html

mysql java驱动下载地址:https://dev.mysql.com/downloads/connector/j/

二、连接代码

导入包:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

声明变量:
private static String USERNAMR = “lsdb”;                           //数据库用户名,在mysql所有数据库用户名密码是一样的,oracle各数据库的用户名密码是单独设置的
private static String PASSWORD = “lsdb123#”;                        //数据库密码
private static String ORACLE_DRVIER = “oracle.jdbc.OracleDriver”;             //Oracle数据库驱动写法
private static String MySQL_DRVIER = “com.mysql.jdbc.Driver”              //MySQL数据库驱动写法
private static String ORACLE_URL = “jdbc:oracle:thin:@192.168.220.128:1521:lsdb”;   //Oracle数据库URL写法,lsdb是要连接的数据库
private static String MySQL_URL = “jdbc:mysql://127.0.0.1:3306/banksystem”;      //MySQL数据库URL写法,banksystem是要连接的数据库

连接:
Class.forName(DRVIER);
Connection conn = DriverManager.getConnection(URL, USERNAMR, PASSWORD);

操作:
String sql = “insert into userx values(?,?)”;                      //要执行的sql语句
PreparedStatement pstm = conn.prepareStatement(sql);  //预编译对象
pstm.setString(1, username);                             //1表示第一个问号的内容
pstm.setString(2, password);                             //2表示第一个问号的内容
pstm.executeUpdate();                                 //增删改使用
ResultSet rs = pstm.executeQuery();                          //查使用
while(rs.next()){
  int userid = rs.getInt(“userid”);                            //取查询结果集合中的“userid”列,取为整型
  String password = rs.getString(“password”);           //取查询结果集合中的“password”列,取为字符串型
}

说明:

由此可以看出数据库的操作书写格式由编程语言决定而不由数据库决定;同种语言操作不同数据库其语言风格基本是一致的。

另外Oracle好像是有缓存机制,在活动窗口中增删改了数据只有退出窗口后在其他地方才会看到改变(至少sqlplus是如此)。

First (一对一) 

    首先我来说下一对一的理解,就是一个班主任只属于一个班级,一个班级也只能有一个班主任。好吧这就是对于一对一的理解

怎么来实现呢?

这里我介绍了两种方式:

   一种是:使用嵌套结果映射来处理重复的联合结果的子集   

  另一种呢是:通过执行另外一个SQL映射语句来返回预期的复杂类型

复制代码

<mapper namespace=“com.yc.mybatis.test.classMapper”>

    <!-- 方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
             封装联表查询的数据(去除重复的数据)
     select \* from class c, teacher t where c.teacher\_id=t.t\_id and c.c\_id=1 \-->

<select id\="getClass" parameterType\="int" resultMap\="getClassMap"\> select \* from class c, teacher t  where c.teacher\_id = t.t\_id and c.teacher\_id=#{id} </select\>

<!-- resultMap:映射实体类和字段之间的一一对应的关系 \-->
<resultMap type\="Classes" id\="getClassMap"\>
    <id property\="id" column\="c\_id"/>   
    <result property\="name" column\="c\_name"/>
    <association property\="teacher" javaType\="Teacher"\>   
        <id property\="id" column\="t\_id"/>
        <result property\="name" column\="t\_name"/>
    </association\>
</resultMap\>

 <!-- 方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型
     SELECT \* FROM class WHERE c\_id=1;
     SELECT \* FROM teacher WHERE t\_id=1   //1 是上一个查询得到的teacher\_id的值
     property:别名(属性名)    column:列名 \-->
      <!-- 把teacher的字段设置进去 \-->
<select id\="getClass1" parameterType\="int" resultMap\="getClassMap1"\> select \* from class where c\_id=#{id} </select\>

<resultMap type\="Classes" id\="getClassMap1"\>
    <id property\="id" column\="c\_id"/>   
    <result property\="name" column\="c\_name"/>
    <association property\="teacher" column\="teacher\_id" select\="getTeacher"/>   
</resultMap\>
<select id\="getTeacher" parameterType\="int" resultType\="Teacher"\> select t\_id id,t\_name name from teacher where t\_id =#{id} </select\>

</mapper>

复制代码

  这里对assacation标签的属性进行解释一下:

property

对象属性的名称

javaType

对象属性的类型

column

所对应的外键字段名称

select

使用另一个查询封装的结果

这里ben层会发生变化 这个classes的被bean层会多一个Teacher的属性,并且增加的get,set方法。

Second (一对多)and (多对一)

     一对多又是怎么样理解呢?

     其实也很容易,一个顾客对应多个订单,而一个订单只能对应一个客户

     而反过来也就是多对一的形式了

多个订单表可以对应一个顾客,一个顾客是可以拥有多个订单的

其实说到底就是有点类似多个一对一的情况,所以多对一的配置基本和一对一的配置保持一样

一对多的xml配置:

复制代码

<mapper namespace=“com.yc.mapper.CustomerMapper”>

<resultMap type=“com.yc.m.Customer” id=“resultCustomerMap”>
<id column=“id” jdbcType=“INTEGER” property=“id” />
<result property=“address” column=“address”/>
<result property=“postcode” column=“postcode”/>
<result property=“sex” column=“sex”/>
<result property=“cname” column=“cname”/>
<collection property=“orders” ofType=“com.yc.m.Orders”>
<id property=“id” column=“id”/>
<result property=“code” column=“code”/>
</collection>

</resultMap>

<select id=“getCustomer” resultMap=“resultCustomerMap” parameterType=“int”> SELECT *
FROM t_customer
WHERE id=#{id} </select>
</mapper>

复制代码

在这里可以明显的看出多出了一个属性ofType,这个ofType的含义就是你collection所对应的是那个bean

当然在bean层中也会发生变化 ,这里在Customer的bean中嵌套一条语句

 private List orders;   //一个Customer 对应N多个Orders

Third (多对多)

      多对多又怎么理解呢?

             一个用户可以属于多个集体(家人,朋友,同学),当然一个集体也包含了多个用户

复制代码

<mapper namespace\="com.yc.bean.Group"\>  
    <!-- resultMap:结合标准javabean规范,能hashmap或arraylist所不能完成的更复杂的resultType \-->
    <resultMap type\="Group" id\="groupMap"\>  
        <id property\="id" column\="id" />  
        <result property\="name" column\="name" />  
        <result property\="createTime" column\="createdate" />  
    </resultMap\>  
  
    <resultMap type\="Group" id\="groupUserMap" <span style\="color:#ff0000;"\><strong\>extends</strong\></span\>\="groupMap"> <collection property\="user" ofType\="User"\>  
        <!--collection:聚集    用来处理类似User类中有List<Group> group时要修改group的情况   user代表要修改的是Group中的List<user> \-->
            <id property\="id" column\="userId" />  
            <result property\="name" column\="userName" />  
            <result property\="password" column\="password" />  
            <result property\="createTime" column\="userCreateTime" />  
        </collection\>  
    </resultMap\>  
  
  
      <select id\="selectAllGroup" resultMap\="groupMap"\> select \* from group\_info </select\>  
  
  
    <!-- 根据Group表中的id或name查询组信息和组内用户信息 \-->  
    <select id\="selectGroupUser" parameterType\="Long" resultMap\="groupUserMap"\> select u.id as userId,u.name as userName,  
        u.password,u.createtime as userCreateTime,  
        gi.id,gi.name,gi.createdate,gi.state from group\_info gi left  
        join user\_group ug on gi.id=ug.group\_id left join user u on  
        ug.user\_id=u.id  where gi.id = #{id} </select\>  
    <!-- 删除组与组内成员之间的对应关系 \-->  
    <delete id\="deleteGroupUser" parameterType\="UserGroupLink"\> delete from user\_group <where\>  
            <if test\="user.id != 0"\>user\_id = #{user.id}</if\>  
            <if test\="group.id != 0"\>and group\_id = #{group.id}</if\>  
        </where\>  
    </delete\>  
</mapper\>  

复制代码

这里还需要对user和group这两个bean之间的映射关系进行描述一下:

复制代码

package com.yc.deom; import java.util.Date; import com.yc.bean.Group; import com.yc.bean.User; /** * @describe: 描述User和Group之间的映射关系 */
public class UserGroupLink { private User user; private Group group; private Date createTime; public Date getCreateTime() { return createTime;
} public void setCreateTime(Date createTime) { this.createTime = createTime;
} public Group getGroup() { return group;
} public void setGroup(Group group) { this.group = group;
} public User getUser() { return user;
} public void setUser(User user) { this.user = user;
}
}

复制代码