spring-redis.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:redis/redis.properties" />
<!--注意新版本2.3以后,JedisPoolConfig的property name,不是maxActive
而是maxTotal,而且没有maxWait属性 -->
<!--redis连接池配置-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大空闲数-->
<property name="maxIdle" value="${redis.maxIdle}"/>
<!--连接池的最大数据库连接数-->
<property name="maxTotal" value="${redis.maxTotal}"/>
<!--建立连接的等待时间-->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<!--去除连接的最小空闲时间,默认1800000毫秒(30分钟)-->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
<!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 -->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
<!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 -->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
<property name="testOnBorrow" value="true"></property>
<property name="testOnReturn" value="true"></property>
<property name="testWhileIdle" value="true"></property>
</bean>
<!--redis连接工厂-->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="poolConfig" ref="jedisPoolConfig"></property>
<!--IP地址-->
<property name="hostName" value="${redis.host.ip}"></property>
<!--端口号-->
<property name="port" value="${redis.port}"/>
<!--如果Redis设置有密码 -->
<property name="password" value="${redis.password}"/>
<!--客户端超时时间单位是毫秒 -->
<property name="timeout" value="${redis.timeout}"></property>
<property name="usePool" value="true"/>
</bean>
<!-- 键值序列化器设置为String 类型 -->
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="jackson2JsonRedisSerializer"
class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:hashValueSerializer-ref="jackson2JsonRedisSerializer">
</bean>
</beans>
pom.xml依赖文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.smart</groupId>
<artifactId>RedisDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>RedisDemo</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<spring.version>4.3.1.RELEASE</spring.version>
<redis.version>2.9.0</redis.version>
<spring.data.redis.version>2.0.10.RELEASE</spring.data.redis.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring.data.redis.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.10</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
</project>
View Code
redisDemo
package com.smart;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
public class RedisStringDemo {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:redis/spring-redis.xml");
// redisTemplate.opsForValue()所返回的对象可以操作简单的键值对,可以是字符串,也可以是对象,具体依据所配置的序列化方案
// 在spring-redis-string.xml中key和value是指定的 stringRedisSerializer
RedisTemplate<String,String> redisTemplate= (RedisTemplate<String, String>) context.getBean("redisTemplate");
//> set key1 value1
redisTemplate.opsForValue().set("key1","value1");
redisTemplate.opsForValue().set("key2","value2");
String value1 = redisTemplate.opsForValue().get("key1");
System.out.println(value1);
Boolean success = redisTemplate.delete("key1");
System.out.println("删除key1是否成功"+success);
Long size = redisTemplate.opsForValue().size("key2");
System.out.println("key2的长度"+size);
//设置新值并返回旧值
String oldValue = redisTemplate.opsForValue().getAndSet("key2", "new_value2");
System.out.println("key2的旧值"+oldValue);
String newValue = redisTemplate.opsForValue().get("key2");
System.out.println("key2的新值"+newValue);
//获取子字符串
String subStr = redisTemplate.opsForValue().get("key2", 0, 3);
System.out.println("subString :"+subStr);
//将新的字符串value加入到原来key指向的字符串末尾
Integer value = redisTemplate.opsForValue().append("key2", "_app");
System.out.println("valie:"+value);
String newValue2=redisTemplate.opsForValue().get("key2");
System.out.println("key2: "+newValue2);
Object result = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
//Delete all keys of the currently selected database.
connection.flushDb();
return "flush db";
}
});
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set("name","Jim");
valueOperations.setIfAbsent("name","Jack");
Map<String, String> map = new HashMap<>();
map.put("alias","Jim");
map.put("age","18");
valueOperations.multiSet(map);
valueOperations.append("alias","_data");
valueOperations.increment("age",2);
}
}
实际工作中并不是那么用的,因为每一 个操作会尝试从连接池里获取 一 个新的 Redis 连接,多个命令应该使用SessionCallback 接口进行操作 。使用SessionCallBack这个接口,通过这个接口就可以把属于多个同一套命令放在同一个Redis连接中去执行
常见场景
缓存
Redis作为缓存层, 绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性, 所以缓存通常能起到加速读写和降低后端压力的作用。
springboot+ redis实现token机制
user域
package com.smart.domain;
import lombok.Data;
@Data
public class User {
private Integer id;
private String username;
private String password;
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User() {
}
}
View Code
Dto域
package com.smart.domain;
public class Dto {
private String token;
private Long tokenCreatedDate;
private Long tokenExpiryDate;
private String isLogin;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Long getTokenCreatedDate() {
return tokenCreatedDate;
}
public void setTokenCreatedDate(Long tokenCreatedDate) {
this.tokenCreatedDate = tokenCreatedDate;
}
public Long getTokenExpiryDate() {
return tokenExpiryDate;
}
public void setTokenExpiryDate(Long tokenExpiryDate) {
this.tokenExpiryDate = tokenExpiryDate;
}
public String getIsLogin() {
return isLogin;
}
public void setIsLogin(String isLogin) {
this.isLogin = isLogin;
}
}
View Code
RedisConfig
package com.smart.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,String> redisTemplate=new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
View Code
RedisUtil
package com.smart.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String,String> redisTemplate;
public void set(String key,String value){
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key,value);
}
public void setex(String key,String value,int seconds){
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key,value,seconds);
}
}
View Code
application.yml
##指定使用redis数据库索引(默认为0)
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
View Code
UserService
package com.smart.service;
import com.smart.domain.User;
public interface UserService {
User login(String username,String password);
}
View Code
package com.smart.service.impl;
import com.smart.domain.User;
import com.smart.service.UserService;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public User login(String username, String password) {
return new User(1,username,password);
}
}
View Code
TokenService
package com.smart.service;
import com.smart.domain.User;
public interface TokenService {
String generateToken(String userAgentStr,String username);
void save(String token,User user);
}
View Code
package com.smart.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.smart.domain.User;
import com.smart.service.TokenService;
import com.smart.util.RedisUtil;
import nl.bitwalker.useragentutils.UserAgent;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
@Service("tokenService")
public class TokenServiceImpl implements TokenService {
@Autowired
private RedisUtil redisUtil;
@Override
public String generateToken(String userAgentStr, String username) {
//生成token(格式为token:设备-加密的用户名-时间-六位随机数)
StringBuilder token = new StringBuilder("token:");
UserAgent userAgent = UserAgent.parseUserAgentString(userAgentStr);
if(userAgent.getOperatingSystem().isMobileDevice()){
token.append("MOBILE-");
}else{
token.append("PC-");
}
token.append(DigestUtils.md5Hex(username)+"-");
token.append(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())+"-");
token.append(String.format("%06d",new Random().nextInt(100000)+1));
return token.toString();
}
@Override
public void save(String token, User user) {
if(token.startsWith("token:PC")){
redisUtil.setex(token, JSONObject.toJSONString(user),2*60*60);
}else{
redisUtil.set(token,JSONObject.toJSONString(user));
}
}
}
View Code
UserController
package com.smart.controller;
import com.alibaba.fastjson.JSONObject;
import com.smart.domain.Dto;
import com.smart.domain.User;
import com.smart.service.TokenService;
import com.smart.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private TokenService tokenService;
@GetMapping("/login")
public String login(@RequestParam("username") String username,@RequestParam("password") String password, HttpServletRequest request){
Dto dto = new Dto();
User user = userService.login(username, password);
if(user !=null){
String userAgent = request.getHeader("user-agent");
String token = tokenService.generateToken(userAgent, username);
tokenService.save(token,user);
dto.setIsLogin("true");
dto.setToken(token);
dto.setTokenCreatedDate(System.currentTimeMillis());
dto.setTokenExpiryDate(System.currentTimeMillis()+2*60*60);
}else{
dto.setIsLogin("false");
}
return JSONObject.toJSONString(dto);
}
}
View Code
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.smart</groupId>
<artifactId>redistoken</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redistoken</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>nl.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.2.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
View Code
计数器
使用Redis作为计数的基础工具, 可以实现快速计数、查询缓存的功能, 同时数据可以异步落地到其他数据源。
限流
举个例子,对某个接口在1分钟内限制调用10次
session共享
使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的, 每次用户更新或者查询登录信息都直接从Redis中集中获取
Lettuce
Lettuce
和 Jedis
的都是连接Redis Server
的客户端程序。Jedis
在实现上是直连redis server
,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接。Lettuce
基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
在 pom.xml
中spring-boot-starter-data-redis
的依赖,Spring Boot2.x
后底层不在是Jedis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
由于Spring Boot2.x
的改动,连接池相关配置需要通过spring.redis.lettuce.pool
或者 spring.redis.jedis.pool
进行配置
spring:
#指定配置环境
profiles:
active: test
devtools:
restart:
enabled: true
#添加需要restart目录文件
additional-paths: src/main/java
#排除不需要restart的目录文件
exclude: static/**
redis:
host: localhost
password:
database: 0
timeout: 10000ms
# Redis默认有16个分片,配置具体使用的分片,默认值为0
database: 0
# 连接池最大的连接数(使用负值表示没有限制)默认 8
lettuce:
pool:
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)默认 -1
max-wait: -1ms
# 连接池中最大空闲连接 默认 8
max-idle: 8
# 连接池中最小空闲连接 默认 0
min-idle: 0
自定义Template
默认情况下的模板只能支持RedisTemplate<String, String>
,也就是只能存入字符串,开发中是不友好的,所以自定义模板是很有必要的,当自定义模板又想使用String
存储这时候就可以使用StringRedisTemplate
的方式,它们并不冲突
package com.smart.boot_mybatis.configuration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration {
@Bean
public RedisTemplate<String,Serializable> redisTemplate(LettuceConnectionFactory redisConnectionFactory){
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
测试:
package com.smart.boot_mybatis;
import com.smart.boot_mybatis.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.Serializable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
@RunWith(SpringRunner.class)
@SpringBootTest
public class redisTest {
private static final Logger log = LoggerFactory.getLogger(redisTest.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, Serializable> redisCacheTemplate;
@Test
public void get(){
ExecutorService executorService = Executors.newFixedThreadPool(100);
IntStream.range(0,1000).forEach(i->executorService.execute(()->
stringRedisTemplate.opsForValue().increment("aa",1)));
stringRedisTemplate.opsForValue().set("k1","v1");
String v1=stringRedisTemplate.opsForValue().get("k1");
log.info("[字符缓存结果] - [{}]", v1);
String key="user:1";
redisCacheTemplate.opsForValue().set(key,new User(1L,"u1","jing"));
User user = (User) redisCacheTemplate.opsForValue().get(key);
log.info("[对象缓存结果] - [{}]", user);
}
}