`
QING____
  • 浏览: 2245911 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Redis编程实践【pipeline和事务】

 
阅读更多

    Redis或许已经在很多企业开始推广并试水,本文也根据个人的实践,简单描述一下Redis在实际开发过程中的使用(部署与架构,稍后介绍),程序执行环境为java + jedis,关于spring下如何集成redis-api,稍后介绍吧。

 

前言:下载redis-2.6.2,安装好redis之后,请在redis.conf文件中,将如下3个配置属性开启(仅供测试使用):

 

##客户端链接的端口,也是server端侦听client链接的端口
##每个client实例,都将和server在此端口上建立tcp长链接
port 6379
## server端绑定的ip地址,如果一个物理机器有多个网络接口时,可以明确指定为某个网口的ip地址
bind 127.0.0.1
##链接中io操作空闲时间,如果在指定时间内,没有IO操作,链接将会被关闭
##此属性和TCP链接中的timeout选项一样,建议设置为0,很多时候,我们一个应用也只会有一个redis实例
##不过,如果你使用连接池的话,你需要对此参数做额外的考虑。
timeout 0

 

1. Pipeline:“管道”,和很多设计模式中的“管道”具有同样的概念,pipleline的操作,将明确client与server端的交互,都是“单向的”:你可以将多个command,依次发给server,但在此期间,你将无法获得单个command的响应数据,此后你可以关闭“请求”,然后依次获取每个command的响应结果。

    从简单来说,在IO操作层面,对于client而言,就是一次批量的连续的“write”请求,然后是批量的连续的“read”操作。其实对于底层Socket-IO而言,对于client而言,只不过是多次write,然后一次read的操作;对于server端,input通道上read到数据之后,就会立即被实施,也会和非pipeline一样在output通道上输出执行的结果,只不过此时数据会被阻塞在网络缓冲区上,直到client端开始read或者关闭链接。神秘的面纱已被解开,或许你也能创造一个pipeline的实现。

    非pipleline模式:

    Request---->执行

    ---->Response

    Request---->执行

    ---->Response

    Pipeline模式下:

    Request---->执行,Server将响应结果队列化

    Request---->执行,Server将响应结果队列化

    ---->Response

    ---->Response

    Client端根据Redis的数据协议,将响应结果进行解析,并将结果做类似于“队列化”的操作。

 
public void pipeline(){
		String key = "pipeline-test";
		String old = jedis.get(key);
		if(old != null){
			System.out.println("Key:" + key + ",old value:" + old);
		}
		//代码模式1,这种模式是最常见的方式
		Pipeline p1 = jedis.pipelined();
		p1.incr(key);
		System.out.println("Request incr");
		p1.incr(key);
		System.out.println("Request incr");
		//结束pipeline,并开始从相应中获得数据
		List<Object> responses = p1.syncAndReturnAll();
		if(responses == null || responses.isEmpty()){
			throw new RuntimeException("Pipeline error: no response...");
		}
		for(Object resp : responses){
			System.out.println("Response:" + resp.toString());//注意,此处resp的类型为Long
		}
		//代码模式2
		Pipeline p2 = jedis.pipelined();
		Response<Long> r1 = p2.incr(key);
		try{
			r1.get();
		}catch(Exception e){
			System.out.println("Error,you cant get() before sync,because IO of response hasn't begin..");
		}
		Response<Long> r2 = p2.incr(key);
		p2.sync();
		System.out.println("Pipeline,mode 2,--->" + r1.get());
		System.out.println("Pipeline,mode 2,--->" + r2.get());
		
	}
     不过需要明确一下,pipeline和“事务”是两个完全不同的概念,pipeline只是表达“交互”中操作的传递的方向性,pipeline也可以在事务中运行,也可以不在。无论如何,pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的相应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义;但是如果pipeline的操作被封装在事务中,那么将有事务来确保操作的成功与失败(事实上,Redis的事务,仍然不像严格意义上的事务,稍后介绍)。
	public void txPipeline(){
		String key = "pipeline-test";
		String old = jedis.get(key);
		if(old != null){
			System.out.println("Key:" + key + ",old value:" + old);
		}
		Pipeline p1 = jedis.pipelined();
		p1.multi();//开启事务
		p1.incr(key);
		System.out.println("Request incr");
		p1.incr(key);
		System.out.println("Request incr");
		Response<List<Object>> txresult= p1.exec();//提交事务
		p1.sync();//关闭pipeline
		//结束pipeline,并开始从相应中获得数据
		List<Object> responses = txresult.get();
		if(responses == null || responses.isEmpty()){
			throw new RuntimeException("Pipeline error: no response...");
		}
		for(Object resp : responses){
			System.out.println("Response:" + resp.toString());//注意,此处resp的类型为Long
		}
	}
     Pipeline在某些场景下非常有用,比如有多个command需要被“及时的”提交,而且他们对相应结果没有互相依赖,而且对结果响应也无需立即获得,那么pipeline就可以充当这种“批处理”的工具;而且在一定程度上,可以较大的提升性能,性能提升的原因主要是TCP链接中较少了“交互往返”的时间。

    不过在编码时请注意,pipeline期间将“独占”链接,此期间将不能进行非“管道”类型的其他操作,直到pipeline关闭;比如在上述代码中间,使用jedis.set(key,value)等操作都将抛出异常。

    如果你的pipeline的指令集很庞大,为了不干扰链接中的其他操作,你可以为pipeline操作新建Client链接,让pipeline和其他正常操作分离在2个client中。不过pipeline事实上所能容忍的操作个数,和socket-output缓冲区大小/返回结果的数据尺寸都有很大的关系;同时也意味着每个redis-server同时所能支撑的pipeline链接的个数,也是有限的,这将受限于server的物理内存或网络接口的缓冲能力。

 

    使用场景举例:因为业务需要,我们需要把用户的操作过程记录在日志中以方便以后的统计,每隔3个小时生成一个新的日志文件,那么后台处理线程,将会扫描日志文件并将每条日志输出为“operation”:1,即表示操作次数为1;如果每个operation都发送一个command,事实上性能是很差的,而且是没有必要的;那么我们就可以使用pipeline批量提交即可。

 

2.Transaction(事务):

    Redis提供了简单的“事务”能力,MULTI,EXEC,DISCARD,WATCH/UNWATCH指令用来操作事务。

    1) MUTIL:开启事务,此后所有的操作将会添加到当前链接的事务“操作队列”中。

    2) EXEC:提交事务

    3) DISCARD:取消事务,记住,此指令不是严格意义上的“事务回滚”,只是表达了“事务操作被取消”的语义,将会导致事务的操作队列中的操作不会被执行,且事务关闭。

    4) WATCH/UNWATCH:“观察”,这个操作也可以说是Redis的特殊功能,但是也可说是Redis不能提供“绝对意义上”的事务能力而增加的一个“补充特性”(比如事务隔离,多事务中操作冲突解决等);在事务开启前,可以对某个KEY注册“WATCH”,如果在事务提交后,将会首先检测“WATCH”列表中的KEY集合是否被其他客户端修改,如果任意一个KEY 被修改,都将会导致事务直接被“DISCARD”;即使事务中没有操作某个WATCH KEY,如果此KEY被外部修改,仍然会导致事务取消。事务执行成功或者被DISCARD,都将会导致WATCH KEY被“UNWATCH”,因此事务之后,你需要重新WATCH。WATCH需要在事务开启之前执行。

    WATCH所注册的KEY,事实上无论是被其他Client修改还是当前Client修改,如果不重新WATCH,都将无法在事务中正确执行。WATCH指令本身就是为事务而生,你或许不会在其他场景下使用WATCH;例如:

 

		String key = "transaction-key";
		jedis.set(key, "20");
		jedis.watch(key);//注册key,此后key将会被监控,如果在事务执行前被修改,则导致事务被DISCARD。
		jedis.incr(key);//此key被修改,即使是自己,也会导致watch在事务中执行失效
		jedis.unwatch();//取消注册
		jedis.watch(key);//重新注册,在重新注册前,必须unwatch
		Transaction tx = jedis.multi();//开启事务
                ....

     Redis中,如果一个事务被提交,那么事务中的所有操作将会被顺序执行,且在事务执行期间,其他client的操作将会被阻塞;Redis采取了这种简单而“粗鲁”的方式来确保事务的执行更加的快速和更少的外部干扰因素。

    EXEC指令将会触发事务中所有的操作被写入AOF文件(如果开启了AOF),然后开始在内存中实施这些数据变更操作;Redis将会尽力确保事务中所有的操作都能够执行,如果redis环境故障,有可能导致事务未能成功执行,那么需要在redis重启后增加额外的校验工作。

   如果在EXEC指令被提交之前,Redis-server即检测到提交的某个指令存在语法错误,那么此事务将会被提前标记为DISCARD,此后事务提交也将直接被驳回;但是如果在EXEC提交后,在实施数据变更时(Redis将不会预检测数据类型,比如你对一个“非数字”类型的key执行INCR操作),某个操作导致了ERROR,那么redis仍然不会回滚此前已经执行成功的操作,而且也不会中断ERROR之后的其他操作继续执行。对于开发者而言,你务必关注事务执行后返回的结果(结果将是一个集合,按照操作提交的顺序排列,对于执行失败的操作,结果将是一个ERROR)。

    Redis的事务之所以如此设计,它为了确保本身的性能,同时不引入“关系型数据库”的设计复杂度;你不能完全希望Redis能为你交付完美的事务操作,只能说,你选择了错误的工具。

 

public void transaction(){
		String key = "transaction-key";
		jedis.set(key, "20");
		jedis.watch(key);
		Transaction tx = jedis.multi();
		tx.incr(key);
		tx.incr(key);
		tx.incr(key);
		List<Object> result = tx.exec();
		if(result == null || result.isEmpty()){
			System.out.println("Transaction error...");//可能是watch-key被外部修改,或者是数据操作被驳回
			return;
		}
		for(Object rt : result){
			System.out.println(rt.toString());
		}
	}

 

关于这两个主题,暂且到此,如有错误,请不吝赐教。谢谢。

   
分享到:
评论
6 楼 di1984HIT 2017-01-31  
xyexueke ~~~~~~  
5 楼 balanar_cx 2014-11-25  
受教,感谢
4 楼 QING____ 2014-05-05  
rxin2009 写道
QING____ 写道

请参看redis指令:watch/unwatch:http://redis.io/commands/watch
对于你说的这种set值的情况无需使用cas或者事物,你原本可以对key首先设定一个默认值(比如false),此后合适的时机修改成true..而不能期望redis的事务锁能够良好的解决cas问题.


'合适的时机',也就是不能保证原子性了(判断、赋值)?


redis还提供了一个指令:setnx,貌似是,如果key不存在,则执行set操作.你查看一下此方式是否可以满足你的需要.

redis通常用来解决:高速缓存和复杂数据结构的存储问题;我们不能让它做太多"关系型数据库"的事情.
3 楼 rxin2009 2014-05-05  
QING____ 写道

请参看redis指令:watch/unwatch:http://redis.io/commands/watch
对于你说的这种set值的情况无需使用cas或者事物,你原本可以对key首先设定一个默认值(比如false),此后合适的时机修改成true..而不能期望redis的事务锁能够良好的解决cas问题.


'合适的时机',也就是不能保证原子性了(判断、赋值)?
2 楼 QING____ 2014-05-05  
rxin2009 写道
你好,请问下,关于redis的事务细节你是从哪里知道的?
我想请教你一个问题,如何通过redis的事务来实现一个cas操作,比如在set一个key的时候,判断不存在的时候set,否在不set


请参看redis指令:watch/unwatch:http://redis.io/commands/watch
对于你说的这种set值的情况无需使用cas或者事物,你原本可以对key首先设定一个默认值(比如false),此后合适的时机修改成true..而不能期望redis的事务锁能够良好的解决cas问题.
1 楼 rxin2009 2014-04-30  
你好,请问下,关于redis的事务细节你是从哪里知道的?
我想请教你一个问题,如何通过redis的事务来实现一个cas操作,比如在set一个key的时候,判断不存在的时候set,否在不set

相关推荐

    redis2-nginx-module-0.15

    Redis2-NGINX-Module 是由 OpenResty 团队开发的,OpenResty 是一个基于 NGINX 的高性能 Web 和反向代理服务器,它包含了大量的 LuaJIT 脚本支持,能够进行动态编程。通过这个模块,开发者可以在 NGINX 配置中直接...

    Redis帮助文档

    例如,它会讲解如何连接Redis服务器、设置和获取键值、执行事务、使用发布/订阅功能等。此外,还会介绍pipeline技术,通过批量发送命令来提高性能,以及如何利用Redis的持久化机制保障数据安全。 其次,"2.redis_...

    StackExchange.Redis-1.2.6

    - **多模式操作**:支持单个命令、管道(pipeline)和事务(transaction)模式,允许一次性执行多个命令,提高效率。 - **异步编程支持**:提供异步API,适应.NET平台的异步编程模型,便于构建高并发的应用。 - *...

    Redis6视频课程配套资料.zip

    笔记.zip包含了学习Redis6.2.1过程中整理的详细笔记,这些笔记涵盖了Redis的基本概念、数据类型(如字符串、哈希、列表、集合、有序集合)、持久化机制(RDB和AOF)、事务、复制、Sentinel哨兵系统、Cluster集群以及...

    redis学习笔记。

    这只是 Redis 学习的基础,深入使用还需要掌握更多的高级特性和最佳实践,例如集群搭建、主从复制、Lua 脚本、Pipeline 使用等。随着对 Redis 的了解加深,你可以将其应用到更复杂的系统设计中,优化性能和提高系统...

    php5.4 redis扩展

    在PHP编程中,Redis是一个非常流行的开源、高性能的键值存储系统,常用于数据库缓存和消息队列。PHP的Redis扩展使得开发者能够直接在PHP脚本中与Redis服务器进行交互,提高了数据处理效率。在PHP 5.4版本中,引入了`...

    Go-Go-Redis是Redis数据库的GoogleGo语言的客户端开发包

    4. **pipeline和事务**:支持pipeline技术,可以批量发送命令,减少网络往返次数,提升性能。同时,也支持Redis事务,保证一系列操作的原子性。 5. **错误处理**:Go-Redis对可能出现的错误进行了封装,提供了友好...

    redis入门指南第二版

    对于复杂操作,如管道(Pipeline)和事务(Transaction),redis库也提供了方便的封装。 此外,Redis的持久化机制包括RDB(快照)和AOF(Append Only File),确保数据安全。RDB定期保存内存快照,AOF记录每次写...

    Redis Go客户端.zip

    3. **Pipeline和Transactions**:Go Redis客户端支持命令管道(Pipeline)和事务(Transaction),可以一次性发送多个命令,提升性能。Pipeline将多个命令打包发送,而Transactions则确保了命令的原子性。 4. **Pub...

    java操作redis工具类

    在Java开发中,Redis作为一个高性能的键值存储系统,常被用作数据缓存,以提高应用程序的响应速度和整体性能。...通过合理的设计和良好的编程实践,这个工具类能帮助开发者更好地管理和利用Redis作为缓存系统。

    redis in action, java+python

    《Redis in Action:Java+Python》是一本深入探讨Redis数据库使用的书籍,结合了两种主流编程语言——Java和Python,提供了丰富的实战案例和源代码。Redis是一个高性能的键值存储系统,广泛应用于缓存、消息队列、...

    redis客户端操作实战-redisDemo.zip

    2. Lettuce:另一个流行的Java Redis客户端,它基于Netty框架,提供了异步和反应式编程模型。Lettuce更适合大型分布式系统。 3. Redisson:除了基本的命令操作,还提供了分布式锁、队列、映射等功能,简化了在分布式...

    Redis实战 高清

    Redis作为一个开源的、基于键值对的数据存储系统,以其高速的读写性能、丰富的数据结构以及支持事务和持久化等特点,在互联网行业中被广泛应用于缓存、消息队列、计数器等多个场景。 Redis实战这本书涵盖了以下几个...

    redis实战相关项目代码,用python编写.zip

    在本项目中,我们主要关注的是使用Python编程语言与Redis数据库进行交互的实战应用。Redis是一个开源、高性能的键值存储系统,常被用于数据库、缓存和消息中间件等场景。通过Python的Redis客户端,我们可以方便地...

    毕业设计-基于Scrapy-redis的分布式爬虫Web平台

    Spring的AOP(面向切面编程)和IOC(控制反转)特性使得代码更易于维护和扩展;SpringMVC提供了一种灵活的处理HTTP请求的方式,可以方便地映射URL到具体的方法;MyBatis允许开发者用SQL语句直接操作数据库,简化了...

    Redis是一个高性能的键值对数据库,支持多种数据结构,如字符串、哈希、列表、集合、有序集合

    ### Redis核心知识点详解 #### 一、Redis简介与特点 Redis是一种开源的内存中的数据结构存储系统,它可以用作数据库、缓存以及...通过实践这些命令和技术,您可以更好地理解Redis的工作原理,并将其应用于实际项目中。

    redistemplate

    综上所述,"redistemplate"可能是一个封装了上述特性的Redis客户端工具,为开发者提供了便利的接口和最佳实践,以简化Redis的使用。通过使用这个模板,开发者可以更专注于业务逻辑,而不是底层通信细节。

    gradle-pride-plugin-0.9.3-2-gc1a9c19.zip

    《Redis Algebra:开源项目解析与应用》 Redis,作为一个高性能的键值存储系统,因其出色的性能和...在实践中,我们应该深入研究该项目的源代码,了解其设计原理和使用方式,以便更好地利用它来优化我们的Redis应用。

    Python库 | rediz-0.13.3-py3-none-any.whl

    Python是一种高级编程语言,以其简洁明了的语法和强大的库支持而闻名。在Python中,开发者可以快速构建各种类型的应用,包括Web应用、科学计算、数据分析等。rediz库就是Python生态系统中的一个组成部分,它专为与...

Global site tag (gtag.js) - Google Analytics