【转】Design for performance - Remote interfaces
转自http://www.javaworld.com/javaworld/jw-03-2001/jw-0323-performance.html
An overview of remote invocation
In distributed applications, an object running on one system can invoke methods on objects running in another system. That is accomplished with the aid of a great deal of machinery that makes the remote objects appear local. To access a remote object, you first need to find it, which can usually be accomplished through the use of a directory or naming service, such as the RMI registry, JNDI, or the CORBA naming service.
When you obtain a reference to a remote object through a directory service, you don't receive an actual reference to that object, but rather a reference to a stub object that implements the same interface as the remote object. When you invoke a method on the stub object, the stub has to marshal the method parameters -- convert them into a byte-stream representation, a process similar to serialization. The stub sends the marshaled parameters over the network to a skeleton object, which unmarshals them and invokes the actual remote method you wanted to invoke. Then the method returns a value to the skeleton, the skeleton marshals the return value and ships it to the stub, and the stub unmarshals it and returns the value to the caller. Phew! That's a lot of work for a single method call. Clearly, despite an outward similarity, a remote method invocation is a more expensive operation than a local method invocation.
The above description glosses over some important details that are significant for program performance. What happens when a remote method returns not a primitive type, but an object? That depends. If the returned object is a type that supports remote method invocation, then it creates a stub and skeleton object, as is the case when looking up a remote object in the registry. That is clearly an expensive operation. (Remote objects support a form of distributed garbage collection, which involves each of the participating JVMs maintaining a thread for talking to the remote garbage collection thread of other JVMs, and sending reference status information back and forth.) If the returned object doesn't support remote invocation, then all of the object's fields and any objects referenced by the returned object have to be marshaled, which could also be an expensive operation.
Performance comparison between remote and local method invocation
The performance characteristics of remote object access differ from those of local access:
- Remote object creation is more expensive than local object creation. Not only does the object need to be created if it does not already exist, but the stub and skeleton objects need to be created, and they have to be made aware of each other.
- Remote method invocations involve a network round-trip -- the marshaled parameters must be sent to the remote system and the response must be marshaled and sent back before the calling program regains control. The delays from marshaling, unmarshaling, network latency, and the actual remote invocation are additive; the client generally waits while all of those steps are being accomplished. The costs of a remote invocation also vary depending on the latency of the underlying network.
- Different data types have different marshaling overhead. Marshaling primitive types is relatively inexpensive; marshaling
simple objects like
Point
orString
is slightly more expensive; marshaling remote objects is much more expensive, and marshaling objects that reference many other objects (like collections) can be even more expensive. That is in stark contrast to local invocations, for which passing a reference to a simple object is no less expensive than passing a reference to a complex object.
Interface design is critical
A poorly designed remote interface can
kill a program's performance. Unfortunately, the characteristics that
make for a good
interface design for local objects can
prove inappropriate for remote objects. Excessive temporary object
creation, as discussed
in parts 1 and 2 of this series, can
hamper distributed applications too, but excessive round-trips are an
even bigger performance
problem. Therefore, calling a remote
method that returns multiple values contained in a temporary object
(such as a Point
) rather than making multiple consecutive method calls to retrieve them individually is likely to be more efficient. (Note
that this is exactly the opposite of the advice I offered in Part 2
for local objects.)
Some important performance guidelines for designing remote applications:
- Watch out for unnecessary round-trips. If a caller wants to retrieve several related items simultaneously, make it easy to do so in one remote invocation, if possible.
- Watch out for returning remote objects when the caller may not need to hold a reference to the remote object.
- Watch out for passing complex objects to remote methods when the remote object doesn't necessarily need to have a copy of the object.
Fortunately, you can spot all of these problems simply by looking at the remote object's interface. The sequence of method calls required to perform any high-level action is usually obvious from the class interface. If you see that a common high-level operation requires many consecutive remote method calls, this should be a warning sign that perhaps you need to revisit the class's interface.
Techniques for reducing remote invocation overhead
As an example, consider the following hypothetical application for managing a corporate directory: A remote Directory
object dispenses references to DirectoryEntry
objects, which represent entries in a phone book:
public interface Directory extends Remote {
DirectoryEntry[] getEntries();
void addEntry(DirectoryEntry entry);
void removeEntry(DirectoryEntry entry);
}
public interface DirectoryEntry extends Remote {
String getName();
String getPhoneNumber();
String getEmailAddress();
}
Now, suppose you want to use the Directory
facility in a GUI email application. The application first calls getEntries()
to retrieve a list of entries, and then calls getName()
on each entry, populating a list box with the results. When the user selects a recipient, the application then calls getEmailAddress()
on the corresponding entry to retrieve the email address.
How many remote method invocations must occur before you can compose an email? You must call getEntries()
once, getName()
once for each entry in your address book, and then getEmailAddress()
once. So if there are N
entries in your address book, you have to make N+2
remote invocations. Note that you also must create N+1
remote object references, which is also an expensive operation. Not
only would that slow the process of opening an email
window if you had numerous names in your
address book, but it would also create heavy network traffic and place a
high load
on your directory server application,
leading to scalability problems.
Now consider this improved interface for Directory
:
public interface Directory extends Remote {
String[] getNames();
DirectoryEntry[] getEntries();
DirectoryEntry getEntryByName(String name);
void addEntry(DirectoryEntry entry);
void removeEntry(DirectoryEntry entry);
}
How much will this reduce the overhead imposed on your email application? Now you can call Directory.getNames()
once and retrieve all the names simultaneously, and you only have to call getEntryByName()
for the recipient to which you actually wish to send email. This process requires three remote invocations, instead of N+2
, and two remote objects, instead of N+1
.
If the address book has more than a few names, this invocation
reduction will make a huge difference in application responsiveness
and in total network and system load.
This technique used to reduce remote invocation and reference-passing overhead is called using a secondary object identifier . Instead of passing back a remote object, use an identifying attribute of the object -- in the case of the email application, the name -- to act as a lightweight identifier for the object. The secondary identifier conveys enough information about the object it describes so that you only need to fetch the remote objects you actually need. In the case of a directory system, a person's name is usually a good secondary identifier. As another example, in a securities portfolio management system, a stock's ticker symbol could be a good secondary identifier.
Another useful technique for reducing the number of remote invocations is bulk retrieval.
You could further improve the Directory
interface by adding another method to retrieve multiple desired DirectoryEntry
objects at once:
public interface Directory extends Remote {
String[] getNames();
DirectoryEntry[] getEntries();
DirectoryEntry getEntryByName(String name);
DirectoryEntry[] getEntriesByName(String names[]);
void addEntry(DirectoryEntry entry);
void removeEntry(DirectoryEntry entry);
}
Now, not only can you retrieve only the remote DirectoryEntry
objects you need, but you can retrieve all the desired entries with a
single remote method call. While that doesn't reduce
the marshaling overhead, it can
significantly reduce the number of round-trips and, if the network
latency is at all significant,
it will result in a more responsive system
(and reduce total network usage).
A third technique for further lightening the load on the RMI layer would be to not make the DirectoryEntry
a remote object, but instead define it as an ordinary object with
fields or accessors for name, address, email address, and
so forth. (In a CORBA system, we would use
the analogous object-by-value mechanism.) Then, when the email
application calls
getEntryByName()
, it will retrieve an entry object by value -- which doesn't require the creation of a stub or skeleton, and the invocation
of getEmailAddress()
will be a local invocation instead of a remote one.
Of course, all of these techniques rely on an understanding of how the remote objects will actually be used, but with that understanding, you don't even need to look at the remote class's implementation to spot several serious potential performance problems.
Conclusion
Distributed applications have performance characteristics that are substantially different from those of local applications. Many operations that are quite cheap in a local application can be very expensive in a remote application, and a naively designed remote interface can lead to an application that has serious scalability and performance problems.
Fortunately, it is easy to identify and fix many common distributed performance problems at design time by examining the common use cases and analyzing them for expensive operations like remote invocations and remote object creations. Judicious use of the techniques presented here -- secondary object identifiers, bulk retrieval, and return-by-value -- can substantially improve both user response time and total system throughput.
上一篇: python中的代码编码格式转换问题