Recently there is a need to implement the plug-in mechanism inside the project, the so-called plug-in means that the program can be extended without releasing a new version of the case, this plug-in mechanism is particularly widely used, common such as our browser extensions, Nginx extensions, PHP extensions and so on.
Inside the Go language, it comes with an official plugin extension mechanism, which I have described in a previous article.
But the official Go comes with this plug-in mechanism is very immature, more like a half-finished product, there are many problems, such as plug-ins can not be uninstalled, the respective version of the dependency must be completely consistent, which must be consistent with the version of the dependency requirements are very deadly, because in a large project, it is difficult to limit the plug-in developers to use the dependency library version, it is difficult to achieve uniform.
Today we will introduce a plug-in mechanism based on RPC implementation, and this approach is proven by large-scale practice, the availability is very high, it is worth trying.
1. Introduction
This is hashicorp open source project, Github: https://github.com/hashicorp/go-plugin
The official description is as follows.
go-plugin is a Go (golang) plugin system based on RPC. It is a plug-in system used by HashiCorp tools for over 4 years. Although originally created for Packer, it is also used by Terraform, Nomad, Vault, and Boundary. While the plug-in system is based on RPC, it is currently designed to work only on local [reliable] networks. Plugins on real networks are not supported and can cause unexpected behavior. The plug-in system has been used on millions of machines in many different projects and has proven to be durable enough for production use.
The introduction mentioned in a number of projects used, but I am not familiar with these, but there is an open source project many people should have heard of, the name is Grafana
Grafana is an open source monitoring visualization platform , which happens to be hashicorp project , currently also provides commercial services , its back end is written in Go , is also considered a very popular open source project in the Go language . Which uses the above-mentioned plug-in library, which Grafana panel inside the data source is the form of plug-ins, users can download and install the corresponding database plug-ins according to their needs.
However, in the Grafana project, this plug-in library is encapsulated in many layers, the code is very much, not so simple, let’s not see, first of all, first understand the functions provided by this plug-in library, according to the official documentation of this plug-in library has the following features.
- plugins are implementations of Go interfaces: this makes writing and using plugins very natural. For the author of the plugin, he only needs to implement a Go interface; for the user of the plugin, he only needs to call a Go interface. go-plugin takes care of all the details of converting local calls to gRPC calls
- cross-language support: plug-ins can be written based on any major language, the same can be consumed by any major language
- support complex parameters, return values: go-plugin can handle interfaces, io.Reader/Writer and other complex types
- two-way communication: in order to support complex parameters, the host process can send the interface implementation to the plug-in, and the plug-in can also call back to the host process
- built-in logging system: any plug-in using the log standard library, will pass log information back to the host process. The host process will add the path of the plug-in binary file in front of these logs, and print the logs
- protocol versioning: support a simple protocol versioning, adding the version number can be based on the old version of the protocol plug-in invalidation. Versions should be added when the interface signature changes
- standard output / error synchronization: plug-ins run as child processes, these child processes are free to use the standard output / error, and the contents of the print will be automatically synchronized to the host process, the host process can be specified for the synchronization of the log io.Writer.
- TTY Preservation: Plug-in sub-processes can be linked to the stdin file descriptor of the host process in order to require TTY software to work properly
- host process upgrade: when the host process is upgraded, the plug-in sub-processes can continue to be allowed and automatically associated with the new host process after the upgrade
- encrypted communication: gRPC channels can be encrypted
- integrity checks: support for the plug-in binary file Checksum
- the plug-in crashed, will not cause the host process to crash
- easy to install: you only need to put the plug-in into a directory that the host process can access
These features look very rich and powerful, however, according to my understanding, the actual application still needs to do some packaging, from the official demos, does not reflect these features.
2. Example
There are several examples inside the official repository, let’s look at the simplest demo first
1. Plugin definition
First of all, let’s look at the greeter_interface.go file. The first part we can understand for the plug-in to define the interface to be implemented, constrain the behavior of the plug-in.
Then define a Greeter that implements this plug-in interface, here it goes through RPC, so an rpc client is needed.
|
|
Immediately afterwards, an RPCServer was defined to wrap it up again.
|
|
Finally, the last is the implementation of the plug-in, mainly the 2 methods Server and Client.
|
|
Definition of the RPC Plugin interface.
|
|
Ultimately, under layers of packaging, this file defines the framework of a plugin and the methods to be implemented, but an implementation is still missing, which is inside the greeter_impl.go file.
2. Plug-in implementation
First define an object to implement Greet method, this is relatively simple, here use a library inside the logger library.
Then there is the content inside main, the operation of this piece is simply to set some parameters, start an RPC service, and wait for the arrival of the request.
|
|
Finally, don’t forget to compile the plug-in. The compilation of the plug-in is actually no different from a normal Go program and will result in a binary file, which will be used later.
|
|
3. Using the plug-in
The code for using plug-ins is relatively simple as well, you just need to New a Client, set the relevant parameters, and then initiate the call.
|
|
What needs to be noted here is a handshakeConfig inside the configuration to be consistent with the plug-in implementation inside, in addition to setting the location of the binary executable.
Second, Plugins is a map which stores the mapping relationship between the plugin name and the plugin definition.
3. My doubts
From the above demo, this library’s plugin system is essentially a local RPC call, although the performance may be a little lower, after all, the local network also has overhead, but it does not have some of the problems of the official plugin mechanism.
But I did not see from this demo how to achieve multiple plug-in coexistence, as well as plug-in updates and other functions, in fact, in my subsequent research I found that the library does not achieve these functions.
If you want to implement these features you may have to implement them yourself, Grafana project in the use of this library has done a lot of packaging.
4. Principle
First, let’s look at the plug-in implementation piece, mainly the plugin.Serve method, which requires passing in a plug-in configuration.
|
|
There is a lot of code, only the core code is shown here, in fact, is doing one thing is to initialize and start the RPC service, ready to accept requests.
More code in the plug-in use of this block, first we New a Client, the Client is to maintain the plug-in, and is a plug-in a Client, so if you want to achieve multiple plug-in coexistence, you can go to the implementation of a plug-in and Client mapping relationship can be.
|
|
In the main inside when we finished NewClient, in turn called the Client and Dispense2 methods, the 2 methods are very important.
|
|
Start this method does a lot of things, simply put, is based on the configuration of the cmd inside, that is, we compile the plug-in to get the binary executable file, start the plug-in rpc service.
Then, depending on the protocol, start the RPC service or GRPC service, get a real available Client, which is equivalent to the channel has been opened, the next is to launch the request.
|
|
Dispense method is based on the name of the plug-in to get the corresponding plug-in object, and then wrapped a layer to get a Client object, remember the initial definition of the plug-in when the Client?
Finally, assert and call the plugin’s method, which is when the RPC request is actually initiated and the return result is obtained.
Finally, assert and call the methods of the plugin, this is when the RPC request is really launched and get the return result One thing I feel particularly strange, from the point of view of this plugin implementation, Dispense this step is more like subdividing the plugins inside the plugins, because in my understanding, a binary file is a plugin, a plugin has only one implementation.
But obviously, this library does not think so, it believes that a plug-in file can be implemented inside multiple plug-ins, so it adds a Plugins to store the mapping relationship of plug-ins, which means you can implement multiple interfaces inside a plug-in.
This implementation is also equivalent to a constraint, and in fact inside the Grafana project, it declares the types of plugins that can be supported in this way. As a plugin, you can implement some of the interfaces according to your needs.
|
|
5. Summary
After writing so much, for this library, I personally feel that the usability is very high, after the actual test of production. But only the core functionality, lack of encapsulation, for the use of plug-in system, at least the following functions should be achieved.
- Management of multiple plug-ins.
- automatic and manual update of plug-ins.
- plug-in health check, because the plug-in is essentially an rpc service, may hang, hang after what to do?
If you really want to use this library , it really needs to spend a lot of effort , there is a good place is that we can refer to the Hashicorp family of other open source projects to improve the code , in fact, I also wonder if the library can be slightly encapsulated to provide a simple and easy to use interface.