Git Product home page Git Product logo

scala-redis's Introduction

Redis Scala client

Key features of the library

  • Native Scala types Set and List responses.
  • Transparent serialization
  • Connection pooling
  • Consistent Hashing on the client.
  • Support for Clustering of Redis nodes.

Information about redis

Redis is a key-value database. It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists and sets with atomic operations to push/pop elements.

http://redis.io

Key features of Redis

  • Fast in-memory store with asynchronous save to disk.
  • Key value get, set, delete, etc.
  • Atomic operations on sets and lists, union, intersection, trim, etc.

Requirements

Installation

Add to build.sbt

libraryDependencies ++= Seq(
    "net.debasishg" %% "redisclient" % "3.41"
)

Usage

Start your redis instance (usually redis-server will do it)

$ cd scala-redis
$ sbt
> update
> console

And you are ready to start issuing commands to the server(s)

Redis 2 implements a new protocol for binary safe commands and replies

Let us connect and get a key:

scala> import com.redis._
import com.redis._

scala> val r = new RedisClient("localhost", 6379)
r: com.redis.RedisClient = localhost:6379

scala> r.set("key", "some value")
res3: Boolean = true

scala> r.get("key")
res4: Option[String] = Some(some value)

Let us try out some List operations:

scala> r.lpush("list-1", "foo")
res0: Option[Long] = Some(1)

scala> r.rpush("list-1", "bar")
res1: Option[Long] = Some(2)

scala> r.llen("list-1")
res2: Option[Long] = Some(2)

Let us look at some serialization stuff:

scala> import serialization._
import serialization._

scala> import Parse.Implicits._
import Parse.Implicits._

scala> r.hmset("hash", Map("field1" -> "1", "field2" -> 2))
res0: Boolean = true

scala> r.hmget[String,String]("hash", "field1", "field2")
res1: Option[Map[String,String]] = Some(Map(field1 -> 1, field2 -> 2))

scala> r.hmget[String,Int]("hash", "field1", "field2")
res2: Option[Map[String,Int]] = Some(Map(field1 -> 1, field2 -> 2))

scala> val x = "debasish".getBytes("UTF-8")
x: Array[Byte] = Array(100, 101, 98, 97, 115, 105, 115, 104)

scala> r.set("key", x)
res3: Boolean = true

scala> import Parse.Implicits.parseByteArray
import Parse.Implicits.parseByteArray

scala> val s = r.get[Array[Byte]]("key")
s: Option[Array[Byte]] = Some([B@6e8d02)

scala> new String(s.get)
res4: java.lang.String = debasish

scala> r.get[Array[Byte]]("keey")
res5: Option[Array[Byte]] = None

Using Client Pooling

scala-redis is a blocking client, which serves the purpose in most of the cases since Redis is also single threaded. But there may be situations when clients need to manage multiple RedisClients to ensure thread-safe programming.

scala-redis includes a Pool implementation which can be used to serve this purpose. Based on Apache Commons Pool implementation, RedisClientPool maintains a pool of instances of RedisClient, which can grow based on demand. Here's a sample usage ..

val clients = new RedisClientPool("localhost", 6379)
def lp(msgs: List[String]) = {
  clients.withClient {
    client => {
      msgs.foreach(client.lpush("list-l", _))
      client.llen("list-l")
    }
  }
}

Using a combination of pooling and futures, scala-redis can be throttled for more parallelism. This is the typical recommended strategy if you are looking forward to scale up using this redis client. Here's a sample usage .. we are doing a parallel throttle of an lpush, rpush and set operations in redis, each repeated a number of times ..

If we have a pool initialized, then we can use the pool to repeat the following operations.

// lpush
def lp(msgs: List[String]) = {
  clients.withClient {
    client => {
      msgs.foreach(client.lpush("list-l", _))
      client.llen("list-l")
    }
  }
}

// rpush
def rp(msgs: List[String]) = {
  clients.withClient {
    client => {
      msgs.foreach(client.rpush("list-r", _))
      client.llen("list-r")
    }
  }
}

// set
def set(msgs: List[String]) = {
  clients.withClient {
    client => {
      var i = 0
      msgs.foreach { v =>
        client.set("key-%d".format(i), v)
        i += 1
      }
      Some(1000)
    }
  }
}

And here's the snippet that throttles our redis server with the above operations in a non blocking mode using Scala futures:

val l = (0 until 5000).map(_.toString).toList
val fns = List[List[String] => Option[Int]](lp, rp, set)
val tasks = fns map (fn => scala.actors.Futures.future { fn(l) })
val results = tasks map (future => future.apply())

Implementing asynchronous patterns using pooling and Futures

scala-redis is a blocking client for Redis. But you can develop high performance asynchronous patterns of computation using scala-redis and Futures. RedisClientPool allows you to work with multiple RedisClient instances and Futures offer a non-blocking semantics on top of this. The combination can give you good numbers for implementing common usage patterns like scatter/gather. Here's an example that you will also find in the test suite. It uses the scatter/gather technique to do loads of push across many lists in parallel. The gather phase pops from all those lists in parallel and does some compuation over them.

Here's the main routine that implements the pattern:

// set up Executors
val system = ActorSystem("ScatterGatherSystem")
import system.dispatcher

val timeout = 5 minutes

private[this] def flow[A](noOfRecipients: Int, opsPerClient: Int, keyPrefix: String,
  fn: (Int, String) => A) = {
  (1 to noOfRecipients) map {i =>
    Future {
      fn(opsPerClient, keyPrefix + i)
    }
  }
}

// scatter across clients and gather them to do a sum
def scatterGatherWithList(opsPerClient: Int)(implicit clients: RedisClientPool) = {
  // scatter
  val futurePushes = flow(100, opsPerClient, "list_", listPush)

  // concurrent combinator: Future.sequence
  val allPushes = Future.sequence(futurePushes)

  // sequential combinator: flatMap
  val allSum = allPushes flatMap {result =>
    // gather
    val futurePops = flow(100, opsPerClient, "list_", listPop)
    val allPops = Future.sequence(futurePops)
    allPops map {members => members.sum}
  }
  Await.result(allSum, timeout).asInstanceOf[Long]
}

// scatter across clietns and gather the first future to complete
def scatterGatherFirstWithList(opsPerClient: Int)(implicit clients: RedisClientPool) = {
  // scatter phase: push to 100 lists in parallel
  val futurePushes = flow(100, opsPerClient, "seq_", listPush)

  // wait for the first future to complete
  val firstPush = Future.firstCompletedOf(futurePushes)

  // do a sum on the list whose key we got from firstPush
  val firstSum = firstPush map {key =>
    listPop(opsPerClient, key)
  }
  Await.result(firstSum, timeout).asInstanceOf[Int]
}

Using Pub/Sub

See an example implementation using Akka at https://github.com/debasishg/akka-redis-pubsub.

RedisCluster

RedisCluster uses data sharding (partitioning) which splits all data across available Redis instances, so that every instance contains only a subset of the keys. Such process allows mitigating data grown by adding more and more instances and dividing the data to smaller parts (shards or partitions).

RedisCluster allows user to pass a special KeyTag, that helps to distribute keys according to special requirements. Otherwise node is selected by hashing the whole key with CRC-32 function.

RedisCluster also allows for dynamic nodes modification with addServer, replaceServer and removeServer methods. Note that data on the disconnected node will be lost immediately. What is more, since modification of the cluster impacts key distribution, some of the data scattered across the cluster could be lost as well.

For automatic node downtime handling, by disconnecting the offline node and reconnecting it as it comes back up, there is a Reconnectable trait. To allow such behaviour mix it into RedisCluster instance:

new RedisCluster(nodes, Some(NoOpKeyTag)) with Reconnectable

you can observe it's behaviour in ReconnectableSpec test.

Encryption/AUTH

For use with cloud services such as AWS Elasticache or GC Memory Store, notice a nuance when enabling encryption in transit. This will require both an AUTH password and sslContext when connecting. For example:

new RedisClient("your-aws-endpoint", 6379, sslContext = Some(SSLContext.getDefault()), secret = "your-auth-secret")

License

This software is licensed under the Apache 2 license, quoted below.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

scala-redis's People

Contributors

akalinovskiy avatar analytically avatar arjenw avatar coldfire-x avatar debasishg avatar derekjw avatar dskrvk avatar felipehummel avatar guersam avatar hughsimpson avatar joekearney avatar khannedy avatar masahitojp avatar mattusifer avatar mosesn avatar msval avatar naderghanbari avatar nicmarti avatar noahlz avatar ocadaruma avatar patriknw avatar relaxkeren avatar sqs avatar timotta avatar tsuyoshizawa avatar uxio0 avatar vikraman avatar xuwei-k avatar zizzle6717 avatar zsolt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scala-redis's Issues

Crash in the build compiler

sorry, me again...

I follow the code in your "akka-redis-pubsub", and everything seems fine, but when I try to run the eclipse project, the IDE told me that I have some error. here is the message in Markers :

The SBT builder crashed while compiling your project. This is a bug in the Scala compiler or SBT. Check the Erorr Log for details. The error message is: null pub_sub_scala Unknown Scala Problem

and the Error Log is below :

null
Error
Mon Aug 20 14:24:00 CST 2012

Crash in the build compiler.

java.lang.Error
at scala.tools.nsc.symtab.SymbolTable.abort(SymbolTable.scala:35)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:737)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$typedApply$1$1.apply(Typers.scala:3355)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$typedApply$1$1.apply(Typers.scala:3355)
at scala.tools.nsc.typechecker.Typers$Typer.silent(Typers.scala:624)
at scala.tools.nsc.typechecker.Typers$Typer.typedApply$1(Typers.scala:3355)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:4106)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typedStat$1(Typers.scala:2100)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.collection.immutable.List.loop$1(List.scala:148)
at scala.collection.immutable.List.mapConserve(List.scala:164)
at scala.tools.nsc.typechecker.Typers$Typer.typedStats(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer.typedBlock(Typers.scala:1919)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3953)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4346)
at scala.tools.nsc.typechecker.Typers$Typer.typedIf$1(Typers.scala:3160)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3989)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typedBlock(Typers.scala:1920)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3953)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.transformedOrTyped(Typers.scala:4430)
at scala.tools.nsc.typechecker.Typers$Typer.typedDefDef(Typers.scala:1760)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3921)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typedStat$1(Typers.scala:2100)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.collection.immutable.List.loop$1(List.scala:148)
at scala.collection.immutable.List.mapConserve(List.scala:164)
at scala.tools.nsc.typechecker.Typers$Typer.typedStats(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer.typedTemplate(Typers.scala:1512)
at scala.tools.nsc.typechecker.Typers$Typer.typedClassDef(Typers.scala:1278)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3912)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typedStat$1(Typers.scala:2100)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.collection.immutable.List.loop$1(List.scala:148)
at scala.collection.immutable.List.mapConserve(List.scala:164)
at scala.tools.nsc.typechecker.Typers$Typer.typedStats(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer.typedBlock(Typers.scala:1919)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3953)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.transformedOrTyped(Typers.scala:4430)
at scala.tools.nsc.typechecker.Typers$Typer.typedDefDef(Typers.scala:1760)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3921)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typedStat$1(Typers.scala:2100)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.collection.immutable.List.loop$1(List.scala:148)
at scala.collection.immutable.List.mapConserve(List.scala:164)
at scala.tools.nsc.typechecker.Typers$Typer.typedStats(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer.typedTemplate(Typers.scala:1512)
at scala.tools.nsc.typechecker.Typers$Typer.typedClassDef(Typers.scala:1278)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3912)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typedStat$1(Typers.scala:2100)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer$$anonfun$24.apply(Typers.scala:2184)
at scala.collection.immutable.List.loop$1(List.scala:148)
at scala.collection.immutable.List.mapConserve(List.scala:164)
at scala.tools.nsc.typechecker.Typers$Typer.typedStats(Typers.scala:2184)
at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3908)
at scala.tools.nsc.transform.Erasure$Eraser.typed1(Erasure.scala:731)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4273)
at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4333)
at scala.tools.nsc.transform.Erasure$ErasureTransformer$$anonfun$transform$1.apply(Erasure.scala:1182)
at scala.tools.nsc.transform.Erasure$ErasureTransformer$$anonfun$transform$1.apply(Erasure.scala:1177)
at scala.tools.nsc.symtab.SymbolTable.atPhase(SymbolTable.scala:95)
at scala.tools.nsc.transform.Erasure$ErasureTransformer.transform(Erasure.scala:1177)
at scala.tools.nsc.ast.Trees$Transformer.transformUnit(Trees.scala:892)
at scala.tools.nsc.transform.Transform$Phase.apply(Transform.scala:30)
at scala.tools.nsc.Global$GlobalPhase.applyPhase(Global.scala:329)
at scala.tools.nsc.Global$GlobalPhase$$anonfun$run$1.apply(Global.scala:297)
at scala.tools.nsc.Global$GlobalPhase$$anonfun$run$1.apply(Global.scala:297)
at scala.collection.Iterator$class.foreach(Iterator.scala:772)
at scala.collection.mutable.ListBuffer$$anon$1.foreach(ListBuffer.scala:318)
at scala.tools.nsc.Global$GlobalPhase.run(Global.scala:297)
at scala.tools.nsc.Global$Run.compileSources(Global.scala:953)
at scala.tools.nsc.Global$Run.compile(Global.scala:1041)
at xsbt.CompilerInterface.run(CompilerInterface.scala:106)
at scala.tools.eclipse.buildmanager.sbtintegration.ScalaSbtCompiler.compile(ScalaSbtCompiler.scala:46)
at scala.tools.eclipse.buildmanager.sbtintegration.AnalysisCompile$$anonfun$6.compileScala$1(AnalysisCompile.scala:94)
at scala.tools.eclipse.buildmanager.sbtintegration.AnalysisCompile$$anonfun$6.apply(AnalysisCompile.scala:130)
at scala.tools.eclipse.buildmanager.sbtintegration.AnalysisCompile$$anonfun$6.apply(AnalysisCompile.scala:72)
at sbt.inc.IncrementalCompile$$anonfun$doCompile$1.apply(Compile.scala:21)
at sbt.inc.IncrementalCompile$$anonfun$doCompile$1.apply(Compile.scala:19)
at sbt.inc.Incremental$.cycle(Incremental.scala:33)
at sbt.inc.Incremental$.compile(Incremental.scala:20)
at sbt.inc.IncrementalCompile$.apply(Compile.scala:17)
at scala.tools.eclipse.buildmanager.sbtintegration.AnalysisCompile.doCompile(AnalysisCompile.scala:148)
at scala.tools.eclipse.buildmanager.sbtintegration.EclipseSbtBuildManager.runCompiler(EclipseSbtBuildManager.scala:178)
at scala.tools.eclipse.buildmanager.sbtintegration.EclipseSbtBuildManager.update(EclipseSbtBuildManager.scala:141)
at scala.tools.eclipse.buildmanager.sbtintegration.EclipseSbtBuildManager.build(EclipseSbtBuildManager.scala:241)
at scala.tools.eclipse.ScalaProject.build(ScalaProject.scala:579)
at scala.tools.eclipse.ScalaBuilder.build(ScalaBuilder.scala:114)
at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:728)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:199)
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:239)
at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:292)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42)
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:295)
at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:351)
at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:374)
at org.eclipse.core.internal.events.AutoBuildJob.doBuild(AutoBuildJob.java:143)
at org.eclipse.core.internal.events.AutoBuildJob.run(AutoBuildJob.java:241)

at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54)

I think that maybe have something to do with RedisClient, cause when I deleted client.publish(channel,message), then the error disappeared
I don't know what happened and how could I fix this problem...

my code is here:
https://www.dropbox.com/sh/dp6hu5el96ofv6d/YJaFRKGXIa/Akka_scala_subpub

Thank you for your time, hope you can figure this out

Simple bug in Cluster

override def rpush(key: Any, value: Any, values: Any_)(implicit format: Format) = processForKey(key)(_.lpush(key, value, values:__))

should be

override def rpush(key: Any, value: Any, values: Any_)(implicit format: Format) = processForKey(key)(_.rpush(key, value, values:__))

Notification to application when redis server is down

When redis server is down, PubSub throws RedisConnectionException which crashes Consumer. There is no way for application to handle it, trigger recovery or do something. A graceful handling could be considered there, like listener for application so that application can do something.

initial reply byte in RedisCluster

Hi,

I got a very strange error when I use RedisCluster. It works well generally. When it takes requests from multiple threads, I got

Protocol error: Got (O,[B@1f639d92) as initial reply byte

However, if I change nodes to localhost on different ports, it works well again. Very confusing. Does anyone see this before?

Thanks

RedisClientPool not detecting dead RedisClient?

Hi @debasishg ,

I'm using RedisClientPool to access my redis server which has a config timeout set to 300. And after some period of inactivities, the subsequent requests to redis got hang. But when timeout was set to 0, everything went fine and the problem didn't occur.

I believe that is caused by RedisClientPool not awaring of server closed connections in case of timeouts.

And I'm using redisclient_2.9.1-2.5.jar and my code is as following:

val pool = new RedisClientPool(redisHost,redisPort)
pool.withClient{
client =>
client.hget(key,field)
}

Am I missing something?

Publisher connection timeout causes exception and message losing

Hi,

I find there is a bug in Redis.send method. Steps to reproduce:

  1. create a RedisClient instance which connects to a host.
  2. when this connection keeps idle and timeout, redis server closes this connection
  3. publish a message from this redis client instance and SocketException is thrown

This issue only occurs on the first message after timeout.

To fix it:

add "case e: SocketException" in Redis.send method with the same exception handling in RedisConnectionException

Suggestion for better error handling

Hi,

Currently every command result is Option[T] even if the command always return some value, so we always need to use match or .get.

As using new akka IO having Future, I suggest using Promise.failure when error occurs and giving Option only when necessary to keep the API simple.

What are your thoughts?

overloaded method value apply

Hi, I follow the PubSubServer.scala, I divide Subscriber and Publisher to two individual classes,
but in the Sub.scala (object Sub), like below:

...................

object Sub {
val actorSystem = ActorSystem("PubSubActorSystem")
println("starting subscription service...")
var r = new RedisClient("localhost",6379)
var s = actorSystem.actorOf(Props(new Subscriber(r)))
^
this line have strange error:
"overloaded method value apply with alternatives: (creator: () => akka.actor.Actor,dispatcher: String,routerConfig: akka.routing.RouterConfig,deploy: akka.actor.Deploy)akka.actor.Props (behavior: akka.actor.ActorContext => akka.actor.Actor.Receive)akka.actor.Props (creator: akka.japi.Creator[_ <: akka.actor.Actor])akka.actor.Props (creator: => akka.actor.Actor)akka.actor.Props (actorClass: Class[_ <: akka.actor.Actor])akka.actor.Props [T <: akka.actor.Actor](implicit evidence$1: ClassManifest[T])akka.actor.Props cannot be applied to (org.sample.pubsub.Subscriber) Sub.scala /pub_sub_scala/src/org/sample/pubsub line 12 Scala Problem"

.................

Anyone know what's happened?

sorry for the mess, hope someone can help me

Thank you for your time!

Jar for Scala 2.10.2

Hello, can I build the jar file for this library for scala 2.10.2? I tried with "sbt package" but it runs into some issues, namely something like:

java.lang.NoClassDefFoundError: scala/reflect/internal/Trees

I looked up in the internet and they are saying are problems with the idea editor, but I'm not really using it, I've just cloned the library and made sbt package in the main folder.

Thank You for this library, it's very nice :)

detect connection lost to server and reconnect

What is a preferable logic to detect connection lost and reconnect to server?

It seems connected and connect methods aren't working as I expect. Surprisingly, select method works and reconnects to server.

How do I check if connection to server has been lost?

passing client with case classes

Hello, I wonder if is possible to pass instances of RedisClient with case classes, because I get a strange error when I try to use it, something like "wrong parameter" ?

Add support for a redis failover proxy

Redis supports master slave configurations and manual failover. Unfortunately, automated failover is unlikely to be added until the RedisCluster project is live for a while. In the meantime, one great solution is the redis_failover gem -> https://github.com/ryanlecompte/redis_failover - this adds a zookeeper backed redis node manager that can automate the slave failover in the event of a node going down. This did require some additional client work, as redis_failover includes a client with some 'smarts' to take advantage of the high-availability features. Would supporting a failover framework like this be something within the scope of scala-redis?

Doesn't receive pmessage

I try to receive some pmessage from redis, but couldn't message.

  val client = new RedisClient("localhost", 6379)
  client.pSubscribe("*") { pubsub => println(pubsub) }

results:

S(*,1)

I'm using a redis version 2.4.17.

RedisCluster always tries to connect to localhost

When using redisCluster with a list of non-local node, rediscluster will still try and connect locally.

val redisCluster = new RedisCluster(Seq("192.168.0.1:6379"): _*) { val keyTag: Option[KeyTag] = Some(NoOpKeyTag) }

Running the above code without a localhost:6379 listener will fail:

java.lang.RuntimeException: java.net.ConnectException: Connection refused
    at com.redis.IO$class.connect(IO.scala:37) ~[redisclient_2.9.1-2.4.2.jar:na]
    at com.redis.RedisClient.connect(RedisClient.scala:41) ~[redisclient_2.9.1-2.4.2.jar:na]
    at com.redis.RedisClient.<init>(RedisClient.scala:44) ~[redisclient_2.9.1-2.4.2.jar:na]
    at com.redis.RedisClient.<init>(RedisClient.scala:46) ~[redisclient_2.9.1-2.4.2.jar:na]
    at com.redis.cluster.RedisCluster.<init>(RedisCluster.scala:59) ~[redisclient_2.9.1-2.4.2.jar:na]

Possibly because of this

def this() = this("localhost", 6379)

mget design qeustion.

So this happens when i want to use mget to get a list of keys

scala> val k=Seq("a","b","c")
k: Seq[String] = List(a, b, c)

It only works as follows
scala> r.mget(Nil,k:_*)
res11: Option[List[Option[String]]] = Some(List(None, Some(3), Some(2), Some(1)))

I tried mget with just taking the second parameter but the console throws out errors.
My question is why mget need two parameters? (key is a key and keys as vararg)
The implementation seems to prepend key to keys anyway...

Inconsistency between LLEN and LRANGE

The code for llen returns a Long, but the arguments for lrange are Int.

This means that if you query for the length of a list, it is possible that you will get
a value that cannot, in turn, be passed to lrange. This doesn't seem logical to
me.

If, in fact, Redis indices can be longs, then lrange (and probably some others) should take Long as their argument type. If Redis indices are restricted to Int
then llen should return that. What we have now is some kind of odd in-between.

Or am I missing something?

evalMultiBulk problem

Hello!
I have lua script that works well in redis-cli, but return error:

Protocol error: Got (*,[B@db75d45) as initial reply byte

, when I try to execute it with evalMultiBulk.

redis-cli:
eval "local ids = redis.call('SORT', 'users', 'ALPHA', 'BY', 'user:*->name'); local result = {}; for index, value in ipairs(ids) do table.insert(result, redis.call('hgetall', 'user:'..value)); end return result;" 0

scala-redis:
r.evalMultiBulk("local ids = redis.call('SORT', 'users', 'ALPHA', 'BY', 'user:*->name'); local result = {}; for index, value in ipairs(ids) do table.insert(result, redis.call('hgetall', 'user:'..value)); end return result;", List.empty, List.empty)

Scala 2.10.0-RC1 support

Any plan to add Scala 2.10.0-RC1 support?

I've tried to use

"net.debasishg" % "redisclient_2.9.2" % "2.7"

but I get

java.lang.NoClassDefFoundError: scala/reflect/ClassManifest 
at com.redis.RedisClient.<init>(RedisClient.scala:53)

error at runtime.

Redis subscribe throwing exception

Based on your Test example PubSubServerDemo.scala to subscribe to a keyword, we used it in Spark Streaming.

When we subscribe as sub("UPDATED_KEYWORD"), we are getting the message,

subscribed to UPDATED_KEYWORD and count = 1

but as soon as we publish through redis client, as <publish "UPDATED_KEYWORD" "TRUE"> we get an exception,

Fatal error caused consumer dead. Please init new consumer reconnecting to master or connect to backup java.lang.NullPointerException

We are using java 1.6, scala 1.9.3 and built your version with scala 1.9.3.
Akka jars that we are using: akka-actor-2.0.3.jar, akka-remote-2.0.3.jar, akka-slf4j-2.0.3.jar, akka-zeromq-2.0.3.jar

Are we doing something wrong ?
Please let me know if you need any more information from my side.

Add connection timeout setting

It would be great to have ability to set connection timeout.

For instance it would be handy for hostname resolution problems(on my host almost 1 minute till java.net.ConnectException thrown). Try yourself:

new RedisClientPool("nonexistent", 6378)

Question about connection pooling and thread safety

My understanding is that RedisClient is not thread safe (it would be bad to instantiate one instance of RedisClient and use it in multiple akka actors). Can you confirm?

So I would have two choices:

  1. Instantiate one RedisClient in each of my actors
  2. Use RedisClientPool

The latter is almost definitely desirable because then I don't have to have one open connection for each of my hundreds of actors.

Getting "ERR operation not permitted" after a while

Redis client throws an exception with the message "ERR operation not permitted" (after I successfully have authenticated). This happens only after a while. Is it possible that the connection is reset or something if it is not being used for some time?

akka.pattern.AskTimeoutException: sending to terminated ref breaks promises

In my SampleTest application, I design the code like after receiving new search pattern from the client the StreamingContext should be stoped (ssc.stop()) and again start with the newly assigned pattern ( ssc.start() ).

Stoping the StreamingContext (ssc.stop() ) is fine but, while starting the StreamingContext (ssc.start() ) it will through the akka.pattern.AskTimeoutException: sending to terminated ref breaks promises exception. I would also want to know why this exception occurs in the code.

Let me know if required any additional information regarding to this problem.

AUTH support?

I think i put it in the wrong place originally,
acrosa#14

If I try
val redisClient = RedisClient(endpoint)
redisClient.auth("pwd")

I get an exception

at akka.pattern.AskableActorRef$.ask$extension(AskSupport.scala:136)
at com.redis.api.KeyOperations$class.auth(KeyOperations.scala:112)
at com.redis.api.RedisOps.auth(RedisOps.scala:7)

OutOfMemory exception at com.redis.IO$class.readLine(IO.scala:97)

Code that causes it (from time to time) is redis.setex(k: String, timeout: Int, value: Array[Byte]). In IO#readLine(), the 'build' length grows infinitely. I believe setex() is only expecting a boolean to be read off the wire.

My temporary workaround is to return null from readLine() if the builder gets over 100KB.

java.lang.OutOfMemoryError: Java heap space
at scala.collection.mutable.ArrayBuilder$ofByte.mkArray(ArrayBuilder.scala:115)
at scala.collection.mutable.ArrayBuilder$ofByte.resize(ArrayBuilder.scala:121)
at scala.collection.mutable.ArrayBuilder$ofByte.ensureSize(ArrayBuilder.scala:133)
at scala.collection.mutable.ArrayBuilder$ofByte.$plus$eq(ArrayBuilder.scala:138)
at com.redis.IO$class.readLine(IO.scala:97)
at com.redis.RedisClient.readLine(RedisClient.scala:41)
at com.redis.Reply$class.receive(RedisProtocol.scala:101)
at com.redis.RedisClient.receive(RedisClient.scala:41)
at com.redis.R$class.asBoolean(RedisProtocol.scala:116)
at com.redis.RedisClient.asBoolean(RedisClient.scala:41)
at com.redis.StringOperations$$anonfun$setex$1.apply$mcZ$sp(StringOperations.scala:28)
at com.redis.StringOperations$$anonfun$setex$1.apply(StringOperations.scala:28)
at com.redis.StringOperations$$anonfun$setex$1.apply(StringOperations.scala:28)
at com.redis.Redis$class.send(RedisClient.scala:19)
at com.redis.RedisClient.send(RedisClient.scala:41)
at com.redis.StringOperations$class.setex(StringOperations.scala:28)
at com.redis.RedisClient.setex(RedisClient.scala:41)

can't import RedisClient

as title, I can't use RedisClient as you did in Readme
it says "not found: type RedisClient", but I did import com.redis

it would be very appreciate if you can help

thanks!

slf4j dependency problem

I'm using Scala 10 with Java 1.7 and am receiving this error -

"Error in Scala compiler: bad symbolic reference. A signature in Log.class refers to term slf4j in package org which is not available. It may be completely missing from the current classpath, or the version on the classpath might be incompatible with the version used when compiling Log.class. HelloRedis Unknown Scala Problem"

Build error

Hello, i've just cloned the project and did "$ sbt", and the next exception appeared:

Mardos-MacBook-Pro:scala-redis mardo$ sbt
[info] Loading global plugins from /Users/mardo/.sbt/plugins
[info] Updating {file:/Users/mardo/.sbt/plugins/}default-462dc6...
[info] Resolving com.typesafe.startscript#xsbt-start-script-plugin;0.5.2 ...
[warn] module not found: com.typesafe.startscript#xsbt-start-script-plugin;0.5.2
[warn] ==== typesafe-ivy-releases: tried
[warn] http://repo.typesafe.com/typesafe/ivy-releases/com.typesafe.startscript/xsbt-start-script-plugin/scala_2.9.2/sbt_0.12/0.5.2/ivys/ivy.xml
[warn] ==== sbt-plugin-releases: tried
[warn] http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases/com.typesafe.startscript/xsbt-start-script-plugin/scala_2.9.2/sbt_0.12/0.5.2/ivys/ivy.xml
[warn] ==== local: tried
[warn] /Users/mardo/.ivy2/local/com.typesafe.startscript/xsbt-start-script-plugin/scala_2.9.2/sbt_0.12/0.5.2/ivys/ivy.xml
[warn] ==== typesafe-ivy-releases: tried
[warn] http://repo.typesafe.com/typesafe/ivy-releases/com.typesafe.startscript/xsbt-start-script-plugin/scala_2.9.2/sbt_0.12/0.5.2/ivys/ivy.xml
[warn] ==== public: tried
[warn] http://repo1.maven.org/maven2/com/typesafe/startscript/xsbt-start-script-plugin_2.9.2_0.12/0.5.2/xsbt-start-script-plugin-0.5.2.pom
[info] Resolving org.scala-sbt#sbt;0.12.0 ...
[info] Resolving org.scala-sbt#main;0.12.0 ...
[info] Resolving org.scala-sbt#actions;0.12.0 ...
[info] Resolving org.scala-sbt#classpath;0.12.0 ...
[info] Resolving org.scala-sbt#launcher-interface;0.12.0 ...
[info] Resolving org.scala-lang#scala-library;2.9.2 ...
[info] Resolving org.scala-sbt#interface;0.12.0 ...
[info] Resolving org.scala-sbt#io;0.12.0 ...
[info] Resolving org.scala-sbt#control;0.12.0 ...
[info] Resolving org.scala-lang#scala-compiler;2.9.2 ...
[info] Resolving org.scala-sbt#completion;0.12.0 ...
[info] Resolving org.scala-sbt#collections;0.12.0 ...
[info] Resolving jline#jline;1.0 ...
[info] Resolving org.scala-sbt#api;0.12.0 ...
[info] Resolving org.scala-sbt#compiler-integration;0.12.0 ...
[info] Resolving org.scala-sbt#incremental-compiler;0.12.0 ...
[info] Resolving org.scala-sbt#logging;0.12.0 ...
[info] Resolving org.scala-sbt#process;0.12.0 ...
[info] Resolving org.scala-sbt#compile;0.12.0 ...
[info] Resolving org.scala-sbt#persist;0.12.0 ...
[info] Resolving org.scala-tools.sbinary#sbinary_2.9.0;0.4.0 ...
[info] Resolving org.scala-sbt#classfile;0.12.0 ...
[info] Resolving org.scala-sbt#compiler-ivy-integration;0.12.0 ...
[info] Resolving org.scala-sbt#ivy;0.12.0 ...
[info] Resolving org.apache.ivy#ivy;2.3.0-rc1 ...
[info] Resolving com.jcraft#jsch;0.1.46 ...
[info] Resolving commons-httpclient#commons-httpclient;3.1 ...
[info] Resolving commons-logging#commons-logging;1.0.4 ...
[info] Resolving commons-codec#commons-codec;1.2 ...
[info] Resolving org.scala-sbt#run;0.12.0 ...
[info] Resolving org.scala-sbt#task-system;0.12.0 ...
[info] Resolving org.scala-sbt#tasks;0.12.0 ...
[info] Resolving org.scala-sbt#tracking;0.12.0 ...
[info] Resolving org.scala-sbt#cache;0.12.0 ...
[info] Resolving org.scala-sbt#testing;0.12.0 ...
[info] Resolving org.scala-sbt#test-agent;0.12.0 ...
[info] Resolving org.scala-tools.testing#test-interface;0.5 ...
[info] Resolving org.scala-sbt#command;0.12.0 ...
[info] Resolving org.scala-sbt#compiler-interface;0.12.0 ...
[info] Resolving org.scala-sbt#precompiled-2_8_2;0.12.0 ...
[info] Resolving org.scala-sbt#precompiled-2_10_0-m4;0.12.0 ...
[warn] ::::::::::::::::::::::::::::::::::::::::::::::
[warn] :: UNRESOLVED DEPENDENCIES ::
[warn] ::::::::::::::::::::::::::::::::::::::::::::::
[warn] :: com.typesafe.startscript#xsbt-start-script-plugin;0.5.2: not found
[warn] ::::::::::::::::::::::::::::::::::::::::::::::
[warn]
[warn] Note: Some unresolved dependencies have extra attributes. Check that these dependencies exist with the requested attributes.
[warn] com.typesafe.startscript:xsbt-start-script-plugin:0.5.2 (sbtVersion=0.12, scalaVersion=2.9.2)
[warn]
sbt.ResolveException: unresolved dependency: com.typesafe.startscript#xsbt-start-script-plugin;0.5.2: not found
at sbt.IvyActions$.sbt$IvyActions$$resolve(IvyActions.scala:211)
at sbt.IvyActions$$anonfun$update$1.apply(IvyActions.scala:122)
at sbt.IvyActions$$anonfun$update$1.apply(IvyActions.scala:121)
at sbt.IvySbt$Module$$anonfun$withModule$1.apply(Ivy.scala:114)
at sbt.IvySbt$Module$$anonfun$withModule$1.apply(Ivy.scala:114)
at sbt.IvySbt$$anonfun$withIvy$1.apply(Ivy.scala:102)
at sbt.IvySbt.liftedTree1$1(Ivy.scala:49)
at sbt.IvySbt.action$1(Ivy.scala:49)
at sbt.IvySbt$$anon$3.call(Ivy.scala:58)
at xsbt.boot.Locks$GlobalLock.withChannel$1(Locks.scala:75)
at xsbt.boot.Locks$GlobalLock.withChannelRetries$1(Locks.scala:58)
at xsbt.boot.Locks$GlobalLock$$anonfun$withFileLock$1.apply(Locks.scala:79)
at xsbt.boot.Using$.withResource(Using.scala:11)
at xsbt.boot.Using$.apply(Using.scala:10)
at xsbt.boot.Locks$GlobalLock.liftedTree1$1(Locks.scala:51)
at xsbt.boot.Locks$GlobalLock.withLock(Locks.scala:51)
at xsbt.boot.Locks$.apply0(Locks.scala:30)
at xsbt.boot.Locks$.apply(Locks.scala:27)
at sbt.IvySbt.withDefaultLogger(Ivy.scala:58)
at sbt.IvySbt.withIvy(Ivy.scala:99)
at sbt.IvySbt.withIvy(Ivy.scala:95)
at sbt.IvySbt$Module.withModule(Ivy.scala:114)
at sbt.IvyActions$.update(IvyActions.scala:121)
at sbt.Classpaths$$anonfun$work$1$1.apply(Defaults.scala:949)
at sbt.Classpaths$$anonfun$work$1$1.apply(Defaults.scala:947)
at sbt.Classpaths$$anonfun$doWork$1$1$$anonfun$54.apply(Defaults.scala:970)
at sbt.Classpaths$$anonfun$doWork$1$1$$anonfun$54.apply(Defaults.scala:968)
at sbt.Tracked$$anonfun$lastOutput$1.apply(Tracked.scala:35)
at sbt.Classpaths$$anonfun$doWork$1$1.apply(Defaults.scala:972)
at sbt.Classpaths$$anonfun$doWork$1$1.apply(Defaults.scala:967)
at sbt.Tracked$$anonfun$inputChanged$1.apply(Tracked.scala:45)
at sbt.Classpaths$.cachedUpdate(Defaults.scala:975)
at sbt.Classpaths$$anonfun$45.apply(Defaults.scala:855)
at sbt.Classpaths$$anonfun$45.apply(Defaults.scala:852)
at sbt.Scoped$$anonfun$hf10$1.apply(Structure.scala:586)
at sbt.Scoped$$anonfun$hf10$1.apply(Structure.scala:586)
at scala.Function1$$anonfun$compose$1.apply(Function1.scala:49)
at sbt.Scoped$Reduced$$anonfun$combine$1$$anonfun$apply$12.apply(Structure.scala:311)
at sbt.Scoped$Reduced$$anonfun$combine$1$$anonfun$apply$12.apply(Structure.scala:311)
at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40)
at sbt.std.Transform$$anon$5.work(System.scala:71)
at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:232)
at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:232)
at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:18)
at sbt.Execute.work(Execute.scala:238)
at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:232)
at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:232)
at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
at sbt.CompletionService$$anon$2.call(CompletionService.scala:30)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:680)
error sbt.ResolveException: unresolved dependency: com.typesafe.startscript#xsbt-start-script-plugin;0.5.2: not found

sbt build with redisclient version 2.12

Hi,
Thanks a lot for redisclient.

I can do a sbt build with
"net.debasishg" %% "redisclient" % "2.11"
but cannot with
"net.debasishg" %% "redisclient" % "2.12"

Results in
module not found: net.debasishg#redisclient_2.10;2.12

Is it available in a particular repository other than Typesafe(repo.typesafe.com), Sonatype(oss.sanatype.org) or Maven(repo1.maven.org)?

Thanks again.

Too many Options in Set operations

SMEMBERS, SINTER, SUNION and SDIFF returns empty Set even if Set(or Sets) does not exists. So I think they should return Set[Option[A]] instead of Option[Set[Option[A]]].
What is more Set items can not be "null", so I think all this operations should return just Set[A] instead of Option[Set[Option[A]]].

All operations that returns Option[Long] should return Long, because they always return Number of affected elements or "0".

Sorry if I'm wrong and correct me, please.

New options for SET from Redis 2.6.12

Starting with Redis 2.6.12 SET supports a set of options that modify its behavior:
EX seconds -- Set the specified expire time, in seconds.
PX milliseconds -- Set the specified expire time, in milliseconds.
NX -- Only set the key if it does not already exist.
XX -- Only set the key if it already exist.
Note: Since the SET command options can replace SETNX, SETEX, PSETEX, it is possible that in future versions of Redis these three commands will be deprecated and finally removed.

Node in HashRing is coupled with host and port

The issue is if one node is down and I can't find a way to replace the problem node. The solution from https://github.com/youngking/redis-shard is quite good. Only node id/name is coupled with hash ring order. If the master is down, slave can be used to replace master without changing hash ring order.

I guess the quickest fix could be:

  1. extends RedisClientPool to let it identifiable
  2. replace node.toString with id/name of RedisClientPool's child class in HashRing.nodeHashFor
  3. add replaceNode(node: T) in HashRing and replaceServer in RedisCluster

What do you think?

Duplicate expire implementation

In Operations:

  def expire(key: Any, expiry: Int)(implicit format: Format): Boolean =
    send("EXPIRE", List(key, expiry))(asBoolean)

and in StringOperations:

  def expire(key: Any, ttl: Any)(implicit format: Format): Boolean =
    send("EXPIRE", List(key,ttl))(asBoolean)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.