2.4.2 ZDB


  • The configuration of the hall


    <xbean name="ChatHallInfo">
    	<variable name="name" type="string" />
    	<variable name="rooms" type="list" value="long" />
    </xbean>
    <table autoIncrement="true" name="chathalls" key="long" value="ChatHallInfo" />
    
    <xbean name="ChatRoomInfo">
    	<variable name="name" type="string" />
    	<variable name="hallid" type="long" />
    </xbean>
    <table autoIncrement="true" name="chatrooms" key="long" value="ChatRoomInfo" />
    
    <table name="hallnamecache" key="string" value="long" persistence="MEMORY" />
    

    Seen from the above description:

    1. The chathalls table is the table with the auto-incremental id, and describes the configuration of the hall. The value field xbean.ChatHallInfo have two attributes, the name of the hall and the roomid list of the hall.

    2. The chatroom table is the table with the auto-incremental id, and describes the configuration of the room. The value field xbean.ChatRoomInfo have two attributes, the name of the chatroom and the hallid of the hall.

    3. The hallnamecache table is the memory table, and map the hall name to the hallid.


  • Obtain the configuration

    SessionManager.java:


    public void onManagerInitialized(Manager manager, Config config) {
    	this.manager = (ServerManager) manager;
    	table.Chathalls.get().walk((k, v) -> {
    		Procedure.call(() -> {
    			table.Hallnamecache.insert(v.getName(), k);
    			return true;
    		});
    		return true;
    	});
    	if (table.Hallnamecache.get().getCacheSize() == 0)
    		.........................
    	else {
    		setViewHallsFromCache();
    		.........................
    	}
    }
    

    Walk in the chathalls table, with the hall name as the key and hallid as the value to initiate the memory table hallnamecache. If the hallnamecache has the data, the setViewHallsFromCache is called to continue to initiate the configuration of the hall.

    SessionManager.java:


    static void setViewHallsFromCache() {
    	final ArrayList<RoomChatHallInfo> halls = new ArrayList<>();
    	table.Hallnamecache
    		.get()
    		.getCache()
    		.walk((name, hid) -> {
    			final Procedure.Result r = Procedure.call(ProcedureHelper.nameProcedure("setViewHallsFromCache",
    				() -> {
    					xbean.ChatHallInfo chi = table.Chathalls.select(hid);
    					halls.add(new RoomChatHallInfo(
    						chi.getName(),
    						hid,
    						chi.getRooms()
    							.stream()
    							.map(rid -> {
    								RoomInfo.mapKey(rid, hid,rid);
    								return new ViewChatRoomInfo(table.Chatrooms.select(rid).getName(),rid);
    							})
    							.collect(() -> new ArrayList<ViewChatRoomInfo>(),List::add, List::addAll)));
    						return true;
    			}));
    			if (!r.isSuccess()) {
    				Trace.fatal("get all chatroom failed!",	r.getException());
    				System.exit(-1);
    			}
    		});
    	CommonInfo.getInstance().setHalls(halls);
    	lastSetHallsTimeStamp = System.currentTimeMillis();
    }
    

    The execution process is: first traversal the hallnamecache, using the hallid to query the xbean.ChatHallInfo one by one in the chathalls table; then traversal the rooms array to obtain the roomid(rid); then obtain the room name from the chatroom table via rid; finally generate all the configuration and save to the halls array.

    A question should be understood here: why traversal the hallnamecache table, but not the chathalls table? The reason is that the zdb provides two kinds of walk, one is traversal the memory cache (the walk in the setViewHallsFromCache), the other is traversal the underlying database (the walk in the onManagerInitialized). The underlying database saves the data after the checkpoint. Only the union set of the memory cache data and underlying data is the total data. The memory table only has the cache data without the underlying data. So the data of the memory table could be complete traversal once.

    There is another question: why not directly provide a walk to merge these two kinds of walk? The simple explanation is to reduce the lock. For example, the cache data is more latest than the underlying data, so the (k0, v0) of the cache is output first and the pair needs to be recorded to avoid the duplication when traversal the underlying database. When traversal the underlying the database, the record (k0, v?) is found. Because the relative record of the k0 has been output, the (k0, v?) has to be ignored. The problem is that if the k0 record is not be locked when transferring the (k0, v0), we could not suppose that the (k0, v?) is older than the (k0, v0). If the (k0, v?) is more latest than the (K0, v0) but be ignored, the result is worse than the dirty read. So the whole cache is locked to resolve this issue, output the cache record, then traversal the underlying database. If lock the whole cache, the other transaction using this table will be blocked and seriously affect the performance. Actually, the zdb provides the walk for the transaction performance. They are dirty read and should be avoid. So the merge of these two walk has no meaning.


  • Modify the configuration

    Delete the room as the example

    Main.java:


    cmdmap.put("deleteroom", (sessionid, params) -> {
    	if (params.length < 3 || !UserInfo.getInstance(sessionid).isCommandMode())
    		return;
    	Procedure.execute(() -> {
    		Long hallid = table.Hallnamecache.select(params[1]);
    		if (null == hallid) {
    			UserInfo.getInstance(sessionid)._setRecvedmessage(
    					new ChatMessage("bad hall name", sessionid));
    			return false;
    		}
    		xbean.ChatHallInfo hallinfo = table.Chathalls.update(hallid);
    		Long removeroomid = null;
    		for (Long roomid : hallinfo.getRooms()) {
    			xbean.ChatRoomInfo roominfo = table.Chatrooms.update(roomid);
    			if (params[2].equals(roominfo.getName())) {
    				removeroomid = roomid;
    				break;
    			}
    		}
    		if (null == removeroomid) {
    			UserInfo.getInstance(sessionid)._setRecvedmessage(
    					new ChatMessage("bad room name", sessionid));
    			return false;
    		}
    		GlobalId.delete(groupRoomNames, params[2]);
    		table.Chatrooms.delete(removeroomid);
    		hallinfo.getRooms().remove(removeroomid);
    		xbean.RoomInfoCache roominfocache = table.Roominfocache.update(removeroomid);
    		List<ChatRoom> roomlist = new ArrayList<>();
    		if (null != roominfocache) {
    			roomlist.add(roominfocache.getRoomview());
    			table.Roominfocache.delete(removeroomid);
    		}
    		updateViewHallsFromCache(sessionid, "delete room succeed",roomlist);
    		return true;
    	});
    });
    

    Query the hallid in the hallnamecache table according to the hall name. Query all the roomid in the chathalls table via hallid. Traversal theses roomid and match the room name which needs to be deleted in the chatrooms table, confirm the removeroomid, and delete the removeroomid record from the chatrooms table. Update the relative hallid record of the hallinfo table, and delete the removeroomid. All these are the operation to delete the room.

    Call updateViewHallsFromCache to update the configuration for the hall.

    Main.java:


    private static void updateViewHallsFromCache(long sessionid, String message, List<ChatRoom> destroylist) {
    	UserInfo.getInstance(sessionid).setRecvedmessage(new ChatMessage(message, sessionid));
    	ProcedureHelper.executeWhileCommit(() -> Engine
    		.getApplicationExecutor().execute(() -> {
    			SessionManager.setViewHallsFromCache();
    			destroylist.forEach(room -> room.destroyInstance());
    		}));
    }
    

    After the transaction is submitted, a task is called to refresh the configuration of the hall by executing the setViewHallsFromCache.

    Some usages of the storage procedure need to be noted:

    The select is seldom used when querying the value via key in the table. Unless no any modification is executed on the relative record, the update should be used. Please refer the Limax manual for the detailed information, the Server development/Zdb/Lock part. So the previous code just uses the select in the hallnamecache.

    The ProcedureHelper.executeWhileCommit arranges a task to be executed after the current transaction is submitted. If the transaction rollback, it is not be executed. In this execution environment, the lock of the xbean referred by the transaction has not been released, and these xbean could be read but not be written. The write operation might cause the exception and rollback the transaction. Actually, this function is provided to the view to implement the data sending function after the transaction is submitted. For the above code, after the transaction is submitted, a task to refresh the configuration is submitted through the applicationExecutor of the Engine. So when executing the setViewHallsFromCache, the locker related to the previous transaction is released and reduce the locker time, which is helpful for the performance improvement.


  • The user information


    <xbean name="UserInfo">
    	<variable name="nickname" type="string" />
    </xbean>
    <table name="userinfo" key="long" value="UserInfo" />
    

    How to modify the user information has been introduced in the SessionView part of the View and is very simple.


  • RoomInfoCache


    <table name="roominfocache" key="long" value="any:chatviews.ChatRoom" persistence="MEMORY" />
    

    The example here displays the memory table usage--- the value of any type, the roominfocache map the roomid to the TemporaryView object ChatRoom.

    UserInfo.java:


    private void joinRoom(long roomid, long sessionid) {
    	Procedure.execute(ProcedureHelper.nameProcedure("joinRoom", () -> {
    		ChatRoom view = table.Roominfocache.update(roomid);
    		if (null == view) {
    			.........................................
    			view = ChatRoom.createInstance();
    			view.setInfo(new ViewChatRoomInfo(info.getName(), roomid));
    			view.setRoomId(roomid);
    			table.Roominfocache.insert(roomid, view);
    		}
    		view.getMemberShip().add(sessionid);
    		return true;
    	}));
    }
    

    When the user enters the chatroom, the client obtains the ChatRoom via roomid. If the ChatRoom does not exist, the new one is created and saved to the cache. If it exists, the ChatRoom is directly obtained to use.

    Main.java:


    cmdmap.put("deleteroom", (sessionid, params) -> {
    	if (params.length < 3 || !UserInfo.getInstance(sessionid).isCommandMode())
    		return;
    	Procedure.execute(() -> {
    		....................
    		ChatRoom view = table.Roominfocache.update(removeroomid);
    		List<ChatRoom> roomlist = new ArrayList<>();
    		if (null != view) {
    			roomlist.add(view);
    			table.Roominfocache.delete(removeroomid);
    		}
    		updateViewHallsFromCache(sessionid, "delete room succeed",roomlist);
    		....................
    		return true;
    	});
    });
    

    When deleting the room from the hall, the update is used to query whether the corresponding ChatRoom exists. If it exists, it will be deleted from the roominfocache.

    Some questions should be clarified here. The memory table equals to a map with the ability to support the transaction. The any type could only be directly or indirectly (through the reference by the xbean) used as value by memory table. The ZDB does not care about the lifetime of the any object, which means that the any object does not be affected by the success or failure of the transaction. From the above deleteroom example, the view is saved in the roomlist and destroyed by the updateViewHallsFromCache. From the completion of the transaction, the above joinRoom is not perfect and should be written like below:


    view = ChatRoom.createInstance();
    final ChatRoom _view = view;
    ProcedureHelper.executeWhileRollback(() -> _view.destroyInstance());
    …………………
    

    From calling the ProcedureHelper.executeWhileRollback method here, the created view is destroyed through calling a task which is executed when the transaction fails.


Prev Next