目录
一、项目背景
Remote Dictionary Server (Redis),即远程字典服务,是使用ANSI C语言编写,支持网络、可基于内存亦可持久化的日志型的,Key-Value型的数据库,并提供多种语言的API。
它通常被称为数据结构服务器,因为值value可以是string、hash、list、set、sorted set等类型。应用场景:缓存、任务队列、排行榜、网站访问统计、数据过期处理、分布式集群架构中session分离。
本项目为非关系数据库课程的作业之一,利用了Redis对用户数量进行统计,在此基础上实现了周期性统计。
开发工具:IntelliJ IDEA
二、项目设计
2.1 类描述
2.1.1 JedisInstance.java
Jedis对象实例, 对外暴露了一个获取User对象的静态方法。
public class JedisInstance {
//私有化构造函数
private JedisInstance(){
}
//定义一个静态枚举类
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private JedisPool jedisPool;
//私有化枚举的构造函数
private SingletonEnum(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(30);
config.setMaxIdle(10);
jedisPool = new JedisPool(config, "127.0.0.1", 6379);
}
public JedisPool getInstnce(){
return jedisPool;
}
}
//对外暴露一个获取User对象的静态方法
public static JedisPool getInstance(){
return SingletonEnum.INSTANCE.getInstnce();
}
}
2.1.2 RedisUtil.java
Redis常用操作封装类,封装了value类型为string、hash、set、list、map的一些操作。该项目使用了简单的封装操作。
String:添加元素set,获取元素get,追加append,incr,decr,incrBy,decrBy
Hash:添加元素hset,获取元素hget,hincrBy
(项目中只用到了get、incrBy、decrBy、hincrBy、hget)
Set:添加元素sadd、删除元素srem、获得key对应的value总数scard、获得key对应的所有value smembers、判断set是否存在sismember、随机获取数据srandmember
List:添加元素lpush、获取list lrange、删除任意类型的key del
Map:设置map hmset、获取map的key个数hlen、获取map中所有的key hkeys、获取map中所有的value hvals、获取map中指定key的value hmget、获取map所有的key和value hgetAll、删除指定key的map。
public class RedisUtil {
//获取连接
private final Jedis jedis = JedisInstance.getInstance().getResource();
//==================================string===================================
//string的value在遇到incr, decr操作时会转成数值型进行计算
//数值加1
public void incr(String key){
jedis.incr(key);
}
//数值减1
public void decr(String key)
{
jedis.decr(key);
}
//数值加num
public void incrBy(String key, long num){
jedis.incrBy(key, num);
}
//数值减num
public void decrBy(String key, long num)
{
jedis.decrBy(key, num);
}
//为string添加元素
public void set(String key, String value) {
jedis.set(key,value);
}
//获取string
public String get(String key) {
return jedis.get(key);
}
//追加string
public void append(String key, String value) {
jedis.append(key,value);
}
//==================================hash===================================
//hash数值加num
public void hincrBy(String key, String field, long num)
{
jedis.hincrBy(key, field, num);
}
//为hash添加元素
public void hset(String key, String field, String value) {
jedis.hset(key,field,value);
}
//获取hash元素
public String hget(String key, String field) {
return jedis.hget(key,field);
}
......
}
2.1.3 Counters.json
Counter的json文件定义。共定义了10个counter:用户数量的显示、增加、减少;用户数量周期性的显示、增加、减少;字符串string的显示、增加;列表list的显示和增加。(目前只用到了前6个)
counter的格式示例:
"counters": [
{
"counterName": "userNumDisplay",
"counterIndex": "1",
"type": "num",
"keyFields": "user"
},
{
"counterName": "userNumIncr",
"counterIndex": "2",
"type": "num",
"keyFields": "user",
"valueFields": "3"
},
......
2.1.4 Actions.json
对counter进行的action的json文件定义。共定义了6个action:用户数量增加、减少;用户数量周期性的增加、减少;字符串的增加;列表的增加。(目前只用到了前4个)
action的格式示例:
"actions": [
{
"actionName": "INCR_USER",
"actionIndex": 1,
"describe": "increase user num",
"featureRetrieve": [
{
"counterName": "userNumDisplay"
}
],
"saveCounter": [
{
"counterName": "userNumIncr"
}
]
},
{
"actionName": "DECR_USER",
"actionIndex": 2,
"describe": "decrease user num",
"featureRetrieve": [
{
"counterName": "userNumDisplay"
}
],
"saveCounter": [
{
"counterName": "userNumDecr"
}
]
},
......
2.1.5 CountesrSpec.java
配置counters的json文件时,构造为该类。包含的成员变量与Counters.json中的key一致,有:counter的名称counterName、索引counterIndex、数据类型type、key的内容keyFields、value的内容valueFields和周期的内容fields。
public class CountersSpec {
private String counterName;
private String counterIndex;
private String type;
private String keyFields;
private String valueFields;
private String fields;
public CountersSpec(String counterName, String counterIndex, String type, String keyFields, String fields, String valueFields) {
this.counterName = counterName;
this.counterIndex = counterIndex;
this.type = type;
this.keyFields = keyFields;
this.fields = fields;
this.valueFields = valueFields;
}
public String getCounterName() {
return counterName;
}
public void setCounterName(String counterName) {
this.counterName = counterName;
}
public String getCounterIndex() {
return counterIndex;
}
public void setCounterIndex(String counterIndex) {
this.counterIndex = counterIndex;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKeyFields() {
return keyFields;
}
public void setKeyFields(String keyFields) {
this.keyFields = keyFields;
}
public String getValueFields() {
return valueFields;
}
public void setValueFields(String valueFields) {
this.valueFields = valueFields;
}
public String getFields() {
return fields;
}
public void setFields(String fields) {
this.fields = fields;
}
@Override
public String toString() {
return "CounterSpec{" +
"counterName='" + counterName + '\'' +
", counterIndex='" + counterIndex + '\'' +
", type='" + type + '\'' +
", keyFields='" + keyFields + '\'' +
", fields='" + fields + '\'' +
", valueFields='" + valueFields +
'}';
}
}
2.1.6 ActionsSpec.java
配置actions的json文件时,构造为该类。包含的成员变量与Actions.json中的key一致,有:action的名称ActionType、featureRetrieve和saveCounter。
public class ActionsSpec {
private enum ActionType {
INCR_USER,
DECR_USER,
INCR_USER_FREQ,
DECR_USER_FREQ,
ADD_STRING,
ADD_LIST
}
private ActionType type;
private List<String> featureRetrieve;
private List<String> saveCounter;
public ActionsSpec() {
}
public String getActionType() {
return type.name();
}
public void setActionType(String type) {
switch (type) {
case "INCR_USER":
this.type = ActionType.INCR_USER;
break;
case "DECR_USER":
this.type = ActionType.DECR_USER;
break;
case "INCR_USER_FREQ":
this.type = ActionType.INCR_USER_FREQ;
break;
case "DECR_USER_FREQ":
this.type = ActionType.DECR_USER_FREQ;
break;
case "ADD_STRING":
this.type = ActionType.ADD_STRING;
break;
case "ADD_LIST":
this.type = ActionType.ADD_LIST;
break;
default:
System.out.println("Illegal action type!");
break;
}
}
public List<String> getFeatureRetrieve() {
return featureRetrieve;
}
public void setFeatureRetrieve(List<String> featureRetrieve) {
this.featureRetrieve = featureRetrieve;
}
public List<String> getSaveCounter() {
return saveCounter;
}
public void setSaveCounter(List<String> saveCounter) {
this.saveCounter = saveCounter;
}
@Override
public String toString() {
return "ActionSpec{" +
"featureRetrieve=" + featureRetrieve +
", saveCounter=" + saveCounter +
'}';
}
}
2.1.7 CountersResolve.java
对counter进行解析。判断需要执行哪一个counter,然后调用RedisUtil中封装好的redis基本操作,实现数据的存储和读取。
部分代码:
public void executeCounter(String counterName) {
for (CountersSpec countersSpec : counterList) {
if (countersSpec.getCounterName().equals(counterName)) {
this.countersSpec = countersSpec;
}
}
System.out.println("Execute counter: " + countersSpec.getCounterName());
int num;
String time;
RedisUtil jedis = new RedisUtil();
switch (countersSpec.getCounterIndex()) {
case "1":
System.out.println("keyFields: " + countersSpec.getKeyFields());
if (jedis.get(countersSpec.getKeyFields()) != null) {
num = Integer.parseInt(jedis.get(countersSpec.getKeyFields()));
System.out.println("num: " + num);
} else
System.out.println("num: null");
break;
case "2":
System.out.println("keyFields: " + countersSpec.getKeyFields());
System.out.println("ValueFields: " + countersSpec.getValueFields());
jedis.incrBy(countersSpec.getKeyFields(),Long.parseLong(countersSpec.getValueFields()));
break;
......
}
}
完整代码中的case4、5、6有未完善之处
2.1.8 ActionsResolve.java
对action进行解析。先执行saveCounter,再执行featureRetrieve。
public class ActionsResolve {
public void executeAction(ActionsSpec actionsSpec) {
List<String> featureRetrieveList = actionsSpec.getFeatureRetrieve();
List<String> saveCounterList = actionsSpec.getSaveCounter();
String featureRetrieve = null;
String saveCounter = null;
for (String s : featureRetrieveList) {
featureRetrieve = s;
}
for (String s : saveCounterList) {
saveCounter = s;
}
CountersResolve countersResolve = new CountersResolve();
countersResolve.executeCounter(saveCounter);
System.out.println();
countersResolve.executeCounter(featureRetrieve);
System.out.println();
}
}
2.1.9 FileListener.java
文件监控类,当手动更改json文件时,程序检测到Counters.json或actions.json文件发生改变,会从固定的路径src/main/resources中自动读取并更新。
//文件监听器类
final class FileListener extends FileAlterationListenerAdaptor {
private final Logger logger = LoggerFactory.getLogger(FileListener.class);
// 文件更改
@Override
public void onFileChange(File file) {
Main.lock.compareAndSet(false, true);
System.out.println(file.getName() + " has be changed..");
Main.loadConfigJson();
System.out.println("JSON file has be overloaded..");
Main.lock.set(false);
}
}
2.1.10 Main.java
运行该程序后,启动文件监听,轮询间隔时间为1000ms。读取并加载两个json文件,构造为定义的ActionSpec和CounterSpec类,将json文件转换为string,再转换为JsonObject,最后转换为JsonArray,并存储在两个ArrayList中。
控制台显示所有action,操作者输入需要执行的action编号(输入0退出)。对于输入的action,实例化一个ActionsResolve类,并调动“执行action”方法。
ActionsResolve中实例化一个CountersResolve类,并调用“执行counter方法”。先执行saveCounter,再执行featureRetrieve。
CountersResolve类中实例化RedisUtil类。调用了string的添加、获取、追加和hash的添加和获取元素。
部分代码:
public static void main(String[] args){
System.out.println("Counter based on redis");
//启动文件监听
startObserver("src/main/resources", new FileListener());
System.out.println("File observer is started..");
//加载json文件
loadConfigJson();
System.out.println("JSON file is loaded..");
Scanner scanner = new Scanner(System.in);
String str;
do {
System.out.println("Actions lists(0 to exit): ");
for (int i = 0; i < actionList.size(); i++) {
System.out.println((i + 1) + ". " + actionList.get(i).getActionType());
}
System.out.println("Enter your choice(0~6): ");
str = scanner.nextLine();
if (str.equals("1") || str.equals("2") || str.equals("3")
|| str.equals("4") || str.equals("5") || str.equals("6")) {
ActionsResolve actionsResolve = new ActionsResolve();
try {
actionsResolve.executeAction(actionList.get(Integer.parseInt(str) - 1));
} catch (Exception e) {
e.printStackTrace();
}
} else if (str.equals("0")) {
try {
monitor.stop();
} catch (Exception e) {
e.printStackTrace();
}
System.exit(1);
} else {
System.out.println("Illegal enter! Please enter your choice(0~6): ");
}
}
while (!str.equals("0"));
}
}
2.2 调用关系
Main实例化了FileListener,ActionsResolve
FileListener调用了Main的loadConfigJson()
ActionsResolve调用了CountersResolve
CountersResolve调用了RedisUtil
RedisUtil调用了JedisInstance
2.3 技术要点
- 利用Reids实现数据的存取:在项目设想的运用场景中,需要高并发且高速的数据库,Redis不失为一种好的选择,项目简单封装了Redis的常用操作。
- 使用Counter系统管理配置:Counter系统通过saveCounter和featureRetrieve两个接口来实现数据通过Redis的存储,通过读取解析counters和actions的json文件来实现相关操作。在配置json文件的时候会把actions.json和counters.json分别构造为定义的ActionsSpec和CountersSpec类并放入ArrayList中。
- 通过commons io里的monitor来实现文件监控:在系统监控到res文件目录下的actions.json或counters.json文件发生改变并保存时,会在控制台发出提醒并按照路径重新下载两个json文件,不需要重启程序。
三、运行结果
运行程序,显示所有的操作actions,可以输入1-6进行操作(目前只实现了输入0或1-4的结果)。
输入1,增加用户数量(这里设置了一次增加3个):
输入2,减少用户数量(这里设置了一次减少1个):
输入3,在特定的时间周期内增加用户数量(一次加3个):
输入4,在特定的时间周期内减少用户数量(一次减1个):
四、完整代码下载
完整代码下载链接:
https://download.csdn.net/download/ycsss/13991447
五、参考博客
FileAlterationListenerAdaptor监听文件和文件夹
非常感谢看到这里的你!
如果对你有帮助的话,欢迎留下点赞和评论~