Monday, April 1, 2013

How to Configure EPiServer Load Balancing with net.tcp

Update: See the note at the bottom of this post in regards to storing custom data in the EPiServer cache.

EPiServer load balancing configuration is critical for large scale enterprise deployments. It allows you to have multiple IIS servers that all connect to same database.

There are three main models that can be used when using EPiServer load balancing.

1. Performance – When most people hear the term "load balancing" they think of the performance benefits that can be obtained. Using the performance model you will be splitting the incoming connections up between two or more IIS servers – each one running a copy of the web application. Using this model requires a minimum of two IIS servers. A hardware load balancer is normally also involved.



2. Security – Most people don’t think of using EPiServer load balancing to help with security, but I’ve found this is actually one of the primary benefits of load balancing. The idea here is to have one dedicated CMS IIS server, and one or more public facing IIS servers. The CMS IIS Server is normally only accessible via the company’s internal network. The public facing server(s) have the CMS edit/admin mode completely disabled. This model requires a minimum of two IIS servers.



3. Performance and Security – A hybrid of the two above models, requires a minimum of three IIS servers.



Note that technically you can also performance load balance the CMS IIS severs, but this is overkill in most cases. Unless of course you have an army of CMS editors maintaining the site.

There are already other blog posts on how to setup EPiServer load balancing using the preferred method, UDP Multicasting. I would be happy to create another blog post of this if there is demand, but the focus of this blog post is how to setup EPiServer load balancing when UPD Multicasting is not an option. In this case you need to use net.tcp.

I've had the privilege of setting up all three of the above models with both UDP multicast load balancing and net.tcp load balancing. You should always try UDP multicasting first, because the configuration is far easier. If you discover (to your horror) that UPD MC is not an option, you’ll have to bite the bullet, have a strong drink, and go the net.tcp route. Here’s a picture of me after I discovered that UDP multicasting wasn’t an option in our latest project.



Disclaimer: this individual isn't actually me, many of my fellow colleagues would never speak to me again if I started doing .NET development on an iMac. No second mouse button, give me a break!

EPiServer load balancing revolves around the broadcast of events to each of the servers. The idea is that if someone publishes a change to any of the pages on one server, the other servers must be told of this change and flush their cache (for the page that has changed). If you don’t have load balancing setup correctly, the other servers will continue to use the old, cached version of the page.

The key difference between UDP MC and using net.tcp is that UPD MC broadcasts events to all the servers listing a single multicast IP address and port. Because it’s a multicast IP address, all servers can receive the broadcast message.

Typically what prevents you from using UDP MC event handling are stricter security rules. Some IT departments don’t like the idea of UDP MC broadcasts being sent out to many servers and prefer to keep things more locked down.

So without further delay, let’s get started.

EPiServer.config Configuration

The first thing you need to do is update your episerver.config file so that each site has event handling turned on. There are two attributes that are required in the <siteSettings> element. These attributes are enableEvents and enableRemoteEvents. Both of these should be set to "true". If you have an EPiServer enterprise environment, you must do this for each <siteSettings> element for each <site>.

<sites>
  <site siteId="MySite" description="My Test Site">
    <siteSettings ... more stuff ... enableEvents="true" enableRemoteEvents="true" />
  </site>
  <site siteId="MySite2" description="My Test Site 2">
    <siteSettings ... more stuff ... enableEvents="true" enableRemoteEvents="true" />
  </site>
        
  ... more ...
 
</sites>

Now comes the fun part. You need to determine which servers will be event "broadcasters" and which servers will be event "listeners". Please note that servers can be both a "broadcaster" and a "listener".

Servers that are "broadcasters" will be broadcasting events to other servers. "Broadcasters" are typically the CMS server(s). If you have more than one server that CMS editors will be connecting to in order to publish changes you will have multiple "broadcasters".

Servers that are "listeners" will be receiving events from other servers. "Listeners" are typically the public facing servers. Please note that if a server is not configured to be a "listener" it will never be alerted if changes to content are made on other servers.

Net.tcp General Configuration

First you want to make sure that you have the appropriate configuration in Web.config to support net.tcp binding. Make sure that you have the <netTcpBinding> element in the <system.serviceModel><bindings> element in your Web.config.

<system.serviceModel>

    <client>
      ... more stuff ...
    </client>

    <bindings>

        <customBinding>
          <binding name="RemoteEventsBinding">
            <binaryMessageEncoding />
            <udpTransport multicast="True" />
          </binding>
        </customBinding>
        
        <netTcpBinding>
          <binding name="RemoteEventsBinding">
            <security mode="None" />
          </binding>
        </netTcpBinding>
 
    </bindings>
    
    ... more stuff ...

</system.serviceModel>


Please note that the <customBinding> element would be used for UDP MC events. It’s safe to leave this in place as a reference.

Configuring "Broadcasters"

The hardest part about configuring "broadcasters" is understanding that "broadcasters" actually act as clients to the listeners. This means that the "broadcasters" call the remote event service on each "listener" server.

To properly configure the "broadcasters", update the <client> element in the <system.serviceModel> element in Web.config. You need to create a separate client endpoint for each enterprise site for each listener server you want to broadcast to. Each client endpoint must have a specific syntax for the name attribute: name="{siteId}-{IP address of server}". Please note that the siteId needs to match the siteId attribute in episerver.config. Also within each collection of client endpoints for a specific target "listener", the endpoints must have unique ports.

So if you have a single "broadcaster" acting as the client to two "listeners" on an EPiServer enterprise environment with two sites you will have four client endpoints.

  • Broadcaster #1 to Listener #1 (Site #1) port 13000
  • Broadcaster #1 to Listener #1 (Site #2) port 13001
  • Broadcaster #1 to Listener #2 (Site #1) port 13000
  • Broadcaster #1 to Listener #2 (Site #2) port 13001

Remember that each client endpoint (within a collection of client endpoints for a specific target "listener" server) will have its own port number. Typically you start at 13000 and work your way up.

The example below should help illustrate.

<system.serviceModel>
  <client>
<!--robstr: no longer using UDP multicast event handling, switched to net.tcp-->
<!--this was previously used for UDP MC event handing
<endpoint
name="RemoteEventServiceClientEndPoint" 
address="soap.udp://224.0.0.1:5000/RemoteEventService" 
binding="customBinding" bindingConfiguration="RemoteEventsBinding" 
contract="EPiServer.Events.ServiceModel.IEventReplication"/>
-->
    <endpoint
        name="MySite-192.168.7.130"
        address="net.tcp://192.168.7.130:13000/RemoteEventService"
        bindingConfiguration="RemoteEventsBinding"
        contract="EPiServer.Events.ServiceModel.IEventReplication"
        binding="netTcpBinding" />
    <endpoint
        name="MySite2-192.168.7.130"
        address="net.tcp://192.168.7.130:13001/RemoteEventService"
        bindingConfiguration="RemoteEventsBinding"
        contract="EPiServer.Events.ServiceModel.IEventReplication"
        binding="netTcpBinding" />
    <endpoint
        name="MySite-192.168.7.131"
        address="net.tcp://192.168.7.131:13000/RemoteEventService"
        bindingConfiguration="RemoteEventsBinding"
        contract="EPiServer.Events.ServiceModel.IEventReplication"
        binding="netTcpBinding" />
    <endpoint
        name="MySite2-192.168.7.131"
        address="net.tcp://192.168.7.131:13001/RemoteEventService"
        bindingConfiguration="RemoteEventsBinding"
        contract="EPiServer.Events.ServiceModel.IEventReplication"
        binding="netTcpBinding" />
  </client>
</system.serviceModel>


Configuring "Listeners"

"Listeners" listen for incoming event broadcasts from the "broadcasters". A listener simply has to have a service endpoint available for the "broadcasters" to connect to. This is done by configuring the appropriate <service> elements in the <system.webService><services> element in Web.config.

In this case you need to make sure to have a separate service available for each EPiServer site (when using EPiServer enterprise). Each service endpoint must have its own port number and the <service> element should have the following pattern syntax for the name attribute: name="{siteId}/EPiServer.Events.Remote.EventReplication".

In the example below, we have two service endpoints (one for each of the two sites). Each "listener" server would have this same configuration (with the exception of the IP addresses).

<system.serviceModel>

... more stuff ...

<!--robstr: we are using net.tcp for load balancing, not UPD mulicast.  The public server configuration must listen, so it has services.-->
  <services>
 
<!-- left over from UDP MC event handling <service name="EPiServer.Events.Remote.EventReplication" xdt:Locator="Match(name)">
<endpoint name="RemoteEventServiceEndPoint" contract="EPiServer.Events.ServiceModel.IEventReplication" binding="customBinding"
bindingConfiguration="RemoteEventsBinding" address="soap.udp://224.89.89.89:5000/RemoteEventService" xdt:Transform="Insert" />
</service>
-->
 
    <service name="MySite/EPiServer.Events.Remote.EventReplication">
      <endpoint
      name="RemoteEventServiceEndPoint"
      contract="EPiServer.Events.ServiceModel.IEventReplication"
      bindingConfiguration="RemoteEventsBinding"
      address="net.tcp://192.168.7.130:13000/RemoteEventService"
      binding="netTcpBinding" />
    </service>
            
    <service name="MySite2/EPiServer.Events.Remote.EventReplication" >
      <endpoint
      name="RemoteEventServiceEndPoint"
      contract="EPiServer.Events.ServiceModel.IEventReplication"
      bindingConfiguration="RemoteEventsBinding"
      address="net.tcp://192.168.7.130:13001/RemoteEventService"
      binding="netTcpBinding" />
    </service>
  </services>
</system.serviceModel>

That about does it. To test your configuration log onto a "broadcaster" server and do a page change/publish. You should see this change reflected in all the "listener" servers.

Stay tuned for my next blog post of disabling CMS edit/admin mode from the public facing IIS servers.
Thanks!

Robert

Update: One of my colleagues pointed out that the approach below only works if you are sticking to the built in EPiServer caching (page data caching). If you are storing any kind of custom data in the EPiServer cache you will need to account for this in C#.  You'll have to manually flush the cache of your custom data from the EPiServer cache using EPiServerCacheManager.Cache.Remove() when necessary.  This will send out a broadcast event causing all servers that are "listeners" to also flush the custom data in the EPiServer cache.