当前位置: 首页 > 编程日记 > 正文

jedis使用_网易架构师心得:Springboot下使用redis踩过的坑

点击?蓝色“ 深入原理”,关注并“设为星标”

技术干货,第一时间推送

首先总结了redis服务端单线程工作模型,redis四种部署方式及使用场景,然后从源码的角度上,分析springboot在jedis和lettuce客户端下使用redis的一些坑~尤其是在集群模式下的一些不兼容问题!

1 Redis服务端单线程模型

200f74df3498099da22724e64abafb6e.png

redis 内部使用文件事件处理(file event handler)处理客户端的请求,文件事件处理器是单线程的,所以redis才叫做单线程的模型。

文件事件处理器的结构包含4个部分:多个socket、IO 多路复用程序、文件事件分派器、事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)。

文件事件处理器采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。

Redis客户端通过socket连接reids服务端,多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

redis 单线程模型也能效率高的原因:

  1. 纯内存操作

  2. 基于非阻塞的 IO 多路复用机制

  3. 单线程反而避免了多线程的频繁上下文切换问题

为什么redis采用单线程模型呢?

如果采用多线程模型,cpu需要进行上下文切换,假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns * 1000 = 1500us,而单线程的读完1MB数据才250us ,所以完全没必要使用多线程。

什么时候适合采用多线程的方案呢?

对于慢速设备:磁盘,网络,SSD 等等,将请求和处理的线程不绑定,请求的线程将请求放在一个buff里,然后等buff快满了,处理的线程再去处理这个buff。然后由这个buff 统一的去写入磁盘,或者读磁盘,这样效率最高。

Redis线程安全吗?

redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。

2 redis部署方式

2.1 单节点模式

单节点模式只有一个节点,一般用来测试

2.2 主从模式

主从模式包括一个主节点和多个从节点,一般来说,主节点用来读写操作,从节点用户读操作,主节点的数据可以同步到从节点,所以从节点即便支持写操作也没有意义。

2.3 哨兵模式

d1ad7a4c2bc487c013990f08e15781f2.png

哨兵模式是基于主从模式的,哨兵模式为了实现主从模式的高可用,监控主从节点的状态,当sentinel发现master节点挂了以后,sentinel就会从slave中重新选举一个master。

一般来说,通过sentinel集群可以管理多个主从redis,sentinel最好不要和Redis部署在同一台机器,不然redis的服务器挂了以后,sentinel也挂了。使用sentinel集群也是为了保证redis的高可用,避免哨兵节点挂了之后影响redis的使用。

当使用sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。

sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中。

2.4 集群模式:

a3b2618e0dfd66cce17b45c556f194a1.png

cluster的出现是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。

cluster可以说是sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。

如图所示,部署了三主三从的redis集群,redis cluster有固定的16384个hash slot,对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot,从而将数据存储至对应的slot上。

3 Springboot使用redis总结

spring-boot-starter-data-redis支持两种redis客户端:jedis和lettuce

3b9e3df2f2f442c5926309278f0e8528.png

Springboot2.0默认使用的客户端是lettuce,下面跟踪源码来分析springboot如何加在lettuce客户端的,首先找到springboot自动加载的jar包下redis相关的加载配置类RedisAutoConfiguration

ea722e074f5d5a3528fefec7db02105a.png

这里采用@Configuration @bean的方式向容器中注入RedisTemplate和StringRedisTemplate,注入两者的方法中需要传入RedisConnectionFactory,RedisConnectionFactory通过@Import导入的LettuceConnectionConfiguration和JedisConnectionConfiguration生成

ff437b22648384bd5b9e40cd0feb2f03.png

5674f8be1f5de108c2d5569984382746.png

可以看到在没有RedisConnectionFactory的情况下,会默认向Spring容器中注入LettuceConnectionFactory,如果要使用jedis客户端,只需要手动配置一个JedisConnectionFactory并注入容器即可。

3.1 jedis和lettuce的区别

  • Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接。

  • Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问, StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

3.2 jedis非线程安全分析:

从源码角度分析jedis客户端执行每个命令的过程

54ff9d2f1252703591feabbf1e10ee9c.png

首先借助于Client类的对应方法去执行命令

f3d238b90801475afb71e4ed7ccb6606.png

然后借助于Connection类的sendCommand方法执行

b1e5bee53adc690db6f4c3a3bf5669b1.png

sendCommand方法每次执行都会调用connect方法

ec872a974673378d9de0aaae39aabb9d.png

从connect方法中可以看到,socket是一个共享变量,假如两个线程公用一个jedis实例,当前还没有建立socket连接,两个线程同时进入建立socket连接

cce972e3f6bd57693ac1e4ffa8473695.png

线程1建立socket连接后,开始获取输入输出流,于此同时,线程2重新初始化socket,并且没有执行到建立socket连接,此时线程1获取输入输出流将失败,因为此时的socket并没有连接。

jedis本身不是多线程安全的,这并不是jedis的bug,而是jedis的设计与redis本身就是单线程相关,jedis实例抽象的是发送命令相关,一个jedis实例使用一个线程与使用100个线程去发送命令没有本质上的区别,所以没必要设置为线程安全的。但是如果需要用多线程方式访问redis服务器怎么做呢?那就使用多个jedis实例,每个线程对应一个jedis实例,而不是一个jedis实例多个线程共享。一个jedis关联一个Client,相当于一个客户端,Client继承了Connection,Connection维护了Socket连接,对于Socket这种昂贵的连接,一般都会做池化,jedis提供了JedisPool。

3.3 集群模式下jedis和lettuce使用的一些坑

1. Lettuce在集群模式下主节点宕机,从节点更新为主节点,lettuce如何更新集群拓扑结构

集群中每个节点只负责部分slot, slot可能从一个节点迁移到另一节点,造成客户端有可能会向错误的节点发起请求。因此需要有一种机制来对其进行发现和修正,这就是请求重定向。

集群拓扑刷新是在ClusterTopologyRefreshScheduler中进行,下面进入类中一探究竟

7b4e3546a7015a6657a3d278c6f7f7c1.png

1db7b7e1f43714e5eaa55691aa67dd00.png

ClusterTopologyRefreshScheduler类实现了ClusterEventListener接口,用来监听redis集群事件,集群事件包括ask重定向,move重定向,以及重新连接等。

67334a79d83a28cefd31e983568d8cef.png

在重定向方法中首先调用isEnabled方法判断是否开启刷新集群拓扑,然后调用indicateTopologyRefreshSignal方法刷新集群拓扑

14a74031de9b8bb6d764bf7f36ca9072.png

判断集群是否开启刷新拓扑结构,依据ClusterTopologyRefreshOptions中自适应刷新的trigger中是否包含指定的重定向trigger,在默认配置下,这个trigger是什么样的呢?

dc0ace880884e08e720e4b605158db84.png

可以看到默认情况下自适应刷新的trigger是空的,所以在集群模式下,使用默认的lettuce配置,如果主节点宕机,是不会刷新集群拓扑的,也就是会导致redis连接失败。

2b8e35fa114eb695e2450ec923b1394a.png

在enableAllAdaptiveRefreshTriggers方法中可以开启自适应刷新集群拓扑。开启自适应刷新集群拓扑后,又是如何刷新的呢?

2c3f73591437fa372a03a2a26b7f938e.png

在indicateTopologyRefreshSignal方法中提交一个刷新集群拓扑的clusterTopologyRefreshTask

87574a73916a6f29b3549e9885d175a6.png

在task中调用RedisClusterClient类的reloadPartitions方法重新加载集群拓扑信息,达到刷新的效果。

除了通过开始自适应刷新集群拓扑之外,还可以通过开启周期刷新的方式刷新集群拓扑

2d0a679f2be5f0f01b0f8b1f7224a14d.png

开启周期刷新集群拓扑后,在初始化集群拓扑的时,会调用activateTopologyRefreshIfNeeded开启周期刷新集群拓扑任务

332ea39eaf0d9fed6070cc50ce641976.png

1f7eed9b7f4988f01c22738a6d9d194e.png

这里会判断是否开启周期刷新,开启后才会提交一个定时任务。

周期刷新和自适应刷新比较:周期刷新和自适应刷新两种方法,最好还是使用自适应刷新,因为周期刷新的周期需要设置,设置太长会导致服务可能一段时间不可用,设置太短对资源是一种浪费,而自适应刷新根据服务端的响应来刷新集群拓扑。

两种刷新方法没必要都开启,都开启对资源也是一种浪费。

2.Jedis客户端执行lua脚本的坑

redis使用lua脚本的好处:

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。

  • 原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。

  • 复用。客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。

那Jedis客户端是如何支持lua脚本的呢?

b1232c6631b4c488726595d43e4167c6.png

Jedis执行lua脚本是通过ScriptExecutor类的execute方法执行的,在方法中进一步调用eval方法

bff9a35010df0d1c596e2da32fa5b322.png

进一步调用RedisScriptingCommands类的eval方法,因为实在集群模式下使用jedis客户端,所以调用JedisClusterScriptingCommands实现类的eval方法

626ee96f9ab2c6cb9a0cf851f57662be.png

再看JedisClusterScriptingCommands实现类的eval方法,居然直接抛出异常,集群模式下不支持脚本。

解决方法是使用lettuce客户端,LettuceScriptingCommands类中的eval方法支持脚本

a8cd7ef9be0bd86c5e2267beba4cc0ed.png

原文:https://juejin.im/post/6871164377236504589?utm_source=gold_browser_extension

-深入原理-  

   知其然并知其所以然    

c4233af64f367cbe27749cd3bad66b55.png

63156b991b5ea9a8af016c4a739c1f8c.png

d78a6f8410e233694b9fa52479617a19.gif

相关文章:

【URAL】1091 Tmutarakan Exams

题意&#xff1a;取k个不同的数&#xff0c;每个数不超过s&#xff0c;问种数。 若kx1,kx2,...,kx3满足条件&#xff0c;则x1,x2,...,x3必然满足条件。 因此枚举素数容斥&#xff0c;2*3*5*7>50&#xff0c;所以枚举之多三层。 1 #include<cstdio>2 #include<cstri…

怎样将无线路由做成无线AP

什么是无线AP&#xff1f; 无线AP&#xff0c;即Access Point&#xff0c;也就是无线接入点。简单来说就是无线网络中的无线交换机&#xff0c;它是移动终端用户进入有线网络的接入点&#xff0c;主要用于家庭宽带、企业内部网络部署等&#xff0c;无线覆盖距离为几十米至上…

Java实现网页截屏功能(基于phantomJs)

公司最近有个需求&#xff1a;把用户第一次的测量身体信息和最近一次测量信息进行对比&#xff0c;并且需要把对比的数据截成图片可以发给用户&#xff08;需要在不打开网页的情况下实时对网页进行截图然后保存到服务器上&#xff0c;返回图片地址&#xff09;&#xff0c;通过…

CPU性能指标

1&#xff0c;主频 主频 时钟频率&#xff0c;它是指CPU内部晶振的频率&#xff0c;常用单位为MHz&#xff0c;它反映了CPU的基本工作节拍; 时钟频率又称主频&#xff0c;它是指CPU内部晶振的频率&#xff0c;常用单位为MHz&#xff0c;它反映了CPU的基本工作节拍; 2&#xff…

canvas 文字颜色_Canvas技术概述

Canvas简介在学习一项新技术之前&#xff0c;先了解这项技术的历史发展及成因会帮助我们更深刻的理解这项技术。历史上&#xff0c;canvas最早是由Apple Inc. 提出的&#xff0c;在Mac OS X webkit中创建控制板组件使用&#xff0c;而在canvas称为HTML草案及标准之前&#xff0…

sql server 2008学习10 存储过程

输入输出参数: 给存储过程传参数,叫做输入参数,用户告诉存储过程需要 利用这个参数干些什么. 输出参数: 从存储过程得到那些数据. 创建一个可选参数的存储过程: create proc pa1 name varchar(50)NULL as if(name is not null)select * from a where name like name%; elsesele…

C#_关于静态类和静态方法(转)

静态类是不能实例化的&#xff0c;即不能new 我们直接使用它的属性与方法&#xff0c;静态类最大的特点就是共享。 静态类中的所有成员必须是静态的。 静态类可以有静态构造函数&#xff0c;静态构造函数不可继承。 静态构造函数可以用于静态类&#xff0c;也可用于非静态类。 …

Struts2和SpringMVC简单配置以及区别总结

Struts2: struts 2 是一个基于MVC(mode-view-con)设计模式的Web应用框架&#xff0c;是由Struts1和WebWork两个经典框架发展而来的。 工作流程&#xff1a; 1客户端浏览器发出HTTP请求 2根据web.xml配置&#xff0c;该请求被FilterDispatcher(过滤器调度员)接收 3根据struts.xm…

python数字类型及运算_Python基础之(基本数据类型及运算)

一、运算 1.1、算数运算1.2、比较运算&#xff1a;1.3、赋值运算&#xff1a;1.4、逻辑运算&#xff1a;1.5、成员运算&#xff1a;针对逻辑运算的进一步研究&#xff1a; 1、在没有()的情况下not 优先级高于 and&#xff0c;and优先级高于or&#xff0c;即优先级关系为( )>…

AJAX跨域访问解决方案

Case I. Web代理的方式 (on Server A) 即用户访问A网站时所产生的对B网站的跨域访问请求均提交到A网站的指定页面&#xff0c;由该页面代替用户页面完成交互&#xff0c;从而返回合适的结果。此方案可以解决现阶段所能够想到的多数跨域访问问题&#xff0c;但要求A网站提供Web…

什么是生成器?

在python中&#xff0c; 要产生一个列表&#xff0c;可以这样写&#xff1a; a[] for i in range(10): a.append(i*2) 但是&#xff0c;这样挺麻烦的&#xff0c;产生一个列表&#xff0c;需要三行语句。所以&#xff0c;有人就想到能不能一行代码来表示呢&#xff1f;其实&a…

一项横断面人群研究中比较放射学阴性的中轴脊柱关节炎患者与强制性脊柱炎患者之间的差别...

原文 译文 Patients with Non-Radiographic Axial Spondyloarthritis Differ From Patients with Ankylosing Spondylitis in Several aspects– Results of a Cross-Sectional Cohort Study Uta Kiltz 1, Xenofon Baraliakos2, Pantelis Karakostas2, Manfred Igelmann…

day12-事务

day12总结[c1] 今日内容 l 事务 l 连接池 事务 事务概述 为了方便演示事务&#xff0c;我们需要创建一个account表&#xff1a; CREATE TABLE account( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(30), balance NUMERIC(10.2) ); INSERT INTO…

ThinkPHP基础概念

OOP 面向对象编程&#xff08;Object Oriented Programming&#xff0c;OOP&#xff0c;面向对象程序设计&#xff09;是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标&#xff1a;重…

008本周总结报告

这周主要做了下PTA的编程题目的练习和学习和了解了java的多线程&#xff0c;了解了进程和线程的定义&#xff0c;区别&#xff0c;联系等&#xff0c;并知道了多线程的利与弊&#xff0c;并了解了JVM下的多运行机制&#xff08;本质是CPU 对应用程序的快速换&#xff09;&#…

python3.8.5是python3吗_Python 升级到3.8.5

mac osx 安装最新版本的3.8.5 将/usr/local/bin目录下的python3.8和pip3.8复制一份并修改为python和pip。 修改python的路径&#xff0c;之后source文件。 输出requirements.txt到桌面 安装新版本的第三方库&#xff0c;我使用的第三方库很多&#xff0c;更新很慢。头大啊。 验…

不看后悔 如何删除WIN7的100M隐藏分区

http://notebook.it168.com/a2010/1101/1120/000001120453_2.shtml

tomcat下面web应用发布路径配置 ( 即虚拟目录配置 )

https://blog.csdn.net/AnQ17/article/details/52122236转载于:https://www.cnblogs.com/gangpao/p/9223504.html

strcpy +memcpy实现循环右移

#include<stdio.h>#include<assert.h>#include<string.h>char *strcpy(char*strDest,const char*strSrc){assert(strDest!NULL&&strSrc!NULL);char * addr strDest;while( *strSrc!\0)*strDest *strSrc;*strDest \0;return addr;}//循环移动steps…

python查看目录下的文件_Python——查看目录下所有的目录和文件

原博文 2019-05-06 19:31 − 写程序我们经常会遇到需要遍历某一个目录下的所有文件这个操作&#xff0c;然而python有现成的库&#xff0c;只需要2个循环就可以搞定。 1 import os 2 3 def all_path(dirname): 4 5 result []#所有的文件 6 7 for ma... 相关推荐 2019-12-10 14…

负载均衡策略深入剖析

在实际应用中&#xff0c;我们可能不想仅仅是把客户端的服务请求平均地分配给内部服务器&#xff0c;而不管服务器是否宕机。而是想使Pentium III服务器比Pentium II能接受更多的服务请求&#xff0c;一台处理服务请求较少的服务器能分配到更多的服务请求&#xff0c;出现故障的…

js 验证数据类型的4中方法

1.typeof 可以检验基本数据类型 但是引用数据类型&#xff08;复杂数据类型&#xff09;无用&#xff1b; 总结 &#xff1a; typeof 无法识别引用数据类型 包括 bull; 2.instanceof是一个二元运算符&#xff0c;左操作数是一个对象&#xff0c;右操作数是一个构造函数。如…

有关 ecshop 属性 {$goods.goods_attr|nl2br} 标签的赋值问题

1、nl2br() 函数在字符串中的每个新行 (\n) 之前插入 HTML 换行符 (<br />)。 2、 如果要向{$goods.goods_attr|nl2br}赋新值&#xff0c;这个值是保存在数据库中的&#xff0c;用户在商品页(goods.php)选择了商品属性(goods.attr)之后&#xff0c;点击"购买"就…

linux cp 强制覆盖_Linux基本操作教程

Linux基本操作教程点击蓝字关注我们01.Linux系统简介Linux&#xff0c;全称GNU/Linux&#xff0c;是一套免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹于1991年第一次释出&#xff0c;它主要受到Minix和Unix思想的启发&#xff0c;是一个基于…

火焰图(Flame Graphs)的安装和基本用法

火焰图&#xff08;Flame Graphs&#xff09; 一、概述&#xff1a; 火焰图&#xff08;flame graph&#xff09;是性能分析的利器&#xff0c;通过它可以快速定位性能瓶颈点。 perf 命令&#xff08;performance 的缩写&#xff09;是 Linux 系统原生提供的性能分析工具&#…

用TCP/IP进行网际互联一

地址解析协议ARP主机知道某个目的主机的IP就可以知道该目的主机的物理地址。改进ARP每个ARP广播分组中都包含有发送方自身的IP和物理地址的绑定&#xff0c;接收方在处理ARP分组时&#xff0c;先在自己的缓存中更新发送方IP到物理地址的绑定信息。ARP是一个隐藏底层网络物理编址…

【learning】矩阵树定理

问题描述 给你一个图&#xff08;有向无向都ok&#xff09;&#xff0c;求这个图的生成树个数    一些概念 度数矩阵&#xff1a;\(a[i][i]degree[i]\)&#xff0c;其他等于\(0\) 入度矩阵&#xff1a;\(a[i][i]in\_degree[i]\)&#xff0c;其他等于\(0\) 出度矩阵&#xff1…

各大知名企业的Research展示

大公司為了要拉開彼此的差距, 除了專注於目前的產品外, 都會為了未來做準備, 而這些研究通常都會做一個 Research 的專區來呈現成果, 如下述列表: Google ResearchYahoo! ResearchThe Facebook ProjectMicrosoft Research - Turning Ideas into Reality微軟亞洲研究院IBM Resea…

解决Eclipse添加新server时无法选择Tomcat7的问题

关闭Eclipse删除WorkSpace目录下/.metadata/.plugins/org.eclipse.core.runtime/.settings目录中的org.eclipse.wst.server.core.prefs和org.eclipse.jst.server.tomcat.core.prefs重启Eclipse转载于:https://www.cnblogs.com/tnsay/p/11466746.html

java 判断object类型_Java学习-方法与多态的学习心得

一 1.什么是方法重写方法的重写或方法的覆盖&#xff08;overriding&#xff09;子类根据需求对从父类继承的方法进行重新编写重写时&#xff0c;可以用super.方法的方式来保留父类的方法构造方法不能被重写 2.方法重写规则(1)方法名相同(2)参数列表相同(3)返回值类型相同或者是…