Redis has stayed the same, architecturally, from the beginning: it’s a keyspace where the values are arbitrary in-memory objects owned by their keys, and where commands resolve to synchronous function-calls against the objects (or ref-cells) at those keys.
Anything that can be done without changing that architecture, is in-scope for Redis. (Though if a data structure doesn’t have wide use outside of a domain, it’s best left to a module.) Anything that cannot be done without changing the architecture, won’t be done.
Much of the “fun” I’ve personally had in watching Redis evolve, has been seeing how Antirez has managed to solve the puzzles of getting features that intuitively wouldn’t be a good fit for this architecture, to fit into it anyway: changing technologies that are implemented one way everywhere else, into something else for Redis, that still work, are still performant, and still solve isomorphic use-cases—if not with the same steps you’d use to solve them in other systems. (E.g. using Streams vs. using a regular MQ; using Redis Cluster vs. using regular RDBMS replication; etc.)
I personally have not enjoyed all the strange and wonderful ways people have found to use Redis. Most of them are pretty fragile and (most dangerously) they often put all Redis uses in the same instance...with eviction turned on.
Maybe that's what Cal and his colleagues were using it for at the time, but the claim that it started as a volatile cache is just not true. Interesting datastructures and the fork based persistence model were there from the start.
Lists, hashes, sets and such that you have available in your programming language, but available as a networked server so any language/app/process can access the same data using the same useful structures.
But... things that are also just useful often and for what you'd need to spin off a separate service for, or make suboptimally in whatever (No)SQL you use to store your data.
So it is perfect if you just want to prototype whether given approach is viable, and "good enough" for many apps.
It works excellent as a message bus, though. And you can add HSET/HDEL to your list as the fourth way.
Lots of reasons why but here is just one because I'm short on time: all of these (four) ways of doing a message bus with Redis offer either a work queue mode or publish/subscribe - but not both (perhaps excepting streams which I've forgotten some of the details of). I have yet to see many real world messaging scenarios where you don't end up using both.
A "proper" message bus needs to have topics, exchanges and queues - like Rabbit. 50% of that functionality is usually not 50% of the value but 10% or less (especially when you consider all the other things about Redis you need to think about - like how eviction is configured).
This is a bit typical of Redis: for any X, you can normally do X in Redis, but is it really a good idea to do X in Redis? IMO, usually no.
Redis Streams actually does exactly what you describe and is basically a single-node fast Kafka replacement with multiple independent consumer groups, per-message acknowledgements, and queue semantics. We use it successfully in many high-load scenarios.
Redis has had tremendous mission creep over the years. It started of course mostly as a volatile cache but I've now seen it also used as a message bus (in three different ways: BLPOP, PUB/SUB and Streams), for service discovery and as a general purpose database - something that Redis Labs (in my opinion: wrongly) encourages.
Memcache existed in 2009 when Redis was first released, also as a volatile cache...and still is just a volatile cache. Memcache is what "saying no" looks like. Redis is what "saying yes" looks like - and there are a lot of gotchas.