基于Redis对用户数量进行统计——计数器系统的模拟实现

一、项目背景

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

五、参考博客

Java封装Redis常用操作

Redis自增计数

redis多种方式实现访问计数器实例详解

FileAlterationListenerAdaptor监听文件和文件夹

非常感谢看到这里的你!
如果对你有帮助的话,欢迎留下点赞和评论~

猜你喜欢

转载自blog.csdn.net/ycsss/article/details/112005912