Running the Tutorial

The tutorial shows a sample social networking application that stores people's profiles and posts. Each person has a profile that lists their friends, and each person can write posts.

Running the tutorial encompasses the following areas:

Prerequisites

Before you begin, verify that you meet the prerequisites:

Part 1: Start a Locator

JVMs running GemFire discover each other through multicast messaging or through a TCP locator service, which is called a locator. Multicasting is a good way to run quick tests, but it is less robust and flexible than the locator service. The locator runs as a separate process to which each new member connects to first discover the list of available peers. Clients connect to the locator to get server connection information. For more information, see How Member Discovery Works. For this example, we use a locator.

Figure 1. Peer Discovery Using a Locator Peer discovery using a locator

  1. Start a locator.
    $ gemfire start-locator -port=55221
    The locator process runs in the background, listening for connections on port 55221. To stop the process, you can use "gemfire stop-locator." But don't stop it yet.

Part 2: Create a Cache and Start Peers

You will store the data on several GemFire peers. The first step to starting up a GemFire peer is to create a com.gemstone.gemfire.cache.Cache. The cache is the central component of GemFire. It manages connections to other GemFire peers. For more information, see Cache Management.

  1. Use a text editor or your favorite IDE to open the GemfireDAO.java file in the tutorial application code. This file is located <SamplesDirectory>/tutorial/src/com/gemstone/gemfire/tutorial/storage directory where <SamplesDirectory> corresponds to the location where you unzipped the product examples distribution.
    Look at the cache creation in the initPeer method in GemfireDAO.java. The first thing this method does is create a cache:
    Cache cache = new CacheFactory()
          .set("locators", "localhost[55221]")
          .set("mcast-port", "0")
          .set("log-level", "error")
          .create();
    Secondly, it configures the cache:
    • The GemFire locators property tells the cache which locators to use to discover other GemFire JVMs.
    • The mcast-port property tells GemFire not to use multicast discovery to find peers.
    • The log-level property controls the log level of GemFire's internal logging. Here it is set to "error" to limit the amount of messaging that will show up in the console.
    When this code is run, after the call to create finishes, this peer will discover other peers and connect to them.
  2. Run the Peer application in two terminal sessions. You already have one window open where you started the locator. Start another terminal window. In each window, run the Peer application:
    $ java com.gemstone.gemfire.tutorial.Peer
    The peers start up and connect to each other.

Part 3: Create Replicated Regions

The GemFire com.gemstone.gemfire.cache.Region interface defines a key-value collection. Region extends the java.util.concurrent.ConcurrentMap interface. The simplest type of region is a replicated region. Every peer that hosts a replicated region stores a copy of the entire region locally. Changes to the replicated region are sent synchronously to all peers that host the region. For more information on regions, see Data Regions.

This procedure walks you through the creation of a replicated region called People.

Perform the following steps:
  1. Look at the initPeer method in GemfireDAO.java to see where it creates the people region.

    To create a com.gemstone.gemfire.cache.Region, you use a com.gemstone.gemfire.cache.RegionFactory class. This is the code in initPeer method that creates the people region:

        people = cache.<String, Profile>createRegionFactory(REPLICATE)
          .addCacheListener(listener)
          .create("people");
    The people region is constructed with com.gemstone.gemfire.cache.RegionShortcut.REPLICATE, which tells the factory to start with the configuration for a replicated region. This method adds a cache listener to the region. You can use a cache listener to receive notifications when the data in the region changes. This sample application includes a LoggingCacheListener class, which prints changes to the region to System.out and lets you see how entries are distributed.
  2. Look at the addPerson method. It adds the entry to the region by calling the put method of Region.
      public void addPerson(String name, Profile profile) {
        people.put(name, profile);
      }
    Calling put on the people region distributes the person to all other peers that host that region. After this call completes, each peer will have a copy of this person.
  3. Add people. In one of your terminal windows, type:
    person Isabella
    person Ethan
    You will see the users show up in the other window:
    In region people created key Isabella value Profile [friends=[]]
    In region people created key Ethan value Profile [friends=[]]

Part 4: Create Partitioned Regions

You expect to create a lot of posts. Because of that, you do not want to host a copy of the posts on every server. Thus you store them in a partitioned region. The API to use the partitioned region is the same as that for a replicated region, but the data is stored differently. A partitioned region lets you control how many copies of your data will exist in the distributed system. The data is partitioned over all peers that host the partitioned region. GemFire automatically maintains the number of copies of each partition that you request.



In the above diagram, the application code writes the post "I like toast" to two JVMs; it stores a primary copy in one peer (JVM 2) and a redundant copy in another peer (JVM 3). You will also notice that while each post appears in the local data of two peers, the logical view contains all three posts.


  1. Look at the code that creates the posts region. You can create partitioned regions with the PARTITION_XXX shortcuts.

    To create the posts region, the initPeer method uses the com.gemstone.gemfire.cache.RegionShortcut.PARTITION_REDUNDANT shortcut to tell GemFire to create a partitioned region that keeps one primary and one redundant copy of each post on different machines.
    posts =  cache.<PostID, String>createRegionFactory(PARTITION_REDUNDANT)
          .addCacheListener(listener)
          .create("posts");
  2. Start another terminal window and launch the peer application in that window.

    You should have three peers running now. Each post you create will be stored in only two of these peers.
  3. Add some posts to the posts region.
    > post Isabella I like toast
    > post Isabella LOL!
    > post Ethan Hello
    You see that the listener in only one of the JVMs is invoked for each post. That's because partitioned regions make one copy of the post the primary copy. By default GemFire only invokes the listener in the peer that holds the primary copy of each post.
  4. From any window, list the available posts with the posts command.

    You should be able to list all posts, because GemFire fetches them from the peer that hosts each post.
    > posts
    Ethan: Waaa!
    Isabella: I like toast
    Ethan: Hello
    If you kill one of the JVMs, you should still be able to list all posts. You can bring that member back up and kill another one, and you should still see all of the posts.
  5. Kill your peers before moving on to the next section. Type quit in each window.

Part 5: Set Up Client/Server Caching

You have a fully working system now, but the UI code is running in the same member in which you are storing data. That works well for some use cases, but it may be undesirable for others. For example, if you have a Web server for the UI layer, you may want to increase or decrease the number of Web servers without modifying your data servers. Or you may need to host only 100 GB of data, but have thousands of clients that might access it. For these use cases, it makes more sense to have dedicated GemFire servers and access the data through a GemFire client.

GemFire servers are GemFire peers, but they also listen on a separate port for connections from GemFire clients. GemFire clients connect to a limited number of these cache servers.



Like regular peers, GemFire servers still need to define the regions they will host. You could take the peer code you already have and create a com.gemstone.gemfire.cache.server.CacheServer instance programmatically, but this example uses the cacheserver script that ships with GemFire. The cacheserver script reads the cache configuration from a cache xml file, which is a declarative way to define the regions in the cache.

  1. Walk through configuring GemFire with XML. All GemFire configuration that you can do in java you can also do in xml. Look at the server.xml file located in the <SamplesDirectory>/tutorial/xml directory (where <SamplesDirectory> corresponds to the directory where you unzipped the example code files). This file creates the same replicated and partitioned regions as the java code in the GemfireDAO.initPeer method, demonstrated in the preceding procedure.
    ?xml version="1.0"?>
    <!DOCTYPE cache PUBLIC 
        "-//GemStone Systems, Inc.//GemFire Declarative Caching 6.6//EN"
        "http://www.gemstone.com/dtd/cache6_6.dtd">
    
    <cache>
      <region name="people" refid="REPLICATE">
        <region-attributes>
          <cache-listener>
            <class-name>
                com.gemstone.gemfire.tutorial.storage.LoggingCacheListener
            </class-name>
          </cache-listener>
        </region-attributes>
      </region>
    <region name="posts" refid="PARTITION_REDUNDANT">
        <region-attributes>
          <cache-listener>
            <class-name>
                com.gemstone.gemfire.tutorial.storage.LoggingCacheListener
            </class-name>
          </cache-listener>
        </region-attributes>
      </region>
    </cache>
    
  2. From the tutorial directory, start two cache servers.
    $ mkdir server1
    $ cacheserver start locators=localhost[55221] mcast-port=0 \
          cache-xml-file=../xml/server.xml -server-port=0 -dir=server1
    $ mkdir server2
    $ cacheserver start locators=localhost[55221] mcast-port=0 \
          cache-xml-file=../xml/server.xml -server-port=0 -dir=server2
    The cacheserver script starts a GemFire peer (cache server) that listens for client connections. The cache servers should now be running. Here is what all those command line parameters mean.
    locators
    List of locator hosts and ports that discover other cache servers.
    mcast-port
    Multicast port that discovers other cache servers. 0 means do not use multicast.
    cache-xml-file
    Where to find the cache xml file to use, relative to the working directory of the server.
    server-port
    Port on which to listen for GemFire clients. 0 means the server will listen on an ephemeral port, which is a temporary port assigned to the process by the OS.
    dir
    The working directory of the server. Logs for the server are written to this directory.
  3. Use a text editor or your favorite IDE to open the GemfireDAO.java file in the tutorial application code. This file is located <SamplesDirectory>/tutorial/src/com/gemstone/gemfire/tutorial/storage directory where <SamplesDirectory> corresponds to the location where you unzipped the product examples distribution.
    1. Look at the code in GemfireDAO.java to see how it starts a client. Starting a GemFire client is very similar to starting a GemFire peer. In the GemFire client, you create an instance of com.gemstone.gemfire.cache.client.ClientCache, which connects to the locator to discover servers.
      Examine the GemfireDAO.initClient method. The first thing the method does is create a ClientCache:
      ClientCache cache = new ClientCacheFactory()
            .addPoolLocator("localhost", 55221)
            .setPoolSubscriptionEnabled(true)
            .setPoolSubscriptionRedundancy(1)
            .set("log-level", "error")
            .create();
      Once you create a ClientCache, it maintains a pool of connections to the servers similar to a JDBC connection pool. However, with GemFire you do not need to retrieve connections from the pool and return them. That happens automatically as you perform operations on the regions. The pool locator property tells the client how to discover the servers. The client uses the same locator that the peers do to discover cache servers. Setting the subscription enabled and subscription redundancy properties allow the client to subscribe to updates for entries that change in the server regions. You are going to subscribe to notifications about any people that are added. The updates are sent asynchronously to the client. Because the updates are sent asynchronously, they need to be queued on the server side. The subscription redundancy setting controls how many copies of the queue are kept on the server side. Setting the redundancy level to 1 means that you can lose 1 server without missing any updates.
    2. Look at the code that creates a proxy region called posts in the client.

      Creating regions in the client is similar to creating regions in a peer. There are two main types of client regions, PROXY regions and CACHING_PROXY regions. PROXY regions store no data on the client. CACHING_PROXY regions allow the client to store keys locally on the client. This example uses a lot of posts, so you won't cache any posts on the client. You can create a proxy region with the shortcut PROXY. Look at the GemFireDAO.initPeer method. This method creates the posts region like this:
          posts = cache.<PostID, String>createClientRegionFactory(PROXY)
            .create("posts");
    3. Look at the code that creates a caching proxy region called people in the client.

      You do not have many people, so for this sample the client caches all people on the client. First you create a region that has local storage enabled. You can create a region with local storage on the client with ClientRegionShortcut.CACHING_PROXY. In the initClient method, here's where the people region is created.
          people = cache.<String, Profile>createClientRegionFactory(CACHING_PROXY)
            .addCacheListener(listener)
            .create("people");
    4. Look at the code that calls the registerInterest method to subscribe to notifications from the server.

      By creating a CACHING_PROXY, you told GemFire to cache any people that you create from this client. However, you can also choose to receive any updates to the people region that occur on other peers or other clients by invoking the registerInterest methods on the client. In this case you want to register interest in all people, so you cache the entire people region on the client. The regular expression ".*" matches all keys in the people region. Look at the initClient method. The next line calls registerInterestRegex:
          people.registerInterestRegex(".*");
      When the registerInterestRegex method is invoked, the client downloads all existing people from the server. When a new person is added on the server it is pushed to the client.
    5. Look at the code that iterates over keys from the client.

      Look at the getPeople and getPosts methods in GemFireDAO.
        public Set<String> getPeople() {
          return people.keySet();
        }
        
        public Set<PostID> getPosts() {
          if(isClient) {
            return posts.keySetOnServer();
          } else {
            return posts.keySet();
          }
        }
      
      GemFireDAO.getPeople calls people.keySet(), whereas GemFireDAO.getPosts calls posts.keySetOnServer() for the client. That's because the keySet method returns the keys that are stored locally on the client. For the People region you can use your locally cached copy of the keys, but for the Post region you need to go to the server to get the list of keys.
  4. Open a new terminal window and start the client application.
    $ java com.gemstone.gemfire.tutorial.Client

    You should be able to add people and posts from the client. You can start another client and see that people are sent from one client to the other.

Part 6: Add and Stop Cache Servers (Peers)

You can dynamically add peers to the system while it is running. The new peers are automatically discovered by the other peers and the client. The new peers automatically receive a copy of any replicated regions they create. However, partitioned regions do not automatically redistribute data to the new peer unless you explicitly instruct GemFire to rebalance the partitioned region. With the cacheserver script, you can pass a command line flag indicating that the new peer should trigger a rebalance of all partitioned regions.


  1. Add a new peer.
    $ mkdir server3
    $ cacheserver start locators=localhost[55221] mcast-port=0 \
          cache-xml-file=../xml/server.xml -server-port=0 -dir=server3 -rebalance
  2. Stop cache servers.
    1. Stop one of the cacheservers:.
      $ cacheserver stop -dir=server1

      Your data is still available and the client automatically ignores the dead server.

    2. Stop the other cache servers. You can leave the client running.
      $ cacheserver stop -dir=server2
      $ cacheserver stop -dir=server3

Part 7: Configure Persistence

GemFire supports shared-nothing persistence. Each member writes its portion of the region data to its own disk files. For the People region, each peer will be writing the entire region to its own disk files. For the Posts region, a copy of each post will reside on two different peers.




  1. Walk through configuring a persistence region.

    Look at the <SamplesDirectory>/xml/persistent_server.xml file (where <SamplesDirectory> corresponds to the directory where you unzipped the example code files). It's exactly the same as the server.xml file, except that each region uses the XXX_PERSISTENT shortcut.
    <?xml version="1.0"?>
    <!DOCTYPE cache PUBLIC
        "-//GemStone Systems, Inc.//GemFire Declarative Caching 6.6//EN"
        "http://www.gemstone.com/dtd/cache6_6.dtd">
    
    <cache>
      <region name="people" refid="REPLICATE_PERSISTENT">
        <region-attributes>
          <cache-listener>
            <class-name>
              com.gemstone.gemfire.tutorial.storage.LoggingCacheListener
            </class-name>
          </cache-listener>
        </region-attributes>
      </region>
    	<region name="posts" refid="PARTITION_REDUNDANT_PERSISTENT">
        <region-attributes>
          <cache-listener>
            <class-name>
              com.gemstone.gemfire.tutorial.storage.LoggingCacheListener
            </class-name>
          </cache-listener>
        </region-attributes>
      </region>
    </cache>
    
  2. Start two servers using this persistent_server.xml.
    $ cacheserver start locators=localhost[55221] mcast-port=0 \
        cache-xml-file=../xml/persistent_server.xml -server-port=0 -dir=server1
    $ cacheserver start locators=localhost[55221] mcast-port=0 \
        cache-xml-file=../xml/persistent_server.xml -server-port=0 -dir=server2
  3. Add some posts from the client.
  4. Kill both servers and restart them to make sure the posts are recovered.
    $ cacheserver stop -dir=server1
    $ cacheserver stop -dir=server2

    When you restart persistent members, you call cacheserver start in parallel for each server.

    The reason is that GemFire ensures that your complete data set is recovered before allowing the JVMs to start. Remember that each member is persisting only part of the Posts region. Each GemFire member waits until all posts are available before starting. This protects you from seeing an incomplete view of the posts region.

    Unix
    $ cacheserver start locators=localhost[55221] mcast-port=0 \
        cache-xml-file=../xml/persistent_server.xml -server-port=0 -dir=server1 &
    $ cacheserver start locators=localhost[55221] mcast-port=0 \
        cache-xml-file=../xml/persistent_server.xml -server-port=0 -dir=server2 &

    Windows

    $ start /B cacheserver start locators=localhost[55221] mcast-port=0 
        cache-xml-file=../xml/persistent_server.xml -server-port=0 -dir=server1
    $ start /B cacheserver start locators=localhost[55221] mcast-port=0 
        cache-xml-file=../xml/persistent_server.xml -server-port=0 -dir=server2