一.安装步骤
方式一:
1.先将你下载好的文件放入到linux虚拟机
2.tar -zxvf redis-7.0.8.tar :解压文件
3.安装 gcc,(编译时会用到 yum install gcc)/apt-get install gcc
4.进入到解压目录中 make distclean && make
方式二:
step01-安装 gcc,编译时会用到 yum install gcc(centos的安装)/apt-get install gcc(ubantu的安装)
step02-远程下载redis压缩包或者在官网上下载好压缩包,进行解压下载
yum install wget :安装wget
wget https://github.com.redis/redis/archive/ref/tags/7.0.5.tar.gz :通过wget远程安装redis
tar -zxvf redis压缩包 :解压缩step03-进入到redis解压缩好的文件夹中,进行编译运行
make distclean:清除上次的,重新编译
make:编译step04-安装
make installstep05-安装完成之后,修改 redis.conf ,设置daemonzie yes ,这个表示让 redis 在后台运行
step06-启动redis服务端
src/redis-server redis.conf
step07-进入redis的客户端
src/redis-cli
step08-Redis 开启远程连接,修改 redis.conf
保护模式(建议不修改,就修改下面两个指令):protectedmode no
#bind 127.0.0.1
requirepass 123如果设置了密码了,再次进入到src/redis-cli时,需要去输入密码
auth 123(建议这种)
src/redis -a 123(不安全)step09-如果远程还是不行,看看关防火墙没
systemctl stop firewalld.service
systemctl disable firewalld.service
二.安装完后使用命令进入redis
第一步:src/redis-server redis-conf :设置redis-conf的配置,通过此配置文件进入到redis服务中去
第二步:src/redis-cli :进入到redis的脚手架
注意点:redis的远程连接需要设定密码,安全校验
保护模式(建议不修改,就修改下面两个指令):protectedmode no
#bind 127.0.0.1
requirepass 123
如果设置了密码了,再次进入到src/redis-cli时,需要去输入密码
auth 123(建议这种)
src/redis -a 123(不安全)
三.redis五种数据类型命令
redis中,key都是字符串,我们说的不同的数据类型都是value
常用的5种数据结构:
- key-string:一个key对应一个值。
- key-hash:一个key对应一个Map。
- key-list:一个key对应一个列表。
- key-set:一个key对应一个集合。
- key-zset:一个key对应一个有序的集合。
1.String
key指的是参数名称
#1. 添加值,如果value中间有空格的,就需要加''来包裹
set key value#2. 取值
get key#3. 如果没有设置key,在key对应的value后,追加内容
append key value#4. 自减命令(自减1),value值要是int
decr key#5. 自增命令(自增1),value值要是int
incr key#6. 自增或自减指定数量,value值要是int
incrby key increment
decrby key increment#7.自增小数/自减小数
incrbyfloat key increment
decrbyfloat key increment#8.查看并删除key
getdel key#9.查看剩下还有多少时间就过期,-1表示不会过期,-2表示过期了
ttl key#10.获取并设置key的过期时间
getex key ex 时间
ex:表示秒
px:表示毫秒#11.获取截取value字符串
getrange key start end
end如果是-1就是拿完整的#12.查找两个key公共部分
lcs key1 key2#13. 批量操作,批量设置和批量获取
mset key value [key value...]
mget key [key...]#14. 设置值,如果当前key存在的话,就不覆盖,没变化(如果这个key不存在,和set命令一样,设置值)
setnx key value#15. 设置值的同时,指定生存时间(每次向Redis中添加数据时,尽量都设置上生存时间)
setex key second value
#16.如果值开始就设置了,就只需要设置时间
setex key ex second
ex:表示秒
px:表示毫秒#16. 查看value字符串的长度
strlen key#17.获取当前的key的值,设置新的
getset key value
2.Hash
hash的value又是一个key-value形式的,key相当于存的是对象,value存的是对象的属性+值
#1. 存储数据,key相当于存的是对象,field存的是对象的属性,value是属性的值,key可以有多个属性field
hset key field value#2. 获取数据
hget key field#3. 批量操作
hmset key field value [field value ...]
hmget key field [field ...]#4. 自增(指定自增的值),当时设定field的值要是int
hincrby key field increment#5. 设置值(如果key-field不存在,那么就正常添加,如果存在,什么事都不做)
hsetnx key field value#6. 检查field是否存在
hexists key field#7. 删除key对应的field,可以删除多个,不能删除key
hdel key field [field ...]#8. 获取当前hash结构中的全部field和value
hgetall key#9. 获取当前hash结构中的全部field
hkeys key#10. 获取当前hash结构中的全部value
hvals key#11. 获取当前hash结构中field的数量
hlen key#12.随机获取key的属性,count表示随机获取几个
hrandfield key count
3.List
像队列一样,可以进行左边进还是右边进,是左边出还是右边出,有序可重复
#1. 存储数据(从左侧插入数据,从右侧插入数据)
lpush key value [value ...]
rpush key value [value ...]#2. 弹栈方式获取数据(左侧弹出数据,从右侧弹出数据)
lpop key
rpop key#3. 追加存储数据(如果key不存在,什么事都不做,如果key存在,但是不是list结构,什么都不做)
lpushx key value
rpushx key value#4. 修改数据(在存储数据时,指定好你的索引位置,覆盖之前索引位置的数据,index超出整个列表的长度,也会失败)
lset key index value#5. 获取指定索引范围的数据(start从0开始,stop输入-1,代表最后一个,-2代表倒数第二个)
lrange key start stop#6. 获取指定索引位置的数据,不会出队列
lindex key index#7. 获取整个列表的长度
llen key#8. 删除列表中的数据(他是删除当前列表中的count个value值,count > 0从左侧向右侧删除,count < 0从右侧向左侧删除,count == 0删除列表中全部的value)
lrem key count value#9. 保留列表中的数据(保留你指定索引范围内的数据,超过整个索引范围被移除掉)
ltrim key start stop#10. 将一个列表中最后的一个数据,插入到另外一个列表的头部位置
rpoplpush list1 list2#11.插入一个元素,pivot是在哪个元素,
linsert key before|after pivot value#12.获取范围内的所有元素
lrange key start stop#13.移动key到另一个key,left和right的意思是从哪边出,从哪边进。移出只是一个元素
lmove key1 key2 left|right left |right#14.移除几个key,left和right的意思是从哪边出,从哪边进,Count number是几个,Count必须写
lmpop keynumber key1 left|right Count number key2 left |right#15.查询指定元素的下边
lpos key element Count number
4.set
set:无序的,不可重复
#1. 存储数据,member是你的元素,可以有多个
sadd key member [member ...]#2. 获取数据(获取全部数据)
smembers key
scard key :获取key的长度#3. 随机获取一个数据(获取的同时,移除数据,count默认为1,代表弹出数据的数量)
spop key [count]
srandmember key [count] :随机获取多个#4. 交集(取多个set集合交集)
sinter set1 set2 ...
sintercard numberkeys set1 set2 :交集的数量#5. 并集(获取全部集合中的数据)
sunion set1 set2 ...#6. 差集(获取多个集合中不一样的数据)从第一个key里面除去第二个key公共的部分
sdiff set1 set2 ...# 7. 删除数据
srem key member [member ...]# 8. 查看当前的set集合中是否包含这个值
sismember key member
smismember key members# 9.除去两个key的公共部分,然后将剩下的存到第三个key上面去
sdiffstore key3 key1 key2# 10.从一个key移动到另一个key
smove key1 key2 element
5.zset
有序的,可以用来做限流
#1. 添加数据(score必须是数值。member不允许重复的。)
zadd key score member [score member ...]#2. 修改member的分数(如果member是存在于key中的,正常增加分数,如果memeber不存在,这个命令就相当于zadd)
zincrby key increment member#3. 查看指定的member的分数
zscore key member#4. 获取zset中数据的数量
zcard key#5. 根据score的范围查询member数量
zcount key min max#6. 删除zset中的成员
zrem key member [member...]#7. 根据分数从小到大排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数)
zrange key start stop [withscores]#8. 根据分数从大到小排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数)
zrevrange key start stop [withscores]#9. 根据分数的返回去获取member(withscores代表同时返回score,添加limit,就和MySQL中一样,如果不希望等于min或者max的值被查询出来可以采用 ‘(分数’ 相当于 < 但是不等于的方式,最大值和最小值使用+inf和-inf来标识)
zrangebyscore key min max [withscores] [limit offset count]#10. 根据分数的返回去获取member(withscores代表同时返回score,添加limit,就和MySQL中一样)
zrangebyscore key max min [withscores] [limit offset count]
四.Java代码实现redis
前言:Jedis,Resdisson(分布式锁),lettuce
redis的通信协议是:RESP(Redis Serialization Protocol)协议,java代码和redis直接的通信规则
1.Jedis方式操作redis
step01-依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
step02-连接测试
public class JedisDemo01 {
public static void main(String[] args) {
//创造连接
Jedis jedis = new Jedis("139.159.210.107", 6379);
//进行密码校验
jedis.auth("123");
String set = jedis.set("k1", "欧克");
System.out.println(set);
String s = jedis.get("k1");
System.out.println(s);
//关闭连接
jedis.close();
}
}
2.连接池测试
public class JedisDemo02 {
public static void main(String[] args) {
//配置Jedis连接池属性
GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();
//最大连接数
poolConfig.setMaxTotal(50);
//最大空闲数
poolConfig.setMaxIdle(30);
//最小空闲数
poolConfig.setMinIdle(10);
//创建Jedis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "139.159.210.107", 6379, null, "123");
//获取一个连接
Jedis jedis = jedisPool.getResource();
String set = jedis.set("k1", "欧克");
System.out.println("set = " + set);
String get = jedis.get("k1");
System.out.println("get = " + get);
//回收资源
jedisPool.returnResource(jedis);
}
}
通过try-with-resources来自动回收资源
public class JedisDemo02 {
public static void main(String[] args) {
//配置Jedis连接池属性
GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();
//最大连接数
poolConfig.setMaxTotal(50);
//最大空闲数
poolConfig.setMaxIdle(30);
//最小空闲数
poolConfig.setMinIdle(10);
//创建Jedis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "139.159.210.107", 6379, null, "123");
//获取一个连接
try(Jedis jedis = jedisPool.getResource()){
String set = jedis.set("k1", "欧克");
System.out.println("set = " + set);
String get = jedis.get("k1");
System.out.println("get = " + get);
}
}
}
3.封装连接池
1.接口类
public interface RedisCallBack {
void run(Jedis jedis);
}
2.封装类
public class Redis {
JedisPool jedisPool;
//初始化连接池
public Redis() {
GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(50);
poolConfig.setMaxIdle(30);
poolConfig.setMinIdle(10);
//创建Jedis连接池
jedisPool = new JedisPool(poolConfig, "139.159.210.107", 6379, null, "123");
}
//连接一个jedis,执行操作,释放资源
public void execute(RedisCallBack redisCallBack){
Jedis jedis = jedisPool.getResource();
redisCallBack.run(jedis);
jedisPool.returnResource(jedis);
}
}
3.函数型接口测试
public class Jedis_demo03 {
public static void main(String[] args) {
new Redis().execute(new RedisCallBack() {
@Override
public void run(Jedis jedis) {
jedis.set("k1","欧克");
System.out.println("jedis.get(\"k1\") = " + jedis.get("k1"));
}
});
}
}
4.springboot方式操作redis
底层默认的是使用lettuce,被spring-data-redis封装了
springboot提供了RedisAutoConfiguration这个自动配置类,当你配置文件中的信息写好了之后,提供Template类给你,当然他也要注入依赖
step01-依赖
<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>
step02-配置文件信息
#redis服务地址
spring.redis.host=139.159.210.107
#redis密码
spring.redis.password=123
#redis端口号,默认为6379
spring.redis.port=6379
#redis数据库,默认为0
spring.redis.database=0
step03-测试
可以自定义RedisTemplate,进行格式的转变
@SpringBootTest
class SpringDataRedisApplicationTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
//设置redis的类型
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("k1","我好想你");
String s = ops.get("k1");
System.out.println("s = " + s);
}
/**
* RedisTemplate的key和value默认都是对象,即使存入的是字符串,也是当成对象。当对象序列化之后,才可以存入到Redis,所以这里k2最终存储完成后,不仅仅k2了,前面还有其他字符
*/
@Test
void test01(){
ValueOperations ops = redisTemplate.opsForValue();
Book book = new Book();
book.setId(1);
book.setName("潘权荣");
book.setAge(18);
ops.set("k2",book);
Object k1 = ops.get("k2");
System.out.println(k1);
}
//不会有前缀字符串,而是单纯的key
@Test
void test02(){
ValueOperations ops = redisTemplate.opsForValue();
ops.set("k1","欧克");
Object k1 = ops.get("k1");
System.out.println(k1);
}
/**
* 之所以StringredisTemplate的key没有多余的前缀,是因为在redisTemplate的基础上修改了序列化方案
* 我们可以输出以json的形式,方便我们阅读
*/
@Test
void test03(){
//也可以自定义一个redisTemplate的配置类,因为当提供了redisTemplate就不会用系统自带的类
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
ValueOperations ops = redisTemplate.opsForValue();
Book book = new Book();
book.setId(1);
book.setName("潘权荣");
book.setAge(18);
ops.set("k2",book);
Object k1 = ops.get("k2");
}
}
如果出现找不到类redisConnectionFactory,加依赖
五.缓存工具Spring Cache操作redis
作用:通过cache工具存入到数据库的同时将数据存到redis中而不是需要又调用redis的代码进行存入
step01-依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<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>
step02-配置文件+开启缓存
#只需要写这些配置就行了,springcache自动帮你弄好了连接对应的缓存工具
spring.redis.host=139.159.210.107
spring.redis.password=123
spring.redis.port=6379
spring.cache.cache-names=c1
@SpringBootApplication
//开启缓存
@EnableCaching
public class SpringCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCacheApplication.class, args);
}
}
step03-实体类User
public class User implements Serializable {
private Integer id;
private String username;
private Integer age;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
'}';
}}
step04-service
@Service
//如果写了CacheConfig就不需要在下面每个方法都配置这个@Cacheable(cacheNames = "c1")了
//@CacheConfig(cacheNames = "c1")
public class UserService {
/**
* @Cacheable:表示将当前方法的返回值缓存起来,缓存的key是cacheNames+'::'+方法的参数,缓存的value是方法的返回值
*/
@Cacheable(cacheNames = "c1")
public User getUserById(Integer id){
System.out.println("getUserById>>>"+id);
User user = new User();
user.setId(id);
return user;
}
@Cacheable(cacheNames = "c1")
public User getUserById(Integer id,String username){
System.out.println("getUserById>>>"+id+">>>"+username);
User user = new User();
user.setId(id);
user.setUsername("username");
return user;
}
/**
* key = "#id":意思是使用id来做缓存的key,缺陷是可能造成数据的错误,因为id可能重复
*/
@Cacheable(cacheNames = "c1",key = "#id")
// @Cacheable(cacheNames = "c1",keyGenerator = "myKeyGenerator")
public User getUser(Integer id,String username){
System.out.println("getUserById>>>"+id+">>>"+username);
User user = new User();
user.setId(id);
user.setUsername(username);
return user;
}
/**
* 对于更新来说,假设:
* 1.首先执行getUserById方法,查询id为99 的用户
* 2.执行updateUserById更新id为99的用户
* 3.再去执行getUserById方法,查询的是id99的用户,由于第一步已经调用了该方法,id为99的用户被缓存起来了,所以拿到的还是旧数据
*
*更新规则:需要保证这里缓存的key要和之前添加的key保持一致
* 为了在更新数据的时候,也更新缓存我们使用CachePut注解
* 和Cacheable的区别就是:如果有该key和value了,就会覆盖前面的
使用这个注解,意味着更新方法的返回值不能是void,必须是刚刚更新过的对象
* @param user
* @return
*/
@CachePut(cacheNames = "c1",key = "#user.id")
public User updateUser(User user){
System.out.println("updateUser");
return user;
}
/**
* @CacheEvict:一般加在删除方法上
* allEntries = false:这个表示是否清除所有缓存
* beforeInvocation = false:这个表示是否在删除数据库之前,先删除缓存
* @param id
使用场景,是因为我们在更新数据的时候,会比较慢,所以不如直接将缓存删了,等你下次来了,我在进行添加
*/
@CacheEvict(cacheNames = "c1",allEntries = false,beforeInvocation = false)
public void delUserById(Integer id){
System.out.println("删除");
}
}
step05-自定义key
当你不想使用默认的cacheNames+'::'+方法的参数为key时,可以通过实现KeyGenerator接口来自定义key
/**
* 自定义key的生成类
*/
@Component
public class MyKeyGenerator implements KeyGenerator {
/**
* generate:当需要用来key的时候就会自动的调用该方法
* @param target 当前对象
* @param method 当前方法
* @param params 当前方法的参数
* @return 返回值就是缓存的key
*/
@Override
public Object generate(Object target, Method method, Object... params) {
String name = method.getName();
String s = Arrays.toString(params);
return name+s;
}
}
step06-测试
@SpringBootTest
class SpringCacheApplicationTests<test> {
@Autowired
UserService userService;
@Test
void contextLoads() {
User user = userService.getUserById(3);
User user2 = userService.getUserById(4);
System.out.println("user = " + user);
System.out.println("user2 = " + user2);
}
@Test
void test2() {
User user = userService.getUserById(3,"ok");
User user2 = userService.getUserById(3,"欧克");
System.out.println("user = " + user);
System.out.println("user2 = " + user2);
}
@Test
void test3() {
User user = userService.getUser(5, "潘权荣");
System.out.println("user = " + user);
User user1 = new User();
user1.setId(5);
user1.setUsername("欧克");
userService.updateUser(user1);
userService.getUser(5, "潘权荣");
}
@Test
void test04(){
//删除缓存
userService.delUserById(5);
}
}
六.Session共享
前言:用来解决一个系统分配在多个服务器上的时候,可以缓解一台服务器的压力,一般使用在redis集群情况下。其实就是你加了session依赖之后,session自动帮你存在了redis中去,不需要做任何的配置了,到时你多个项目的时候,依然可以在redis中获取同一个session
step01-配置文件+依赖
<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>
//当你要获取session的时候,自动帮你去redis中获取,让你发送请求,然后设置session的时候,自动帮你存
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
spring.redis.host=139.159.210.107
spring.redis.password=123
spring.redis.port=6379
server.port=8080
step02-写一个接口验证
step03-为了统一端口,只需要访问接口就行了,提高用户体验
部署两个服务器
后面的8080.log和8081.log是执行的日志写在这些日志文件里头
使用nginx来做反向代理,在default.conf中配置这两个springboot项目的上游服务器
step04-端口统一了
七.Redis处理接口幂等性
前言:同一个接口,相同的参数,执行多次和执行一次的结果是一样的(主要针对增删改)
方式一:通过拦截器的方式
step01-依赖+配置文件
<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>
spring.redis.host=139.159.210.107
spring.redis.password=123
spring.redis.port=6379
step02-自定义注解
/**
* 注解一般都是要通过aop来进行调用,但是在controller层中我们可以通过另一种方式,使用spring提供的handlMethod
*/
//运行时发生
@Retention(RetentionPolicy.RUNTIME)
//使用在方法上
@Target(ElementType.METHOD)
public @interface Idempontent {
}
step03-controller
@RestController
public class HellController {
@Autowired
TokenService tokenService;
//发送请求进行提交的时候
@PostMapping("/hello")
//每次这个接口都是用幂等性处理
@Idempontent
public String helloPost(){
return "hello Post";
}
@GetMapping("/hello")
public String helloGet(){
return "hello Get";
}
//当前端点击按钮的时候,生成令牌,然后携带该令牌去redis中查看是否有该令牌
@GetMapping("/generate")
public String generateToken(){
return tokenService.generateToken();
}
}
step04-拦截器处理@Idempotent注解
/**
* 自定义拦截器,如果哪个接口需要处理幂等性,就在哪个接口上添加@Idempotent注解,如果有就检查请求的token是否合法,如果没有,这个请求就直接放行
*/
@Component
public class IdempontentInterceptor implements HandlerInterceptor {
@Autowired
TokenService tokenService;
/**
* 在controller之前执行
* @param request
* @param response
* @param handler :如果handle不是注解定义的,像之前springmvc那样,这个handler就是每一个controller对象,但是我们是通过注解的方式进行的,所以此时这个handler是你的处理器
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// HandlerMethod是spring提供在Controller层中用来将每一个接口方法封装成一个HandlerMethod对象,
if(handler instanceof HandlerMethod){
//这个实际上就是具体的接口方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
//获取写在方法上的注解
Idempontent methodAnnotation = handlerMethod.getMethodAnnotation(Idempontent.class);
//说明这个接口需要进行幂等性出来
if(methodAnnotation!=null){
//检查是否携带令牌,令牌是存放在request中的
boolean result=tokenService.checkToken(request);
if(!result){
RespBean respBean = new RespBean();
respBean.setMsg("请求重复");
respBean.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(respBean));
//也可以返回自定义的幂等性异常
return false;
}
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
将拦截器注入到配置中
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
IdempontentInterceptor idempontentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(idempontentInterceptor)
.addPathPatterns("/**");
}
}
step05-检查令牌
@Service
public class TokenService {
//设置给予redis的令牌
@Autowired
RedisService redisService;
public boolean checkToken(HttpServletRequest request) {
//从请求头中获取token
String token = request.getHeader("token");
//如果请求头没有,就从请求参数中获取
if (token == null || "".equals(token)) {
token = request.getParameter("token");
if (token == null || "".equals(token)) {
//说明请求没有携带令牌
return false;
}
}
//查看redis中是否存在
boolean exists = redisService.exists(token);
//如果redis中存在令牌
if (exists) {
boolean delResult = redisService.deleteToken(token);
return delResult;
}
return false;
}
//生成令牌,并存令牌在redis中
public String generateToken() {
String s = UUID.randomUUID().toString();
redisService.saveToken(s);
return s;
}
}
step06-检查redis中是否有令牌
@Service
public class RedisService {
@Autowired
StringRedisTemplate redisTemplate;
public boolean exists(String token) {
//判断redis中是否存在token参数
return redisTemplate.hasKey(token);
}
//担心并发的时候,同时删除了,但还是要在操作一次,会报错,所以我们在删除的时候需要在先查一次,看看token是否存在
public boolean deleteToken(String token) {
if(exists(token)){
return redisTemplate.delete(token);
}
return false;
}
//在redis中存储令牌
public void saveToken(String token) {
redisTemplate.opsForValue().set(token,token);
}
}
step07-自定义幂等异常
/**
* 自定义的幂等异常
*/
public class IdempontentException extends RuntimeException {
public IdempontentException(String message) {
super(message);
}
}
/**
* 配置全局捕获异常
*/
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(IdempontentException.class)
public RespBean idempontentException(IdempontentException e){
RespBean respBean = new RespBean();
respBean.setMsg(e.getMessage());
respBean.setStatus(500);
return respBean;
}
}
方式二:通过Aop
step01-依赖+配置
<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-starter-aop</artifactId>
</dependency>
step02-注解
//运行时发生
@Retention(RetentionPolicy.RUNTIME)
//使用在方法上
@Target(ElementType.METHOD)
public @interface Idempontent {
}
step03-Aop切面
@Component
@Aspect
@EnableAspectJAutoProxy
public class IdempontentAop {
@Autowired
TokenService tokenService;
//拦截该注解
@Pointcut("@annotation(com.pan.idempontent.annottion.Idempontent)")
public void pointcut(){
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获得当前请求
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpServletResponse response = requestAttributes.getResponse();
boolean checkToken = tokenService.checkToken(request);
//方式一,直接返回一个json字符串
// if(checkToken){
//继续执行
// return pjp.proceed();
//
// }else {
// RespBean respBean = new RespBean();
// respBean.setMsg("请求重复");
// respBean.setStatus(500);
// return new ObjectMapper().writeValueAsString(respBean);
// }
//方式二
if(checkToken){
//继续执行
return pjp.proceed();
}else {
throw new IdempontentException("请求重复");
}
}
}
step04-自定义幂等异常
/**
* 自定义的幂等异常
*/
public class IdempontentException extends RuntimeException {
public IdempontentException(String message) {
super(message);
}
}
/**
* 配置全局捕获异常
*/
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(IdempontentException.class)
public RespBean idempontentException(IdempontentException e){
RespBean respBean = new RespBean();
respBean.setMsg(e.getMessage());
respBean.setStatus(500);
return respBean;
}
}
step05-检查令牌
@Service
public class TokenService {
//设置给予redis的令牌
@Autowired
RedisService redisService;
public boolean checkToken(HttpServletRequest request) {
//从请求头中获取token
String token = request.getHeader("token");
//如果请求头没有,就从请求参数中获取
if (token == null || "".equals(token)) {
token = request.getParameter("token");
if (token == null || "".equals(token)) {
//说明请求没有携带令牌
return false;
}
}
//查看redis中是否存在
boolean exists = redisService.exists(token);
//如果redis中存在令牌
if (exists) {
boolean delResult = redisService.deleteToken(token);
return delResult;
}
return false;
}
//生成令牌,并存令牌在redis中
public String generateToken() {
String s = UUID.randomUUID().toString();
redisService.saveToken(s);
return s;
}
}
step06-检查redis中是否有令牌
@Service
public class RedisService {
@Autowired
StringRedisTemplate redisTemplate;
public boolean exists(String token) {
//判断redis中是否存在token参数
return redisTemplate.hasKey(token);
}
//担心并发的时候,同时删除了,但还是要在操作一次,会报错,所以我们在删除的时候需要在先查一次,看看token是否存在
public boolean deleteToken(String token) {
if(exists(token)){
return redisTemplate.delete(token);
}
return false;
}
//在redis中存储令牌
public void saveToken(String token) {
redisTemplate.opsForValue().set(token,token);
}
}
step07-controller
@RestController
public class HellController {
@Autowired
TokenService tokenService;
//发送请求进行提交的时候
@PostMapping("/hello")
//每次这个接口都是用幂等性处理
@Idempontent
public String helloPost(){
return "hello Post";
}
@GetMapping("/hello")
public String helloGet(){
return "hello Get";
}
//当前端点击按钮的时候,生成令牌,然后携带该令牌去redis中查看是否有该令牌
@GetMapping("/generate")
public String generateToken(){
return tokenService.generateToken();
}
}
八.redis事务
- 开启事务:multi
- 输入要执行的命令:被放入到一个队列中
- 执行事务:exec
- 取消事务:discard
redis的事务和mysql的事务不太一样,redis中的事务,只有编译的错误他才会回滚,如果是类型错误的incr是不会回滚,只是不会执行错的,其他没错的照常执行。
为了解决这个弊端,我们得搭配watch来使用
在开启事务之前,先通过watch命令去监听一个或多个key,在开启事务之后,如果有其他客户端修改了我监听的key,事务会自动取消。
如果执行了事务,或者取消了事务,watch监听自动消除,一般不需要手动执行unwatch。
九.Redis持久化机制
1.RDB(快照,二进制文件)
可以在redis.conf文件中修改配置
RDB是Redis默认的持久化机制
- RDB持久化文件,速度比较快,而且存储的是一个二进制的文件,传输起来很方便。
- RDB持久化的时机:
save 900 1:在900秒内,有1个key改变了,就执行RDB持久化。
save 300 10:在300秒内,有10个key改变了,就执行RDB持久化。
save 60 10000:在60秒内,有10000个key改变了,就执行RDB持久化。
当满足要求,或者shutdown或者save/bgsave指令的时候,数据存储到redis配置文件的同一目录:dump.rdb,当你重新启动的时候,会从该磁盘文件中,获取数据。
- RDB无法保证数据的绝对安全。,因为是达到要求才能够进行存储数据到rdb文件
关闭RDB的操作:
1.进去到redis的配置文件中去
vi redis.conf
2.配置文件中搜/rdb,找到save "",取消注释关闭RDB备份
2.AOF
AOF持久化机制默认是关闭的,Redis官方推荐同时开启RDB和AOF持久化,更安全,避免数据丢失。
- AOF持久化的速度,相对RDB较慢的,存储的是一个文本文件,到了后期文件会比较大,传输困难。
- AOF持久化时机。
appendfsync always:每执行一个写操作,立即持久化到AOF文件中,性能比较低。
appendfsync everysec:每秒执行一次持久化。(默认)
appendfsync no:会根据你的操作系统不同,环境的不同,在一定时间内执行一次持久化。- AOF相对RDB更安全,推荐同时开启AOF和RDB。
开启AOF机制:
1.在redis.conf配置文件中搜/appendonly
2.将appendonly开启
生成的备份文件名时:存在/appendonlydir/appendonly.aof
十.Redis一主两从
前言:使用主机来增删改,从机来读
1.修改redis.conf配置文件,因为需要弄多个redis实例才能实现一主二从
使用AOF的时候,我们在设置一主二从的时候,需要添加多个redis实例,所以要修改备份文件的名字,一个实例对应一个备份文件名
appenddirname "appendonlydir6379"使用RDB备份的话,就要修改这个备份文件名
logfile "6379.log"还要修改pid进程文件
pidfile /var/run/redis_6379.pid
2.在修改配置文件的时候,可以通过命令快速修改内容
3.我们需要将6380和6381当作从机,所有在6380和6381配置文件中还需要修改多一个配置
replicaof masterip 6379 :告诉从机,主机是ip和端口号6379
masterauth 主机密码
4.查看副本信息:info replication
十一.哨兵模式
step01-修改sentinel.conf里面的配置
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel auth-pass mymaster 123
其中mymaster是给要监控的主机取的名字,随意取,后面是主机地址,最后面的1表示有多少个sentinel认为主机挂掉了,就进行切换(我这里只有一个,因此设置为1)
step02-启动哨兵
redis-sentinel sentinel.conf
十二.集群
前言:当创建好了集群,只需要登录到其中一个节点上,数据会随机分配到任意的节点上,进行切换
step01-在redis.conf中配置
# 端口号
port 7001
# 本来这个配置是只允许本地登录,注释掉之后就允许远程登录了
#bind 127.0.0.1
# 开启集群模式
cluster-enabled yes
# 集群的配置,每一个节点都对应一个配置
cluster-config-file nodes-7001.conf
# 关闭保护模式
protected no
# 后台运行
daemonize yes
# 主机密码(这个是给从机配置的)
masterauth 123
# 配置主机密码
requirepass 123
# 日志文件
logfile 7001.log
# 每个节点的进程文件
pidfile /var/run/redis_7001.pid
# 关闭 rdb 持久化(aof持久化默认就是关闭的)
save ""快速的将复制的配置文件进行修改
step02-这个表示各个节点都启动成功了。接下来我们就可以进行集群的创建了
./src/redis-cli --cluster create --cluster-replicas 1 192.168.91.130:7001 192.168.91.130:7002 192.168.91.130:7003 192.168.91.130:7004 192.168.91.130:7005 192.168.91.130:7006 -a 123
注意:创建的redis一定要为空,否则搭建不了集群,所以要删除缓存文件等
step03-集群创建成功后,我们可以登录到 Redis 控制台查看集群信息,注意登录时要添加 -c
参数,表示以集群方式连接
step04-可以在上面的集群基础上添加新的主节点
src/redis-cli -a 123 -p 7001 --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
但是通过cluster nodes命令查看的时候7007并没有任何的槽
step05-重新分配槽
src/redis-cli -a 123 -p 7001 --cluster reshard 127.0.0.1:7001
因为 hash 槽目前已经全部分配完毕,要重新从已经分好的节点中拿出来一部分给 7007,必然要让另外三个节点把吃进去的吐出来,这里我们可以输入多个节点的编号,每次输完一个点击回车,输完所有的输入 done 表示输入完成,这样就让这几个节点让出部分 slot,如果要让所有具有 slot 的节点都参与到此次 slot 重新分配的活动中,那么这里直接输入 all 即可
step06-也可以添加从节点
src/redis-cli -a 123 -p 7001 --cluster add-node 127.0.0.1:7008 127.0.0.1:7001 --cluster-slave --cluster-master-id 8e8eedb372b9dc36a57ff81d4b4b4634f3f2bffa
8e8eedb372b9dc36a57ff81d4b4b4634f3f2bffa是你要将7008定义为谁的从机
删除槽,在删除之前,要将槽分出去,确保集群的完整性
src/redis-cli -a 123 -p 7001 --cluster del-node 127.0.0.1:7001 ce53609f0fc6b0af5daf2ce37d6c4ec5798c7d98
Jedis 操作 RedisCluster
需要的是jedis的依赖
public class RedisCluster {
public static void main(String[] args) {
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.91.128", 7001));
nodes.add(new HostAndPort("192.168.91.128", 7002));
nodes.add(new HostAndPort("192.168.91.128", 7003));
nodes.add(new HostAndPort("192.168.91.128", 7004));
nodes.add(new HostAndPort("192.168.91.128", 7005));
nodes.add(new HostAndPort("192.168.91.128", 7006));
nodes.add(new HostAndPort("192.168.91.128", 7007));
GenericObjectPoolConfig config = new JedisPoolConfig();
//连接池最大空闲数
config.setMaxIdle(300);
//最大连接数
config.setMaxTotal(1000);
//连接最大等待时间,如果是 -1 表示没有限制
config.setMaxWaitMillis(30000);
//在空闲时检查有效性
config.setTestOnBorrow(true);
JedisCluster cluster = new JedisCluster(nodes, 15000, 15000, 5, "123", config);
String set = cluster.set("k1", "v1");
System.out.println(set);
String k1 = cluster.get("k1");
System.out.println(k1);
}
}
十三.布隆过滤器
每一个布隆过滤器,在 Redis 中都对应了一个大型的位数组以及几个不同的
hash 函数。
前言:1.Bloom Filter专门是用来解决去重问题的
2.底层是根据hash函数进行运算的
3.再对应的位置查看有没有对应的数,虽说有不一定真的有,需要去数据库查找,说没有的话,就一定是没有的
1.安装
1.docker安装
docker run -p 6379:6379 --name redis-redisbloom
redislabs/rebloom:latest2.自己编译安装
cd redis-5.0.7
//方式一
git clone https://github.com/RedisBloom/RedisBloom.git
//方式二
wget http://github.com/RedisBloom/RedisBloom/archive/refs/tags/v2.2.18.tar.gz
cd RedisBloom/
//使用gcc的指令
make
cd ..
//记得redis.conf文件需要修改,和之前创建好的redis一样的修改就行了
redis-server redis.conf --loadmodule ./RedisBloom/redisbloom.so安装完成后,执行 bf.add 命令,测试安装是否成功。
每次启动时都输入 redis-server redis.conf --loadmodule ./RedisBloom/redisbloom.so 比较麻烦,我们可以将要加载的模块在
redis.conf 中提前配置好。
##MODULES
\# Load modules at startup. If the server is not able to load modules
\# it will abort. It is possible to use multiple loadmoduledirectives.
\#
\# loadmodule /path/to/my_module.so
\# loadmodule /path/to/other_module.soloadmodule /root/redis-5.0.7/RedisBloom/redisbloom.so
最下面这一句,配置完成后,以后只需要 redis-server redis.conf 来启动 Redis 即可。
2.使用
默认情况下,我们使用的布隆过滤器它的错误率是 0.01 ,默认的元素大小是 100。但是这两个参数也是可以配置的。
我们可以调用 bf.reserve 方法进行配置。
BF.RESERVE k1 0.0001 1000000
第一个参数是 key,第二个参数是错误率,错误率越低(意味着位数组越长),占用的空间越大,第三个参数预计存储的数量,当实际数量超出预计数量时,错误率会上升。
例子:
3.使用java代码使用布隆过滤器
方式一:直接使用布隆过滤器的依赖
step01-i加入布隆过滤器的依赖,但是呢,它有兼容性的问题,需要搭配低版本的jedis
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>1.2.0</version>
</dependency>
//搭配布隆过滤器的jedis这个低版本
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
step02-代码
public class BloomFilter {
public static void main(String[] args) {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxIdle(300);
config.setMaxTotal(1000);
config.setMaxWaitMillis(30000);
config.setTestOnBorrow(true);
JedisPool pool = new JedisPool(config, "192.168.91.128",6379, 30000, "javaboy");
//布隆过滤器提供的方法
Client client = new Client(pool);
//存入数据
for (int i = 0; i < 100000; i++) {
client.add("name", "javaboy-" + i);
}
//检查数据是否存在
boolean exists = client.exists("name", "javaboy-9999999");
System.out.println(exists);
}
}
方式二:使用lettuce提供的工具,自定义
step01-依赖
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
还需要加上lettuce依赖
step02-代码
十四.分布式锁
方式一:自己写锁
需要引入lua脚本,它附有原子性,再java代码执行的过程中,主要lua脚本没有执行完之前,其他java代码的命令是一概不处理的
![]()
![]()
方式二:使用Redisson锁,直接调用它提供的方法
相对于 Jedis 这种原生态的应用,Redisson 对 Redis 请求做了较多的封装,对于锁,也提供了对应的方
法可以直接使用:
step01-依赖
step02-java代码
Config config = new Config();
//配置 Redis 基本连接信息
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123");
//获取一个 RedissonClient 对象
RedissonClient redisson = Redisson.create(config);
//获取一个锁对象实例
RLock lock = redisson.getLock("lock");
try {
//获取锁
boolean b = lock.tryLock(500, 1000, TimeUnit.MILLISECONDS);
if (b) {
//获取到锁了,开始写业务,getBucket相当于设置一个key
RBucket<Object> bucket = redisson.getBucket("k1");
//设置value值
bucket.set("www.javaboy.org");
Object o = bucket.get();
System.out.println(o);
}else{
System.out.println("没拿到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
在这段代码中,核心的就是 lock.tryLock(500, 1000, TimeUnit.MILLISECONDS); ,第一个参数是 尝试加锁的等待时间为 500 毫秒,第二个参数表示锁的超时时间为 1000 毫秒,也就是这个锁在 1000毫秒后会自动失效。(如果第二个参数不写的话,那么redisson会有watchdog来查看你的业务是否完成,如果没有完成自动续期) redisson中的加锁和设置过期时间等操作都是基于lua脚本完成的,底层是setnx+lua脚本(保证原子性)
十五.redis限流(令牌桶限流)
wget https://github.com/brandur/redis-cell/releases/download/v0.2.4/redis-cellv0.2.4-x86_64-unknown-linux-gnu.tar.gz
tar -zxvf redis-cell-v0.2.4-x86_64-unknown-linux-gnu.tar.gz
mkdir redis-cell
mv libredis_cell.d ./redis-cell
mv libredis_cell.so ./redis-cell接下来修改 redis.conf 文件,加载额外的模块:
loadmodule /root/redis-5.0.7/redis-cell/libredis_cell.so启动 Redis
redis-server redis.conf
step01-解压缩redis-cell压缩包
step02-再配置文件中加入redis-cell的工具的路径
step03-启动redis,设置令牌通的参数
redis 启动成功后,如果存在 CL.THROTTLE 命令,说明 redis-cell 已经安装成功了
CL.THROTTLE 命令一共有五个参数
\1. 第一个参数是 key
\2. 第二个参数是**令牌桶容量**
\3. **令牌产生个数**
\4. **令牌产生时间**
\5. **本次取走的令牌数**