百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

redis官方推荐:SpringBoot用这个,一键多线程

mhr18 2024-11-18 14:41 21 浏览 0 评论

Lettuce是一个可伸缩的线程安全的Redis客户端,提供了同步,异步和响应式使用方式。 如果多线程避免阻塞和事务操作(如BLPOP和MULTI / EXEC),则多个线程可共享一个连接。 Lettuce使用通信使用netty。 支持先进的Redis功能,如Sentinel,群集,管道传输,自动重新连接和Redis数据模型。

下面分享来自网易后端工程师的Lettuce的使用心得~

自己整理的Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我【资料】发给你~一起学习进步!

Lettuce在Spring boot中的配置

 @Bean(name="clusterRedisURI")
    RedisURI clusterRedisURI(){
        return RedisURI.builder().withHost("xxx").withPort(6954).build();
    }

    @Bean
    ClusterClientOptions clusterClientOptions(){
        return ClusterClientOptions.builder().autoReconnect(true).maxRedirects(1024).build();
    }

    @Bean
    RedisClusterClient redisClusterClient(ClientResources clientResources, ClusterClientOptions clusterClientOptions, RedisURI clusterRedisURI){
        RedisClusterClient redisClusterClient= RedisClusterClient.create(clientResources,clusterRedisURI);
        redisClusterClient.setOptions(clusterClientOptions);
        return redisClusterClient;
    }

 @Bean(destroyMethod = "close")
    StatefulRedisClusterConnection statefulRedisClusterConnection(RedisClusterClient redisClusterClient){
        return redisClusterClient.connect();
    }


基本的使用方式

 @Bean(name="clusterRedisURI")
    RedisURI clusterRedisURI(){
        return RedisURI.builder().withHost("xxx").withPort(6954).build();
    }

    @Bean
    ClusterClientOptions clusterClientOptions(){
        return ClusterClientOptions.builder().autoReconnect(true).maxRedirects(1024).build();
    }

    @Bean
    RedisClusterClient redisClusterClient(ClientResources clientResources, ClusterClientOptions clusterClientOptions, RedisURI clusterRedisURI){
        RedisClusterClient redisClusterClient= RedisClusterClient.create(clientResources,clusterRedisURI);
        redisClusterClient.setOptions(clusterClientOptions);
        return redisClusterClient;
    }

 @Bean(destroyMethod = "close")
    StatefulRedisClusterConnection statefulRedisClusterConnection(RedisClusterClient redisClusterClient){
        return redisClusterClient.connect();
    }


集群模式

 @Bean(name="clusterRedisURI")
    RedisURI clusterRedisURI(){
        return RedisURI.builder().withHost("xxx").withPort(6954).build();
    }

    @Bean
    ClusterClientOptions clusterClientOptions(){
        return ClusterClientOptions.builder().autoReconnect(true).maxRedirects(1024).build();
    }

    @Bean
    RedisClusterClient redisClusterClient(ClientResources clientResources, ClusterClientOptions clusterClientOptions, RedisURI clusterRedisURI){
        RedisClusterClient redisClusterClient= RedisClusterClient.create(clientResources,clusterRedisURI);
        redisClusterClient.setOptions(clusterClientOptions);
        return redisClusterClient;
    }

 @Bean(destroyMethod = "close")
    StatefulRedisClusterConnection statefulRedisClusterConnection(RedisClusterClient redisClusterClient){
        return redisClusterClient.connect();
    }


客户端订阅事件

客户端使用事件总线传输运行期间产生的事件;EventBus可以从客户端资源进行配置和获取,并用于客户端和自定义事件。  

如下事件可以被客户端发送:

  • 连接事件
  • 测量事件 (Lettuce命令延迟测量(CommandLatency))
  • 集群拓扑事件

订阅所有事件,并将事件输出到控制台

client.getResources().eventBus().get().subscribe(e -> {
            System.out.println("client 订阅事件: " + e);
        });

输出到内容有:

client 订阅事件: ConnectionActivatedEvent [/xx:49910 -> /xx:6008]
client 订阅事件: ConnectionActivatedEvent [/xx:49911 -> /xx:6018]
client 订阅事件: ConnectedEvent [/xx:49912 -> /xx:6018]


发布事件

用户除了可以通过事件总线订阅事件外还可以通过事件总线发布自定义事件

eventBus.publish(new Event() {
           @Override
           public String toString() {
               return "自定义事件";
           }
       });

订阅到到内容如下:

client 订阅事件: 自定义事件


读写分离

lettuce master/slave模式支持读写分离,下面看看具体使用方式,只需要指定ReadFrom就可以了

@Bean(destroyMethod = "close")
    StatefulRedisMasterSlaveConnection statefulRedisMasterSlaveConnection(RedisClient redisClient, RedisURI redisURI) {
        StatefulRedisMasterSlaveConnection connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisURI);
        connection.setReadFrom(ReadFrom.NEAREST);
        return connection;
    }
}

ReadFrom可选参数以及含义:

参数含义

MASTER

从master节点读取

SLAVE

从slave节点读取

MASTER_PREFERRED

从master节点读取,如果master节点不可以则从slave节点读取

SLAVE_PREFERRED

从slave节点读取,如果slave节点不可用则倒退到master节点读取

NEAREST

从最近到节点读取


下面看看源码是如何实现读写分离的,

//根据意图获取连接
   public StatefulRedisConnection getConnection(Intent intent) {
 
       if (debugEnabled) {
           logger.debug("getConnection(" + intent + ")");
       }
       //如果readFrom不为null且是READ
       if (readFrom != null && intent == Intent.READ) {
           //根据readFrom配置从已知节点中选择可用节点描述
           List selection = readFrom.select(new ReadFrom.Nodes() {
               @Override
               public List getNodes() {
                   return knownNodes;
               }
 
               @Override
               public Iterator iterator() {
                   return knownNodes.iterator();
               }
           });
           //如果可选择节点集合为空则抛出异常
           if (selection.isEmpty()) {
               throw new RedisException(String.format("Cannot determine a node to read (Known nodes: %s) with setting %s",
                       knownNodes, readFrom));
           }
           try {
               //遍历所有可用节点
               for (RedisNodeDescription redisNodeDescription : selection) {
                   //获取节点连接
                   StatefulRedisConnection readerCandidate = getConnection(redisNodeDescription);
                   //如果节点连接不是打开到连接则继续查找下一个连接
                   if (!readerCandidate.isOpen()) {
                       continue;
                   }
                   //返回可用连接
                   return readerCandidate;
               }
               //如果没有找到可用连接,默认返回第一个
               return getConnection(selection.get(0));
           } catch (RuntimeException e) {
               throw new RedisException(e);
           }
       }
       //如果没有配置readFrom或者不是READ 则返回master连接
       return getConnection(getMaster());
   }


自定义负载均衡

通过上文的读写分离实现代码可以发现,只需要readFrom select方法每次返回的list都是随机无序的就可以实现随机的负载均衡

public class Sharded< C extends StatefulRedisConnection,V> {
 
    private TreeMap nodes;
    private final Hashing algo = Hashing.MURMUR_HASH;
    private final Map resources = new LinkedHashMap<>();
    private RedisClient redisClient;
    private String password;
    private Set sentinels;
    private RedisCodec codec;
 
    public Sharded(List masters, RedisClient redisClient, String password, Set sentinels, RedisCodec codec) {
        this.redisClient = redisClient;
        this.password = password;
        this.sentinels = sentinels;
        this.codec = codec;
        initialize(masters);
    }
 
    private void initialize(List masters) {
        nodes = new TreeMap<>();
 
        for (int i = 0; i != masters.size(); ++i) {
            final String master = masters.get(i);
            for (int n = 0; n < 160; n++) {
                nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), master);
            }
            RedisURI.Builder builder = RedisURI.builder();
            for (HostAndPort hostAndPort : sentinels) {
                builder.withSentinel(hostAndPort.getHostText(), hostAndPort.getPort());
            }
 
            RedisURI redisURI = builder.withPassword(password).withSentinelMasterId(master).build();
            resources.put(master, MasterSlave.connect(redisClient, codec, redisURI));
        }
 
    }
 
    public StatefulRedisConnection getConnectionBy(String key) {
        return resources.get(getShardInfo(SafeEncoder.encode(key)));
    }
 
    public Collection getAllConnection(){
        return Collections.unmodifiableCollection(resources.values());
    }
 
    public String getShardInfo(byte[] key) {
        SortedMap tail = nodes.tailMap(algo.hash(key));
        if (tail.isEmpty()) {
            return nodes.get(nodes.firstKey());
        }
        return tail.get(tail.firstKey());
    }
 
 
    public void close(){
       for(StatefulRedisConnection connection:  getAllConnection()){
            connection.close();
        }
    }
 
    private static  class SafeEncoder {
 
         static byte[] encode(final String str) {
            try {
                if (str == null) {
                    throw new IllegalArgumentException("value sent to redis cannot be null");
                }
                return str.getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }
    }
    private interface Hashing {
        Hashing MURMUR_HASH = new MurmurHash();
 
        long hash(String key);
 
        long hash(byte[] key);
    }
 
 
    private static  class MurmurHash implements Hashing {
 
         static long hash64A(byte[] data, int seed) {
            return hash64A(ByteBuffer.wrap(data), seed);
        }
 
 
         static long hash64A(ByteBuffer buf, int seed) {
            ByteOrder byteOrder = buf.order();
            buf.order(ByteOrder.LITTLE_ENDIAN);
 
            long m = 0xc6a4a7935bd1e995L;
            int r = 47;
 
            long h = seed ^ (buf.remaining() * m);
 
            long k;
            while (buf.remaining() >= 8) {
                k = buf.getLong();
 
                k *= m;
                k ^= k >>> r;
                k *= m;
 
                h ^= k;
                h *= m;
            }
 
            if (buf.remaining() > 0) {
                ByteBuffer finish = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
                // for big-endian version, do this first:
                // finish.position(8-buf.remaining());
                finish.put(buf).rewind();
                h ^= finish.getLong();
                h *= m;
            }
 
            h ^= h >>> r;
            h *= m;
            h ^= h >>> r;
 
            buf.order(byteOrder);
            return h;
        }
 
        public long hash(byte[] key) {
            return hash64A(key, 0x1234ABCD);
        }
 
        public long hash(String key) {
            return hash(SafeEncoder.encode(key));
        }
    }
 
 
 
 
}



来源:网易工程师--张伟

有任何问题欢迎留言交流~


整理总结不易,如果觉得这篇文章有意思的话,欢迎转发、收藏,给我一些鼓励~

有想看的内容或者建议,敬请留言!

最近利用空余时间整理了一些精选Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我发给你~一起学习进步!有任何问题也欢迎交流~

Java日记本,每日存档超实用的技术干货学习笔记,每天陪你前进一点点~

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: