The problem is that having a counter doesn't really work, because you can't force the client to take the updated JWT. (And the previous JWT is still valid as it's checked based on signature.)
The way to go is pretty simple - issue a long-term refresh token, and a very short-term JWT to the client (as little as 5 mins or so). The app can then make requests directly to the services, which can independently verify the validity of the JWT.
The services then also check for tokens which have been revoked - using a very fast mechanism, like holding them in-memory or querying a Redis store. The token's JTI only needs to be held in the revocation list for a short period of time - until the JWT expiry time - using a TTL in Redis, or similar.
The app uses the refresh token to get replacement JWTs. It goes with the refresh token directly to the auth service, which can then check whether the user should be able to get new tokens or not.