Multiple programming languages are supported through Redis implementation of RPC remote method invocation of

  • 2020-05-09 19:37:04
  • OfStack

One of the things I've found that I'm always looking at and getting excited about is extending the system. Now this means different things to different people. As part 1 of the migration of Monolithic to the Microservices architecture approach, how to deal with the Microservices architecture is why I studied RPC.

RPC (or remote process call) is a concept that has been around in computer science for a long time. A very simple understanding of this is the ability to send a message to a remote process, whether it is on the same system or on a remote system. Overall this is very vague and open to many implementations. In my opinion, there is quite a bit to discuss when it comes to RPC, such as the format of messages and how you send them to remote processes. There are many ways to implement RPC, and this is one of the ways I've used it, but for this article, I'm going to use 'JSON-RPC' to handle the message format and Redis to publish messages.

RPC and message queues

The principle is basically the same, but with RPC, the client will wait for a return message with the result of the RPC call. If your message queuing system allows you to handle callback messages for the sender, you can probably use it for RPC. In most message queues, they are used to trigger tasks that no longer need to be replied to the client.

Why Redis and not the others?

You should be able to find that Redis is a very advanced technology in a certain landowner. If you say you haven't found it, what's wrong with you? Redis is a great tool for many things, and you should take a hard look at it. The road to learning is smooth and you don't have to learn too much new stuff. Redis fits those ideas perfectly, so let's see what we can do.

Client


require 'redis'
require 'securerandom'
require 'msgpack' class RedisRpcClient   def initialize(redis_url, list_name)
    @client = Redis.connect(url: redis_url)
    @list_name = list_name.to_s
  end   def method_missing(name, *args)
    request = {
      'jsonrpc' => '2.0',
      'method' => name,
      'params' => args,
      'id' => SecureRandom.uuid
    }     @client.lpush(@list_name, request.to_msgpack)
    channel, response = @client.brpop(request['id'], timeout=30)     MessagePack.unpack(response)['result']
  end end client = RedisRpcClient.new('redis://localhost:6379', :fib)
(1..30).each { |i| puts client.fib(i) }

Server


require 'redis'
require 'msgpack'
class Fibonacci   def fib(n)
    case n
    when 0 then 0
    when 1 then 1
    else
      fib(n - 1) + fib(n - 2)
    end
  end end
class RedisRpcServer   def initialize(redis_url, list_name, klass)
    @client = Redis.connect(url: redis_url)
    @list_name = list_name.to_s
    @klass = klass
  end   def start
    puts "Starting RPC server for #{@list_name}"
    while true
      channel, request = @client.brpop(@list_name)
      request = MessagePack.unpack(request)       puts "Working on request: #{request['id']}"       args = request['params'].unshift(request['method'])
      result = @klass.send *args       reply = {
        'jsonrpc' => '2.0',
        'result' => result,
        'id' => request['id']
      }       @client.rpush(request['id'], MessagePack.pack(reply))
      @client.expire(request['id'], 30)
    end   end end RedisRpcServer.new('redis://localhost:6379', :fib,  Fibonacci.new).start

Indeed, it works because Redis has commands that let you block the wait while you wait for data to come back from the server. This is a great way to make your client code look like it's calling local methods.

Ruby is pretty cool, but...

What if you want to use another language? No problem, as long as your language has a good Redis library, you can do the same thing. Let's take a look at creating a server application using Python.


import redis
import msgpack class Fibonacci:   def fib(self,n):
    if n == 0:
      return 0
    elif n == 1:
      return 1
    else:
      return self.fib(n-1) + self.fib(n-2)
class RedisRpcServer:   def __init__(self, redis_url, list_name, klass):
    self.client = redis.from_url(redis_url)
    self.list_name = list_name
    self.klass = klass   def start(self):
    print("Starting RPC server for " + self.list_name)
    while True:
      channel, request = self.client.brpop('fib')
      request = msgpack.unpackb(request, encoding='utf-8')       print("Working on request: " + request['id'])       result = getattr(self.klass, request['method'])(*request['params'])       reply = {
        'jsonrpc': '2.0',
        'result': result,
        'id': request['id']
      }       self.client.rpush(request['id'], msgpack.packb(reply, use_bin_type=True))
      self.client.expire(request['id'], 30)
RedisRpcServer('redis://localhost:6379', 'fib', Fibonacci()).start()

conclusion

This is a good indication of what you have in mind, and of course, more work is needed to handle exceptions. If you have any problems with this method, I'd be happy to help you. I do want to use RabbitMQ in the same idea 1, but if you have already used Redis in your project, this is a great way to do it.


Related articles: