├── .gitignore ├── README.md ├── pom.xml ├── scripts ├── featureflags │ ├── curls.md │ └── flag.json └── hyperloglog │ └── curls.md ├── slides └── High Performance Web Applications.pdf └── src └── main └── java └── com └── justinrmiller └── redismicroservices ├── Main.java ├── config └── LocalRedisConfig.java ├── controllers ├── AuthTokenController.java ├── FeatureFlagsController.java └── HyperLogLogController.java ├── pojo ├── Feature.java └── TokenPermissions.java └── service ├── AuthTokenService.java ├── FeatureFlagsService.java └── HyperLogLogService.java /.gitignore: -------------------------------------------------------------------------------- 1 | # mac 2 | .DS_store 3 | 4 | # build 5 | target 6 | 7 | # idea 8 | .idea 9 | *.iml 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Redis Presentation and Source Code 2 | ================================== 3 | 4 | src/ - A Spring Boot application with three examples (Auth, Feature Flags, HyperLogLog/User Counts) 5 | slides/ - the slides 6 | 7 | Prereqs: 8 | - A running Redis instance (localhost) 9 | - Java 1.7 10 | - Maven 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.justinrmiller.redismicroservices 8 | redis-microservices 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | spring-milestones 14 | Spring Milestones 15 | http://repo.spring.io/milestone 16 | 17 | false 18 | 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 26 | 1.7 27 | 28 | [30.0-jre,) 29 | 30 | 1.5.0.M1 31 | 32 | 2.6.0 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-parent 38 | 1.2.1.RELEASE 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-web 45 | 46 | 47 | org.slf4j 48 | slf4j-log4j12 49 | 50 | 51 | log4j 52 | log4j 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-actuator 60 | 61 | 62 | 63 | com.google.guava 64 | guava 65 | ${guava.version} 66 | 67 | 68 | 69 | org.springframework.data 70 | spring-data-redis 71 | ${spring.data.redis.version} 72 | 73 | 74 | org.slf4j 75 | slf4j-log4j12 76 | 77 | 78 | log4j 79 | log4j 80 | 81 | 82 | 83 | 84 | 85 | redis.clients 86 | jedis 87 | ${jedis.version} 88 | jar 89 | compile 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.springframework.boot 97 | spring-boot-maven-plugin 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /scripts/featureflags/curls.md: -------------------------------------------------------------------------------- 1 | Add a feature flag (MEGA_FEATURE) for application "app" with flag definition found in flag.json (note: the name in the json must match the name in the path): 2 | 3 | curl -X POST -d@flag.json -H "Content-Type: application/json" http://localhost:8080/flags/application/app/feature/MEGA_FEATURE 4 | 5 | Retrieve all feature flags for application "app": 6 | 7 | curl http://localhost:8080/flags/application/app 8 | 9 | -------------------------------------------------------------------------------- /scripts/featureflags/flag.json: -------------------------------------------------------------------------------- 1 | {"name":"MEGA_FEATURE","createTimestamp":1421417126,"author":"Justin Miller","enabled":false} 2 | -------------------------------------------------------------------------------- /scripts/hyperloglog/curls.md: -------------------------------------------------------------------------------- 1 | Add a user visit for the main site (adding the user multiple times should result in the same count): 2 | 3 | curl -X POST http://localhost:8080/hyperloglog/key/user_main_site_visits/value/1 4 | 5 | Get a count for the number of visitors to the main site: 6 | 7 | curl http://localhost:8080/hyperloglog/key/user_main_site_visits 8 | -------------------------------------------------------------------------------- /slides/High Performance Web Applications.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinrmiller/spring-boot-redis-microservices/f5383490c3481bb0000e2d89e3e26b012064d542/slides/High Performance Web Applications.pdf -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/Main.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | 9 | import org.springframework.context.annotation.ComponentScan; 10 | 11 | @EnableAutoConfiguration 12 | @ComponentScan 13 | public class Main { 14 | private static Logger logger = LoggerFactory.getLogger(Main.class); 15 | 16 | public static void main(String[] args) throws Exception { 17 | SpringApplication.run(Main.class, args); 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/config/LocalRedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import org.springframework.data.redis.connection.RedisConnectionFactory; 8 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 9 | import org.springframework.data.redis.core.StringRedisTemplate; 10 | 11 | import redis.clients.jedis.JedisPoolConfig; 12 | 13 | /** 14 | * @author Justin Miller (Copyright 2015) 15 | */ 16 | @Configuration 17 | @ComponentScan(basePackages="com.justinrmiller.redismicroservices.service") 18 | public class LocalRedisConfig { 19 | @Bean 20 | public RedisConnectionFactory jedisConnectionFactory(){ 21 | JedisPoolConfig poolConfig=new JedisPoolConfig(); 22 | poolConfig.setMaxIdle(5); 23 | poolConfig.setMinIdle(1); 24 | poolConfig.setTestOnBorrow(true); 25 | poolConfig.setTestOnReturn(true); 26 | poolConfig.setTestWhileIdle(true); 27 | poolConfig.setNumTestsPerEvictionRun(10); 28 | poolConfig.setTimeBetweenEvictionRunsMillis(60000); 29 | 30 | JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(poolConfig); 31 | return jedisConnectionFactory; 32 | } 33 | 34 | @Bean 35 | public StringRedisTemplate redisTemplate() { 36 | StringRedisTemplate redisTemplate = new StringRedisTemplate(jedisConnectionFactory()); 37 | return redisTemplate; 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/controllers/AuthTokenController.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.controllers; 2 | 3 | import com.google.common.base.Optional; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.io.IOException; 14 | 15 | import com.justinrmiller.redismicroservices.pojo.TokenPermissions; 16 | import com.justinrmiller.redismicroservices.service.AuthTokenService; 17 | 18 | /** 19 | * @author Justin Miller (Copyright 2015) 20 | */ 21 | public class AuthTokenController { 22 | private static Logger logger = LoggerFactory.getLogger(AuthTokenController.class); 23 | 24 | @Autowired 25 | private AuthTokenService authTokenService; 26 | 27 | @RequestMapping( 28 | value = "/auth/token/{token}", 29 | method = RequestMethod.GET, 30 | produces = "application/json; charset=utf-8") 31 | @ResponseBody 32 | public ResponseEntity get(@PathVariable String token) throws IOException { 33 | logger.info("Getting a token"); 34 | 35 | Optional tokenPermissionsOptional = authTokenService.get(token); 36 | 37 | if (tokenPermissionsOptional.isPresent()) { 38 | return new ResponseEntity<>(tokenPermissionsOptional.get(), HttpStatus.OK); 39 | } else { 40 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 41 | } 42 | } 43 | 44 | @RequestMapping( 45 | value = "/auth/token/{token}", 46 | method = RequestMethod.POST, 47 | produces = "application/json") 48 | @ResponseBody 49 | public ResponseEntity post( 50 | @PathVariable String token, 51 | @RequestBody TokenPermissions tokenData) throws IOException { 52 | logger.info("Token {}", tokenData); 53 | 54 | authTokenService.add(token, tokenData); 55 | return new ResponseEntity(HttpStatus.OK); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/controllers/FeatureFlagsController.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.controllers; 2 | 3 | import com.google.common.base.Optional; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.io.IOException; 15 | import java.util.List; 16 | 17 | import com.justinrmiller.redismicroservices.pojo.Feature; 18 | import com.justinrmiller.redismicroservices.service.FeatureFlagsService; 19 | 20 | /** 21 | * @author Justin Miller (Copyright 2015) 22 | */ 23 | @RestController 24 | public class FeatureFlagsController { 25 | private static Logger logger = LoggerFactory.getLogger(FeatureFlagsController.class); 26 | 27 | @Autowired 28 | private FeatureFlagsService featureFlagsService; 29 | 30 | @RequestMapping( 31 | value = "/flags/application/{application}/feature/{feature}", 32 | method = RequestMethod.GET, 33 | produces = "application/json; charset=utf-8") 34 | @ResponseBody 35 | public ResponseEntity get( 36 | @PathVariable String application, 37 | @PathVariable String feature) throws IOException { 38 | logger.info("Getting a feature flag"); 39 | 40 | Optional featureOptional = featureFlagsService.get(application, feature); 41 | 42 | if (featureOptional.isPresent()) { 43 | return new ResponseEntity<>(featureOptional.get(), HttpStatus.OK); 44 | } else { 45 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 46 | } 47 | } 48 | 49 | @RequestMapping( 50 | value = "/flags/application/{application}", 51 | method = RequestMethod.GET, 52 | produces = "application/json; charset=utf-8") 53 | @ResponseBody 54 | public ResponseEntity get( 55 | @PathVariable String application) throws IOException { 56 | logger.info("Getting all feature flags"); 57 | 58 | Optional> featureFlagOptional = featureFlagsService.getAll(application); 59 | 60 | if (featureFlagOptional.isPresent()) { 61 | return new ResponseEntity<>(featureFlagOptional.get(), HttpStatus.OK); 62 | } else { 63 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 64 | } 65 | } 66 | 67 | @RequestMapping( 68 | value = "/flags/application/{application}/feature/{feature}", 69 | method = RequestMethod.POST, 70 | produces = "application/json") 71 | @ResponseBody 72 | public ResponseEntity post( 73 | @PathVariable String application, 74 | @PathVariable String feature, 75 | @RequestBody Feature featureData) throws IOException { 76 | logger.info("Feature {}", featureData); 77 | 78 | if (!feature.equals(featureData.getName())) { 79 | // error out 80 | return new ResponseEntity(HttpStatus.BAD_REQUEST); 81 | } 82 | 83 | featureFlagsService.add(application, featureData); 84 | return new ResponseEntity(HttpStatus.OK); 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/controllers/HyperLogLogController.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.controllers; 2 | 3 | import com.justinrmiller.redismicroservices.service.HyperLogLogService; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | /** 14 | * @author Justin Miller (Copyright 2015) 15 | */ 16 | @RestController 17 | public class HyperLogLogController { 18 | private static Logger logger = LoggerFactory.getLogger(HyperLogLogController.class); 19 | 20 | @Autowired 21 | private HyperLogLogService hyperLogLogService; 22 | 23 | @RequestMapping( 24 | value = "/hyperloglog/key/{key}", 25 | method = RequestMethod.GET, 26 | produces = "application/json; charset=utf-8") 27 | @ResponseBody 28 | public ResponseEntity get(@PathVariable String key) { 29 | logger.info("Retrieving HyperLogLog count: {}", key); 30 | 31 | Long count = hyperLogLogService.count(key); 32 | 33 | return new ResponseEntity<>(count, HttpStatus.OK); 34 | } 35 | 36 | @RequestMapping( 37 | value = "/hyperloglog/key/{key}/value/{value}", 38 | method = RequestMethod.POST, 39 | produces = "application/json") 40 | @ResponseBody 41 | public ResponseEntity post(@PathVariable String key, @PathVariable String value) { 42 | logger.info("Key: {}, Value: {}", key, value); 43 | 44 | hyperLogLogService.add(key, value); 45 | return new ResponseEntity(HttpStatus.OK); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/pojo/Feature.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.pojo; 2 | 3 | /** 4 | * @author Justin Miller (Copyright 2015) 5 | */ 6 | public class Feature { 7 | private String name; 8 | private Long createTimestamp; 9 | private String author; 10 | private Boolean enabled; 11 | 12 | public Feature() { 13 | // jackson 14 | } 15 | 16 | public Feature(String name, Long createTimestamp, String author, Boolean enabled) { 17 | this.name = name; 18 | this.createTimestamp = createTimestamp; 19 | this.author = author; 20 | this.enabled = enabled; 21 | } 22 | 23 | public Boolean getEnabled() { 24 | return enabled; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public Long getCreateTimestamp() { 32 | return createTimestamp; 33 | } 34 | 35 | public String getAuthor() { 36 | return author; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) return true; 42 | if (o == null || getClass() != o.getClass()) return false; 43 | 44 | Feature feature = (Feature) o; 45 | 46 | if (author != null ? !author.equals(feature.author) : feature.author != null) return false; 47 | if (createTimestamp != null ? !createTimestamp.equals(feature.createTimestamp) : feature.createTimestamp != null) 48 | return false; 49 | if (enabled != null ? !enabled.equals(feature.enabled) : feature.enabled != null) return false; 50 | if (name != null ? !name.equals(feature.name) : feature.name != null) return false; 51 | 52 | return true; 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | int result = name != null ? name.hashCode() : 0; 58 | result = 31 * result + (createTimestamp != null ? createTimestamp.hashCode() : 0); 59 | result = 31 * result + (author != null ? author.hashCode() : 0); 60 | result = 31 * result + (enabled != null ? enabled.hashCode() : 0); 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/pojo/TokenPermissions.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.pojo; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Justin Miller (Copyright 2015) 7 | */ 8 | public class TokenPermissions { 9 | private Long createTimestamp; 10 | private List permissions; 11 | 12 | public TokenPermissions() { 13 | // jackson 14 | } 15 | 16 | public TokenPermissions(Long createTimestamp, List permissions) { 17 | this.createTimestamp = createTimestamp; 18 | this.permissions = permissions; 19 | } 20 | 21 | public Long getCreateTimestamp() { 22 | return createTimestamp; 23 | } 24 | 25 | public List getPermissions() { 26 | return permissions; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | 34 | TokenPermissions that = (TokenPermissions) o; 35 | 36 | if (createTimestamp != null ? !createTimestamp.equals(that.createTimestamp) : that.createTimestamp != null) 37 | return false; 38 | if (permissions != null ? !permissions.equals(that.permissions) : that.permissions != null) return false; 39 | 40 | return true; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | int result = createTimestamp != null ? createTimestamp.hashCode() : 0; 46 | result = 31 * result + (permissions != null ? permissions.hashCode() : 0); 47 | return result; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/service/AuthTokenService.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.service; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | 5 | import com.google.common.base.Optional; 6 | 7 | import com.justinrmiller.redismicroservices.pojo.TokenPermissions; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.data.redis.core.HashOperations; 11 | import org.springframework.data.redis.core.StringRedisTemplate; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.io.IOException; 15 | 16 | /** 17 | * @author Justin Miller (Copyright 2015) 18 | */ 19 | @Component 20 | public class AuthTokenService { 21 | private final static ObjectMapper MAPPER = new ObjectMapper(); 22 | 23 | @Autowired 24 | private StringRedisTemplate redisTemplate; 25 | 26 | public Optional get(String token) throws IOException { 27 | HashOperations hashOps = redisTemplate.opsForHash(); 28 | 29 | String permissionsAsJson = hashOps.get(token, "permissions"); 30 | 31 | if (permissionsAsJson != null) { 32 | return Optional.of(MAPPER.readValue(permissionsAsJson, TokenPermissions.class)); 33 | } else { 34 | return Optional.absent(); 35 | } 36 | } 37 | 38 | public void add(String token, TokenPermissions permissions) throws IOException { 39 | HashOperations hashOps = redisTemplate.opsForHash(); 40 | 41 | String permissionsAsJson = MAPPER.writeValueAsString(permissions); 42 | 43 | hashOps.put(token, "permissions", permissionsAsJson); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/service/FeatureFlagsService.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.service; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | 5 | import com.google.common.base.Optional; 6 | import com.google.common.collect.ImmutableList; 7 | 8 | import com.justinrmiller.redismicroservices.pojo.Feature; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.data.redis.core.HashOperations; 12 | import org.springframework.data.redis.core.StringRedisTemplate; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.io.IOException; 16 | import java.util.List; 17 | 18 | /** 19 | * @author Justin Miller (Copyright 2015) 20 | */ 21 | @Component 22 | public class FeatureFlagsService { 23 | private final static ObjectMapper MAPPER = new ObjectMapper(); 24 | 25 | @Autowired 26 | private StringRedisTemplate redisTemplate; 27 | 28 | public Optional get(String application, String feature) throws IOException { 29 | HashOperations hashOps = redisTemplate.opsForHash(); 30 | 31 | String featureJson = hashOps.get(application, feature); 32 | 33 | if (featureJson != null) { 34 | return Optional.of(MAPPER.readValue(featureJson, Feature.class)); 35 | } else { 36 | return Optional.absent(); 37 | } 38 | } 39 | 40 | public Optional> getAll(String application) throws IOException { 41 | HashOperations hashOps = redisTemplate.opsForHash(); 42 | 43 | List featureJson = hashOps.values(application); 44 | 45 | if (featureJson != null) { 46 | ImmutableList.Builder builder = new ImmutableList.Builder<>(); 47 | 48 | for (String s : featureJson) { 49 | builder.add(MAPPER.readValue(s, Feature.class)); 50 | } 51 | return Optional.of(builder.build()); 52 | } else { 53 | return Optional.absent(); 54 | } 55 | } 56 | 57 | public void add(String application, Feature feature) throws IOException { 58 | HashOperations hashOps = redisTemplate.opsForHash(); 59 | 60 | String featureAsJson = MAPPER.writeValueAsString(feature); 61 | 62 | hashOps.put(application, feature.getName(), featureAsJson); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/justinrmiller/redismicroservices/service/HyperLogLogService.java: -------------------------------------------------------------------------------- 1 | package com.justinrmiller.redismicroservices.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.StringRedisTemplate; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * @author Justin Miller (Copyright 2015) 9 | * 10 | * This relies on a milestone release of Spring Data 11 | * see: 12 | * https://github.com/spring-projects/spring-data-redis/pull/116 13 | */ 14 | @Component 15 | public class HyperLogLogService { 16 | @Autowired private StringRedisTemplate redisTemplate; 17 | 18 | public Long count(String ... key) { 19 | return redisTemplate.opsForHyperLogLog().size(key); 20 | } 21 | 22 | public void add(String key, String ... values) { 23 | redisTemplate.opsForHyperLogLog().add(key, values); 24 | } 25 | } 26 | --------------------------------------------------------------------------------