高频写入redis场景优化

智码沐星使
• 阅读 244

前言

工作中经常遇到要对redis进行高频写入,但是对于读取时数据的实时性要求又不高的场景。为了优化性能,决定采用本地缓存一部分数据整合后写入。

依赖

<dependency>

<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0-rc2</version>

</dependency>

基础类

public class BufferCache implements Closeable {

// CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
private Cache localCacheData;
private static int maxItemSize = 1000;
private static String key = "defaultKey";
private static final Object lock = new Object();

public BufferCache(String key, int currencyLevel, int writeExpireTime,
                   int accessExpireTime, int initialCapacity, int maximumSize,
                   int maxItemSize, RemovalListener removalListener) {
    currencyLevel = currencyLevel < 1 ? 1 : currencyLevel;
    initialCapacity = initialCapacity < 100 ? 100 : initialCapacity;
    if (key!=null&&key.isEmpty()) {
        BufferCache.key = key;
    }

    BufferCache.maxItemSize = maxItemSize;

    localCacheData = CacheBuilder.newBuilder()
            // 设置并发级别为8,并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(currencyLevel)
            // 设置写缓存后expireTime秒钟过期
            .expireAfterWrite(writeExpireTime, TimeUnit.SECONDS)
            // 设置请求后expireTime秒钟过期
            .expireAfterAccess(accessExpireTime, TimeUnit.SECONDS)
            // 设置缓存容器的初始容量为10
            .initialCapacity(initialCapacity)
            // 设置缓存最大容量为Integer.MAX_VALUE,超过Integer.MAX_VALUE之后就会按照LRU最近虽少使用算法来移除缓存项
            .maximumSize(maximumSize)
            // 设置要统计缓存的命中率
            .recordStats()
            // 设置缓存的移除通知
            .removalListener(removalListener)
            // build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
            .build();

    Runtime.getRuntime().addShutdownHook(
            new Thread(() -> localCacheData.invalidate(key)));
}

public void addListSync(String key, Object value) {
    synchronized (lock) {
        List<Object> gs = (List<Object>) localCacheData.getIfPresent(key);
        if (gs == null) {
            gs = new ArrayList<>();
        }
        gs.add(value);
        localCacheData.put(key, gs);

        // 如果队列长度超过设定最大长度则清除key
        if (gs.size() > maxItemSize) {
            localCacheData.invalidate(key);
        }
    }
}

public void addListSync(Object value) {
    addListSync(BufferCache.key, value);
}

@Override
public void close() {
    localCacheData.invalidate(key);
}

}
采用 google 的 cache,利用其监听事件(详见 com.google.common.cache.RemovalCause 类)触发写入redis操作,addListSync方法中使用 synchronized 进行加锁,防止高并发场景下List数据错误。

新建配置文件

cache.key=name
cache.currencyLevel=1
cache.writeExpireTime=900
cache.accessExpireTime=600
cache.initialCapacity=1
cache.maximumSize=1000
cache.maxItemSize=1000
针对不同业务场景可以自定义不同的配置参数
业务实现
@Configuration
@ConditionalOnResource(resources = "bufferCache.properties")
@PropertySource(value = "bufferCache.properties", ignoreResourceNotFound = true)
public class CacheConfig implements ApplicationContextAware {

private ApplicationContext ctx;

@Bean("buffCache")
@ConditionalOnProperty(prefix = "cache", value = "currencyLevel")
public BufferCache guildBuffCache(@Value("${cache.key}") String key,
        @Value("${cache.currencyLevel}") int currencyLevel,
        @Value("${cache.writeExpireTime}") int writeExpireTime,
        @Value("${cache.accessExpireTime}") int accessExpireTime,
        @Value("${cache.initialCapacity}") int initialCapacity,
        @Value("${cache.maximumSize}") int maximumSize,
        @Value("${cache.maxItemSize}") int maxItemSize) {

    // 异步监听
    RemovalListener<String, List<GuildActiveEventEntity>> async = RemovalListeners
            .asynchronous(new MyRemovalListener(),
                    ExecutorServiceUtil.getExecutorServiceByType(
                            ExecutorServiceUtil.ExecutorServiceType.BACKGROUND));
    return new BufferCache(key, currencyLevel, writeExpireTime,
            accessExpireTime, initialCapacity, maximumSize, maxItemSize,
            async);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException {
    ctx = applicationContext;
}

// 创建一个监听器
private class MyRemovalListener
        implements RemovalListener<String, List<GuildActiveEventEntity>> {
    @Override
    public void onRemoval(
            RemovalNotification<String, List<GuildActiveEventEntity>> notification) {
        RemovalCause cause = notification.getCause();

        // 当超出缓存队列限制大小时或者key过期或者主动清除key时更新数据
        if (cause.equals(RemovalCause.SIZE)
                || cause.equals(RemovalCause.EXPIRED)
                || cause.equals(RemovalCause.EXPLICIT)) {
            //根据不同业务场景调用不同业务方法进行写入操作
        }

    }
}

}

此类实现 ApplicationContextAware 为了获取指定业务方法 Bean ,进行解析缓存中value模型后进行存储。
在以上几个步骤都完成后,只需在业务层声名
@Autowired
private BufferCache buffCache;
调用其addListSync方法即可。

总结

总体思路是使用本地缓存去分担高频写的压力,此方法其实不仅仅适用与redis的写入,还可用于其他场景,具体使用方法可以按照业务场景自己扩展。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Redis和MySQL数据一致中出现的几种情况
1\.MySQL持久化数据,Redis只读数据redis在启动之后,从数据库加载数据。读请求:不要求强一致性的读请求,走redis,要求强一致性的直接从mysql读取写请求:数据首先都写到数据库,之后更新redis(先写redis再写mysql,如果写入失败事务回滚会造成redis中存在脏数据)2.MySQL和Redis
Wesley13 Wesley13
3年前
Mysql 批量写入数据 性能优化
测试环境配置直接影响执行速度,先上一下测试机配置:cpui75500U(低电压伤不起,以后再也不买低电压的U了)内存8Gddr31600php7.1mysql5.5.40开发框架CodeIgniter3.1.2影响写入效率的因素都有什么?
Wesley13 Wesley13
3年前
Java CopyOnWrite容器
   CopyOnWrite简称COW(写时复制),是一种程序设计中的优化策略,读取时,直接读取,写入时,copy一个副本,在这个副本上进行写入,写入完成,用副本替换原数据,这是一种延时懒惰策略。   从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,CopyOnWriteArrayList和CopyOnW
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
3年前
Nginx + lua +[memcached,redis]
精品案例1、Nginxluamemcached,redis实现网站灰度发布2、分库分表/基于Leaf组件实现的全球唯一ID(非UUID)3、Redis独立数据监控,实现订单超时操作/MQ死信操作SelectPollEpollReactor模型4、分布式任务调试Quartz应用
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Redis 单机模式,主从模式,哨兵模式(sentinel),集群模式(cluster),第三方模式优缺点分析
Redis的几种常见使用方式包括:单机模式主从模式哨兵模式(sentinel)集群模式(cluster)第三方模式单机模式Redis单副本,采用单个Redis节点部署架构,没有备用节点实时同步数据,不提供数据持久化和备份策略,适用于数据可靠性要求不高的纯缓存业务场景。优点:
Wesley13 Wesley13
3年前
MongoDB与MySQL关于写确认的异同
!(https://pic3.zhimg.com/80/v2af9f6637b50b09be60b00a42f3812d5e_1440w.jpg)云妹导读:所谓写确认,是指用户将数据写入数据库之后,数据库告知用户写入成功的一个概念。根据数据库的特点和配置,可以在不同的写入程度上,返回给用户,而这其中,就涉及到了不同的性能、数据
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
智码沐星使
智码沐星使
Lv1
若是你一贫如洗我能不能是你的最后行李
文章
2
粉丝
0
获赞
0