验证 J2Cache 进程间一级和二级缓存同步

本文将介绍怎样在两个进程之间同步一级缓存,即EhCache缓存。

我们先看看运行效果图:

验证 J2Cache 进程间一级和二级缓存同步

上图中,我们启动了两个java进程,分别运行在 8080 和 8081 端口。当我们在 8080 端口添加 name 和 version 缓存后,立马在 8081 端口所在的 Java 进程中显示出来。同样,从 8081 端口删除一个缓存,立马在 8080 中体现出来。如果你仔细看,你会发现我们在 8080 端口中新添加的 name 和 version 缓存信息后,在 8081 中首次显示出来的 level 为 2(表示来自二级缓存),然后变为 level = 1(一级缓存)。

我们先看看项目结构,如下图:

验证 J2Cache 进程间一级和二级缓存同步

application.properties 配置

下面将配置 Freemarker 模板,如下:

## Freemarker
# 指定 HttpServletRequest 的属性是否可以覆盖 controller 中与 model 同名项
spring.freemarker.allow-request-override=false
# 是否开启模版缓存
spring.freemarker.cache=true
# 是否检查模版路径是否存在
spring.freemarker.check-template-location=true
# 设定模版编码
spring.freemarker.charset=utf-8
# 设定Content-Type
spring.freemarker.content-type=text/html
# 设定所有request的属性在merge到模板的时候,是否要都添加到model中
spring.freemarker.expose-request-attributes=false
# 设定所有HttpSession的属性在merge到模板的时候,是否要都添加到model中
spring.freemarker.expose-session-attributes=false
# 设定是否以springMacroRequestContext的形式暴露RequestContext给Spring’s macrolibrary使用
spring.freemarker.expose-spring-macro-helpers=false
# 设定freemarker模板的前缀
spring.freemarker.prefix=
# 指定RequestContext属性的名
spring.freemarker.request-context-attribute=freemarker
# 设定模板的后缀
spring.freemarker.suffix=.ftl
# 设定模板的加载路径,多个以逗号分隔,默认: ["classpath:/templates/"]
spring.freemarker.template-loader-path=classpath:/templates/

j2cache.properties 配置

#J2Cache configuration


#########################################
# Cache Broadcast Method
# values:
# jgroups -> use jgroups's multicast
# redis -> use redis publish/subscribe mechanism (using jedis)
# lettuce -> use redis publish/subscribe mechanism (using lettuce, Recommend)
# rabbitmq -> use RabbitMQ publisher/consumer mechanism
# rocketmq -> use RocketMQ publisher/consumer mechanism
# none -> don't notify the other nodes in cluster
# xx.xxxx.xxxx.Xxxxx your own cache broadcast policy classname that implement net.oschina.j2cache.cluster.ClusterPolicy
#########################################

j2cache.broadcast = redis

# jgroups properties
jgroups.channel.name = j2cache
jgroups.configXml = /network.xml

# RabbitMQ properties
rabbitmq.exchange = j2cache
rabbitmq.host = localhost
rabbitmq.port = 5672
rabbitmq.username = guest
rabbitmq.password = guest

# RocketMQ properties
rocketmq.name = j2cache
rocketmq.topic = j2cache
# use ; to split multi hosts
rocketmq.hosts = 127.0.0.1:9876

#########################################
# Level 1&2 provider
# values:
# none -> disable this level cache
# ehcache -> use ehcache2 as level 1 cache
# ehcache3 -> use ehcache3 as level 1 cache
# caffeine -> use caffeine as level 1 cache(only in memory)
# redis -> use redis as level 2 cache (using jedis)
# lettuce -> use redis as level 2 cache (using lettuce)
# readonly-redis -> use redis as level 2 cache ,but never write data to it. if use this provider, you must uncomment `j2cache.L2.config_section` to make the redis configurations available.
# memcached -> use memcached as level 2 cache (xmemcached),
# [classname] -> use custom provider
#########################################

j2cache.L1.provider_class = ehcache3
j2cache.L2.provider_class = redis

# When L2 provider isn't `redis`, using `L2.config_section = redis` to read redis configurations
# j2cache.L2.config_section = redis

# Enable/Disable ttl in redis cache data (if disabled, the object in redis will never expire, default:true)
# NOTICE: redis hash mode (redis.storage = hash) do not support this feature)
j2cache.sync_ttl_to_redis = true

# Whether to cache null objects by default (default false)
j2cache.default_cache_null_object = true

#########################################
# Cache Serialization Provider
# values:
# fst -> using fast-serialization (recommend)
# kryo -> using kryo serialization
# json -> using fst's json serialization (testing)
# fastjson -> using fastjson serialization (embed non-static class not support)
# java -> java standard
# fse -> using fse serialization
# [classname implements Serializer]
#########################################

j2cache.serialization = json
#json.map.person = net.oschina.j2cache.demo.Person

#########################################
# Ehcache configuration
#########################################

# ehcache.configXml = /ehcache.xml
ehcache3.configXml = /ehcache3.xml
ehcache3.defaultHeapSize = 10000

#########################################
# Caffeine configuration
# caffeine.region.[name] = size, xxxx[s|m|h|d]
#
#########################################
caffeine.properties = /caffeine.properties

#########################################
# Redis connection configuration
#########################################

#########################################
# Redis Cluster Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (\u6570\u636e\u5e93\u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528 database = 0\uff09
# sharded -> sharded servers  (\u5bc6\u7801\u3001\u6570\u636e\u5e93\u5fc5\u987b\u5728 hosts \u4e2d\u6307\u5b9a\uff0c\u4e14\u8fde\u63a5\u6c60\u914d\u7f6e\u65e0\u6548 ; redis://user:password@127.0.0.1:6379/0\uff09
#
#########################################

redis.mode = single

#redis storage mode (generic|hash)
redis.storage = generic

## redis pub/sub channel name
redis.channel = j2cache
## redis pub/sub server (using redis.hosts when empty)
redis.channel.host =

#cluster name just for sharded
redis.cluster_name = j2cache

## redis cache namespace optional, default[empty]
redis.namespace =

## redis command scan parameter count, default[1000]
#redis.scanCount = 1000

## connection
# Separate multiple redis nodes with commas, such as 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379

redis.hosts = 127.0.0.1:6379
redis.timeout = 2000
redis.password =
redis.database = 0
redis.ssl = false

## redis pool properties
redis.maxTotal = 100
redis.maxIdle = 10
redis.maxWaitMillis = 5000
redis.minEvictableIdleTimeMillis = 60000
redis.minIdle = 1
redis.numTestsPerEvictionRun = 10
redis.lifo = false
redis.softMinEvictableIdleTimeMillis = 10
redis.testOnBorrow = true
redis.testOnReturn = false
redis.testWhileIdle = true
redis.timeBetweenEvictionRunsMillis = 300000
redis.blockWhenExhausted = false
redis.jmxEnabled = false

#########################################
# Lettuce scheme
#
# redis -> single redis server
# rediss -> single redis server with ssl
# redis-sentinel -> redis sentinel
# redis-cluster -> cluster servers
#
#########################################

## redis command scan parameter count, default[1000]
#lettuce.scanCount = 1000
lettuce.namespace =
lettuce.storage = hash
lettuce.channel = j2cache
lettuce.scheme = redis
lettuce.hosts = 127.0.0.1:6379
lettuce.password =
lettuce.database = 0
lettuce.sentinelMasterId =
lettuce.maxTotal = 100
lettuce.maxIdle = 10
lettuce.minIdle = 10
# timeout in milliseconds
lettuce.timeout = 10000
# redis cluster topology refresh interval in milliseconds
lettuce.clusterTopologyRefresh = 3000

#########################################
# memcached server configurations
# refer to https://gitee.com/mirrors/XMemcached
#########################################

memcached.servers = 127.0.0.1:11211
memcached.username =
memcached.password =
memcached.connectionPoolSize = 10
memcached.connectTimeout = 1000
memcached.failureMode = false
memcached.healSessionInterval = 1000
memcached.maxQueuedNoReplyOperations = 100
memcached.opTimeout = 100
memcached.sanitizeKeys = false

ehcache3.xml 配置

<!-- for ehcache 3.x -->
<config
        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns='http://www.ehcache.org/v3'
        xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">

    <!-- Don't remote default cache configuration -->
    <cache-template name="default">
        <key-type>java.lang.String</key-type>
        <value-type>java.io.Serializable</value-type>
        <expiry>
            <ttl unit="seconds">1800</ttl>
        </expiry>
        <resources>
            <heap>1000</heap>
            <offheap unit="MB">100</offheap>
        </resources>
    </cache-template>

    <cache alias="default" uses-template="default"/>

</config>

index.ftl

本文件提供 UI 简单界面,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>J2Cache</title>
    <script type="text/javascript" src="js/jquery-v1.8.1.js"></script>
</head>
<body>

<h3>新增/修改缓存</h3>
<div>
 键: <input id="key" type="text"/> &nbsp;&nbsp;
 值: <input id="value" type="text"/> &nbsp;&nbsp;
 <button id="btnSubmit">提交</button>
</div>

<h3>缓存列表 &nbsp;&nbsp;
 <label style="font-size:14px;font-weight:bold;">
        <input type="checkbox" id="autoRefresh" value="1" checked>自动刷新
 </label>
</h3>
<table width="100%" border="1">
    <thead>
        <tr>
            <th>key</th>
            <th>value</th>
            <th>region</th>
            <th>level</th>
            <th></th>
        </tr>
    </thead>
    <tbody id="keyList"></tbody>
</table>
<script type="text/javascript">
 var autoRefresh = false;
 var intervalObj = null;

 $(function(){
        autoRefresh = $("#autoRefresh:checked").val();

 function getCacheList(){
            $.getJSON("/getCacheList", {}, function(ret){
                var htmlCode = [];
 for(var attr in ret) {
                    var row = ret[attr] || {};
 htmlCode.push("<tr>")
                    htmlCode.push("<td>" + attr + "</td>");
 htmlCode.push("<td>" + row.value + "</td>");
 htmlCode.push("<td>" + row.region + "</td>");
 htmlCode.push("<td>" + row.level + "</td>");
 htmlCode.push("<td><a href=\"javascript:void(0);\" key=\"" + attr + "\"" +
                        " onclick=\"J2CacheDemo.deleteCache(event)\">删除</a></td>");
 htmlCode.push("</tr>")
                }
                $("#keyList").html(htmlCode.join(""));
 });
 }

        function startInterval() {
            if(!!intervalObj) {
                clearInterval(intervalObj);
 }
            intervalObj = setInterval(function(){
                if(!!autoRefresh) {
                    getCacheList();
 }
            }, 2000);
 }

        $("#autoRefresh").change(function(){
            autoRefresh = $("#autoRefresh:checked").val();
 startInterval();
 });

 // 添加缓存
 $("#btnSubmit").click(function(){
            var key = $("#key").val();
 var value = $("#value").val();
 $.getJSON("/addCache", {
                key: key,
 value: value
            }, function(ret){
                if(ret.statu == "1") {
                    $("#key").val("");
 $("#value").val("");
 }
                alert(ret.message);
 });
 });

 function deleteCache(event) {
            var key = $(event.target).attr("key");
 $.getJSON("/deleteCache", {
                key: key
            }, function(ret){
                if(ret.statu == "1") {
                    getCacheList();
 }
                alert(ret.message);
 })
        }

        getCacheList();
 startInterval();

 window.J2CacheDemo = {
            deleteCache: deleteCache
 };
 });
</script>
</body>
</html>

示例代码

package com.huangx.j2cache.demo2;

import net.oschina.j2cache.CacheChannel;
import net.oschina.j2cache.CacheObject;
import net.oschina.j2cache.J2Cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;

/**
 * 可以通过运行时动态指定端口,如下:
 * --server.port=8081
 */
@Controller
@SpringBootApplication
public class J2cacheDemo2Application {
    private static final String REGION_NAME = "default";
    private CacheChannel cacheChannel;

    public J2cacheDemo2Application() {
        this.cacheChannel = J2Cache.getChannel();
    }

    public static void main(String[] args) {
        SpringApplication.run(J2cacheDemo2Application.class, args);
    }

    @RequestMapping("/")
    public String index() {
        return "index";
    }

    /**
     * 添加缓存
     * @param key 键
     * @param value 值
     * @return
     */
    @GetMapping("/addCache")
    @ResponseBody
    public Map<String,Object> addCache(@RequestParam("key") String key,
                           @RequestParam("value") String value) {
        Map<String,Object> map = new HashMap<>();
        try {
            this.cacheChannel.set(REGION_NAME, key, value);
            map.put("statu", "1");
            map.put("message", "操作成功");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("statu", "0");
            map.put("message", "新增缓存失败,原因:" + e.getMessage());
        }
        return map;
    }

    /**
     * 删除缓存
     * @param key 键
     * @return
     */
    @GetMapping("/deleteCache")
    @ResponseBody
    public Map<String,Object> deleteCache(@RequestParam("key") String key) {
        Map<String,Object> map = new HashMap<>();
        try {
            this.cacheChannel.evict(REGION_NAME, key);
            map.put("statu", "1");
            map.put("message", "操作成功");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("statu", "0");
            map.put("message", "删除缓存失败,原因:" + e.getMessage());
        }
        return map;
    }

    /**
     * 获取缓存列表
     * @return
     */
    @GetMapping("/getCacheList")
    @ResponseBody
    public Map<String, CacheObject> getCacheList() {
        Map<String, CacheObject> map = new HashMap<String, CacheObject>();
        try {
            map = this.cacheChannel.get(REGION_NAME, this.cacheChannel.keys(REGION_NAME));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

}

运行上面实例,在程序参数中通过 --server.port 指定服务的端口,分别运行 --server.port=8080 和 --server.port=8081。

业精于勤,荒于嬉。——韩愈《进学解》
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号