在这里对jedis关于事务、管道和分布式的调用方式做一个简单的介绍和对比:
一、普通同步方式
最简单和基础的调用方式,
@Test public void test1Normal() { Jedis jedis = new Jedis("localhost"); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = jedis.set("n" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
很简单吧,每次set
之后都可以返回结果,标记是否成功。
二、事务方式(Transactions)
redis的事务很简单,他主要目的是保障,一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。
看下面例子:
@Test public void test2Trans() { Jedis jedis = new Jedis("localhost"); long start = System.currentTimeMillis(); Transaction tx = jedis.multi(); for (int i = 0; i < 100000; i++) { tx.set("t" + i, "t" + i); } List<Object> results = tx.exec(); long end = System.currentTimeMillis(); System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
我们调用jedis.watch(…)
方法来监控key,如果调用后key值发生变化,则整个事务会执行失败。另外,事务中某个操作失败,并不会回滚其他操作。这一点需要注意。还有,我们可以使用discard()
方法来取消事务。
三、管道(Pipelining)
有时,我们需要采用异步方式,一次发送多个指令,不同步等待其返回结果。这样可以取得非常好的执行效率。这就是管道,调用方法如下:
@Test public void test3Pipelined() { Jedis jedis = new Jedis("localhost"); Pipeline pipeline = jedis.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("p" + i, "p" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
四、管道中调用事务
就Jedis提供的方法而言,是可以做到在管道中使用事务,其代码如下:
@Test public void test4combPipelineTrans() { jedis = new Jedis("localhost"); long start = System.currentTimeMillis(); Pipeline pipeline = jedis.pipelined(); pipeline.multi(); for (int i = 0; i < 100000; i++) { pipeline.set("" + i, "" + i); } pipeline.exec(); List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
但是经测试(见本文后续部分),发现其效率和单独使用事务差不多,甚至还略微差点。
五、分布式直连同步调用
@Test public void test5shardNormal() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedis sharding = new ShardedJedis(shards); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = sharding.set("sn" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds"); sharding.disconnect(); }
这个是分布式直接连接,并且是同步调用,每步执行都返回执行结果。类似地,还有异步管道调用。
六、分布式直连异步调用
@Test public void test6shardpipelined() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedis sharding = new ShardedJedis(shards); ShardedJedisPipeline pipeline = sharding.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sp" + i, "p" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds"); sharding.disconnect(); }
七、分布式连接池同步调用
如果,你的分布式调用代码是运行在线程中,那么上面两个直连调用方式就不合适了,因为直连方式是非线程安全的,这个时候,你就必须选择连接池调用。
@Test public void test7shardSimplePool() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards); ShardedJedis one = pool.getResource(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = one.set("spn" + i, "n" + i); } long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds"); pool.destroy(); }
上面是同步方式,当然还有异步方式。
八、分布式连接池异步调用
@Test public void test8shardPipelinedPool() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards); ShardedJedis one = pool.getResource(); ShardedJedisPipeline pipeline = one.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sppn" + i, "n" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds"); pool.destroy(); }
九、需要注意的地方
事务和管道都是异步模式。在事务和管道中不能同步查询结果。比如下面两个调用,都是不允许的:
Transaction tx = jedis.multi(); for (int i = 0; i < 100000; i++) { tx.set("t" + i, "t" + i); } System.out.println(tx.get("t1000").get()); //不允许 List<Object> results = tx.exec(); … … Pipeline pipeline = jedis.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("p" + i, "p" + i); } System.out.println(pipeline.get("p1000").get()); //不允许 List<Object> results = pipeline.syncAndReturnAll();
事务和管道都是异步的,个人感觉,在管道中再进行事务调用,没有必要,不如直接进行事务模式。
分布式中,连接池的性能比直连的性能略好(见后续测试部分)。
分布式调用中不支持事务。
因为事务是在服务器端实现,而在分布式中,每批次的调用对象都可能访问不同的机器,所以,没法进行事务。
十、测试
运行上面的代码,进行测试,其结果如下:
Simple SET: 5.227 seconds Transaction SET: 0.5 seconds Pipelined SET: 0.353 seconds Pipelined transaction: 0.509 seconds Simple@Sharing SET: 5.289 seconds Pipelined@Sharing SET: 0.348 seconds Simple@Pool SET: 5.039 seconds Pipelined@Pool SET: 0.401 seconds
另外,经测试分布式中用到的机器越多,调用会越慢。上面是2片,下面是5片:
Simple@Sharing SET: 5.494 seconds Pipelined@Sharing SET: 0.51 seconds Simple@Pool SET: 5.223 seconds Pipelined@Pool SET: 0.518 seconds
下面是10片:
Simple@Sharing SET: 5.9 seconds Pipelined@Sharing SET: 0.794 seconds Simple@Pool SET: 5.624 seconds Pipelined@Pool SET: 0.762 seconds
下面是100片:
Simple@Sharing SET: 14.055 seconds Pipelined@Sharing SET: 8.185 seconds Simple@Pool SET: 13.29 seconds Pipelined@Pool SET: 7.767 seconds
分布式中,连接池方式调用不但线程安全外,根据上面的测试数据,也可以看出连接池比直连的效率更好。
十一、完整的测试代码
package com.example.nosqlclient; import java.util.Arrays; import java.util.List; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisShardInfo; import redis.clients.jedis.Pipeline; import redis.clients.jedis.ShardedJedis; import redis.clients.jedis.ShardedJedisPipeline; import redis.clients.jedis.ShardedJedisPool; import redis.clients.jedis.Transaction; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestJedis { private static Jedis jedis; private static ShardedJedis sharding; private static ShardedJedisPool pool; @BeforeClass public static void setUpBeforeClass() throws Exception { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6379)); //使用相同的ip:port,仅作测试 jedis = new Jedis("localhost"); sharding = new ShardedJedis(shards); pool = new ShardedJedisPool(new JedisPoolConfig(), shards); } @AfterClass public static void tearDownAfterClass() throws Exception { jedis.disconnect(); sharding.disconnect(); pool.destroy(); } @Test public void test1Normal() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = jedis.set("n" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test2Trans() { long start = System.currentTimeMillis(); Transaction tx = jedis.multi(); for (int i = 0; i < 100000; i++) { tx.set("t" + i, "t" + i); } //System.out.println(tx.get("t1000").get()); List<Object> results = tx.exec(); long end = System.currentTimeMillis(); System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test3Pipelined() { Pipeline pipeline = jedis.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("p" + i, "p" + i); } //System.out.println(pipeline.get("p1000").get()); List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test4combPipelineTrans() { long start = System.currentTimeMillis(); Pipeline pipeline = jedis.pipelined(); pipeline.multi(); for (int i = 0; i < 100000; i++) { pipeline.set("" + i, "" + i); } pipeline.exec(); List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds"); } @Test public void test5shardNormal() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = sharding.set("sn" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test6shardpipelined() { ShardedJedisPipeline pipeline = sharding.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sp" + i, "p" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test7shardSimplePool() { ShardedJedis one = pool.getResource(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = one.set("spn" + i, "n" + i); } long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test8shardPipelinedPool() { ShardedJedis one = pool.getResource(); ShardedJedisPipeline pipeline = one.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sppn" + i, "n" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds"); } }
摘要: Chili ProjectOmnigroupPaymoSpringloopsComindworkWebprojectorSoloOpen AtriumCaseboxRedboothAgilezenProducteevRedmineApollohqCentral DesktopRationalplanTeamworkTeamlabFastdueFreedcampHuddleIssue BurnerN...
阅读全文
摘要: 用途及用法 网络请求通常有两种形式:第一种,请求不是很频繁,而且每次连接后会保持相当一段时间来读数据或者写数据,最后断开,如文件下载,网络流媒体等。另一种形式是请求频繁,但是连接上以后读/写很少量的数据就断开连接。考虑到服务的并发问题,如果每个请求来到以后服务都为它启动一个线程,那么这对服务的资源可能会造成很大的浪费,特别是第二种情况。因为通常情...
阅读全文
回想起来,我也算是国内接触推荐系统较早的人之一了,最近和人聊天,觉得不少人对推荐系统有所误解,以为需要多么高大上的算法才能搭建起来的,我只想说我经常说的那句话【不是这样的】,所以有了这篇文章。
第一次接触【推荐系统】是在两年前在某高校的互联网信息处理实验室的时候,那时候,【机器学习】和【大数据】都是新概念,但是差不多半年后,【大数据】的概念就开始风靡全球了,到现在已经被爆炒得面目全非。
那年还因此买了一本项亮的书《推荐系统实践》,那本书和现在的很多热门书籍一样,都是跟着概念热起来的。 虽然有一些作者自己的实战经验在里面,但是总体上来说并没有太多值得重复翻开的地方。
几乎所有宣扬【推荐系统】的人,都要拿【啤酒和尿布】,【亚马逊推荐占营收20%】之类的经典例子来说力证推荐系统的牛逼之处。到处宣扬【推荐系统】插上【机器学习】等算法的翅膀,就能让电子商务变得精准无比,能智能的猜出用户想买的东西。
殊不知,其实这两个例子和所谓的【算法】其实关系不大。
1. 啤酒和尿布 首先是【啤酒和尿布】,超市的人员发现买啤酒的男人容易顺手买尿布。这其实是一种数据分析,是根据数据统计加上人工分析得出,是一种以经验来改善销售的行为。和【机器学习】【数据挖掘】等算法的关系不大。 刚接触【推荐系统】的时候,【协同过滤算法】大热, 我也曾经迷恋得研究过该算法,以为发现了什么宝贝一样。但是实际上,在工程中【协同过滤】出来的效果往往惨不忍睹,所谓的【算法工程师】每天能做的就是在那调整【协同过滤】算法的相关参数,然后看看第二天的点击率有没有上升。然后调整到最后你会发现,牛逼哄哄的【协同过滤】其实还不如简简单单的【看了又看】效果来的好,虽然协同过滤算法本质上也是一种【看了又看】的思想。
2. 亚马逊的推荐系统 亚马逊的推荐系统占了营收比,我记得是20%,不知道现在上升了还是下降了。这个说辞会让很多人误以为只要你搞好了推荐系统,你的营收就能上升20%以上一样。其实不然,对于亚马逊来说,为什么推荐能起到这么高的销量,一个很重要的原因在于,【亚马逊的首页点击率高的部分位置划分给了推荐系统的】,从广告学上讲,广告位置的好坏极大的决定了广告的销量。这个很容易理解,假设你的产品的广告牌能挂上天安门城楼的话,你觉得你还需要担心该产品的销量吗?
当然不可否定的是亚马逊的推荐系统应该是很牛逼的,但是这并不说明他们采用的【推荐算法】非常牛逼。推荐系统我认为其实和搜索系统并无太大差异,我一直认为推荐系统其实只是一个个性化的搜索引擎。之前在【秘密】上很火的有个爆料是:“360搜索的Rank刚开始就是用【机器学习】的算法去做,屎一样的效果,是我把百度的基于规则的算法偷过去之后才变好的。” ,这个爆料出来不少人讽刺【基于规则】,觉得这是在黑百度的算法。 其实不是这样的,记得当时阿里搜索挖了一个谷歌搜索的员工,该人在阿里分享的时候就说过:【谷歌的搜索效果比别人好的原因就是规则库牛逼,关于算法使用的都是成熟的人尽皆知的算法,并没什么新奇酷的算法】。 可能也是这个原因,谷歌研究院的科学家几乎全是【工程师背景】出身的。还记得上次【CCF推荐系统前言讲座】,刚开始叫了几个学院派的讲师在那大讲特讲各种酷炫掉渣天的算法,然后淘宝的大数据负责人车品觉 上台之后直接来了句【我们实验出各种算法效果不太好,还不如最基本的 关联规则效果来的好】直接把前面的学院派专家们打脸打得都肿了。
我心目中的推荐系统 不管是电商,或者是新闻,都有【个性化推荐】和【热门推荐】的取舍。一个商品热门或者点击量高是有其原因的。所以将热门的东西推荐给用户是非常合情合理的,因为既然热门,也侧面说明了很大概率上该用户也会喜欢该商品。而【个性化推荐】本质上是为了解决【长尾】问题,把那些不热门的东西,但是很可能符合某特定用户品味的商品【挖掘】出来,推荐给特定的用户群。
首先,在推荐中,醒目的推荐位应该是【热门推荐】或者【人工推荐】,【人工推荐】是指比如在体育新闻中,巴萨夺冠之类的大新闻是直接让编辑来【人工推荐】即可,就是此新闻一出,马上登上头条,而不是在那磨磨唧唧的计算特征值,计算相似度,计算是否符合用户兴趣。 对于推荐中的【冷启动】,最理想的推荐就是【相关推荐】。说到这里,整个推荐系统的 80% 已经搭建完毕,【热门推荐+人工推荐+相关推荐】,这三者都是【个性化】都没什么关系,也算法关系也不大,但是这三者效果的好坏就决定了整个系统推荐效果好坏的 80% 。好多人连最基本的这三者都没有做好,就开始想一步登天,很可惜,这样的捷径是不存在的。 接下来是 20% 的【个性化】的做法,如上所说,个性化是为了解决【长尾】问题,正是因为长尾占商品的 20% ,所以在此我认为【个性化】其实也只有 20% 。要解决个性化,首先就是要对用户分析,最成熟的办法就是对用户打标签(是否让你想起来社交网络为什么经常让你选用合适的标签描述自己,没错,就是为了分析你)。
其实,给用户打标签,逼格更高的说法叫【用户特征提取】或者【用户行为分析】。说到这两个词,那些所谓的算法工程师可能就会开始扯什么高大上的算法,机器学习,自然语言处理,数据挖掘等各种算法。其实在我看来,算法很大情况根本派不上用场,我认为这方面的关键在于【数据统计 + 人工分析】。将用户的浏览记录等记录下来,统计他最常点击的东西,最常去的频道,然后给他打上这些频道或者商品的标签。或者收集更详细的信息,比如年龄,打上【青少年,男人,女人,老人】等标签,根据这些标签进行推荐。比如当推荐护肤的商品时,就可以偏向于女人,推荐运动产品时,就可以偏向于男人和青少年,推荐保健品时,就可以偏向于老年人。所以,光看年龄这个标签的维度,就可以做很多文章。所以标签库的设计和积累,是非常广泛和重要的,而这方面需要大量依赖于【人工分析】,而不是看论文调算法能做到的。 就好比现在的中文分词,拼到最后大家都在比词库的积累,谁的词库好,谁的效果就好,【搜狗】的【拼音输入法】效果好也是因为词库比别人好。
最后就是根据标签的定向推荐,这个推荐概率是有【权重设置】在里面,就比如刚才对年龄这个维度的权重,是需要给予对应的权重值,如何给定呢?其实就是【拍脑袋】,当然,如果有某些公司已经得出经验值了直接可以拿来用就会更好。但是在拍完脑袋之后需要做的就是观察点击率变化,查Bad Case,然后再对权重进行调整,也就是根据评测和反馈来调整,没有【评测和反馈】,整个系统等于是一个黑盒,谈何优化?在我看来,【推荐系统】本质上首先是一个系统,需要不断的对各种效果进行【评测】,查各种【Bad Case】,而这些都不是看论文可以学到的东西。
总结 1、实力派的【算法工程师】往往都是ABC[always be coding],这样的算法工程师才能根据实际问题建立模型或者建立规则库,是真正能解决问题的人。往往是一些有研究背景,经验丰富的研究员,更加重视工程,因为工程架构上一些恰当合理的设计,效果往往就能远远高过于模型算法优化。
2、学院派的【算法工程师】往往是为了算法而算法,而不是为了解决推荐系统的问题去找最适合算法。这也是为什么大公司经常招了一些博士毕业的算法工程师后,不是研究算法而是让他们整天在那看数据报表?【因为发现算法没啥好研究,只能让他们在那看看报表找找规律了。】
3、【几乎所有所谓的智能推荐算法都是花拳绣腿】
4、当一个做推荐系统的部门开始重视【数据清理,数据标柱,效果评测,数据统计,数据分析】这些所谓的脏活累活,这样的推荐系统才会有救。
策略模式:
场景:又称警察模式,假设小明开快车,遇到警察,可能是好警察,只是口头警告一下,就让小明走了,也可能是强硬的警察,给小明开了罚单。但小明是不知道到底会遇到哪种警察,要到RUNTIME的时候才知道。
不好的封装:将好警察的处罚行为封装为一个类A,将强硬警察的处罚行为封装为另一个类B,将判断如何处罚封装成一个类C,在这个类中判断类的类型,如果是A类,则执行A方法,如果是B类,则执行B方法。
良好的封装:将警察的处罚行为统一为一个接口I-A的一个方法,类C的执行方法只传入接口I-A。
传统MySQL+ Memcached架构遇到的问题
实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题:
MySQL需要不断进行拆库拆表,Memcached也需不断跟着扩容,扩容和维护工作占据大量开发时间。
Memcached与MySQL数据库数据一致性问题。
Memcached数据命中率低或down机,大量访问直接穿透到DB,MySQL无法支撑。
跨机房cache同步问题。
众多NoSQL百花齐放,如何选择
最近几年,业界不断涌现出很多各种各样的NoSQL产品,那么如何才能正确地使用好这些产品,最大化地发挥其长处,是我们需要深入研究和思考的问 题,实际归根结底最重要的是了解这些产品的定位,并且了解到每款产品的tradeoffs,在实际应用中做到扬长避短,总体上这些NoSQL主要用于解决 以下几种问题
少量数据存储,高速读写访问。此类产品通过数据全部in-momery 的方式来保证高速访问,同时提供数据落地的功能,实际这正是Redis最主要的适用场景。
海量数据存储,分布式系统支持,数据一致性保证,方便的集群节点添加/删除。
这方面最具代表性的是dynamo和bigtable 2篇论文所阐述的思路。前者是一个完全无中心的设计,节点之间通过gossip方式传递集群信息,数据保证最终一致性,后者是一个中心化的方案设计,通过 类似一个分布式锁服务来保证强一致性,数据写入先写内存和redo log,然后定期compat归并到磁盘上,将随机写优化为顺序写,提高写入性能。
Schema free,auto-sharding等。比如目前常见的一些文档数据库都是支持schema-free的,直接存储json格式数据,并且支持auto-sharding等功能,比如mongodb。
面对这些不同类型的NoSQL产品,我们需要根据我们的业务场景选择最合适的产品。
Redis适用场景,如何正确的使用
前面已经分析过,Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed 的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用 Memcached,何时使用Redis呢?
Redis与Memcached的比较
网络IO模型
Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接 描述字pipe 传递给worker线程,进行读写IO, 网络层使用libevent封装的事件库,多线程模型可以发挥多核作用,但是引入了cache coherency和锁的问题,比如,Memcached最常用的stats 命令,实际Memcached所有操作都要对这个全局变量加锁,进行计数等工作,带来了性能损耗。
(Memcached网络IO模型)
Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和 select,对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大,但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操 作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞住的。
内存管理方面
Memcached使用预分配的内存池的方式,使用slab和大小不同的chunk来管理内存,Item根据大小选择合适的chunk存 储,内存池的方式可以省去申请/释放内存的开销,并且能减小内存碎片产生,但这种方式也会带来一定程度上的空间浪费,并且在内存仍然有很大空间时,新的数 据也可能会被剔除,原因可以参考Timyang的文章:http://timyang.net/data/Memcached-lru-evictions/
Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎 片,Redis跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致 swap也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上Redis更适合作为存储而不是cache。
数据一致性问题
Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题。 Redis没有提供cas 命令,并不能保证这点,不过Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断。
存储方式及其它方面
Memcached基本只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能
Redis除key/value之外,还支持list,set,sorted set,hash等众多数据结构,提供了KEYS
进行枚举操作,但不能在线上使用,如果需要枚举线上数据,Redis提供了工具可以直接扫描其dump文件,枚举出所有数据,Redis还同时提供了持久化和复制等功能。
关于不同语言的客户端支持
在不同语言的客户端方面,Memcached和Redis都有丰富的第三方客户端可供选择,不过因为Memcached发展的时间更久一 些,目前看在客户端支持方面,Memcached的很多客户端更加成熟稳定,而Redis由于其协议本身就比Memcached复杂,加上作者不断增加新 的功能等,对应第三方客户端跟进速度可能会赶不上,有时可能需要自己在第三方客户端基础上做些修改才能更好的使用。
根据以上比较不难看出,当我们不希望数据被踢出,或者需要除key/value之外的更多数据类型时,或者需要落地功能时,使用Redis比使用Memcached更合适。
关于Redis的一些周边功能
Redis除了作为存储之外还提供了一些其它方面的功能,比如聚合计算、pubsub、scripting等,对于此类功能需要了解其实现原理,清 楚地了解到它的局限性后,才能正确的使用,比如pubsub功能,这个实际是没有任何持久化支持的,消费方连接闪断或重连之间过来的消息是会全部丢失的, 又比如聚合计算和scripting等功能受Redis单线程模型所限,是不可能达到很高的吞吐量的,需要谨慎使用。
总的来说Redis作者是一位非常勤奋的开发者,可以经常看到作者在尝试着各种不同的新鲜想法和思路,针对这些方面的功能就要求我们需要深入了解后再使用。
总结:
Redis使用最佳方式是全部数据in-memory。
Redis更多场景是作为Memcached的替代者来使用。
当需要除key/value之外的更多数据类型支持时,使用Redis更合适。
当存储的数据不能被剔除时,使用Redis更合适。
后续关于Redis文章计划:
Redis数据类型与容量规划。
如何根据业务场景搭建稳定,可靠,可扩展的Redis集群。
Redis参数,代码优化及二次开发基础实践。
关于作者
田琪,目前负责新浪微博平台底层架构与研发工作,之前曾担任搜狐白社会实时游戏平台核心架构工作,主要关注webgame, 分布式存储,nosql 和 erlang 等领域,目前主要从事mysql源代码的一些深入研究工作,浪微博:http://weibo.com/bachmozart。
摘要: 简单来说,观察者模式=发布者+订阅者。下面是一个有关猎头的典型的例子。在下面这张图当中有两个角色:猎头和寻找工作的人。找工作的人向猎头订阅,告知自己希望得到一份工作,当有新的工作机会的时候,猎头就会把这个信息通知给曾经向他订阅过的人。Java代码Subject接口:1public interface Subject {2 publi...
阅读全文