The Java 2 Platform, Enterprise Edition (J2EE), defines the standard for developing and deploying multitier enterprise applications. At the core of J2EE architecture are application servers - containers for your J2EE components.
This article explains the architecture of an application server that embraces standards such as J2EE, and focuses on ways to leverage the application server platform for caching, load balancing, scalability, and clustering services. It also provides an understanding of how to build J2EE applications, by looking at both design and runtime scenarios. It concludes with some best practices for designing and implementing enterprise applications.
The Metamorphosis: From Web Servers to Application Servers
During the early '90s, the advent of the Internet gave birth to myriad programming models like CGI and FastCGI. This enabled ubiquitous access to computing resources. A shift toward server-centric IT development took shape to leverage these resources efficiently, and resulted in Web servers that could run various kinds of applications.This gave birth to the application server.
Later, application servers provided proprietary interfaces such as NSAPI and ISAPI that enabled developers to access a server's internal services. However, these techniques were not standard across application servers, limiting flexibility and deployment choices. Developers facing these challenges embraced standards such as J2EE that enabled them to focus on business logic, and not the underlying plumbing and proprietary interfaces. This created a drive for the next-generation application server that supported standards such as J2EE for building e-business applications.
Application Server Architectures
Few companies have been able to create an application server that addresses the different functionalities necessary in a multitiered architecture in a cohesive way. In general, application servers should provide:
Additionally, application servers are required to provide support for multiple platforms, including Linux, Solaris, and Windows NT.
- Programming standards and models such as J2EE
- Access to all tiers
- Application partitioning
- Messaging, transactional, and distributed services
This article uses Oracle9i Application Server (Oracle9iAS) (see Figure 1) to describe the basic building blocks of an application server including:
- Mechanism to handle HTTP requests
- Scalable execution environment for Java programs
- Containers for J2EE components
- Performance optimizations, such as caching, load balancing, fault tolerance, and clustering
Mere support of J2EE alone is not sufficient. Advanced features such as wireless integration, portals, business intelligence, Web services, collaboration, and process integration can make it easier for developers to create e-business applications. We'll describe these concepts using Oracle9iAS in an upcoming issue of JDJ.
Mechanism to Handle HTTP Requests
Oracle HTTP Server is powered by Apache, a popular Web server. The HTTP server not only serves files, but also provides functionality to execute programs to generate dynamic content. To facilitate e-business, HTTP engines support the HTTPS protocol, thereby providing a safe and secure transport. In choosing an HTTP server, one must pay attention to its ability to handle high loads.
Oracle9iAS builds on Apache's extensible architecture by adding modules to extend the core functionality. The modules abstract the server's operations to improve performance. Oracle HTTP Server comes with additional modules including mod_ssl, mod_perl, mod_ose (OracleServlet engine), and mod_plsql. Oracle9iAS has SSL support for both 40-bit and 128-bit encryption. Oracle provides customer support for all Apache modules shipped with Oracle9iAS.
Scalable Execution Environment for Java Programs
A JVM is an abstract machine that runs compiled Java programs. Every application server vendor that supports J2EE has to provide a concrete implementation that conforms to the JVM specification.
A platform is scalable if it provides consistent performance regardless of the number of concurrent users. As the load on standard VMs increases, congestion of requests can take place. As a result, more VMs have to be started to handle the increasing number of user requests. A few of these VMs can become overloaded and server response can start to fail. Oracle Enterprise Java Engine (EJE) addresses this problem with a session-oriented VM that reduces the response time by making sure each session has its own virtual Java VM, Java global variables, threads, and garbage collector. The underlying runtime provides the needed scalability and threading, enabling the VM to efficiently perform memory management utilizing a low session footprint.
Oracle9iAS includes a scalable EJE to provide an execution environment for Java components. It supports use of the standard JDK JVM for lightweight components and the Oracle EJE for heavy-duty applications. The Oracle EJE is the same engine featured in the database and has been proven for concurrency (see "Enterprise Java: Oracle8iJVM," JDJ, Vol. 5, issue 10).
Containers for Various J2EE Components
The J2EE runtime environment is composed of containers and components. A container acts as a vessel to hold the component, and provides a J2SE runtime environment and implementation of J2EE APIs for services like transactions, messaging, and distributed protocols. In addition, application servers come with a number of tools to create, assemble, deploy, and manage applications to support J2EE roles.
Containers play a critical role in hosting J2EE components by shielding users from the complexities of the middle tier. Containers take away the necessity of writing plumbing code and allow developers to focus on business logic, delegating the container to handle transaction management, scalability, persistence, and security services.
Oracle9iAS has a complete implementation of J2EE containers for enterprise APIs, including Servlet, EJB, JSP, XML, JMS, and JNDI. The Oracle EJE programming environment supports SQL data access through JDBC and SQLJ, distributed applications through EJB, and dynamic Web site development using JavaServer Pages and servlets.
Performance of an application server hinges on caching, load balancing, fault tolerance, and clustering.
One of the notable properties of the Internet is that, at times, an HTML page can get popular quickly, creating a hot spot on your Web site. These hot spots are dynamic in nature and keep moving around with time. A key performance measure for the Web is the speed with which content is served to users. As traffic on the Web increases, users are faced with increasing delays and failures in data delivery. Caching is one of the key strategies to improve performance.
Solutions should include a Web cache to improve application server performance and a database cache to improve the data access performance. Both technologies are necessary and work to complement each other. Oracle9iAS has both Web cache and database cache technologies.
Improving Application Server Performance Using Web Cache
An important issue in many caching systems is how to cache frequently changing data and provide fast response to users. The Oracle9iAS Web Cache solution includes:
- Dynamic content caching and content invalidation: A popular myth is that dynamic data is uncachable. However, Oracle9iAS Web Cache allows cachability rules for both static and dynamic content created by J2EE components. Cache invalidation can be time-based, or triggered by sending XML messages in an HTTP post request. For example, if a column in a table is updated, a database trigger can send an invalidation request to the cache.
- Personalization of cached data: To provide personalization features, Oracle9iAS Web Cache embeds session IDs in every URL to make them unique for each user. Using these IDs and a substitution mechanism, the Web cache can insert personalized information into cached objects. For example, a Welcome page with the greeting "Hello Sudhakar" is generated by a JSP. When the page is served through Web cache, it parses out the parameter and caches a "template." When the next site is accessed, the parameter is substituted into the template and served by Web cache. The second request won't even hit the application server.
- Server acceleration: In this technique, cache servers are placed in front of a cluster of Web servers to intercept all HTTP requests. The cache stores objects returned by an origin server, handles thousands of concurrent users, and frees processing resources on the origin server.
- Quality of service: To prevent an overload on origin servers, caching systems assure performance by setting a limit on the number of concurrent connections that servers can handle. Oracle9iAS Web Cache helps distribute the load to origin Web servers using a heuristic algorithm and request queues.
Improving Database Tier Performance Using Database Cache
Many applications rely on EJB components for heavy-duty transaction operations. Acquiring JDBC connections is usually the slowest part of the process. To circumvent this problem, most EJB servers perform connection pooling and caching through JNDI and data sources. JDBC drivers that support connection pooling are useful, but this alone is not enough for better performance.
Minimizing the number of round-trips between the middle tier and data tier can influence the response time. The relative percentage of read-only data in the middle-tier application can have an effect on the number of round-trips your application has to perform. Database caches are effective in multitier environments where databases are located on separate nodes. These caches reduce the load on the database tier by processing the most common read-only requests to data sets on the application server tier.
A database cache should be an operational aspect of the Web site and transparent to the application developer. Oracle9iAS Database Cache keeps track of queries and can intelligently route to the database cache or to the origin database without any application code modification. A flexible synchronization policy determines how frequently the origin database is replicated to all middle-tier caches. The database cache stores tables, packages, procedures, and functions. Cache management is highly configurable and hit/miss statistics are available through the Oracle Enterprise Manager console.
Oracle9iAS Database Cache is both an in-memory and disk-backed cache. This combination doesn't limit the cache size of the memory footprints and also means that the cache is not "cold" immediately after a machine reboot.
Load Balancing, Fault Tolerance, and Clustering
Load balancing deals with response times and the act of distributing connection loads across multiple servers. Fault tolerance ensures no single point of failure. Clustering involves grouping together independent nodes to work together as a single system, and architecting for high availability and scalability. Clustering is used to load balance and provide fault tolerance.
Oracle supports many ways of load balancing:
Routing Requests to Improve Response Times
- Round-robin DNS for distributing HTTP requests across the cluster: This simple mechanism works well for Web traffic in which multiple IP addresses are assigned the same name in the DNS name space. Requests are alternated between the hosts that are presented in rotational order.
- Use of hardware load-balancing devices and IP sprayers: To address the scalability problem, a router or "IP-Sprayer" is placed between the clients and a cluster of application servers to spread the load evenly over the nodes.
- Spawning a number of HTTP processes to handle load: This increases the ability of the server to handle thousands of client requests. Load balancing is used to distribute the load among the running instances.
- Using Oracle HTTP Servers in reverse proxy mode: Oracle HTTP Server can be used in front of origin servers simply configured as a reverse proxy (i.e., proxy for content servers). This mechanism relieves the origin server load by taking advantage of the caching features of the proxy server.
In addition, Oracle9iAS offers techniques to improve the performance of servlets, EJBs, and J2EE components delivering dynamic content. J2EE components rely on "HTTP" for communication between Web clients and servers, and "RMI/IIOP" for communication between objects requesting service from each other in a distributed computing environment.
When an HTTP request arrives, the load-balancing device redistributes the load over Web caches that sit in front of the application server farm. Oracle9iAS Web Cache distributes HTTP requests according to the relative capacity of each application server, which is configurable. If one of the application servers in the farm were to fail, Web cache automatically redistributes the load among the remaining servers. Oracle9iAS Web Cache reduces application server load not only on Oracle9iAS, but on other application servers too.
Balancing the Load of Servlet Processes
Oracle HTTP Server load-balances servlet processes that use the Apache JServ servlet engine. Several JServ processes can serve single or multiple instances of Oracle HTTP Server using a weighted load-balancing algorithm that can be configured, depending on the load-handling capability of the target machines.
Providing Fault Tolerance Using Listeners
Oracle9iAS listener/dispatcher architecture provides a fault-tolerant, resilient environment without a single point of failure. With this architecture, each physical machine has a listener on a designated port and a number of dispatchers to service J2EE container requests. The bridge in this paradigm is that each dispatcher registers itself with any number of nodes in the application server farm. Thus, if a particular node is no longer able to service requests, the listener will send incoming requests to another dispatcher on another node. It's important that an application server redirect both HTTP and IIOP requests intelligently for the purpose of load balancing. Redirection is essential to load balance at a protocol level; however, there's no concept of "redirection" in HTTP - but there is one for IIOP. Oracle leverages its listener/dispatcher architecture with Apache modules to provide HTTP redirection. For example, mod_ose load balances servlet requests across nodes by redirecting HTTP requests.
Oracle9iAS provides mechanisms to load balance incoming requests be they in IIOP or other network formats in multithreaded server mode. This has great benefits to enterprise components residing on multiple nodes. The listener process has the ability to load balance across these nodes using IIOP redirection. This is possible because of the listener's ability to inspect IIOP headers for session IDs and redirect them to the appropriate node. Incoming IIOP connections are redirected to dispatchers on the node with least load. The load is computed based on a number of parameters including CPU utilization and number of sessions in progress. With load balancing you can split the load across multiple servers. The Listener process within Oracle9iAS performs load balancing by distributing EJB lookup across multiple application servers. Services that are located in a number of places can be grouped together by specifying a service name in the URL. Listener handles HTTP connections by transferring such requests to configured dispatchers on a node.
A Glimpse into J2EE Best Practices
This section covers some of the best practices to keep in mind when writing J2EE applications. Using a flight reservations system deployed on an application server, several issues central to J2EE application development are addressed. The goal is to present a design based on the J2EE blueprint (http://Java.sun.com/j2ee/blueprints/) and to demonstrate the simplicity with which you can leverage the underlying application server for infrastructure services.
To meet these requirements, the application should be carefully designed using interfaces and design patterns (for example, Model-View-Controller, fašade, proxy). It should be logically partitioned as components in different tiers (see Table 1).
Figure 2 depicts the components for the end-to-end design of this J2EE application.
XML is used as a standard for information exchange. For example, given the XML content for the "Pick a Flight" screen, it can be transformed (using XSLT) into HTML for a Web browser or WML for a WAP device.
For Web users, JSPs are ideal to render this content as a rich HTML presentation. Oracle9iAS includes an XML Development Kit (XDK) with a DOM/SAX parser, XSLT processor, Oracle XML Schema Processor, and other utilities. Oracle9iAS also includes a wireless and portal framework to seamlessly transform content to be part of a portal or rendered for any number of wireless devices (Palm, WAP, Rim). Thus, the "Pick a Flight" screen is generated once, but is available through a Web interface, user-customizable portal, and wireless devices.
Regardless of which browser/devices are used to access the application, requests from concurrent users should be coordinated and tracked by the application tier. This tier manages the state of each user in the system and logically guides them through the process of building their itineraries. For example, this tier will know that after a user has selected an itinerary, he or she should be offered "discount rentals," before being taken to the "checkout" screen.
Servlets are suited to dispatch browser requests and perform controller functions (MVC pattern). In its post() method, the servlet should accept an XML message and evaluate the message in a Finite State Machine (FSM). For example, when a customer accesses the site, the servlet receives an XML message and establishes an HTTP session for this user. It then uses an FSM to handle the message, and responds with an XML document containing the list of flights for those sectors. This is rendered as HTML and presented as a list of flights from which the user selects a single entry.
Typical servlet engines use threads to isolate concurrent users. This becomes a bottleneck when there are a large number of users and a stateful application. A good design will limit session state to only the essentials; however, even this can cause resource (thread) contention within the container. Oracle's approach is to use a JVM architecture that isolates concurrent users. The result is less contention and a container that's more resilient to application failures within an individual session.
When creating the itinerary, the Application Tier will make calls such as validateCustomer(Customer customer) and handleReservationEvent(ReservationEvent re) to the Business Tier to validate the user's entry.
These methods will evaluate against business rules stipulating what constitutes a valid reservation for a particular customer. When the itinerary is finished, a session bean will make the reservation via a method call such as createBooking(Itinerary itinerary).
In certain business scenarios the session bean may interact with remote systems through XML messages. Making a JMS call from a session bean frees the developer from worrying about the distributed transaction spanning these remote systems. The container will provide this service.
Business processes today involve getting many types of information to multiple people according to constantly changing rules. The Oracle Workflow engine is integrated into Oracle9iAS to automate and route information according to business rules. Workflow supports both synchronous and asynchronous processes. For example, when the booking has been completed, postnotification function executes to send an itinerary to the user.
The persistent objects underlying Flight Reservations (e.g., PassengerManager and FlightsController) are entity beans. These objects are stored as rows in database tables, but leverage the container to look up, store, and manage a read-consistent cache of this persistent data in the middle tier.
Most J2EE implementations would use a database cache in the middle tier to minimize the overhead of fetching data from the database tier. For example information about Daily Flight Specials, Airport Locations, and City Tour Spots (i.e., relatively static data) should be cached ideally in the middle tier to avoid frequent network round-trips.
Entity bean containers typically support container-managed persistence (CMP) for relational data sources. But what if some of the persistent data (e.g., Flights) comes from nonrelational data sources (e.g., a hierarchical database)? Oracle offers Persistence Service Interface (PSI), a Java API that allows CMP providers such as Oracle Business Components for Java to manage O/R mappings from entity beans to database entities. Using such an interface makes it possible to map entity beans to any data source including other relational databases and even nonrelational databases. Thus these legacy sources can still benefit from the look-up, transaction, and caching simplicity provided by the container.
A common criticism against entity beans is the processing overhead imposed by the container, especially for fine-granularity beans. The Oracle9iAS container resides in the same shared memory and address space as the middle-tier application cache. This minimizes the container-imposed and network overheads since the relevant persistent data can be cached in the middle tier using the Oracle9iAS Database Cache.
Deployment and Runtime Architecture
Figure 2 depicts all the components residing within a single instance of the application server. This is one possible deployment scenario, but is usually not the case in a production environment, where it's best to distribute the components across many nodes to avoid scalability bottlenecks, and cater to fault tolerance.
Factors to consider when distributing components should be the proximity of components to the underlying data (i.e., avoid network round-trips) and how to distribute any resource bottlenecks (i.e., CPU, memory, I/O) across the different nodes. Ideally, a developer would balance the trade-off between data transfer/network overheads and fault tolerance/scalability requirements by distributing the components accordingly. For example, the servlet components may be CPU intensive, while the session bean and entity bean may be typically memory and I/O intensive. Should these components reside on the same node or be distributed across different ones? Since the balance is sometimes hard to find, the application server must be flexible enough to seamlessly distribute components to different nodes as necessary.
Figure 3 shows an extreme example of deploying each component on a separate tier to show the granularity to which the application server scales and provides fault tolerance. We've deployed the components into five tiers, across 10 physical nodes in the application server farm:
- Two nodes for HTTP listeners
- Two for JSP components
- Two for servlet components
- Two for session bean components
- Two for entity bean components
In this example the dispatcher on Machine C will register itself with the listeners on Machines B and G. The dispatcher on Machine I registers itself with the listeners on Machines C and H, and so on. An instance of the Web cache is placed in front of this "farm" to serve as a reverse-proxy, load balancer, and request router. Thus each tier can tolerate a machine failure and continue to service incoming application requests.
When a user accesses the flight reservations Web site, http://flights.oracle.com/, to make a travel reservation, the request first hits the Web cache listening on that host/port combination. If the request matches a recently cached item (based on predefined cachability rules), it's served directly from the Web cache and doesn't even hit any of the application server nodes.
If it were a "new" request, then the Oracle9iAS Web Cache, based on its predefined load-balancing criteria, would hand the request to a particular HTTP instance (i.e., Machine A or F). The HTTP Server then needs to invoke the JSP to render the user interface. Assuming the request hits Machine A, the HTTP listener on that machine will try to route it to any of the dispatchers serving JSPs (i.e., Machine B or Machine G). If Machine B is not available, the request will be sent to Machine G and vice versa.
The JSP then invokes the servlet to start the Flight Reservations application and keep track of the users' itinerary (i.e., HTTP session) as they build it. This request will be routed either to Machine C or H. Once the request has been routed to a Machine (say, H), and an HTTP session is established, all future requests from the same client will be routed automatically to the servlet instance on Machine H (using sess_iiop protocol).
The dispatcher architecture will similarly route this request to Machine D for business processing and to Machine E to query data from the entity beans. As the application queries persistent data (e.g., through the FlightController entity beans), the Database Cache Management Engine intelligently resolves the query by fetching data sets from the cache.
Similarly, changes to the underlying persistent data in the database tier (e.g., flight schedules) trigger an XML invalidation message to the Oracle9iAS Web Cache so that a subsequent request goes to the origin server to avoid serving stale data.
This article discussed the essential capabilities of application servers. To understand application design principles and ways in which J2EE applications can leverage the underlying application server infrastructure, we walked you through a simple J2EE flight reservations system. Using Oracle9iAS as an example, you've seen how to design, develop, assemble, and deploy J2EE enterprise applications.
Christopher G. Chelliah is a principal solutions architect at Oracle Corporation. He has been a project leader and active member on many projects involving distributed object-oriented systems based on a CORBA/Java/EJB/Internet platform. [email protected]
Sudhakar Ramakrishnan is a principal member of the technical staff at Oracle Corporation. He is a member of the Internet Platform group, and has more than six years of development experience in areas of user interface, protocols, distributed computing, and component frameworks.