Skip to content

Instantly share code, notes, and snippets.

@danmrichards
Last active January 31, 2018 23:07
Show Gist options
  • Save danmrichards/e5da8326bd54194e98812526dc35de93 to your computer and use it in GitHub Desktop.
Save danmrichards/e5da8326bd54194e98812526dc35de93 to your computer and use it in GitHub Desktop.
Incrementor - Go service for incrementing and persisting scoped integers
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"sync"
"github.com/danmrichards/incrementor/models"
)
const (
serverHost = "localhost"
serverPort = "8000"
requests = 20 // Number of requests to make per batch.
batches = 50 // Number of request batches to make.
)
var (
client *http.Client
tester = sync.Map{}
wg sync.WaitGroup
)
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
client = &http.Client{}
// Run batches of requests. Upper limit for server is ~1000
// concurrent requests, adjust requests & batches accordingly.
wg.Add(batches)
for i := 0; i < batches; i++ {
go func() {
defer wg.Done()
for j := 0; j < requests; j++ {
req, err := http.NewRequest(
http.MethodPost,
fmt.Sprintf("http://%s/increment", net.JoinHostPort(serverHost, serverPort)),
nil,
)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var increment models.Increment
err = json.Unmarshal(body, &increment)
if err != nil {
log.Fatal(err)
}
if _, ok := tester.Load(increment.String()); ok {
log.Panicf("duplicate entry: %s", increment.String())
}
tester.Store(increment.String(), struct{}{})
fmt.Println(increment.String())
}
}()
}
wg.Wait()
fmt.Printf("got %d items with no duplicates", batches*requests)
}
package models
import (
"fmt"
"sync"
)
// Increment represents a scoped incremental value.
type Increment struct {
Scope string `json:"scope"`
Value int64 `json:"value"`
sync.Mutex `json:"-"`
}
// String returns a string representation of the incremental value.
func (i *Increment) String() string {
return fmt.Sprintf("%s-%d", i.Scope, i.Value)
}
// NewIncrement returns a new incremental value.
func NewIncrement(scope string) Increment {
return Increment{
Scope: scope,
}
}
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/boltdb/bolt"
"github.com/danmrichards/incrementor/models"
)
const (
dbFile = "increments"
bucket = "ordernumbers"
fileMode = 0600
defaultScope = "UK" // Harcoded for now, could easily be passed by http request.
)
var (
db *bolt.DB
increment = models.NewIncrement(defaultScope)
)
func IncrementHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte(http.StatusText(http.StatusMethodNotAllowed)))
return
}
var increment models.Increment
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucket))
// Get current increment for the scope, or create a new one.
incrementData := b.Get([]byte(defaultScope))
if incrementData != nil {
increment.Lock()
err := json.Unmarshal(incrementData, &increment)
if err != nil {
return err
}
} else {
increment = models.NewIncrement(defaultScope)
increment.Lock()
}
// Do the increment and persist back to the db.
increment.Value++
incrementVal, err := json.Marshal(increment)
if err != nil {
return err
}
err = b.Put([]byte(defaultScope), incrementVal)
if err != nil {
return err
}
return nil
})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("could not perform increment: %s", err)))
return
}
incrementResp, err := json.Marshal(increment)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("could not marshal json: %s", err)))
return
}
increment.Unlock()
w.Header().Set("Content-Type", "application/json")
w.Write(incrementResp)
}
func main() {
var err error
db, err = bolt.Open(dbFile, fileMode, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte(bucket))
if err != nil {
return fmt.Errorf("create bucket: %s", err)
}
return nil
})
http.HandleFunc("/increment", IncrementHandler)
http.ListenAndServe(":8000", nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment