Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群。如下图所示:
 
Ehcache支持多种集群方式,下面以RMI通信方式为例,来具体分析一下Ehcache集群的源码。
 

1服务Provider

Ehcache支持两种服务发现方式:一种是通过广播的方式,服务间自动发现,动态更新存活服务的列表;另一种就是在配置文件中配置好静态服务列表。

1.1自动发现配置

Server1和2的配置都一样,广播地址为230.0.0.1:

<cacheManagerPeerProviderFactory 
     class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" 
     properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
          multicastGroupPort=4446, timeToLive=32"/>

 

1.2手动发现配置

Server1的配置,rmiUrls为server2上的两个cache:

<cacheManagerPeerProviderFactory 
     class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" 
     properties="peerDiscovery=manual,rmiUrls=//server2:40001/sampleCache11|//server2:40001/sampleCache12"/>

 
Server2的配置,rmiUrls为server1上的两个cache:

<cacheManagerPeerProviderFactory 
     class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" 
     properties="peerDiscovery=manual,rmiUrls=//server1:40001/sampleCache11|//server1:40001/sampleCache12"/>

 

1.3源码分析-RMICacheManagerPeerProviderFactory

对应上面两种配置方式,根据peerDiscovery属性的值,创建自动或手动两种Provider。
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    public CacheManagerPeerProvider createCachePeerProvider(CacheManager cacheManager, Properties properties)
            throws CacheException {
        String peerDiscovery = PropertyUtil.extractAndLogProperty(PEER_DISCOVERY, properties);
        if (peerDiscovery == null || peerDiscovery.equalsIgnoreCase(AUTOMATIC_PEER_DISCOVERY)) {
            try {
                return createAutomaticallyConfiguredCachePeerProvider(cacheManager, properties);
            catch (IOException e) {
                throw new CacheException("Could not create CacheManagerPeerProvider. Initial cause was " + e.getMessage(), e);
            }
        else if (peerDiscovery.equalsIgnoreCase(MANUALLY_CONFIGURED_PEER_DISCOVERY)) {
            return createManuallyConfiguredCachePeerProvider(properties);
        else {
            return null;
        }
    }
 
    protected CacheManagerPeerProvider createManuallyConfiguredCachePeerProvider(Properties properties) {
        String rmiUrls = PropertyUtil.extractAndLogProperty(RMI_URLS, properties);
        if (rmiUrls == null || rmiUrls.length() == 0) {
            LOG.info("Starting manual peer provider with empty list of peers. " +
                    "No replication will occur unless peers are added.");
            rmiUrls = new String();
        }
        rmiUrls = rmiUrls.trim();
        StringTokenizer stringTokenizer = new StringTokenizer(rmiUrls, PayloadUtil.URL_DELIMITER);
        RMICacheManagerPeerProvider rmiPeerProvider = new ManualRMICacheManagerPeerProvider();
        while (stringTokenizer.hasMoreTokens()) {
            String rmiUrl = stringTokenizer.nextToken();
            rmiUrl = rmiUrl.trim();
            rmiPeerProvider.registerPeer(rmiUrl);
 
                LOG.debug("Registering peer {}", rmiUrl);
        }
        return rmiPeerProvider;
    }
 
以创建手动发现服务的Provider为例,新建ManualRMICacheManagerPeerProvider实例后,会调用其registerPeer方法将配置文件中配置的集群服务都注册上。
例如rmiUrls=//server2:40001/sampleCache11|//server2:40001/sampleCache12。
 
注册代码如下。注册方法仅仅将服务器地址保存到Map中,当后面要讲到的Replicator想要与集群中其他结点通信时,会调用Provider的listRemoteCachePeers()方法,
通过RMI的Naming.lookup()方法找到远程结点并返回。
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    public final synchronized void registerPeer(String rmiUrl) {
        peerUrls.put(rmiUrl, new Date());
    }
 
    public final synchronized List listRemoteCachePeers(Ehcache cache) throws CacheException {
        List remoteCachePeers = new ArrayList();
        List staleList = new ArrayList();
        for (Iterator iterator = peerUrls.keySet().iterator(); iterator.hasNext();) {
            String rmiUrl = (String) iterator.next();
            String rmiUrlCacheName = extractCacheName(rmiUrl);
 
            if (!rmiUrlCacheName.equals(cache.getName())) {
                continue;
            }
            Date date = (Date) peerUrls.get(rmiUrl);
            if (!stale(date)) {
                CachePeer cachePeer = null;
                try {
                    cachePeer = lookupRemoteCachePeer(rmiUrl);
                    remoteCachePeers.add(cachePeer);
                catch (Exception e) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Looking up rmiUrl " + rmiUrl + " through exception " + e.getMessage()
                                ". This may be normal if a node has gone offline. Or it may indicate network connectivity"
                                " difficulties", e);
                    }
                }
            else {
                    LOG.debug("rmiUrl {} should never be stale for a manually configured cluster.", rmiUrl);
                staleList.add(rmiUrl);
            }
 
        }
 
        //Remove any stale remote peers. Must be done here to avoid concurrent modification exception.
        for (int i = 0; i < staleList.size(); i++) {
            String rmiUrl = (String) staleList.get(i);
            peerUrls.remove(rmiUrl);
        }
        return remoteCachePeers;
    }
 
    public CachePeer lookupRemoteCachePeer(String url) throws MalformedURLException, NotBoundException, RemoteException {
        LOG.debug("Lookup URL {}", url);
        CachePeer cachePeer = (CachePeer) Naming.lookup(url);
        return cachePeer;
    }
 
广播方式的自动发现Provider与上面源码很像,只是多了两个心跳线程,一个用来监听服务器列表的变化,并动态更新Provider中的列表,一个用来发送自己的心跳。
1
2
3
4
5
6
7
8
9
10
11
    public MulticastRMICacheManagerPeerProvider(CacheManager cacheManager, InetAddress groupMulticastAddress,
                                                Integer groupMulticastPort, Integer timeToLive, InetAddress hostAddress) {
        super(cacheManager);
 
 
 
        heartBeatReceiver = new MulticastKeepaliveHeartbeatReceiver(this, groupMulticastAddress,
                groupMulticastPort, hostAddress);
        heartBeatSender = new MulticastKeepaliveHeartbeatSender(cacheManager, groupMulticastAddress,
                        groupMulticastPort, timeToLive, hostAddress);
    }
 

2服务Listener

服务Listener用来监听集群中其他服务器Ehcache的消息,所以Listener要监听本机上端口。

2.1配置文件

server1和server2配置一样,都是监听本机上40001端口:

<cacheManagerPeerListenerFactory 
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" 
properties="hostName=localhost, port=40001,
socketTimeoutMillis=2000"/>

 
2.2源码分析
 
取出当前配置,然后新建一个RMICacheManagerPeerListener实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    public final CacheManagerPeerListener createCachePeerListener(CacheManager cacheManager, Properties properties)
            throws CacheException {
        String hostName = PropertyUtil.extractAndLogProperty(HOSTNAME, properties);
 
        String portString = PropertyUtil.extractAndLogProperty(PORT, properties);
        Integer port = null;
        if (portString != null && portString.length() != 0) {
            port = Integer.valueOf(portString);
        else {
            port = Integer.valueOf(0);
        }
 
        //0 means any port in UnicastRemoteObject, so it is ok if not specified to make it 0
        String remoteObjectPortString = PropertyUtil.extractAndLogProperty(REMOTE_OBJECT_PORT, properties);
        Integer remoteObjectPort = null;
        if (remoteObjectPortString != null && remoteObjectPortString.length() != 0) {
            remoteObjectPort = Integer.valueOf(remoteObjectPortString);
        else {
            remoteObjectPort = Integer.valueOf(0);
        }
 
        String socketTimeoutMillisString = PropertyUtil.extractAndLogProperty(SOCKET_TIMEOUT_MILLIS, properties);
        Integer socketTimeoutMillis;
        if (socketTimeoutMillisString == null || socketTimeoutMillisString.length() == 0) {
            socketTimeoutMillis = DEFAULT_SOCKET_TIMEOUT_MILLIS;
        else {
            socketTimeoutMillis = Integer.valueOf(socketTimeoutMillisString);
        }
        return doCreateCachePeerListener(hostName, port, remoteObjectPort, cacheManager, socketTimeoutMillis);
    }
 
    protected CacheManagerPeerListener doCreateCachePeerListener(String hostName,
                                                                 Integer port,
                                                                 Integer remoteObjectPort,
                                                                 CacheManager cacheManager,
                                                                 Integer socketTimeoutMillis) {
        try {
            return new RMICacheManagerPeerListener(hostName, port, remoteObjectPort, cacheManager, socketTimeoutMillis);
        catch (UnknownHostException e) {
            throw new CacheException("Unable to create CacheManagerPeerListener. Initial cause was " + e.getMessage(), e);
        }
    }
 
之后RMICacheManagerPeerListener的init()方法会调用RMI的API,提供RMI服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    public void init() throws CacheException {
        if (!status.equals(Status.STATUS_UNINITIALISED)) {
            return;
        }
        RMICachePeer rmiCachePeer = null;
        try {
            startRegistry();
            int counter = 0;
            populateListOfRemoteCachePeers();
            synchronized (cachePeers) {
                for (Iterator iterator = cachePeers.values().iterator(); iterator.hasNext();) {
                    rmiCachePeer = (RMICachePeer) iterator.next();
                    bind(rmiCachePeer.getUrl(), rmiCachePeer);
                    counter++;
                }
            }
            LOG.debug(counter + " RMICachePeers bound in registry for RMI listener");
            status = Status.STATUS_ALIVE;
        catch (Exception e) {
            String url = null;
            if (rmiCachePeer != null) {
                url = rmiCachePeer.getUrl();
            }
 
            throw new CacheException("Problem starting listener for RMICachePeer "
                    + url + ". Initial cause was " + e.getMessage(), e);
        }
    }
 
    protected void startRegistry() throws RemoteException {
        try {
            registry = LocateRegistry.getRegistry(port.intValue());
            try {
                registry.list();
            catch (RemoteException e) {
                //may not be created. Let's create it.
                registry = LocateRegistry.createRegistry(port.intValue());
                registryCreated = true;
            }
        catch (ExportException exception) {
            LOG.error("Exception starting RMI registry. Error was " + exception.getMessage(), exception);
        }
    }
 
    protected void populateListOfRemoteCachePeers() throws RemoteException {
        String[] names = cacheManager.getCacheNames();
        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            Ehcache cache = cacheManager.getEhcache(name);
            synchronized (cachePeers) {
                if (cachePeers.get(name) == null) {
                    if (isDistributed(cache)) {
                        RMICachePeer peer = new RMICachePeer(cache, hostName, port, remoteObjectPort, socketTimeoutMillis);
                        cachePeers.put(name, peer);
                    }
                }
            }
        }
    }
 

3 事件Listener

通过Provider记录集群中其他服务器的地址,通过Listener在40001端口监听RMI消息,就差配置Replicator监听本地缓存增删改查的事件并发送到集群中其他服务器了。

3.1配置文件

可以在每个<cache>配置中提供一个EventListener。可以配置Replicator是同步还是异步的,并配置Put、Update、Remove等哪些事件需要同步:

<!-- Sample cache named sampleCache2. --> 
<cache name ="sampleCache2"
  maxEntriesLocalHeap ="10"
  eternal="false" 
  timeToIdleSeconds ="100"
  timeToLiveSeconds ="100"
  overflowToDisk="false" >
<cacheEventListenerFactory 
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" 
properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true,
replicateUpdatesViaCopy=false, replicateRemovals=true "/> 
</cache>

 
另一种简单配法是将EventListener配置到Cache的属性上,EventListener的所有属性都采用默认值。
<!-- Sample cache named sampleCache4. All missing RMICacheReplicatorFactory properties
default to true --><cachename="sampleCache4"maxEntriesLocalHeap="10"eternal="true"overflowToDisk="false"memoryStoreEvictionPolicy="LFU"><cacheEventListenerFactoryclass="net.sf.ehcache.distribution.RMICacheReplicatorFactory"/></cache>

3.2源码分析

 
RMICacheReplicatorFactory会根据replicateAsynchronously属性创建同步或异步的Replicator。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    public final CacheEventListener createCacheEventListener(Properties properties) {
        boolean replicatePuts = extractReplicatePuts(properties);
        boolean replicatePutsViaCopy = extractReplicatePutsViaCopy(properties);
        boolean replicateUpdates = extractReplicateUpdates(properties);
        boolean replicateUpdatesViaCopy = extractReplicateUpdatesViaCopy(properties);
        boolean replicateRemovals = extractReplicateRemovals(properties);
        boolean replicateAsynchronously = extractReplicateAsynchronously(properties);
        int asynchronousReplicationIntervalMillis = extractReplicationIntervalMilis(properties);
 
        if (replicateAsynchronously) {
            return new RMIAsynchronousCacheReplicator(
                    replicatePuts,
                    replicatePutsViaCopy,
                    replicateUpdates,
                    replicateUpdatesViaCopy,
                    replicateRemovals,
                    asynchronousReplicationIntervalMillis);
        else {
            return new RMISynchronousCacheReplicator(
                    replicatePuts,
                    replicatePutsViaCopy,
                    replicateUpdates,
                    replicateUpdatesViaCopy,
                    replicateRemovals);
        }
    }
 
先以同步Replicator的ElementPut事件为例,看同步Replicator是如何处理事件的。replicatePutsViaCopy属性的JavaDoc文档解释的很清楚,这个属性是用来说明,当发生Put事件时,是通知集群中其他服务器结点更新该Element,还是直接置为失效。前者适合Element的新建很耗时,而后者适合重新同步数据库中的数据。要通知的服务器列表就来自上面配置的CacheManagerPeerProviderFactory创建出的Provider对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    /**
     * Whether a put should replicated by copy or by invalidation, (a remove).
     * <p/>
     * By copy is best when the entry is expensive to produce. By invalidation is best when
     * we are really trying to force other caches to sync back to a canonical source like a database.
     * An example of a latter usage would be a read/write cache being used in Hibernate.
     * <p/>
     * This setting only has effect if <code>#replicateUpdates</code> is true.
     */
    protected boolean replicatePutsViaCopy;
 
    public void notifyElementPut(final Ehcache cache, final Element element) throws CacheException {
        if (notAlive()) {
            return;
        }
 
        if (!replicatePuts) {
            return;
        }
 
        if (!element.isSerializable()) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Object with key " + element.getObjectKey() + " is not Serializable and cannot be replicated");
            }
            return;
        }
 
        if (replicatePutsViaCopy) {
            replicatePutNotification(cache, element);
        else {
            replicateRemovalNotification(cache, (Serializable) element.getObjectKey());
        }
    }
 
    protected static void replicatePutNotification(Ehcache cache, Element element) throws RemoteCacheException {
        List cachePeers = listRemoteCachePeers(cache);
        for (Object cachePeer1 : cachePeers) {
            CachePeer cachePeer = (CachePeer) cachePeer1;
            try {
                cachePeer.put(element);
            catch (Throwable t) {
                LOG.error("Exception on replication of putNotification. " + t.getMessage() + ". Continuing...", t);
            }
        }
    }
 
    static List listRemoteCachePeers(Ehcache cache) {
        CacheManagerPeerProvider provider = cache.getCacheManager().getCacheManagerPeerProvider("RMI");
        return provider.listRemoteCachePeers(cache);
    }
 
异步Replicator的实现方式也很简单。以ElementPut事件为例,之前的同步Replicator是直接通知其他结点,异步Replicator将事件保存到队列中,
后台线程ReplicationThread会定时将队列中积压的事件发送到集群中其他结点。之前同步Replicator调用的CachePeer的单条增删改查方法,
这次ReplicationThread调用的是CachePeer的批量方法send()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    public final void notifyElementPut(final Ehcache cache, final Element element) throws CacheException {
        if (notAlive()) {
            return;
        }
 
        if (!replicatePuts) {
            return;
        }
 
        if (replicatePutsViaCopy) {
            if (!element.isSerializable()) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("Object with key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
                }
                return;
            }
            addToReplicationQueue(new CacheEventMessage(EventMessage.PUT, cache, element, null));
        else {
            if (!element.isKeySerializable()) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("Object with key " + element.getObjectKey()
                            " does not have a Serializable key and cannot be replicated via invalidate.");
                }
                return;
            }
            addToReplicationQueue(new CacheEventMessage(EventMessage.REMOVE, cache, null, element.getKey()));
        }
 
    }
 
    protected void addToReplicationQueue(CacheEventMessage cacheEventMessage) {
        if (!replicationThread.isAlive()) {
            LOG.error("CacheEventMessages cannot be added to the replication queue because the replication thread has died.");
        else {
            synchronized (replicationQueue) {
                replicationQueue.add(cacheEventMessage);
            }
        }
    }
 
    private final class ReplicationThread extends Thread {
        public ReplicationThread() {
            super("Replication Thread");
            setDaemon(true);
            setPriority(Thread.NORM_PRIORITY);
        }
 
        public final void run() {
            replicationThreadMain();
        }
    }
 
    private void replicationThreadMain() {
        while (true) {
            // Wait for elements in the replicationQueue
            while (alive() && replicationQueue != null && replicationQueue.size() == 0) {
                try {
                    Thread.sleep(asynchronousReplicationInterval);
                catch (InterruptedException e) {
                    LOG.debug("Spool Thread interrupted.");
                    return;
                }
            }
            if (notAlive()) {
                return;
            }
            try {
                if (replicationQueue.size() != 0) {
                    flushReplicationQueue();
                }
            catch (Throwable e) {
                LOG.error("Exception on flushing of replication queue: " + e.getMessage() + ". Continuing...", e);
            }
        }
    }
 
    private void flushReplicationQueue() {
        List replicationQueueCopy;
        synchronized (replicationQueue) {
            if (replicationQueue.size() == 0) {
                return;
            }
 
            replicationQueueCopy = new ArrayList(replicationQueue);
            replicationQueue.clear();
        }
 
 
        Ehcache cache = ((CacheEventMessage) replicationQueueCopy.get(0)).cache;
        List cachePeers = listRemoteCachePeers(cache);
 
        List resolvedEventMessages = extractAndResolveEventMessages(replicationQueueCopy);
 
 
        for (int j = 0; j < cachePeers.size(); j++) {
            CachePeer cachePeer = (CachePeer) cachePeers.get(j);
            try {
                cachePeer.send(resolvedEventMessages);
            catch (UnmarshalException e) {
                String message = e.getMessage();
                if (message.indexOf("Read time out") != 0) {
                    LOG.warn("Unable to send message to remote peer due to socket read timeout. Consider increasing" +
                            " the socketTimeoutMillis setting in the cacheManagerPeerListenerFactory. " +
                            "Message was: " + e.getMessage());
                else {
                    LOG.debug("Unable to send message to remote peer.  Message was: " + e.getMessage());
                }
            catch (Throwable t) {
                LOG.warn("Unable to send message to remote peer.  Message was: " + t.getMessage(), t);
            }
        }
        if (LOG.isWarnEnabled()) {
            int eventMessagesNotResolved = replicationQueueCopy.size() - resolvedEventMessages.size();
            if (eventMessagesNotResolved > 0) {
                LOG.warn(eventMessagesNotResolved + " messages were discarded on replicate due to reclamation of " +
                        "SoftReferences by the VM. Consider increasing the maximum heap size and/or setting the " +
                        "starting heap size to a higher value.");
            }
 
        }
    }
  
这里注意因为使用的是RMI通信方式,实际上CachePeer就是实现了RMI的Remote接口的存根对象。对CachePeer方法的调用就是对远程方法的调用。所以上面两种Replicator调用CachePeer时,就是将缓存事件同步到远程了。
 
结束语
 
RMI方式的Ehcache集群的实现比较简单、易理解,但对于前端用Nginx做负载均衡时,连续的几次调用可能是转发到不同的后端Ehcache服务器上,
异步方式的Ehcache缓存同步会不会有问题呢?

[转]RMI方式Ehcache集群的源码分析的更多相关文章

  1. RMI方式Ehcache集群的源码分析

    Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群.如下图所示: Ehcache支持多种集群方式,下面以RMI通信方式为例,来具体分 ...

  2. 【一起学源码-微服务】Nexflix Eureka 源码十二:EurekaServer集群模式源码分析

    前言 前情回顾 上一讲看了Eureka 注册中心的自我保护机制,以及里面提到的bug问题. 哈哈 转眼间都2020年了,这个系列的文章从12.17 一直写到现在,也是不容易哈,每天持续不断学习,输出博 ...

  3. tomcat集群实现源码级别剖析

    随着互联网快速发展,各种各样供外部访问的系统越来越多且访问量越来越大,以前Web容器可以包揽接收-逻辑处理-响应整个请求生命周期的工作,现在为了构建让更多用户访问更强大的系统,人们通过不断地业务解耦. ...

  4. Jedis cluster集群初始化源码剖析

    Jedis cluster集群初始化源码剖析 环境 jar版本: spring-data-redis-1.8.4-RELEASE.jar.jedis-2.9.0.jar 测试环境: Redis 3.2 ...

  5. Go合集,gRPC源码分析,算法合集

    年初时,朋友圈见到的最多的就是新的一年新的FlAG,年末时朋友圈最多的也是xxxx就要过去了,你的FLAG实现了吗? 这个公众号2016就已经创建了,但截至今年之前从来没发表过文章,现在想想以前很忙, ...

  6. Quartz.net 定时任务之储存与持久化和集群(源码)

    一.界面 1.这篇博客不上教程.直接看结果(包括把quartz任务转换成Windows服务) (1).主界面 (2).添加任务(默认执行) (3).编辑(默认开启) (4).关闭和开启 2.代码说明 ...

  7. 吾日三省吾身 java核心代码 高并发集群 spring源码&思想

    阿里面试题    未解决https://my.oschina.net/wuweixiang/blog/1863322 java基础  有答案  https://www.cnblogs.com/xdp- ...

  8. EhCache 集群 配置(RMI方式)

    这里先说明下环境:JDK1.6.ehcache-core-2.1.0.jar.Tomcat6.Spring3.0.2.使用的是RMI方式配置集群的,这里先吐槽下遇到的情况,在搜相关知识的时候发现到处都 ...

  9. lodash源码分析之缓存使用方式的进一步封装

    在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...

随机推荐

  1. Go语言学习笔记(六)net

    加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 net import "net" net包提供了可移植的网络I/O接口,包括TCP/IP.UD ...

  2. Java 字符排序问题

    Java 字符排序问题 未专注于排序算法,而是写了一个MyString类,实现了comparable的接口,然后用Arrays的sort方法来实现排序.我觉得这道题的难度在于如果比较两个.因为大小写的 ...

  3. VMware workstation虚拟集群实践(1)—— 配置集群多节点互信

    一. 简述 节点互信,是集群管理的基本操作之一.节点互信是通过SSH协议的公钥密钥认证来代替密码认证来实现的.对于单点批量管理多个节点,多个节点之间相互通信来说,配置SSH单方向信任,或者互信十分必要 ...

  4. TCP 滑动窗口

    滑动窗口协议 流量控制方法 PUSH 慢启动   隔一个报文段确认"的策略实际就是因为 delayed ack,同时接收到两个待确认的ACK包时,就立即发送确认包.   滑动窗口实例   解 ...

  5. wamp本地安装phpwind问题:‘Rewrit…

    一.问题出现的现象: 1.http://localhost/phpwind_v9.0_utf8/upload/install.php显示. 2.apache错误文件httpd.conf显示: .hta ...

  6. hdu--1026--Ignatius and the Princess I(bfs搜索+dfs(打印路径))

    Ignatius and the Princess I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (J ...

  7. 【思维】【水】 南阳oj 喷水装置(一)

    描述 现有一块草坪,长为20米,宽为2米,要在横中心线上放置半径为Ri的喷水装置,每个喷水装置的效果都会让以它为中心的半径为实数Ri(0<Ri<15)的圆被湿润,这有充足的喷水装置i(1& ...

  8. 异步加载回来的数据不受JS控制了

    写成下面这种方式时,异步加载回来的数据不受JS控制 $(."orderdiv").click(function(){ $(this).find(".orderinfo&q ...

  9. PHP部分问题的总结

    一.php连接sql sever 2005 中文编码转换问题. 这个问题是近期做Yii项目遇到的,而且php项目中用sql server做数据库,就是一个很坑的事,但没办法啊,客户是大爷,得听他的.( ...

  10. 9. Palindrome Number 回文 my second leetcode 20170807

    Determine whether an integer is a palindrome. Do this without extra space. click to show spoilers. S ...