`

Redis工作总结

 
阅读更多
1. 需求:把ChrmMember类存入Redis, 以备重启server后,聊天室的成员不会丢失。

ChrmMember类的结构:
-- private int msgNum; //聊天室中的消息数
-- private long lastMsgTime; //聊天室中最新的消息时间
-- private Stack<String> list = new Stack<>(); //存储聊天室成员userId的stack, 按加入聊天室时间顺序存储
-- private HashMap<String, Member> members = new HashMap<String, Member>(); //hashmap的聊天室成员,key是userId, value是Member类的实例,表示聊天室成员。

2. 工作要思考的问题:
1)存入redis时候,是否可以把整个ChrmMember类序列化后存储?
答:这样不好。因为将来的扩展性考虑,如果将来要添加ChrmMember或者Member的成员变量,那么redis存储的被序列化过的旧的数据有问题(只有要序列化的类中只包含成员变量的时候,这样当成员变量添加或删除时,旧的数据才可用)。
实际采用的办法是,将ChrmMember拆分成两个类,存到redis里面,分别为:
-- ChrmInfoForRedis.java: 包含msgNum和lastMsgTime
-- ChrmMemForRedis.java: 与Member结构完全一致,只是没有set,get方法,成员变量是public的,这样是为了减小序列化后文件的大小,提高传输性能
这里,我们没有把list存入redis, 而是在反序列化之后通过排序方式重新构建

2)何时调用Redis操作?
在对于缓存做set, get操作时,同样操作redis.

3) 如何设计redis的存储结构?
-- 存储ChrmInfoForRedis: 使用map结构。
选map而不是key/value结构是因为:对于ChrmInfoForRedis类本身要有一个Key,这里我用appId类表示, 第二个key用chatroomId表示,这样既可以查询聊天室个数(根据appId), 也可以查询具体的聊天室(根据appId+chatroomId)。所以,我用map结构,相当于有两个Key, 第一个key标识该业务(存储ChrmInfoForRedis),第二个Key表示聊天室,value表示ChrmInfoForRedis序列化后的结果。

-- 存储ChrmMemForRedis同理:key1: chrmMem_appId_chrmId, key2: userId, value: ChrmMemForRedis反序列化后的结果

4)写redis操作(添加、删除)时需要注意的问题?
最重要的是redis什么时候从redis池获取ShardedJedis的实例,及何时释放资源,通常这样写redis操作。
ShardedJedis jedis = RedisManager.getShardedJedis();
try {
    ...//jedis操作,如:jedis.hdel(key1, key2);
} catch (Exception e) {
    logger.error(e.getMessage(), e);
    RedisManager.releaseShardeJedis(jedis);
} finally {
    RedisManager.returnShardedJedisResource(jedis);
}
但要注意的是,a)避免多次不必要的创建、释放资源操作(如果redis资源会一直被使用,不用每次都释放资源然后再申请)
b)用完千万不要忘记释放redis,否则会outOfMemory.

5)写jedis时需要考虑的性能问题?
jedis操作时,由于存储redis是要经过网络的(存储到专门的redis服务器),所以其性能不如直接写入内存快。
当我们操作缓存时,同时操作Redis,在大数据高并发情况下,redis操作会造成性能瓶颈,给用户造成延时甚至超时请求失败。
基于这样的考虑,我做了如下优化:
采用异步方式:对redis存的时候(因为存时不严格要求数据实时),取的时候要求实时,就不适合异步模型了。
为redis写了个生产者/消费者异步模型,添加了阻塞队列:
private static LinkedBlockingQueue<ArrayList<byte[]>> setBytesQueue = new LinkedBlockingQueue<ArrayList<byte[]>>(1024*256);
作为成员变量,当操作redis时(这里主要是存),直接存入该缓存,相当于生产者。

那么,怎样写消费者呢? 在类的开头static代码块里面

new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
        try {
            String key = delByStrKeyQueue.take();
            //通过key执行Redis操作
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e1) {
                logger.error(e.getMessage(), e1);
            }
        }
    }
}).start();

这样就可以异步消费了。

这里面有一个技巧:使用了LinkedBlockingQueue的take方法,而不是poll. 这是因为,redis中的数据(可能是删除掉的聊天室),不是每时每刻queue中都有数据的,所以用take方法做(当没有值时,线程阻塞等待),而不是用poll一直去queue中取数据。这样节约了CUP资源。

这样够优化了吗?如果当queue中的数据非常多需要消费时,我们必须要等待redis执行完毕,再去消费另一条,而redis操作是需要通过网络的(传输到redis服务器),所以解决此问题的方法:redis操作使用线程池做异步操作:每次对Redis操作时,只把这个任务存入线程池(此步骤为内存操作),然后就可以继续消费queue中的下一条数据了。然后线程池异步消费每一个线程。

注意初始化时线程池的大小,我们通常这样做:
public static ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);

然后为每一个需要消费的队列专门写一个内部线程类,如:
public static class SetBytesQueueTask implements Runnable {
    private ArrayList<byte[]> list;

    public SetBytesQueueTask(ArrayList<byte[]> list) {
        this.list = list;
    }

    @Override
    public void run() {
        if (list != null && list.size() == 3) {
            byte[] key1 = list.get(0);
            byte[] key2 = list.get(1);
            byte[] value = list.get(2);
            if (key1 != null && key2 != null && value != null) {
                ShardedJedis jedis = RedisManager.getShardedJedis();
                try {
                    jedis.hset(key1, key2, value);
                } catch (Exception e) {
                    logger.error("Error happened when consuming SetBytesQueueTask in RedisUtil", e);
                    RedisManager.releaseShardeJedis(jedis);
                } finally {
                    RedisManager.returnShardedJedisResource(jedis);
                }
            } else {
                logger.error("Key has null value in SetBytesQueueTask");
            }
        } else {
            logger.error("SetBytesQueueTask element's size should be 3, but now it is {}, skip...", list.size());
        }
    }
}

然后这样消费线程:
new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            try {
                String key = delByStrKeyQueue.take();
                pool.submit(new DelByStrKeyQueueTask(key));
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e1) {
                    logger.error(e.getMessage(), e1);
                }
            }
        }
    }
}).start();










分享到:
评论

相关推荐

    Redis学习总结

    综上所述,了解Redis的工作原理及其数据结构对于开发高性能的应用程序至关重要。掌握Redis的安装、配置和基本使用方法是成为一名合格的Redis用户的首要步骤。而随着对Redis的进一步学习,例如学习其持久化机制、事务...

    redis之相关理解分析以及面试问题总结

    本文将深入探讨Redis的基本概念、工作原理、存储机制,并总结一些常见的面试问题。 1. Redis基本概念: - 数据类型:Redis支持五大数据类型:字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted...

    redis桌面链接工具redis-desktop

    - **数据分析**:对于非技术人员来说,使用RDM可以更直观地理解存储在Redis中的数据结构和内容,便于进行数据分析工作。 #### 四、总结 Redis Desktop Manager是一款功能全面、易于使用的Redis管理工具,无论是对于...

    Redis-6.2.7 Windows 版

    总结来说,"Redis-6.2.7 Windows 版"为Windows用户提供了一个可以直接使用的Redis服务,具备高性能、易部署的特点。在使用过程中,用户应关注配置、持久化、安全以及资源管理,确保系统的稳定和高效运行。

    NoSql数据库之Redis笔记

    - **集群搭建**:通过多节点协同工作来提供高可用性和可扩展性,支持数据分片等功能。 - **Jedis连接集群**:通过Jedis Cluster类来实现对Redis集群的支持。 通过以上介绍,我们可以看到Redis不仅是一种高性能的...

    redis 离线集群redis5.0以上版本

    总结来说,Redis 5.0.2在Linux环境下的部署需要考虑集群配置、数据分布、故障恢复、客户端适配等多个方面。理解并掌握这些知识点对于构建高效、可靠的Redis服务至关重要。在使用过程中,还需要不断关注Redis的更新,...

    redis-3.2.9安装包

    Redis是一款高性能的键值对数据库,常用于缓存、消息队列等场景。这里我们讨论的是Redis的3.2.9版本的安装包。...无论是作为开发人员还是系统管理员,掌握Redis的使用和管理都是提升工作效率的重要手段。

    IDEA免费的REDIS插件

    总结来说,IDEA的免费Redis插件为Java开发者提供了集成化的Redis管理工具,提升了开发效率。它们通常包含键值查看、搜索、命令执行等多种功能,是Redis开发者的得力助手。通过合理利用这些插件,开发者可以在IDEA这...

    Redis下载以及Redis可视化工具下载

    总结:Redis的下载和安装过程相对简单,通过配置文件可以定制其行为。同时,使用可视化工具能帮助我们更好地管理和理解Redis中的数据,提升工作效率。无论是开发、测试还是运维,了解并掌握这些工具都能为工作带来...

    redis常用文档(自己总结的)

    根据提供的文件信息,本文将对Redis的常用命令进行详细的解读与总结。Redis是一种高性能的键值存储系统,常被用于数据库、缓存以及消息中间件等场景。下面将按照文件中提到的不同方面来展开讲解: ### Redis服务...

    redis 管理工具 RedisDesktopManager

    总结来说,RedisDesktopManager作为一款强大的Redis管理工具,提供了全面的功能,包括连接管理、数据操作、数据导入导出、状态监控和脚本执行等,是开发和运维人员在工作中不可或缺的辅助工具。通过熟练使用Redis...

    redis -ppt笔记

    通过阅读这个 PDF,你可以深入理解 Redis 的工作原理和应用场景,这对于学习和掌握 Redis 非常有帮助。 **总结** Redis 是一个强大且灵活的键值数据库,它的内存存储、丰富的数据结构和高并发特性使其在现代 Web ...

    redis3.0.1加RedisDesktopManager

    总结来说,Redis 3.0.1 版本是 Redis 的一个关键更新,它在性能、功能和易用性方面都有显著提升,为开发者提供了更好的体验。配合 RedisDesktopManager,用户可以更直观地管理和操作 Redis 数据库,进一步提升了开发...

    redismanage下载redis客户端 redis连接工具

    Redis是一款高性能的键值数据库,常用于数据缓存、消息队列等场景。RedisManage是专为管理Redis设计的一款客户端工具,它提供...理解并熟练掌握这两个工具,将有助于我们在实际工作中更好地利用Redis来处理和存储数据。

    redis studio下载 redis可视化工具

    Redis Studio 是一个专为 Redis ...无论你是初学者还是经验丰富的开发者,它都能帮助你更好地管理和优化 Redis 实例,提升工作效率。如果你正在寻找一个易用且功能全面的 Redis 工具,Redis Studio 绝对值得尝试。

    redis win32andwin64

    **Redis 概述** Redis(Remote Dictionary Server)是一款开源、高性能、无模式的键值对存储系统,由意大利开发者 Salvatore ...无论是开发人员还是运维人员,了解并掌握Redis的使用都能极大地提升工作效率。

    redissentinel基于phpredis扩展的redissentinel客户端

    总结来说,这个项目为PHP开发者提供了一个便捷的工具,能够方便地在PHP应用中接入Redis Sentinel,从而实现对Redis集群的高可用性管理。通过使用这个客户端,开发者可以减轻在处理主从切换、故障恢复等问题时的工作...

    redis Windows管理工具

    通过`redis-windows-master`提供的工具和第三方应用,如Redis Desktop Manager,可以有效地在Windows环境下进行Redis的日常维护工作。了解并熟练掌握这些工具和技巧,对于Windows环境下的Redis应用开发和运维至关...

    redis深度历险,redis佳作。

    在深入学习部分,作者会剖析Redis的内部工作原理,包括数据结构实现、命令执行流程、网络模型等,这对于优化Redis的使用和性能调优至关重要。读者可以通过这部分内容理解Redis为何能实现毫秒级的响应时间,并学习...

    redis开机启动脚本

    总结起来,Redis开机启动脚本是确保服务器启动时自动运行Redis服务的关键。它涉及到Linux服务管理机制,如init或systemd,以及对Redis配置文件的理解。正确配置并启用开机启动脚本,可以确保Redis在服务器重启后仍能...

Global site tag (gtag.js) - Google Analytics