SqlServerProxy的一些资料

SqlServerProxy的一些资料

下载地址:https://files.cnblogs.com/files/lyhabc/SqlServerProxy.rar

1、特性及实现原理

SqlServerProxy 特性介绍:

#-------#--------------------------------------------------------------------------------------#
|特性 |不需要版本升级就兼容SQL Server 2000 到 SQL Server 2014的所有版本。 |
|-------|--------------------------------------------------------------------------------------|
|特性2 |开箱即用:只需简单配置一下就能开始使用。 |
|-------|--------------------------------------------------------------------------------------|
|特性3 |内置命令能查看历史、实时、单个客户端的通信量、请求数、事务数等。 |
|-------|--------------------------------------------------------------------------------------|
|特性4 |开放请求分发处理接口,可基于业务需求自己定制,默认提供一个基于正则表达式匹配的实现。 |
#-------#--------------------------------------------------------------------------------------#

 

SqlServerProxy 实现原理

Proxy能解析SQLServer通信采用的TDS协议内容,因而能再次封装分发客户端的请求。但是,不负责数据同步,需要采用复制等技术来数据同步。没有分布式事务,只在通信层工作,不会往数据库中添加任何东西,还开放执行请求内容记录接口,方便对执行内容进行审计。

 

SqlServerProxy的其它说明:

不支持客户端连接时的加密选项为True,就是连接字符串中“Encrypt=True”,必须为False。

暂不支持使用TDS 4.2协议的客户端连接,TDS 4.2作为早期与Sybase兼容的通信协议,已很少使用。

使用MARS(多活动结果集)能正常连接到proxy,也能统计通信量信息,但暂不支持请求分发,将来会实现。

将请求分发到从服务器时,使用的上下文数据库是请求会话在主服务器上的上下文数据库名,如需不同名称,需要定制"ISlaveProvider"接口,详见“实践手册”。


最新版下载地址

http://www.projky.com/downloads/3a382709-d680-476c-853d-dc3af5c625de/SqlServerProxy.zip

最新版对.NET Framework版本要求降到了.NET 4,现在支持Windows XP和Windows Server 2003了,详见《安装及3分钟快速体验.txt》。


正则测试器


2、配置手册

1、“dbServers.xml” 配置SqlServerProxy监听的端口、主服务器从服务器的连接地址信息。

<?xml version="1.0" encoding="utf-8" ?>
<!--定义主数据库和从服务器等信息。-->
<akuma>
  <!-- 代理服务监听的端口信息,可以配置监听多个端口。 -->
  <proxys>
    <proxy>
        <!-- 可为IPV6 或 IPV4 的地址。 -->
        <property name="ipAddress">0.0.0.0</property>
          <property name="port">8999</property>
          <property name="backlog">1024</property>
      </proxy>
    <proxy>
        <property name="ipAddress">::</property>
          <property name="port">7999</property>
          <property name="backlog">1024</property>
      </proxy>
   </proxys>
  <!-- 主服务器的连接信息。 -->
  <master>
      <property name="ipAddress">127.0.0.1</property>
          <property name="port">1433</property>
          <property name="backlog">256</property>
    <!-- 权重,分0到10级。0级将那些能分发的请求都发送到从服务器。 -->
      <property name="weight">5</property>
  </master>
  <!-- 从服务器的连接信息。 -->
    <slaves>
        <slave>
            <property name="ipAddress">127.0.0.1</property>
              <property name="port">1433</property>
              <property name="backlog">256</property>
              <!-- 必须提供正确的登陆user 和 password, 就采用该帐号和密码登录从服务器及心跳检测。-->
              <property name="user">sa</property>
              <property name="password">admin</property>
              <!-- 权重,分0到10级。0级不会分发请求到从服务器。 -->
              <property name="weight">5</property>
        </slave>
    </slaves>
  <heartbeat>
    <!-- 心跳SQL语句,检测从服务器是否在线。如果主服务器宕了之后,整个proxy服务不可用。 -->
    <!-- 默认单位为秒,如果你以毫秒为单位,那么需要在后面跟ms单位,如1500ms即代表1.5s。 -->
    <!-- 心跳SQL执行的间隔。 -->
    <property name="keepalive">2</property>
    <!-- 超过以下时间服务器仍然没响应,就认为宕机了。 -->
    <property name="deadtime">30</property>
    <property name="heartSQL">SELECT HOST_NAME();</property>
  </heartbeat>
</akuma>

 


<akuma>\<proxys>下配置服务监听的端口,可以是IPV4或者IPV6的地址,还可包含多个监听地址。测试时,将测试程序的连接字符串修改为监听地址及端口就ok了。
<akuma>\<master>配置主服务器的IP地址及端口。该地址可为IPV4或IPV6地址,只能包含一个元素。权重必须位于0~10之间(包括0和10)。
<akuma>\<slaves>下配置从服务器的IP地址及端口。可包含任意个<slave>元素。
<akuma>\<heartbeat>从服务器心跳检测,心跳检测执行时采用的AppName为“SqlServerProxy Heartbeat”。

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Akuma.Contract</name>
    </assembly>
    <members>
        <member name="T:Akuma.Contract.Heartbeat.HeartbeatInterval">
            <summary>配置的心跳间隔参数。</summary>
        </member>
        <member name="P:Akuma.Contract.Heartbeat.HeartbeatInterval.KeepAlive">
            <summary>心跳SQL执行的间隔,单位毫秒。</summary>
        </member>
        <member name="P:Akuma.Contract.Heartbeat.HeartbeatInterval.DeadTime">
            <summary>超过以下时间服务器仍然没响应,就认为宕机了,单位毫秒。</summary>
        </member>
        <member name="P:Akuma.Contract.Heartbeat.HeartbeatInterval.HeartSql">
            <summary>执行的心跳语句。</summary>
        </member>
        <member name="T:Akuma.Contract.Heartbeat.IHeartbeat">
            <summary>心跳检测。</summary>
        </member>
        <member name="P:Akuma.Contract.Heartbeat.MonitorEntry.Conn">
            <summary>心跳连接字符串</summary>
        </member>
        <member name="P:Akuma.Contract.Heartbeat.MonitorEntry.SlaveId">
            <summary>心跳监视的服务器Id</summary>
        </member>
        <member name="P:Akuma.Contract.Heartbeat.MonitorEntry.Interval">
            <summary>心跳监视的间隔、下线时间、心跳Sql。</summary>
        </member>
        <member name="T:Akuma.Contract.Heartbeat.OnlineChangedEventArgs">
            <summary>标记监控项在线情况变化事件参数。</summary>
        </member>
        <member name="T:Akuma.Contract.Log.DefaultLogs">
            <summary>
              一个强类型的资源类,用于查找本地化的字符串等。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ResourceManager">
            <summary>
              返回此类使用的缓存的 ResourceManager 实例。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.Culture">
            <summary>
              使用此强类型资源类,为所有资源查找
              重写当前线程的 CurrentUICulture 属性。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.AcceptClientConnect">
            <summary>
              查找类似 {0} 连接到Proxy {1}。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientCorrectTerminated">
            <summary>
              查找类似 客户端 {0} 连接正常断开。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientProxyLoginSlaveSuccess">
            <summary>
              查找类似 客户端 {0} Proxy登陆到从服务器 {1} 成功。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientProxyMasterCorrectTerminated">
            <summary>
              查找类似 客户端 {0} Proxy所使用主服务器 {1} 连接正常断开。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientProxyMasterUnexpectedTerminated">
            <summary>
              查找类似 客户端 {0} Proxy所使用主服务器 {1} 连接意外中断。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientProxySlaveCorrectTerminated">
            <summary>
              查找类似 客户端 {0} Proxy所使用从服务器 {1} 连接正常断开。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientProxySlaveUnexpectedTerminated">
            <summary>
              查找类似 客户端 {0} Proxy所使用从服务器 {1} 连接意外中断。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientProxyTryConnectMasterFail">
            <summary>
              查找类似 客户端 {0} Proxy尝试连接到主服务器 {1} 失败。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientProxyTryLoginSlaveFail">
            <summary>
              查找类似 客户端 {0} Proxy尝试登陆到从服务器 {1} 失败。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ClientUnexpectedTerminated">
            <summary>
              查找类似 客户端 {0} 连接意外中断。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.DbServersConfigFileNotExist">
            <summary>
              查找类似 在运行目录下没有发现配置文件“dbServers.xml”。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.InternalCommand">
            <summary>
              查找类似 内置命令 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.MasterUseSlave">
            <summary>
              查找类似 主服务器会话 {0} 使用从服务器会话 {1}。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.NotSupportClientEncryption">
            <summary>
              查找类似 客户端 {0} 连接字符串启用了加密,必须关闭数据库连接的加密选项才能正常使用。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.NotSupportClientTdsVersion">
            <summary>
              查找类似 客户端 {0} 使用通信协议Tds4.2暂不支持,详情咨询开发商。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.ProxyAppOutOfRestricitDate">
            <summary>
              查找类似 SQLServer Proxy已过期,不能继续使用。详情咨询projky@126.com或1781575586@qq.com。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.RpcBatchLogFormat">
            <summary>
              查找类似 {0} 客户端 [{1}] {2} 事务 {3} 在 {4} 执行RPC [{5}] 个。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.RpcLogFormat">
            <summary>
              查找类似   RPC {0} 名称 {1} 参数 {2} 个。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.RpcParameterFormat">
            <summary>
              查找类似     参数 {0} 名称 {1}  类型 {2} 值 [{3}]。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.RulePatternCannotBeEmpty">
            <summary>
              查找类似 &quot;{0}&quot; 规则表达式字符串不能为空。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.SqlBatchLogFormat">
            <summary>
              查找类似 {0} 客户端 [{1}] {2} 事务 {3} 在 {4} 执行SQL [{5}]。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.DefaultLogs.StartListenEndPoint">
            <summary>
              查找类似 开始监听 {0}。 的本地化字符串。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Log.ExecuteEndPoint.ClientRemoteEndPoint">
            <summary>客户端连接到Proxy时使用的端点信息。</summary>
        </member>
        <member name="P:Akuma.Contract.Log.ExecuteEndPoint.ExecRemoteEndPoint">
            <summary>客户端请求被执行的主服务器或从服务器端点信息。</summary>
        </member>
        <member name="P:Akuma.Contract.Log.ExecuteEndPoint.ClientAppName">
            <summary>客户端请求被执行时使用的程序名称。</summary>
        </member>
        <member name="T:Akuma.Contract.Log.IBatchLog">
            <summary>客户端完整请求执行的SQL和RPC记录。</summary>
        </member>
        <member name="T:Akuma.Contract.Log.IRunLog">
            <summary>程序运行日志。</summary>
        </member>
        <member name="T:Akuma.Contract.Log.ISessionLog">
            <summary>通信时字节日志记录。</summary>
        </member>
        <member name="T:Akuma.Contract.Policy.ApplayOption">
            <summary>请求在从服务器上执行时的一些选项。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.ApplayOption.UseMasterSessionOption">
            <summary>
            默认为False。
            True,在从服务器执行查询前,将主服务器的@@OPTIONS选项同步到从服务器;False,不同步。
            仅同步ANSI_WARNINGS、ANSI_PADDING、ANSI_NULLS、ARITHABORT、ARITHIGNORE、QUOTED_IDENTIFIER 这几个选项。
            当UseMasterSessionOption为True,且CustomOptions != SessionOptions.None时,会导致冲突,优先采用CustomOptions提供的选项。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Policy.ApplayOption.CustomOptions">
            <summary>
            默认为SessionOptions.None。
            在从服务器执行查询前,将自定义的@@OPTIONS选项应用到从服务器。
            当UseMasterSessionOption为True,且CustomOptions != SessionOptions.None时,会导致冲突,优先采用CustomOptions提供的选项。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Policy.ApplayOption.UseMaserSessionDbName">
            <summary>
            默认为True。
            True,在从服务器执行查询前,将主服务器的数据库上下文名称同步到从服务器;False,不同步。
            当UseMaserSessionDbName为True,且CustomDbName != null时,会导致冲突,优先采用CustomDbName提供的数据库名称。
            UseMaserSessionDbName与CustomDbName必须至少一个有效。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Policy.ApplayOption.CustomDbName">
            <summary>
            默认null。
            在从服务器执行查询前,将从服务器数据库上下文切换到自定义的数据库名称。
            当UseMaserSessionDbName为True,且CustomDbName != null时,会导致冲突,优先采用CustomDbName提供的数据库名称。
            UseMaserSessionDbName与CustomDbName必须至少一个有效。
            </summary>
        </member>
        <member name="T:Akuma.Contract.Policy.ApplayResult">
            <summary>
            请求分发的结果。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Policy.ApplayResult.SlaveId">
            <summary>从服务器标识Id。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.ApplayResult.Option">
            <summary>在从服务器执行时的选项。</summary>
        </member>
        <member name="T:Akuma.Contract.Policy.ISlaveProvider">
            <summary>每一个客户端连接必须使用不同的ISlaveProvider对象。否则,会造成负载不均衡。</summary>
        </member>
        <member name="M:Akuma.Contract.Policy.ISlaveProvider.ApplyRpc(Akuma.Contract.Policy.RequestRpcCollection,Akuma.Contract.Policy.SessionInfo,Akuma.Contract.Policy.SlaveInfo[])">
            <summary>
            请求一个Rpc批处理。
            </summary>
            <param name="rpcColl">Rpc批处理。</param>
            <param name="sessionInfo">客户端会话的信息。</param>
            <param name="slaves">从服务器列表。</param>
            <returns>null,将请求分发到主服务器;非null值,根据从服务器Id将请求分发到对应从服务器。</returns>
        </member>
        <member name="M:Akuma.Contract.Policy.ISlaveProvider.ApplySql(Akuma.Contract.Policy.RequestSql,Akuma.Contract.Policy.SessionInfo,Akuma.Contract.Policy.SlaveInfo[])">
            <summary>
            请求一个Sql批处理。
            </summary>
            <param name="requestSql">Sql语句。</param>
            <param name="sessionInfo">客户端会话的信息。</param>
            <param name="slaves">从服务器列表。</param>
            <returns>null,将请求分发到主服务器;非null值,根据从服务器Id将请求分发到对应从服务器。</returns>
        </member>
        <member name="T:Akuma.Contract.Policy.RequestRpcCollection">
            <summary>
            Rpc批处理请求对象。Rpc请求一般只包含一个rpc,但特别情况下会包含多个rpc。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Policy.RequestRpcCollection.UseTransaction">
            <summary>
            当前Rpc批处理请求是否在事务中。
            </summary>
        </member>
        <member name="T:Akuma.Contract.Policy.RequestSql">
            <summary>
            Sql请求处理对象。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Policy.RequestSql.Text">
            <summary>
            请求执行的Sql语句。
            </summary>
        </member>
        <member name="P:Akuma.Contract.Policy.RequestSql.UseTransaction">
            <summary>
            当前sql请求是否在事务中。
            </summary>
        </member>
        <member name="T:Akuma.Contract.Policy.SessionInfo">
            <summary>客户端连接到Proxy时的上下文信息。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.AppName">
            <summary>客户端连接时指定的ApplicationName。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.InstanceName">
            <summary>客户端连接时指定的实例名。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.InitialCatalog">
            <summary>客户端连接时指定的初始化数据库。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.DbName">
            <summary>客户端连接正在使用的上下文数据库。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.IntegratedSecurity">
            <summary>True,使用Windows身份认证;False,使用SQL Server认证。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.LoginName">
            <summary>SQL Server认证时使用的登录名。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.HostName">
            <summary>客户端计算机名称。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.ClientRemoteEndPoint">
            <summary>客户端连接时使用的地址信息。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.SessionId">
            <summary>Proxy 内部使用的会话Id。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.MajorVersion">
            <summary>客户端通过连接协议所获得的SQL SERVER 主版本号。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.MinorVersion">
            <summary>客户端通过连接协议所获得的SQL SERVER 次版本号。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.BuildNum">
            <summary>客户端通过连接协议所获得的SQL SERVER Build Number</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.TdsVersion">
            <summary>客户端连接所使用的协议版本。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SessionInfo.MasterWeight">
            <summary>主服务器权重。</summary>
        </member>
        <member name="T:Akuma.Contract.Policy.SlaveInfo">
            <summary>从服务器配置的关键信息。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SlaveInfo.Status">
            <summary>On,从服务器当前在线;Off,从服务器当前宕机。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SlaveInfo.Address">
            <summary>从服务器的Ip地址信息。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SlaveInfo.Port">
            <summary>从服务器的端口信息。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SlaveInfo.Weight">
            <summary>从服务器进行负载的权重。</summary>
        </member>
        <member name="P:Akuma.Contract.Policy.SlaveInfo.SlaveId">
            <summary>从服务器具有的唯一标识Id。</summary>
        </member>
    </members>
</doc>

 



该配置文件只会在启动时加载一次,修改后必须重启服务才能生效。



2、“balanceRules.xml” 配置当客户端的SQL请求或Rpc请求到来时,将依据本配置决定是否将请求分发到从服务器。

<?xml version="1.0" encoding="utf-8" ?>
<!-- 定义基于正则表达式的负载分发匹配规则 -->
<akuma>
<!--
匹配顺序[hostname] -> [clientendpoint] -> [appname] -> [loginname] -> [dbname] -> [statement]

匹配结果:
按匹配顺序,先应用reject规则,如果任何一条reject匹配成功,对应的请求将只发送到主服务器。
然后,同样按匹配顺序,再应用accept规则,如果任何一条accept匹配成功,对应的请求将发送到从服务器。

可同时并存任意条reject或accept规则。
-->

<!-- 针对无参数化查询语句和参数化查询生成的sql语句。 -->
<!--参数化查询生成的语句,是执行sp_executesql存储过程,此规则匹配存储过程参数“@statement”。-->
<sqlbatch>
<hostname>
<!--
ignorecase = true,正则表达式忽略大小写。
ignorecase = false,正则表达式大小写敏感。
transaction=true,请求在事务下,才进行匹配。
transaction=false,请求不在事务下,才进行匹配。
transaction=all,请求不论是否在事务下,都进行匹配。

如果不指定,默认ignorecase=true,transaction=all

正则表达式字符串不允许为空,为空将导致程序异常。可以删除reject 或者 accept项。
-->
<!--<reject ignorecase="true" transaction="all">Win-MustSendToMaster1</reject>
<reject ignorecase="true" transaction="all">Win-MustSendToMaster2</reject>
<accept ignorecase="true" transaction="all">Win-MustSendtoSlave1</accept>
<accept ignorecase="true" transaction="all">Win-MustSendtoSlave2</accept>-->
</hostname>
<!--如果在Ipv4下,一般要匹配的字符串格式为“192.168.201.1:2653” ip地址加端口号-->
<clientendpoint>
<!--<reject ignorecase="true" transaction="all">172.16.888.888</reject>
<accept ignorecase="true" transaction="all">172.16.999.999</accept>-->
</clientendpoint>
<appname>
<!-- Management studio 默认不发送到从服务器。 -->
<reject ignorecase="true" transaction="All">^Microsoft SQL Server Management Studio|^SQL Server Profiler</reject>
<!--<accept>.NET readonly client</accept>
<accept>reporting readonly client</accept>-->
</appname>
<loginname>
<!--<reject>updateableUser</reject>
<accept>readonlyUser</accept>-->
</loginname>
<dbname>
<!--<reject>updateableDbName</reject>
<accept>readonlyDbName</accept>-->
</dbname>
<statement>
<!-- 排除改变数据、修改会话变量等-->
<reject>insert|update|delete|begin|save|commit|rollback|create|alter|drop|truncate|set|#|##|exec|execute</reject>
<accept ignorecase="true" transaction="false">\bSELECT\b</accept>
</statement>
</sqlbatch>

<!--针对 Rpc 请求。-->
<rpcbatch>
<hostname>
<!--<reject ignorecase="true" transaction="all">Win-MustSendToMaster</reject>
<accept ignorecase="true" transaction="all">Win-MustSendtoSlave</accept>-->
</hostname>
<clientendpoint>
<!--<reject ignorecase="true" transaction="all">172.16.888.888</reject>
<accept ignorecase="true" transaction="all">172.16.999.999</accept>-->
</clientendpoint>
<appname>
<!-- Management studio 默认不发送到从服务器。 -->
<reject ignorecase="true" transaction="All">^Microsoft SQL Server Management Studio|^SQL Server Profiler</reject>
<!--<accept>.NET readonly client</accept>-->
<!--<accept>reporting readonly client</accept>-->
</appname>
<loginname>
<!--<reject>updateableUser</reject>
<accept>readonlyUser</accept>-->
</loginname>
<dbname>
<!--<reject>updateableDbName</reject>
<accept>readonlyDbName</accept>-->
</dbname>
<rpcname>
<reject>^usp\w+By(Delete|Update)$</reject>
<accept transaction="false" ignorecase="true">^uspGet\w+$|^usp\w+BySelect$</accept>
</rpcname>
</rpcbatch>
</akuma>


所有的规则内容都是正在表达式,不允许为空。可将事务中的查询也分发到从服务器,但这要业务需求允许才可以。
reject规则:如果匹配成功,请求就会发送到主服务器。
accept规则:如果匹配成功,将按权重将请求发送到主服务器或者从服务器。
<akuma>\<sqlbatch>匹配无参的sql查询或参数化查询生成的语句(内部是执行sp_executesql存储过程,此规则匹配存储过程参数“@statement”)。-->
<akuma>\<rpcbatch>匹配的是Rpc请求。

特别提醒1:事务是不能够在主服务器开始再到从服务器结束的。必须尽量在主服务器开始和结束。对于“begin tran ..... commit tran”开始和结束都在一起的sql语句可以分发到从服务器,但对于先在主服务器执行“begin tran”再到从服务器执行“commit tran”的请求,客户端会报错。

特别提醒2:因为是采用正则表达式进行匹配,每个请求都要应用,所以配置的正则表达式必须注重效率,以免导致请求反应速度下降。

规则示例1:

<appname>
<reject transaction="All" ignorecase="true">^Microsoft SQL Server Management Studio|^SQL Server Profiler</reject>
</appname>

意味着无论是否在事务下(transaction="All"),忽略大小写(ignorecase="true"),凡是客户端应用程序名以"Microsoft SQL Server Management Studio"或"SQL Server Profiler"开头的请求都应该发送到主服务器。

规则示例2:

<statement>
<!-- 排除改变数据、修改会话变量等-->
<reject>insert|update|delete|begin|save|commit|rollback|create|alter|drop|truncate|set|#|##|exec|execute</reject>
<accept transaction="false" ignorecase="true">\bSELECT\b</accept>
</statement>

reject项意味着无论是否在事务下(默认的),忽略大小写(默认的),凡是包含关键字“insert|update|delete|begin|save|commit|rollback|create|alter|drop|truncate|set|#|##|exec|execute”的语句请求都应该发送到主服务器。
accept项意味着不在事务下(transaction="false"),忽略大小写,且包含“SELECT”单词的语句请求可分发到从服务器。

匹配顺序[hostname] -> [clientendpoint] -> [appname] -> [loginname] -> [dbname] -> [statement]
匹配结果: 按匹配顺序,先应用reject规则,如果任何一条reject匹配成功,对应的请求将只发送到主服务器。 然后,同样按匹配顺序,再应用accept规则,如果任何一条accept匹配成功,对应的请求将发送到从服务器。 可同时并存任意条reject或accept规则。

该配置文件修改后不必重启服务,而是实时生效。



3、 “SqlServerProxy.exe.config” 配置解析接口实现类的配置文件。SqlServerProxy.exe 服务启动时会读取该文件,如果解析失败,将不能正常使用服务。
IHeartbeat 接口即心跳检测接口,只检测从服务器。
IBatchLog 接口即请求执行记录接口,只记录Sql请求和Rpc请求,接口参数将包含执行sql的语句和rpc名称参数值等信息,可以单独实现该接口来审计通过SqlServerProxy的执行记录。
IRunLog 程序运行日志接口,包含客户端连接记录等。
ISessionLog 客户端和服务器响应时发送的网络包内容。
ISlaveProvider 接口即根据执行的Sql语句和rpc请求内容,选择从服务器的接口,决定读写分离,最为核心。默认提供了一个基于正则表达式的实现,但特定业务通过该接口定制,将能获得最大性能。

该配置文件修改后必须重启服务才能生效。

 

4、“Akuma.Imp.config” 配置默认的IBatchLog、IRunLog、ISessionLog记录的日志目录,和记录等级。

调整目录示例:
比如:<file value="${ALLUSERSPROFILE}\SqlServerProxy\apprun.log" /> 在本测试机上就是“C:\Users\All Users\SqlServerProxy\apprun.log”文件。需要将日志文件移动到“D:”下,就可以更改为:<file value="D:\Logs\SqlServerProxy\apprun.log" />

调整记录等级能让更多或更少记录被记下来。ISessionLog 和 IBatchLog 默认实现必须为“All”或“Debug”级别才记录,默认不记录。
调整记录等级示例:
<logger name="Akuma.Imp.IBatchLogDefault.BatchLog">
<level value="WARN" />
<appender-ref ref="BatchLogFileAppender" />
</logger>
更改为要记录请求日志内容:
<logger name="Akuma.Imp.IBatchLogDefault.BatchLog">
<level value="ALL" />
<appender-ref ref="BatchLogFileAppender" />
</logger>

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
  </configSections>
  <!-- This section contains the log4net configuration settings -->
  <log4net>
    <appender name="BatchLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="${ALLUSERSPROFILE}\SqlServerProxy\sql_rpc_batch.log" />
      <appendToFile value="true" />
      <MaxSizeRollBackups  value="0"/>
      <maximumFileSize value="50MB" />
      <!--<Threshold value="WARN"/>-->
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message%newline" />
      </layout>
    </appender>
    <appender name="RunLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="${ALLUSERSPROFILE}\SqlServerProxy\apprun.log" />
      <appendToFile value="true" />
      <MaxSizeRollBackups  value="0"/>
      <!--<Threshold value="WARN"/>-->
      <maximumFileSize value="50MB" />
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message%newline" />
      </layout>
    </appender>
    <appender name="SessionLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="${ALLUSERSPROFILE}\SqlServerProxy\request_response.log" />
      <appendToFile value="true" />
      <MaxSizeRollBackups  value="0"/>
      <maximumFileSize value="500MB" />
      <!--<Threshold value="WARN"/>-->
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message" />
      </layout>
    </appender>
    <logger name="Akuma.Imp.IBatchLogDefault.BatchLog">
      <level value="WARN" />
      <appender-ref ref="BatchLogFileAppender" />
    </logger>
    <logger name="Akuma.Imp.IRunLogDefault.RunLog">
      <level value="ALL" />
      <appender-ref ref="RunLogFileAppender" />
    </logger>
    <logger name="Akuma.Imp.ISessionLogDefault.SessionLog">
      <level value="WARN" />
      <appender-ref ref="SessionLogFileAppender" />
    </logger>
  </log4net>
</configuration>

 


该配置文件修改后必须重启服务才能生效。


3、安装及3分钟快速体验

SqlServerProxy 环境要求:

.NET 4 Framework,.NET 3.5 Framework(两个同时装,现在支持Windows XP和Windows Server 2003了)。

安装:运行解压后的“InstallSvc.bat”,允许管理员权限即能成功安装,并自动启动SqlServerProxy服务。

卸载:运行解压后的“UninstallSvc.bat”,允许管理员权限即能成功卸载。

用vbs来进行自动化安装和配置

cacls
显示或修改任意访问控制列表 (ACL) 文件。
关于C盘整个盘符被锁定的解决办法
当使用这个命令 cacls c:\ /p administrator:N 后,你将失去对系统C盘控制权.这时,一般的方法
cacls c: /p administrator:F 命令恢复权限已经不可行了,可以用下面的一个方法尝试:

无getadmin.vbs这个文件

安装服务 installutil 

set dir = %cd%
SET PATH = %PATH%,%dir%

@echo off

REM ________________________________________________________________

>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"

if '%errorlevel%' NEQ '0' (

    echo 请求管理员权限...

    goto UACPrompt

) else ( goto gotAdmin )

:UACPrompt

    echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"

    echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"

    "%temp%\getadmin.vbs"

    exit /B

:gotAdmin

    if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )

    pushd "%CD%"

    CD /D "%~dp0"

REM ________________________________________________________________


@echo 安装服务
%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil SqlServerProxy.exe

@echo 启动服务
net start SqlServerProxy

pause

 



3分钟快速体验:

#-----------#-----------------#------#------------------#--------------#
| |ip地址 |端口 | 机器名 |SQLServer版本 |
|-----------|-----------------|------|------------------|--------------|
|主服务器 |192.168.201.134 |1433 |WIN-D5FC592I20C |08R2 |
|-----------|-----------------|------|------------------|--------------|
|从服务器 |192.168.201.1 |1433 | AKUMA-PC |08R2 |
#-----------#-----------------#------#------------------#--------------#
从服务器登陆名及密码 cqUser cqPass@12ns

以上是测试环境,SqlServerProxy运行在主服务器上。


第1步:编辑“dbServers.xml”,设置测试环境的值。仅列出关键部分。
#----------------------------------------------------------------------#

<proxys>
<proxy>
<!-- 使用“0.0.0.0”更方便 -->
<property name="ipAddress">0.0.0.0</property>
<property name="port">8999</property>
<property name="backlog">1024</property>
</proxy>
</proxys>
<master>
<property name="ipAddress">192.168.201.134</property>
<property name="port">1433</property>
<property name="backlog">256</property>
<!-- 主服务器权重设置为0,方便将符合条件的请求都分发到从服务器。-->
<property name="weight">0</property>
</master>
<slaves>
<slave>
<property name="ipAddress">192.168.201.1</property>
<property name="port">1433</property>
<property name="backlog">256</property>
<!-- 必须提供一个可用的账户。-->
<property name="user">cqUser</property>
<property name="password">cqPass@12ns</property>
<property name="weight">5</property>
</slave>
</slaves>

#----------------------------------------------------------------------#

这样配置后,SqlServerProxy运行后将在主服务器上监听 8999 端口。

第2步:编辑"balanceRules.xml", 去掉对连接程序名“Microsoft SQL Server Management Studio”的限制。仅列出关键部分。
#----------------------------------------------------------------------#

<sqlbatch>
<appname>
<!-- 去掉对连接程序名“Microsoft SQL Server Management Studio”,默认配置文件只修改这一处。 -->
<reject ignorecase="true" transaction="All">^SQL Server Profiler</reject>
</appname>
<statement>
<reject>insert|update|delete|begin|save|commit|rollback|create|alter|drop|truncate|set|#|##|exec|execute</reject>
<accept ignorecase="true">\bSELECT\b</accept>
</statement>
</sqlbatch>

#----------------------------------------------------------------------#

第3步:重启SqlServerProxy服务(在“services.msc”名称显示为“SQL Server Proxy Service”),或者先运行“UninstallSvc.bat”再运行“InstallSvc.bat”重装。

第4步:使用“Management Studio”登陆到“192.168.201.134,8999”。

执行“SELECT @@SERVERNAME;”,可以看到返回结果为“AKUMA-PC”,没有reject规则匹配成功,而accept规则匹配成功了,是在从服务器上执行的。

执行“exec('SELECT @@SERVERNAME');”,因为包含了关键字“exec”,reject规则匹配成功,在主服务器执行,返回结果为“WIN-D5FC592I20C”。

执行“SELECT @@SERVERNAME; exec('SELECT @@SERVERNAME');” 同样因为包含关键字“exec”,reject规则匹配成功,在主服务器执行,返回结果为两个“WIN-D5FC592I20C”。


通过这个小测试,就能看到,通过简单的配置,能将请求分发到从服务器上去。

 


4、实践手册

目录:
1、内置命令
2、正则表达式测试器
3、怎么使用单机版的Proxy
4、虚拟ip结合的技巧
5、自定义SQL语句前缀来实现分发
6、业务需要,自己定制请求分发实现

 

1、内置命令

内置命令是仅连接到SqlServerProxy端口执行的特定sql语句,用来查看从服务器状态和会话历史等。命令是大小写敏感的,且固定大小,不能包含多余空格等,只能单次执行 一个命令。

命令1、“SELECT vt_active;” 查看当前活动连接信息。
返回结果解析:

#------------------#---------------------------------------#----------------------------------------------------#
|列名 |示例值 |客户端属性 |
|------------------|---------------------------------------|----------------------------------------------------|
|endpoint |192.168.201.1:34298 |连接时的远程ip及端口。 |
|------------------|---------------------------------------|----------------------------------------------------|
|app_name |Microsoft SQL Server Management Studio |应用程序名。 |
|------------------|---------------------------------------|----------------------------------------------------|
|to_client |2.55MB/5.23MB |2.55MB从服务器返回数据量,5.23MB返回客户端总数据量。|
|------------------|---------------------------------------|----------------------------------------------------|
|from_client |12.26KB/25.88KB |12.26KB到从服务器的数据量,25.88客户端发送总数据量。|
|------------------|---------------------------------------|----------------------------------------------------|
|sqlbatch_count |19 |执行SQL请求数。 |
|------------------|---------------------------------------|----------------------------------------------------|
|rpcbatch_count |1 |执行Rpc请求数。 |
|------------------|---------------------------------------|----------------------------------------------------|
|transaction_count |0 |执行事务数。 |
|------------------|---------------------------------------|----------------------------------------------------|
|command_count |6 |执行内置命令数。 |
|------------------|---------------------------------------|----------------------------------------------------|
|mars |False |True,启用Mars(多活动结果集);False,没启用。 |
|------------------|---------------------------------------|----------------------------------------------------|
|last_sql |select HOST_NAME(); |最后执行SQL(只包括最前面的256个字符)。 |
|------------------|---------------------------------------|----------------------------------------------------|
|last_rpc |UpdateTask |最后执行的Rpc名称。 |
#------------------#---------------------------------------#----------------------------------------------------#

特别说明:
to_client 前半部分代表了从服务器返回客户端的数据量,后半部分代表了从服务器+主服务器返回客户的数据量,两者的比值越大,从服务器分担的负载越多。
from_client 前半部分代表了客户端到从服务器的数据量,后半部分代表了客户端到从服务器+主服务器的数据量,两者的比值越大,从服务器分担的负载越多。
transaction_count 仅统计客户通过如"sqlConn.BeginTransaction();"开始的事务数,不统计如执行SQL“begin tran”引发的事务。

命令2、“SELECT vt_realtime;” 查看当前实时汇总连接信息。
返回结果解析:

#------------------#---------------------------------------#----------------------------------------------------#
|列名 |示例值 |SqlServerProxy在时间窗口内统计值 |
|------------------|---------------------------------------|----------------------------------------------------|
|span_time |21:46:58 - 21:47:08 |实时统计数据的时间窗口,为10秒。 |
|------------------|---------------------------------------|----------------------------------------------------|
|to_client |43.25MB/85.23MB |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|from_client |88.26KB/278.88KB |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|session_count |3 |新建立了3个会话。 |
|------------------|---------------------------------------|----------------------------------------------------|
|sqlbatch_count |58 |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|rpcbatch_count |20 |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|transaction_count |30 |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|command_count |3 |参见命令1。 |
#------------------#---------------------------------------#----------------------------------------------------#

命令3、“SELECT vt_history;” 查看当前SqlServerProxy历史运行统计信息。
返回结果解析:

#------------------#---------------------------------------#----------------------------------------------------#
|列名 |示例值 |SqlServerProxy启动后的历史统计 |
|------------------|---------------------------------------|----------------------------------------------------|
|run_time |0.12:37:10 |启动服务后连续运行了0天12小时37分10秒。 |
|------------------|---------------------------------------|----------------------------------------------------|
|to_client |70.58MB/138.84MB |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|from_client |24.15MB/54.67MB |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|session_count |92 |参见命令2。 |
|------------------|---------------------------------------|----------------------------------------------------|
|sqlbatch_count |2098 |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|rpcbatch_count |1673 |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|transaction_count |3200 |参见命令1。 |
|------------------|---------------------------------------|----------------------------------------------------|
|command_count |63 |参见命令1。 |
#------------------#---------------------------------------#----------------------------------------------------#

命令4、“SELECT vt_slave;” 查看SqlServerProxy配置的从服务器是否在线(即心跳检测的结果)。
返回结果解析:

#------------------#---------------------------------------#----------------------------------------------------#
|列名 |示例值 |SqlServerProxy配置的从服务器属性 |
|------------------|---------------------------------------|----------------------------------------------------|
|endpoint |192.168.201.1:1433 |从服务器的ip及端口。 |
|------------------|---------------------------------------|----------------------------------------------------|
|status |True |True,在线;False,宕机了。 |
#------------------#---------------------------------------#----------------------------------------------------#

命令5、“SELECT vt_server;” 查看运行SqlServerProxy服务的服务器信息
返回结果解析:

#------------------#---------------------------------------#----------------------------------------------------#
|列名 |示例值 |服务器信息 |
|------------------|---------------------------------------|----------------------------------------------------|
|date |2014-10-14 22:15:06.807 |服务器时间 |
|------------------|---------------------------------------|----------------------------------------------------|
|memory |2.86GB/4.00GB |共有4Gb内存,已使用2.86Gb。 |
|------------------|---------------------------------------|----------------------------------------------------|
|run_time |1.12:25:13 |运行时间。 |
|------------------|---------------------------------------|----------------------------------------------------|
|app_memory |64.88MB |SqlServerProxy使用内存量 |
#------------------#---------------------------------------#----------------------------------------------------#

 

2、正则表达式测试器

用来测试输入字符串对正则表达式是否匹配,以及将表达式在转义字符和正常字符间的转换。

Expression:在“Pattern”文本框中输正则表达式,在“Input”处输测试字符串,点击“Go”,出现“Yes”表达匹配,出现“No”表达不匹配。如果匹配成功,会计算执行匹配100次所用的毫秒数。

Escaping:在“Plain”文本框中输入要转义的正则表达式,自动在“Escaping”中出现转义后的字符串。反正也然。

 

3、怎么使用单机版的Proxy

使用单机可以统计SqlServerProxy所执行的会话信息、及对执行请求的审计、验证TDS解析效果等。
方法是在“dbServers.xml”中去掉所有“slave”元素即可。
<akuma>
<slaves>
<!-- 此处留空。 -->
</slaves>
</akuma>


4、虚拟ip结合的技巧

添加虚拟Ip的方法参见《手工添加虚拟IP的实现方法.pdf》或博客:http://www.cnblogs.com/ProJKY/p/VirtualIPAddressImp.html

技巧1:在运行SqlServerProxy服务的服务器上添加虚拟Ip,客户端连接字符串中的DataSource就执行该虚拟Ip,方便切换SqlServerProxy服务的服务器。

技巧2:在“dbServers.xml”使用从服务器的虚拟Ip地址,也方便切换从服务器地址。压力小了,直接删除虚拟Ip地址即可。

技巧3:因为配置文件“dbServers.xml”只加载一次,所以后续所加从服务器不能够识别,但可以添加多个虚拟Ip,将来压力大了,再将这些虚拟Ip放到从服务器上,心跳检测能快速检测出使用虚拟Ip的从服务器上线了,而将请求分发到这些新上线的虚拟Ip从服务器上。

 

5、自定义SQL语句前缀来实现分发

使用该方法要求能修改客户端执行SQL请求的代码,但能避免正则表达式匹配速度慢的问题。

例如在执行只读可分发到从服务器执行的SQL语句前加前缀"--SqlServerProxyReadonly",对应“balanceRules.xml”里面添加一条accept规则“<accept>^--SqlServerProxyReadonly<accept>”能加快将只读查询分发到从服务器。类似的添加前缀如“--SqlServerProxyMaster”,对应“balanceRules.xml”里面添加一条reject规则“<reject>^--SqlServerProxyMaster<reject>”能加快将不符合条件的Sql语句分发到主服务器上。

 

6、业务需要,自己定制请求分发实现

请求分发在SqlServerProxy中被抽象为“ISlaveProvider”接口,位于“Akuma.Contract”程序集中。

实现“ISlaveProvider”接口的要点位于接口签名中,实现接口后,再修改“SqlServerProxy.exe.config”文件即可。


 

command.sql

SELECT vt_active;

SELECT vt_realtime;

SELECT vt_history;

SELECT vt_slave;

SELECT vt_server;

SELECT N'command' as Col

 

 


 

关于SqlServerProxy

本压缩包的程序将在2015.5.1日过期,过期后将不能继续使用。如需测试,请将数据库和运行Proxy服务的系统时间调到前面。

如果您觉得这些特性不够用或者Bug,请联系projky@126.com。

 


 

配置程序连接代理中间件字符串

假设你的SqlServerProxy运行在192.168.2.136上,在dbServers.xml配置的代理监听如下:

<?xml version="1.0" encoding="utf-8" ?>
<!--定义主数据库和从服务器等信息。-->
<akuma>
<!-- 代理服务监听的端口信息,可以配置监听多个端口。 -->
<proxys>
<proxy>
<!-- 可为IPV6 或 IPV4 的地址。 -->
<property name="ipAddress">0.0.0.0</property>
<property name="port">8999</property>
<property name="backlog">1024</property>
</proxy>

那么根据配置,代理监听的端口号就是:8999,那么字符串中Data Source就是192.168.2.136,8999。

#-------------------------------------------------#-------------------------------------------------------------#
| 希望SqlServerProxy采用Windows认证连接主服务器 |Data Source=192.168.2.136,8999;Integrated Security = SSPI; |
| |Initial Catalog = myDataBase;Encrypt=False; |
|-------------------------------------------------|-------------------------------------------------------------|
| 希望SqlServerProxy采用SqlServer认证连接主服务器 |Data Source=192.168.2.136,8999;Initial Catalog = myDataBase; |
| |User ID = sa;Password=samplepassword;Encrypt=False; |
#-------------------------------------------------#-------------------------------------------------------------#

连接字符串里面的“Encrypt=False”可以不写,但不能为“Encrypt=True”,详情参见文档“1、特性及实现原理.txt”。



master_slave.png


手工添加虚拟IP的实现方法.pdf

f

f

f

f

f

f

比较核心的技术了 虚拟ip的一种实现方式(手工添加和C#添加)

需要删除一个虚拟IP的话,在string[] ip 中去掉虚拟IP和对应的子网掩码就ok了。经测试,和手工添加的效果一样,能ping通。

版权归博客园和作者共有。.NET类库源码参考 扣丁格鲁 www.projky.com,转载请注明出处。
f

    虚拟IP技术在高可用领域像数据库SQLSERVER、web服务器等场景下使用很多,很疑惑它是怎么实现的,偶然,发现了一种方式可以实现虚拟ip。它的原理在于同一个物理网卡,是可以拥有多个ip地址的,至于虚拟网卡,也可用通过该方式拥有多个ip。下面,就拿安装vmware后的虚拟网卡做例子,该网络连接已具有192.168.32.1的ip,尝试添加一个192.168.32.6的虚拟ip地址,介绍这两种方式。

   第一种,手工添加

   1、打开控制面板,在想要添加虚拟ip的网络连接上右键,选择属性。在打开的属性窗口中选中“此连接使用下列项目”中的“Internet 协议版本 4 (TCP/IPv4)”。

    

    2、点击“属性”按钮,进入“Internet 协议版本 4(TCP/IPv4)属性”窗口,可以看到,该网络连接设置了固定的ip地址为192.168.32.1。

    3、点击“高级”按钮,进入“高级TCP/IP设置”窗口。

    4、点击在IP地址栏中的添加按钮,弹出添加IP地址的窗口,输入192.168.32.6即可,子网掩码自动生成。

    5、点击添加按钮后,一步一步点击确定按钮,最后,尝试ping一下该虚拟ip地址。能ping通,则成功了。

 

 第二种, C#添加虚拟IP

    采用编程方式,可以自动添加和移除该虚拟IP地址,所谓的虚拟IP地址的转移就是这么来实现的。当拥有虚拟IP的机器故障时,在故障机器上尝试删除该虚拟IP并在健康机器上重新添加该虚拟IP。具体的实现就相当复杂了,下面,就列出添加虚拟IP的代码,供参考。

    需要在该项目上引用“System.Management”程序集。

复制代码
using System;
using System.Management;

namespace ProJKY{

    public class AddVirtualIpAddress {
        public void Test() {
            ManagementClass findAdapters =
                            new ManagementClass("Win32_NetworkAdapterConfiguration");
            ManagementObjectCollection adapters = findAdapters.GetInstances();

            foreach (ManagementObject adapter in adapters) {
                // 查找符合条件的网卡
                if ((string)adapter["ServiceName"] != "VMware Virtual Ethernet Adapter for VMnet8")
                    continue;

                string[] ip = new string[] { "192.168.32.1","192.168.32.6"};
                string[] mask = new string[] { "255.255.255.0","255.255.255.0"};

                try{
                    ManagementBaseObject newIP = adapter.GetMethodParameters("EnableStatic");

                    newIP["IPAddress"] = ip;
                    newIP["SubnetMask"] = mask;

                    ManagementBaseObject setIP = adapter.InvokeMethod("EnableStatic", newIP, null);

                    UInt32 result = (UInt32)(setIP["returnValue"]);
                }
                catch (Exception) {
                    throw;
                }
            }
        }
    }
}
复制代码

 


ISlaveProvider.sample

using Akuma.Contract.Heartbeat;
using Akuma.Contract.Policy;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace CustomSlaveProvider {
    [Serializable]
    public class SlaveProvider : ISlaveProvider {
        public ApplayResult ApplyRpc(RequestRpcCollection rpcColl, SessionInfo sessionInfo, SlaveInfo[] slaves) {
            bool isSp_executesql = rpcColl.All(rpc => rpc.Name.Equals(SystemRpc.SP_EXECUTESQL, StringComparison.OrdinalIgnoreCase));
            bool accept = false;

            if (isSp_executesql) {    // 如果执行的是参数化查询
                foreach (var rpc in rpcColl) {
                    if (rpc.Parameters.Length == 0) {
                        accept = false;
                        break;
                    }

                    string statement = rpc.Parameters[0].Value as String;
                    if (String.IsNullOrEmpty(statement) == true) {
                        accept = false;
                        break;
                    }

                    RequestSql requestSql = new RequestSql(rpc.Parameters[0].Value as String, rpcColl.UseTransaction);
                    // _ruleService 是默认实现的正则表达式匹配服务
                    accept = _ruleService.AcceptSql(requestSql, sessionInfo);
                    if (accept == false) {
                        break;
                    }
                }
            } else {
                accept = _ruleService.AcceptRpc(rpcColl, sessionInfo);
            }

            if (accept == false) {
                return null;   // 返回null,将会分发到主服务器执行
            }

            // 主服务器也有权重,按权重是否该在主服务器上执行
            bool turnToMaster = SholdTurnToMaster(sessionInfo.MasterWeight, slaves);

            if (turnToMaster == true) {
                return null;
            }

            SlaveInfo currentSlave = slaves.FirstOrDefault(slave => _currentSlaveResult != null && slave.SlaveId == _currentSlaveResult.SlaveId && slave.Status == OnlineStatus.On);
            if (currentSlave != null) {    // 如果可以到从服务器执行,那么尽量返回上一次分配的从服务器,避免不必要的网络流量。
                return _currentSlaveResult;
            }

            _currentSlaveResult = GetRandomIndexByWeight(slaves);    // 根据权重,计算该分配到那个从服务器

            return _currentSlaveResult;
        }

        public ApplayResult ApplySql(RequestSql requestSql, SessionInfo sessionInfo, SlaveInfo[] slaves) {
            bool accept = _ruleService.AcceptSql(requestSql, sessionInfo);

            if (accept == false) {
                return null;
            }

            bool turnToMaster = SholdTurnToMaster(sessionInfo.MasterWeight, slaves);

            if (turnToMaster == true) {
                return null;
            }

            SlaveInfo currentSlave = slaves.FirstOrDefault(slave => _currentSlaveResult != null && slave.SlaveId == _currentSlaveResult.SlaveId && slave.Status == OnlineStatus.On);
            if (currentSlave != null) {
                return _currentSlaveResult;
            }

            _currentSlaveResult = GetRandomIndexByWeight(slaves);

            return _currentSlaveResult;
        }

        bool SholdTurnToMaster(int masterWeight, SlaveInfo[] slaves) {
            int length = masterWeight + slaves.Where(slave => slave.Status == OnlineStatus.On).Sum(slave => slave.Weight);
            int index = _random.Next(0, length);

            if (index < masterWeight && masterWeight > 0) {
                return true;
            }

            return false;
        }

        ApplayResult GetRandomIndexByWeight(SlaveInfo[] slaves) {
            SlaveInfo[] onlineSlaves = slaves.Where(slave => slave.Status == OnlineStatus.On).ToArray();

            List<SlaveInfo> list = new List<SlaveInfo>();
            foreach (var slave in onlineSlaves) {
                list.AddRange(Enumerable.Repeat(slave, slave.Weight));
            }
            if (list.Count == 0) {
                return null;
            }
            int index = _random.Next(0, list.Count);
            string slaveId = list[index].SlaveId;

            ApplayResult applayResult = new ApplayResult();
            applayResult.SlaveId = slaveId;
            return applayResult;
        }

        /// <summary>
        /// 当前会话的从服务器Id。
        /// 每个SqlConnection连接就是一个会话,会话的从服务器确定后,就尽量不要修改,避免不必要的网络通信。
        /// 如果修改了,会导致额外的网络通信。
        /// </summary>
        ApplayResult _currentSlaveResult = null;

        Random _random = new Random();
    }
}

 


优点

1、通过正则表达式来匹配 

2、负载均衡

3、读写分离

4、会记录所有通过proxy 执行的sql到日志里

5、实时心跳检测从库是否宕机,如果宕机,命令不转发给从库

6、可以解释TDS协议数据包(最核心)

7、可能有自动摘除宕机的从库,没有测试过

 

TDS协议解释下载:https://files.cnblogs.com/files/lyhabc/%5BMS-TDS%5D--2.pdf

 

 

缺点

1、事务都在主库完成,不能跨越主从

2、只能统计C#发起的事务,其他语言不行,TSQL也不行

3、不包含自动故障转移,主服务器宕机,整个proxy服务不可用

4、只转发不理会两边数据是否一致,例如 :MySQL的binlog对比

5、主从数据库名称一定要相同

6、不支持加密连接

7、不支持分库分表

8、多字符集不支持

9、不支持黑白名单,它可以让我们自定义危险语句,比如delete忘了加where这样的SQL

10、统计的SQL长时间运行影响性能,并且阻止其运行,有统计,无阻止

11、无统一配置界面接口,直接修改XML文件来进行配置,XML配置文件比较分散 


匹配顺序[hostname] -> [clientendpoint] -> [appname] -> [loginname] -> [dbname] -> [statement]

SQL匹配:insert|update|delete|begin|save|commit|rollback|create|alter|drop|truncate|set|#|##|exec|execute

 

 

源代码卖5万人民币

除了最核心的功能:可以解释TDS协议数据包 ,其他都是比较简单的功能,所以卖5万人民币不值得

测试的时候,SQL Server Proxy Service服务启动失败,测试无做好

 

posted @ 2015-04-15 11:04  桦仔  阅读(1811)  评论(0编辑  收藏  举报