scalascala 判断是否是数字有一个相当于C#的收益

scala雾中风景(7): val x:Int = x + 1 的问题 | 写点什么当前位置: →
→ Scala中的语言特性是如何实现的1
Scala中的语言特性是如何实现的1
& 作者及来源: 崔鹏飞 - 博客园 &
&收藏到→_→:
摘要: Scala中的语言特性是如何实现的(1)
"Scala中的语言特性是如何实现的1"::
scala可以编译为java bytecode和cil,从而在jvm和cli之上运行。scala有很多在java和c#的世界中显得陌生的语言特性,本文将分析这些语言特性是如何实现的。
scala中可以像这样创建object:
object how bjectimplementedinscala {
def printsomething() {
println("printsomething")
然后在代码的其他地方调用printsomething,一个object究竟是什么东西呢? 我们将这段scala编译为java bytecode,然后反编译为java,会发现为how bjectimplementedinscala这个object生成了两个类:
public final class how bjectimplementedinscala
public static void printsomething()
how bjectimplementedinscala..module$.printsomething();
public final class how bjectimplementedinscala$
public static final
public void printsomething()
predef..module$.println("printsomething");
private how bjectimplementedinscala$()
module$ = this;
第一个类只包含一个静态方法,其实现依赖于第二个叫做how bjectimplementedinscala$的类。
how bjectimplementedinscala$是一个单例,其静态块实例化自己并把this赋值给module$这个public static的成员,从而可以被外界访问。
同样,我们可以把这段代码编译为cil,然后反编译为c#:
public sealed class how bjectimplementedinscala
public static void printsomething()
how bjectimplementedinscala$.module$.printsomething();
public sealed class how bjectimplementedinscala$ : scalaobject
public static how bjectimplementedinscala$ module$;
public override void printsomething()
predef$.module$.println("printsomething");
private how bjectimplementedinscala$()
how bjectimplementedinscala$.module$ = this;
static how bjectimplementedinscala$()
new how bjectimplementedinscala$();
和java代码大同小异,除了静态构造和某几个关键字外,基本一样。一个object就是一个scala帮我们实现的singleton。
var:可变。val:不可变。关于这两个关键字何时该使用哪一个,这里不做讨论,我们只是观察这二者在编译后是如何被表示的。
这段scala代码:
class howarevarandvalimplementedinscala {
var v1 = 123
val v2 = 456
def method1() = {
var v3 = 123
val v4 = 456
println(v3 + v4)
定义了两个字段一个var,一个val,方法中定义了两个局部变量,一个var,一个val。
编译为java bytecode并反编译之后:
public class howarevarandvalimplementedinscala
private int v1 = 123;
private final int v2 = 456;
public int v1()
return this.v1;
public void v1_$eq(int x$1) { this.v1 = x$1; }
public int v2() { return this.v2; }
public void method1() {
int v3 = 123;
int v4 = 456;
predef..module$.println(boxesruntime.boxtointeger(v3 + v4));
声明为字段的v1和v2,一个是普通字段,另一个则被标记为final。为v1生成了getter和setter,为v2则只有getter,因为v2作为immutable的字段是不可以被重新赋值的。
有趣的是方法中的局部变量都是此文来自: 马开东博客
转载请注明出处 网址:
普通的变量,没有被final修饰。
再来看这段scala编译为cil再反编译为c#之后的样子:
public class howarevarandvalimplementedinscala : scalaobject
private int v1;
private int v2;
public override int v1()
return this.v1;
public override void v1_$eq(int x$1)
this.v1 = x$1;
public override int v2()
return this.v2;
public override void method1()
int v3 = 123;
int v4 = 456;
predef$.module$.println(v3 + v4);
public howarevarandvalimplementedinscala()
this.v1 = 123;
this.v2 = 456;
有一个明显的问题,v2没有标为readonly(c#世界中用于声明变量不可以重新赋值的关键字),这是compiler的bug吗?
除此之外,和java代码一致。但是有趣的是代码中的所有public方法(包括上一段演示object的代码)都被标为了override,原因不明。
本来以为研究这么简单的两个语言特性不会有啥收获,仅仅是反编译一下,看看compiler都做了啥,满足下好奇心罢了。
结果还是有意外收获,我在反编译后的代码中发现了三个有趣的问题:
在scala中被声明为val的v4为什么在反编译的java中不是final的呢?
在scala中被声明为val的v2为什么在反编译的c#中不是readonly的呢?
为什么反编译出来的c#代码中的实例级公开方法都是标有override的呢?
为什么呢?为什么呢?为什么呢?答案下期揭晓。搜索此文相关文章:此文来自: 马开东博客
网址: 站长QQ
Scala中的语言特性是如何实现的1_博客园相关文章
博客园_总排行榜
博客园_最新
博客园_月排行榜
博客园_周排行榜
博客园_日排行榜Scala的Option monad和C#的null-conditional操作符比较 – 过往记忆
欢迎关注Hadoop、Spark、Flink、Hive、Hbase、Flume等大数据资料分享微信公共账号:iteblog_hadoop。
文章总数:920
浏览总数:12,673,730
评论:3663
分类目录:97 个 注册用户数:5163 最后更新:日 Apache Spark易用性调研,认真填写的用户均有机会获得精美礼物一件,活动时间即日起至06月05日结束。 欢迎关注微信公共帐号:iteblog_hadoop大数据猿:bigdata_ai
  这篇文章中将介绍 6.0的一个新特性,这将加深我们对 monad的理解。文章目录Null-conditional操作符  假如我们有一个嵌套的数据类型,然后我们需要访问这个嵌套类型里面的某个属性。比如Article可以没有作者(Author)信息;Author可以没有Address信息;Address可以没有City信息,如下:
/////////////////////////////////////////////////////////////////////
User: 过往记忆
Time: 22:25:23
bolg: https://www.iteblog.com
本文地址:https://www.iteblog.com/archives/1583
过往记忆博客,专注于hadoop、hive、spark、shark、flume的技术博客,大量的干货
过往记忆博客微信公共帐号:iteblog_hadoop
/////////////////////////////////////////////////////////////////////
public class Address {
public string Street { }
public string City { } // can be null
public class Author {
public string Name { }
public string Email { }
public Address Address { } // can be null
public class Article {
public string Title { }
public string Content { }
public Author Author {} // can be null
Console.WriteLine(article.Author.Address.City.ToUpper());
上面的代码会有产生NullReferenceException的风险,所以在使用这些对象之前,我们必须先做null校验来避免NullReferenceException:
if (article != null) {
if (article.Author != null) {
if (article.Author.Address != null) {
if (article.Author.Address.City != null) {
Console.WriteLine(article.Author.Address.City.ToUpper());
呸,使用如此复杂的代码来做一件非常简单的事!而且这段代码的可读行真的很差而且很混乱!幸运的是, 6.0 引入了一个null-conditional操作符。新的操作符使用?.表示,而且可以在操作符左边对象为null的如何情况下使用。  比如,下面的代码可以解释成:如果bob不为空,那么调用它的ToUpper方法;否则将bobUpper设置为null:
var bob = &Bob&;
var bobUpper = bob?.ToUpper();
回到之前的例子,我们可以将它简写成:
Console.WriteLine(article?.Author?.Address?.City?.ToUpper());
Option类型  在中,我们一般都会避免使用null。然而,我们依然想将某些数据设置成可选的。Option[T]这类可以显示地将某个值设置成可选状态。比如,下面的someBob对象的类型是Option[String],这代表它既可以持有String类型的值,或者什么都没有:
val someBob: Option[String] = Some(&Bob&)
val noBob: Option[String] = None
因此,上面的例子在Scala中我们可以使用下面的代码实现:
case class Address(street: String, city: Option[String])
case class Author(name: String, email: String, address: Option[Address])
case class Article(title: String, content: String, author: Option[Author])
注意,和C#对比,在Scala中,我们必须显示地声明哪些字段是可选的!然后我们打印出某篇文章作者的城市的小写形式:
if (article.author.isDefined) {
if (article.author.get.address.isDefined) {
if (article.author.get.address.get.city.isDefined) {
println(article.author.get.address.get.city.get)
这种原生的方法和C#比较并没有什么提升,然而在Scala中,我们可以将上面代码写成下面:
author &- article.author
address &- author.address
city &- address.city
} yield println(city.toLowerCase())
  虽然这个版本的代码也没有C#的 null-conditional操作符版本短,但是,重要的是,我们摆脱了使用嵌套的if语句,而且这个版本的代码可读性更高。这就是Scala中for语法和Option类型的monadic面的结合。Option的monad特性  在我解释前面代码之前,首先让我来多介绍一点关于Option类型的方法。你是否记得List类型的map方法?它接收一个函数,然后将这个函数使用在list里面的所有元素中。有趣的是,Option类型也有map函数,我们可以将Option类型想象成一个List,它里面可以有一个(Some)或者零个(None)元素。所以,Option.map也可以接收一个函数,如果其中含有一个值,那么它将把那个函数应用到那个值上;如果其中不含有值,那么Option.map直接返回None,如下:
/////////////////////////////////////////////////////////////////////
User: 过往记忆
Time: 22:25:23
bolg: https://www.iteblog.com
本文地址:https://www.iteblog.com/archives/1583
过往记忆博客,专注于hadoop、hive、spark、shark、flume的技术博客,大量的干货
过往记忆博客微信公共帐号:iteblog_hadoop
/////////////////////////////////////////////////////////////////////
scala& val address = Address(&street&, Some(&New York&))
address: HelloScala.Address = Address(street,Some(New York))
scala& address.city.map(city =& city.toLowerCase())
res1: Option[String] = Some(new york)
scala& val address = Address(&street&, None)
address: HelloScala.Address = Address(street,None)
scala& address.city.map(city =& city.toLowerCase())
res2: Option[String] = None
现在,我们是否可以将她使用到之前的例子中呢?
val cityLowerCase = article.author.map { author =&
author.address.map { address =&
address.city.map(city =& city.toLowerCase)
  我想这个代码片段比直接使用if语句要好的多。使用这个代码的问题是,cityLowerCase的返回类型是Option[Option[Option[String]]]。最终的结果类型嵌套了这么多层!我们想要的类型是Option[String]。如果我们使用flatMap,我们将可以得到我们期望的类型:
val cityLowerCase: Option[String] = article.author.flatMap { author =&
author.address.flatMap { address =&
address.city.map(city =& city.toLowerCase)
  Option.flatMap接收一个函数,并将option中的元素转换成另一个option,最后返回这个转换的值。这个方法和List的List.flatMap方法功能类似,其接收一个函数,并将list里面的元素转换成另外一个list,最后它将合并list里面的所有元素:
scala& List(1, 2, 3, 4).flatMap(el =& List(el, el + 1))
res3: List[Int] = List(1, 2, 2, 3, 3, 4, 4, 5)
  Option[T]和List[T]拥有flatMap方法意味着它们可以很容易地进行组合。在Scala中,所有拥有flatMap方法的类型都是monad!换句话说,monad就是说任何带有类型的泛型并且可以和其他同类型的泛型实例进行组合(使用flatMap方法)。现在,让我们回到之前的for语句,之所以没有在里面使用嵌套语句,是因为flatMap和map的语法糖导致的,我们可以把
val city = for {
author &- article.author
address &- author.address
city &- address.city
} yield println(city.toLowerCase())
val cityLowerCase: Option[String] = article.author.flatMap { author =&
author.address.flatMap { address =&
address.city.map(city =& city.toLowerCase)
为了加深对monad的理解,让我们来看看一个关于list的例子:
scala& for {
el &- List(1, 2, 3, 4)
list &- 1 to el
} yield list
res4: List[Int] = List(1, 1, 2, 1, 2, 3, 1, 2, 3, 4)
  对于第一个list里面的元素,我们生成一个从1到那个元素的范围,最后我们将所有返回的list进行组合。总结  这篇文章中,我的观点是来说明C#和Scala引入的一些特性来避免嵌套代码的编写。C#中引入了null-conditional操作符,来处理if语句中的嵌套null值检测;Scala则拥有一个更加通用的for语句和flatMap函数机制来避免嵌套代码的编写。  本文翻译自Scala's Option monad versus null-conditional operator in C#: http://www.codewithstyle.info/2016/02/scalas-option-monad-versus-null.html本博客文章除特别声明,全部都是原创!禁止个人和公司转载本文、谢谢理解:本文链接:
下面文章您可能感兴趣随笔 - 1469&
文章 - 199&评论 - 280&trackbacks - 0
原文地址:http://www.infoq.com/cn/articles/scala-architecture-wacai
编者按:本文是根据ArchSummit大会上挖财资深架构师王宏江的演讲《Scala在挖财的应用实践》整理而成。
这次分享有三个方面,1是介绍一下挖财当前的开发情况和后端的架构, 2是挖财选择Scala的原因,3是挖财使用Scala相关的技术时碰到的问题以及经验。
第一部分是团队的情况和后端技术的架构。近一年我们的开发团队从50人增长到了现在两百人,公司总人数扩张到600左右,技术人员占的比例跟国内大多互联网创业公司的比例差不多,1/3左右的样子,昨天大会上王天提到Twitter的工程师44%左右在硅谷差不多是平均水平,国内公司比例要低一些。我们是一家典型的业务驱动公司,并不是技术驱动的,业务驱动的一个特点是需要快速尝试各种可能性,需要技术人员能尽快跟上,好的程序员并不是那么好招的,某些阶段在一定程度上先靠人数来堆。所以挖财技术团队中少部分人是有一定经验的,核心的一些架构师主要是从阿里出来的,大部分的程序员相对普通。
而后端技术用的主要是比较大众的东西,Web容器用Tomcat,框架主要是Spring MVC,也有少量的Play,中间服务层是Dubbo,微容器用Spring Boot,服务注册这一块是用ZooKeeper,核心业务开发方式还是围绕着Spring和Mybatis等;数据的存储这块是MySQL和Hbase,分布存储这块是用阿里巴巴之前开源的一个中间件Cobar。消息和实时计算这块主要是Kafka, Storm,日志以及监控系统则是用典型的ELK和Zabbix。另外我们将要放弃MongoDB和Memcached,并不是这些产品自身的问题,而是人的因素,最早的架构师对MongoDB很熟,后来因为他离开,这块的一些问题缺乏强有力的人把控,就放弃了。其他人对MySQL和Hbase更熟悉,并且这两种组合基本满足我们的需求了。Memcached则是被Redis替代。总的来说,我们裁剪掉了一些之前使用的相似产品,减少为一种,主要是基于自身运维能力考虑的,不在于这些技术是否足够好或者是否符合团队的特性和遇到的问题,而是你能不能把它维护的好,或者说风险控制好。
业务上面我们大概也是比较典型的。基础设施是一块,然后中间是服务,上面是日常的应用。基础设施就包括典型的存储、消息、框架,还有发布、运维、监控等等。中间就是现在典型的SOA服务,用户的资产、服务、流水、交易等等,所有的服务采用Spring Boot打包部署,主要便于运维上的统一和方便做一些切面层的事情。最上面是应用,包括社区、记帐、钱管家、理财。而安全和风控会切入在每一个环节。
其实作为一个互联网的金融公司,很多时候我们对金融会有一点点陌生,在我们看来做一个理财产品或者一个理财业务其实和电商业务有很大的相似之处。主要就是理财产品,他也是一个商品,这个商品可能有高的回报,最初形成这个商品的时候背后有一套综合系统或者说有一套逻辑。并且购买这个商品之后,这个商品是有历史的,他的收益是在变化的,所以简化之后,我们发现跟电商系统是相似的。
这就是典型的架构,前面是Web或Gateway,中间是Service,中间层协议是Dubbo默认方式或者是HTTP。
数据存储这一块主要是MySQL和Hbase,当前还有小部分数据使用的Mongo,ORMapping这块我们用了MyBatis。数据的水平扩展主要是基于刚才提到的Cobar,它是阿里比较成功的开源产品之一。Cobar支持数据的二维扩展,能满足我们的场景,比如说几千万用户的流水记录可以按用户加时间的维度来划分。如果单纯按时间的维度分区,在查询用户的流水时要散落到各个分区上,对各个分区的压力都较大。而按用户的维度则可能导致这个分区在一定时间后达到我们预设的容量上线,因为一天有几百万甚至上千万的流水进入。二维的方式可以解决这些问题。尽管我们后来发现用户的数据大多在最近一段时间内较热,时间比较早的数据相对较冷,只用了Cobar的一维扩展方式,将冷数据归档到Hbase集群来解决。在数据的在线处理这块,有很多用Akka框架的程序,比如计息系统主要是运算的,所以Akka很适合干这个事情。
自动记帐这块主要是钱管家这款产品,他会根据银行的帐单把这些数据汇总到这个产品里面,主要是从其他系统汇聚出原始的数据,根据这些数据我们会进行分类,进行加工和抽取,最后汇总到database,这个过程的思路就是command+pipeline下面这套组合,只不过是分布式命令和管道,分布式里面命令是Akka所承担的角色。
数据分析这块,Canal会把binlog数据丢到Kafka然后同步到其他系统。在线分析主要通过Storm进行,数据分析过程中缓存用了豌豆荚的Codis。离线分析目前用Hadoop/Spark的方式,数据分析之后,一部分存到Hbase,另一部分在通过Cober存储到MySQL里面。
前面介绍了挖财的架构,第二部分说说我们选择Scala的原因。首先Scala的语言还是有点争议的,不管是大师级的人物还是编程语言爱好者,对编程语言的争议从来没停止过的。但不管别人说Scala怎么样,从工程师的角度,从实用的角度来说,我是比较认可这个语言的。我觉得松本行弘批判保罗&格雷厄姆的话非常代表我的观点,他说的语言会朝着&小而干净&的方向进化,他自己也创造过一个Lisp的方言叫ARC,他崇尚保持简洁和纯粹的设计思路,但这门语言并不算成功。从总体或者说未来的趋势来看,语言已经不可能保持那么小而精的状态。能够真正做到解放生产力这么一个目标才是演化的方向,所以从解放生产力这个角度来看的话,Scala实际上是达到了这个效果的。不管怎么骂,Scala确实为我们解决了很多实际问题,加速了我们的效率。
我们为什么会选择Scala?可以说有一些巧合,在挖财快速发展的过程中,几个核心的架构师对这种语言都是比较喜爱的,并且之前有些经验。如果是一个人,或者说你的公司想做这个事情,但是没有人能够支持你去做这件事情的话,那其实是相当麻烦的。因为你至少要有好几个能力相当不错的人能够一起来做这个事情,才可能在公司把这个事情推广,才能够影响更多的人。这样形成核心的小的影响之后,会吸引来更多的爱好者的加入,这样才能够使这个语言被大家所接受。所以这就必须是一个团队,既使个人能力再强也没用,团队的意愿非常重要,假如他不愿意用这个语言,或者他不接受这个语言,那么用主流的Java也是OK的,培养而不是强迫,如果用这个语言当然更好,如果不使用的话也没有关系。但是我们可能在口味上会偏好这些人,因为我发现相对来讲这些人他会更愿意去思考,或者说在处理一些问题的时候会比普通的程序员想的更多。在这个过程中还有个非常重要的事,就是慎重的选择开发栈。就拿选择Scala来讲,Scala有他自己的生态系统,跟Java还不太一样。比如说Java在Web层有Struts/Spring MVC框架,Scala有Play/Lift,构建工具Java里面是Maven,Scala则以sbt为主。Scala有一套自己的玩法或者说技术栈,你是不是选择整套技术栈是需要非常慎重的。挖财处于一个中等创业公司的规模,对我们来说,Java还是更大众化的。在业务发展过程中,人员加入还是比较快的。而且很多人还是以Java为主,所以我们的做法是不改变这些人员的习惯,还是以Java生态为主,没有强制的用Scala的开发栈。而且Scala的技术栈里有不错的产品,也有维护的很差的产品。你选择一个产品时,先看它的邮件列表,如果邮件列表里都没有什么来往邮件,这个产品的维护性和持续性肯定不够。
Scala的优点体现在,原来Java里的设计模式,现在在Scala的语言层面就提供了。比如说Java里的单例,Scala直接对应的就是object;访问者模式我们可以用更优雅直观的模式去匹配;还有构造器模式;依赖注入在Scala里面有蛋糕模式。
不可变的模式主要是在并发编程里面的,并发编程我们主要面临的就是状态,怎么同步或维护这个状态,保证这个状态不被污染掉。如果这个状态本身就是不可变的,那么就不存在竞争性。Scala本身推崇不可变的思路,当你想要去改变这个状态时,实际是new一个新的状态,原来的数据对象并不会改变本身,但Scala也并不是说完全像一些纯粹的函数式语言偏执于只用一种纯粹的不可变对象,所以Scala也提供了一种像Java的可变的数据变量(用var声明)。还有就是Java里面我们用来在不同的领域(尤其是在网络)之间传输的都是值对象,值对象的描述跟一般的对象一样,表达上略微有点啰嗦。而Scala里使用的case class在表达上则简洁一些,并且这些case class配合模式匹配非常好用。这些都是表达方式的简洁化,或者提升生产力的一种体现。
其他的优点比如动态类型以静态类型的方式去实现动态的效果,我们称之为duck typing,他看起来像duck听起来也像duck那么它就是duck。Scala里面以隐式参数或者隐式转换的方式实现动态语言的等价效果,只不过这种方式在编译器能够更好的检测,所以在安全里更有保障。还有利用函数式特性去自定义流控,比如说C#里的using是一个很好的特性,我们在using的括号里面定义一个resource,不管是一个文件流或者是一个disposable资源,程序处理完会把这个资源自动释放,我们可以在Scala里面通过柯里化和函数特性也可以灵活的定义类似的流控,实现等价效果。另外还有它强大的集合操作,就举两个例子,我们想要从同一组流中进行下图的两个查找,同样的效果的写Java很麻烦,而Scala通过高效的运算以很简洁的方式实现出来。
当然他有不好的地方就是门槛相对高一点,这个门槛就是函数式背景,举一个例子,比如说eta规约,比如说我这个函数里面的参数是接收另外一个函数,简单的说就是高级函数,这个高级函数里面传给他的参数比如说是& arg =& method(arg) ,就是一个参数传给另外一个方法,我们可以直接简化一下把这个表达式直接简化成 method,(在Java里这种Lambda的简化表达为class::method还相对好辨识一些) 这些可能会导致很多人在看这些函数式的编程语法的时候会产生一些疑惑和不熟悉,这些疑惑表面上觉得是编程语言的俗语,实际上背后是函数式的理论或者它的一些特性,不是大部分人都熟悉的。另外一个不好的地方就是在类型上确实做的有点复杂,有点过头了,他的类型系统是从ML系语言以及Hashkell过来的,在类型方面有很多高阶特性,远比Java要复杂多了,可以这么对比,Java的类型系统是一维世界,那么Scala的类型系统是二维世界。甚至他的类型系统本身也是图灵完备的,可以用类型系统解决SKI组合子问题,我推荐大家去搜一下用Scala的类型系统来解决汉诺塔的程序,依赖类型来解决问题思路是这样的:编译即运算。如果编译器编译通过,就说明类型演算证明了这些问题。这样带来的好处就是我只要编译成功,类型演算就完全被编译器执行,就是这些事情完全交给编译器去做。不好的地方还在于他的灵活性必须有一些克制和一些妥协,减少高阶特性,并不是每个人都喜欢高阶特性。我们还要在编码风格上有一些约定。当然在编译过程中也要利用好编译器的参数,这些参数会帮你提前发现一些问题。所以在挖财,我们主要引入Scala语言,但没有用Scala这个生态系统里面的一些框架(除了Akka),没有全面用它提倡的全面异步化或基于事件的处理方式也没有用它Web或RPC之类的框架,我们没有改变传统模式,还是基于Servlet模型,还是以Spring为中心。这种保守的做法使得大家对整个架构接受起来是比较容易的,并且较好把控。
接着讲讲Scala在挖财的使用方式。我们用一种很自然很透明的方式就能将Spring这些框架与Scala集成的很好, Scala与Java只稍微有一些差异,所以Scala与Java的生态系统集成这块是非常顺当自然的。但在生态系统集成这块也会遇到一些问题,就是Akka和Spring都是比较&强势&的托管者。Spring是bean的托管者,bean的创建和生命周期是由context来掌管的,而Akka系统中所有的actor创建都由其父actor掌管,当把actor也当作spring中的一个bean时,Akka需要做一点&妥协&,它提供了Indirect的Producer,可以包装到一个Akka的Extension里来简化actor在spring中的构造。我们选择了Spring,最大的原因就是希望所有的开发人员在接受这些程序时不会感觉到陌生,他所有的编程习惯没有改变,只是语法上的不同而已。Spring与Dubbo集成没有什么问题,只要注意到一点,就是多个系统之间,有的系统不是用Scala的时候,你必须用纯的Java来返回API里定义的模型,否则可能一个底层是Scala实现的对象在对方那里反序列化会找不到类。在不同的系统间,你在API这个层面暴露的只能是Java定义API。
然后要用好REPL,它是交互式的工具,它对于大家快速地验证简短的几行程序是非常有帮助的。编译我们还是没有选择sbt,sbt是基于apache ivy的包管理方式,所以出于统一的考虑我们大多数scala也是采用maven管理。每个人发布他的项目必须保证他的项目是自测的或者他自己用mvn spring-boot:run或mvn tomcat7:run的方式能够跑起来。一些参数的话,编译系统的参数可能也有一些对比,在提前编译的时候,我发现你的问题是有好处的,其中比如第二个"-Yno-adapted-args"这个参数我举一个例子,就是Scala里面有一些类型推导会自动适配,假设有一个Unit类型,就是类似于Java里面Void,println(a:Any)这个方法在Scala的里面定义的其实就是接受Any任何类型的参数,我执行print的时候我可以传给他任何参数,比如 print(1,2,3),这个时候你会发现与你预期不符,明明我传递了三个参数,怎么它最后也成功打印出来了,你就会觉得很诡异,这个到底是编译器搞了什么鬼还是bug,这个叫做类型适配,这个里面就会涉及到Scala的Tuple类型,叫做元组,元组这种类型就是当我用小括号,一般是用小括号去描述它的,比如说(1、2、3)这样的元组,他是三个元素的元组,用小括号来描述一个元组时,你如果没有小括号编译器发现你这个参数与他期望的参数不匹配的时候,他会把这个东西适配成一个元组,然后这里面变成一个Tuple3,就是一个Any具体的实现,导致这个方法可以通过,所以no-adapted-args这个参数告诉编译器我们不要去做适配的事情。
第三部分我会介绍一下我们在使用的一些中间件产品如Cobar、Kafka、Akka的问题和经验。挖财使用Cobar主要是因为之前我们的首席架构师王福强曾参与过Cobar的开发。相对来讲在我们的团队里面,它还是比较稳定的。对于个别的小问题我们是可以规避的,或者我们知道了以后是可以改的。我们现在采用的分区策略有两种,一种是by range的方式还有一种是by hash。第一种比如说每天你记帐进来,按照这个时间的纬度当一个库承载不了时,他会按照时间的纬度自动的到下一个库,所以是比较简单的方式。第二种就是你开始就明确的建好,比如说我们分成十个,当后面发现不够的时候,可以再把它变成20个。所以你只要开始预留好,当你翻倍的时候你再做一些事情就可以了。Cobar本身也是支持二维的,但是这个方式稍微有点高阶,尽管我们有些场景需要这个。就是说你按照时间维度去划分时,很多时候用户的请求,也就是人的流水会分布在所有的时间,这个会造成一些麻烦,他可能会碰到多个区间。但是我们可以用一些方法来解决这些问题。Kafka在挖财是比较重要的中间件,我们现在的规模并不大,业务消息是几千万级的情况,日志相对多一些有几个亿,Kafka在我们这就是万金油,它不仅仅是消息,并且当数据在处理时,把它当成分布式管道来运用的情况是很多的,还有一些数据我现在先把它放在Kafka里,然后又交给其他的程序做后续处理。所以它是被我们广泛使用的中间件。我们了解到一些金融公司,他们是采用那种EventSourcing的设计思路,也就是事件驱动。事件驱动这块他会面临一些问题,就是event要怎么样去journal好,不管是审计还是其他的一些需要。也看到有一些系统利用它做了应用级的journal。
在我们这边的话,Kafka的一种方式,就是基于JVM的业务系统直接去连接这个集群,而另外一些异构的系统,通过HTTP-proxy的方式可以简单地处理。我们稍微有一点不同的是,我们是把offset全部存在Redis里,而ZooKeeper里没有存offset。
没有选择ZooKeeper存储是因为刚搭建的时候,当时对ZooKeeper的运维能力相对弱一些,所以我们会更偏好使用相对有把握的一些东西,所以我们基于SimpleConsumer做了一些封装,把offset的存储为本地文件或Redis。我们在运维上做了一个简单的约束,当前业务量不是很大的情况下我们尽量减少分区数,大多情况一个分区就够了,必要的话可以多个topic。
有一个案例就是Kafka,不管同步还是异步发送都会出现消息重复的情况。还有一个我们当时的测试环境, 某个topic发现消息较大发送不成功,调整了一下msgeSize和客户端的fetchSize,当时leader这边可以接收这个大的消息,但follow出了问题,副本是使用的replicaSize的,结果发现它是不可修改(至少在我们遇到的例子时的版本里并没有地方可以修改这个参数)而导致了两个follow从leader去取数据的时候就取不了这个大的消息,因为这个msgSize大于它的replicaSize,所以造成follow这边不断的去连,但是又消费不了,整个网络流量都异常了。
Akka在我们这边也是广泛使用的系统,在业务里面在用它做运算,其他的场景主要是在数据处理,它天生具有扩展性,所以我们开始做一个事情的时候,可能一个Akka就可以处理这个东西,等到将来它量变大时,因为一个Akaa和多个Akka之间的扩展性是有天然的,所以扩展起来还是非常方便的。我们在使用它的时候也因为人员的情况,并没有使用一些很复杂的情况,比如说持久化那块曾经去尝试过,但是发现还是有一些问题,所以后来就没有再用持久化,还有它的集群也还没有用。下面我介绍一个简单的场景。假设你在类似于BAT这样的流量入口做一些广告或者做一些引流的时候,可能它会瞬间给你带来巨大的流量,而如果你自己的私有云或者你自己的机房没有立刻准备好几百个机器应对时,可能会宕机。这时就有了公有云和私有云结合的方案,我们就在公有云上面申请上百个甚至几百个实例,公有云是活动的入口,你这个广告可能会面向几千万的用户,但是假设这几千万的用户就像一个漏斗层层在损失,最后能用的时候就变成了几十万,取决于你的业务特性,在最前头也就是入口这块量是最大的,你怎么解决入口这块呢?可以简单地在公有云上购买一大堆服务器,然后把流量引入过来之后,把这些请求全部丢到Kafka里,然后用Akka异步做一些处理,这些处理需要你去调你做的这个活动的接口,调用完之后再把这些东西存到Akka,由你自己的集团去搭那些处理过的东西,这样就极大的利用公有云构建了这些设备,解决了你活动的需求。
还是刚才这个例子里面我们看一下在Akka里的几个actor角色,这前面我们通过Netty把这些请求丢掉Kafka之后,后面每个Akka都会处理这些请求,第一个就是KafkaReceiver,他负责从Kafka去拉这些消息,这块采用pull而不是采用push,一个原因是我们的Kafka-client是有自己独立的线程,并非Akka里的dispatch线程,因为最早实现Kafka-client的时候,它并非为Akka所设计和使用。KafkaReceiver角色里通过一个BlockingQueue来平衡receiver与后续actor之间处理的能力的不匹配问题。
最重要的一点是在actor的世界里面千万避免同步阻塞,所有的actor底层的线程池是复用的,如果你的HTTP调用在里面堵塞住了,那么可能整个actor都可能被堵塞了,mailbox里后续的消息无法被处理,甚至整个系统都会被阻塞,所以同步在这里面是一定要避免的,当这些调用成功的时候我们交给后面一组actor,异常的时候我们会交给另外一个组,他可能会丢到另外一边,由另外一边来处理,这样就避免了你自己在一套逻辑里容错的时候要考虑一下这个问题。Akka的感觉比Scala原本自己的actor是要做的更好一些,要不然TypeSafe也不会收购Akka这个团队,在actor的模型里面,他有很天然的扩展能力,但是在JVM里,他的底层实现还是要通过线程调度,他的线程实现这块是利用非常高效的fork/join,最大的特点就是双向队列,当一个队列里面被处理完以后,线程会从其他队列里偷取别人的任务,这样会解决不均的情况,有的处理慢有的处理快,处理快的就帮助处理慢的这些,所以他在实际执行过程中Akka还是很容易把CPU跑满,所以利用率还是非常高效。
我们大部分的数据处理,是用Akka和Kafka的简单模式,就是Unix上的管道模式,现在是用分布式的方式,我们用它也还是要和业务结合,就比如说你要聚焦这个服务,而这些现有的服务都是Dubbo的一种,所以Akka与Spring整合起来还是很方便的。最后我们总结一下对Akka使用的一些小结。第一是尽可能保证Actor职责尽可能单一。第二避免阻塞,阻塞在Akka的模式下一定要避免这个方式。第三是Supervisor和错误处理也是要有自己的策略,到底是收到以后重启它还是立刻把这个上报,这个都要根据自己的业务情况确定,在消息处理的时候要保持平衡,这边生产出的那边要消费了,你的能力强和弱的时候你要自己去考虑。有些地方我们可以通过加一些简单的监控,比如对mailbox的计数,或者访问统计,将待处理的任务用WaterMark标记出来,就能够知道当前Actor的处理能力,让我们更好的了解各个Actor状况。还有常用的模式,类似于替身模式,或者短路模式等等。因为时间的关系我们就不去展开的讲。
最后分享一个小技巧,就是关于Actor的优雅关闭。其实优雅关闭的话题在每个系统里都要有的,比如你发布后,你怎么样让这个系统优雅地下线,还有你后面关闭服务的时候,Actor或Akka如何优雅关闭。你应该先关闭什么后关闭什么从而让这个顺序有所保障,这就是让你尽可能的做到优雅。
有一种模式就是我们根据Terminator模式设计的一个变种Governor模式,所有干活的Actor都由Governor来掌管,Governor是他们的父Actor,他们在消亡中的时候所有都是由Governor来掌控他们的生杀大权的。另外一个角色是Master,他不是真正干活的,他只是负责这个任务要开始真正执行了,Master跟Governor之间是协作,Master把自己注册到Governor,由Governor去watch它,当Master死亡的时候会发送消息给Governor。实现上先提供一个Governor的模板,在这里负责创建那些干活的Actor,以及顺序停止的时候先停止谁后停止谁这么一个过程。把他的顺序制订好,比如说KafkaReceiver,他相比Processor的顺序是1,然后Processor的顺序是2,大家在关闭的时候都会按照这个顺序先把KafkaReceiver给关闭了,然后再关闭Processor,最后在关闭Storer。当Master发给自己一个毒药丸说我要退出了时,因为Governor会关注Master,所以他会按顺序杀死子Actor。
最后推荐一些Scala相关的书。第一本是《Programming in Scala》,是Scala比较重量级的一本书,Scala之父他们几个人联合写的一本书,里面写的很含蓄但是信息量是非常大的,所以Scala真的要去消化很多遍。第二本是《Scala for the impatient》,第三本是《Scala in depth》这个是相对高级一点的书,最后一本是《Functional Programming In Scala》,这本书里面讲到了很多类型性的和函数式的比较高阶的比如 monad等一些特性。
阅读(...) 评论()}

我要回帖

更多关于 scala 判断是否为空 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信