GithubHelp home page GithubHelp logo

franz's Introduction

Franz is a simple reactive-ish Scala wrapper around the Amazons SQS persistent message queue service.

#Initialization First you will need an instance of the trait SQSClient. The only currently available implementation is SimpleSQSClient, which has three constructors

new SimpleSQSClient(
	credentialProvider: com.amazonaws.auth.AWSCredentialsProvider,
	region: com.amazonaws.regions.Regions,
	buffered: Boolean
)

SimpleSQSClient(
	credentials: com.amazonaws.auth.AWSCredentials,
	region: com.amazonaws.regions.Regions,
	buffered: Boolean=false
)

SimpleSQSClient(key: String, secret: String, region: com.amazonaws.regions.Regions)

(Warning: Be careful when using buffered=true. It can improve performance, but it's buggy. Use at your own risk.)

Let's use the third.

import com.amazonaws.regions.Regions
val sqs = SimpleSQSClient(<your aws access key>, <your aws secret key>, Regions.US_WEST_1)

We'll come back to how to actually get a queue from the client shortly.

#SQSQueue The type you'll be using to actually interact with an SQS Queue is SQSQueue[T]. It provides all the primitives for sending and receiving messages.

##Sending SQSQueue[T] provides multiple methods for sending messages:

def send(msg: T)(implicit ec: ExecutionContext): Future[MessageId]

There is no current use for the returned MessageId, but you can use the success of the Future as a send confimation.

If you need to pass one or more SQS message attributes along with the message, provide a Map[String,String] to the optional messageAttributes.

def send(msg: T, messageAttributes: Option[Map[String, String])(implicit ec: ExecutionContext): Future[MessageId]

You can submit up to 10 batched messages using the sendBatch method:

If you supply more than ten message the client will return a failed Future.

def send(messages: (T, Option[Map[String,String]]))(implicit ec: ExecutionContext): Future[(Seq[MessageId],Seq[MessageId])]

##Receiving

###Direct SQSQueue provides several methods for getting the next message in the queue

def next(implicit ec: ExecutionContext): Future[Option[SQSMessage[T]]]
def nextBatch(maxBatchSize: Int)(implicit ec: ExecutionContext): Future[Seq[SQSMessage[T]]]
def nextWithLock(lockTimeout: FiniteDuration)(implicit ec: ExecutionContext): Future[Option[SQSMessage[T]]]
def nextBatchWithLock(maxBatchSize: Int, lockTimeout: FiniteDuration)(implicit ec: ExecutionContext): Future[Seq[SQSMessage[T]]]

The returned SQSMessage[T] objects have the fields

val body: T //actual message payload
val attributes: Map[String,String] //raw attributes from com.amazonaws.services.sqs.model.Message
val consume: () => Unit //deletes the message from the queue

and the method

def consume[K](block: T => K): K

Which will call consume if no exception is thrown so you can do either

    processMyEvent(sqsMessage.body)
    sqsMessage.consume()

or

    sqsMessage.consume { body =>
        processMyEvent(body)
    }

The *WithLock methods lock (or rather, hide) the retrieved message(s) in the queue so that no other call will retrieve them during the lock timeout. You need to call consume on the message before the timeout expires in order to permanently remove it form the queue.

If the lock expires the message will again be available for retrieval, which is useful e.g. in case of an error when consume was never called.

The implementation uses 20 second long polls behind the scenes. If no message was available within that time a None or Seq.empty will be returned (depending on the method used). Note that due to the distributed and eventually consistent nature of SQS it is sometimes possible to get an empty response even if there are some (but few) messages in the queue if you happen to poll an empty node. The best practice solution to that is continuous retries, i.e. you'll make 3 requests per minute.

###Iteratees For the more functionally inclined SQSQueue[T] also provides enumerators to be used with your favorite Iteratee

def enumerator(implicit ec: ExecutionContext): Enumerator[SQSMessage[T]]
def enumeratorWithLock(lockTimeout: FiniteDuration)(implicit ec: ExecutionContext): Enumerator[SQSMessage[T]]

The semantics of retrievel and locking are identical to those of the next* methods.

#Getting a Queue

SQSClient currently has three methods for getting a specific queue

def simple(queue: QueueName, createIfNotExists: Boolean=false): SQSQueue[String]
def json(queue: QueueName, createIfNotExists: Boolean=false): SQSQueue[JsValue]
def formatted[T](queue: QueueName, createIfNotExists: Boolean=false)(implicit format: Format[T]): SQSQueue[T]

Where Format[T] and JsValue are form play.api.libs.json. QueueName is simply a typed wrapper around a string, which should be the full queue name (not the queue url).

#SQS Limitations

  • Fairly high latency. Not really suitable for things that require immediate action.
  • Message size is limited to ~64KB.
  • FIFO not guaranteed for messages sent close together (i.e. there is no strict ordering of messages).
  • Multicasting is somewhat cumbersome (could be done through SNS fanout).
  • No replay. Once a message is consumed, it's gone.

#Installation

You can get Franz from maven central. The artifact is franz_2.10 or franz_2.11 and the group id is com.kifi. The current version is 0.3.16. For example, if you are using sbt, just add this to your dependencies:

"com.kifi" % "franz_2.11" % "0.3.16"

To add a dependency that matches your scala version (2.10.x or 2.11.x), use

"com.kifi" %% "franz" % "0.3.16"

All classes are in in com.kifi.franz.

#See Also

Kifi's Reactive Scala Wrapper for Amazon SQS blog post

franz's People

Contributors

andrewconner avatar bwayne avatar eishay avatar jeffsteinmetz avatar jerezzprime avatar leogrim avatar marekzebrowski avatar mbseid avatar neil-rubens avatar nielsdraaisma avatar stkem 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

Watchers

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

franz's Issues

Possibility of duplicate queue reads via nextBatchRequestWithLock?

In SQSQueue.scala, I can see that nextBatchRequestWithLock creates a ReceiveMessageRequest , asynchronously submits that to an SQS queue, and returns a Future of the eventual SQSMessage result. I've been trying to Google the answer to this question, but is it safe to assume that on the SQS/AWS side, the ReceiveMessageRequest's will be processed in sequence of submission and that the eventual ReceiveMessageResult's will be distinct across the async Future's created?

My concern comes from the fact that I have a scenario where I will be firing off n requests to nextWithLock in very very close sequence (ns to ms). I'm wondering if I should write code to ensure distinctness of received messages across these requests.

Duplicates while using enumerator/iteratee

This could potentially problem in my code or an issue in the implementation of enumerator. I see duplicates while using enumerator (I understand that SQS can occasionally return duplicate messages, but this is consistent). Maybe I am using enumerator/Iteratee incorrectly. Attached is the code.

Do you see anything fishy before I look more closely at the implementation.

Thanks
Amit

object SimSQSConsumer {
  def main(args:Array[String]):Unit={
    val awsKey = System.getProperty("aws.key")
    val awsSecret = System.getProperty("aws.secret")
    require(awsKey != null)
    require(awsSecret != null)
    import scala.concurrent._
    import ExecutionContext.Implicits.global
    val credentials = new AWSCredentials {
      def getAWSAccessKeyId() = awsKey
      def getAWSSecretKey() = awsSecret
    }
    val sqs = SimpleSQSClient(credentials, Regions.US_WEST_1, buffered = false)
    val queueName = QueueName("queue1")
    val queue = sqs.simple(queueName)
    def ensureIntervalIteratee = Iteratee.foreach{ message:SQSMessage[String] =>
      message.consume()
      println(message.body)
    }
   queue.enumerator.run(ensureIntervalIteratee)
  }

}
hello world 1
hello world 4
hello world 3
hello world 5
hello world 5
hello world 2
hello world 2
object SimSQSPublisher {

  def main(args: Array[String]): Unit = {

    val awsKey = System.getProperty("aws.key")
    val awsSecret = System.getProperty("aws.secret")

    require(awsKey != null)
    require(awsSecret != null)
    import scala.concurrent._
    import ExecutionContext.Implicits.global
    val credentials = new AWSCredentials {
      def getAWSAccessKeyId() = awsKey
      def getAWSSecretKey() = awsSecret
    }
    val sqs = SimpleSQSClient(credentials, Regions.US_WEST_1, buffered = false)
    val queueName = QueueName("queue1")
    val queue = sqs.simple(queueName)
    for(i <- 1 to 5) {
      val ft = queue.send("hello world "+ i)
      ft.onComplete {
        processSending
      }
    }
  }


  def processSending(message: Try[MessageId]): Unit = {
    message match {
      case Failure(ex) => {
        println("Failed sending:  " + ex)
      }
      case Success(messageId) => {
        println("Succeeded sending: " + messageId)
      }
    }
  }

}
Succeeded sending: MessageId(a1aee4b5-b686-48e9-9276-cc6b0e7d3504)
Succeeded sending: MessageId(fe3ac294-92b0-4f30-aedb-5ed15047c440)
Succeeded sending: MessageId(7960f941-830e-4652-878e-93f8dc98f2f3)
Succeeded sending: MessageId(1574e78d-d8b2-4b5d-ab77-ccb2ba0a5d1d)
Succeeded sending: MessageId(20267698-2721-4ddb-933a-ce338306bcdf)

Receiving messages does not work when buffered=true.

Hi,

I've got the same issue as #22 - receiving messages does not work when buffered=true. buffered=false remedies the problem, however it's a bad solution because it disables buffering of the deletion requests.

Any ideas what causes the problem? To improve the usability of the library I suggest setting the default to false and adjust the Readme while this option does not work.

Best,
Mark

README still suggests using 0.3.10 with buffered client

I understand previous issues and pull requests have pointed out that the underlying buffered client is unsafe, as well as explicitly set the buffered default value to false in all SimpleSQSClient constructors as of 0.3.11.

However, the current README still:

  • Defines 0.3.10 as the current version
  • Suggests using a constructor that explicitly sets buffered to true as of 0.3.10

I would suggest to:

  • Add additional warnings in the README about that constructor in 0.3.10
  • Define 0.3.11 as the current version in the README

Not sure how versioning is handled so I'd rather discuss instead of blindly submitting a PR.

Thanks!

Support for SNS

We have modeled some SNS support on the patterns used here for SQS. Is this something you would be interested in accepting a PR for?

Enumerator may end when SQS returns an error

I'm taking a look at it now, but I believe that the Enumerator stops after there is an error for SQS. I'm trying to isolate and fix the problem now, and I'll keep you posted.

Code I'm looking at:
SQSQueue.scala

def enumeratorWithLock(lockTimeout: FiniteDuration)(implicit ec: ExecutionContext): Enumerator[SQSMessage[T]] = Enumerator.repeatM[SQSMessage[T]]{ loopFuture(nextWithLock(lockTimeout)) }

  private def loopFuture[A](f: => Future[Option[A]], promise: Promise[A] = Promise[A]())(implicit ec: ExecutionContext): Future[A] = {
    f.onComplete {
      case util.Success(Some(res)) => promise.success(res)
      case util.Success(None) => loopFuture(f, promise)
      case util.Failure(ex) => promise.failure(ex)
    }
    promise.future
  }

Reduce AWS Java SDK dependencies

Putting in a dependency on aws-java-sdk now means pulling in about 30-odd jars, now that they've broken it up. It'd be great if you could reduce the dependencies down to what you actually need.

Is this project still alive?

There are some issues that need resolving and there's also a pending pull request. Two of the contributors at kifi haven't made any contributions in the past year, and there has been no activity for kifi/franz sine January 7th, 2017.

If the owners are no longer interested or too busy to maintain this library, would they be willing to let someone else take ownership?

Example consumer API

The consumer API using enumerators isn't completely intuitive, and it would be great to have an example in the README.

Scala 2.11

Hello,
Is 2.11 compatibility on your roadmap?
Thanks

Consuming stops on first parsing error

I'm seeing behavior where I receive a json message that my consumer did not expect and the parsing failure seems to stop the enumerator from consuming.

My assumption had been that the following will cause the enumerator to run indefinitely consuming until client is closed:

s.enumeratorWithLock(20.seconds).run(i)

What I realized is that run returns a future - from my tests this future completes on the first failure and the consuming appears to stop. Seems to me the desired behavior would be to log an error but continue consuming from the queue...right?

This issue seems to have been brought up in 2014 but closed...[https://github.com//issues/32](Issue 32)

Add a means to determine the success of consuming a message

Right now, there isn't a way to determine if a message has been successfully deleted/consumed, since SQSMessage.consume is of type () => Unit. Maybe this can be changed to return Future[Boolean], or whatever is appropriate.

That being said, thank you to the folks at Kifi (and others) for creating this simple, convenient library!

Cannot receive multiple messages

Hi,

I am trying to receive multiple messages from the queue in order to handle them. I'm working with Scala Under Play-2.3.10

Yet,I don't manage to.

Previous Investigation has shown it maybe related to the maxNumberOfMessages param.

Yet, We couldn't find a way to set it via Franz and Scala.

Many Thanks,
Shiri.

SQS receive usage

Hello,
thanks a lot for this library, looks great and what I just need.
I am new to scala, so my question may be due to my lack of scala experience/understanding, apologize in advance.

I was able to send messages to my SQS queue using SimpleSQSClient.
I am struggling with receiving these messages.

case class MyTask(taskType:String, name:String)

object MyTask {
  implicit val myTaskWrites: Writes[MyTask] = (
    (__ \ "taskType").write[String] and
      (__ \ "name").write[String]
    )(unlift(MyTask.unapply))
}

class SQSTest {
def testSend() = {
   val sqs =  SimpleSQSClient(Play.current.configuration.getString("aws.accessKey").get,
       Play.current.configuration.getString("aws.secretKey").get,
       Regions.US_EAST_1)

  val queue = sqs.json(new QueueName(Play.current.configuration.getString("my.tasks.queue").get), true)

 queue.nextBatch(10) onComplete {
       case Success(tasks) => for (task <- tasks) Logger.debug(task.body.toString())
       case Failure(t) =>  Logger.debug("An error has occured: " + t.getMessage)
     }
}
}

The thing is that I got no messages. Actually even the onSuccess() of SQSQueue.nextBatchWithLock() never called.

Will appreciate your advise.

Thanks a lot in advance.
Victor.

Problem receiving message, an example would help

An end-to-end example of 1) create queue, 2) send message, 3) receive message and 4) delete queue would be a really helpful little piece of documentation.

As it is, I can accomplish 1 & 2, but not 3. I am a scala noob, for sure, so chances are high that I'm just missing something obvious. The symptom that I see is that my onComplete never seems to get called. When executed at the command-line the program will run for about a minute and then exit.

Here is some really basic code of mine that does not do 3:

package com.learnAWSScala 

import com.amazonaws.regions.Regions
import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.concurrent.duration
import scala.util.{Success, Failure}

import com.kifi.franz.SimpleSQSClient

object Hello {

  def main(args : Array[String]) = {
    println("Hello World") 

    val sqs = SimpleSQSClient("MY ID HERE", "MY SECRET HERE", Regions.US_EAST_1)

    val q = sqs.simple(com.kifi.franz.QueueName("testScalaQueue"), true)

    q.send("Hello AWS!")

    val msg = q.next(scala.concurrent.ExecutionContext.global)

    msg onComplete {
      case Success(message) => println("got a message")
      case Failure(t) => println("An error has occured")
    }

    println("More stuff")
  }
}

SQS ExpiredToken: The security token included in the request is expired status code: 403

I have a long-running worker process running on EC2 that consumes items from an SQS queue. After a time (8-12 hours, I reckon) I begin getting expired security token errors. I would expect the aws lib to handle the refresh of credentials automatically but this seems not to be the case. Is it in anyway handled within the SimpleSQSClient? The stacktrace is as below

com.amazonaws.AmazonServiceException: The security token included in the request is expired (Service: AmazonSQS; Status Code: 403; Error Code: ExpiredToken; Request ID: 12c88087-5b49-5054-aa22-cf08d63f5dd9) at com.amazonaws.http.AmazonHttpClient.handleErrorResponse(AmazonHttpClient.java:1182) at com.amazonaws.http.AmazonHttpClient.executeOneRequest(AmazonHttpClient.java:770) at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:489) at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:310) at com.amazonaws.services.sqs.AmazonSQSClient.invoke(AmazonSQSClient.java:2419) at com.amazonaws.services.sqs.AmazonSQSClient.receiveMessage(AmazonSQSClient.java:1130) at com.amazonaws.services.sqs.AmazonSQSAsyncClient$24.call(AmazonSQSAsyncClient.java:1783) at com.amazonaws.services.sqs.AmazonSQSAsyncClient$24.call(AmazonSQSAsyncClient.java:1779) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)

Any help would be greatly appreciated. Thanks!

Multicasting

In Readme as one of the limitations it is mentioned that Multicasting is really cumbersome .

Multicasting could be easily achieved with SNS fanout.

p.s. thanks for a nice library

Ability to update the visibility timeout on a message.

It would be very convenient to have a method similar to consume, that allows the visibility timeout of a message to be extended. I'm imagining something like:

SQSMessage[T].extendLock(timeout: FiniteDuration): Future[Unit]

Is there a work around for this currently?

Enumerator closes after queue is "done"

After all the messages on the queue are consumed the enumerator closes. When a new message is queued up again, the enumerator won't consume it. I believe the enumerator is getting closing, like an EOF or something.

Here is the code I am running. The important parts at at the bottom. Where the enumerator is. Ignore most of my code. I wish I had more time to clean it up for you.

class SerializedSQSQueue[T <: AnyRef](
                            protected val sqs: AmazonSQSAsync,
                            val queue: QueueName,
                            protected val createIfNotExists: Boolean = false,
                            implicit val m:Manifest[T]) extends SQSQueue[T] {
  val tGrater = grater[T](models.mongoContext.context, m)
  protected implicit def asString(obj: T) = tGrater.toCompactJSON(obj)
  protected implicit def fromString(s: String) = tGrater.fromJSON(s)
}
class NaytevSQS(credentials: AWSCredentialsProvider, region: Regions, buffered: Boolean = true) extends SimpleSQSClient(credentials, region, buffered){
  def serialized[T <: AnyRef] (queue: QueueName, createIfNotExists: Boolean=false)(implicit m: Manifest[T]): SQSQueue[T] = {
    new SerializedSQSQueue[T](sqs, queue, createIfNotExists, m)
  }
}


object QueueService {
  val key = Play.configuration.getString("aws.sqs.key").get
  val secret = Play.configuration.getString("aws.sqs.secret").get

  val credentialProvider = new AWSCredentialsProvider {
    def getCredentials() = new AWSCredentials {
      def getAWSAccessKeyId() = key
      def getAWSSecretKey() = secret
    }
    def refresh() = {}
  }

  val SQSClient = new NaytevSQS(credentialProvider, Regions.US_EAST_1, buffered = false)


  val ENSURE_INTERVAL_QUEUE_NAME = Play.configuration.getString("aws.sqs.ensureInterval").get
  val EnsureIntervalQueue = SQSClient.serialized[EnsureInterval](QueueName(ENSURE_INTERVAL_QUEUE_NAME))

  def ensureInterval(bandit:Bandit, location:String) = {
    EnsureIntervalQueue.send(EnsureInterval(bandit, location))
  }
  def ensureIntervalIteratee = Iteratee.foreach{ message:SQSMessage[EnsureInterval] =>
    message.body
    //Time to let them know
    message.consume()
  }
  EnsureIntervalQueue.enumerator.run(ensureIntervalIteratee)

  }
}

Thanks for the help!

Maven update

Some documentation as well as some API updates have been merged to master.
The README references the latest version as 0.3.9.

Can the Maven JAR be updated with the most recent merges to master, with a version bump to 0.3.10 ?

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.