• SqlServerProxy的一些资料


    SqlServerProxy的一些资料

    下载地址:http://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">SELECT</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>^uspw+By(Delete|Update)$</reject>
    <accept transaction="false" ignorecase="true">^uspGetw+$|^uspw+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">SELECT</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}SqlServerProxyapprun.log" /> 在本测试机上就是“C:UsersAll UsersSqlServerProxyapprun.log”文件。需要将日志文件移动到“D:”下,就可以更改为:<file value="D:LogsSqlServerProxyapprun.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}SqlServerProxysql_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}SqlServerProxyapprun.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
    equest_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%system32cacls.exe" "%SYSTEMROOT%system32configsystem"
    
    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.NETFrameworkv4.0.30319installutil 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">SELECT</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协议解释下载:http://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服务启动失败,测试无做好

  • 相关阅读:
    MySql 定时完成备份
    smarty插件
    PHP字符串函数小结
    eclipse搭建maven project的spring4 spring mvc mybatis
    C#数组存入引用类型
    C#数组
    【转】Linus:利用二级指针删除单向链表
    [LeetCode] Reverse Linked List II
    ubuntu配置git
    mint安装Node.js
  • 原文地址:https://www.cnblogs.com/lyhabc/p/4427890.html
Copyright © 2020-2023  润新知