[SPARK-3795] Heuristics for dynamically scaling executors

This is part of a bigger effort to provide elastic scaling of executors within a Spark application ([SPARK-3174](https://issues.apache.org/jira/browse/SPARK-3174)). This PR does not provide any functionality by itself; it is a skeleton that is missing a mechanism to be added later in [SPARK-3822](https://issues.apache.org/jira/browse/SPARK-3822).

Comments and feedback are most welcome. For those of you reviewing this in detail, I highly recommend doing it through your favorite IDE instead of through the diff here.
For the way apps normally use YARN, the concept of "request acknowledged" doesn't really make sense.  Updating the local bookkeeping on the number of executors needed is usually asynchonous from passing on that requirement to the ResourceManager.  YarnAllocator should have a thread that periodically heartbeats to the ResourceManager.  If there's a temporary network partition that happens to coincide with the addTimer expiring, we'll communicate what we needed to on the next heartbeat.