GVKun编程网logo

使用 Server-Side Swift 开发 RESTful API(swift服务端开发)

1

本文的目的是介绍使用Server-SideSwift开发RESTfulAPI的详细情况,特别关注swift服务端开发的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解使

本文的目的是介绍使用 Server-Side Swift 开发 RESTful API的详细情况,特别关注swift服务端开发的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解使用 Server-Side Swift 开发 RESTful API的机会,同时也不会遗漏关于Angular 6 Server Side Errror:找不到模块:错误:无法解析’./dist/server/main.bundle’、Apache Spark 探秘:实现 Map-side Join 和 Reduce-side Join、Client Side Cache 和 Server Side Cache 的区别、dubbo异常Waiting server-side response timeout by scan timer的知识。

本文目录一览:

使用 Server-Side Swift 开发 RESTful API(swift服务端开发)

使用 Server-Side Swift 开发 RESTful API(swift服务端开发)

责编:唐小引,技术之路,共同进步。欢迎技术投稿、给文章纠错,请发送邮件至tangxy@csdn.net。

声明: 本文为《程序员》原创文章,未经允许请勿转载,更多精彩文章请订阅2017 年《程序员》。


【导语】Swift 自发布以来就备受众多 Apple 开发者关注,但由于 API 尚不稳定,系统没有内置 Framework 导致 App 包增大等问题,使得线上主力使用的公司还很少,不少客户端开发者都还没有机会使用 Swift 进行开发。等到 2015 年 12 月 Swift 开源并正式支持 Linux 系统,广大 Apple 开发者迎来了更广泛的开发场景,可以用它来进行服务端开发。不到一年时间各种 Server-Side Swift Web Framework 相继问世,其中以 Kitura、Perfect、Vapor、Zewo 最为成熟。


文章正式开始前,我们先对当前几款主流框架进行了解与对比。


Kitura 是 IBM 推出的框架,使用 IBM Cloud Tools for Swift 管理组件依赖,并支持部署代码到 IBM 的云服务 Bluemix。另外还有一个在线 Swift 编码网站,可以看作是线上 GUI 版本的 Swift REPL,开发者可以直接在 Web 上编写代码并查看输出。Kitura 整个产品从代码编写到部署全部包揽,提供了完整的生态环境。


Perfect 拥有 GitHub 上最多的 Star,各种功能组件和数据库连接工具也最为齐全。近期推出的 Perfect Assistant 是运行在 macOS 上的管理工具,同样支持组件依赖管理,自动化代码部署(支持 AWS、Azure),并通过调用本地 Docker 的方式实现了在 macOS 上编译 Linux 产物的功能。


Vapor 以其友好的文档和 Pure Swift 代码实现著称,其 HTTP Parser 是使用 Swift 编写实现,而不像 Kitura 和 Perfect 是用 CHTTPParser 封装,这对最终的服务性能有很大影响。Vapor 还开发了命令行工具对 SPM 进行封装,好处是开发体验更好,但提高了学习成本。另外 Vapor 比较早就做了 ORM 工具 Fluent,整体感觉十分技术范、小清新。


Zewo 是一系列开源组件的集合平台,特点是使用 libmill 实现了类似 Go 的协程功能,模块化的设计也不同于其他的框架。


这些框架在各具特色的前提下都有高性能、易扩展等优点。正好部门内部有一个信息管理平台项目,需求很简单,只要有基本的增删改查就行,于是不用麻烦后端同学排期,可以自己来开发,也算是提前实践 Swift,积累经验。


正式开发是在 2016 年 8 月,彼时 Swift 3 尚未发布,Beta 版本的 Toolchain 每周都在更新,框架也在积极跟进发布支持最新版本的 Toolchain。技术选型期间我先后尝试了 Kitura、Vapor 和 Perfect。Kitura 的整套产品耦合太紧密,用起来比较重,对于轻量级小项目并不合适。Vapor 一开始用起来很愉快,但写到数据库连接工具时一直无法连接成功,再加上当时还在 Beta 版本,问题不少也被弃用。最后,使用 Perfect 完成了项目研发。接下来,本文将着重介绍如何使用 Perfect 完成一套 RESTful API 的开发,希望能够对大家进行 Swift Server 端开发有所裨益。


在编写代码前,要先了解目前开源的 Swift 项目包括了 Swift、SPM 以及配套的编译调试工具,在核心库方面有 libdispatch、Foundation、XCTest 这三个项目。在客户端开发中,Foundation 是最常用的工具库,它提供了一系列国际化、系统无关的 API。服务端项目增加 Foundation 支持可以统一开发体验和复用客户端代码,尤其是和系统无关的 API 可以大大增加可移植性,本属于 Swift 3.0 的组成部分但至今并没有开发完成,原因在于 Foundation 中用到了一些 Objective-C Runtime 的代码,而这部分代码并不在开源范围之内。于是在开发中需要用到 Foundation 库时就会碰到不少问题。


环境配置


macOS 上依然是 Xcode 搞定一切事情,Linux 上目前只支持两个版本的 Ubuntu,所以推荐使用 Docker 搭建 Swift 编译和执行环境,这样可以支持所有 Linux 系统,也方便在 Swift 快速迭代时及时更新 Toolchain。代码都用 SPM 管理,在实际使用中还是有些问题,比如不支持 MysqL 5.7,创建工程文件配置时漏掉了编译设置,寻找公共代码库路径在不同操作系统上没有适配等。


开发中使用了两个第三方库 SwiftLog 和 MysqL-Swift。SwiftLog 支持自定义日志级别和增量写入的日志文件,并使用了喵神的 Rainbow 在 Linux 环境下输出彩色日志。MysqL-Swift 支持 MysqL 连接池复用,可以提高访问数据库的性能。


部署环境使用 CentOS 作为宿主机,开启两个 Docker 实例分别运行 Perfect 和 MysqL,两个 Docker 实例通过 link 方式实现通信。使用 link 参数运行 Docker 实例,主 Docker 的 hosts 文件会增加从 Docker 的 host 信息,从而达到通过 Docker 别名进行通信的效果。初始化 MysqL 容器时可以将数据库文件路径设置到虚拟卷中,再使用 crontab 执行定期任务运行 AutoMysqLBackup 来备份数据。


编码


先初始化工程,使用 swift package init 新建工程,此时会生成 Package.swift 配置文件和源代码、测试代码目录。在 Package.swift 中添加 Perfect、SwiftLog、MysqL-Swift 后执行 Swift Build 即可拉取所有依赖代码。此时的目录并不包含 Xcode 工程文件,需要再执行命令 swift package generate-xcodeproj 生成,工程中各个依赖库的配置都通过自身的 Package.swift 配置文件读取。


SPM 不仅是去中心化的包管理器,它还可以编译出可执行文件,我们甚至能够直接在服务端编写代码并编译运行。如果说在使用 Objective-C 开发时用 Xcodebuild 开发自定义打包工作流,那么开发 Server-Side Swift 就需要使用 SPM 在服务端实现编译、打包等流程。虽然在服务端目前还有些兼容问题,但 SPM 作为 Swift 的组成部分,一直在快速改进与提高。


然后,添加 API 路由,我们先添加路由配置,后面再将所有配置一起设置到 Server 对象上。Perfect 的路由 API 设计参考了 Express,只需要设置 HTTP 请求类型、路由地址和对应的处理函数即可,支持使用通配符与参数,用起来还算简洁。我们可以先设置基础 API 路径,再通过 Routes().add 方法添加自定义路由,这样所有的路由都被添加到指定 API 路径下。最后将路由对象输出一下,这样开启服务时会将所有注册的路由输出到日志。

func makeURLRoutes() -> Routes {
   var routes = Routes()    
   var apiRoutes = Routes(baseUri: "/api")    
   var api = Routes()    api.add(method: .get,uri: "/staff/{id}",handler: fetchStaffById)    apiRoutes.add(routes: api)    routes.add(routes: apiRoutes)    SLogWarning("\(routes.navigator.description)")    
   return routes }

Swift 没有 define 关键字,但可以通过 typealias 自定义类型名称,比如 Perfect 里的路由处理回调函数就被定义为 public typealias RequestHandler = (HTTPRequest,HTTPResponse) -> ()。从定义可以看到,响应函数有两个参数,HTTPRequest 包含了客户端请求的所有信息,包括 HTTP headers 和 content data。HTTPResponse 包括 HTTP headers、body 以及 HTTP Status,这些属性可以在函数中赋值并返回给客户端。


接着来实现路由函数。先将请求参数拿到,将参数进行错误处理后调用数据库工具方法获取信息,并根据获取数据的成败做对应的处理,最后返回 JSON 格式的结果。


在路由处理函数最后都需要调用 response.completed()方法通知 Server 回调处理完成,由于函数有几个结束点,在参数错误或者获取数据的时候都可能抛出异常并提前结束函数,所以需要在好几个地方执行 response.completed()方法。Swift 和 Golang 一样拥有 defer 关键字,我们可以在函数中使用它来完成资源回收或这种需要多处调用的代码。

defer {
    response.completed()
}

进行数据库请求和生成 JSON 返回值的操作都有可能抛出异常,这样在 do-catch 中会抛出两种类型的异常,我们可以使用 switch-as 语法针对不同类型的异常进行处理。

do {
   let staffData = try StaffDataBaseUtil.sharedInstance.searchStaffByID(idString)    
   try response.setBody(json: jsonBody(errorCode: returnCode,data: ["staff": staffData])) } catch let error {  
   switch error {
       case let error as QueryError:      
        // 数据库请求出错    default:      
        // 服务端出错  } }

Perfect 并没有使用 SwiftyJSON 之类的第三方库,而是自己实现了很好用的 JSON 扩展,对常用的数据类型增加了 JSON 序列化和反序列化方法,Swift 的 Extension 在这里得到了充分的使用。

extension Dictionary: JSONConvertible {
   /// Convert a Dictionary into JSON text.    public func jsonEncodedString() throws -> String {        
       var s = "{"        var first = true        for (k,v) in self {            guard let strKey = k as? String else {                
               throw JSONConversionError.invalidKey(k)            }            
       if !first {                s.append(",")            } else {                first = false            }            s.append(try strKey.jsonEncodedString())            s.append(":")            s.append(try jsonEncodedStringWorkAround(v))        }        s.append("}")        
       return s    } }

数据获取需要进行数据库请求,我们使用 MysqL-Swift 作为数据库连接工具,为的是使用连接池复用连接,但也给自己挖了个坑。在 Mac 上开发时都没有问题,但在服务器端就编译失败,后来发现有一部分的代码还不支持 Linux,原来是时区的问题。在连接数据库时需要传入一个 config,包括数据库地址、端口、密码等,其中就有时区配置。在从数据库获取数据时,如果字段中有日期类型,就会将获取的绝对时间转换为对应时区的时间字符串。在日期类型数据的处理部分用到了 NSTimezone,但这一类型在 Linux 上没有实现,于是编译失败。修复的方式很简单,使用 CFTimezone 传递信息就好,但 CFTimezone 返回的类型是 CFString,于是又要针对 macOS 和 Linux 实现不同的 CFString 转换到 String 的代码。如果 Swift 有一套跨平台的 Foundation 就不会出现这个问题了。


MysqL-Swift 底层使用的是 CMysqL 库进行连接。根据 options 初始化数据库连接,将连接保存到连接池中,这样后续的数据库操作不需要再次重新连接数据库。再对每个连接对象添加是否正在使用的标记,如果当前连接池中的所有连接都在使用当中,则再次新增一个数据库连接对象进行操作。可以手动设置连接池的最大连接数,当连接池中的连接数到达最大连接数时,后续的请求将会抛出获取数据库连接失败异常。


MysqL-Swift 将查询方法封装的十分优雅,工具类在初始化时候根据提前设置好的 config 生成连接池,再调用 pool.execute 方法,并传入查询执行的闭包就行。这里用到了高阶函数和范型,语法简洁又安全。使用的时候不需要关心数据库连接对象的创建和状态,只需要直接使用闭包里传进来的 Connection 连接对象进行查询即可。

private init() {
    pool = ConnectionPool(options: DBConfigOption)
}let result: [Staff] = try pool.execute { conn in
    try conn.query(querysql)
}

对于同名不同返回值的函数,Swift 会针对代码中返回值的类型推断进行分析,确定最终运行时调用哪一个函数。上面 conn.query 函数就会根据不同的返回值执行不同的函数,所以在编写上面代码的时候一定要显示的声明返回的是 Staff 数组,不然函数返回的结果就会不同。另外在 query 的实现中可以看到,最后的不同返回结果是从同一个函数返回的 tuple 中拿到的,这样的代码阅读起来很有效率。

public func query<t: queryrowresulttype="">(_ query: String,_ args: [QueryParameter] = []) throws -> ([T],QueryStatus) {
   return try self.query(query: queryString) } public func query<t: queryrowresulttype=arameter] = []) throws -> [T] {    
   let (rows,_) = try self.query(query,args) as ([T],QueryStatus)    
   return rows } public func query(_ query: arameter] = []) throws -> QueryStatus {    
   let (_,status) = try self.query(query,args) as ([EmptyRowResult],QueryStatus)    
   return status }</t:></t:>

拿到数据后可以将结果转换为自定义的数据类型。这里会将数据库查询结果解码为定义了默认值的结构体,并提供序列化方法给路由处理函数。

static func decodeRow(r: QueryRowResult) throws -> Staff {
   return try Staff(        id: r <| 0,       name: r <| "name",       department: r <| "department",       timestamp: r <| "timestamp"    ) }

在开发完成后的测试中发现返回数据的时间戳总是差几个小时,但是直接查看数据库里的数据,时间戳又是正确的,于是一步步从获取数据到返回 response 调试看看哪里出的问题,最后发现是 Swift 时区没有自动设置为和系统相同,而一直是 UTC 时间,在生成时间戳文案的时候就出错了。


所以在使用 Dateformatter 的时候需要手动设置时区,这也是 Foundation 的一个坑。

func resultDic() -> Dictionary<string,any=""> {
   let dateFormatter = DateFormatter()    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"    dateFormatter.timeZone = TimeZone(identifier: "Asia/Shanghai")    let dateString = dateFormatter.string(from: timestamp.date())    return Dictionary<string,68); Box-sizing: border-Box !important; word-wrap: break-word !important;">"">.init(dictionaryLiteral:        ("id",id),       ("timestamp",dateString)    ) }</string,></string,>

在拿到数据后,可以将 HTTP Response 包装一下,在每个返回结果中都包括错误码,数据和时间戳这三个字段,并增加错误码对应的错误提示。这样不用在每个路由处理函数的最后都手动写一次。

public func jsonBody(errorCode: ErrorCode,data: Dictionary<string,68); Box-sizing: border-Box !important; word-wrap: break-word !important;">"">?) -> [String: Any] {
   var body = [String: Any]()    body["s"] =  ["code": errorCode.rawValue,"desc": errorCode.description]    
   if let jsonData = data {        body["data"] = jsonData    }    body["t"] = UInt(Date().timeIntervalSince1970)    
   return body }<至此代码基本编写完成,为了方便调试,我们可以通过 Perfect 的 Filter 方法添加自定义日志,将每次的 HTTP 请求和返回的信息输出。这样我们不需要在每个路由处理函数中调用日志方法就可以输出所有请求的参数和最终返回的结果。

func incomeMiddleware(request: HTTPRequest) {
    SLogInfo("Request URL: " + request.uri)
    SLogInfo("Request Method: " + request.method.description)
    SLogVerbose("Request Params: " + String(describing: request.params()))
   for (name,detail) in request.headers {        SLogVerbose("Request HEADER: " + name.standardName.uppercased() + " -> " + detail)    } }

我使用的 SwiftLog 作为日志工具,没想到在部署测试的时候也出了问题,表现为开启服务的时候可以输出日志,一旦请求进来就崩溃,抛出 Segmentation fault。但是代码在开发机上运行正常,一路调试下来发现是 “(Date())” 的问题,这段代码第一次执行没问题,第二次执行就会导致崩溃。直接在服务端用 REPL 执行两次 pring(Date())也发生了相同的崩溃。推断应该是 Date 对象的 description 代码执行出错,于是自定义 Date 对象的 description 方法,避免调用自带的方法,问题解决。


所有配置和路由函数开发完成后,开始设置 server 对象。Perfect 支持静态文件路由,可以设置静态文件的路径。这里推荐针对 macOS 设置静态文件路径单独设置,因为 Xcode 编译出的可执行文件并不在代码目录,因此在本地调试时候会出现找不到静态文件的问题。

#if os(OSX)
server.documentRoot = "~/webroot"
#elseif os(Linux) server.documentRoot = "./webroot"
#endif

代码开发完成后,使用 Docker 初始化 Swift 实例并拉取代码,使用 swift build -c release 生成可执行文件,将可执行文件和 libCHTTPParser.so、libcopenSSL.so、libLinuxBridge.so 三个依赖库文件复制出来提交到目标 Docker 中即完成部署。目前 Swift 官方只支持 Ubuntu 系统,也有人尝试在 CentOS 上手动编译 Swift 源码,但由于缺少官方的全面测试所以不推荐在生产环境使用。


总结


Swift 作为服务端开发语言的新成员,有着不少的先天优势,比如智能的类型推导、强大的协议扩展、丰富好用的语法糖,这也是官方宣传的 Safe、Fast 和 Expressive 的具体体现。开源后的 Swift 吸引了更多的开发者参与其中,从 4.0 演进表也可以看到更多强力且有趣的功能包括反射、并发、稳定的 ABI 等。未来是美好的,现实是残酷的,以目前国内的 Swift 开发生态环境,在客户端尚且无法占据主流位置,更不用想挑战 Java、PHP、Python 等语言在服务端的地位。想要用 Swift 替代各大公司线上成熟的开发方案是不现实的,但可以从小做起,从辅助工具之类的角度着眼,先做出广泛使用的产品,逐步找到自己的定位,再扩展使用场景。私以为 Go 在这方面做的很好,Docker 的流行让更多人知道了 Go 这门语言且证明了其实力。


目前看来 Swift 最需要解决的是 ABI 稳定性和跨平台兼容两大问题,对于 ABI 来说,之所以到 3.0 版本还没有稳定下来,是开发小组认为目前稳定 ABI 将无法去掉现有实现中错误的部分,且很可能带着补丁开发后续版本将成倍提高今后的开发难度。越早提交的代码留存率越低。这对于语言的开发是件好事,不用带着很多历史包袱开发新功能。但对于开发者来说这意味着在语言的新版本发布后不能方便的快速跟进,除非所有依赖的 Swift 代码库都及时跟进并发布基于新版本编译的代码库,这会大大降低使用 Swift 开发的积极性。


另外 Server-Side Swift 目前只支持 Ubuntu 系统,Foundation 的移植也还在进行当中,并且各种兼容 Bug 频出,在开发过程中很容易遇到开发环境和部署环境运行效果不同的情景。好消息是针对后面的问题,Swift 开发团队成立了 Server APIs Work Group,工作组的目标就是提供服务端跨平台 API,消除平台相关代码差异,提供基础框架功能代替 C 库的引入,进一步降低服务端的开发门槛,提高客户端代码的可移植性。同时 Swift 3.1 的修改内容中明确说明了会改进 Swift on Linux 和 SPM 的质量,期待 2017 年春天发布的这个版本会给 Server-Side Swift 带来显著的改进。



【长按识别立即关注】 

 品读之后,愿有所获。

Angular 6 Server Side Errror:找不到模块:错误:无法解析’./dist/server/main.bundle’

Angular 6 Server Side Errror:找不到模块:错误:无法解析’./dist/server/main.bundle’

我正在开发一个项目,将其更新为Angular 6.更新后,我在尝试运行服务器端渲染构建时遇到此错误
Module not found: Error: Can't resolve './dist/server/main.bundle'

我尝试使用https://angular.io/guide/universal并将我的代码与Angular Universal中的SSR文件相匹配.这对我不起作用.

似乎dist文件夹没有生成/ server文件夹,但正在创建/浏览器.我不确定为什么.

这是我的angular.json文件

{
 "$schema": "./node_modules/@angular/cli/lib/config/schema.json","version": 1,"newProjectRoot": "projects","projects": {
    "xilo": {
      "root": "","sourceRoot": "src","projectType": "application","architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser","options": {
            "outputPath": "dist/browser","index": "src/index.html","main": "src/main.ts","tsConfig": "src/tsconfig.app.json","polyfills": "src/polyfills.ts","assets": [
              "src/assets","src/favicon.ico"
            ],"styles": [
              "src/styles.css","node_modules/font-awesome/css/font-awesome.min.css"
            ],"scripts": [
              "node_modules/moment/min/moment.min.js"
            ]
          },"configurations": {
            "production": {
              "optimization": true,"outputHashing": "all","sourceMap": false,"extractCss": true,"namedChunks": false,"aot": true,"extractLicenses": true,"vendorChunk": false,"buildOptimizer": true,"fileReplacements": [
                {
                  "replace": "src/environments/environment.ts","with": "src/environments/environment.prod.ts"
                }
              ]
            }
          }
        },"serve": {
          "builder": "@angular-devkit/build-angular:dev-server","options": {
            "browserTarget": "xilo:build"
          },"configurations": {
            "production": {
              "browserTarget": "xilo:build:production"
            }
          }
        },"extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n","options": {
            "browserTarget": "xilo:build"
          }
        },"test": {
          "builder": "@angular-devkit/build-angular:karma","options": {
            "main": "src/test.ts","karmaConfig": "./karma.conf.js","tsConfig": "src/tsconfig.spec.json","scripts": [
              "node_modules/moment/min/moment.min.js"
            ],"src/favicon.ico"
            ]
          }
        },"lint": {
          "builder": "@angular-devkit/build-angular:tslint","options": {
            "tsConfig": [
              "src/tsconfig.app.json","src/tsconfig.spec.json"
            ],"exclude": [
              "**/node_modules/**"
            ]
          }
        },"server": {
          "builder": "@angular-devkit/build-angular:server","options": {
            "outputPath": "dist/server","main": "main.server.ts","tsConfig": "tsconfig.server.json"
          }
        }
      }
    },"xilo-e2e": {
      "root": "","sourceRoot": "e2e","architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor","options": {
            "protractorConfig": "./protractor.conf.js","devServerTarget": "xilo:serve"
          }
        },"options": {
            "tsConfig": [
              "e2e/tsconfig.e2e.json"
            ],"exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },"defaultProject": "xilo","schematics": {
    "@schematics/angular:class": {
      "spec": false
    },"@schematics/angular:component": {
      "spec": false,"inlinestyle": true,"inlineTemplate": true,"prefix": "app","styleext": "css"
    },"@schematics/angular:directive": {
      "spec": false,"prefix": "app"
    },"@schematics/angular:guard": {
      "spec": false
    },"@schematics/angular:module": {
      "spec": false
    },"@schematics/angular:pipe": {
      "spec": false
    },"@schematics/angular:service": {
      "spec": false
    }
  }
}

server.ts

// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-Metadata';

import { enableProdMode } from '@angular/core';

import * as express from 'express';
import { join } from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const disT_FOLDER = join(process.cwd(),'dist');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory,LAZY_MODULE_MAP } = require('./dist/server/main.bundle');

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

app.engine('html',ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

app.set('view engine','html');
app.set('views',join(disT_FOLDER,'browser'));

// Todo: implement data requests securely
app.get('/api/*',(req,res) => {
  res.status(404).send('data requests are not supported');
});

// Server static files from /browser
app.get('*.*',express.static(join(disT_FOLDER,'browser')));

// All regular routes use the Universal engine
app.get('*',res) => {
  res.render('index',{ req });
});

// Start up the Node server
app.listen(PORT,() => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});

一切都完全按照Angular Universal方向设置.

任何人都知道为什么我的构建脚本不会生成/ server文件夹?

"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server"

的package.json

{
  "name": "XXX","version": "XXX","license": "MIT","repository": {
    "type": "git","url": "XXXXX"
  },"engines": {
    "node": "10.7.0","npm": "6.1.0"
  },"scripts": {
    "ng": "ng","start": "node dist/server","build": "ng build","build:client-and-server-bundles": "ng build --prod && ng build --prod --project xilo --output-hashing=all","build:prerender": "npm run build:client-and-server-bundles && npm run webpack:server && npm run generate:prerender","build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server","deploy": "git push origin master && git push heroku master","generate:prerender": "cd dist && node prerender","postinstall": "npm run build:ssr","webpack:server": "webpack --config webpack.server.config.js --progress --colors","serve:prerender": "cd dist/browser && http-server","serve:ssr": "node dist/server"
  },"private": true,"dependencies": {
    "@angular/animations": "6.1.0","@angular/common": "6.1.0","@angular/compiler": "6.1.0","@angular/compiler-cli": "6.1.0","@angular/core": "^6.1.0","@angular/forms": "6.1.0","@angular/http": "6.1.0","@angular/language-service": "6.1.0","@angular/platform-browser": "6.1.0","@angular/platform-browser-dynamic": "6.1.0","@angular/platform-server": "6.1.0","@angular/router": "6.1.0","@nguniversal/express-engine": "^5.0.0-beta.5","@nguniversal/module-map-ngfactory-loader": "^5.0.0-beta.5","@nicky-lenaers/ngx-scroll-to": "^0.5.0","@types/moment": "^2.13.0","@types/node": "^8.10.21","angular-file": "^0.4.1","angular2-moment": "^1.9.0","classlist.js": "^1.1.20150312","core-js": "^2.5.7","cpy-cli": "^1.0.1","express": "^4.16.3","font-awesome": "^4.7.0","http-server": "^0.10.0","moment": "^2.22.2","ng-circle-progress": "^0.9.9","ng-hotjar": "0.0.13","ng-intercom": "^1.0.0-beta.5-2","ng2-google-charts": "^3.4.0","ng4-geoautocomplete": "^0.1.0","ngx-filter-pipe": "^1.0.2","ngx-loading": "^1.0.14","ngx-pagination": "^3.0.3","reflect-Metadata": "^0.1.10","rxjs": "^6.2.2","rxjs-compat": "^6.2.2","ts-loader": "^4.4.2","typescript": "2.9.2","web-animations-js": "^2.3.1","zone.js": "^0.8.20"
  },"devDependencies": {
    "@angular-devkit/build-angular": "~0.6.8","@angular/cli": "^6.0.8","webpack-cli": "^3.1.0"
  }
}
npm cache clear --force
rm -rf node_modules
npm install

并祈祷.

Apache Spark 探秘:实现 Map-side Join 和 Reduce-side Join

Apache Spark 探秘:实现 Map-side Join 和 Reduce-side Join

Apache Spark 探秘:实现 Map-side Join 和 Reduce-side Join

Category: Frameworks (Spark) On YARN View: 13,756 Author: Dong
作者: Dong  | 新浪微博: 西成懂  | 可以转载,但必须以超链接形式标明文章原始出处和作者信息及 版权声明
网址: http://dongxicheng.org/framework-on-yarn/apache-spark-join-two-tables/
本博客的文章集合: http://dongxicheng.org/recommend/

本博客微信公共账号:hadoop123(微信号为:hadoop-123),分享 hadoop 技术内幕,hadoop 最新技术进展,发布 hadoop 相关职位和求职信息,hadoop 技术交流聚会、讲座以及会议等。二维码如下:


在大数据处理场景中,多表 Join 是非常常见的一类运算。为了便于求解,通常会将多表 join 问题转为多个两表连接问题。两表 Join 的实现算法非常多,一般我们会根据两表的数据特点选取不同的 join 算法,其中,最常用的两个算法是 map-side join 和 reduce-side join。本文将介绍如何在 apache spark 中实现这两种算法。

(1)Map-side Join

Map-side Join 使用场景是一个大表和一个小表的连接操作,其中,“小表” 是指文件足够小,可以加载到内存中。该算法可以将 join 算子执行在 Map 端,无需经历 shuffle 和 reduce 等阶段,因此效率非常高。

在 Hadoop MapReduce 中, map-side join 是借助 DistributedCache 实现的。DistributedCache 可以帮我们将小文件分发到各个节点的 Task 工作目录下,这样,我们只需在程序中将文件加载到内存中(比如保存到 Map 数据结构中),然后借助 Mapper 的迭代机制,遍历另一个大表中的每一条记录,并查找是否在小表中,如果在则输出,否则跳过。

在 Apache Spark 中,同样存在类似于 DistributedCache 的功能,称为 “广播变量”(Broadcast variable)。其实现原理与 DistributedCache 非常类似,但提供了更多的数据 / 文件广播算法,包括高效的 P2P 算法,该算法在节点数目非常多的场景下,效率远远好于 DistributedCache 这种基于 HDFS 共享存储的方式,具体比较可参考 “Performance and Scalability of Broadcast in Spark”。使用 MapReduce DistributedCache 时,用户需要显示地使用 File API 编写程序从本地读取小表数据,而 Spark 则不用,它借助 Scala 语言强大的函数闭包特性,可以隐藏数据 / 文件广播过程,让用户编写程序更加简单。

假设两个文件,一小一大,且格式类似为:

Key,value,value

Key,value,value

则利用 Spark 实现 map-side 的算法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vartable1=sc.textFile(args(1))
vartable2=sc.textFile(args(2))
 
// table1 is smaller, so broadcast it as a map<String, String>
varpairs=table1.map { x=>
  varpos=x.indexOf('','')
  (x.substring(0, pos), x.substring(pos +1))
}.collectAsMap
varbroadCastMap=sc.broadcast(pairs)//save table1 as map, and broadcast it
 
// table2 join table1 in map side
varresult=table2.map { x=>
  varpos=x.indexOf('','')
  (x.substring(0, pos), x.substring(pos +1))
}.mapPartitions({ iter=>
  varm=broadCastMap.value
  for{
    (key, value) <- iter
    if(m.contains(key))
  }yield(key, (value, m.get(key).getOrElse("")))
})
 
result.saveAsTextFile(args(3))//save result to local file or HDFS

(2)Reduce-side Join

当两个文件 / 目录中的数据非常大,难以将某一个存放到内存中时,Reduce-side Join 是一种解决思路。该算法需要通过 Map 和 Reduce 两个阶段完成,在 Map 阶段,将 key 相同的记录划分给同一个 Reduce Task(需标记每条记录的来源,便于在 Reduce 阶段合并),在 Reduce 阶段,对 key 相同的进行合并。

Spark 提供了 Join 算子,可以直接通过该算子实现 reduce-side join,但要求 RDD 中的记录必须是 pair,即 RDD [KEY, VALUE],同样前一个例利用 Reduce-side join 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vartable1=sc.textFile(args(1))
vartable2=sc.textFile(args(2))
 
varpairs=table1.map{x=>
  varpos=x.indexOf('','')
  (x.substring(0, pos), x.substring(pos +1))
}
 
varresult=table2.map{x=>
  varpos=x.indexOf('','')
  (x.substring(0, pos), x.substring(pos +1))
}.join(pairs)
 
result.saveAsTextFile(args(3))

(3)总结

本文介绍了 Spark 中 map-side join 和 reduce-side join 的编程思路,希望对大家有借鉴意义。但需要注意的是,在使用这两种算法处理较大规模的数据时,通常需要对多个参数进行调优,否则可能会产生 OOM 问题。通常需要调优的相关参数包括,map 端数据输出 buffer 大小,reduce 端数据分组方法(基于 map 还是基于 sort),等等。

(4)两个问题

问题 1:如果在 map-side join 中,不使用以下语句对文件 1 进行广播,

var broadCastMap = sc.broadcast(pairs)
也可以在后面程序中直接使用变量pairs存储的数据进行join,这两种方式有什么异同,性能会有何不同?
问题2:将map-side join中的以下语句:


  
  
  
1
2
3
4
5
6
mapPartitions({ iter=>
  varm=broadCastMap.value
  for{
    (key, value) <- iter
    if(m.contains(key))
  }yield(key, (value, m.get(key).getOrElse("")))

改为:

1
2
3
4
varm=broadCastMap.value//这一句放在var table2 = sc.textFile(args(2))后面
map {case(key, value)=>
  if(m.contains(key)) (key, (value, m.get(key).getOrElse("")))
}

最终结果是有问题的,为什么? 本文两个示例程序可以从百度网盘上下载,地址为Spark-Join-Exmaple。

原创文章,转载请注明: 转载自董的博客

本文链接地址: http://dongxicheng.org/framework-on-yarn/apache-spark-join-two-tables/

作者:Dong,作者介绍:http://dongxicheng.org/about/

本博客的文章集合:http://dongxicheng.org/recommend/

Client Side Cache 和 Server Side Cache 的区别

Client Side Cache 和 Server Side Cache 的区别

Cache me outside

缓存是一种优化网页加载时间并优化其使用的内存量的持久化机制。

Cache 的定义

出于篇幅限制,本文重点介绍 Web 缓存。 当您加载网站时,网站数据(如图像、视频和 html 文档)将被保存。 重新加载网站后,它不会调用数据库再次获取该数据。 它只是将数据从内存中读取。

缓存以容量换取速度,缓存通常会暂时存储数据的子集,而数据库的数据通常是完整且持久的。

Client Side Caching

客户端缓存通常称为浏览器缓存,尽管客户端缓存是一个稍微宽泛的术语。其运行原理如下图所示:

一旦浏览器从服务器请求数据,它就会将其存储到浏览器创建的文件夹中。 下次打开网页时,它不会调用服务器获取数据,而是从浏览器缓存文件夹中提取数据。

Server Side Caching

服务器端缓存是一个类似的概念,但稍微复杂一些。

一旦用户向网站发出请求,其数据就会存储在服务器上。 下次用户发出请求时,它只需从服务器取回保存的数据,无需从数据库中提取数据,从而节省时间。

这些缓存由站点管理员实现,充当浏览器和源服务器之间的中介。 它们通常也基于 HTTP 缓存指令。

Remote Caching

远程缓存类似于服务器端缓存,但它也可以运行应用程序来序列化和反序列化数据。 不同之处在于您可以控制远程服务器,而不是由其他人操作。

通常通过应用程序代码或使用可以利用内存中数据存储的应用程序框架来检索 Web 内容。

Server-Side vs Remote vs Client Side

主要区别在于网站的数据在本地保存在客户端缓存中,而在服务器端缓存中可以分发给所有用户,而在远程中,数据也得到处理。
大多数精心设计的网站或应用程序将同时使用服务器端和客户端。

Why caching?

通过减少后端资源的负载和网络拥塞,缓存 Web 内容有助于提高网站的响应能力。 - AWS

Downside

如果您尝试访问的网站已更新,则在清除缓存之前您可能看不到更新。 这可能发生在您之前,特别是如果您是一名 Web 开发人员并且您正在尝试更新您网站的设计。 如果缓存存储在您的客户端,解决方案很简单。

这适用于客户端缓存,如果服务器端缓存没有正确处理这种情况,可能会在服务器端缓存上返回错误。
如果您使用远程缓存,它可以处理该错误并为您清除缓存并为您加载新请求。

简而言之,client side cache:

您的网络浏览器(Chrome、Firefox、Safari 或任何您使用的浏览器)决定记住网页的外观,因此不必再次请求服务器发送网页。 这通过消除几乎整个网络通信来节省时间(和带宽)。 但是,如果服务器决定更改网页的外观,您就会遇到麻烦,因为浏览器对它认为页面应该是什么样子的“记忆”现在已经过时了,它会给您一个旧版本的页面而不是新的。 这就是为什么人们有时会告诉您“清除浏览器缓存” - 这会迫使您的浏览器“忘记”页面的外观。 这迫使它向服务器询问页面的新的、更新的版本。

server side cache:

负责生成网页(即,将网页放在一起)的服务器决定记住网页的外观,因此不必再次生成它。 这节省了时间(但不是带宽),因为现在服务器不必经历构建整个页面的麻烦——它可以重新发送上次浏览器请求时发送的任何内容。 但是,如果网页上的任何数据需要更改,服务器将被迫丢弃其对页面外观的“记忆”,并需要重新生成页面。 如果页面真的很复杂并且需要很多时间来生成,这种缓存很有用。

更多Jerry的原创文章,尽在:“汪子熙”:

本文同步分享在 博客“汪子熙”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

dubbo异常Waiting server-side response timeout by scan timer

dubbo异常Waiting server-side response timeout by scan timer

调用dubbo api报异常:

Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2022-03-23 10:58:30.697, end time: 2022-03-23 10:58:35.726, client elapsed: 1 ms, server elapsed: 5028 ms, timeout: 5000 ms, request: Request [id=1, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation at org.apache.dubbo.remoting.exchange.support.DefaultFuture.doReceived(DefaultFuture.java:210) at org.apache.dubbo.remoting.exchange.support.DefaultFuture.received(DefaultFuture.java:175) at org.apache.dubbo.remoting.exchange.support.DefaultFuture$TimeoutCheckTask.notifyTimeout(DefaultFuture.java:298) at org.apache.dubbo.remoting.exchange.support.DefaultFuture$TimeoutCheckTask.lambda$run$0(DefaultFuture.java:285) at org.apache.dubbo.common.threadpool.ThreadlessExecutor.waitAndDrain(ThreadlessExecutor.java:93) at org.apache.dubbo.rpc.AsyncRpcResult.get(AsyncRpcResult.java:179)

度娘出来要不说timeout时间过短,要不注册中心地址写错了,不用怀疑,肯定是provider超时了,直接看provider service的异常吧

我们今天的关于使用 Server-Side Swift 开发 RESTful APIswift服务端开发的分享就到这里,谢谢您的阅读,如果想了解更多关于Angular 6 Server Side Errror:找不到模块:错误:无法解析’./dist/server/main.bundle’、Apache Spark 探秘:实现 Map-side Join 和 Reduce-side Join、Client Side Cache 和 Server Side Cache 的区别、dubbo异常Waiting server-side response timeout by scan timer的相关信息,可以在本站进行搜索。

本文标签: