redis开启redis_Redis的真实世界

在本系列之前,我已经讨论过NoSQL的概念,并介绍了各种与Java平台兼容的NoSQL数据存储,包括Google的Bigtable和Amazon的SimpleDB。 我还讨论了更常规的基于服务器的数据存储,例如MongoDB和CouchDB。 每个数据存储都有优点和缺点,尤其是应用于特定域方案时。

本月的Java开发2.0焦点是Redis,这是一种轻量级的键值数据存储。 大多数NoSQL实现本质上都是键值,但是Redis支持异常丰富的值集,包括字符串,列表,集和哈希。 因此,Redis通常被标记为数据结构服务器 。 Redis还以极快的速度而闻名,这使其成为特定类别用例的最佳选择。

在尝试了解新事物时,将其与您已经熟悉的事物进行比较可能会有所帮助,因此我们将通过考虑Redis与memcached的相似性来开始对Redis的探索。 然后,我将演示Redis的关键功能,这些功能可以使它在某些应用程序场景中优于内存缓存。 最后,我将向您展示如何将Redis用作模型对象的传统数据存储。

Redis和Memcached

Memcached是一种众所周知的内存中对象缓存系统,其工作原理是将目标键和值放入内存缓存中。 因此,Memcached避免了读取访问磁盘时发生的I / O开销。 将Memcached粘贴在Web应用程序和数据库之间可以提高读取性能。 因此,对于需要快速查找数据的应用程序,Memcached是一个不错的选择。 一个例子是股票查询服务,否则它将在数据库中查找相当静态的数据,例如股票代号或价格信息。

但是memcached有一些限制,包括其所有值都是简单字符串的事实。 Redis作为Memcached的替代品,支持更丰富的功能集。 一些基准测试还表明,Redis比memcached快得多。 与Memcached相比,Redis丰富的数据类型使在内存中存储更复杂的数据成为可能。 与memcached不同,Redis可以保留其数据。

Redis是一个很棒的缓存解决方案,但是其丰富的功能集导致了其他用途。 由于Redis能够在磁盘上存储数据并在节点之间复制数据,因此可以将其用作传统数据模型的数据存储库(也就是说,可以像使用RDBMS一样使用Redis)。 Redis也经常被用作排队系统。 在此用例中,Redis是支持工作队列的持久存储的基础,该队列利用Redis的列表类型。 GitHub是以这种方式使用Redis的大规模基础架构的一个示例。

获取Redis并开始!

为了开始使用Redis,您必须具有访问权限,可以通过本地安装或托管提供程序进行访问。 如果您使用的是Mac,安装过程再简单不过了。 如果您使用Windows®,则需要安装Cygwin 。 无论您如何访问Redis,您都可以按照本文后面的示例进行操作。 不过,我应该指出,使用托管Redis提供程序进行缓存可能不是一个很好的缓存解决方案,因为网络延迟可能会抵消任何性能提升。

您通过命令与Redis进行交互,这意味着没有类似SQL的查询语言。 使用Redis非常类似于使用传统的map数据结构-每个东西都有一个键和一个值,每个值都有一组与之关联的丰富数据类型。 每种数据类型也都有其自己的命令集。 例如,如果您计划使用简单的数据类型(例如某种缓存方案),则可以使用setget命令。

您可以通过命令行外壳与Redis实例进行交互。 还有多种客户端实现可通过编程方式与Redis一起使用。 清单1显示了使用基本命令的简单命令行shell交互:

清单1.使用基本的Redis命令
redis 127.0.0.1:6379> set page registration
OK
redis 127.0.0.1:6379> keys *
1) "foo"
2) "page"
redis 127.0.0.1:6379> get page
"registration"

在这里,我通过set命令将键“ page”与值“ registration”相关联。 接下来,我发出了keys命令(结尾的*表示我想查看所有可用的实例键)。 keys命令显示有一个page键和一个foo键-我可以通过get命令检索与键关联的值。 请记住,从get检索的值只能是一个字符串。 例如,如果键的值是列表,则必须使用特定于列表的命令来检索列表的元素。 (请注意,有一些命令可以查询值的类型。)

与Jedis的Java集成

对于希望将Redis集成到Java应用程序中的程序员,Redis团队推荐一个名为Jedis的项目。 Jedis是一个轻量级的库,它将本地Redis命令映射到简单的Java方法。 例如,Jedis让我获取和设置简单的值,如清单2所示:

清单2. Java代码中的基本Redis命令
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
Jedis jedis = pool.getResource();

jedis.set("foo", "bar");
String foobar = jedis.get("foo");
assert foobar.equals("bar");

pool.returnResource(jedis);
pool.destroy();

在清单2中,我配置了一个连接池并获取一个连接(就像在典型的JDBC场景中一样),然后在清单的底部返回该连接。 在连接池逻辑之间,我使用键"foo"设置值"bar" ,该键通过get命令检索。

与memcached类似,Redis允许您将到期时间与值相关联。 因此,我可以设置一个值(例如股票的临时交易价格),该值最终将从Redis缓存中清除。 如果要在Jedis中设置到期时间,请在发出set调用后通过将其与到期时间相关联来进行set ,如清单3所示:

清单3. Redis值可以设置为过期
jedis.set("gone", "daddy, gone");
jedis.expire("gone", 10);
String there = jedis.get("gone");
assert there.equals("daddy, gone");

Thread.sleep(4500);

String notThere = jedis.get("gone");
assert notThere == null;

在清单3中,我使用了expire调用来将“ gone”的值设置为在10秒内到期。 调用Thread.sleep后, get null将返回null

Redis中的数据类型

使用Redis数据类型(例如列表和哈希)需要专门的命令用法。 例如,我可以通过将值附加到键来创建列表。 在清单4中的代码,我发出rpush命令,它附加一个值到一个列表的右侧或尾部 。 (相应的lpush命令将值添加到列表的lpush 。)

清单4. Redis列表
jedis.rpush("people", "Mary");
assert jedis.lindex("people", 0).equals("Mary");

jedis.rpush("people", "Mark");

assert jedis.llen("people") == 2;
assert jedis.lindex("people", 1).equals("Mark");

Redis支持多种用于处理数据类型的命令。 此外,每种数据类型都有其自己的命令集。 我将向您展示其中一些在实际的应用程序开发场景中的工作,而不是逐一介绍它们。

Redis作为缓存解决方案

我已经提到过,Redis很容易用作缓存解决方案,而且碰巧我需要其中之一! 在此应用程序示例中,我将把Redis与我的基于位置的移动Web服务(称为Magnus)集成。

如果您还没有遵循这个系列,那么我首先使用Play框架实现了Magnus,从那时起,我已经在各种实现中开发或重构了它。 Magnus是一项简单的服务,可通过HTTP PUT请求获取JSON文档。 这些文档描述了特定帐户的位置,这意味着一个人持有移动设备。

现在,我想将缓存集成到Magnus中-也就是说,我想通过将不经常更改的数据存储在内存中来以查找的形式减少I / O流量。

马格努斯高速缓存!

清单5中的第一步是通过get调用来确定Redis中是否有传入的帐户名(这是键)。 调用get将返回帐户ID作为值或返回null 。 如果返回值, acctId其用作我的acctId变量。 如果返回null (表示该帐户的名称不是Redis中的键),那么我将在MongoDB中查找该帐户的值,并通过set命令将其添加到Redis中。

这样做的好处是速度:下次请求的帐户提交位置时,我将能够从Redis获得其ID(用作内存中的缓存),而不必去MongoDB并产生读取I / O费用。

清单5.使用Redis作为内存中的缓存
"/location/:account" {
  put {
    def jacksonMapper = new ObjectMapper()
    def json = jacksonMapper.readValue(request.contentText, Map.class)
    def formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm")
    def dt = formatter.parse(json['timestamp'])
    def res = [:]
    
    try{

      def jedis = pool.getResource()	
      def acctId = jedis.get(request.parameters['account'])

      if(!acctId){
        def acct = Account.findByName(request.parameters['account'])
        jedis.set(request.parameters['account'], acct.id.toString())
        acctId = acct.id
      }

      pool.returnResource(jedis)
      new Location(acctId.toString(), dt, json['latitude'].doubleValue(), 
      json['longitude'].doubleValue() ).save()
      res['status'] = 'success'
    }catch(exp){
      res['status'] = "error ${exp.message}"
    }
   response.json = jacksonMapper.writeValueAsString(res)
  }
}

请注意,清单5中的aMagnus实现(用Groovy编写)仍然使用NoSQL实现来存储数据模型。 它只是将Redis用作查询数据的缓存实现。 由于我的主要帐户数据位于MongoDB中(实际上,它位于MongoHQ.com中),而我的Redis数据存储在本地运行,因此Magnus在查找后续帐户ID时将大大提高速度。

可是等等! 为什么我同时需要MongoDB 和 Redis? 我不能只使用一个吗?

适用于ORM的Node.js

许多项目为Redis提供了类似ORM的映射,包括一个很有影响力的基于Ruby的替代方案,称为Ohm。 我签出了该项目的基于Java的派生类(称为JOhm),但最终决定使用为Node编写的变体。 Ohm及其衍生项目的优点在于,它们允许您将对象模型映射到基于Redis的数据结构中。 因此,您的模型对象在读取情况下既持久又(在大多数情况下)非常快。

使用Nohm,我可以用JavaScript快速重写我的Magnus应用程序,并可以快速保存Location对象。 在清单6中,我定义了一个Location模型,其中包含三个属性。 (请注意,我通过使timestamp为字符串而不是真实时间戳记来简化示例)。

清单6. Node.js中的Redis ORM
var Location = nohm.model('Location', {
	properties: {
	    latitude: {
	      type: 'float',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
	    },
		longitude: {
	      type: 'float',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
	    },
		timestamp: {
	      type: 'string',
	      unique: false,
	      validations: [
	        ['notEmpty']
	      ]
        }
     }
});

Node的Express框架使使用我的新Nohm Location对象变得非常容易。 在我的应用程序的PUT实现中,我抓取传入的JSON值,并通过Nohm的p调用将它们放入Location的实例中。 然后,我检查实例是否有效。 如果是这样,我坚持下去。

清单7.在Node的Express.js中使用Nohm
app.put('/', function(req, res) {
  res.contentType('json');
	
  var location = new Location;
  location.p("timestamp", req.body.timestamp);
  location.p("latitude", req.body.latitude);
  location.p("longitude", req.body.longitude);
  
  if(location.valid()){	
  	location.save(function (err) {
	  	if (!err) {
		    res.send(JSON.stringify({ status: "success" }));
		  } else {		
		   res.send(JSON.stringify({ status: location.errors }));
		  }
	  });
  }else{
   res.send(JSON.stringify({ status: location.errors }));
  }
});

如清单7所示,Redis很容易升级为内存中速度非常快的数据存储。 在某些情况下,它甚至可能比memcached更好。

结论

Redis可用于各种数据存储方案,并且由于它可以将数据持久保存到磁盘(并且因为它支持丰富的数据集),因此有时是内存缓存的有力竞争者。 在对您的域有意义的情况下,可以将Redis用作数据模型和队列的后备存储。 Redis客户端实现已被移植到几乎每种编程语言中。

Redis并不是RDMBS的完全替代品,也不是重量级的存储,它具有像MongoDB这样的查询功能。 但是,在许多情况下,它可以与这些技术并存。 正如我在本文中所展示的,对于需要大量数据查找或可以通过Redis快速的原子操作完成实时统计的应用程序,Redis可以是一个很好的独立数据存储解决方案。


翻译自: https://www.ibm.com/developerworks/java/library/j-javadev2-22/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值