Güncelleme
Bu cevap şeyler şimdi daha iyi olmasına rağmen, hala geçerli ve bilgilendirici yerleşik ekler kodlayıcı desteği için 2.2 / 2.3, çünkü Set
, Seq
, Map
, Date
, Timestamp
, ve BigDecimal
. Yalnızca vaka sınıfları ve olağan Scala türleriyle türler yapmaya devam ederseniz, yalnızca içinde örtük olan SQLImplicits
.
Ne yazık ki, buna yardımcı olmak için neredeyse hiçbir şey eklenmedi. Aranıyor @since 2.0.0
içinde Encoders.scala
veya SQLImplicits.scala
bulur şeyler çoğunlukla ilkel türleri (ve vaka sınıflarının bazı verdiği) ile ilgisi yok. Öyleyse, söylenecek ilk şey: şu anda özel sınıf kodlayıcılar için gerçek anlamda iyi bir destek yok . Bunun dışında, şu anda elimizde olanı göz önünde bulundurarak, umduğumuz kadar iyi bir iş çıkaran bazı püf noktaları takip ediyor. Önceden bir sorumluluk reddi beyanı olarak: Bu mükemmel bir şekilde çalışmayacak ve tüm sınırlamaları net ve açık hale getirmek için elimden geleni yapacağım.
Sorun tam olarak nedir
Eğer bir veri kümesi yapmak istediğinizde, Kıvılcım "genellikle gelen implicits aracılığıyla otomatik olarak oluşturulduğunu (ve iç Spark SQL gösteriminden tipi T bir JVM nesnesi dönüştürmek için) bir kodlayıcı gerektirir SparkSession
, ya da statik yöntemler arayarak açıkça oluşturulabilir on Encoders
"( üzerindeki belgelerdencreateDataset
alınmıştır ). Kodlayıcı, kodladığınız türün Encoder[T]
bulunduğu biçimi alacaktır T
. İlk öneri eklemektir import spark.implicits._
(bu size bu örtük kodlayıcıları verir ) ve ikinci öneri, bu kodlayıcıyla ilgili işlevler kümesini kullanarak örtük kodlayıcıyı açıkça geçirmektir .
Normal sınıflar için kodlayıcı yoktur, bu nedenle
import spark.implicits._
class MyObj(val i: Int)
val d = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
size aşağıdaki örtük ilgili derleme zamanı hatasını verecektir:
Veri Kümesinde depolanan tür için kodlayıcı bulunamıyor. İlkel türler (Int, String, vb.) Ve Ürün türleri (vaka sınıfları) sqlContext.implicits'in içe aktarılmasıyla desteklenir._ Diğer türleri serileştirme desteği gelecekteki sürümlerde eklenecektir
Ancak, genişleyen bazı sınıflarda yukarıdaki hatayı almak için kullandığınız türü sararsanız Product
, hata kafa karıştırıcı bir şekilde çalışma zamanına ertelenir, bu nedenle
import spark.implicits._
case class Wrap[T](unwrap: T)
class MyObj(val i: Int)
val d = spark.createDataset(Seq(Wrap(new MyObj(1)),Wrap(new MyObj(2)),Wrap(new MyObj(3))))
İyi derler, ancak çalışma zamanında başarısız olur.
java.lang.UnsupportedOperationException: MyObj için Kodlayıcı bulunamadı
Bunun nedeni Spark'ın yarattığı kodlayıcıların gerçekte sadece çalışma zamanında (scala relfection yoluyla) yapılmasıdır. Bu durumda, derleme zamanında tüm Spark kontrolleri, en dıştaki sınıfın genişlediğidir Product
(tüm durum sınıfları bunu yapar) ve yalnızca çalışma zamanında ne yapacağını hala bilmediğini anlar MyObj
(aynı sorun, a Dataset[(Int,MyObj)]
- Spark, çalışma zamanının kapanmasını bekler MyObj
). Bunlar, düzeltilmesi gereken önemli sorunlardır:
Product
her zaman çalışma zamanında çökmesine rağmen derlemeyi genişleten bazı sınıflar ve
- yuvalanmış türler için özel kodlayıcılarda geçiş yapmanın bir yolu yoktur (Spark'ı kodlayıcıyı
MyObj
daha sonra nasıl kodlanacağını Wrap[MyObj]
veya kodlayacağını bilecek şekilde beslememin bir yolu yok (Int,MyObj)
).
Sadece kullan kryo
Herkesin önerdiği çözüm, kryo
kodlayıcıyı kullanmaktır .
import spark.implicits._
class MyObj(val i: Int)
implicit val myObjEncoder = org.apache.spark.sql.Encoders.kryo[MyObj]
val d = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
Yine de bu oldukça sıkıcı bir hal alıyor. Özellikle kodunuz her türden veri kümesini manipüle ediyorsa, birleştiriyorsa, gruplandırıyorsa, vb. Sonunda bir sürü ekstra sonuç elde edersiniz. Öyleyse, neden tüm bunları otomatik olarak yapan bir örtük yapmıyorsunuz?
import scala.reflect.ClassTag
implicit def kryoEncoder[A](implicit ct: ClassTag[A]) =
org.apache.spark.sql.Encoders.kryo[A](ct)
Ve şimdi, neredeyse istediğim her şeyi yapabilirmişim gibi görünüyor (aşağıdaki örnek , otomatik olarak içe aktarıldığı spark-shell
yerde çalışmayacaktır spark.implicits._
)
class MyObj(val i: Int)
val d1 = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
val d2 = d1.map(d => (d.i+1,d)).alias("d2")
val d3 = d1.map(d => (d.i, d)).alias("d3")
val d4 = d2.joinWith(d3, $"d2._1" === $"d3._1")
Ya da neredeyse. Sorun şu ki kryo
, Spark'ı kullanmak sadece veri kümesindeki her satırı düz bir ikili nesne olarak depoluyor. İçin map
, filter
, foreach
yeterli olduğunu ancak böyle işlemler için join
, Spark gerçekten bu sütunlara ayrılmış olması gerekir. Şemayı d2
veya için inceleyerek d3
, yalnızca bir ikili sütun olduğunu görürsünüz:
d2.printSchema
Demetler için kısmi çözüm
Bu yüzden, Scala'daki ima sihrini kullanarak (daha fazlası 6.26.3 Aşırı Yükleme Çözünürlüğü ), kendime mümkün olduğu kadar iyi bir iş çıkaracak, en azından gruplar için bir dizi çıkarım yapabilirim ve mevcut etkilerle iyi çalışacak:
import org.apache.spark.sql.{Encoder,Encoders}
import scala.reflect.ClassTag
import spark.implicits._
implicit def single[A](implicit c: ClassTag[A]): Encoder[A] = Encoders.kryo[A](c)
implicit def tuple2[A1, A2](
implicit e1: Encoder[A1],
e2: Encoder[A2]
): Encoder[(A1,A2)] = Encoders.tuple[A1,A2](e1, e2)
implicit def tuple3[A1, A2, A3](
implicit e1: Encoder[A1],
e2: Encoder[A2],
e3: Encoder[A3]
): Encoder[(A1,A2,A3)] = Encoders.tuple[A1,A2,A3](e1, e2, e3)
Daha sonra, bu türden etkilerle donanmış olarak, yukarıdaki örneğimi, bazı sütun yeniden adlandırmalarıyla da olsa, çalıştırabilirim.
class MyObj(val i: Int)
val d1 = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
val d2 = d1.map(d => (d.i+1,d)).toDF("_1","_2").as[(Int,MyObj)].alias("d2")
val d3 = d1.map(d => (d.i ,d)).toDF("_1","_2").as[(Int,MyObj)].alias("d3")
val d4 = d2.joinWith(d3, $"d2._1" === $"d3._1")
Henüz (beklenen tanımlama grubu isimleri nasıl çözemedim _1
, _2
onları yeniden adlandırma olmadan varsayılan olarak, ...) - başkası Bununla oynamak istiyorsa, bu isim nerede "value"
tanıtıldı alır ve bu nereye başlık olur isimler genellikle eklenir. Bununla birlikte, kilit nokta, şimdi güzel bir yapılandırılmış şemaya sahip olmam:
d4.printSchema
Özetle, bu geçici çözüm:
- tuple'lar için ayrı sütunlar almamızı sağlar (böylece tuple'lara tekrar katılabiliriz, yaşasın!)
- yine sadece sonuçlara güvenebiliriz (bu yüzden
kryo
her yerden geçmeye gerek yok )
- ile neredeyse tamamen geriye doğru uyumludur
import spark.implicits._
(bazı yeniden adlandırma dahil)
- yok değil bize üzerinde katılmasına izin
kyro
tefrika ikili sütunlar, o olabilir tarlalarda dursun
- bazı tuple sütunlarını "değer" olarak yeniden adlandırmanın hoş olmayan yan etkisine sahiptir (gerekirse, bu, dönüştürerek
.toDF
, yeni sütun adlarını belirleyerek ve bir veri kümesine geri dönüştürerek geri alınabilir - ve şema adları birleştirme yoluyla korunmuş gibi görünüyor , en çok ihtiyaç duyulan yer).
Genel olarak sınıflar için kısmi çözüm
Bu daha az hoş ve iyi bir çözümü yok. Bununla birlikte, şimdi yukarıdaki tuple çözümüne sahip olduğumuza göre, daha karmaşık sınıflarınızı demetlere dönüştürebileceğiniz için başka bir cevabın örtük dönüştürme çözümünün de biraz daha az acı verici olacağına dair bir önsezim var. Ardından, veri kümesini oluşturduktan sonra, muhtemelen veri çerçevesi yaklaşımını kullanarak sütunları yeniden adlandırırsınız. Her şey yolunda giderse, bu gerçekten bir gelişme çünkü artık sınıflarımın alanlarında birleştirme yapabiliyorum. Eğer bir tane düz ikili kryo
serileştirici kullansaydım bu mümkün olmazdı.
İşte her şeyin biraz yapar örneğidir: Bir sınıf olması MyObj
tiplerinin alanları vardır Int
, java.util.UUID
ve Set[String]
. İlki kendi başının çaresine bakar. İkincisi, kullanarak serileştirebilsem kryo
de, bir olarak saklanırsa daha kullanışlı olurdu String
(çünkü UUID
s genellikle karşı çıkmak isteyeceğim bir şeydir). Üçüncüsü gerçekten bir ikili sütuna aittir.
class MyObj(val i: Int, val u: java.util.UUID, val s: Set[String])
type MyObjEncoded = (Int, String, Set[String])
implicit def toEncoded(o: MyObj): MyObjEncoded = (o.i, o.u.toString, o.s)
implicit def fromEncoded(e: MyObjEncoded): MyObj =
new MyObj(e._1, java.util.UUID.fromString(e._2), e._3)
Şimdi, bu makineyi kullanarak güzel bir şema ile bir veri kümesi oluşturabilirim:
val d = spark.createDataset(Seq[MyObjEncoded](
new MyObj(1, java.util.UUID.randomUUID, Set("foo")),
new MyObj(2, java.util.UUID.randomUUID, Set("bar"))
)).toDF("i","u","s").as[MyObjEncoded]
Ve şema bana doğru adlara sahip sütunlar ve katılabileceğim ilk iki şeyle birlikte gösteriyor.
d.printSchema