SignalR - Switch between different Redis backplanes - c#

Let's assume we have 2 Redis Server Backplanes, one as Master and the other as Slave.
Each web application is using SignalR in order to push content to the connected clients as it happens and in order to connect them to the backplane I am using in Application_Start
GlobalHost.DependencyResolver.UseRedis(host, port, "", new[] {"signalr.key"});
RouteTable.Routes.MapHubs();
Now in case Master Redis Backplane fails, I would like to promote the Slave Redis server to Master and switch all existing connections from web servers to the new Master Redis Server.
In order to promote the Slave Server to Master I am using the following code
using (var conn = new RedisConnection(host, port, allowAdmin: true))
{
if (conn.ServerType != ServerType.Master)
{
conn.Open();
var makeMaster = conn.Server.MakeMaster();
var info = conn.Wait(conn.GetInfo());
conn.Wait(makeMaster);
}
}
that seems to do the work.
Can you please help me on how I can inform my web application that the backplane has changed how to connect to the new one, in order to sustain communication between my connected clients?

We don't use SignalR specifically, but we have something pretty similar in the way we use redis, especially when switching between nodes. Specifically, we use redis pub/sub to subscribe to a channel, and we broadcast to that channel when changing master.
Our configuration is a little different, because we use the delimited configuration version based around ConnectionUtils.Connect(...). This means we can specify multiple nodes, with ConnectionUtils handling the concerns of figuring out which is the current master. But in your case you could perhaps publish the new master information as part of the pub/sub. I should also note that much of the code to handle switching masters (with notification) is wrapped up behind ConnectionUtils.SwitchMaster. This includes a broadcast of the change, which you can subscribe to via ConnectionUtils.SubscribeToMasterSwitch. As a minor implementation detail, the channel it uses for this is "__Booksleeve_MasterChanged" - but that is opaque if you just use the public methods.

Related

Some Questions About ServiceStack.Redis

Which Proxy is supported? If any, how can i use it?
Whether hashtag is supported? Or something like that?
Except for unit tests, is there any complete use case? (i.e. Although I read the official GitHub document, I still don't understand how to use it.)
Official GitHub Docs
You're linking to the Configure Redis Sentinel Servers docs so I'm assuming you want to configure your ServiceStack.Redis instance to work with a Redis Sentinel configuration.
Note Redis Sentinel is Redis's high-availability solution (it's not a proxy), I'd recommend reading the Redis's official Redis Sentinel docs to learn about how it works.
First you'll want to setup a Redis Sentinel configuration. A popular setup is to have 1x Redis Master and 2x Redis replica slaves, in addition it's common to have a separate redis sentinel instance (which monitors the running redis instances) on each server that's running a redis instance. To make it easy to develop with you can use ServiceStack's redis-config project which makes it easy to run 1x master, 2x slaves and 3x sentinel processes on the same server.
Then when you have your Redis configuration running (assuming localhost) you can connect to it using ServiceStack's RedisSentinel class by passing in the IP and port of each sentinel instance, e.g:
var sentinelHosts = new[]{
"127.0.0.1:26380",
"127.0.0.1:26381",
"127.0.0.1:26382",
};
var sentinel = new RedisSentinel(sentinelHosts, masterName: "mymaster");
IRedisClientsManager redisManager = sentinel.Start();
Note: you don't have to include the IP and ports for Redis master or Redis slave instances as they'll be automatically discovered and can even change. You also can start with a single Redis Sentinel Instance as RedisSentinel will also be able to discover other sentinels in the same "mymaster" group.
Once you call sentinel.Start() it will return a configured IRedisClientsManager which maintains a pool of open Redis client connections as well as listening to Redis's sentinel server instances for any changes to the Redis Sentinel Configuration, e.g. in-case the Redis master falls over to one of the running slave replicas.
You should maintain the redisManager as a singleton and use it to resolve all redis clients you need, e.g. if using an IOC you can register it as a singleton:
container.Register<IRedisClientsManager>(redisManager);
Whenever you need to connect with Redis you can use GetClient() to resolve an redis connection with the current master instance:
using (var redis = redisManager.GetClient())
{
}
And the end of the using statement (or when calling .Dispose()) your open Redis connection will be returned to the internal connection pool, awaiting for the next time it's resolved.

How to send updates from server to clients?

I am building a c#/wpf project.
It's architecture is this:
A console application which will be on a virtual machine (or my home computer) that will be the server side.
A wpf application that will be the client app.
Now my problem is this - I want the server to be able to send changes to the clients. If for example I have a change for client ABC, I want the server to know how to call a service on the clients computer.
The problem is, that I don't know how the server will call the clients.
A small example in case I didn't explain it well:
The server is on computer 1, and there are two clients, on computers 2 and 3.
Client 2 has a Toyota car and client 3 has a BMW car.
The server on computer 1 wants to tell client 2 that it has a new car, an Avenger.
How do I keep track and call services on the clients?
I thought of saving their ip address (from calling ipconfig from the cmd) in the DB - but isn't that based on the WI-FI/network they are connected to?
Thanks for any help!
You could try implementing SignalR. It is a great library that uses web sockets to push data to clients.
Edit:
SignalR can help you solve your problem by allowing you to set up Hubs on your console app (server) that WPF application (clients) can connect to. When the clients start up you will register them with a specified Hub. When something changes on the server, you can push from the server Hub to the client. The client will receive the information from the server and allow you to handle it as you see fit.
Rough mockup of some code:
namepsace Server{}
public class YourHub : Hub {
public void SomeHubMethod(string userName) {
//clientMethodToCall is a method in the WPF application that
//will be called. Client needs to be registered to hub first.
Clients.User(userName).clientMethodToCall("This is a test.");
//One issue you may face is mapping client connections.
//There are a couple different ways/methodologies to do this.
//Just figure what will work best for you.
}
}
}
namespace Client{
public class HubService{
public IHubProxy CreateHubProxy(){
var hubConnection = new HubConnection("http://serverAddress:serverPort/");
IHubProxy yourHubProxy = hubConnection.CreateHubProxy("YourHub");
return yourHubProxy;
}
}
}
Then in your WPF window:
var hubService = new HubService();
var yourHubProxy = hubService.CreateHubProxy();
yourHubProxy.Start().Wait();
yourHubProxy.On("clientMethodToCall", () => DoSometingWithServerData());
You need to create some kind of subscription model for the clients to the server to handle a Publish-Subscribe channel (see http://www.enterpriseintegrationpatterns.com/patterns/messaging/PublishSubscribeChannel.html). The basic architecture is this:
Client sends a request to the messaging channel to register itself as a subscriber to a certain kind of message/event/etc.
Server sends messages to the channel to be delivered to subscribers to that message.
There are many ways to handle this. You could use some of the Azure services (like Event hub, or Topic) if you don't want to reinvent the wheel here. You could also have your server application track all of these things (updates to IP addresses, updates to subscription interest, making sure that messages don't get sent more than once; taking care of message durability [making sure messages get delivered even if the client is offline when the message gets created]).
In general, whatever solution you choose is plagued with a common problem - clients hide behind firewalls and have dynamic IP addresses. This makes it difficult (I've heard of technologies claiming to overcome this but haven't seen any in action) for a server to push to a client.
In reality, the client talks and the server listens and response. However, you can use this approach to simulate a push by;
1. polling (the client periodically asks for information)
2. long polling (the client asks for information and the server holds onto the request until information arrives or a timeout occurs)
3. sockets (the client requests server connection that is used for bi-directional communication for a period of time).
Knowing those terms, your next choice is to write your own or use a third-party service (azure, amazon, other) to deliver messages for you. I personally like long polling because it is easy to implement. In my application, I have the following setup.
A web API server on Azure with and endpoint that listens for message requests
A simple loop inside the server code that checks the database for new messages every 100ms.
A client that calls the API, handling the response.
As mentioned, there are many ways to do this. In your particular case, one way would be as follows.
Client A calls server API to listen for message
Server holds onto call, waiting for new message entry in database
Client B calls server API to post new message
Server saves message to database
Server instance from step 2 sees new message
Server returns message to Client A.
Also, the message doesn't have to be stored in a database - it just depends on your needs.
Sounds like you want to track users à la https://www.simple-talk.com/dotnet/asp.net/tracking-online-users-with-signalr/ , but in a desktop app in the sense of http://www.codeproject.com/Articles/804770/Implementing-SignalR-in-Desktop-Applications or damienbod.wordpress.com/2013/11/20/signalr-a-complete-wpf-client-using-mvvm/ .

Push Notifications in SignalR from multiple Databases

I am relatively new to SignalR, What I have done so far are example chat applications on it to get started.
We have a project that includes multiple databases, it's a machine data collection app. I have created a web control in ASP.NET MVC to view data of multiple machines at one place.
We have multiple users and they can access machines related to their projects. The current solution have a jQuery observer on the mvc page which refreshes the machine controls in a specific time.
I am thinking of making it a Push Notification solution. But as I am a beginner on SignalR I don't know how to approach this.
The goal is to notify the user of changes in data for machines that the user has access to (Not all machines).
Also how can I send messages from a database server to SignalR notifying of changes in the data?
I need some guidance getting started on this.
SignalR Send method allows you to send data to user! It may use push, frame, server events or websocket techniques. You developer don't care about the technique! It depends on server and client handshake.
What you really need is to retrieve data from your databases and then to users, without worries
// Server side:
protected override Task OnConnected(IRequest request,
string connectionId)
{
var db = new allEntities();
var db2 =new allEntities2();
var data1 = db.Tables.Where(e=>e.LastUpdate<=datetime.now.AddDays(-1));
var data2=db2.Tables.Where(e=>e.Something==someRef);
var data=combine(data1,data2);//combine logic goes here
return Connection.Send(connectionId, data.ToList());
}

TIBCO EMS Failover reconnect for C# (TIBCO.EMS.dll)

We have a TIBCO EMS solution that uses built-in server failover in a 2-4 server environment. If the TIBCO admins fail-over services from one EMS server to another, connections are supposed to be transfered to the new server automatically at the EMS service level. For our C# applications using the EMS service, this is not happening - our user connections are not being transfered to the new server after failover and we're not sure why.
Our application connection to EMS at startup only so if the TIBCO admins failover after users have started our application, they users need to restart the app in order to reconnect to the new server (our EMS connection uses a server string including all 4 production EMS servers - if the first attempt fails, it moves to the next server in the string and tries again).
I'm looking for an automated approach that will attempt to reconnect to EMS periodically if it detects that the connection is dead but I'm not sure how best to do that.
Any ideas? We are using TIBCO.EMS.dll version 4.4.2 and .Net 2.x (SmartClient app)
Any help would be appreciated.
First off, yes, I am answering my own question. Its important to note, however, that without ajmastrean, I would be nowhere. thank you so much!
ONE:
ConnectionFactory.SetReconnAttemptCount, SetReconnAttemptDelay, SetReconnAttemptTimeout should be set appropriately. I think the default values re-try too quickly (on the order of 1/2 second between retries). Our EMS servers can take a long time to failover because of network storage, etc - so 5 retries at 1/2s intervals is nowhere near long enough.
TWO:
I believe its important to enable the client-server and server-client heartbeats. Wasn't able to verify but without those in place, the client might not get the notification that the server is offline or switching in failover mode. This, of course, is a server side setting for EMS.
THREE:
you can watch for failover event by setting Tibems.SetExceptionOnFTSwitch(true); and then wiring up a exception event handler. When in a single-server environment, you will see a "Connection has been terminated" message. However, if you are in a fault-tolerant multi-server environment, you will see this: "Connection has performed fault-tolerant switch to ". You don't strictly need this notification, but it can be useful (especially in testing).
FOUR:
Apparently not clear in the EMS documentation, connection reconnect will NOT work in a single-server environment. You need to be in a multi-server, fault tolerant environment. There is a trick, however. You can put the same server in the connection list twice - strange I know, but it works and it enables the built-in reconnect logic to work.
some code:
private void initEMS()
{
Tibems.SetExceptionOnFTSwitch(true);
_ConnectionFactory = new TIBCO.EMS.TopicConnectionFactory(<server>);
_ConnectionFactory.SetReconnAttemptCount(30); // 30retries
_ConnectionFactory.SetReconnAttemptDelay(120000); // 2minutes
_ConnectionFactory.SetReconnAttemptTimeout(2000); // 2seconds
_Connection = _ConnectionFactory.CreateTopicConnectionM(<username>, <password>);
_Connection.ExceptionHandler += new EMSExceptionHandler(_Connection_ExceptionHandler);
}
private void _Connection_ExceptionHandler(object sender, EMSExceptionEventArgs args)
{
EMSException e = args.Exception;
// args.Exception = "Connection has been terminated" -- single server failure
// args.Exception = "Connection has performed fault-tolerant switch to <server url>" -- fault-tolerant multi-server
MessageBox.Show(e.ToString());
}
This post should sum up my current comments and explain my approach in more detail...
The TIBCO 'ConnectionFactory' and 'Connection' types are heavyweight, thread-safe types. TIBCO suggests that you maintain the use of one ConnectionFactory (per server configured factory) and one Connection per factory.
The server also appears to be responsible for in-place 'Connection' failover and re-connection, so let's confirm it's doing its job and then lean on that feature.
Creating a client side solution is going to be slightly more involved than fixing a server or client setup problem. All sessions you have created from a failed connection need to be re-created (not to mention producers, consumers, and destinations). There are no "reconnect" or "refresh" methods on either type. The sessions do not maintain a reference to their parent connection either.
You will have to manage a lookup of connection/session objects and go nuts re-initializing everyone! or implement some sort of session failure event handler that can get the new connection and reconnect them.
So, for now, let's dig in and see if the client is setup to receive failover notification (tib ems users guide pg 292). And make sure the raised exception is caught, contains the failover URL, and is being handled properly.
Client applications may receive notification of a failover by setting the tibco.tibjms.ft.switch.exception system property
Perhaps the library needs that to work?

.Net 2.0: How to subscribe to a event publisher on a remote computer using transient subscriptions?

My problem is that I want to have a server application (on a remote computer) to publish certain events to several client computers. The server and client communicate using .Net-Remoting so currently I am using remoted .Net-Events to get the functionality. But there is one drawback: when the server (the event publisher) comes offline and is restarted, the clients lose the connection since the remote object references become invalid.
I am looking into Loosely Coupled Events and Transient COM Subscriptions to solve this issue. I put together a small demo application with one publisher and two subscribers. It works beautifully on one computer.
I am using the COMAdmin-Libraries to create a transient subscription for the event subscribers. The code looks like this:
MyEventHandler handler = new MyEventHandler();
ICOMAdminCatalog catalog;
ICatalogCollection transientCollection;
ICatalogObject subscription;
catalog = (ICOMAdminCatalog)new COMAdminCatalog();
transientCollection = (ICatalogCollection)catalog.GetCollection("TransientSubscriptions");
subscription = (ICatalogObject)transientCollection.Add();
subscription.set_Value("Name", "SubTrans");
subscription.set_Value("SubscriberInterface", handler);
string eventClassString = "{B57E128F-DB28-451b-99D3-0F81DA487EDE}";
subscription.set_Value("EventCLSID", eventClassString);
string sinkString = "{9A616A06-4F8D-4fbc-B47F-482C24A04F35}";
subscription.set_Value("InterfaceID", sinkString);
subscription.set_Value("FilterCriteria", "");
subscription.set_Value("PublisherID", "");
transientCollection.SaveChanges();
handler.Event1 += OnEvent1;
handler.Event2 += OnEvent2;
My question now is: what do I have to change in the subscription to make this work over a network? Is it even possible?
What about MSMQ? It seems perfect for what you are trying to achieve? You can use a traditional publish/subscribe model or multicast the messages.
This might be a step too far, but have you considered using WCF and the callback element of WCF?
Callback effectively turns the what was client into a server. To be honest, I don't know a great deal about callback and have only experimented. Perhaps worth a 10 minute google though.
If your server comes offline every once and a while I cannot see how you can avoid to poll it to check that it is alive.
As you are talking about COM and remote computers, I suspect you'll have to do some DCOM security configuration.

Categories

Resources