Great question!
Accounts in Substrate are stored in the FRAME System Pallet here.
Other pallets may depend on an account (consumers) or other pallets may need the existence of this account for its own purposes (sufficients) - regardless of which, the account first must be deemed as existing (providers).
Consumers
The number of other modules that currently depend on this account's existence. The account cannot be reaped until this is zero.
In order to increment the consumer reference for an account, first the provider reference for that account has to be greater than zero (this indicates that the account is active and can be depended upon).
Incrementing the consumers reference is a way of letting other pallets know that the account is active and depended upon by other pallets therefore do not remove that account.
Providers
The number of other modules that allow this account to exist. The account may not be reaped until this and sufficients
are both zero.
Providers is a way to tell other pallets whether an account is active (>0) or not.
The idea/design/concept of these reference counters is to keep users accountable for data stored on chain:
If users want to remove their accounts and get back the existential deposit, they need to remove the dependencies from those on-chain pallets, such as clearing data stored on-chain for those pallets, which decrement consumers counter. Pallets also have cleanup functions to decrement providers to mark the account as deactivated within the pallet-managed scope. When the account providers reaches 0, with the prerequisite that consumers has reached 0 by this point, this account is considered deactivated by all on-chain pallets.
When it comes to incrementing/decrementing remember:
Each increment call of a certain counter should be accompanied by a corresponding decrement call of the counter in an account life cycle, else it is a design bug.
Sufficients
The number of modules that allow this account to exist for their own purposes only. The account may not be reaped until this and providers
are both zero.
An example we normally give is the Assets
pallet. In order to create an asset, you need an account. The Assets
pallet depends on the account for its own purposes but the account itself does not depend on it necessarily, e.g. it has no balance of its own with that asset. Because of this the account is deemed as self-sufficient. It has enough purpose to exist.
Reference: https://docs.substrate.io/reference/account-data-structures/