- 简介
- 背景
- Redis概述
- 版本及参考说明
- Redis初始操作
- 启动Redis服务
- 进入redis容器
- 登录redis服务
- 查看Redis信息
- 清空redis屏幕
- 退出redis服务
- 关闭redis服务
- 系统帮助
- Redis数据存储操作
- 简易数据存取
- Key有效时间设计
- Redis常用数据类型
- Java中操作redis
- 购物车实现
- 分布式ID实现
- RedisPool连接池
- 基于redis的单点登录设计及实现
- Redis实现投票系统(简易)
- 重点: 定制序列化与反序列化方式
- 指定序列化方式前
- 指定序列化方式后
- 自定义序列化方式1
- 自定义序列化方式2
- 通过AOP实现自定义序列化
- Redis事务处理
- 基本指令
- Redis架构设计应用实践
我们现在的项目架构中,基本上是Web服务器(Tomcat)和数据库独立部署,独占服务器资源,随着用户数的增长,并发读写数据库,会加大数据库访问压力,导致性能的下降,严重时直接导致系统宕机,例如:
此时,我们可以在Tomcat同服务器上中增加本地缓存,并在外部增加分布式缓存,缓存热门数据。也就是通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。例如:
基于这样的一种架构设计,于是类似redis的一些分布式数据库就诞生了。
Redis是一个key-value存储系统(官网:http://redis.io),是一个分布式缓存数据库。在DB-Engines.com的数据库排行中, Redis上升排行第七,如图所示:
Redis的次版本号(第一个小数点后的数字)为偶数的版本是稳定版本(2.4、2.6等),奇数为非稳定版本(2.5、2.7),一般推荐在生产环境使用稳定版本。最新版本6.2.2,新增了stream的处理方式,性能更高。Redis官方是不支持windows平台的,windows版本是由微软自己建立的分支,基于官方的Redis源码上进行编译、发布、维护的,所以windows平台的Redis版本要略低于官方版本。
Redis 相关参考网址如下所示:
Bootnb 相关:https://www.runoob.com/redis/redis-tutorial.html Redis 官网:https://redis.io/ 源码地址:https://github.com/redis/redis Redis 在线测试:http://try.redis.io/ Redis 命令参考:http://doc.redisfans.com/Redis初始操作 启动Redis服务
Docker环境下的启动(docker环境启动需要运行多个容器):
docker start redis01 #底层也是通过redis-server启动,start单词后的redis01为容器名
docker中查看redis服务
docker ps
查看启动的redis进程信息
ps -ef|grep redis root 3511 1 0 16:29 ? 00:00:01 redis-server *:6379 root 3515 1 0 16:29 ? 00:00:01 redis-server 127.0.0.1:6380进入redis容器
docker exec -it redis01 bash #redis01为容器名登录redis服务
登录本地redis
redis-cli 或者 redis-cli -p 6379 或者 redis-cli -p 6379 -a password #-a后面为password,此操作需要开启redis.conf文件中的 requirepass选项
登录远程redis
redis-cli -h ip -p 6379 -a passworld查看Redis信息
首先登录redis,然后输入info指令
127.0.0.1:6379> info #查看当前redis节点的详细配置信息清空redis屏幕
清除redis屏幕内容
127.0.0.1:6379> clear退出redis服务
127.0.0.1:6379> exit关闭redis服务
127.0.0.1:6379> shutdown
系统帮助shotdown 受保护模式的关闭(关闭前会将数据持久化)
可以基于help指令查看相关指令帮助,例如:
127.0.0.1:6379> help redis-cli 2.8.19 Type: "help @" to get a list of commands in "help " for help on "help " to get a list of possible help topics "quit" to exit
127.0.0.1:6379> help type TYPE key summary: Determine the type stored at key since: 1.0.0 group: genericRedis数据存储操作 简易数据存取
基于查看redis中的key
127.0.0.1:6379> keys * (empty list or set)
基于key/value形式存储数据
127.0.0.1:6379> set test1 123 OK 127.0.0.1:6379> set test2 ab OK 127.0.0.1:6379> keys * 1) "test1" 2) "test2"
基于key获取redis中存储的数据
127.0.0.1:6379> get test1 "123" 127.0.0.1:6379> get test2 "ab" 127.0.0.1:6379> get test3 (nil) 127.0.0.1:6379>
清除redis中的数据
清除当前数据库的数据
127.0.0.1:6379> flushdb OK
清除所有数据库的数据(redis一共有15个数据库)
127.0.0.1:6379> flushall OKKey有效时间设计
实际工作中我们经常要控制redis中key的有效时长,例如秒杀操作的计时,缓存数据的有效时长等。
Expire (设置生效时长-单位秒)
语法:EXPIRE key seconds
127.0.0.1:6379> set bomb tnt OK 127.0.0.1:6379> expire bomb 10 (integer) 1 127.0.0.1:6379> ttl bomb (integer) 5 127.0.0.1:6379> ttl bomb (integer) 3 127.0.0.1:6379> ttl bomb (integer) 3 127.0.0.1:6379> ttl bomb (integer) 2 127.0.0.1:6379> ttl bomb (integer) 1 127.0.0.1:6379> ttl bomb (integer) -2 127.0.0.1:6379> ttl bomb (integer) -2 127.0.0.1:6379>
其中,TTL查看key的剩余时间,当返回值为-2时,表示键被删除。
当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。
Persist (取消时长设置)
通过persist让对特定key设置的生效时长失效。
语法:PERSIST key
127.0.0.1:6379> set bomb tnt OK 127.0.0.1:6379> expire bomb 60 (integer) 1 127.0.0.1:6379> ttl bomb (integer) 49 127.0.0.1:6379> persist bomb (integer) 1 127.0.0.1:6379> ttl bomb (integer) -1 127.0.0.1:6379>
其中,设置新的数据时需要重新设置该key的生存时间,重新设置值也会清除生存时间。
pexpire (单位毫秒)
pexpire 让key的生效时长以毫秒作为计量单位,这样可以做到更精确的时间控制。例如,可应用于秒杀场景。
语法:PEXPIRE key milliseconds
127.0.0.1:6379> set bomb tnt OK 127.0.0.1:6379> pexpire bomb 10000 (integer) 1 127.0.0.1:6379> ttl bomb (integer) 6 127.0.0.1:6379> ttl bomb (integer) 3 127.0.0.1:6379> ttl bomb (integer) -2 127.0.0.1:6379>Redis常用数据类型
父工程中添加依赖
org.apache.maven.plugins maven-compiler-plugin 3.8.1 8 8
子工程中添加依赖
redis.clients jedis 3.5.2 junit junit 4.12 test com.google.code.gson gson 2.8.6
Redis常用数据类型
Java中操作redisJava中操作redis
购物车实现package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.Map;
public class CartDemo01 {
private static final String IP = "192.168.81.128";
private static final int PORT = 6379;
private static final String PREFIX = "cart:";
static void addCart(String userId,Long productId,int num){
//1.建立redis连接
Jedis jedis = new Jedis(IP, PORT);
//2.添加商品
jedis.hincrBy(PREFIX+userId, String.valueOf(productId), num);
//3.释放资源
jedis.close();
}
static Map listCart(String userId){
//1.建立redis连接
Jedis jedis = new Jedis(IP, PORT);
//2.添加商品
Map stringStringMap = jedis.hgetAll(PREFIX+userId);
//3.释放资源
jedis.close();
return stringStringMap;
}
static void updateCart(String userId,Long productId,int num){
//1.建立redis连接
Jedis jedis = new Jedis(IP, PORT);
//2.添加商品
jedis.hincrBy(PREFIX+userId, String.valueOf(productId), num);
//3.释放资源
jedis.close();
}
static void deleteCart(String userId,String... productId){
//1.建立redis连接
Jedis jedis = new Jedis(IP, PORT);
//2.删除商品
//批量删除
// List longs = Arrays.asList(productId);
// jedis.hdel(PREFIX+userId, longs.toArray(new String[]{}));
jedis.hdel(PREFIX+userId, productId);
//3.释放资源
jedis.close();
}
public static void main(String[] args) {
//1.向购物车添加商品
updateCart("101", 2001L, 1);
updateCart("101", 2002L, 3);
updateCart("101", 2003L, 5);
//2.查看购物车商品
Map stringStringMap = listCart("101");
System.out.println(stringStringMap);
//3.修改购物车商品数量
updateCart("101", 2003L, -10);
//4.删除购物车商品
deleteCart("101", "2001");
//5.清空购物车
deleteCart("101","2001","2002","2003");
Map stringStringMap1 = listCart( "101");
System.out.println(stringStringMap1);
}
}
分布式ID实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class IdGeneratorDemo01 {
public static Long getId(){
//Jedis jedis = new Jedis("192.168.81.128");
Jedis jedis = JedisDataSource.getConnection();
//假如redis设置了访问密码123456
//jedis.auth("123456");
Long id = jedis.incr("id");
jedis.close();
return id;
}
public static void main(String[] args) {
//构建一个最多只有3个线程的线程池(启动一个线程需要1兆内存)
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i <= 10; i++) {
//从线程池中获取线程
//这个任务会存储到阻塞式任务队列中
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "->" + getId());
}
});
}
}
}
RedisPool连接池
package com.jt.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisDataSource {
public static final String IP = "192.168.81.128";
public static final int PORT = 6379;
private static volatile JedisPool jedisPool;
//方案一1: 饿汉式对象的创建
// static {
// //1.1 构建连接池配置(可选,不写有默认的)
// JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// //最大连接数
// jedisPoolConfig.setMaxTotal(16);
// //最大空闲数
// jedisPoolConfig.setMaxIdle(8);
// jedisPool = new JedisPool(IP,PORT);
//
// }
//
// public static Jedis getConnection(){
// return jedisPool.getResource();
// }
public static Jedis getConnection(){
if (jedisPool == null) {
synchronized (JedisDataSource.class) {
if (jedisPool == null) {
//1.1 构建连接池配置(可选,不写有默认的)
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大连接数
jedisPoolConfig.setMaxTotal(16);
//最大空闲数
jedisPoolConfig.setMaxIdle(8);
jedisPool = new JedisPool(IP, PORT);
//创建对象分析
//1. 开辟内存
//2. 执行属性的默认初始化
//3. 执行构造方法
//4. 将创建的对象的内存地址赋值给jedisPool变量
//假如使用了volatile修饰jedisPool变量,可以保证如上几个步骤是顺序执行的
}
}
}
return jedisPool.getResource();
}
public static void close(){
jedisPool.close();
}
}
基于redis的单点登录设计及实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class SsoDemo01 {
static String token;
static String doLogin(String username, String password){
//1.检验数据的合法性(判断用户名,密码是否为空,密码的长度,是否有数字字母特殊符号)
if (username==null || "".equals(username)){
throw new IllegalArgumentException("用户名不能为空");
}
//2.基于基于用户名查询用户信息,并判定密码是否正确
if (!"jack".equals(username)){
throw new RuntimeException("此用户不存在");
}
if (!"123456".equals(password)){
throw new RuntimeException("密码不正确");
}
//3.用户存在且面膜正确,将用户信息写入到redis
Jedis jedis = new Jedis(JedisDataSource.IP, JedisDataSource.PORT);
token = UUID.randomUUID().toString();
jedis.hset(token,"username",username);
jedis.hset(token,"permission","sys:resource:create");
jedis.expire(token,10);
jedis.close();
//4.将token返回给客户端(将来使用response对象响应到客户端)
return token;
}
static Object doGetResource(String token){
//1. 校验token是否为空
if (token == null){
throw new IllegalArgumentException("请先登录");
}
//2. 基于token查询redis数据,假如有对应数据说明用户登陆了
Jedis jedis = new Jedis(JedisDataSource.IP, JedisDataSource.PORT);
String username = jedis.hget(token, "username");
if (username==null){
throw new IllegalArgumentException("登录超时,请重新登录");
}
String permission = jedis.hget(token, "permission");
if (!"sys:resource:create".equals(permission)){
throw new RuntimeException("你没有权限访问这个资源");
}
//3. 检查用户是否有访问资源的权限,假如有则允许访问
//4. 返回要访问的资源
return "your resource";
}
public static void main(String[] args) {
//1.登陆操作(用户身份认证)
token = doLogin("jack", "123456");
//2.携带token访问资源服务器
System.out.println(token);
Object o = doGetResource(token);
System.out.println(o);
}
}
Redis实现投票系统(简易)
package com.jt.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.Set;
public class VoteDemo01 {
public static volatile JedisPool jedisPool;
public static Jedis getJedis(){
if (jedisPool == null) {
synchronized (VoteDemo01.class) {
if (jedisPool == null) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(16);
jedisPoolConfig.setMaxIdle(8);
jedisPool = new JedisPool(jedisPoolConfig, JedisDataSource.IP, JedisDataSource.PORT);
}
}
}
return jedisPool.getResource();
}
static void doVote(String activityId,String userId){
//1.建立连接
Jedis jedis = getJedis();
//2.执行投票
Boolean flag = jedis.sismember(activityId, userId);
if (flag){
//假如已经投过票,再投票就取消投票
jedis.srem(activityId, userId);
}else {
//没有投过票则执行投票
jedis.sadd(activityId,userId);
}
//3.释放资源
jedis.close();
}
static Long doCount(String activityId){
//1.建立连接
Jedis jedis = getJedis();
//2.获取当前活动的总票数
Long scard = jedis.scard(activityId);
return scard;
}
static Set doGetMembers(String activityId){
//1.建立连接
Jedis jedis = getJedis();
Set smembers = jedis.smembers(activityId);
return smembers;
}
public static void main(String[] args) {
String activityId = "101";
String userId1 = "1";
String userId2 = "2";
String userId3 = "3";
//执行投票动作
doVote(activityId,userId1);
doVote(activityId,userId2);
doVote(activityId,userId3);
//获取总票数
Long aLong = doCount(activityId);
System.out.println(aLong);
//获取哪些人参与了投票
Set strings = doGetMembers(activityId);
System.out.println(strings);
}
}
重点: 定制序列化与反序列化方式
指定序列化方式前
指定序列化方式后
自定义序列化方式1
package com.jt;
import com.jt.pojo.Blog;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
import java.util.Map;
@SpringBootTest
public class RedisTemplateTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString01(){
//修改1序列化方式
redisTemplate.setKeySerializer(RedisSerializer.string());
//1.获取字符串操作对象
ValueOperations vo = redisTemplate.opsForValue();
//2.读写数据
vo.set("name", "redis");
// Duration.ofSeconds(10) 表示有效时间
vo.set("author", "tony", Duration.ofSeconds(10));
String name = vo.get("name");
System.out.println(name);
String author = vo.get("author");
System.out.println(author);
}
@Test
void testHash(){
HashOperations vo = redisTemplate.opsForHash();
vo.put("blog", "name", "tony");
vo.put("blog", "id", "100");
Map
自定义序列化方式2
package com.jt;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
public RedisSerializer jsonSerializer(){
//1.定义Redis序列化,反序列化规范对象(此对象底层通过ObjectMapper完成对象序列化和反序列化)
// new Jackson2JsonRedisSerializer(Blog.class); 指定序列化的对象
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
//2.创建ObjectMapper(由jackson api提供)对象,基于此对象进行序列化和反序列化
//2.1 创建ObjectMapper对象
ObjectMapper objectMapper = new ObjectMapper();
//2.2 设置按哪些方法规则进行序列化 PropertyAccessor.GETTER get方法
objectMapper.setVisibility(PropertyAccessor.GETTER,
//JsonAutoDetect.Visibility.ANY any表示任意访问修饰符
JsonAutoDetect.Visibility.ANY);
//2.3 激活序列化类型存储,对象序列化时还回将对象的类型存储到redis数据库
//假如没有这个配置,redis存储数据时不存储类型,反序列化时会默认将其数据存储到map
objectMapper.activateDefaultTyping(
//多态校验分析
objectMapper.getPolymorphicTypevalidator(),
//激活序列化形式存储
ObjectMapper.DefaultTyping.NON_FINAL,
//PROPERTY 表示类型会以json对象属性形式存储
JsonTypeInfo.As.PROPERTY);
//对象属性值为null时,不进行序列化存储
//objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
serializer.setObjectMapper(objectMapper);
return serializer;
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
//设置value的序列化方式
template.setValueSerializer(jsonSerializer());
template.setHashValueSerializer(jsonSerializer());
//更新一下RedisTemplate对象对的默认配置
template.afterPropertiesSet();
return template;
}
//
// @Bean
// @ConditionalOnMissingBean(
// name = {"redisTemplate"}
// )
// public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// RedisTemplate template = new RedisTemplate();
// template.setConnectionFactory(redisConnectionFactory);
// //设置key的序列化方式
// template.setKeySerializer(RedisSerializer.string());
// template.setHashKeySerializer(RedisSerializer.string());
// //设置value的序列化方式
// template.setValueSerializer(RedisSerializer.json());
// template.setHashValueSerializer(RedisSerializer.json());
// return template;
// }
}
通过AOP实现自定义序列化
- 启动类上添加此注解
- 在Service层添加注解
package com.jt.service;
import com.jt.dao.MenuMapper;
import com.jt.pojo.Menu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class DefaultMenuService implements MenuService {
@Autowired
private MenuMapper menuMapper;
@Cacheable(value = "menuCache",key = "#id")
@Override
public Menu selectById(Long id) {
return menuMapper.selectById(id);
}
@Override
@CachePut(value = "menuCache", key = "#menu.id")
public Menu insertMenu(Menu menu) {
menuMapper.insert(menu);
return menu;
}
@CachePut(value = "menuCache",key = "#menu.id")
@Override
public Menu updateMenu(Menu menu) {
menuMapper.updateById(menu);
return menu;
}
}
- 添加配置类
package com.jt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class CacheManagerConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public CacheManager cacheManager() {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
//定义key的序列化方式
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
//定义value的序列化方式
.serializevaluesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
//建造者模式(复杂对象的创建,建议使用这种方式,封装了对象的创建细节)
.build();
}
}
Redis事务处理
基本指令
multi 开启事务
exec 提交事务
discard 取消事务
watch 监控,如果监控的值发生变化,则提交事务时会失败
unwatch 去掉监控
Redsi 事务处理实践
Redis架构设计应用实践Redis主从,哨兵,集群



