首页 文章 精选 留言 我的

精选列表

搜索[官方镜像],共10000篇文章
优秀的个人博客,低调大师

HarmonyOS官方模板学习 之 Grid Ability(Java)

@[toc](目录) # Grid Ability(Java) ## 介绍 使用Java语言开发,用于Phone设备的Feature Ability模板,使用XML布局,显示内容为两部分网格表,网格每行显示4个项目,网格内元素可进行拖拽排序。 ## 搭建环境 安装DevEco Studio,详情请参考[DevEco Studio下载](https://developer.harmonyos.com/cn/develop/deveco-studio)。 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境: 如果可以直接访问Internet,只需进行[下载HarmonyOS SDK](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/environment_config-0000001052902427)操作。 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考[配置开发环境](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/environment_config-0000001052902427)。 ## 代码结构解读 注意:'#'代表注释 后台功能 ```json gridabilityjava │ MainAbility.java │ MyApplication.java │ ├─component │ DragLayout.java #自定义的拖拽功能组件 │ GridView.java #自定义的Grid视图组件,extends TableLayout │ ├─model │ GridItemInfo.java #Grid item 模型 │ ├─provider │ GridAdapter.java #给Grid提供实例化好的item 组件列表;提供了计算单个item的宽度的方法 │ ├─slice │ MainAbilitySlice.java #主能力页,负责实例化自定义的DragLayout拖拽组件 │ └─utils AppUtils.java #工具类,提供了从element资源中中获取value;获取屏幕的坐标的方法 ``` 这是几个java类之间的关系 ![image.png](https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202106/580eaf59927ec18dd90122d1ceee31f6a4f3ff.png?x-oss-process=image/resize,w_658,h_771) 页面资源 ```json resources ├─base │ ├─element │ │ color.json │ │ float.json │ │ integer.json │ │ string.json │ │ │ ├─graphic │ │ background_bottom_button.xml #页面底部按钮形状 │ │ background_bottom_layout.xml #页面底部布局形状 │ │ background_item_active_button.xml #grid item 激活形状 │ │ background_item_button.xml #grid item 默认形状 │ │ background_table_layout_down.xml #下面的 grid 形状 │ │ background_table_layout_up.xml #上面的 grid 形状 │ │ │ ├─layout │ │ ability_main.xml #主显示页面 │ │ app_bar_layout.xml #app工具栏布局页面 │ │ grid_item.xml #单个grid item布局页面 │ │ │ ├─media │ │ 5G.png │ │ back.png │ │ back_white.png ``` ## 页面布局 ### ability_main.xml #主显示页 此页面由DirectionalLayout、StackLayout、DependentLayout 布局构成,整体布局是上下布局。 上面时app工具栏,使用了StackLayout布局,通过includ标签引入到主页面。 下面是支持拖拽的GridView,由DependentLayout 和DirectionalLayout布局组成,使用的组件有ScrollView、GridView、Text、Button、Image。 ![image.png](https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202106/995bf5c59199f2ed3d79375f5593bd8e67a9a8.png?x-oss-process=image/resize,w_696,h_639) ### app_bar_layout.xml #app工具栏布局页面 ![image.png](https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202106/03992ad991e2af350920520652a8c66bc1cdc5.png?x-oss-process=image/resize,w_265,h_149) ### grid_item.xml #单个grid item布局页面 ![image.png](https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202106/2800b6b146e1585dde0483cfac61a140a44c94.png?x-oss-process=image/resize,w_264,h_125) ## 后台逻辑 ### 1.初始化上面的GridView 先构建item模拟数据列表,将构建好的数据传递给GridAdapter 初始化item组件列表,通过GridView.setAdapter方法给每个item组件绑定长按事件,并设置GridView的TAG属性(TAG就是指上面的GridView还是下面的GridView)。 ```java /** * 初始化上面的Grid item */ private void initUpListItem() { //构建item模拟数据列表 List upperItemList = new ArrayList<>(); for (int i = 0; i < UP_ITEM_COUNT; i++) { int iconId = icons[i]; String text = texts[i]; upperItemList.add(new GridItemInfo(text, iconId, UP_GRID_TAG)); } GridView gridView = (GridView) slice.findComponentById(ResourceTable.Id_grid_view_up); //将构建好的数据传递给GridAdapter 初始化item组件列表 GridAdapter adapter = new GridAdapter(slice.getContext(), upperItemList); //通过GridView.setAdapter方法给每个item组件绑定长按事件 gridView.setAdapter(adapter, longClickListener); //设置GridView的TAG属性 gridView.setTag(UP_GRID_TAG); } ``` ### 2.初始化下面的GridView 逻辑同上 ```java /** * 初始化下面的Grid item */ private void initDownListItem() { String itemText = AppUtils.getStringResource(slice.getContext(), ResourceTable.String_grid_item_text); List lowerItemList = new ArrayList<>(); for (int i = 0; i < DOWN_ITEM_COUNT; i++) { //随意取的图标 int iconId = icons[i + 5]; String text = texts[i + 5]; lowerItemList.add(new GridItemInfo(text, iconId, DOWN_GRID_TAG)); } if (slice.findComponentById(ResourceTable.Id_grid_view_down) instanceof GridView) { GridView gridView = (GridView) slice.findComponentById(ResourceTable.Id_grid_view_down); GridAdapter adapter = new GridAdapter(slice.getContext(), lowerItemList); gridView.setAdapter(adapter, longClickListener); gridView.setTag(DOWN_GRID_TAG); } } ``` ### 3.初始化底部的按钮 这个地方做了一个屏幕适配,就是根据屏幕的宽度、边距来设置按钮的宽度, 同时添加了按钮的监听事件,点击按钮 关闭当前Ability。 ```java /** * Calculating button width based on screen width. * The actual width is the screen width minus the margin of the buttons. * 设置底部 2个按钮的宽度 */ private void initBottomItem() { int screenWidth = AppUtils.getScreenInfo(slice.getContext()).getPointXToInt(); //计算按钮宽度 int buttonWidth = (screenWidth - AttrHelper.vp2px(80, slice.getContext())) / 2; Component leftButton = slice.findComponentById(ResourceTable.Id_bottom_left_button); leftButton.setWidth(buttonWidth); //关闭Ability leftButton.setClickedListener(component -> slice.terminateAbility()); Component rightButton = slice.findComponentById(ResourceTable.Id_bottom_right_button); rightButton.setWidth(buttonWidth); //关闭Ability rightButton.setClickedListener(component -> slice.terminateAbility()); } ``` ### 4.初始化app工具栏 这个没做什么,似乎是想根据本地化信息,设置返回箭头的方向,因为有的语言是从右往左看的。 ```java /** * 检查指定 Locale 的文本布局是否从右到左。 * 设置返回箭头的方向 */ private void initAppBar() { if (TextTool.isLayoutRightToLeft(Locale.getDefault())) { Image appBackImg = (Image) slice.findComponentById(ResourceTable.Id_left_arrow); appBackImg.setRotation(180); } } ``` ### 5.初始化监听事件 包括返回按钮的返回事件、ScrollView的touch事件。 touch事件包含大量的细节操作,如拖拽时有一个阴影效果,滚动条的处理,拖拽交换结束的处理,过渡效果,上下grid 有效区域的计算,拖拽完成将拖拽的组件添加到对应grid的操作等,参照着拿来用吧。 ```java /** * 初始化监听事件,包括返回按钮返回事件、ScrollView的touch事件 */ private void initEventListener() { //‘返回按钮’的监听事件 if (slice.findComponentById(ResourceTable.Id_left_arrow) instanceof Image) { Image backIcon = (Image) slice.findComponentById(ResourceTable.Id_left_arrow); // backIcon.setClickedListener(component -> slice.terminateAbility()); } //ScrollView的 Touch事件监听,拿来用就可以了 scrollView.setTouchEventListener( (component, touchEvent) -> { //按下屏幕的位置 MmiPoint downScreenPoint = touchEvent.getPointerScreenPosition(touchEvent.getIndex()); switch (touchEvent.getAction()) { //表示第一根手指触摸屏幕。这表示交互的开始 case TouchEvent.PRIMARY_POINT_DOWN: currentDragX = (int) downScreenPoint.getX(); currentDragY = (int) downScreenPoint.getY(); //获取指针索引相对于偏移位置的 x 和 y 坐标。 MmiPoint downPoint = touchEvent.getPointerPosition(touchEvent.getIndex()); scrollViewTop = (int) downScreenPoint.getY() - (int) downPoint.getY(); scrollViewLeft = (int) downScreenPoint.getX() - (int) downPoint.getX(); return true; //表示最后一个手指从屏幕上抬起。这表示交互结束 case TouchEvent.PRIMARY_POINT_UP: //恢复下面grid的描述 changeTableLayoutDownDesc(ResourceTable.String_down_grid_layout_desc_text); case TouchEvent.CANCEL: if (isViewOnDrag) { selectedView.setScale(1.0f, 1.0f); selectedView.setAlpha(1.0f); selectedView.setVisibility(Component.VISIBLE); isViewOnDrag = false; isScroll = false; return true; } break; //表示手指在屏幕上移动 case TouchEvent.POINT_MOVE: if (!isViewOnDrag) { break; } int pointX = (int) downScreenPoint.getX(); int pointY = (int) downScreenPoint.getY(); this.exchangeItem(pointX, pointY); if (UP_GRID_TAG.equals(selectedView.getTag())) { this.swapItems(pointX, pointY); } this.handleScroll(pointY); return true; } return false; } ); } ``` ## 归纳总结 ### 1.自定义组件在构造函数中传递slice 这样的目的是便于获取页面的其它组件。 ```java Component itemLayout=LayoutScatter.getInstance(slice.getContext()) .parse(ResourceTable.Layout_grid_item, null, false); ``` 需要注意的是slice指代的是页面,但是自定义组件往往是有自己的布局文件的,一般不在slice中,所以不要通过slice获取自定义组件的子组件,获取不到,不过可以通过LayoutScatter获取 ```java //错误的方式 Component gridItem= slice.findComponentById(ResourceTable.Layout_grid_item); //正确的方式 Component gridItem = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_grid_item, null, false); ``` ### 2.单位转换vp2px java组件对象宽高、边距的单位默认时px, 从element中获取的值需要进行单位转换,可以使用AttrHelper.vp2px 将vp转换为px。 ```java if (gridItem.findComponentById(ResourceTable.Id_grid_item_text) instanceof Text) { Text textItem = (Text) gridItem.findComponentById(ResourceTable.Id_grid_item_text); textItem.setText(item.getItemText()); textItem.setTextSize(AttrHelper.fp2px(10, context)); } ``` ### 3.子组件的获取 获取一个组件对象后,可以使用该组件对象的findComponentById方法继续获取内部的子组件 ```java Component gridItem = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_grid_item, null, false); Image imageItem = (Image) gridItem.findComponentById(ResourceTable.Id_grid_item_image); ``` ### 4.TableLayout的使用 TableLayout继承自ComponentContainer,提供用于在带有表格的组件中排列组件的布局。 TableLayout 提供了用于对齐和排列组件的接口,以在带有表格的组件中显示组件。 排列方式、行列数、元件位置均可配置。 例如 removeAllComponents();可以用来清除 ComponentContainer 管理的所有组件,addComponent 用来将组件添加到ComponentContainer 容器中。示例中GridView就是继承自TableLayout。 ```java /** * The setAdapter * * @param adapter adapter * @param longClickedListener longClickedListener */ void setAdapter(GridAdapter adapter, LongClickedListener longClickedListener) { //清除 ComponentContainer 管理的所有组件 removeAllComponents(); //遍历item组件列表 for (int i = 0; i < adapter.getComponentList().size(); i++) { //为组件中的长按事件注册一个监听器(组件被点击并按住) adapter.getComponentList().get(i).setLongClickedListener(longClickedListener); //将组件添加到容器中 addComponent(adapter.getComponentList().get(i)); } } ``` ## 效果展示 示例代码模拟了一下手机控制中心,编辑快捷开关的效果 |原效果|模拟效果| |-|-| |![image.png](https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202106/c362eba72a004a3896d752987de60df614e988.png?x-oss-process=image/resize,w_331,h_692)|![动画2.gif](https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202106/18c5aea55f50cb0521b243a0278f415bffe0c2.gif?x-oss-process=image/resize,w_331,h_692)| ## 完整代码 文章相关附件可以点击下面的原文链接前往下载 原文链接:https://harmonyos.51cto.com/posts/6257#bkwz

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

苹果官方回应新款 Mac 安装 Windows 问题

Windows版迁移助理更新 由于苹果自研芯片 M1 的强大性能,新款 MacBook Air/Pro 以及 Mac mini 销量持续突破,不少人纷纷选购了新款 Mac 产品(包括我)。 在此之前,相信有不少人一直使用的是 Windows 系统,而并且由于新款 Mac 无法安装 Windows 系统,令不少老 PC 用户感到懵逼。 所以苹果在近日更新了旗下的 Windows 上的迁移助理(Migration Assistant)软件,使其与 macOS Big Sur 兼容。 不过这可不是代表新 Mac 就可以安装 Windows 系统了,这个软件用途是可以将你的 Windows 系统上的资料,移动至 Mac 电脑中,让你更加快速的习惯 Mac 电脑, 当然了,这个迁移只限制 Windows 电脑中的联系人,日历,电子邮件帐户等资料,而游戏,软件等是不会被迁移过去的,因为 Mac 并不支持 Windows 上的 .exe 文件。 苹果 Windows 何时可以安装? 由于苹果 M1 芯片采用的 ARM 架构,这直接导致了新款 Mac 无法再安装 Windows 系统,让不少有需要的用户非常难受。 不过近日苹果高管在接受采访的时候,针对于这个新款 Mac 安装 Windows 系统的问题进行了回应。 Federighi 表示,M1 Mac 上的 Windows 是 “ 由微软决定的 "。核心技术是存在的,Mac 也能胜任,但微软必须决定是否向 Mac 用户授权其基于 ARM 架构的 Windows 版本。 也就是说,苹果已经明确表示了新款 Mac 和 M1 芯片性能和各方面是支持 Windows 系统的,但是需要微软自己推出支持的版本才行,毕竟苹果无法逼着微软去推出桌面版本的 ARM Windows。 除此之外,苹果高管还表示,如果现在用户想要在 Mac 上使用 Windows,可以考虑云端运行 Windows。 值得注意的是,其实 Windows 是拥有 ARM 架构的版本,不过是一个极度阉割版本,被称为 Windows RT 系统只能运行一些特定的应用,并不能和 Mac 一样兼容运行其他应用。

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

scrapy官方文档提供的常见使用问题

Scrapy与BeautifulSoup或lxml相比如何? BeautifulSoup和lxml是用于解析HTML和XML的库。Scrapy是一个用于编写Web爬虫的应用程序框架,可以抓取网站并从中提取数据。 Scrapy提供了一种用于提取数据的内置机制(称为选择器),但如果您觉得使用它们感觉更舒服,则可以轻松使用BeautifulSoup(或lxml)。毕竟,他们只是解析可以从任何Python代码导入和使用的库。 换句话说,将BeautifulSoup(或lxml)与Scrapy进行比较就像将jinja2与Django进行比较一样。 我可以和BeautifulSoup一起使用Scrapy吗? 是的你可以。如所提到的上面,BeautifulSoup可用于在Scrapy回调解析HTML响应。您只需将响应的主体提供给BeautifulSo

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

组复制官方翻译二、Group Replication Background

https://dev.mysql.com/doc/refman/8.0/en/group-replication-background.html 这一章主要描述一些组复制的背景 构建一个容错系统最常用的方法就是让组件冗余,换句话说就是组件即便被移除掉,整个系统还是能够正常对外提供服务这无疑在不同层面上提出了更多的挑战需要注意的是,复制结构的数据库系统必须思考的一个事实就是:他们需要维护和管理一堆不同的sever此外,他们还必须解决分布式系统所面临的问题:比如 脑裂、网络分区等等 因此,最大的挑战就是去融合这种逻辑数据库,保证数据复制的一致性换句话说,为了让不同server都同意这个系统的状态,他们每一台server的数据修改都必须验证一致这就意味着他们需要运作的想一个状态机一样(分布式) MySQL Group Replication提供了一套分布式状态机制复制管理方法 对于要提交的事务,这个group采取大部分原则来投票,让事务全局有序决定commit还是拒绝这个事务都是由server自行判断,但是所有servers都会做出一样的决定如果网络产生了分区,脑裂产生导致成员之间无法达成一致投票决定,那么这个系统会停止运行直到这个问题被解决所以,他有一个内置、自动的脑裂包含机制在运行 以上所有的功能都是由Group Communication System (GCS) 协议来保证它有错误检测机制、组成员通信服务、安全可靠的顺序一致消息分发所有这些特性是搭建一个 数据完全一致性的系统 的关键要素在一些非常核心重要的技术点上 罗列了Paxos 算法的实现,它扮演着组复制通信引擎的角色,至关重要 18.1.1 Replication Technologies 在了解MGR内幕之前,这里先主要介绍下相关的背景概念、以及概述这章主要告诉我们,MGR需要什么,以及传统的异步复制和MGR直接的一些区别 18.1.1.1 Primary-Secondary Replication 传统的复制提供了一个简单的主从复制架构(Primary-Secondary)primary就是master,secondary就是slaves,可以有多个slavesmaster执行事务、commit事务,然后异步的将这些事务发送到slaves,让他们re-executed一遍(statement模式)或者 重新applied (ROW模式)它是share-nothing架构,即所有server都有一份完整的数据copy 还有一种传统复制叫:半同步复制它意味着:在commit的之前,master等待,直到slaves给master一个确认接收到事务的ack,master才恢复commit的操作 在上面的两幅图中,你能看到异步传统复制协议的基本架构,箭头代表client消息的流动和转变 18.1.1.2 Group Replication 组复制是一个实现了容错系统的技术组复制集群就是一堆机器,他们之间通过消息进行沟通communication 层:提供了一系列的保障机制,atomic message(原子广播) , total order message delivery(全局序列消息分发机制) MGR在此基础上构建并实现了一个multi-master的复制协议,它可以在任何server上写数据集群的本质就是多server,每个server可以独立的处理事务但是所有的读写(RW)事务都必须经过集群的审核所有的只读(RO)事务不受任何影响换句话说,对于RW事务,group只需要决定它应该commit还是拒绝commit,因此事务操作并不是单方面(origi server)的决定确切的说,当origin server准备进行事务commit的时候,这个server会自动广播这个写集然后,一个全局排序的事务产生了这意味着,所有的server都接收同样顺序的事务集由于是有序的,所有server应用相同顺序,相同数据的写集,因此他们的数据也是一致的 然而,如果是并发写在不同server的场景会遇到冲突因此,对应这种情况需要进行冲突检测,这个过程叫做认证 certification如果两个并发事务在不同server同时执行,并且更新了相同的row,那么他们就是冲突的那么它的解决方案就是,排在前面的事务会被标记commit,排在后面的会被拒绝(这个事务在origin server会回滚,其他server会被丢弃) 最后,MGR也是一种share-nothing架构,每个server都有一份完整的数据copy 18.1.2 Group Replication Use Cases 组复制提供了一个高容错性的系统,即使一些机器宕机,只要不是所有或者大多数机器不可用,那么整个系统还是可用状态总结下来,MGR保证数据库持续可用 18.1.2.1 Examples of Use Case Scenarios 以下就是典型的MGR使用案例 Elastic Replication 可伸缩的复制 Highly Available Shards 高可用的分片 Alternative to Master-Slave replication 可选择master-slave架构 Autonomic Systems 完全自动化的系统 18.1.3 Group Replication Details 18.1.3.1 Failure Detection 它提供一个错误检测机制,可以找到或报告出哪些servers没有回应,哪些server挂了在高一层次来将,错误检测机制就是一个分布式服务,用于提供哪些server挂掉或可能挂掉的情报信息之后,如果组成员通过某种协议认证了这个嫌疑犯(可能挂掉的家伙)已经真的挂了,那么集群就会决定这个嫌疑犯真的的确挂了这意味着,组的其他成员一致决定将这个嫌疑犯踢出集群 当Server A 在指定time-out时间内没有收到来自server B的回应,那么B就会被提升为嫌疑犯如果一个Server被其他group成员隔离,那么它就会怀疑所有其他的成员都挂了由于它不能达成投票的一致性认可(没有达到法定人数的确认),所以它认为的嫌疑犯就不能被确认为failed如果一个Server在这种情况下被隔离,那么他是不能执行local事务的 18.1.3.2 Group Membership MGR依赖组会员服务(Group Membership Service,简称GMS),它是内置的它定义了哪些servers是online并加入了这个group,这些online servers经常被称为view因此,这个组里面的任一online成员都有一个一致的view 如果servers同意让一个新server加入到这个group中来,那么这个group就好重新自动将其配置上,且重新触发形成一个新的view如果一个server非自愿的离开了group,那么错误检测机制就开始识别,也会重新配置上一个新的view上面提到的这些都需要一个协议,并且需要大多数人参与并认可的协议如果这个group没有满足达到这个协议认可的要求,那么自动配置将不会起作用,并且该系统会被阻塞来防止脑裂的产生最后,这意味着管理员需要手动介入来解决这个问题 18.1.3.3 Fault-tolerance MGR 是在Paxos分布式算法构建实现的,以此它需要满足大多数活跃成员进行投票选举的策略。有一个公式:n = 2 x f + 1 , n代表group的成员数,f代表允许挂掉的成员数 ,在这个公式下,整个集群是安全的 如果n=3,那么允许挂掉的server是1,也能满足要求,但是如果再挂一个呢, 其实就问题非常大了 集群成员数量n majority 可允许挂掉的server数量 1 1 0 2 2 0 3 2 1 4 3 1 5 3 2 6 4 2 7 4 3

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

Redisson官方文档 - 10. 额外功能

10.1. 对Redis节点的操作 Redisson的NodesGroup对象提供了许些对Redis节点的操作。 NodesGroup nodesGroup = redisson.getNodesGroup(); nodesGroup.addConnectionListener(new ConnectionListener() { public void onConnect(InetSocketAddress addr) { // Redis节点连接成功 } public void onDisconnect(InetSocketAddress addr) { // Redis节点连接断开 } }); 也可以用来PING单个Redis节点或全部节点。 NodesGroup nodesGroup = redisson.getNodesGroup(); Collection<Node> allNodes = nodesGroup.getNodes(); for (Node n : allNodes) { n.ping(); } // 或者 nodesGroup.pingAll(); 10.2. 复杂多维对象结构和对象引用的支持 Redisson突破了Redis数据结构维度的限制,通过一个特殊引用对象的帮助,Redisson允许以任意的组合方式构建多维度的复杂对象结构,实现了对象之间的类似传统数据库里的关联关系。使用范例如下: RMap<RSet<RList>, RList<RMap>> map = redisson.getMap("myMap"); RSet<RList> set = redisson.getSet("mySet"); RList<RMap> list = redisson.getList("myList"); map.put(set, list); // 在特殊引用对象的帮助下,我们甚至可以构建一个循环引用,这是通过普通序列化方式实现不了的。 set.add(list); list.add(map); 可能您已经注意到了,在map包含的元素发生改变以后,我们无需再次“保存/持久”这些对象。因为map对象所记录的并不是序列化以后的值,而是元素对象的引用。这让Redisson提供的对象在使用方法上,与普通Java对象的使用方法一致。从而让Redis成为内存的一部分,而不仅仅是一个储存空间。 以上范例中,一共创建了三个Redis数据结构:一个Redis HASH,一个Redis SET和一个Redis LIST。 10.3. 命令的批量执行 多个连续命令可以通过RBatch对象在一次网络会话请求里合并发送,这样省去了产生多个请求消耗的时间和资源。这在Redis中叫做管道。 RBatch batch = redisson.createBatch(); batch.getMap("test").fastPutAsync("1", "2"); batch.getMap("test").fastPutAsync("2", "3"); batch.getMap("test").putAsync("2", "5"); batch.getAtomicLongAsync("counter").incrementAndGetAsync(); batch.getAtomicLongAsync("counter").incrementAndGetAsync(); // 原子化(事务)批量执行所有的命令 batch.atomic(); // 告知Redis不用返回结果(可以减少网络用量) batch.skipResult(); // 将写入操作同步到从节点 // 同步到2个从节点,等待时间为1秒钟 batch.syncSlaves(2, 1, TimeUnit.SECONDS) // 处理结果超时为2秒钟 batch.timeout(2, TimeUnit.SECONDS); // 命令重试等待间隔时间为2秒钟 batch.retryInterval(2, TimeUnit.SECONDS); // 命令重试次数,仅适用于未发送成功的命令 batch.retryAttempts(4); BatchResult res = batch.execute(); // 或者 Future<BatchResult> asyncRes = batch.executeAsync(); 在集群模式下,所有的命令会按各个槽所在的节点,筛选分配到各个节点并同时发送。每个节点返回的结果将会汇总到最终的结果列表里。 10.4. 脚本执行 redisson.getBucket("foo").set("bar"); String r = redisson.getScript().eval(Mode.READ_ONLY, "return redis.call('get', 'foo')", RScript.ReturnType.VALUE); // 通过预存的脚本进行同样的操作 RScript s = redisson.getScript(); // 首先将脚本保存到所有的Redis主节点 String res = s.scriptLoad("return redis.call('get', 'foo')"); // 返回值 res == 282297a0228f48cd3fc6a55de6316f31422f5d17 // 再通过SHA值调用脚本 Future<Object> r1 = redisson.getScript().evalShaAsync(Mode.READ_ONLY, "282297a0228f48cd3fc6a55de6316f31422f5d17", RScript.ReturnType.VALUE, Collections.emptyList()); 10.5. 底层Redis客户端 Redisson在底层采用了高性能异步非阻塞式Java客户端,它同时支持异步和同步两种通信模式。如果有哪些命令Redisson还没提供支持,也可以直接通过调用底层Redis客户端来实现。Redisson支持的命令在Redis命令和Redisson对象匹配列表里做了详细对比参照。 // 在使用多个客户端的情况下可以共享同一个EventLoopGroup EventLoopGroup group = new NioEventLoopGroup(); RedisClientConfig config = new RedisClientConfig(); config.setAddress("redis://localhost:6379") // 或者用rediss://使用加密连接 .setPassword("myPassword") .setDatabase(0) .setClientName("myClient") .setGroup(group); RedisClient client = RedisClient.create(config); RedisConnection conn = client.connect(); // 或 RFuture<RedisConnection> connFuture = client.connectAsync(); conn.sync(StringCodec.INSTANCE, RedisCommands.SET, "test", 0); // 或 conn.async(StringCodec.INSTANCE, RedisCommands.GET, "test"); conn.close() // 或 conn.closeAsync() client.shutdown(); // 或 client.shutdownAsync();

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

App开发架构指南(谷歌官方文档译文)

这篇文章面向的是已经掌握app开发基本知识,想知道如何开发健壮app的读者。 注:本指南假设读者对 Android Framework 已经很熟悉。如果你还是app开发的新手,请查看 Getting Started 系列教程,该教程涵盖了本指南的预备知识。 app开发者面临的常见问题 跟传统的桌面应用开发不同,Android app的架构要复杂得多。一个典型的Android app是由多个app组件构成的,包括activity,Fragment,service,content provider以及broadcast receiver。而传统的桌面应用往往在一个庞大的单一的进程中就完成了。 大多数的app组件都声明在app manifest中,Android OS用它来决定如何将你的app与设备整合形成统一的用户体验。虽然就如刚说的,桌面app只运行一个进程,但是一个优秀的Android app却需要更加灵活,因为用户操作在不同app之间,不断的切换流程和任务。 比如,当你要在自己最喜欢的社交网络app中分享一张照片的时候,你可以想象一下会发生什么。app触发一个camera intent,然后Android OS启动一个camera app来处理这一动作。此时用户已经离开了社交网络的app,但是用户的操作体验却是无缝对接的。而 camera app反过来也可能触发另一个intent,比如启动一个文件选择器,这可能会再次打开另一个app。最后用户回到社交网络app并分享照片。在这期间的任意时刻用户都可被电话打断,打完电话之后继续回来分享照片。 在Android中,这种app并行操作的行为是很常见的,因此你的app必须正确处理这些流程。还要记住移动设备的资源是有限的,因此任何时候操作系统都有可能杀死某些app,为新运行的app腾出空间。 总的来说就是,你的app组件可能是单独启动并且是无序的,而且在任何时候都有可能被系统或者用户销毁。因为app组件生命的短暂性以及生命周期的不可控制性,任何数据都不应该把存放在app组件中,同时app组件之间也不应该相互依赖。 通用的架构准则 如果app组件不能存放数据和状态,那么app还是可架构的吗? 最重要的一个原则就是尽量在app中做到separation of concerns(关注点分离)。常见的错误就是把所有代码都写在Activity或者Fragment中。任何跟UI和系统交互无关的事情都不应该放在这些类当中。尽可能让它们保持简单轻量可以避免很多生命周期方面的问题。别忘了能并不拥有这些类,它们只是连接app和操作系统的桥梁。根据用户的操作和其它因素,比如低内存,Android OS可能在任何时候销毁它们。为了提供可靠的用户体验,最好把对它们的依赖最小化。 第二个很重要的准则是用。之所以要持久化是基于两个原因:如果OS销毁app释放资源,用户数据不会丢失;当网络很差或者断网的时候app可以继续工作。Model是负责app数据处理的组件。它们不依赖于View或者app 组件(Activity,Fragment等),因此它们不会受那些组件的生命周期的影响。保持UI代码的简单,于业务逻辑分离可以让它更易管理。 app架构推荐 在这一小节中,我们将通过一个用例演示如何使用Architecture Component构建一个app。 注:没有一种适合所有场景的app编写方式。也就是说,这里推荐的架构适合作为大多数用户案例的开端。但是如果你已经有了一种好的架构,没有必要再去修改。 假设我们在创建一个显示用户简介的UI。用户信息取自我们自己的私有的后端REST API。 创建用户界面 UI由UserProfileFragment.java以及相应的布局文件user_profile_layout.xml组成。 要驱动UI,我们的data model需要持有两个数据元素。 User ID: 用户的身份识别。最好使用fragment argument来传递这个数据。如果OS杀死了你的进程,这个数据可以被保存下来,所以app再次启动的时候id仍是可用的。 User object: 一个持有用户信息数据的POJO对象。 我们将创建一个继承ViewModel类的UserProfileViewModel来保存这一信息。 一个ViewModel为特定的UI组件提供数据,比如fragment 或者 activity,并负责和数据处理的业务逻辑部分通信,比如调用其它组件加载数据或者转发用户的修改。ViewModel并不知道View的存在,也不会被configuration change影响。 现在我们有了三个文件。 user_profile.xml: 定义页面的UI UserProfileViewModel.java: 为UI准备数据的类 UserProfileFragment.java: 显示ViewModel中的数据与响应用户交互的控制器 下面我们开始实现(为简单起见,省略了布局文件): publicclassUserProfileViewModelextendsViewModel{ privateStringuserId; privateUseruser; publicvoidinit(StringuserId){ this.userId=userId; } publicUsergetUser(){ returnuser; } } publicclassUserProfileFragmentextendsLifecycleFragment{ privatestaticfinalStringUID_KEY="uid"; privateUserProfileViewModelviewModel; @Override publicvoidonActivityCreated(@NullableBundlesavedInstanceState){ super.onActivityCreated(savedInstanceState); StringuserId=getArguments().getString(UID_KEY); viewModel=ViewModelProviders.of(this).get(UserProfileViewModel.class); viewModel.init(userId); } @Override publicViewonCreateView(LayoutInflaterinflater, @NullableViewGroupcontainer,@NullableBundlesavedInstanceState){ returninflater.inflate(R.layout.user_profile,container,false); } } 注:上面的例子中继承的是LifecycleFragment而不是Fragment类。等Architecture Component中的lifecycles API稳定之后,Android Support Library中的Fragment类也将实现LifecycleOwner。 现在我们有了这些代码模块,如何连接它们呢?毕竟当ViewModel的user成员设置之后,我们还需要把它显示到界面上。这就要用到LiveData了。 LiveData是一个可观察的数据持有者。 无需明确在它与app组件之间创建依赖就可以观察LiveData对象的变化。LiveData还考虑了app组件(activities, fragments, services)的生命周期状态,做了防止对象泄漏的事情。 注:如果你已经在使用RxJava或者Agera这样的库,你可以继续使用它们,而不使用LiveData。但是使用它们的时候要确保正确的处理生命周期的问题,与之相关的LifecycleOwner stopped的时候数据流要停止,LifecycleOwner destroyed的时候数据流也要销毁。你也可以使用android.arch.lifecycle:reactivestreams让LiveData和其它的响应式数据流库一起使用(比如, RxJava2)。 现在我们把UserProfileViewModel中的User成员替换成LiveData,这样当数据发生变化的时候fragment就会接到通知。LiveData的妙处在于它是有生命周期意识的,当它不再被需要的时候会自动清理引用。 publicclassUserProfileViewModelextendsViewModel{ ... privateUseruser; privateLiveData<User>user; publicLiveData<User>getUser(){ returnuser; } } 现在我们修改UserProfileFragment,让它观察数据并更新UI。 @Override publicvoidonActivityCreated(@NullableBundlesavedInstanceState){ super.onActivityCreated(savedInstanceState); viewModel.getUser().observe(this,user->{ //updateUI }); } 每当User数据更新的时候 onChanged 回调将被触发,然后刷新UI。 如果你熟悉其它library的observable callback的用法,你会意识到我们不需要重写fragment的onStop()方法停止对数据的观察。因为LiveData是有生命周期意识的,也就是说除非fragment处于活动状态,否则callback不会触发。LiveData还可以在fragmentonDestroy()的时候自动移除observer。 对我们也没有做任何特殊的操作来处理 configuration changes(比如旋转屏幕)。ViewModel可以在configuration change的时候自动保存下来,一旦新的fragment进入生命周期,它将收到相同的ViewModel实例,并且携带当前数据的callback将立即被调用。这就是为什么ViewModel不应该直接引用任何View,它们游离在View的生命周期之外。参见ViewModel的生命周期。 获取数据 现在我们把ViewModel和fragment联系了起来,但是ViewModel该如何获取数据呢?在我们的例子中,假设后端提供一个REST API,我们使用Retrofit从后端提取数据。你也可以使用任何其它的library来达到相同的目的。 下面是和后端交互的retrofit Webservice: publicinterfaceWebservice{ /** *@GETdeclaresanHTTPGETrequest *@Path("user")annotationontheuserIdparametermarksitasa *replacementforthe{user}placeholderinthe@GETpath */ @GET("/users/{user}") Call<User>getUser(@Path("user")StringuserId); } ViewModel的一个简单的实现方式是直接调用Webservice获取数据,然后把它赋值给User对象。虽然这样可行,但是随着app的增大会变得难以维护。ViewModel的职责过多也违背了前面提到的关注点分离(separation of concerns)原则。另外,ViewModel的有效时间是和Activity和Fragment的生命周期绑定的,因此当它的生命周期结束便丢失所有数据是一种不好的用户体验。相反,我们的ViewModel将把这个工作代理给Repository模块。 Repository模块负责处理数据方面的操作。它们为app提供一个简洁的API。它们知道从哪里得到数据以及数据更新的时候调用什么API。你可以把它们看成是不同数据源(persistent model, web service, cache, 等等)之间的媒介。 下面的UserRepository类使用了WebService来获取用户数据。 publicclassUserRepository{ privateWebservicewebservice; //... publicLiveData<User>getUser(intuserId){ //Thisisnotanoptimalimplementation,we'llfixitbelow finalMutableLiveData<User>data=newMutableLiveData<>(); webservice.getUser(userId).enqueue(newCallback<User>(){ @Override publicvoidonResponse(Call<User>call,Response<User>response){ //errorcaseisleftoutforbrevity data.setValue(response.body()); } }); returndata; } } 虽然repository模块看起来没什么必要,但它其实演扮演着重要的角色;它把数据源从app中抽象出来。现在我们的ViewModel并不知道数据是由Webservice提供的,意味着有必要的话可以替换成其它的实现方式。 注:为简单起见我们省略了网络错误出现的情况。实现了暴露网络错误和加载状态的版本见下面的Addendum: exposing network status。 管理不同组件间的依赖: 前面的UserRepository类需要Webservice的实例才能完成它的工作。可以直接创建它就是了,但是为此我们还需要知道Webservice所依赖的东西才能构建它。这显著的增减了代码的复杂度和偶合度(比如,每个需要Webservice实例的类都需要知道如何用它的依赖去构建它)。另外,UserRepository很可能不是唯一需要Webservice的类。如果每个类都创建一个新的WebService,就变得很重了。 有两种模式可以解决这个问题: 依赖注入: 依赖注入允许类在无需构造依赖的情况下定义自己的依赖对象。在运行时由另一个类来负责提供这些依赖。在Android app中我们推荐使用谷歌的Dagger 2来实现依赖注入。Dagger 2 通过遍历依赖树自动构建对象,并提供编译时的依赖。 Service Locator:Service Locator 提供一个registry,类可以从这里得到它们的依赖而不是构建它们。相对依赖注入来说要简单些,所以如果你对依赖注入不熟悉,可以使用 Service Locator 。 这些模式允许你扩展自己的代码,因为它们提供了清晰的模式来管理依赖,而不是不断的重复代码。两者均支持替换成mock依赖来测试,这也是使用它们主要优势之一。 在这个例子中,我们将使用 Dagger 2 来管理依赖。 连接ViewModel和repository 现在我们修改UserProfileViewModel以使用repository。 publicclassUserProfileViewModelextendsViewModel{ privateLiveData<User>user; privateUserRepositoryuserRepo; @Inject//UserRepositoryparameterisprovidedbyDagger2 publicUserProfileViewModel(UserRepositoryuserRepo){ this.userRepo=userRepo; } publicvoidinit(StringuserId){ if(this.user!=null){ //ViewModeliscreatedperFragmentso //weknowtheuserIdwon'tchange return; } user=userRepo.getUser(userId); } publicLiveData<User>getUser(){ returnthis.user; } } 本文作者:佚名 来源:51CTO

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

Apache Storm 官方文档 —— Ack 框架的实现

Storm 的acker使用哈希校验和来跟踪每个 tuple 树的完成情况:每个 tuple 在被发送出的时候,它的值会与校验和进行异或运算,然后在 tuple 被 ack 的时候这个值又会再次与校验和进行异或运算。这样,一旦所有的 tuple 都被成功 ack,校验和就会变为 0(随机生成的校验和为 0 的概率极小,可以忽略不计)。 你可以在wiki中了解更多关于可靠性机制的信息。 ackerexecute() Acker 实际上也是一个 bolt,它的execute 方法是定义在mk-acker-bolt中的。在一个新的 tuple 树生成的时候,spout 为每个 tuple 发送一个用于异或的固有 id,acker 会将这些 id 记录在它的挂起队列中。每次 executor ack 一个 tuple 的时候,acker 会接收到一个部分校验和,这个校验和是 tuple 自身的 id(将其从挂起队列中清除)和 executor 发送的每个下游 tuple 的 id(放入挂起队列中)的异或值。 这个过程是这样的: 在接收到 tick tuple 信号的时候,将 tuple 树的校验值向超时方向移动并且返回。同时,在 tuple 树中更新或者创建一个记录。 初始化阶段:使用指定的校验和值进行初始化,并且记录 spout 的 id; ack 阶段:将部分校验和与当前的校验和进行异或运算; fail 阶段:仅仅将 tuple 标记为 failed 状态。 接下来,将记录存入RotatingMap(重新设置超时计数值)并且继续以下过程: 如果总校验和为 0, 表明 tuple 树已经完成:将记录从挂起队列中移除,并通知 spout 处理成功; 如果 tuple 树失败了,也会有一种完成状态:将记录从挂起队列中移除,并通知 spout 处理失败。 最后,发送一个我们自己的 ack 信号。 挂起 tuples 与RotatingMap Acker 将挂起树存放在一个RotatingMap中。RotatingMap是一个在 Storm 中多处使用的简单工具,它主要用于高效地处理过程的超时。 RotatingMap 与 HashMap 类似,支持 O(1) 时间的 get 操作。 在 RotatingMap 内部有多个 HashMap(称为槽,buckets),每个 HashMap 都保存有一群会在同一时间超时的记录。我们称存在时间最长的 bucket 为死亡牢房(death row),而访问最多的 bucket 称为苗圃(nursery)。一个新的值在被.put()到 RotatingMap 中,它都会被重定位到 nursery 中,并且从其他的它之前可能在的 bucket 中移除(这是一种高效的重新设置延时时间的方法)。 在 RotatingMap 的所有者调用.rotate()方法的时候,RotatingMap 会将每个 bucket 向着超时的方向移动一步(一般 Storm 对象会在收到一个系统 tick 流 tuple 的时候调用 rotate 方法)。如果此时在前面所说的 death row bucket 中有 key-value 键值对,RotatingMap 会为每个 key-value 键值对触发一个回调函数(在构造器中定义的),让他们的所有者选择一个合适的操作(例如,将 tuple 标记为处理 转载自并发编程网 - ifeve.com 失败)。

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

Apache Storm 官方文档 —— 分布式 RPC

分布式 RPC(DRPC)的设计目标是充分利用 Storm 的计算能力实现高密度的并行实时计算。Storm 接收若干个函数参数作为输入流,然后通过 DRPC 输出这些函数调用的结果。严格来说,DRPC 并不能算作是 Storm 的一个特性,因为它只是一种基于 Storm 原语 (Stream、Spout、Bolt、Topology) 实现的计算模式。虽然可以将 DRPC 从 Storm 中打包出来作为一个独立的库,但是与 Storm 集成在一起显然更有用。 概述 DRPC 是通过一个 DRPC 服务端(DRPC server)来实现分布式 RPC 功能的。DRPC server 负责接收 RPC 请求,并将该请求发送到 Storm 中运行的 Topology,等待接收 Topology 发送的处理结果,并将该结果返回给发送请求的客户端。因此,从客户端的角度来说,DPRC 与普通的 RPC 调用并没有什么区别。例如,以下是一个使用参数 “http://twitter.com”调用 “reach” 函数计算结果的例子: DRPCClient client = new DRPCClient("drpc-host", 3772); String result = client.execute("reach", "http://twitter.com"); 下图是 DRPC 的原理示意图。 客户端通过向 DRPC 服务器发送待执行函数的名称以及该函数的参数来获取处理结果。实现该函数的拓扑使用一个DRPCSpout从 DRPC 服务器中接收一个函数调用流。DRPC 服务器会为每个函数调用都标记了一个唯一的 id。随后拓扑会执行函数来计算结果,并在拓扑的最后使用一个名为ReturnResults的 bolt 连接到 DRPC 服务器,根据函数调用的 id 来将函数调用的结果返回。 定义 DRPC 拓扑 可以直接使用普通的拓扑构造方法来构造 DRPC 拓扑,如下所示: public static class ExclaimBolt extends BaseBasicBolt { public void execute(Tuple tuple, BasicOutputCollector collector) { String input = tuple.getString(1); collector.emit(new Values(tuple.getValue(0), input + "!")); } public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("id", "result")); } } public static void main(String[] args) throws Exception { TopologyBuilder builder = new TopologyBuilder(); // builder.setSpout(drpcSpout); // builder.setBolt(new ExclaimBolt(), 3); // submit(builder.createTopology()); } 本地模式 DRPC DRPC 可以在本地模式下运行。以下是使用本地模式构造拓扑的例子: LocalDRPC drpc = new LocalDRPC(); DRPCSpout spout = new DRPCSpout("exclamation", drpc); builder.setSpout("drpc", spout); builder.setBolt("exclaim", new ExclaimBolt(), 3) .shuffleGrouping("drpc"); builder.setBolt("return", new ReturnResults(), 3) .shuffleGrouping("exclaim"); LocalCluster cluster = new LocalCluster(); Config conf = new Config(); cluster.submitTopology("drpc-demo", conf, builder.createTopology()); // local mode 测试代码 System.out.println(drpc.execute("exclamation", "hello")); cluster.shutdown(); drpc.shutdown(); 在这种模式下,首先你会创建一个LocalDPRC对象,该对象会在进程中模拟一个 DRPC 服务器,其作用类似于LocalCluster在进程中模拟 Storm 集群的功能。在定义好拓扑的各个组件之后,就可以使用LocalCluster来提交拓扑。在本地模式下LocalDPRC对象不会绑定到任何一个实际的端口,所以需要通过向DRPCSpout传入参数的方式来关联到拓扑中。 在启动拓扑后,你可以使用execute方法来完成 DRPC 调用。 远程模式 DRPC 在一个实际的集群中使用 DRPC 有以下三个步骤: 配置并启动 DRPC 服务器; 在集群的各个服务器上配置 DRPC 服务器的地址; 将 DRPC 拓扑提交到集群运行。 可以像 Nimbus、Supervisor 那样使用storm命令来启动 DRPC 服务器(注意,此 server 的基本配置,如 nimbus,ZooKeeper 等参数应该与 Storm 集群其他机器相同): bin/storm drpc 接下来,你需要在集群的各个服务器上配置 DRPC 服务器的地址。这是为了让DRPCSpout了解从哪里获取函数调用的方法。可以通过编辑storm.yaml或者添加拓扑配置的方式实现配置。配置storm.yaml的方式类似于下面这样: drpc.servers: - "drpc1.foo.com" - "drpc2.foo.com" 最后,你可以像其他拓扑一样使用StormSubmitter来启动拓扑。 以下是使用远程模式构造拓扑的一个例子: TopologyBuilder builder = new TopologyBuilder(); DRPCSpout spout = new DRPCSpout("exclamation"); builder.setSpout("drpc", spout, 3); builder.setBolt("exclaim", new ExclamationBolt(), 3) .shuffleGrouping("drpc"); builder.setBolt("return", new ReturnResults(), 7) .shuffleGrouping("exclaim"); Config conf = new Config(); conf.setNumWorkers(2); StormSubmitter.submitTopology("drpc-demo", conf, builder.createTopology()); 更复杂的例子 请参考Trident 教程一文中计算指定 URL 的 Reach 数的例子。 转载自并发编程网 - ifeve.com

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

Apache Storm 官方文档 —— 命令行操作

本文介绍了 Storm 命令行客户端中的所有命令操作。如果想要了解怎样设置你的 Strom 客户端和远程集群的交互,请按照配置开发环境一文中的步骤操作。 Storm 中支持的命令包括: jar kill activate deactivate rebalance repl classpath localconfvalue remoteconfvalue nimbus supervisor ui drpc jar 语法:storm jar topology-jar-path class ... 使用指定的参数运行 main 方法(也就是打包好的拓扑 jar 包中的 main 方法)。Storm 所需要的 jar 包和配置信息都在类路径(classpath)中。这个运行过程已经配置好了,这样StormSubmitter就可以在提交拓扑的时候将topology-jar-path中的 jar 包上传到集群中。 kill 语法:storm kill topology-name [-w wait-time-secs] 杀死集群中正在运行的名为topology-name的拓扑。执行该操作后,Storm 首先会注销拓扑中的 spout,使得拓扑中的消息超时,这样当前的所有消息就会结束执行。随后,Storm 会将所有的 worker 关闭,并清除他们的状态。你可以使用-w参数来调整 Storm 在注销与关闭拓扑之间的间隔时间。 activate 语法:storm activate topology-name 激活运行指定拓扑的所有 spout。 deactivate 语法:storm deactivate topology-name 停止指定拓扑的所有 spout 的运行。 rebalance 语法:storm rebalance topology-name [-w wait-time-secs] 有些场景下需要对正在运行的拓扑的工作进程(worker)进行弹性扩展。例如,加入你有 10 个节点,每个节点上运行有 4 个 worker,现在由于各种原因你需要为集群添加 10 个新节点。这时你就会希望通过扩展正在运行的拓扑的 worker 来使得每个节点只运行两个 worker,降低集群的负载。实现这个目的的一种直接的办法是 kill 掉正在运行的拓扑,然后重新向集群提交。不过 Storm 提供了再平衡命令可以以一种更简单的方法实现目的。 再平衡首先会在一个超时时间内(这个时间是可以通过-w参数配置的)注销掉拓扑,然后在整个集群中重新分配 worker。接着拓扑就会自动回到之前的状态(也就是说之前处于注销状态的拓扑仍然会保持注销状态,而处于激活状态的拓扑则会返回激活状态)。 repl 语法:storm repl 打开一个带有类路径上的 jar 包和配置信息的 Clojure 的交互式解释器(REPL)。该命令主要用于调试。 classpath 语法:storm classpath 打印客户端执行命令时使用的类路径环境变量。 localconfvalue 语法:storm localconfvalue conf-name 打印出本地 Storm 配置中conf-name属性的值。这里的本地配置指的是~/.storm/storm.yaml和defaults.yaml两个配置文件综合后的配置信息。 remoteconfvalue 语法:storm remoteconfvalue conf-name 打印出集群配置中conf-name属性的值。这里的集群配置指的是$STORM-PATH/conf/storm.yaml和defaults.yaml两个配置文件综合后的配置信息。该命令必须在一个集群机器上执行。 nimbus 语法:storm nimbus 启动 nimbus 后台进程。该命令应该在daemontools或者monit这样的工具监控下执行。详细信息请参考配置 Storm 集群一文。 supervisor 语法:storm supervisor 启动 supervisor 后台进程。该命令应该在daemontools或者monit这样的工具监控下执行。详细信息请参考配置 Storm 集群一文。 ui 语法:storm ui 启动 UI 后台进程。UI 提供了一个访问 Storm 集群的 Web 接口,其中包含有运行中的拓扑的详细信息。该命令应该在daemontools或者monit这样的工具监控下执行。详细信息请参考配置 Storm 集群一文。 drpc 语法:storm drpc 启动 DRPC 后台进程。该命令应该在daemontools或者monit这样的工具监控下执行。详细信息请参考分布式 RPC一文。 转载自并发编程网 - ifeve.com

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

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文件系统,支持十年生命周期更新。