<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>HongJack (HongJack)</title>
    <link>https://ruby-china.org/HongJack</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>个推数据统计产品 (个数) iOS 集成实践</title>
      <description>&lt;p&gt;最近业务方给我们部门提了新的需求，希望能一站式统计 APP 的几项重要数据。这次我们尝试使用的是个推（之前专门做消息推送的）旗下新推出的产品“个数·应用统计”，根据官方的说法，个推的数据统计产品通过专业的移动应用数据分析，可以为用户的应用提供实时数据统计分析服务，包括了解版本质量、渠道状况、用户画像等。数据最后以可视化形式展现，很直观。我们尝试了一段时间，发现效果还是很不错的，这篇文章将为大家介绍如何从零开始快速高效地集成个数 iOS SDK。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;一、登录账号并创建应用获取 APP ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、访问&lt;a href="https://dev.getui.com/dev/#/login" rel="nofollow" target="_blank" title=""&gt;个推开发者中心&lt;/a&gt;，点击立即注册：
（也可以从个推官网进入 www.getui.com，点击右上角“开发者中心”。）当然，如果你已经注册过个推推送的账号，直接登录使用即可。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/532df5b4-c24f-4259-b902-9d48d1ab9b05.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、根据个人情况填写并注册账号：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/15b3d8d6-fd42-4738-92d7-4b2d8c4d9a0e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;3、返回&lt;a href="https://dev.getui.com/dev/#/login" rel="nofollow" target="_blank" title=""&gt;个推开发者中心&lt;/a&gt;使用上个步骤注册好的账号登录，进入开发者平台面板后，选择左边菜单栏的个数·应用统计：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/d4c564f0-2986-48b9-af98-3a5b3ff18b59.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;4、进入个数·应用统计面板后，选择右上角的新增应用添加新的应用：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/19ca789d-db07-48cb-9246-d75cbedf5d83.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;5、填写应用相关信息，勾选 iOS，点击提交新增应用：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/bee345cb-1844-472e-8c65-ec0c475d0d3d.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;6、新增应用成功会自动返回应用列表，找到新增的应用，点击应用配置：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/612d7174-7099-4cfc-a951-2a4b150d363e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;7、在配置信息下可以看到 APP ID，将其复制保存，后续集成将会使用到 APP ID：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/c87d005a-bee4-4599-bb43-71b1589c0f57.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;二、配置个数 SDK&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;个数 iOS SDK 提供两种集成方式，分别是 CocoaPods 集成和 Xcode 手动集成，两种集成方式本文都会介绍。在集成 SDK 时选择其中一种即可（推荐使用 CocoaPods 集成）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;三、CocoaPods 集成方式&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、安装 CocoaPods&lt;/p&gt;

&lt;p&gt;安装方式很简单 , Mac 下都自带 ruby，使用 ruby 的 gem 命令即可下载安装：&lt;/p&gt;

&lt;p&gt;$ sudo gem install cocoapods
$ pod setup&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/db40721d-f512-4e89-aeea-1b620bd599dc.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、准备 Podfile&lt;/p&gt;

&lt;p&gt;使用时需要新建一个名为 Podfile 的文件（若已存在该文件则不需要重新创建），如下格式，将依赖的库名字依次列在文件中即可：&lt;/p&gt;

&lt;p&gt;target 'YourTargetName' do
    platform :ios, "8.0"
    pod 'GCSDK'
end&lt;/p&gt;

&lt;p&gt;3、完成 GCSDK 导入&lt;/p&gt;

&lt;p&gt;将编辑好的 Podfile 文件放到你的项目根目录中，执行如下命令即可：&lt;/p&gt;

&lt;p&gt;$ cd ""
$ pod install&lt;/p&gt;

&lt;p&gt;4、使用 CocosPods 集成 SDK 后，需要关闭原工程，重新在项目根目录下打开 yourProjectName.xcworkspace 的文件进行后续开发：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;四、Xcode 集成方式&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、个数应用统计提供了一个 SDK 开发工具包，包含了 iOS SDK 的全部所需资源，前往个推文档中心下载，地址：&lt;a href="http://docs.getui.com/download.htmliOS%E7%AB%AF%E4%B8%8B%E8%BD%BDSDK%E8%B5%84%E6%BA%90%E5%8C%85" rel="nofollow" target="_blank"&gt;http://docs.getui.com/download.htmliOS端下载SDK资源包&lt;/a&gt;，选择个数·应用统计下的：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/7108934d-ae9e-44fa-ab92-8567dfa14343.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、解压资源包内容可以看到如下文件结构：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/419999d3-95a0-4bd3-92f0-a82cb1324683.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;3、资源包内容详解&lt;/p&gt;

&lt;p&gt;接入文档/个数集成文档.pdf : 个数集成文档;&lt;/p&gt;

&lt;p&gt;资源文件/GTCountSDK.h: 个数 SDK 头文件&lt;/p&gt;

&lt;p&gt;资源文件/libGTCountSDK.a: 个数 SDK 主包静态库&lt;/p&gt;

&lt;p&gt;资源文件/libGTCommonSDK.a: 个数 SDK 工具库&lt;/p&gt;

&lt;p&gt;Demo 工程/GTCountDemo/: 个数 demo 工程&lt;/p&gt;

&lt;p&gt;4、注意：libGTCountSDK.a、libGTCommonSDK.a 使用 lipo 工具将
支持 i386、x86_64、arm64、armv7 的代码打包到了一起，所以这个库将同时支持 simulator 和 device，支持的 iOS 版本为 7.0 及以上。&lt;/p&gt;

&lt;p&gt;5、个数 SDK 静态库设置&lt;/p&gt;

&lt;p&gt;右击添加文件，导入资源文件目录下的文件。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/a98a70f6-09dc-4b6c-949b-37f9531b8f60.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/ea53e4eb-793d-4788-a5ce-88ecfcbbb6ee.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;6、添加依赖库（必须，如下图）&lt;/p&gt;

&lt;p&gt;添加系统库支持：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;libsqlit3.tbd&lt;/li&gt;
&lt;li&gt;libz.tbd&lt;/li&gt;
&lt;li&gt;AdSupport.framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/0242560b-5631-4434-ae0f-8a64f8895fbd.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;7、设置 Other Linker Flags&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/3471ef79-6fc6-48c6-9a87-382c5a1a47cc.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;找到主工程的 target －&amp;gt; Build Setting －&amp;gt; Linking －&amp;gt; Other Linker Flags，将其设置为-ObjC（如图所示）：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;五、初始化并启动 SDK&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、初始化启动接口的相关信息：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/7a09866e-691f-4988-b88d-93e5f72c18b6.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、在项目工程的 AppDelegate.m 中添加头文件，使用前面获取的 APP ID 初始化并启动 SDK：&lt;/p&gt;

&lt;p&gt;#import 'GTCountSDK.h'
#define kGcAppId @"xxxxxxx"&lt;/p&gt;

&lt;p&gt;&lt;a href="/implementation" class="user-mention" title="@implementation"&gt;&lt;i&gt;@&lt;/i&gt;implementation&lt;/a&gt; AppDelegate&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 启动个数 SDK
[GTCountSDK startSDKWithAppId:kGcAppId withChannelId:@"appstore"];
// 使用 SDK 实例的 reportStrategy 属性设置上报策略。
[[GTCountSDK sharedInstance] setReportStrategy:GESHU_STRATEGY_WIFI_ONLY]
// 使用 SDK 实例的 sessionTime 属性获取 sessionTime 的值。
NSLog(@"sessionTime %ld",[[GTCountSDK sharedInstance]sessionTime]);
return YES;
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;六、高级功能：自定义事件&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;自定义事件可以统计某些用户自定义埋点的发生时间以及次数，例如广告点击、短信数量等。通常 event_id 用于表示某种行为或功能的统计（如统计“发送”按钮被触发多少次），而参数则用于标识统计的具体对象（如功能为“下载”的按钮），由 event_id 和 properties 唯一标识一个事件。&lt;/p&gt;

&lt;p&gt;自定义事件主要分为两种：&lt;/p&gt;

&lt;p&gt;（1）次数统计：统计指定行为被触发的次数。&lt;/p&gt;

&lt;p&gt;（2）时长统计：统计指定行为消耗的时间，单位为秒。需要 eventBegin 和 eventEnd 接口成对使用才可生效。&lt;/p&gt;

&lt;p&gt;其中每类事件都支持使用 properties 参数类型。&lt;/p&gt;

&lt;p&gt;注意：event_id 需要先在个推开发者中心（&lt;a href="https://dev.getui.com/event_id%E4%B8%8D%E8%83%BD%E5%8C%85%E5%90%AB%E7%A9%BA%E6%A0%BC%E6%88%96%E8%BD%AC%E4%B9%89%E5%AD%97%E7%AC%A6%EF%BC%8C%E5%A6%82%E4%B8%8B%EF%BC%9A" rel="nofollow" target="_blank"&gt;https://dev.getui.com/event_id不能包含空格或转义字符，如下：&lt;/a&gt;）进行配置，才能参与正常的数据统计。&lt;/p&gt;

&lt;p&gt;1、点击侧边菜单栏事件列表。
&lt;img src="https://l.ruby-china.com/photo/2018/e00c0117-7e07-46cd-a1db-34142f74068b.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、点击新增事件按钮。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/d6b4767d-6e55-4167-bb56-26b619597207.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;3、根据事件类型分别输入相应的事件 ID 以及事件名称。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/853e4770-6b67-4715-a190-67adf5e745a7.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;4、在移动端项目中添加对应事件类型和对应 event_id 的事件：&lt;/p&gt;

&lt;p&gt;&lt;a href="/implementation" class="user-mention" title="@implementation"&gt;&lt;i&gt;@&lt;/i&gt;implementation&lt;/a&gt; TrackCountEventController&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(IBAction)clickCount:(id)sender {
[GTCountSDK trackCountEvent:@"countid1" withArgs:@{@"ckey1":@"cvalue1"}];
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-(void) viewDidAppear:(BOOL)animated {
    // 为了正确统计，要确保开始和结束接口的参数 self.eventProperty 内存地址是一致的。
    self.eventProperty = @{@"key":@"value1"};
       [GTCountSDK trackCustomKeyValueEventBegin:@"eid1" withArgs:self.eventProperty];&lt;br&gt;
        [super viewDidAppear:animated];
}&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(void)viewWillDisappear:(BOOL)animated {
[GTCountSDK trackCustomKeyValueEventEnd:@"eid1" withArgs:self.eventProperty];
[super viewWillDisappear:animated];
}
&lt;a href="/end" class="user-mention" title="@end"&gt;&lt;i&gt;@&lt;/i&gt;end&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;七、高级功能：数据上报策略&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;个数应用统计可自定义数据上报策略，开发者可根据自身应用需求设置，能够有效控制用户流量开销。&lt;/p&gt;

&lt;p&gt;1、SDK 的数据上报策略包括以下 5 种（默认为 GESHU_STRATEGY_PERIOD，周期为 60 分钟）：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/9c7e895b-34e5-4bd8-8f93-08efcece9355.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、注意：数据上报策略建议在应用启动时设置。数据上报策略使用以下接口进行设置和查看：&lt;/p&gt;

&lt;p&gt;/**
 设置上报策略
 */
&lt;a href="/property" class="user-mention" title="@property"&gt;&lt;i&gt;@&lt;/i&gt;property&lt;/a&gt;(nonatomic,assign)GeShuStatReportStrategyType reportStrategy;&lt;/p&gt;

&lt;p&gt;3、WIFI 环境下上报策略&lt;/p&gt;

&lt;p&gt;考虑到 WIFI 网络环境下上报数据的代价较小，因此默认情况在 WIFI 环境下，使用实时上报策略。若要关闭该策略，可以调用以下接口关闭：&lt;/p&gt;

&lt;p&gt;/**
 智能上报
 开启以后设备接入 WIFI 会实时上报
 否则按照全局策略上报
 默认打开
 */
&lt;a href="/property" class="user-mention" title="@property"&gt;&lt;i&gt;@&lt;/i&gt;property&lt;/a&gt; (nonatomic, assign)BOOL smartReporting;&lt;/p&gt;

&lt;p&gt;4、数据上报策略相关接口&lt;/p&gt;

&lt;p&gt;/**
 统计上报策略为 BATCH 时，触发上报时最小缓存消息数，默认 32 条
 */
&lt;a href="/property" class="user-mention" title="@property"&gt;&lt;i&gt;@&lt;/i&gt;property&lt;/a&gt; (nonatomic, assign)NSUInteger minBatchReportNumber;&lt;/p&gt;

&lt;p&gt;/**
 上报策略为 PERIOD 时发送间隔，单位分钟，默认一天（60 分钟）
 */
&lt;a href="/property" class="user-mention" title="@property"&gt;&lt;i&gt;@&lt;/i&gt;property&lt;/a&gt; (nonatomic, assign)NSUInteger periodMinutes;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;八、集成 SDK 的应用提交 App Store 注意事项&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、为了获取精准的统计结果，需添加 AdSupport.framework 库支持，因此在提交 App Store 时需做以下操作：&lt;/p&gt;

&lt;p&gt;(1) 在 App 内投放广告，获取 IDFA 可通过苹果审核。&lt;/p&gt;

&lt;p&gt;(2)App 内无广告，但先前投放了特定广告，可参考如下勾选，通过苹果审核。     &lt;/p&gt;

&lt;p&gt;勾选如图：
&lt;img src="https://l.ruby-china.com/photo/2018/15782798-02bc-4dbf-a7ea-78fb15f0f9b5.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;以上就是我集成个推应用统计产品（个数）的全过程，希望对你有帮助！如果大家有什么其他问题，我们可以留言区交流。&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Fri, 27 Jul 2018 09:54:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/37232</link>
      <guid>https://ruby-china.org/topics/37232</guid>
    </item>
    <item>
      <title>大数据科学新发展展望：不得不知的四大趋势</title>
      <description>&lt;p&gt;从 2012 年开始，几乎人人（至少是互联网界）言必称大数据，似乎不和大数据沾点边都不好意思和别人聊天。从 2016 年开始，大数据系统逐步开始在企业中进入部署阶段，大数据的炒作逐渐散去，随之而来的是应用的蓬勃发展期，一些代表成熟技术的标志性 IPO 在国内外资本市场也不断出现。转眼间，大数据几年前经历的泡沫正在无可争议地转移到人工智能身上。可以说，在过去的一年，AI 所经历的共同意识“大爆炸”与当年的大数据相比，有过之而无不及。最近风口又转移到区块链上了，某种程度上也成为业内人士焦虑的一种诱因了。&lt;/p&gt;

&lt;p&gt;但无论技术热点如何变换，我们能看到的是，随着行业沉下心来进行实质的落地，大数据生态也越来越细分。今天就我和大家来谈谈大数据领域的一些新变化、新趋势。&lt;/p&gt;

&lt;p&gt;一、数据治理与安全 Data Governance&amp;amp; Security
就发展趋势而言，这个可以放在第一位来讲讲。&lt;/p&gt;

&lt;p&gt;多年来，数据已经在企业中不断快速积累。物联网 (IoT) 更是不断加速数据的生成。&lt;/p&gt;

&lt;p&gt;对于许多企业来说，大数据的解决方案就是利用类似于开源的 Apache Hadoop 等技术作为基础支持，创建数据湖 (Data Lake)，即创建整个企业的数据管理平台，用于以本机格式存储企业的所有数据。数据湖将通过提供一个单一的数据存储库来消除信息孤岛，整个组织都可以使用该存储库来进行业务分析、数据挖掘等各种应用。当有了数据湖之后，大家会倾向于认为这东西将会成为一个全方位和万能的大数据集，例如点击流数据、物联网数据、日志数据等都会被要求进入这个湖中，而这些数据很难处理的问题却会被忽略。&lt;/p&gt;

&lt;p&gt;但是，除非你知道数据湖里具体有什么，并且能够访问到合适的数据进行分析，否则数据湖再大也没有意义。因此，最后大家都会意识到许多数据湖是表现不佳的资源，人们不知道其中存储着什么内容，如何进行访问，或者如何从这些数据中获取洞察力。&lt;/p&gt;

&lt;p&gt;但是，方便地找到想要的东西、同时管理好权限并不容易。除了数据湖以外，治理的另一个主题是以安全的、可审计的方式为任何人提供对可靠数据的便捷访问。&lt;/p&gt;

&lt;p&gt;所以，站在管理并使用好公司数据资产的角度而言，数据治理犹如公司的顶层制度和宣言一样需要被重视，并且用相应的策略、流程等来进行落实。最终目的是通过实现数据治理，来提升数据管理、确保数据质量、形成开放共享的新局面等。此外，数据治理也是决策、职能以及操作流程有机组合的系统，并且人们对这些数据资产承担责任。&lt;/p&gt;

&lt;p&gt;二、致力于协作的数据工作台发展
在大多数大型企业里，大数据的采用是从少数独立项目开始的，个推也是如此：譬如这里做一点 Hadoop 集群，那里用一用分析工具，跑一个简单业务模型，以及意识到需要设立一些新的职位（数据科学家、首席数据官）等等。&lt;/p&gt;

&lt;p&gt;现在，业务场景越来越丰富，异质性也越来越突出，各种各样的工具在整个企业范围内得到了使用。在公司的组织范围内，集中化的“数据科学部门”正在逐渐让位于更加去中心化的组织，原因在于集中化的部门越来越走向瓶颈，也更容易造成资源的流失。&lt;/p&gt;

&lt;p&gt;这个由数据科学家、数据工程师以及数据分析师组成的群体，正日益嵌入到不同的业务部门里。因此，对于平台来说需求已经很明显了，那就是要让一切都能协作到一起来，因为大数据的成功正是建立在设立一条由技术、人以及流程组成的装配线基础之上的。&lt;/p&gt;

&lt;p&gt;因此，一些全新的协作平台类型（譬如 Jupyter 等）正在加快出现，引领着所谓的 DataOps（与 DevOps 对应）领域的发展。  &lt;/p&gt;

&lt;p&gt;三、数据科学自动化
数据科学家 (Data Scientist) 依然是市场上炙手可热的争夺对象。但是我们在周围却很少见到这类人，哪怕是财富前 1000 强的公司也为无法招到更多“数据科学家”而感到困扰。而在一些组织里，数据科学部门正在从使能者演变为瓶颈。&lt;/p&gt;

&lt;p&gt;与此同时，AI 的大众化以及自服务工具的蔓延使得数据科学技能有限的数据工程师，甚至是数据分析师在执行一些基本操作时变得更加容易了，而这些操作直到最近仍然是数据科学家的领地。在自动化工具的帮助下，企业大量的大数据工作，尤其是那些简单枯燥的工作，将由数据工程师和数据分析师进行处理，而不必麻烦有着深厚技术技能的数据科学家。当然，即便如此，数据科学家目前还不需要太过“恐惧”。&lt;/p&gt;

&lt;p&gt;在可预见的未来里，自服务工具和自动化模型将会“增强”数据科学家而不是消灭他们，会解放他们，让他们把焦点放在需要判断、创造力、社会化技能或者需要垂直行业知识的任务上，那样才能更加体现科学家的名号。&lt;/p&gt;

&lt;p&gt;四、大数据管理员的崛起
大数据管理员 (BDA) 也对标于数据库管理员 (DBA)，虽然两个英文字母只是变换了一下顺序，但是其内涵相差甚远。一个非常明显的趋势是，企业将对一个新岗位角色产生需求，即大数据管理员。DBA 大家已经非常熟悉，但它与大数据时代下的数据管理员，有非常大的差别。&lt;/p&gt;

&lt;p&gt;数据管理员处于数据使用者和数据工程师之间。为了取得成功，数据管理员在进行大数据系统的维护工作之外，还必须了解数据的含义以及掌握应用于数据中的一些技术。&lt;/p&gt;

&lt;p&gt;数据管理员需要清楚整个组织内需要执行的数据分析类型，哪些数据集非常适用于这项工作，以及如何将数据从原始状态转换为数据使用者执行这项工作所需的形态和形式。数据管理员应使用像自助服务数据平台这样的系统来加快数据使用者访问基本数据集的端到端流程，而无需制作无数的数据副本。&lt;/p&gt;

&lt;p&gt;结语
以上四个方面是数据科学在实践发展中提出的新需求，谁能在这些方面得到好的成绩，谁便会在这个大数据时代取得领先的位置。&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Tue, 29 May 2018 14:34:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/36858</link>
      <guid>https://ruby-china.org/topics/36858</guid>
    </item>
    <item>
      <title>高并发大容量 NoSQL 解决方案探索</title>
      <description>&lt;p&gt;大数据时代，企业对于 DBA 也提出更高的需求。同时，NoSQL 作为近几年新崛起的一门技术，也受到越来越多的关注。本文将基于个推 SRA 孟显耀先生所负责的 DBA 工作，和大数据运维相关经验，分享两大方向内容：一、公司在 KV 存储上的架构演进以及运维需要解决的问题；二、对 NoSQL 如何选型以及未来发展的一些思考。&lt;/p&gt;

&lt;p&gt;据官方统计，截止目前（2018 年 4 月 20 日）NoSQL 有 225 个解决方案，具体到每个公司，使用的都是其中很小的一个子集，下图中蓝色标注的产品是当前个推正在使用的。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/1b073b72-0552-48f0-8936-12674a1cdc03.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NoSQL 的由来&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1946 年，第一台通用计算机诞生。但一直到 1970 年 RDMBS 的出现，大家才找到通用的数据存储方案。到 21 世纪，DT 时代让数据容量成为最棘手的问题，对此谷歌和亚马逊分别提出了自己的 NoSQL 解决方案，比如谷歌于 2006 年提出了 Bigtable。2009 年的一次技术大会上，NoSQL 一词被正式提出，到现在共有 225 种解决方案。&lt;/p&gt;

&lt;p&gt;NoSQL 与 RDMBS 的区别主要在两点：第一，它提供了无模式的灵活性，支持很灵活的模式变更；第二，可伸缩性，原生的 RDBMS 只适用于单机和小集群。而 NoSQL 一开始就是分布式的，解决了读写和容量扩展性问题。以上两点，也是 NoSQL 产生的根本原因。&lt;/p&gt;

&lt;p&gt;实现分布式主要有两种手段：副本（Replication）和分片（Sharding）。Replication 能解决读的扩展性问题和 HA（高可用），但是无法解决读和容量的扩展性。而 Sharding 可以解决读写和容量的扩展性。一般 NoSQL 解决方案都是将二者组合起来。&lt;/p&gt;

&lt;p&gt;Sharding 主要解决数据的划分问题，主要有基于区间划分（如 Hbase 的 Rowkey 划分）和基于哈希的划分。为了解决哈希分布式的单调性和平衡性问题，目前业内主要使用虚拟节点。后文所述的 Codis 也是用虚拟节点。虚拟节点相当于在数据分片和托管服务器之间建立了一层虚拟映射的关系。&lt;/p&gt;

&lt;p&gt;目前，大家主要根据数据模型和访问方式进行 NoSQL 分类。
&lt;img src="https://l.ruby-china.com/photo/2018/9cd152f6-cfa5-4268-982d-fa0abf9a5c13.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;个推常用的几种 NoSQL 解决方案&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;个推 Redis 系统规模如下图。下面介绍一下运维过程遇到的几个问题。
&lt;img src="https://l.ruby-china.com/photo/2018/a2271a70-1ecb-479e-ba61-414a928978c1.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;首先是技术架构演进过程。个推以面向 APP 开发者提供消息推送服务起家，在 2012 年之前，个推的业务量相对较小，当时我们用 Redis 做缓存，用 MySQL 做持久化。在 2012-2016 年，随着个推业务的高速发展，单节点已经无法解决问题。在 MySQL 无法解决高 QPS、TPS 的情况下，我们自研了 Redis 分片方案。此外，我们还自研了 Redis 客户端，用它来实现基本的集群功能，支持自定义读写比例，同时对故障节点的监测和隔离、慢监控以及每个节点健康性进行检查。但这种架构没有过多考虑运维效率的问题，缺少运维工具。&lt;/p&gt;

&lt;p&gt;当我们计划完善运维工具的时候，发现豌豆荚团队将 Codis 开源，给我们提供了一个不错的选项。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;个推 Codis+ 的优势&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Codis 是 proxy-based 架构，支持原生客户端，支持基于 web 的集群操作和监控，并且也集成了 Redis Sentinel。可以提高我们运维的工作效率，且 HA 也更容易落地。&lt;/p&gt;

&lt;p&gt;但是在使用过程中，我们也发现一些局限。因此我们提出了 Codis+，即对 Codis 做一些功能增强。&lt;/p&gt;

&lt;p&gt;第一、采用 2N+1 副本方案，解决故障期间 Master 单点的问题。&lt;/p&gt;

&lt;p&gt;第二、Redis 准半同步。设置一个阈值，比如 slave 仅在 5 秒钟之内可读。&lt;/p&gt;

&lt;p&gt;第三、资源池化。能通过类似 HBase 增加 RegionServer 的方式去进行资源扩容。&lt;/p&gt;

&lt;p&gt;此外，还有机架感知功能和跨 IDC 的功能。Redis 本身是为了单机房而设置的，没有考虑到这些问题。&lt;/p&gt;

&lt;p&gt;那么，为什么我们不用原生的 rRedis cluster？这里有三个原因：一、原生的集群，它把路由转发的功能和实际上的数据管理功能耦合在一个功能里，如果一个功能出问题就会导致数据有问题；二、在大集群时，P2P 的架构达到一致性状态的过程比较耗时，codis 是树型架构，不存在这个问题。三、集群没有经过大平台的背书。&lt;/p&gt;

&lt;p&gt;此外，关于 Redis，我们最近还在看一个新的 NoSQL 方案 Aerospike，我们对它的定位是替换部分集群 Redis。Redis 的问题在于数据常驻内存，成本很高。我们期望利用 Aerospike 减少 TCO 成本。Aerospike 有如下特性：&lt;/p&gt;

&lt;p&gt;一、Aerospike 数据可以放内存，也可以放 SSD，并对 SSD 做了优化。&lt;/p&gt;

&lt;p&gt;二、资源池化，运维成本继续降低。&lt;/p&gt;

&lt;p&gt;三、支持机架感知和跨 IDC 的同步，但这属于企业级版本功能。&lt;/p&gt;

&lt;p&gt;目前我们内部现在有两个业务在使用 Aerospike，实测下来，发现单台物理机搭载单块 Inter SSD 4600，可以达到接近 10w 的 QPS。对于容量较大，但 QPS 要求不高的业务，可以选择 Aerospike 方案节省 TCO。&lt;/p&gt;

&lt;p&gt;在 NoSQL 演进的过程中，我们也遇到一些运维方面的问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;标准化安装&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我们共分了三个部分：OS 标准化、Redis 文件和目录标准、Redis 参数标准化，全部用 saltstack + cmdb 实现；  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;扩容和缩容&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在技术架构不断演进过程中，扩容和缩容的难度也在变低，原因之一在于 codis 缓解了一部分问题。当然，如果选择 Aerospike，相关操作就会非常轻松。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;做好监控，降低运维成本&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;大部分的运维同学都应该认真阅读《SRE：Google 运维揭秘》，它在理论层面和实践层面提出了很多非常有价值的方法论，强烈推荐。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;个推 Redis 监控复杂性&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;三种集群架构：自研、codis2 和 codis3，这三种架构采集数据的方式并不相同。&lt;/p&gt;

&lt;p&gt;三类监控对象：集群、实例、主机，需要有元数据维护逻辑关系，并在全局做聚合。&lt;/p&gt;

&lt;p&gt;三种个性化配置：个推的 Redis 集群，有的集群需要有多副本，有的不需要。有的节点允许满做缓存，有的节点不允许满。还有持久化策略，有的不做持久化，有的做持久化，有的做持久化 + 异地备份，这些业务特点对我们监控灵活性提出很高的要求。&lt;/p&gt;

&lt;p&gt;Zabbix 是一个非常完备的监控系统，约三年多的时间里，我都把它作为主要的监控系统平台。但是它有两个缺陷：一是它使用 MySQL 作为后端存储，TPS 有上限；二是不够灵活。比如：一个集群放在一百台机器上，要做聚合指标，就很困难。&lt;/p&gt;

&lt;p&gt;小米的 open-falcon 解决了这个问题，但是也会产生一些新问题。比如告警函数很少，不支持字符串，有时候会增加手工的操作等等。后来我们对它进行功能性补充，便没有遇到大的问题。&lt;/p&gt;

&lt;p&gt;下图是个推运维平台。
&lt;img src="https://l.ruby-china.com/photo/2018/7a8645e0-0e51-45d8-b62a-2ee26909599e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;第一个是 IT 硬件资源平台，主要维护主机维度的物理信息。比如说主机在哪个机架上接的哪个交换机，在哪个机房的哪一个楼层等等，这是做机架感知和跨 IDC 等等的基础。&lt;/p&gt;

&lt;p&gt;第二个是 CMDB，这个是维护主机上的软件信息，主机上装了哪些实例，实例属于哪些集群，我们用了哪些端口，这些集群有什么个性化的参数配置，包括告警机制不一样，全是通过 CMDB 实现。CMDB 的数据消费方包含 grafana 监控系统和监控采集程序，采集程序由我们自己开发。这样 CMDB 数据会活起来。如果只是一个静态数据没有消费方，数据就会不一致。&lt;/p&gt;

&lt;p&gt;grafana 监控系统聚合了多个 IDC 数据，我们运维每天只需看一下大屏就够了。&lt;/p&gt;

&lt;p&gt;Slatstack，用于实现自动化发布，实现标准化并提高工作效率。&lt;/p&gt;

&lt;p&gt;采集程序是我们自行研发的，针对公司的业务特点定制化程度很高。还有 ELK（不用 logstach，用 filebeat）做日志中心。&lt;/p&gt;

&lt;p&gt;通过以上这些，我们搭建出个推整个监控体系。&lt;/p&gt;

&lt;p&gt;下面讲一下搭建过程中遇到的几个坑。&lt;/p&gt;

&lt;p&gt;一、主从重置，会导致主机节点压力爆增，主节点无法提供服务。&lt;/p&gt;

&lt;p&gt;主从重置有很多原因。&lt;/p&gt;

&lt;p&gt;Redis 版本低，主从重置的概率很高。Redis3 主从重置的概率比 Redis2 大大减少，Redis4 支持节点重启以后也能增量同步，这是 Redis 本身进行了很多改进。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/02c97ba4-7348-4bb3-864b-8c6d2a1060b6.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我们现在主要使用的是 2.8.20，属于比较容易能产生主从重置。&lt;/p&gt;

&lt;p&gt;Redis 的主从重置一般是触发了如下条件中的一个。&lt;/p&gt;

&lt;p&gt;1、repl-backlog-size 太小，默认是 1M，如果你有大量的写入，很容易击穿这个缓冲区；2、repl-timeout，Redis 主从默认每十秒钟 ping 一次，60 秒钟 ping 不推就会主从重置，原因可能是网络抖动、总节点压力过大，无法响应这个包等；3、tcp-baklog，默认是 511。操作系统的默认是限制到 128，这个可以适度提高，我们提高到 2048，这个能对网络丢包现象进行一定容错。&lt;/p&gt;

&lt;p&gt;以上都是导致主从重置的原因，主从重置的后果很严重。Master 压力爆增无法提供服务，业务就把这个节点定为不可用。响应时间变长 Master 所在所有主机的节点都会受到影响。&lt;/p&gt;

&lt;p&gt;二、节点过大，部分是人为原因造成的。第一是拆分节点的效率较低，远远慢于公司业务量的增长。此外，分片太少。我们的分片是 500 个，codis 是 1024，codis 原生是 16384 个，分片太少也是个问题。如果做自研的分布式方案，大家一定要把分片数量，稍微设大一点，避免业务发展超过你预期的情况。节点过大之后，会导致持久化的时间增长。我们 30G 的节点要持久化，主机剩余内存要大于 30G，如果没有，你用 Swap 导致主机持久化时间大幅增长。一个 30G 的节点持久化可能要 4 个小时。负载过高也会导致主从重置，引起连锁反应。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/a968a461-28ff-48a4-9664-78bf926fb53d.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;关于我们遇到的坑，接下来分享几个实际的案例。&lt;/p&gt;

&lt;p&gt;第一个案例是一次主从重置。这个情况是在春节前两天出现的，春节前属于消息推送业务高峰期。我们简单还原一下故障场景。首先是大规模的消息下发导致负载增加；然后，Redis Master 压力增大，TCP 包积压，OS 产生丢包现象，丢包把 Redis 主从 ping 的包给丢了，触发了 repl-timeout 60 秒的阈值，主从就重置了。同时由于节点过大，导致 Swap 和 IO 饱和度接近 100%。解决的方法很简单，我们先把主从断开。故障原因首先是参数不合理，大都是默认值，其次是节点过大让故障效果进行放大。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/5113c562-fb40-40ec-897e-35ef0a9f4b12.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;第二个案例是 codis 最近遇到的一个问题。这是一个典型的故障场景。一台主机挂掉后，codis 开启了主从切换，主从切换后业务没有受影响，但是我们去重新接主从时发现接不上，接不上就报了错。这个错也不难查，其实就是参数设置过小，也是由于默认值导致。Slave 从主节点拉数据的过程中，新增数据留在 Master 缓冲区，如果 Slave 还没拉完，Master 缓冲区就超过上限，就会导致主从重置，进入一个死循环。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/18419db5-7fae-4d94-aa7d-c2dcbc71193d.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;基于这些案例，我们整理了一份最佳实践。
&lt;img src="https://l.ruby-china.com/photo/2018/74566731-d7a1-4710-a9e7-b4e4efe15c43.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;一、配置 CPU 亲和。Redis 是单机点的结构，不亲和会影响 CPU 的效率。&lt;/p&gt;

&lt;p&gt;二、节点大小控制在 10G。&lt;/p&gt;

&lt;p&gt;三、主机剩余内存最好大于最大节点大小 +10G。主从重置需要有同等大小的内存，这个一定要留够，如果不留够，用了 Swap，就很难重置成功。&lt;/p&gt;

&lt;p&gt;四、尽量不要用 Swap。500 毫秒响应一个请求还不如挂掉。&lt;/p&gt;

&lt;p&gt;五、tcp-backlog、repl-backlog-size、repl-timeout 适度增大。&lt;/p&gt;

&lt;p&gt;六、Master 不做持久化，Slave 做 AOF+ 定时重置。&lt;/p&gt;

&lt;p&gt;最后是个人的一些思考和建议。选择适合自己的 NoSQL，选择原则有五点：&lt;/p&gt;

&lt;p&gt;1、业务逻辑。首先要了解自身业务特点，比如是 KV 型就在 KV 里面找；如果是图型就在图型里找，这样范围一下会减少 70%-80%。&lt;/p&gt;

&lt;p&gt;2、负载特点，QPS、TPS 和响应时间。在选择 NoSQL 方案时，可以从这些指标去衡量，单机在一定配置下的性能指标能达到多少？Redis 在主机足够剩余情况下，单台的 QPS40-50 万是完全 OK 的。&lt;/p&gt;

&lt;p&gt;3、数据规模。数据规模越大，需要考虑的问题就越多，选择性就越小。到了几百个 TB 或者 PB 级别，几乎没太多选择，就是 Hadoop 体系。&lt;/p&gt;

&lt;p&gt;4、运维成本和可不可监控，能否方便地进行扩容、缩容。&lt;/p&gt;

&lt;p&gt;5、其它。比如有没有成功案例，有没有完善的文档和社区，有没有官方或者企业支持。可以让别人把坑踩过之后我们平滑过去，毕竟自己踩坑的成本还是蛮高的。&lt;/p&gt;

&lt;p&gt;结语：关于 NoSQL 的释义，网络上曾有一个段子：从 1980 年的 know SQL，到 2005 年的 Not only SQL，再到今日的 No SQL！互联网的发展伴随着技术概念的更新与相关功能的完善。而技术进步的背后，则是每一位技术人的持续的学习、周密的思考与不懈的尝试。&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Mon, 14 May 2018 17:40:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/36769</link>
      <guid>https://ruby-china.org/topics/36769</guid>
    </item>
    <item>
      <title>AI 技术说：人工智能相关概念与发展简史</title>
      <description>&lt;p&gt;作者：个推大数据科学家朱金星&lt;/p&gt;

&lt;p&gt;作为近几年的一大热词，人工智能一直是科技圈不可忽视的一大风口。随着智能硬件的迭代，智能家居产品逐步走进千家万户，语音识别、图像识别等 AI 相关技术也经历了阶梯式发展。如何看待人工智能的本质？人工智能的飞速发展又经历了哪些历程？本文就从技术角度为大家介绍人工智能领域经常提到的几大概念与 AI 发展简史。&lt;/p&gt;

&lt;p&gt;一、人工智能相关概念&lt;/p&gt;

&lt;p&gt;1、人工智能（Artifical Intelligence, AI)：就是让机器像人一样的智能、会思考，
是机器学习、深度学习在实践中的应用。人工智能更适合理解为一个产业，泛指生产更加智能的软件和硬件，人工智能实现的方法就是机器学习。
&lt;img src="https://l.ruby-china.com/photo/2018/6d0abd70-f3cc-43dc-a7e8-5d6bddb71bb8.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、数据挖掘：数据挖掘是从大量数据中提取出有效的、新颖的、有潜在作用的、可信的、并能最终被人理解模式 (pattern) 的非平凡的处理过程。&lt;/p&gt;

&lt;p&gt;数据挖掘利用了统计、机器学习、数据库等技术用于解决问题；数据挖掘不仅仅是统计分析，而是统计分析方法学的延伸和扩展
，很多的挖掘算法来源于统计学。&lt;/p&gt;

&lt;p&gt;3、机器学习：专门研究计算机怎样模拟或实现人类的学习行为，以获取新的知识或技能，机器学习是对能通过经验自动改进的计算机算法的研究。
&lt;img src="https://l.ruby-china.com/photo/2018/0407787c-df5f-43de-ac64-9b773cf19b06.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;机器学习是建立在数据挖掘技术之上发展而来，只是数据挖掘领域中的一个新兴分支与细分领域，只不过基于大数据技术让其逐渐成为了当下显学和主流。它是人工智能的核心，是使计算机具有智能的根本途径，其应用遍及人工智能的各个领域。
&lt;img src="https://l.ruby-china.com/photo/2018/be65beac-e409-4b4f-9170-21f4acd049e3.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;4、深度学习（Deep Learning）：是相对浅层学习而言的，是机器学习研究中的一个新的领域，其动机在于建立、模拟人脑进行分析学习的神经网络。它模仿人脑的机制来解释数据，例如图像，声音和文本。深度学习的概念源于人工神经网络的研究。深度学习通过组合低层特征形成更加抽象的高层表示属性类别或特征，以发现数据的分布式特征表示。&lt;/p&gt;

&lt;p&gt;到了当下，经过深度学习技术训练的机器在识别图像方面已不逊于人类，比如识别猫、识别血液中的癌细胞特征、识别 MRI 扫描图片中的肿瘤。在谷歌 AlphaGo 学习围棋等等领域，AI 已经超越了人类目前水平的极限。&lt;/p&gt;

&lt;p&gt;为了方便大家理解，我们将上文提到的四个概念的关系用下图表示。需要注意的是，图示展现的只是一种大致的从属关系，其中数据挖掘与人工智能并不是完全的包含关系。
&lt;img src="https://l.ruby-china.com/photo/2018/4ce57f6d-bcfc-4f21-ab85-86ca8de6d5ef.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;二、人工智能发展历史
&lt;img src="https://l.ruby-china.com/photo/2018/f29e9e50-7657-4ce3-aa8f-2c6931e3799b.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;（图片来源于网络）&lt;/p&gt;

&lt;p&gt;由图可以明显看出 Deep Learning 从 06 年崛起之前经历了两个低谷，这两个低谷也将神经网络的发展分为了几个不同的阶段，下面就分别讲述这几个阶段。&lt;/p&gt;

&lt;p&gt;1、第一代神经网络（1958-1969）&lt;/p&gt;

&lt;p&gt;最早的神经网络的思想起源于 1943 年的 MP 人工神经元模型，当时是希望能够用计算机来模拟人的神经元反应的过程，该模型将神经元简化为了三个过程：输入信号线性加权，求和，非线性激活（阈值法）。如下图所示：
&lt;img src="https://l.ruby-china.com/photo/2018/4c034b71-3eff-466a-abef-cd0e926c04e8.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;1958 年 Rosenblatt 发明的感知器（perceptron）算法。该算法使用 MP 模型对输入的多维数据进行二分类，且能够使用梯度下降法从训练样本中自动学习更新权值。1962 年，该方法被证明为能够收敛，理论与实践效果引起第一次神经网络的浪潮。&lt;/p&gt;

&lt;p&gt;1、第二代神经网络（1986~1998）&lt;/p&gt;

&lt;p&gt;第一次打破非线性诅咒的当属现代 Deep Learning 大牛 Hinton，其在 1986 年发明了适用于多层感知器（MLP）的 BP 算法，并采用 Sigmoid 进行非线性映射，有效解决了非线性分类和学习的问题。该方法引起了神经网络的第二次热潮。&lt;/p&gt;

&lt;p&gt;1989 年，Robert Hecht-Nielsen 证明了 MLP 的万能逼近定理，即对于任何闭区间内的一个连续函数 f，都可以用含有一个隐含层的 BP 网络来逼近该定理的发现极大的鼓舞了神经网络的研究人员。&lt;/p&gt;

&lt;p&gt;同年，LeCun 发明了卷积神经网络-LeNet，并将其用于数字识别，且取得了较好的成绩，不过当时并没有引起足够的注意。&lt;/p&gt;

&lt;p&gt;值得强调的是在 1989 年以后由于没有特别突出的方法被提出，且神经网络（NN）一直缺少相应的严格的数学理论支持，神经网络的热潮渐渐冷淡下去。&lt;/p&gt;

&lt;p&gt;1997 年，LSTM 模型被发明，尽管该模型在序列建模上的特性非常突出，但由于正处于 NN 的下坡期，也没有引起足够的重视。&lt;/p&gt;

&lt;p&gt;3、统计学建模的春天（1986~2006）&lt;/p&gt;

&lt;p&gt;1986 年，决策树方法被提出，很快 ID3，ID4，CART 等改进的决策树方法相继出现。&lt;/p&gt;

&lt;p&gt;1995 年，线性 SVM 被统计学家 Vapnik 提出。该方法的特点有两个：由非常完美的数学理论推导而来（统计学与凸优化等），符合人的直观感受（最大间隔）。不过，最重要的还是该方法在线性分类的问题上取得了当时最好的成绩。&lt;/p&gt;

&lt;p&gt;1997 年，AdaBoost 被提出，该方法是 PAC（Probably Approximately Correct）理论在机器学习实践上的代表，也催生了集成方法这一类。该方法通过一系列的弱分类器集成，达到强分类器的效果。&lt;/p&gt;

&lt;p&gt;2000 年，KernelSVM 被提出，核化的 SVM 通过一种巧妙的方式将原空间线性不可分的问题，通过 Kernel 映射成高维空间的线性可分问题，成功解决了非线性分类的问题，且分类效果非常好。至此也更加终结了 NN 时代。&lt;/p&gt;

&lt;p&gt;2001 年，随机森林被提出，这是集成方法的另一代表，该方法的理论扎实，比 AdaBoost 更好的抑制过拟合问题，实际效果也非常不错。&lt;/p&gt;

&lt;p&gt;2001 年，一种新的统一框架 - 图模型被提出，该方法试图统一机器学习混乱的方法，如朴素贝叶斯，SVM，隐马尔可夫模型等，为各种学习方法提供一个统一的描述框架。&lt;/p&gt;

&lt;p&gt;4、快速发展期（2006~2012）&lt;/p&gt;

&lt;p&gt;2006 年，深度学习（DL）元年。是年，Hinton 提出了深层网络训练中梯度消失问题的解决方案：无监督预训练对权值进行初始化 + 有监督训练微调。其主要思想是先通过自学习的方法学习到训练数据的结构（自动编码器），然后在该结构上进行有监督训练微调。但是由于没有特别有效的实验验证，该论文并没有引起重视。&lt;/p&gt;

&lt;p&gt;2011 年，ReLU 激活函数被提出，该激活函数能够有效的抑制梯度消失问题。&lt;/p&gt;

&lt;p&gt;2011 年，微软首次将 DL 应用在语音识别上，取得了重大突破。&lt;/p&gt;

&lt;p&gt;5、爆发期（2012~至今）&lt;/p&gt;

&lt;p&gt;2012 年，Hinton 课题组为了证明深度学习的潜力，首次参加 ImageNet 图像识别比赛，其通过构建的 CNN 网络 AlexNet 一举夺得冠军，且碾压第二名（SVM 方法）的分类性能。也正是由于该比赛，CNN 吸引到了众多研究者的注意。&lt;/p&gt;

&lt;p&gt;AlexNet 的创新点：&lt;/p&gt;

&lt;p&gt;（1）首次采用 ReLU 激活函数，极大增大收敛速度且从根本上解决了梯度消失问题；&lt;/p&gt;

&lt;p&gt;（2）由于 ReLU 方法可以很好抑制梯度消失问题，AlexNet 抛弃了“预训练 + 微调”的方法，完全采用有监督训练。也正因为如此，DL 的主流学习方法也因此变为了纯粹的有监督学习；&lt;/p&gt;

&lt;p&gt;（3）扩展了 LeNet5 结构，添加 Dropout 层减小过拟合，LRN 层增强泛化能力/减小过拟合；&lt;/p&gt;

&lt;p&gt;（4）首次采用 GPU 对计算进行加速。&lt;/p&gt;

&lt;p&gt;结语：作为 21 世纪最具影响力的技术之一，人工智能不仅仅在下围棋、数据挖掘这些人类原本不擅长的方面将我们打败，还在图像识别、语音识别等等领域向我们发起挑战。如今，人工智能也在与物联网、量子计算、云计算等等诸多技术互相融合、进化，以超乎我们想象的速度发展着。而这一切的发生与演变，只用了几十年的时间……&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Wed, 14 Mar 2018 14:10:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/35235</link>
      <guid>https://ruby-china.org/topics/35235</guid>
    </item>
    <item>
      <title>程序员到 30 岁就油腻了？职场魔咒如何破解？</title>
      <description>&lt;p&gt;“焦虑”是当下青年谈论的最多的词汇之一，无论高矮胖瘦富穷美丑，每个人都有自己独特的难题。造成“焦虑”的原因有很多种，比如生存压力，情感问题，以及困扰着相当一部分人的职场焦虑。今天这篇关于“职场迷茫”的不完全解决手册献给个推的主要用户——广大开发者们。&lt;/p&gt;

&lt;p&gt;也许你刚刚毕业，在“没有选择”或“太多选择”面前感到恐慌，也许从业两三年之后的你失去了原有的激情，又或许是即将“奔三”的你陷入了瓶颈期，对年龄渐长而感到焦虑……我们筛选出开发者职业生涯不同阶段最关注的几个问题，并给出了答案。希望在 6 分钟的阅读里，能带给你一些启迪，重新认识眼前的迷雾，并找到前进的方向。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;董霖，个推高级技术总监&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Q：很多优秀的技术同学在大厂与创业公司的 offer 面前不清楚如何抉择，在职场人发展方面，两者分别具有哪些优势？&lt;/p&gt;

&lt;p&gt;A：不可否认的是，大厂有相对成熟的培训以及工作流程体系，能够帮初入职场的新人快速完成从学生到工程师的角色转变。尤其是大厂内的创新业务小组，类似于一个小型创业公司，如果产品切入点好，可以在大厂的资源扶持下快速推向市场，团队成员的成就感和收益自然不言而喻。&lt;/p&gt;

&lt;p&gt;而对于创业型公司，一方面可以提供给技术人员一个能者多劳的内部创业环境，另一方面还有一个极具竞争力的外部环境。很多技术问题和业务问题在大厂的环境和背景支撑下，可以轻松顺利解决，但是对于创业型公司，则可能遇到更多阻碍，需要合理调动资源，充分发挥聪明才智去解决。此外，还需要面对友商更残酷的竞争。每个人都是多面手，无需给自己加天花板。如果技术同学有意愿在未来开启自己的事业，这样的磨练是必不可少的，挣脱襁褓，野蛮生长。&lt;/p&gt;

&lt;p&gt;另外，对于技术人员来说，很重要的一项能力是技术方案设计能力，需要跳出代码的框框，站在更高的视角来审视业务需求、提出解决方案、完成方案逻辑、实现平滑切换。在创业型公司，人员相对精干，人人都是架构师，而不只是机械的 Coder。&lt;/p&gt;

&lt;p&gt;Q：“程序员”在部分人眼中是吃“青春饭”的，怎样看待技术岗位与年龄之间的关系？对于那些存在“奔三忧虑”的技术小伙伴，有哪些建议给到他们？&lt;/p&gt;

&lt;p&gt;A：奔三忧虑或者“三十岁焦虑综合症”确实是比较普遍的现象，不过有焦虑说明自己还有更高的目标，没有过早进入舒适区。这个问题对于技术人员可能更为突出，因为技术圈内大家似乎认为 30 岁还在写代码是不是有点 out 了，其实不同年龄段有不同的工作方式，不应该为技术工作设置一个超时时间。&lt;/p&gt;

&lt;p&gt;技术人员的成长，无外乎两个方向：深耕业务，成为业务线研发主管，保障业务系统的按时交付和稳定运行；深耕技术，成为公司基础技术平台负责人，推进新技术新工具新流程的落地实践。&lt;/p&gt;

&lt;p&gt;其中很重要的是，管理能力的成长需要跟上年龄的增长。为什么这么说？随着年龄成长的是经验，经验是个人通过长期的工作生活动态调整出的一种相对最优的思维方式。一个团队内，新人总是占大多数的（公司需要新鲜血液）。没有管理，经验就无法传承，新人重复着自己走过的坑，一切从头开始。&lt;/p&gt;

&lt;p&gt;有一种观点说技术人员的职业发展分技术线和管理线，我认为是不准确的。广义的管理思维适用于所有人：人与人的沟通协作。网聚人的力量，一切皆有可能。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;袁凯，个推大数据架构师&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Q：柯洁的人机大战让机器学习再度大火，针对期望向数据岗位转型的小伙伴，有哪些建议可以给到他们？&lt;/p&gt;

&lt;p&gt;A：对大数据的挖掘和使用是机器学习特别是深度学习的一个必要条件，但是数据处理相关的又不仅仅只是机器学习，所以首先要理清楚这两者的差别和联系。对于大多数企业而言，大数据处理方面包括的内容会比较广泛。下面是我们的一些建议：&lt;/p&gt;

&lt;p&gt;1、了解数据领域：先可以看一些大数据入门的书籍，例如《数据之巅》、《数据之美》，了解数据是如何具体应用，理解基于数据思维来解决问题；&lt;/p&gt;

&lt;p&gt;2、选择自己感兴趣方向：数据岗位主要分为数据分析师、数据挖掘工程师、数据开发工程师等，可以通过招聘网站查询岗位的职责以及要求，看看是不是自己有兴趣；特别是一些涉及算法的岗位，对数学基础要求比较高。数据开发工程师则更多涉及到具体代码实现、工程实施；&lt;/p&gt;

&lt;p&gt;3、学习和实践方面：首先建议先就一些具体项目（例如：网上公开的项目）去尝试完成；然后把自己的解决方案和这个项目实际方案对比，找到自己的薄弱环节，总结出下一步需要学习的技术（分析方法、特征工程、机器学习算法、spark、hive 等）和数学知识（统计知识、概率知识、线性代数知识等）；此外还需要积累自己负责领域的业务知识；最后是多培养自己的数据思维，多尝试基于数据统计思想去考虑生活中的一些现象和问题等。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;姜季廷，WEB 前端首席架构师&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Q：通常来说技术岗位的同学大致的职业发展线路是什么样的呢？&lt;/p&gt;

&lt;p&gt;A:&lt;img src="https://l.ruby-china.com/photo/2017/150d3d21-d3a0-40d9-ada2-275a6e468f1b.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Q：很多技术同学是互联网知识多面手，如果一个拥有 web 前端工作经验不足三年的同学期望转岗，有哪些方向比较好？&lt;/p&gt;

&lt;p&gt;A：如果一个同学萌生转岗的想法，说明他并不满足于前端，或者说兴趣点不在前端。&lt;/p&gt;

&lt;p&gt;所以如果你已有方向，那么就按照自己设想的方向前进就好，略过后文即可。&lt;/p&gt;

&lt;p&gt;如果既不想做前端又对未来感到迷茫，可以接着往下看：&lt;/p&gt;

&lt;p&gt;1、问问自己是否真的了解前端，也许你当前工作涉及到的仅仅只是前端的很小一部分，比如写 CSS，其实你可以尝试些其他前端的工作内容，比如写逻辑，写 Node JS（大前端的工作）再做决定。&lt;/p&gt;

&lt;p&gt;2、尝试前端上下游岗位：&lt;/p&gt;

&lt;p&gt;1）设计师：向往设计，有美工功底；&lt;/p&gt;

&lt;p&gt;2）产品经理：可以规划产品的功能走向，向用户展示你的理念；&lt;/p&gt;

&lt;p&gt;3）后端开发：还是想写代码，但想写点深入的逻辑，设计数据库之类的；&lt;/p&gt;

&lt;p&gt;4）测试：专业找茬 20 年，找出这些不爽的点才能让我爽（个人认为，在理想情形下，测试岗位还是交给有丰富前后端经验的同学来做比较好）；&lt;/p&gt;

&lt;p&gt;5）数据分析（建模）：大数据方向；&lt;/p&gt;

&lt;p&gt;6）数据可视化：算法及前端展示（这个也算是前端方向）。&lt;/p&gt;

&lt;p&gt;3、其他编程：安卓开发、iOS 开发、桌面应用开发等。&lt;/p&gt;

&lt;p&gt;Q：作为一名 web 前端架构师，请简谈一下自己做好这项工作所需的最重要的几项技能（硬技术 + 软技能均可）。&lt;/p&gt;

&lt;p&gt;A：我们可以把“web 前端架构师”拆开来看：&lt;/p&gt;

&lt;p&gt;web 前端：需要掌握相关的技术，从最基础的 HTML、CSS、JS 到前沿的前端框架，比如 ng/vue 等；&lt;/p&gt;

&lt;p&gt;架：用做支撑的东西。所以要做一些支撑的相关工作：比如去探索技术的最佳实践，去踩一些坑，为团队整理出合适的工作流程等等；&lt;/p&gt;

&lt;p&gt;构：意为结成，组合。比如将团队的人、技术、业务需求有效地结合起来，基于团队现状，选取合适的技术、流程、实践方案等；&lt;/p&gt;

&lt;p&gt;师：师者，所以传道授业解惑也。技术上，团队管理上，或者类似这样的职业规划的问题，一个优秀的“师”会结合自己的经验，总结，倾囊相授给有需要的人。&lt;/p&gt;

&lt;p&gt;叮~上述的难题更多是关于个人成长方向的探寻，而对于企业中高层管理者来说，当制定战略、公司管理以及个人发展方面的疑惑杂糅在一起时，问题会显得更为复杂。我们邀请了个推 CTO，听听他为我们带来的职场建议。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;个推 CTO，叶新江&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Q：怎样看待业务、产品与技术三者之间的关系？&lt;/p&gt;

&lt;p&gt;A：其实这里的业务更贴切应该表述为市场及运营，因为业务和产品及技术是密不可分的。正确的理解是业务是属于公司的，而不是属于某个部门的，运营和市场是业务方，而不是业务。&lt;/p&gt;

&lt;p&gt;只有这几方面协作起来才能成为业务的完整组成部分，因此大家是在一个共同的目标和事业下来各自执行，然后手拉手合作完成任务。市场的同事要负责把客户以及市场的需求和反馈传达到产品和技术，产品和技术需要充分理解业务要求，从更好满足业务要求和质量角度来进行实现。&lt;/p&gt;

&lt;p&gt;Q：回顾“CTO 养成之路”，是否会在某个阶段感受自己遇到了技术提升的瓶颈期、钝化对新生技术的敏感程度？后来又是怎样克服这个问题的？&lt;/p&gt;

&lt;p&gt;A：是的，肯定会存在这个阶段的。一般会在从事某个业务领域相对长时间之后，对于熟悉的环境和技术会产生一种倦怠。&lt;/p&gt;

&lt;p&gt;而要克服这个问题，首先还是在于对技术要有热情，以及对管理方面要有追求。&lt;/p&gt;

&lt;p&gt;在技术方面，只有对技术有热情才会一直往前走下去，否则有很大可能会选择转型。其次要为自己寻找新的领域，特别是一些处于上升阶段或者风口阶段的方向。然后为自己设定一个目标，至少要去了解技术的实质内容、面临的挑战、实际可以发挥作用的业务领域。&lt;/p&gt;

&lt;p&gt;在管理方面，CTO 所面对的是公司战略发展以及领导力方面的突破，所以对于本行业或者和公司相关方向的大势等需要进行跟踪和研究；在提高团队管理能力、团队梯队建设、培养核心人员等方面，也需要不断补全自己的知识体系。&lt;/p&gt;

&lt;p&gt;Q：对于中高层管理者来说，在激发下属的工作激情与创新能力方面，有哪些经验与我们分享？&lt;/p&gt;

&lt;p&gt;A：我的经验归纳起来就是 BEST: Believe、Encourage、Share、Trust. 相信团队，鼓励团队，和团队共享知识和成果，依赖和对团队有信心。让公司成为员工成功的平台，而不是把员工作为工具；帮助员工认识到他们是对自己的生命负责，对自己的时间负责，公司是他们成功的资源。&lt;/p&gt;

&lt;p&gt;希望这些“过来人”们在各自岗位上积累的宝贵经验与思考能让你有所领悟与收获。如你意犹未尽，想与文中解答者深入探讨职业规划问题，欢迎大家留言讨论。&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Fri, 22 Dec 2017 11:38:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/34751</link>
      <guid>https://ruby-china.org/topics/34751</guid>
    </item>
    <item>
      <title>Python 与 R 的争锋：大数据初学者该怎样选？</title>
      <description>&lt;p&gt;在当下，人工智能的浪潮席卷而来。从 AlphaGo、无人驾驶技术、人脸识别、语音对话，到商城推荐系统，金融业的风控，量化运营、用户洞察、企业征信、智能投顾等，人工智能的应用广泛渗透到各行各业，也让数据科学家们供不应求。Python 和 R 作为机器学习的主流语言，受到了越来越多的关注。数据学习领域的新兵们经常不清楚如何在二者之间做出抉择，本文就语言特性与使用场景为大家对比剖析。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;一．Python 和 R 的概念与特性&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python 是一种面向对象、解释型免费开源高级语言。它功能强大，有活跃的社区支持和各式各样的类库，同时具备简洁、易读以及可扩展等优点，在近几年成为高人气的编程语言。&lt;/p&gt;

&lt;p&gt;Python 的优势：&lt;/p&gt;

&lt;p&gt;1、Python 的使用场景非常多，不仅和 R 一样可以用于统计分析，更广泛应用于系统编程、图形处理、文本处理、数据库编程、网络编程、Web 编程、网络爬虫等，非常适合那些想深入钻研数据分析或者应用统计技术的程序员。&lt;/p&gt;

&lt;p&gt;2、目前主流的大数据和机器学习框架对 Python 都提供了很好的支持，比如 Hadoop、Spark、Tensorflow；同时，Python 也有着强大的社区支持，特别是近年来随着人工智能的兴起，越来越多的开发者活跃在 Python 的社区中。&lt;/p&gt;

&lt;p&gt;3、Python 作为一种胶水语言，能够和其他语言连结在一起，比如你的统计分析部分可以用 R 语言写，然后封装为 Python 可以调用的扩展类库。&lt;/p&gt;

&lt;p&gt;R 语言是一种用来进行数据探索、统计分析和作图的解释型语言，但更像一种数学计算的环境。它模块丰富，为数学计算提供了极为方便的编程方式，特别是针对矩阵的计算。&lt;/p&gt;

&lt;p&gt;R 语言的优势：&lt;/p&gt;

&lt;p&gt;1、R 语言拥有许多优雅直观的图表，常见的数据可视化的工具包有：&lt;/p&gt;

&lt;p&gt;· 交互式图表 rCharts、Plotly，交互时序图 dygraphs，交互树状图 TreeMap&lt;/p&gt;

&lt;p&gt;· ggplot2-一个基于图形语法的绘图系统&lt;/p&gt;

&lt;p&gt;· lattice-R 语言格子图形&lt;/p&gt;

&lt;p&gt;· rbokeh-针对 Bokeh 的 R 语言接口&lt;/p&gt;

&lt;p&gt;· RGL-使用了 OpenGL 的 3D 可视化&lt;/p&gt;

&lt;p&gt;· Shiny-用于创建交互式应用和可视化的框架&lt;/p&gt;

&lt;p&gt;· visNetwork-交互式网络可视化&lt;/p&gt;

&lt;p&gt;散点图
&lt;img src="https://l.ruby-china.com/photo/2017/fc0a57f0-5e8c-452e-900d-36e25d9288e9.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;时序图
&lt;img src="https://l.ruby-china.com/photo/2017/5afa5cb0-a7c2-4f17-b36d-df5e9d22fb94.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;词云图
&lt;img src="https://l.ruby-china.com/photo/2017/c3680c6c-e3c3-4b62-a97b-a0565bdb2a6e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;2、拥有大量专门面向统计人员的实用功能和丰富的数学工具包。自带 base 一 R 的基础模块、mle 一极大似然估计模块、ts 一时间序列分析模块、mva 一多元统计分析模块、survival 一生存分析模块等，同时用户可以灵活使用数组和矩阵的操作运算符，及一系列连贯而又完整的数据分析中间工具。&lt;/p&gt;

&lt;p&gt;3、语言简洁上手快，不需要明确定义变量类型。比如下面简简单单三行代码，就能定义一元线性回归，是不是很酷炫：&lt;/p&gt;

&lt;p&gt;x &amp;lt;- 1:10
y &amp;lt;- x+rnorm(10, 0, 1)
fit &amp;lt;- lm(y ~ x)&lt;/p&gt;

&lt;p&gt;同时，R 语言对向量化的支持程度高，通过向量化运算，数据在计算过程中前后不依赖，是一种高度并行计算的实现，也避免了许多循环结构的使用。&lt;/p&gt;

&lt;p&gt;当然了，相比于 Python 它也存在着一些劣势。比如内存管理问题，在大样本的回归中，如使用不当就会出现内存不足的情况，但目前 spark 也提供了对 R 的支持，开发者可以使用 sparkR 进行大数据的计算处理。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;二.Python 和 R 在文本信息挖掘和时序分析方面的区别&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python 和 R 都有非常强大的代码库，Python 有 PyPi，R 有 CRAN。但两者方向不同，Python 使用的范围更加广泛，涉及到方方面面；R 更专注统计方面，但在数据量大时运行速度很慢。下面我针对数据分析中的两种使用场景来比较 Python 和 R：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;文本信息挖掘：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;文本信息挖掘的应用非常广泛，例如根据网购评价、社交网站的推文或者新闻进行情感极性分析等。这里我们用例子分析比较一下。&lt;/p&gt;

&lt;p&gt;Python 有良好的程序包帮助我们进行分析。比如 NLTK，以及专门针对中文的 SnowNLP，包含了中文分词、词性标注、情感分析，文本分类、TextRank、TF-IDF 等模块。&lt;/p&gt;

&lt;p&gt;在用 Python 做情感极性分析时，首先需要将句子分解为单词，这里我们可以使用 Python 中 jieba 分词，使用起来也非常简单：&lt;/p&gt;

&lt;p&gt;word=jieba.cut(m,cut_all=False)&lt;/p&gt;

&lt;p&gt;然后操作特征提取，可以利用 NLTK 中的 stopwords 先去除停用词。如果有需要，可以对文本进行向量化处理，这里我们可以采用 Bag of Words，选择 TF-IDF 进行基于权重的向量转化，也可以使用 Word2Vec 进行基于相似度的转化。接下来，使用 sklearn 包中的 pca 进行降维：&lt;/p&gt;

&lt;p&gt;pca=PCA(n_components=1)&lt;/p&gt;

&lt;p&gt;newData=pca.fit_transform(data)&lt;/p&gt;

&lt;p&gt;除了 pca，还可以选择使用互信息或者信息熵等其他方法。&lt;/p&gt;

&lt;p&gt;之后，我们进行分类算法模型训练和模型评估，可以使用朴素贝叶斯（NaiveBayes），决策树（Decision Tree）等 NLTK 自带的机器学习方法。&lt;/p&gt;

&lt;p&gt;使用 R 进行情感极性分析&lt;/p&gt;

&lt;p&gt;首先需要对数据进行预处理，安装 Rwordseg/rJava（其中有不少坑）两个包；&lt;/p&gt;

&lt;p&gt;进行数据清理清除掉没用的符号后，进行分词：Rwordseg 中的 segmentCN 方法可以对中文进行分词。当然，也可以使用 jiebaR；&lt;/p&gt;

&lt;p&gt;接下来构建单词 - 文档 - 标签数据集，去除停用词；&lt;/p&gt;

&lt;p&gt;创建文档 - 词项矩阵，可以选择 TermDocumentMatrix，使用 weightTfIdf 方法得到 tf-idf 矩阵；&lt;/p&gt;

&lt;p&gt;最后用 e1071 包中的贝叶斯方法进行文本分类，或者可以用 RTextTools 包中的其他机器学习算法来完成分类，其中包含九种算法：BAGGING(ipred:bagging)：bagging 集成分类&lt;/p&gt;

&lt;p&gt;BOOSTING (caTools:LogitBoost)：Logit Boosting 集成分类&lt;/p&gt;

&lt;p&gt;GLMNET(glmnet:glmnet)：基于最大似然的广义线性回归&lt;/p&gt;

&lt;p&gt;MAXENT(maxent:maxent)：最大熵模型&lt;/p&gt;

&lt;p&gt;NNET(nnet:nnet) ：神经网络&lt;/p&gt;

&lt;p&gt;RF(randomForest:randomForest)：随机森林&lt;/p&gt;

&lt;p&gt;SLDA(ipred:slda)：scaled 线性判别分析&lt;/p&gt;

&lt;p&gt;SVM(e1071:svm) ：支持向量机&lt;/p&gt;

&lt;p&gt;TREE (tree:tree)：递归分类树&lt;/p&gt;

&lt;p&gt;2.时序分析：&lt;/p&gt;

&lt;p&gt;时间序列分析是根据系统观察得到的时间序列数据，通过曲线拟合和参数估计来建立数学模型的理论和方法，通常用于金融领域、气象预测、市场分析领域等。R 语言拥有许多程序包可用于处理规则和不规则时间序列，因而更有优势。&lt;/p&gt;

&lt;p&gt;Python 进行时序分析的时常用 ARIMA(p,d,q) 模型，其中 d 指的是差分项，p 和 q 分别代表自回归项和移动平均项。构建 ARIMA 模型使用最多的就是 statsmodels 模块，该模块可以用来进行时间序列的差分，建模和模型的检验。这里例举一个周期性预测的例子：&lt;/p&gt;

&lt;p&gt;下面是一组数据，代表美国某公交公司发布的五十年中每年的乘客相关数据（比如 1950-2000）：&lt;/p&gt;

&lt;p&gt;data = [9930, 9318, 9595, 9972, 6706, 5756, 8092, 9551, 8722, 9913, 10151, 7186, 5422, 5337, 10649, 10652, 9310, 11043, 6937, 5476, 8662, 8570, 8981, 8331, 8449, 5773, 5304, 8355, 9477, 9148, 9395, 10261, 7713, 6299, 9424,9795, 10069, 10602, 10427, 8095, 6707, 9767, 11136, 11812, 11006, 11528, 9329, 6818, 10719, 10683]&lt;/p&gt;

&lt;p&gt;1).首先，使用 pandas 进行处理和存储数据：&lt;/p&gt;

&lt;p&gt;data=pd.Series(data)&lt;/p&gt;

&lt;p&gt;2).然后需要对数据进行平稳性检验，一般利用单位根检验，常用的方法有 ADF、DFGLS、PP 等等：&lt;/p&gt;

&lt;p&gt;Python 中直接用 ADF(data), DFGLS(data) 就可以得出 pvalue 的结果&lt;/p&gt;

&lt;p&gt;3).序列平稳性是进行时间序列分析的前提条件，如果上一个步骤显示结果不平稳，就需要对时间序列做平稳性处理，一般用差分法最多：&lt;/p&gt;

&lt;p&gt;diff1 = data.diff(2)&lt;/p&gt;

&lt;p&gt;其中 diff（object）表示差分的阶数，这里我们使用 2 阶，当然你也可以用 1 阶、3 阶、4 阶等等&lt;/p&gt;

&lt;p&gt;4).进行白噪声检验：&lt;/p&gt;

&lt;p&gt;value=acorr_ljungbox(data,lags=1)&lt;/p&gt;

&lt;p&gt;5).现在，我们的 ARIMA(p,d,q) 中的 d=2，接下来我们进行模型选择。第一步是计算出 p 和 q，首先检查平稳时间序列的自相关图和偏自相关图，通过 sm.graphics.tsa.plot_acf (data) 和 sm.graphics.tsa.plot_pacf(data)，然后通过系数情况进行模型选择，可供选择的有 AR,MA,ARMA,ARIMA。&lt;/p&gt;

&lt;p&gt;6).模型训练：model=sm.tsa.ARMA(data,(p,d,q)).fit()，此处用 ARMA 模型计算出 p 和 q，从而训练出模型。&lt;/p&gt;

&lt;p&gt;用 R 来构建时间序列模型&lt;/p&gt;

&lt;p&gt;R 针对时间序列有各式各样的工具包，比如：&lt;/p&gt;

&lt;p&gt;library(xts)，library(timeSeires)，library(zoo)—时间基础包&lt;/p&gt;

&lt;p&gt;library(urca)--进行单位根检验&lt;/p&gt;

&lt;p&gt;library(tseries)--arma 模型&lt;/p&gt;

&lt;p&gt;library(fUnitRoots)--进行单位根检验&lt;/p&gt;

&lt;p&gt;library(FinTS)--调用其中的自回归检验函数&lt;/p&gt;

&lt;p&gt;library(fGarch)--GARCH 模型&lt;/p&gt;

&lt;p&gt;library(nlme)--调用其中的 gls 函数&lt;/p&gt;

&lt;p&gt;library(fArma)--进行拟合和检验&lt;/p&gt;

&lt;p&gt;library(forecast)—arima 建模&lt;/p&gt;

&lt;p&gt;下面我介绍一下 R 语言中 forecast 工具包里面两个很强大的工具：ets 和 auto.arima。用户什么都不需要做，这两个函数会自动挑选一个最恰当的算法去分析数据。比如用 ets 来处理：&lt;/p&gt;

&lt;p&gt;fit&amp;lt;-ets(train)&lt;/p&gt;

&lt;p&gt;accuracy(predict(fit,12),test)&lt;/p&gt;

&lt;p&gt;或者用 auto.arima 处理：&lt;/p&gt;

&lt;p&gt;fit&amp;lt;-auto.arima(train)&lt;/p&gt;

&lt;p&gt;accuracy(forecast(fit,h=12),test)&lt;/p&gt;

&lt;p&gt;除此之外，forecast 包中有针对增长或者降低趋势并且存在季节性波动的时间序列算法 Holt-Winters。Holt-Winters 的思想是把数据分解成三个成分：平均水平（level），趋势（trend），周期性（seasonality）。R 里面一个简单的函数 stl 就可以把原始数据进行分解。&lt;/p&gt;

&lt;p&gt;本文主要从各自优势及具体例子中分析了 Python 与 R 两种编程语言。不难看出，二者在“综合实力”上难分伯仲，具体选择哪一种深入学习，依然需要考虑自己实际期望解决的问题、应用的领域等等方面。最后欢迎大家就大数据编程语言相关问题与我沟通交流~&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Thu, 21 Dec 2017 18:42:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/34748</link>
      <guid>https://ruby-china.org/topics/34748</guid>
    </item>
    <item>
      <title>浅析开源数据库 MySQL 架构</title>
      <description>&lt;p&gt;数据库是所有应用系统的核心，故保证数据库稳定、高效、安全地运行是所有企业日常工作的重中之重。数据库系统一旦出现问题无法提供服务，有可能导致整个系统都无法继续工作。所以，一个成功的数据库架构在高可用设计方面也是需要充分考虑的。下面就为大家介绍一下如何构建一个高可用的 MySQL 数据库系统。&lt;/p&gt;

&lt;p&gt;做过 DBA 或者是运维的同学都应该知道，任何设备或服务，存在单点就会带来巨大风险，因为这台物理机一旦宕机或服务模块 crash，若在短时间内无法找到替换的设备，势必会影响整个应用系统。因而如何保证不出现单点就是我们的重要工作，使用 MySQL 高可用方案可以很好地解决这个问题，一般有以下几种：&lt;/p&gt;

&lt;p&gt;一、利用 MySQL 自身的 Replication 来实现高可用&lt;/p&gt;

&lt;p&gt;MySQL 自带的 Replication 就是我们常说的主从复制（AB 复制），通过对主服务器做一个从机，在主服务器宕机的情况下快速地将业务切换到从机上，保证应用的正常使用。利用 AB 复制做高可用方案也分为几种不同的架构：&lt;/p&gt;

&lt;p&gt;1、常规的 MASTER---SLAVE 解决方案&lt;/p&gt;

&lt;p&gt;普通的 MASTER---SLAVE 是目前国内外大多数中小型公司最常用的一种架构方案，主要的好处就是简单、使用设备较少（成本较低）、维护方便。这种架构能解决单点的问题，而且还能在很大程度上解决系统的性能问题。在一个 MASTER 后面可以带上一个或者多个的 SLAVE（主从级联复制），不过这种架构要求一个 MASTER 必须能够满足系统所有的写请求，不然就需要做水平拆分分担读的压力。
&lt;img src="https://l.ruby-china.com/photo/2017/b0df3107-7024-444a-bd8a-3fd98b22ff31.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图一
&lt;img src="https://l.ruby-china.com/photo/2017/b5de93e5-a0a7-48c6-afd3-e66a9b16426f.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图二&lt;/p&gt;

&lt;p&gt;图一到图二展示的是：解决单点问题和利用读写分离达到提高性能的过程。&lt;/p&gt;

&lt;p&gt;2、DUAL MASTER 与级联复制结合&lt;/p&gt;

&lt;p&gt;双主多从是在上面的方案中衍生而来的一种更加合理的方案。这个方案的好处是：当两个主服务器中任何一个挂掉时，整个架构都不用做大的调整。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/78097b96-32f3-4fc6-9a3a-865ef7f4c474.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图三
&lt;img src="https://l.ruby-china.com/photo/2017/1f74599f-8ba2-44af-8c3f-028cde5571fb.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图四
&lt;img src="https://l.ruby-china.com/photo/2017/b32cb545-d61e-4be3-9487-03badff846ff.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图五&lt;/p&gt;

&lt;p&gt;这个过程如上图所示。但图五这种情况比较特殊，即 MASTER-B 宕机的话怎么办呢？首先可以确定的是我们的所有 Write 请求都不会受到任何影响，而且所有的 Read 请求也都能够正常访问；但所有 Slave 的复制都会中断，Slave 上面的数据会开始出现滞后的现象。这时候我们需要做的就是将所有的 Slave 进行 CHANGE MASTER TO 操作，改为从 Master A 进行复制。由于所有 Slave 的复制都不可能超前最初的数据源，所以可以根据 Slave 上面的 Relay Log 中的时间戳信息与 Master A 中的时间戳信息进行对照，来找到准确的复制起始点，从而避免造成数据的丢失。&lt;/p&gt;

&lt;p&gt;二、利用 MYSQL CLUSTER 实现整体的高可用&lt;/p&gt;

&lt;p&gt;就目前而言，利用 MYSQL CLUSTER 实现整体的高可用（即 NDB CLUSTER）的方案在国内的公司并没有很普及。NDB CLUSTER 节点实际上就是一个多节点的 MySQL 服务器，但是并不包含数据，所以任何机器只要安装了就可以使用。当集群中某一个 sql 节点 crash 之后，因为节点不存具体的数据，所以数据不会丢失。如图六：
&lt;img src="https://l.ruby-china.com/photo/2017/7c8ee04a-cf89-408c-b8f9-ff79b6167558.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图六&lt;/p&gt;

&lt;p&gt;三、通过 MySQL 的衍生产品实现高可用&lt;/p&gt;

&lt;p&gt;在目前 MySQL 实现高可用的衍生产品中，知名度的和普及度比较高的是 GALERA CLUSTER 和 PERCONA XTRDB CLUSTER（PXC）。相关的内容本文暂不展开讲述，感兴趣的同学可以查阅相关资料进一步了解。这两种集群的实现方式都是类似的，如图七、图八：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/152e2519-99fe-4251-925d-543d496d6eeb.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图七&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/2bc538a4-6aa3-476c-b29e-f700fa3b4b5e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图八&lt;/p&gt;

&lt;p&gt;四、各种高可用方案的利弊比较&lt;/p&gt;

&lt;p&gt;在前面各种高可用设计方案的介绍中读者们可能已经发现，不管是哪一种方案，都存在自己独特的优势，但也都或多或少存在一些限制。这一节将针对上面的几种主要方案做一个利弊分析，以供大家选择过程中参考。&lt;/p&gt;

&lt;p&gt;1, MySQL Replication&lt;/p&gt;

&lt;p&gt;优势：部署简单，实施方便，维护也不复杂，是 MySQL 天生就支持的功能。且主备机之间切换方便，通过第三方软件或者自行编写的脚本即可自动完成主备切换。&lt;/p&gt;

&lt;p&gt;劣势：如果 Master 主机硬件故障且无法恢复，则可能造成部分未传送到 Slave 端的数据丢失。&lt;/p&gt;

&lt;p&gt;2, MySQL Cluster (NDB)&lt;/p&gt;

&lt;p&gt;优势：可用性非常高，性能非常好。每一份数据至少在不同主机上面存在一份拷贝，且冗余数据拷贝实时同步。&lt;/p&gt;

&lt;p&gt;劣势：维护较为复杂，产品较新，存在部分 bug，目前还不一定适用于比较核心的线上系统。&lt;/p&gt;

&lt;p&gt;3、GALERA CLUSTER 和 PERCONA XTRDB CLUSTER（PXC）&lt;/p&gt;

&lt;p&gt;优势：可靠性非常高，所有节点可以同时读写每一份数据，至少在不同主机上面存在一份拷贝，且冗余数据拷贝实时同步。&lt;/p&gt;

&lt;p&gt;劣势：随着集群的规模扩大，性能会越来越差。&lt;/p&gt;

&lt;p&gt;4、不得不提的 DRBD 磁盘网络镜像方案&lt;/p&gt;

&lt;p&gt;从架构上来说，它有点类似 Replication，只是它是通过第三方的软件实现数据同步的过程，可靠性比 Replication 更高，但是也牺牲了性能。&lt;/p&gt;

&lt;p&gt;优势：软件功能强大，数据在底层块设备级别跨物理主机镜像，且可根据性能和可靠性要求配置不同级别的同步。IO 操作保持顺序，可满足数据库对数据一致性的苛刻要求。&lt;/p&gt;

&lt;p&gt;劣势：非分布式文件系统环境无法支持镜像数据同时可见，即性能和可靠性两者相互矛盾，无法适用于对二者要求都比较苛刻的环境。维护成本高于 MySQL Replication。&lt;/p&gt;

&lt;p&gt;说完了各种常用架构的优缺点后，剩下的就是如何选择合适的架构在现实的生产环境中使用的问题。在这方面每个人都有自己的想法和经验，具体哪个方案是最优的就见仁见智了。在日常的工作中架构的完善并不是一蹴而就，而是一个不断演变优化完善的过程。&lt;/p&gt;

&lt;p&gt;个推在数据库方面也经历了从单点到主从再到主从 + 高可用的过程，同时也经历了从单一的 MySQL+redis 到 MySQL+redis+es，最后到现在 MySQL+redis+es+codis 等等的演变。每一次的演变都是为了解决生产环境下的实际问题和痛点。单从 MySQL 来说任何一个架构都无法解决所有的问题（痛点），都需要根据实际的情况选择一个合适架构。MySQL 集群实现的方案非常灵活多变，对于 MySQL 工作者来说如何选择一个合适的架构也是一种挑战，同时也是我们不断钻研和学习 MySQL 的动力。&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Thu, 14 Sep 2017 15:08:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/34120</link>
      <guid>https://ruby-china.org/topics/34120</guid>
    </item>
    <item>
      <title>如何用 JavaScript 进行数组去重</title>
      <description>&lt;p&gt;今天的文章和大家谈一谈如何用 JavaScript 进行数组去重，这是一道常见的面试（笔试）题，可以很好地考察出一个人的逻辑思维及边界考虑情况，希望此文能够帮助大家在解决类似问题时拓宽思路。据我到目前为止面试的情况，很少有人能在现场考虑很全，基本上的人都是浅尝辄止。&lt;/p&gt;

&lt;p&gt;当然，“使用库中的一个函数就能去重”并不在本篇文章的讨论范围内，我们针对的是需要自己写代码的场景。考虑到实际情况，我们使用 ES5（主要就用了 indexOf 方法，如果是更古老的环境，可以自己增加这段代码，或者使用 ES5 兼容库 es5-sham.js）。&lt;/p&gt;

&lt;p&gt;我们先审题：数组，题目中并没有说是什么样的数组，即数组的组成元素可能是字符串、数字、布尔、数组、对象、Null、Undefined。&lt;/p&gt;

&lt;p&gt;在开始之前我们先看看这些类型以及他们的值比较关系：
&lt;img src="https://l.ruby-china.com/photo/2017/9f8e88b4-0795-4cc8-961f-176e37d238df.png!large" title="" alt=""&gt;
接着我们来看看数组中的 indexOf 方法：
var gtArray = [66],
    gtObject = {
        id: 1
    },
    gtTestArr = ["1", 1, true, [66], gtArray, { id: 1 }, gtObject, null, undefined];&lt;/p&gt;

&lt;p&gt;gtTestArr.indexOf("1"); // 0
gtTestArr.indexOf(1); // 1
gtTestArr.indexOf(true); // 2
gtTestArr.indexOf([66]); // -1
gtTestArr.indexOf(gtArray); // 4
gtTestArr.indexOf({ id: 1 }); // -1
gtTestArr.indexOf(gtObject); // 6
gtTestArr.indexOf(null); // 7
gtTestArr.indexOf(undefined); // 8&lt;/p&gt;

&lt;p&gt;从上述效果中看我们可以得出结论：indexOf 可以帮我们找到一个数组中某个元素（若该元素为数组或者对象，则为该引用的地址值）对应的索引值，在人脑“看”来相同的 [66] 和 gtArray，实际上除了都用 gtArray 表示的部分是一样的，其他的 [66] 之间以及 gtArray 都是不同的引用地址，自然也就找不到索引值啦。&lt;/p&gt;

&lt;p&gt;好了，回归正题，我们要进行数组去重，那么先想个大致的思路，比如：
1）新建一个空数组，老数组从第一个开始，看看新数组中有没有，如果没有就 push 进入新数组，如果存在就下一个。
2）在一个数组里面从第一个开始，将它后面的元素依次与当前这个比较，如果相等，就把后面的那个元素删掉，依次往复操作，直到最后一个。接着比较对象变成第二个，重复上述步骤，直到比较对象是最后一个。
3) and so on&lt;/p&gt;

&lt;p&gt;当然每个思路有不同的算法，对于一种判断描述也可以有不同的实现方式（如下面的相等），比如用 map，用下标等。不同方式可能也会有不同的局限性或者前置条件。&lt;/p&gt;

&lt;p&gt;好，现在我们界定一下什么是相等，简单的 1 与 1 肯定是相等的，而 1 与“1”是不等的，对于引用类型我们可以分为几种模式（级别）：
1）仅引用地址一样才算相等。
2）引用地址可以不一样，但对应的数组（对象）所拥有的元素（键值对）一模一样就算相等。即在我们看来，这两个数据写出来，看上去就是一样的。
3）对于是非数组的对象，针对几个 key 的值是一样的情况，我们将其认定是一样的。比如 { id: 1, name: ”张三” } 和 { id: 1, name: ”李四” } 在只考虑 id 字段时他们就是一样的。当然这种类型是我们人为赋予的模式。&lt;/p&gt;

&lt;p&gt;好了，准备工作已做好，我们开始码代码吧。
按照思路 1，相等的模型取第二种，直接上代码如下：
function gtUniqueArr(arr) {
    var i,
        newArr = [];&lt;/p&gt;

&lt;p&gt;function isExist(_item, _arr) {
        var k,
            find = false;
        if (typeof _item !== "object" || _item === null) {
            return _arr.indexOf(_item) &amp;gt; -1;
        }
        for (k in _arr) {
            if (_arr.hasOwnProperty(k)) {
                find = isEqual(_item, _arr[k]);
                if (find) {
                    break;
                }
            }
        }
        return find;
    };
    function isEqual(_a, _b) {
        var k,
            keysA,
            keysB,
            equal = true;
        if (typeof _a !== "object" || _a === null ||
            typeof _b !== "object" || _b === null) { // 有非引用类型（数组与对象）或者有 NULL 类型时直接判断
            return _a === _b;
        }
        // _a _b 不同为数组或者对象时 直接认为不同，否则长得像数组的对象也会互判相等
        if (_a instanceof Array !== _b instanceof Array) {
            return false;
        }
        // 同为对象或者数组
        keysA = Object.keys(_a);
        keysB = Object.keys(_b);
        if (keysA.length !== keysB.length) { // 元素量不同肯定就不是一样了啊
            return false;
        }
        // 其实也可以先判断下引用地址是否一样，一样肯定就相等啦
        for (k in _a) {
            if (_a.hasOwnProperty(k)) {
                equal = isEqual(_a[k], _b[k]);
                if (!equal) {
                    break;
                }
            }
        }
        return equal;
    };&lt;/p&gt;

&lt;p&gt;if (arr &amp;amp;&amp;amp; arr.length) {
        for (i = 0; i &amp;lt; arr.length; i++) {
            if (!isExist(arr[i], newArr)) {
                newArr.push(arr[i]);
            }
        }
    }
    return newArr;
}&lt;/p&gt;

&lt;p&gt;我们可以把 isExist，isEqual 提取成公共函数，按照思路 2，相等类型依然为第二种，上代码：
function gtUniqueArr(arr) {
    var i, j;&lt;/p&gt;

&lt;p&gt;if (arr &amp;amp;&amp;amp; arr.length) {
        for (i = 0; i &amp;lt; arr.length; i++) {
            for (j = i + 1; j &amp;lt; arr.length; j++) {
                if (isEqual(arr[i], arr[j])) {
                    arr.splice(j, 1);
                    j--; // 复原因数组删除导致的遗漏了的元素指向
                }
            }
        }
    }
    return arr;
}
当然，要采取不同的相等模式，只要改变 isEqual 函数即可，此处其他两种相等模式（或者你还有其他假设的相等模式）诉求相对较少，此处便不再展开叙述了（模式 1，直接用===比较两者即可；模式 3，用===检测要求的字段的值是否一样）。&lt;/p&gt;

&lt;p&gt;当我们的环境是 ES6 时，一般的去重标准可以使用 set 来做：
var rs = new Set(arr);
但是当数组元素为引用类型时，引用地址不一样但在我们看来是完全一样的两个元素，这个方法是去不掉的。&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Thu, 17 Aug 2017 14:10:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/33861</link>
      <guid>https://ruby-china.org/topics/33861</guid>
    </item>
    <item>
      <title>详解 Cookie 和 Session 关系和区别</title>
      <description>&lt;p&gt;​
&lt;img src="https://l.ruby-china.com/photo/2017/3202ade5-a1c3-4264-a90a-ea5f2b55bb19.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;作者 | 个推 web 前端架构师 姜季廷&lt;/p&gt;

&lt;p&gt;在技术面试中，经常被问到“说说 Cookie 和 Session 的区别”，大家都知道，Session 是存储在服务器端的，Cookie 是存储在客户端的，然而如果让你更详细地说明，你能说出几点？今天个推君就和大家谈谈“Cookie 和 Session”的那些事儿。&lt;/p&gt;

&lt;p&gt;Cookie 是什么？&lt;/p&gt;

&lt;p&gt;从它的词语本身含义来看：
Cookie：
     n. 饼干；小甜点
    N-COUNT A cookie is a piece of computer software which enables a website you have visited to recognize you if you visit it again. 再次访问某一网站时，能令网站识别访问人的计算机软件。&lt;/p&gt;

&lt;p&gt;Cookie 是客户端保存用户信息的一种机制，用来记录用户的一些信息。如何识别特定的客户呢？cookie 就可以做到。每次 HTTP 请求时，客户端都会发送相应的 Cookie 信息到服务端。它的过期时间可以任意设置，如果你不主动清除它，在很长一段时间里面都可以保留着，即便这之间你把电脑关机了。&lt;/p&gt;

&lt;p&gt;既然它是存储在客户端的，换句话说通过某些手法我就可以篡改本地存储的信息来欺骗服务端的某些策略，那该怎么办呢？我们先按下不表，来看看另外一位朋友 —— Session。&lt;/p&gt;

&lt;p&gt;Session 是什么？&lt;/p&gt;

&lt;p&gt;同样，我们先来看看释义：
Session：
    普通释义：n. 会议；（法庭的）开庭；（议会等的）开会；学期；讲习会
    计算机释义：会话&lt;/p&gt;

&lt;p&gt;Session 是在无状态的 HTTP 协议下，服务端记录用户状态时用于标识具体用户的机制。它是在服务端保存的用来跟踪用户的状态的数据结构，可以保存在文件、数据库或者集群中。在浏览器关闭后这次的 Session 就消失了，下次打开就不再拥有这个 Session。其实并不是 Session 消失了，而是 Session ID 变了，服务器端可能还是存着你上次的 Session ID 及其 Session 信息，只是他们是无主状态，也许一段时间后会被删除。&lt;/p&gt;

&lt;p&gt;实际上 Cookie 与 Session 都是会话的一种方式。它们的典型使用场景比如“购物车”，当你点击下单按钮时，服务端并不清楚具体用户的具体操作，为了标识并跟踪该用户，了解购物车中有几样物品，服务端通过为该用户创建 Cookie/Session 来获取这些信息。&lt;/p&gt;

&lt;p&gt;如果你的站点是多节点部署，使用 Nginx 做负载均衡，那么有可能会出现 Session 丢失的情况（比如，忽然就处于未登录状态）。这时可以使用 IP 负载均衡（IP 绑定 ip_hash，每个请求按访问 ip 的 hash 结果分配，这样每个访客固定访问一个后端服务器，可以解决 Session 的问题），或者将 Session 信息存储在集群中。在大型的网站中，一般会有专门的 Session 服务器集群，用来保存用户会话，这时可以使用缓存服务比如 Memcached 或者 Redis 之类的来存放 Session。&lt;/p&gt;

&lt;p&gt;目前大多数的应用都是用 Cookie 实现 Session 跟踪的。第一次创建 Session 时，服务端会通过在 HTTP 协议中反馈到客户端，需要在 Cookie 中记录一个 Session ID，以便今后每次请求时都可分辨你是谁。有人问，如果客户端的浏览器禁用了 Cookie 怎么办？建议使用 URL 重写技术进行会话跟踪，即每次 HTTP 交互，URL 后面都被附加上诸如 sid=xxxxx 的参数，以便服务端依此识别用户。&lt;/p&gt;

&lt;p&gt;换个姿势~&lt;/p&gt;

&lt;p&gt;客户端和服务端之间的通信交流，可以这样简单理解：
比如当你在个推技术分享沙龙上觉得某位讲师讲得很好，在会后问了他几个问题，他对你这些问题进行了回答，这就是一个会话。但这个讲师太受欢迎，于是工作人员收集问题，并给每个提问者一个号码牌，讲师按照号码牌依次给出相应解答并告诉相应的人。这就是 Session。一段时间后，当你再次遇见这位讲师，他发现你身上有上次回复你的答案，知晓你是那个好学的程序猿。于是你欣喜若狂，哇塞，讲师居然认出我了，这就是 Cookie，你的小甜点。客户端好比听课的技术爱好者，服务端就是这位讲师。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/04f87c31-dd66-4633-a1fb-1e5fc2c02885.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Cookie 还可以在一些方便用户的场景下使用。比如你某次登陆过一个网站，下次登录的时候不想再次输入账号了，怎么办？这个信息可以被写到 Cookie 里面，当访问网站时，网站页面的脚本可以读取这个信息，自动填写用户名，方便用户使用，给用户一点甜头。&lt;/p&gt;

&lt;p&gt;总结语：&lt;/p&gt;

&lt;p&gt;1、Cookie 在客户端（浏览器），Session 在服务器端。
2、Cookie 的安全性一般，他人可通过分析存放在本地的 Cookie 并进行 Cookie 欺骗。在安全性第一的前提下，选择 Session 更优。重要交互信息比如权限等就要放在 Session 中，一般的信息记录放 Cookie 就好了。
3、单个 Cookie 保存的数据不能超过 4K，很多浏览器都限制一个站点最多保存 20 个 Cookie。
4、Session 可以放在 文件、数据库或内存中，比如在使用 Node 时将 Session 保存在 redis 中。由于一定时间内它是保存在服务器上的，当访问增多时，会较大地占用服务器的性能。考虑到减轻服务器性能方面，应当适时使用 Cookie。
5、Session 的运行依赖 Session ID，而 Session ID 是存在 Cookie 中的，也就是说，如果浏览器禁用了 Cookie，Session 也会失效（但是可以通过其它方式实现，比如在 url 中传递 Session ID）。
6、用户验证这种场合一般会用 Session。因此，维持一个会话的核心就是客户端的唯一标识，即 Session ID。&lt;/p&gt;

&lt;p&gt;题外话，那么话说 Session Cookie 能被篡改么？
理论上可以，只要改变了连接时的 Session ID 就可以了~&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Mon, 26 Jun 2017 13:13:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/33313</link>
      <guid>https://ruby-china.org/topics/33313</guid>
    </item>
    <item>
      <title>教你如何用 AST 语法树对代码 “动手脚”</title>
      <description>&lt;p&gt;作者 | 刘斌
个推安卓工程师，负责公司移动端项目的架构和开发，主导移动端日志管理平台系统架构和开发工作，熟悉前后端的技术线，参与个推 SDK 主要业务研发工作，善于解决项目中遇到的痛点问题。&lt;/p&gt;

&lt;p&gt;作为程序猿，每天都在写代码，但是有没有想过通过代码对写好的代码”动点手脚”呢？今天就与大家分享——如何通过用 AST 语法树改写 Java 代码。&lt;/p&gt;

&lt;p&gt;先抛一个问题：如何将图一代码改写为图二？&lt;/p&gt;

&lt;p&gt;void someMethod(){
    String rst=callAnotherMethod();
    LogUtil.log(TAG,”这里是一条非常非常长，比唐僧还啰嗦的日志信息描述，但是我短一点还不方便进行错误日志分析，调用 callSomeMethod 返回的结果是:”+rst);
……
}
图一&lt;/p&gt;

&lt;p&gt;void someMethod(){
    String rst=callAnotherMethod();
    LogUtil.log(TAG,”&amp;lt;-(1)-&amp;gt;”+rst);
……
}
图二&lt;/p&gt;

&lt;p&gt;此题需要把代码中和程序逻辑无关的字符串提取出来，替换为 id。比如个推日志输出类，缩短日志描述信息后，输出的日志就随之变短，根据映射表可以恢复真实原始日志。&lt;/p&gt;

&lt;p&gt;通过何种方案改写？&lt;/p&gt;

&lt;p&gt;你可能会想通过万能的“正则表达式”匹配替换，但当代码较为复杂时（如下图所示），使用“正则表达法”则会将问题复杂化，难以确保所有代码的完美覆盖并匹配。若通过 AST 语法树，可以很好地解决此问题。&lt;/p&gt;

&lt;p&gt;import static Log.log;
…
log(“i am also the log”);
String aa=“i am variable string”;
log(“i am the part of log”+ aa +String.format(“current time is %d”,System.currentTimeMillis()));&lt;/p&gt;

&lt;p&gt;什么是 AST 语法树？&lt;/p&gt;

&lt;p&gt;AST（Abstract syntax tree）即为“抽象语法树”，简称语法树，指代码在计算机内存的一种树状数据结构，便于计算机理解和阅读。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/cdde4fe2-b699-488f-ab13-e837acca9239.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;一般只有语言的编译器开发人员或者从事语言设计的人员才涉及到语法树的提取和处理，所以很多人会对这个概念比较陌生。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/bbd70be1-a22d-40e7-85c7-7664670801a4.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上图即为语法树，左边树的节点对应右边相同颜色覆盖的代码块。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/c9195549-4e96-49ab-8c14-ce6b232009f1.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;众所周知，Java 编译流程（上图）中也有对 AST 语法树的提取处理，那是否可以在此环节操作语法树呢？由于编译链代码栈太深，鲜有对外的接口和文档，使得其可操作性不强。不过，如果采用迂回战术如下图所示，可以对其进行操作。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/d6a0069c-cda2-4953-94b8-3aaa33e4e4dc.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;个推 log-rewrite 项目改写日志，就是用 AST 语法树进行的，流程图如下图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/4e879139-d57b-4128-abd9-96d49a3cbea6.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;先把所有源码解析为 AST 语法树，遍历每一个编译单元与单元的类声明，在类声明里根据日志方法的签名找到所有的方法调用，然后遍历每个方法调用，将方法调用的第二个参数表达式放入递归方法，对字符串字面值进行改写。&lt;/p&gt;

&lt;p&gt;对应的代码较为简短，使用 github 的 Netflix-Skunkworks/rewrite开源库与kotlin语言，能读懂Java的你也一定能读明白。&lt;/p&gt;

&lt;p&gt;val JavaSources:List //Java source file path list
OracleJdkParser().parse(JavaSources)
 .forEach { unit -&amp;gt;
   unit.refactor(Consumer { tx -&amp;gt;
       unit.classes.forEach { clazz -&amp;gt;
           clazz.findMethodCalls("demo.LogUtillog(String,String)").forEach{ mc -&amp;gt;
               val args = mc.args.args
               val expression = args[1]
               logMapping.refactor(clazz, expression, tx)
            }
       }
        val fix = tx.fix()
        val newFile = ...//dist Source File ...
       newFile.writeText(fix.print())
    })
}
fun refactor(clazz: Tr.ClassDecl, target: Expression, refactor: Refactor, originSb: StringBuilder): Unit {
        when(target) {
           is Tr.Literal -&amp;gt; {
               refactor.changeLiteral(target) { t -&amp;gt;
                        val id = pushMapping(clazz, t) //pushLiteral to mapping and return id
                        originSb.append("$PREFIX$t$POSTFIX")
                        return@changeLiteral rewriteNormal(id)
                    }
               }
           }
           is Tr.Binary -&amp;gt; {
               refactor(clazz, target.left, refactor, originSb)
               refactor(clazz, target.right, refactor, originSb)
            }
       }
}&lt;/p&gt;

&lt;p&gt;如果想将日志恢复原样，可根据前缀、后缀定制正则表达式，逐行匹配替换。如下图所示。&lt;/p&gt;

&lt;p&gt;val normalPattern = Pattern.compile("(&amp;lt;!--\[([^|]+)\|(\d+)_(\d+):(\d+)]--&amp;gt;)")
logFiles.forEach { file -&amp;gt;
file.bufferedReader().use { reader -&amp;gt;
   File(distDir, file.name).bufferedWriter().use { writer -&amp;gt;
        var line: String
        while(true){
           line = reader.readLine()
           if (line == null) break
           val matcher = normalPattern.matcher(line)
           var newLine: String = line + ""
           while (matcher.find()) { //normal recover
               val token = matcher.group(1)
               val projectName = matcher.group(2)
               val appVersion = matcher.group(3).toInt()
               val targetVersion = matcher.group(4).toInt()
               val id = matcher.group(5).toLong()
               val replaceMent = findReplacement(projectName,appVersion, targetVersion, id)
               newLine = newLine.replace(token, replaceMent)
           }
           writer.write(newLine)
           writer.newLine()
       }
     }
 }&lt;/p&gt;

&lt;p&gt;AST 有哪些应用场景？&lt;/p&gt;

&lt;p&gt;1、编译工具从 ant 到 gradle 的切换&lt;/p&gt;

&lt;p&gt;the ant env SDK_VERSION=2.0.0.2
// #expand public static final Stringsdk_conf_version = "%SDK_VERSION%";
publicstaticfinalString sdk_conf_version = "1.0.0.1";&lt;/p&gt;

&lt;p&gt;publicstaticfinalString sdk_conf_version = “2.0.0.2";
//public static final String sdk_conf_version= "1.0.0.1";&lt;/p&gt;

&lt;p&gt;此项目起步于 ant 主流时期，随着技术日渐成熟，gradle 逐渐取代了 ant 的位置，演变成官方的编译打包方式。因为历史原因，若直接将上图类似预编译的代码切换到 gradle 较为棘手，通过 AST 语法树重写，再用 gradle 编译，就可以解决此问题。&lt;/p&gt;

&lt;p&gt;try{
    value = Boolean.parseBoolean(str);
} catch (Throwable e) {
    // #debug
    e.printStackTrace();
}&lt;/p&gt;

&lt;p&gt;try{
    value = Boolean.parseBoolean(str);
} catch (Throwable e) {&lt;/p&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;void m(){
    relaseCall();
    //#mdebug
    String info="some debug infomation";
    LogUtil.log(info);
    //#enddebug
}&lt;/p&gt;

&lt;p&gt;void m(){
    relaseCall();
}&lt;/p&gt;

&lt;p&gt;上图的#debug 和#mdebug 指令，也可以通过 AST 改写之后再进行编译。&lt;/p&gt;

&lt;p&gt;2、自动静态埋点&lt;/p&gt;

&lt;p&gt;void onClick(View v){
    doSomeThing()
}&lt;/p&gt;

&lt;p&gt;void onClick(View v){
    RUtil.recordClick(v); 
    doSomeThing();
}&lt;/p&gt;

&lt;p&gt;代码中需要运营统计、数据分析等，需要通过代码埋点进行用户行为数据收集。传统的做法是手动在代码中添加埋点代码，但此过程较为繁琐，可能会对业务代码造成干扰，倘若通过改写 AST 语法树，在编译打包期添加这种类似的埋点代码，就可减少不必要的繁琐过程，使其更加高效。&lt;/p&gt;

&lt;p&gt;最后附推荐操作 AST 类库链接&amp;amp;完整项目源码地址，希望可以帮助大家打开脑洞，设想更多的应用场景。&lt;/p&gt;

&lt;p&gt;推荐操作 AST 类库链接
&lt;a href="https://github.com/Netflix-Skunkworks/rewrite" rel="nofollow" target="_blank"&gt;https://github.com/Netflix-Skunkworks/rewrite&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/Javaparser/Javaparser" rel="nofollow" target="_blank"&gt;https://github.com/Javaparser/Javaparser&lt;/a&gt;
&lt;a href="https://github.com/antlr/antlr4" rel="nofollow" target="_blank"&gt;https://github.com/antlr/antlr4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;完整项目源码地址如下，欢迎 fork&amp;amp;start
&lt;a href="https://github.com/foxundermoon/log-rewrite" rel="nofollow" target="_blank"&gt;https://github.com/foxundermoon/log-rewrite&lt;/a&gt;&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Mon, 15 May 2017 10:29:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/32991</link>
      <guid>https://ruby-china.org/topics/32991</guid>
    </item>
    <item>
      <title>个推首席架构师 Qcon 分享 | 微服务架构的那些事儿</title>
      <description>&lt;p&gt;什么是微服务？&lt;/p&gt;

&lt;p&gt;传统的单体服务架构是单独服务包，共享代码与数据，开发成本较高，可维护性、伸缩性较差，技术转型、跨语言配合相对困难。而微服务架构强调一个服务负责一项业务，服务可以单独部署，独立进行技术选型和开发，服务间松耦合，服务依赖的数据也独立维护管理。虽然微服务存在部署复杂、运维难度较大、分布式事务控制难、容错要求高等缺点，但总体而言，微服务的优点远大于其复杂性。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/8c2674d9-249a-4109-b265-825f3958c6de.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;微服务架构需要注意哪些问题？&lt;/p&gt;

&lt;p&gt;微服务架构，首先考虑客户端与服务端之间的通信问题。有两种解决办法，一是客户端与多个服务端直接进行通信，但存在对外暴露接口细节、众多接口协议无法统一、客户端的代码复杂、服务端升级相对困难等问题。二是客户端访问统一的 API 网关，由 API 网关调用众多服务接口，易实现统一通信协议，降低客户端和服务端代码耦合，也可以达到统一的鉴权和流控，然而此方式存在延时增加的风险，可能使 API 网关成为系统发展的瓶颈。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/c4b3c922-774f-4429-b915-f1898a9ea8ea.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;微服务架构是分布式服务架构，如何进行服务的注册和发现也是需要解决的问题。一种是通过客户端发现，调用方需要知道所有依赖服务的地址，开发成本较高，协议升级比较困难。另一种是通过统一网关发现具体服务的地址，对客户端来说实现比较简单，能够在网关进行统一鉴权和流控，要求网关高度可用性。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/6ae5620f-a4e7-4f4a-a666-abdfa6a2ed59.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;调用微服务尽可能做到有序，避免相互调用，杜绝循环调用。服务间要有清晰的层次关系，上层服务可以依赖下层服务，如果遇到下层服务需要同步消息给上层调用方的情况，可以考虑通过消息队列异步解耦，比如订单/审核系统在创建订单或者改变订单状态时，可以考虑使用双写（即写入数据库后，同时在消息队列也写一份），异构系统（比如订单执行系统）可以通过订阅消息保存异构数据。&lt;/p&gt;

&lt;p&gt;个推在微服务上有哪些实践？&lt;/p&gt;

&lt;p&gt;个推的服务场景主要有三种。其一是个推推送场景，通过 Java 语言开发，SOA 的架构方式来实现，保证信息推送的实时性与高并发性，这块微服务改造比较困难；其二是广告交易平台，比如投放平台、DMP 数据管理，以 Java 为主进行开发，对并发数要求较高，我们在逐步进行基于 Java 的微服务框架的微服务化尝试；其三是 web 业务系统，它为前端提供无状态的业务 API 接口，是典型的 request/response 方式，同时，这是我们目前微服务实践最多的场景。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/4d5f7140-4104-4e7d-966c-1ae5e531931a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;随着业务快速发展，公司 web 相关的业务系统开发需求不断增加，这些系统都涉及到用户管理，后台管理，权限管理等。为进一步提高团队开发效率，我们对服务进行平台化、模块化改造，选择了如上的技术选型。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/c3783d6a-10ec-47dc-8da6-120d4ef60bf5.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;个推的整体架构如上图所示。请求先经过 LVS/HaProxy，到达基于 OpenResty 实现的 API 网关，API 网关会根据请求将流量 upstream 到服务单元。服务单元作为一个整体，支持通过 Lua、Node.js、Java 等语言实现业务逻辑，启动时向 Zookeeper 注册，API 网关会从 Zookeeper 获取服务单元部署信息。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/c43b42cf-1223-4f4b-8972-fd3bc7d78889.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;服务单元如上图所示。它由 Openresty 统一对外暴露服务端，Openresty 内置了 lua 语言，可以在 weblua 框架中通过编写 lua 程序来进行业务逻辑处理。Openresty 通过 proxy_pass,upstream 将请求转给 webnode 处理，也可以在 Openresty 中通过配置处理 Java 业务逻辑。服务单元就像一个抽屉柜，具体的业务（app）就像一个抽屉，只要尺寸符合抽屉柜的要求，就能将抽屉推入到抽屉柜中，抽屉所用的语言不作要求。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/cb8f9028-02b6-4032-a89e-089305f2a609.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Openresty 集成了 lua 脚本作为编程语言，使用 Zookeeper 服务注册。为了解决 lua 与 Zookeeper 不适配问题，在 Openresty 启动时启动 websocket 服务，Node.js 进程启动时同步启动 websocket 的 client，这样每个 Node.js 进程会和 Openresty 保持长连接和心跳，Openresty 会选择一个 Node.js 进程，启动 Zookeeperclient 完成服务注册。API 网关也是基于服务单元通过类似的方式实现完成服务注册的。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/b41ae38e-4652-40c6-be60-da326e8edc79.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;服务单元在 Openresty 层完成了统一鉴权，不会产生额外的性能开销。&lt;/p&gt;

&lt;p&gt;我们可以用“通道化”来阐述服务间的调用问题。我们将微服务之间的调用比喻成水管，从水管的一端流进去的是请求信息，那么从另一端流出来的也应该是请求信息，我们只要求流入流出的信息保持一致，而不关心这些信息在传输过程中经过了转换或其他过程。如上图，A、B 之间的调用通过进程内 require 就能实现，而 A、C 间的调用是通过服务单元内的 http 完成的。&lt;/p&gt;

&lt;p&gt;Q&amp;amp;A 环节&lt;/p&gt;

&lt;p&gt;Q：微服务架构在运维部署是否会很麻烦？&lt;/p&gt;

&lt;p&gt;A：随着微服务的不断推进，服务数量势必会越来越多，这就需要考虑 DEVOPS。比如代码提交之后通过 Jenkins 自动触发打包编译，自动生成版本号，进而触发自动测试部署和自动化测试，测试通过后进行一键部署升级、支持升级失败自动回滚等。我们目前已经实现了自动打包和测试部署，后续环节正在推进。&lt;/p&gt;

&lt;p&gt;Q：Openresty 里对 Session 管理进行了处理，开发人员会不会觉得不方便？&lt;/p&gt;

&lt;p&gt;A：在引入微服务框架之前，公司有很多独立业务服务，每个服务都有自己的账号系
统实现登录验证逻辑。虽然有现成的代码模块可以用，但每次都需要重新测试验证。如今，具体的业务服务都可以通过 Openresty 完成鉴权并统一对外，对开发和测试人员来说，反而减少了一定的工作量。&lt;/p&gt;

&lt;p&gt;以上内容是俞锋锋 Qcon 大会的《基于 OpenResty 和 Node.js 的微服务架构实践》演讲的主要内容，如有问题，欢迎与我们留言探讨。&lt;/p&gt;</description>
      <author>HongJack</author>
      <pubDate>Wed, 26 Apr 2017 14:06:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/32870</link>
      <guid>https://ruby-china.org/topics/32870</guid>
    </item>
  </channel>
</rss>
