Source code analysis-skynet source code analysis: service

Skynet was single-process and multi-threaded at first, and it was composed of services one by one. Doing development on skynet is actually writing services. The service communicates with the service through the message queue.

As a core function, Skynet only solves one problem:

Start a compliant C module from the dynamic library (so file), and bind a digital id that never repeats (even if the module exits) as its handle. The module is called a service, and messages can be sent freely between services. Each module can register a callback function with the Skynet framework to receive messages sent to it. Each service is driven by a message packet. When no packet arrives, they will be in a suspended state, which consumes zero CPU resources. If you need autonomous logic, you can use the timeout message provided by the Skynet system to trigger regularly.

A service does not execute any logic by default. It will execute the corresponding logic when it needs someone to make a request to it (the timer also tells the specified service to execute timing events through the message queue), and returns the result to the request when needed By. Requesters are often other services. The request, response, and push between services are not executed directly by calling the other party's api, but through a message queue, that is to say, whether it is a request, response or push, it needs to be forwarded to another service through this message queue . The message queue of skynet is divided into two levels, a global message queue, which contains a head and tail pointer, respectively pointing to two secondary message queues belonging to the specified service. Every service in skynet has a unique and exclusive secondary message queue.

The essence of skynet service

Each skynet service is a lua state, which is a lua virtual machine instance. Moreover, each service is isolated, each uses its own independent memory space, and the services complete data exchange by sending messages.

Lua state itself does not have multi-thread support. In order to achieve CPU sharing, the skynet implementation runs multiple Lua state instances in one thread. At the same time, the scheduling thread only runs one service instance. In order to improve the concurrency of the system, skynet will start a certain number of scheduling threads. At the same time, in order to improve the concurrency of the service, the lua coroutine is used for concurrent processing.

Therefore, the concurrency of skynet has three points:

1. Multiple scheduling threads concurrently
2. Lua coroutine concurrent processing
3. Service scheduling switching

The design of skynet service is based on the Actor model. There are two characteristics:

  1. Each actor processes the received messages in turn
  2. Different actors can process their own messages at the same time

In terms of implementation, cpu will be allocated to each Actor according to certain rules, and each Actor will not monopolize the cpu. After processing a certain number of messages, it will actively give up the cpu and process the messages for other processes.

How to call skynet example

Server :
simpledb.lua: skynet.register "SIMPLEDB" Register a service
agent.lua in skynet: skynet.call("SIMPLEDB", "text", text) Call the corresponding service
main.lua: skynet.newservice("simpledb ”) Start a service The
above functions are all in the \lualib\skynet.lua file

The following are a few frequently used functions when writing services.

newservice(name, …) starts a new service named name.
uniqueservice(name, …) starts a unique service. If the service is already started, it returns the started service address.
queryservice(name) Query the address of a unique service started by uniqueservice, and wait if the service has not been started.
localname(name) returns the address of the named service registered with register in the same process.

newservice can start multiple services in a process, which is suitable for stateless services.
Uniqueservice is similar to a singleton in the design pattern, which is suitable for services that require uniqueness. For example, if you write a log, you just want to write one copy. Or global shared data.

Message mechanism

The SKYNET design overview talks about modules called services. "Messages can be sent freely between services. Each module can register a callback function with the Skynet framework to receive messages sent to it." It also mentioned that "a C module that conforms to the specification can be downloaded from the dynamic library (so file) Startup, bind a digital id that never repeats (even when the module exits) as its handle. Skynet provides a name service, and you can also give a specific service a readable name instead of using id to refer to it. The id is related to the runtime state. There is no guarantee that every time the service is started, there will be a consistent id, but the name is OK." The two files skynet_handle.c and skynet_handle.h to be analyzed today are used to implement name services.

skynet_handle.c actually does two core things, one is to assign a handle to the service, and the other is to associate the handle with the name.

Associating handle with name is easier to understand. In fact, an array is used. When associating, use binary search to find the name in the array. If the name does not exist, insert an element, and then associate the name with the handle. When inserting elements, if the array space is insufficient, the capacity is expanded to twice the original size.

Assigning a handle to a service is a bit more complicated. In fact, a slot array is used. The array subscript uses a hash, and the array elements point to the context of the service. This hash algorithm is relatively simple and rude. It is to see if there is any free subscript (that is, the subscript points to null) from the handle_indx to the slot_size. If the traversal is completed or not, the slot will be doubled. , If it doesn’t exist, it will double again until a slot is found, or the slot length exceeds the limit.

After getting the handle, the harbor id must be attached to the upper 8 bits of the handle.

Each service is divided into three operating phases:

The first is the service loading stage. When the source file of the service is loaded, it will be executed according to Lua's operating rules. At this stage, it is not allowed to call any skynet api that may block the service. Because, at this stage, the skynet settings associated with the service have not been initialized.

Then comes the service initialization phase, which is executed by the initialization function registered by the skynet.start api. This initialization function can theoretically call any skynet api, but the skynet.newservice api that starts the service will wait until the initialization function ends before returning.

The last is the service work phase. When you register a message processing function in the initialization phase, as long as there is a message input, the registered message processing function will be triggered. These messages are all Skynet internal messages. External network data and timers will also be expressed in the form of internal messages.

From the perspective of skynet's underlying framework, each service is a message processor. But this is not the case in the application layer. It uses lua's coroutine to work. When your service sends a request (ie a message with session) to another service, it can be considered that the current message has been processed, and the service will be suspended by skynet. After the corresponding service receives the request and responds (sends a response type message), the service will find the suspended coroutine, pass the response information in, and continue the unfinished business process. From the user's point of view, it is more like an independent thread processing this business process. Each business process has its own independent context, unlike the callback mode used in other frameworks such as nodejs.

However, the framework has provided a service module called snlua developed in C, which can be used to parse a Lua script to implement business logic. In other words, you can start any snlua service on skynet, but they carry different Lua scripts. In this way, it is enough that we only use Lua for development.

ShareData

When you split your business into multiple services, how to share data may be the most common problem.

The simplest and rude way is to pass data through messages. If service A needs the data in service B, service B can send a message to package the data. If it is a piece of data and many places need to obtain it, then use a service to load this set of data and provide a set of query interfaces. The DataCenter module simply encapsulates this.

Datacenter can be used to share data across nodes in the entire skynet network.

If you only need a set of read-only structure information to share with many services (such as some configuration data), you can write the data to a lua file and let different services load it. The Cluster configuration file does just that. Note: By default, skynet uses its own modified version of lua, which will cache lua source files. When a lua file is loaded through loadfile, the modification on the disk will not affect the next load. So you need to open the file directly with io.open, and then load the string in memory with load.

Another better way is to use the sharedata module.

When a large number of services may need to share a large piece of structured data that does not need to be updated, each service uses only a small part of it. You can imagine that these data are placed in a data warehouse during development, and each service retrieves the required parts as needed.

The data warehouse required by the entire project may be large in scale, but each service only needs to use a small part of the data. If each service loads all the data into the memory, when the number of services is large, it will not be touched because of repeated loading. Data and waste a lot of memory. In the development phase, it is difficult to divide the data into smaller granularities, because it is difficult to re-segment according to changes in demand at all times.

If you use DataCenter, a centralized management solution, you cannot avoid making an RPC call every time you retrieve data, and the performance may not be affordable.

The sharedata module is designed to solve this need. Sharedata only supports sharing data within the same node (under the same process). If you need to cross nodes, you need to synchronize it yourself.

Realization of skynet call-service and service interaction

If a service produces a large amount of data and wants to pass it to you for a service consumption, in the same process, it does not need to go through the serialization process, but only needs to pass the memory address pointer through the message. This optimization has the performance difference between O(1) and O(n), which cannot be ignored.
Insert picture description here

Skynet core technology points:

Full four-hour video sharing
Insert picture description here

Guess you like

Origin blog.csdn.net/qq_28581269/article/details/112653175