首页 文章 精选 留言 我的

精选列表

搜索[快速入门],共10000篇文章
优秀的个人博客,低调大师

Linux存储入门:简易数据恢复方案--分区和LVM实战

数据恢复有没有简易方案? IT工程师一般都知道如何操作和使用文件和目录。但是,对于系统如何构建出、抽象出文件和目录,一般就不熟悉了。至于更下层的概念,可能大家知道最多的就是驱动了。所以,为了规避这点,可行的简易方案之一,就是以黑箱方式使用testdisk等工具,在我们在对底层了解不多甚至一无所知的情况下,进行数据恢复(商业工具,恢复效果估计更好,当然商业工具的价格也更好)。但是,对于工程师而言,多数时候,仅仅以黑箱方式依赖某些工具进行数据恢复是不够的。 数据恢复,经常是突发事故响应中关键而又耗时的一步。多数情况下,工程师往往并非专司数据恢复,操作环境往往是生产环境,趁手工具难以部署,执行操作要遵循种种约束,加之业务中断的压力。这种情形下,工程师很可能还需要推定数据恢复的结果/耗时等信息,提供数据供决策者使用。很明显,如果你准备考验一下

优秀的个人博客,低调大师

计算机视觉入门基础——计算机如何‘看’片

更多深度文章,请关注:https://yq.aliyun.com/cloud 计算机视觉是一门研究如何使机器‘看’的科学,我们都喜欢看美丽的图像,但是你有没有想过计算机是如何看这些图像的?接下来,我会详细介绍说明计算机如何处理图像的。 看到上面的图像,一个正常的人可以很容易地知道,图像中有一只猫。但是,计算机可以真正看到猫吗?答案是否定的,计算机看到数字矩阵(0到255之间)。一般来说,我们可以将图像分类为灰度图像或彩色图像。首先,我们先讨论灰度图像然后再讨论彩色。上图是灰度图像,图像中每个像素都表示的是像素的亮度,下面我们就说一说这个亮度问题。了解更多关于像素。让我们先来看看上面图片中计算机是怎么看的。 我已将上面的图像大小调整为18 * 18,以便于理解。与我们不同的是,计算机将图像看作2D矩阵。你可能听说有人说这幅画的大小是1800 * 700或1300 * 700,这个大小显示了一个图像的宽度和高度。换句话说,如果大小为1300 * 700,则水平方向为1300像素,垂直方向为700像素。这意味着总共有910000(1300 * 700)像素。如果图像的大小为700 * 500,那么矩阵的维数将为(700,500)。这里,矩阵中的每个元素(像素)表示该像素中的亮度强度。这里,0表示黑色,255表示白色,数字越小,越接近黑色(数字大小决定黑的程度)。 彩色图像 在灰度图像中,每个像素表示仅一种颜色的强度。换句话说,它有一个通道。而在彩色图像中,我们有3个通道RGB(红,绿,蓝)。标准数码相机都有3(RGB)通道。 如上图所示,彩色图像由红色、绿色和蓝色三个通道组成。现在的问题是,计算机如何看待这个形象?同样,答案是他们看到矩阵。现在下一个问题应该是,我们要如何在矩阵中表示这个图像,因为它有3个通道,与我们只有一个通道的灰度图像不同。在这种情况下,我们利用3D矩阵来实现表示彩色图像。我们有一个通道的矩阵,但在这种情况下,我们将有三个矩阵堆叠在一起,这就是为什么它是3D。700 * 700彩色图像的尺寸将为(700,700,3)。假设第一个矩阵表示红色通道,则该矩阵的每个元素表示该像素中的红色强度,同样为绿色和蓝色。通常,彩色图像中的每个像素具有与其相关联的三个数字(0至255)。这些数字表示该特定像素中的红色、绿色和蓝色的强度。至于为什么是红绿蓝这三色,想必大家都知道色度学的最基本原理,即三基色原理。大多数颜色都可以通过三色按照不同的比例混合产生。 结论 计算机将图像看作矩阵。灰度图像具有一个通道(灰色),因此我们可以在2D矩阵中表示灰度图像,其中每个元素表示该特定像素中亮度的强度。记住,0表示黑色,255表示白色。灰度图像有一个通道,而彩色图像有三个通道RGB(红、绿、蓝)。我们可以在深度为3的3D矩阵中表示彩色图像。 本文由阿里云云栖社区组织翻译。 文章原标题《How do computers see an image ?》,作者:Savan Visalpara 作者个人网站:https://savan77.github.io/,可以与作者交流。 译者:袁虎,审阅:李烽 阿福 文章为简译,更为详细的内容,请查看原文

优秀的个人博客,低调大师

Docker技术入门与实战(第2版)1.1 什么是Docker

初识容器与Docker 如果说主机时代大家比拼的是单个服务器物理性能(如CPU主频和内存),那么在云时代,最为看重的则是凭借虚拟化技术所构建的集群处理能力。 伴随着信息技术的飞速发展,虚拟化技术早已经广泛应用到各种关键场景中。从20世纪60年代IBM推出的大型主机虚拟化,到后来以Xen、KVM为代表的虚拟机虚拟化,再到现在以Docker为代表的容器技术,虚拟化技术自身也在不断进行创新和突破。 传统来看,虚拟化既可以通过硬件模拟来实现,也可以通过操作系统软件来实现。而容器技术则更为优雅,它充分利用了操作系统本身已有的机制和特性,可以实现远超传统虚拟机的轻量级虚拟化。因此,有人甚至把它称为“新一代的虚拟化”技术,并将基于容器打造的云平台亲切地称为“容器云”。 Docker毫无疑问正是众多容器技术中的佼佼者,是容器技术发展过程中耀眼的一抹亮色。

优秀的个人博客,低调大师

Storm入门之第6章一个实际的例子

本文翻译自《Getting Started With Storm》译者:吴京润 编辑:郭蕾 方腾飞 本章要阐述一个典型的网络分析解决方案,而这类问题通常利用Hadoop批处理作为解决方案。与Hadoop不同的是,基于Storm的方案会实时输出结果。 我们的这个例子有三个主要组件(见图6-1) 一个基于Node.js的web应用,用于测试系统 一个Redis服务器,用于持久化数据 一个Storm拓扑,用于分布式实时处理数据 图6-1 架构概览 NOTE:你如果想先把这个例子运行起来,请首先阅读附录C 基于Node.js的web应用 我们已经伪造了简单的电子商务网站。这个网站只有三个页面:一个主页、一个产品页和一个产品统计页面。这个应用基于Express和Socket.io两个框架实现了向浏览器推送内容更新。制作这个应用的目的是为了让你体验Storm集群功能并看到处理结果,但它不是本书的重点,所以我们不会对它的页面做更详细描述。 主页 这个页面提供了全部有效产品的链接。它从Redis服务器获取数据并在页面上把它们显示出来。这个页面的URL是http://localhost:3000/。(见图6-2,译者注,图6-2翻译如下,全是文字就不制图了) 有效产品: DVD播放器(带环绕立体声系统) 全高清蓝光dvd播放器 媒体播放器(带USB 2.0接口) 全高清摄像机 防水高清摄像机 防震防水高清摄像机 反射式摄像机 双核安卓智能手机(带64GB SD卡) 普通移动电话 卫星电话 64GB SD卡 32GB SD卡 16GB SD卡 粉红色智能手机壳 黑色智能手机壳 小山羊皮智能手机壳 图6-2 首页 产品页 产品页用来显示指定产品的相关信息,例如,价格、名称、分类。这个页面的URL是:http://localhost:3000/product/:id。(见图6-3,译者注:全是文字不再制图,翻译如下) 产品页:32英寸液晶电视 分类:电视机 价格:400 相关分类 图6-3,产品页 产品统计页 这个页面显示通过收集用户浏览站点,用Storm集群计算的统计信息。可以显示为如下概要:浏览这个产品的用户,在那些分类下面浏览了n次产品。该页的URL是:http://localhost:3000/product/:id/stats。(见图6-4,译者注:全是文字,不再制图,翻译如下) 浏览了该产品的用户也浏览了以下分类的产品: 1.摄像机 2.播放器 3.手机壳 4.存储卡 图6-4. 产品统计视图 启动这个Node.js web应用 首先启动Redis服务器,然后执行如下命令启动web应用: node webapp/app.js 为了向你演示,这个应用会自动向Redis填充一些产品数据作为样本。 Storm拓扑 为这个系统搭建Storm拓扑的目标是改进产品统计的实时性。产品统计页显示了一个分类计数器列表,用来显示访问了其它同类产品的用户数。这样可以帮助卖家了解他们的用户需求。拓扑接收浏览日志,并更新产品统计结果(见图6-5)。 图6-5 Storm拓扑的输入与输出 我们的Storm拓扑有五个组件:一个spout向拓扑提供数据,四个bolt完成统计任务。 UsersNavigationSpout 从用户浏览数据队列读取数据发送给拓扑 GetCategoryBolt 从Redis服务器读取产品信息,向数据流添加产品分类 UserHistoryBolt 读取用户以前的产品浏览记录,向下一步分发Product:Category键值对,在下一步更新计数器 ProductCategoriesCounterBolt 追踪用户浏览特定分类下的商品次数 NewsNotifierBolt 通知web应用立即更新用户界面 下图展示了拓扑的工作方式(见图6-6) package storm.analytics; ... public class TopologyStarter { public static void main(String[] args) { Logger.getRootLogger().removeAllAppenders(); TopologyBuilder builder = new TopologyBuilder(); builder.setSpout("read-feed", new UsersNavigationSpout(),3); builder.setBolt("get-categ", new GetCategoryBolt(),3) .shuffleGrouping("read-feed"); builder.setBolt("user-history", new UserHistoryBolt(),5) .fieldsGrouping("get-categ", new Fields("user")); builder.setBolt("product-categ-counter", new ProductCategoriesCounterBolt(),5) .fieldsGrouping("user-history", new Fields("product")); builder.setBolt("news-notifier", new NewsNotifierBolt(),5) .shuffleGrouping("product-categ-counter"); Config conf = new Config(); conf.setDebug(true); conf.put("redis-host",REDIS_HOST); conf.put("redis-port",REDIS_PORT); conf.put("webserver", WEBSERVER); LocalCluster cluster = new LocalCluster(); cluster.submitTopology("analytics", conf, builder.createTopology()); } } Figure 6-6 Storm拓扑 UsersNavigationSpout UsersNavigationSpout负责向拓扑提供浏览数据。每条浏览数据都是一个用户浏览过的产品页的引用。它们都被web应用保存在Redis服务器。我们一会儿就要看到更多信息。 你可以使用https://github.com/xetorthio/jedis从Redis服务器读取数据,这是个极为轻巧简单的Java Redis客户端。 NOTE:下面的代码块就是相关代码。 package storm.analytics; public class UsersNavigationSpout extends BaseRichSpout { Jedis jedis; ... @Override public void nextTuple() { String content = jedis.rpop("navigation"); if(content==null || "nil".equals(content)){ try { Thread.sleep(300); } catch (InterruptedException e) {} } else { JSONObject obj=(JSONObject)JSONValue.parse(content); String user = obj.get("user").toString(); String product = obj.get("product").toString(); String type = obj.get("type").toString(); HashMap<String, String> map = new HashMap<String, String>(); map.put("product", product); NavigationEntry entry = new NavigationEntry(user, type, map); collector.emit(new Values(user, entry)); } } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("user", "otherdata")); } } spout首先调用jedis.rpop(“navigation”)从Redis删除并返回”navigation”列表最右边的元素。如果列表已经是空的,就休眠0.3秒,以免使用忙等待循环阻塞服务器。如果得到一条数据(数据是JSON格式),就解析它,并创建一个包含该数据的NavigationEntryPOJO: 浏览页面的用户 用户浏览的页面类型 由页面类型决定的额外页面信息。“产品”页的额外信息就是用户浏览的产品ID。 spout调用collector.emit(new Values(user, entry))分发包含这些信息的元组。这个元组的内容是拓扑里下一个bolt的输入。 GetCategoryBolt 这个bolt非常简单。它只负责反序列化前面的spout分发的元组内容。如果这是产品页的数据,就通过ProductsReader类从Redis读取产品信息,然后基于输入的元组再分发一个新的包含具体产品信息的元组: 用户 产品 产品类别 package storm.analytics; public class GetCategoryBolt extends BaseBasicBolt { private ProductReader reader; ... @Override public void execute(Tuple input, BasicOutputCollector collector) { NavigationEntry entry = (NavigationEntry)input.getValue(1); if("PRODUCT".equals(entry.getPageType())){ try { String product = (String)entry.getOtherData().get("product"); //调用产品条目API,得到产品信息 Product itm = reader.readItem(product); if(itm == null) { return; } String categ = itm.getCategory(); collector.emit(new Values(entry.getUserId(), product, categ)); } catch (Exception ex) { System.err.println("Error processing PRODUCT tuple"+ ex); ex.printStackTrace(); } } } ... } 正如前面所提到的, 使用ProductsReader类读取产品具体信息。 package storm.analytics.utilities; ... public class ProductReader { ... public Product readItem(String id) throws Exception{ String content = jedis.get(id); if(content == null || ("nil".equals(content))){ return null; } Object obj = JSONValue.parse(content); JSONObjectproduct = (JSONObject)obj; Product i = new Product((Long)product.get("id"), (String)product.get("title"), (Long)product.get("price"), (String)product.get("category")); return i; } ... } UserHistoryBolt UserHistoryBolt是整个应用的核心。它负责持续追踪每个用户浏览过的产品,并决定应当增加计数的键值对。 我们使用Redis保存用户的产品浏览历史,同时基于性能方面的考虑,还应该保留一份本地副本。我们把数据访问细节隐藏在方法getUserNavigationHistory(user)和addProductToHistory(user,prodKey)里,分别用来读/写访问。它们的实现如下 package storm.analytics; ... public class UserHistoryBolt extends BaseRichBolt{ @Override public void execute(Tuple input) { String user = input.getString(0); String prod1 = input.getString(1); String cat1 = input.getString(2); //产品键嵌入了产品类别信息 String prodKey = prod1+":"+cat1; Set productsNavigated = getUserNavigationHistory(user); //如果用户以前浏览过->忽略它 if(!productsNavigated.contains(prodKey)) { //否则更新相关条目 for (String other : productsNavigated) { String[] ot = other.split(":"); String prod2 = ot[0]; String cat2 = ot[1]; collector.emit(new Values(prod1, cat2)); collector.emit(new Values(prod2, cat1)); } addProductToHistory(user, prodKey); } } } 需要注意的是,这个bolt的输出是那些类别计数应当获得增长的产品。 看一看代码。这个bolt维护着一组被每个用户浏览过的产品。值得注意的是,这个集合包含产品:类别键值对,而不是只有产品。这是因为你会在接下来的调用中用到类别信息,而且这样也比每次从数据库获取更高效。这样做的原因是基于以下考虑,产品可能只有一个类别,而且它在整个产品的生命周期当中不会改变。 读取了用户以前浏览过的产品集合之后(以及它们的类别),检查当前产品以前有没有被浏览过。如果浏览过,这条浏览数据就被忽略了。如果这是首次浏览,遍历用户浏览历史,并执行collector.emit(new Values(prod1,cat2))分发一个元组,这个元组包含当前产品和所有浏览历史类别。第二个元组包含所有浏览历史产品和当前产品类别,由collectior.emit(new Values(prod2,cat1))。最后,将当前产品和它的类别添加到集合。 比如,假设用户John有以下浏览历史: 下面是将要处理的浏览数据 该用户没有浏览过产品8,因此你需要处理它。 因此要分发以下元组: 注意,左边的产品和右边的类别之间的关系应当作为一个整体递增。 现在,让我们看看这个Bolt用到的持久化实现。 public class UserHistoryBolt extends BaseRichBolt{ ... private Set getUserNavigationHistory(String user) { Set userHistory = usersNavigatedItems.get(user); if(userHistory == null) { userHistory = jedis.smembers(buildKey(user)); if(userHistory == null) userHistory = new HashSet(); usersNavigatedItems.put(user, userHistory); } return userHistory; } private void addProductToHistory(String user, String product) { Set userHistory = getUserNavigationHistory(user); userHistory.add(product); jedis.sadd(buildKey(user), product); } ... } getUserNavigationHistory方法返回用户浏览过的产品集。首先,通过usersNavigatedItems.get(user)方法试图从本地内存得到用户浏览历史,否则,使用jedis.smembers(buildKey(user))从Redis服务器获取,并把数据添加到本地数据结构usersNavigatedItems。 当用户浏览一个新产品时,调用addProductToHistory,通过userHistory.add(product)和jedis.sadd(buildKey(user),product)同时更新内存数据结构和Redis服务器。 需要注意的是,当你需要做并行化处理时,只要bolt在内存中维护着用户数据,你就得首先通过用户做域数据流分组(译者注:原文是fieldsGrouping,详细情况请见第三章的域数据流组),这是一件很重要的事情,否则集群内将会有用户浏览历史的多个不同步的副本。 ProductCategoriesCounterBolt 该类持续追踪所有的产品-类别关系。它通过由UsersHistoryBolt分发的产品-类别数据对更新计数。 每个数据对的出现次数保存在Redis服务器。基于性能方面的考虑,要使用一个本地读写缓存,通过一个后台线程向Redis发送数据。 该Bolt会向拓扑的下一个Bolt——NewsNotifierBolt——发送包含最新记数的元组,这也是最后一个Bolt,它会向最终用户广播实时更新的数据。 public class ProductCategoriesCounterBolt extends BaseRichBolt { ... @Override public void execute(){ String product = input.getString(0); String categ = input.getString(1); int total = count(product, categ); collector.emit(new Values(product, categ, total)); } ... private int count(String product, String categ) { int count = getProductCategoryCount(categ, product); count++; storeProductCategoryCount(categ, product, count); return count; } ... } 这个bolt的持久化工作隐藏在getProductCategoryCount和storeProductCategoryCount两个方法中。它们的具体实现如下: package storm.analytics; ... public class ProductCategoriesCounterBolt extends BaseRichBolt { // 条目:分类 -> 计数 HashMap<String,Integer> counter = new HashMap<String, Integer>(); //条目:分类 -> 计数 HashMap<String,Integer> pendingToSave = new HashMap<String,Integer>(); ... public int getProductCategoryCount(String categ, String product) { Integer count = counter.get(buildLocalKey(categ, product)); if(count == null) { String sCount = jedis.hget(buildRedisKey(product), categ); if(sCount == null || "nil".equals(sCount)) { count = 0; } else { count = Integer.valueOf(sCount); } } return count; } ... private void storeProductCategoryCount(String categ, String product, int count) { String key = buildLocalKey(categ, product); counter.put(key, count); synchronized (pendingToSave) { pendingToSave.put(key, count); } } ... } 方法getProductCategoryCount首先检查内存缓存计数器。如果没有有效令牌,就从Redis服务器取得数据。 方法storeProductCategoryCount更新计数器缓存和pendingToSae缓冲。缓冲数据由下述后台线程持久化。 package storm.analytics; public class ProductCategoriesCounterBolt extends BaseRichBolt { ... private void startDownloaderThread() { TimerTask t = startDownloaderThread() { @Override public void run () { HashMap<String, Integer> pendings; synchronized (pendingToSave) { pendings = pendingToSave; pendingToSave = new HashMap<String,Integer>(); } for (String key : pendings.keySet) { String[] keys = key.split(":"); String product = keys[0]; String categ = keys[1]; Integer count = pendings.get(key); jedis.hset(buildRedisKey(product), categ, count.toString()); } } }; timer = new Timer("Item categories downloader"); timer.scheduleAtFixedRate(t, downloadTime, downloadTime); } ... } 下载线程锁定pendingToSave, 向Redis发送数据时会为其它线程创建一个新的缓冲。这段代码每隔downloadTime毫秒运行一次,这个值可由拓扑配置参数download-time配置。download-time值越大,写入Redis的次数就越少,因为一对数据的连续计数只会向Redis写一次。 NewsNotifierBolt 为了让用户能够实时查看统计结果,由NewsNotifierBolt负责向web应用通知统计结果的变化。通知机制由Apache HttpClient通过HTTP POST访问由拓扑配置参数指定的URL。POST消息体是JSON格式。 测试时把这个bolt从拜年中删除。 01 packagestorm.analytics; 02 ... 03 publicclassNewsNotifierBoltextendsBaseRichBolt { 04 ... 05 @Override 06 publicvoidexecute(Tuple input) { 07 String product = input.getString(0); 08 String categ = input.getString(1); 09 intvisits = input.getInteger(2);</code> 10 11 String content ="{\"product\":\"+product+"\",\"categ\":\""+categ+"\",\"visits\":"+visits+"}"; 12 HttpPost post =newHttpPost(webserver); 13 try{ 14 post.setEntity(newStringEntity(content)); 15 HttpResponse response = client.execute(post); 16 org.apache.http.util.EntityUtils.consume(response.getEntity()); 17 }catch(Exception e) { 18 e.printStackTrace(); 19 reconnect(); 20 } 21 } 22 ... 23 } Redis服务器 Redis是一种先进的、基于内存的、支持持久化的键值存储(见http://redis.io)。本例使用它存储以下信息: 产品信息,用来为web站点服务 用户浏览队列,用来为Storm拓扑提供数据 Storm拓扑的中间数据,用于拓扑发生故障时恢复数据 Storm拓扑的处理结果,也就是我们期望得到的结果。 产品信息 Redis服务器以产品ID作为键,以JSON字符串作为值保存着产品信息。 1 redis-cli 2 redis 127.0.0.1:6379&gt; get 15 3 "{\"title\":\"Kids smartphone cover\",\"category\":\"Covers\",\"price\":30,\"id\": 4 15}" 用户浏览队列 用户浏览队列保存在Redis中一个键为navigation的先进先出队列中。用户浏览一个产品页时,服务器从队列左侧添加用户浏览数据。Storm集群不断的从队列右侧获取并移除数据。 01 redis 127.0.0.1:6379&gt; llen navigation 02 (integer) 5 03 redis 127.0.0.1:6379&gt; lrange navigation 0 4 04 1) "{\"user\":\"59c34159-0ecb-4ef3-a56b-99150346f8d5\",\"product\":\"1\",\"type\": 05 \"PRODUCT\"}" 06 2) "{\"user\":\"59c34159-0ecb-4ef3-a56b-99150346f8d5\",\"product\":\"1\",\"type\": 07 \"PRODUCT\"}" 08 3) "{\"user\":\"59c34159-0ecb-4ef3-a56b-99150346f8d5\",\"product\":\"2\",\"type\": 09 \"PRODUCT\"}" 10 4) "{\"user\":\"59c34159-0ecb-4ef3-a56b-99150346f8d5\",\"product\":\"3\",\"type\": 11 \"PRODUCT\"}" 12 5) "{\"user\":\"59c34159-0ecb-4ef3-a56b-99150346f8d5\",\"product\":\"5\",\"type\": 13 \"PRODUCT\"}" 中间数据 集群需要分开保存每个用户的历史数据。为了实现这一点,它在Redis服务器上保存着一个包含所有用户浏览过的产品和它们的分类的集合。 1 redis 127.0.0.1:6379&gt; smembershistory:59c34159-0ecb-4ef3-a56b-99150346f8d5 2 1)"1:Players" 3 2)"5:Cameras" 4 3)"2:Players" 5 4)"3:Cameras" 结果 Storm集群生成关于用户浏览的有用数据,并把它们的产品ID保存在一个名为“prodcnt”的Redis hash中。 1 redis 127.0.0.1:6379&gt; hgetall prodcnt:2 2 1)"Players" 3 2)"1" 4 3)"Cameras" 5 4)"2" 测试拓扑 使用LocalCluster和一个本地Redis服务器执行测试(见图6-7)。向Redis填充产品数据,伪造访问日志。我们的断言会在读取拓扑向Redis输出的数据时执行。测试用户用java和groovy完成。 图6-7. 测试架构 初始化测试 初始化由以下三步组成:启动LocalCluster并提交拓扑。初始化在AbstractAnalyticsTest实现,所有测试用例都继承该类。当初始化多个AbstractAnalyticsTest子类对象时,由一个名为topologyStarted的静态标志属性确定初始化工作只会进行一次。 需要注意的是,sleep语句是为了确保在试图获取结果之前LocalCluster已经正确启动了。 01 publicabstractclassAbstractAnalyticsTestextendsAssert { 02 defjedis 03 statictopologyStarted = false 04 staticsync=newObject() 05 privatevoidreconnect() { 06 jedis =newJedis(TopologyStarter.REDIS_HOST, TopologyStarter.REDIS_PORT) 07 } 08 @Before 09 publicvoidstartTopology(){ 10 synchronized(sync){ 11 reconnect() 12 if(!topologyStarted){ 13 jedis.flushAll() 14 populateProducts() 15 TopologyStarter.testing = true 16 TopologyStarter.main(null) 17 topologyStarted = true 18 sleep1000 19 } 20 } 21 } 22 ... 23 publicvoidpopulateProducts() { 24 deftestProducts = [ 25 [id:0, title:"Dvd player with surround sound system", 26 category:"Players", price:100], 27 [id:1, title:"Full HD Bluray and DVD player", 28 category:"Players", price:130], 29 [id:2, title:"Media player with USB 2.0 input", 30 category:"Players", price:70], 31 ... 32 [id:21, title:"TV Wall mount bracket 50-55 Inches", 33 category:"Mounts", price:80] 34 ] 35 testProducts.each() { product -&gt; 36 defval = 37 "{ \"title\": \"${product.title}\" , \"category\": \"${product.category}\","+ 38 " \"price\": ${product.price}, \"id\": ${product.id} }" 39 printlnval 40 jedis.set(product.id.toString(), val.toString()) 41 } 42 } 43 ... 44 } 在AbstractAnalyticsTest中实现一个名为navigate的方法。为了测试不同的场景,我们要模拟用户浏览站点的行为,这一步向Redis的浏览队列(译者注:就是前文提到的键是navigation的队列)插入浏览数据。 01 publicabstractclassAbstractAnalyticsTestextendsAssert { 02 ... 03 publicvoidnavigate(user, product) { 04 String nav = 05 "{\"user\": \"${user}\", \"product\": \"${product}\", \"type\": \"PRODUCT\"}".toString() 06 println"Pushing navigation: ${nav}" 07 jedis.lpush('navigation', nav) 08 } 09 ... 10 } 实现一个名为getProductCategoryStats的方法,用来读取指定产品与分类的数据。不同的测试同样需要断言统计结果,以便检查拓扑是否按照期望的那样执行了。 01 publicabstractclassAbstractAnalyticsTestextendsAssert { 02 ... 03 publicintgetProductCategoryStats(String product, String categ) { 04 Stringcount= jedis.hget("prodcnt:${product}", categ) 05 if(count==null||"nil".equals(count)) 06 return0 07 returnInteger.valueOf(count) 08 } 09 ... 10 } 一个测试用例 下一步,为用户“1”模拟一些浏览记录,并检查结果。注意执行断言之前要给系统留出两秒钟处理数据。(记住ProductCategoryCounterBolt维护着一份计数的本地副本,它是在后台异步保存到Redis的。) 01 packagefunctional 02 classStatsTestextendsAbstractAnalyticsTest { 03 @Test 04 publicvoidtestNoDuplication(){ 05 navigate("1","0")// Players 06 navigate("1","1")// Players 07 navigate("1","2")// Players 08 navigate("1","3")// Cameras 09 Thread.sleep(2000)// Give two seconds for the system to process the data. 10 assertEquals1, getProductCategoryStats("0","Cameras") 11 assertEquals1, getProductCategoryStats("1","Cameras") 12 assertEquals1, getProductCategoryStats("2","Cameras") 13 assertEquals2, getProductCategoryStats("0","Players") 14 assertEquals3, getProductCategoryStats("3","Players") 15 } 16 } 对可扩展性和可用性的提示 为了能在一章的篇幅中讲明白整个方案,它已经被简化了。正因如此,一些与可扩展性和可用性有关的必要复杂性也被去掉了。这方面主要有两个问题。 Redis服务器不只是一个故障的节点,还是性能瓶颈。你能接收的数据最多就是Redis能处理的那些。Redis可以通过分片增强扩展性,它的可用性可以通过主从配置得到改进。这都需要修改拓扑和web应用的代码实现。 另一个缺点就是web应用不能通过增加服务器成比例的扩展。这是因为当产品统计数据发生变化时,需要通知所有关注它的浏览器。这一“通知浏览器”的机制通过Socket.io实现,但是它要求监听器和通知器在同一主机上。这一点只有当GET /product/:id/stats和POST /news满足以下条件时才能实现,那就是这二者拥有相同的分片标准,确保引用相同产品的请求由相同的服务器处理。 文章转自并发编程网-ifeve.com

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册