This is probably going to be a 2 part approach.
Step 1 (and I start with this since this is the easy part) :
Get the list of servers running. There are a few caveats to consider. Such as the list of servers can contain stale server entries. They normally get cleaned up if their heartbeat has passed the threshold and there is another AppServer on the same storage to do the cleanup but it might take some time.
The code to get the list is fairly simple (note that you first have to set up your storage with GlobalConfiguration.Configuration.UseSqlStorage or similar):
var c = JobStorage.Current.GetMonitoringApi();
var s = c.Servers();
This will give an IList which contains all the currently known servers and some extra info. This will help you get the list of servers (and also the queues they service).
Step 2 (more complex)
You will need a queue that is server specific, there are a number of ways to do this of course and I am not sure what would be the best fit for you here. Making the individual AppServers listen to a server specific queue is easy enough. You can hardcode it, make it configurable per server or let the server ‘calculate’ the server specific queuename based on (for instance) the host name.
The complication comes with enqueue’ing the job to this specific queue given that it is a fire and forget job. For recurring jobs you can specify the queue in the AddOrUpdate method call, for fire and forget (i.e. run once and delete) Enqueue’s it’s a little more involved.
For this you probably want to look in this discussion : Specify Hangfire Queue in BackgroundJob Calling Method