一:为什么使用Redis
二:Redis简介
官网:https://redis.io/
Redis 是完全开源的,C语言编写,基于内存,是一个高性能的 Nosql 数据库。
NoSQL===非关系型数据分为以下2种:
1:KV(Redis、Memcache)
2:文档型(ElasticSearch、Solr、Mongodb)
Redis 优势:
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Redis 与其他 key - value(Memcache) 缓存产品有以下三个特点:
1:Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
2:Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
3:Redis支持数据的备份,即master-slave模式的数据备份。
Redis:使用场景
1:缓存,减轻数据库压力
2:存储会话信息
3:存储短信验证码
4:消息队列
三:Redis安装
Window安装Redis
下载地址:https://github.com/microsoftarchive/redis/releases
Linux系统安装Redis
注意:redis6版本要求gcc版本必须5.3以上,centos7默认安装的版本是4.8.5,查看gcc版本命令
升级gcc
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile --使永久生效
yum install tcl -y
上传redis的压缩包到服务器,并解压
[root@zhuxm01 soft]# tar -zxvf redis-6.0.8.tar.gz -C /export/server/
编译:将.c文件编译成.o文件
[root@zhuxm01 redis-6.0.8]# cd /export/server/redis-6.0.8/
[root@zhuxm01 redis-6.0.8]# make
安装
[root@zhuxm01 redis-6.0.8]# cd /export/server/redis-6.0.8/
[root@zhuxm01 redis-6.0.8]# make PREFIX=/export/server/redis install
前台启动
后台启动
1:复制redis的核心配置文件redis.conf到bin目录下
[root@zhuxm01 redis-6.0.8]# pwd
/export/server/redis-6.0.8
[root@zhuxm01 redis-6.0.8]# cp redis.conf /export/server/redis/bin/
2:修改配置文件
daemonize yes
3:启动
[root@zhuxm01 bin]# pwd
/export/server/redis/bin
[root@zhuxm01 bin]# ./redis-server ./redis.conf
连接
[root@zhuxm01 bin]# ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379>
配置redis环境变量
vi/etc/profile
export REDIS_HOME=/export/server/redis
export PATH=$PATH:$REDIS_HOME/bin
立即生效
source /etc/profile
配置完环境变量后,我们可以在任何目录下启动redis-server
redis-server /export/server/redis/bin/redis.conf
四:Redis数据类型
redis是以KV的方式存储数据,V的数据格式有5种常用的
4.1:String
set k v
get k
del k
strlen k
keys *
#观看视频的人数
incr article
incrby article 3
decr article
decyby article
#截取字符串
getrange name 0 -1
getrange name 0 1
#替换
setrange name 0 x
# SETEX 是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成
SETEX pro 10 华为
#1,当 key 的值被设置 0,当 key 的值没被设置
#分布式锁
setnx(set if not exist)
4.2:List
lpush num 1 2 3 4 5
lrange num 0 -1
rpush num2 1 2 3 4 5
lrange num2 0 -1
#对列:先进先出
lpush num3 1 2 3 #左边推进去
rpop num3 1 #右边取出来
或者
rpush num4 1 2 3
lpop num4
#栈:先进后出
lpush num5 1 2 3
lpop num5
或者
rpush num6 1 2 3
rpop num6
#list的长度
llen num5
4.3:Set
sadd ips '192.168.22.1' '192.168.22.2' '192.168.22.2'
smembers ips
scard ips #获取set的长度
srem ips '192.168.22.1'
#随机
sadd rands 3 4 5 0 9 8 9 0 8 6
srandmember rands 3
#随机出栈
spop rands
4.4:Hash
K V(K V)
hset person name 'jack'
hset person age 40
hget person name
hget person age
hmset person name 'rose' age 12
hlen person
hexists person age
hkeys person
hvals person
#hash可以大大减少redis中的K 同时hash结构特别适合存放对象
pro:hit :
123 '手机'
234 '袜子'
4.5:Zset(sorted set)
通过score进行排序
zadd pro:hot 300 '华为met10' 10 '苹果10' 19 '小米'
zrange pro:hot 0 -1
zrevrange pro:hot 0 -1
#分数范围过滤
zrangebyscore pro:hot 11 100
zrangebyscore pro:hot 10 100 limit 0 1
#删除
zrem pro:hot '小米'
zcard pro:hot #查看集合的元素个数
五:其他指令
#########################key相关指令##############
key parttern
127.0.0.1:6379> keys art*
1) "article2"
2) "artile1"
127.0.0.1:6379>
#如果存在返回1,否则返回0
exists key
del key
#给key设置过期时间
expire key seconds
#查看key的剩余时间 -1:表示没有过期时间 >0 表示剩余的时间 -2:过期,数据被回收
ttl key
#不设置过期时间,让它常驻内存
persist key
################db相关的指令##########################
Redis有16个db 分别为0,1,...,15【redis的默认数据库为0】
select index
move key dbindex
#清空数据库
flushdb
#清空所有的数据库
flushall
#数据库的数据总条数
dbsize
#最近一次保存数据的时间戳
lastsave
六:Redis事务
Redis中的事务跟mysql不一样,Redis单条指令可以保证原执行,但是多条不能保证原子性
setex pro 30 book #原子
set pro book
expire pro 30
mutli #开启事务
exec #提交事务
discard #回滚事务
#正常事务提交
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name jack
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> exec
#redis事务保证顺序执行、同时一起执行
#放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name sdf
QUEUED
127.0.0.1:6379> set age 12
QUEUED
127.0.0.1:6379> discard
#编译时异常,回滚
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name 123
QUEUED
127.0.0.1:6379> kset xx sdf
ERR unknown command `kset`, with args beginning with: `xx`, `sdf`,
127.0.0.1:6379> set age 21
QUEUED
127.0.0.1:6379> exec
EXECABORT Transaction discarded because of previous errors.
#运行时异常,不回滚
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name jack
QUEUED
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> set age 12
QUEUED
127.0.0.1:6379> exec
OK
ERR value is not an integer or out of range
OK
127.0.0.1:6379> get age
12
七:Jedis
pom依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
java代码
@Test
public void testRedis (){
Jedis jedis = new Jedis("192.168.234.122", 6379);
jedis.set("name", "jack");
System.out.println(jedis.get("name"));
jedis.close();
}
@Test
public void testJedispwd (){
Jedis jedis = new Jedis("192.168.234.131",6379);
jedis.auth("123456");
jedis.set("name","jack");
String name = jedis.get("name");
System.out.println(name);
jedis.close();
}
@Test
public void testJedisPool() {
JedisPoolConfig config = new JedisPoolConfig();
//最大空闲连接数, 默认8个
config.setMaxIdle(8);
//最大连接数, 默认8个
config.setMaxTotal(8);
JedisPool jedisPool = new JedisPool(config,"192.168.234.122", 6379);
Jedis jedis = jedisPool.getResource();
jedis.set("name", "rose");
System.out.println(jedis.get("name"));
jedis.close();
jedisPool.close();
}
注意:
1:修改bind
bind ip错误理解:不是说bind哪个ip ,就只能哪个ip来访问redis
bind ip正确理解: bind本机的网卡对应的ip地址,只有通过指定网卡进来的主机才能访问redis
bind 127.0.0.1(默认),本地回环地址。那么访问redis服务只能通过本机的客户端连接,而无法通过远程连接
bind 192.168.234.131 通过本机ens33网卡进来的主机都可以访问redis,这样设置后基本所有的主机都可以访问
如果要限制只允许某些host访问,那么可以通过配置安全组实现
2:redis设置密码
Redis6之前Redis就只有一个用户(default)权限最高,通过配置文件的requirepass配置
Redis6版本推出了**ACL(Access Control List)访问控制权限**的功能,基于此功能,我们可以设置多个用户,为了保证**向下兼容**,Redis6保留了default用户和使用requirepass的方式给default用户设置密码,默认情况下default用户拥有Redis最大权限,我们使用redis-cli连接时如果没有指定用户名,用户也是默认default
3:ACL常用命令
ACL whoami
ACL list
ACL setuser allen on >123456 +@all ~*
AUTH allen mypasswd
#只能创建以lakers为前缀的key
ACL setuser james on >123456 +@all ~lakers*
#不拥有set权限
ACL setuser james -SET
ACL DELUSER james
jedis常见api
字符串
// 1.string
//输出结果: OK
jedis.set("hello", "world");
//输出结果: world
jedis.get("hello");
//输出结果:1
jedis.incr("counter");
哈希
// 2.hash
jedis.hset("myhash", "f1", "v1");
jedis.hset("myhash", "f2", "v2");
//输出结果 : {f1=v1, f2=v2}
jedis.hgetAll("myhash");
列表
// 3.list
jedis.rpush("mylist", "1");
jedis.rpush("mylist", "2");
jedis.rpush(" mylist", "3");
//输出结果 : [1, 2, 3]
jedis.lrange("mylist", 0, -1);
集合
// 4.set
jedis.sadd(" myset", "a");
jedis.sadd(" myset", "b");
jedis.sadd(" myset", "a");
//输出结果 : [b, a]
jedis.smembers("myset");
有序集合
// 5.zset
jedis.zadd("myzset", 99, "tom");
jedis.zadd("myzset", 66, "peter");
jedis.zadd("myzset", 33, "james");
//输出结果 : [[["james"],33.0], [["peter"],66.0], [["tom"],99.0]]
jedis.zrangeWithScores("myzset", 0, -1);
八:Spring-data-redis
官网:https://spring.io/projects/spring-data-redis
pom依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
配置文件
redis.properties
#最大活动对象数
redis.pool.maxTotal=1000
#最大能够保持idel状态的对象数
redis.pool.maxIdle=100
#最小能够保持idel状态的对象数
redis.pool.minIdle=50
#当池内没有返回对象时,最大等待时间
redis.pool.maxWaitMillis=10000
#当调用borrow Object方法时,是否进行有效性检查
redis.pool.testOnBorrow=true
#当调用return Object方法时,是否进行有效性检查
redis.pool.testOnReturn=true
#“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
redis.pool.timeBetweenEvictionRunsMillis=30000
#向调用者输出“链接”对象时,是否检测它的空闲超时;
redis.pool.testWhileIdle=true
# 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
redis.pool.numTestsPerEvictionRun=50
#redis服务器的IP
redis.host=192.168.234.131
#redis服务器的Port
redis.port=6379
redis.timeout =5000
redis.pass=123456
applicationContext-jedis.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.properties" ignore-unresolvable="true" />
<!--配置redis连接池-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}"></property>
<property name="maxIdle" value="${redis.pool.maxIdle}"></property>
<property name="minIdle" value="${redis.pool.minIdle}"></property>
</bean>
<!--配置连接工场-->
<bean id="connectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:hostName="${redis.host}" p:password="${redis.pass}" p:port="${redis.port}"
p:poolConfig-ref="jedisPoolConfig"
/>
<!--模板对象-->
<bean class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
</bean>
</beans>
测试
package com.wfx.test;
import com.wfx.dao.SysRoleDao;
import com.wfx.entity.SysRole;
import com.wfx.service.IsysRoleService;
import com.wfx.vo.TreeNodeVO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:applicationContext-*.xml")
public class TestSysRoleServie {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testString (){
redisTemplate.boundValueOps("name").set("jack");
System.out.println(redisTemplate.boundValueOps("name").get());
}
@Test
public void testList (){
redisTemplate.boundListOps("names").leftPush("张飞");
redisTemplate.boundListOps("names").leftPush("刘备");
redisTemplate.boundListOps("names").leftPush("关羽");
redisTemplate.boundListOps("names").leftPush("曹操");
System.out.println(redisTemplate.boundListOps("names").rightPop());
System.out.println(redisTemplate.boundListOps("names").rightPop());
System.out.println(redisTemplate.boundListOps("names").rightPop());
System.out.println(redisTemplate.boundListOps("names").rightPop());
}
@Test
public void testSet (){
redisTemplate.boundSetOps("ips").add("123","123","345","678");
System.out.println(redisTemplate.boundSetOps("ips").members());
System.out.println(redisTemplate.boundSetOps("ips").size());
}
@Test
public void testHash (){
redisTemplate.boundHashOps("person").put("name","jack");
redisTemplate.boundHashOps("person").put("age","12");
System.out.println(redisTemplate.boundHashOps("person").get("name"));
System.out.println(redisTemplate.boundHashOps("person").size());
}
@Test
public void testZset (){
redisTemplate.boundZSetOps("pro:hot").add("华为",100);
redisTemplate.boundZSetOps("pro:hot").add("小米",99.99);
redisTemplate.boundZSetOps("pro:hot").add("苹果",0.99);
Set<ZSetOperations.TypedTuple> set = redisTemplate.boundZSetOps("pro:hot").rangeWithScores(0l, -1l);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
ZSetOperations.TypedTuple next = (ZSetOperations.TypedTuple)iterator.next();
System.out.println(next.getScore());
System.out.println(next.getValue());
}
}
}
九:Redis应用
短信登录功能
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单登录</title>
<style>
html,
body {
height: 100%;
overflow: hidden;
font-family: '微软雅黑';
}
body {
margin: 0;
padding: 0;
/* background-color: #F7F7F7; */
background: url('./img/wallpaper.jpg') no-repeat center /100% 100%;
display: flex;
justify-content: center;
align-items: center;
}
ul {
margin: 0;
padding: 50px;
padding-top: 0px;
list-style: none;
}
.register {
width: 800px;
background-color: #F9F9F9;
border: 1px solid #CCC;
border-radius: 5px;
}
li {
display: flex;
margin: 20px 0;
}
label,
input {
display: block;
float: left;
height: 46px;
font-size: 24px;
box-sizing: border-box;
color: #333;
}
label {
width: 200px;
line-height: 46px;
margin-right: 30px;
text-align: right;
}
input {
width: 320px;
padding: 8px;
line-height: 1;
outline: none;
position: relative;
}
input.code {
width: 120px;
}
input.verify {
width: 190px;
margin-left: 10px;
}
input.disabled {
background-color: #CCC !important;
cursor: not-allowed !important;
}
input[type=button] {
border: none;
color: #FFF;
background-color: #E64145;
border-radius: 4px;
cursor: pointer;
}
.tips {
width: 100%;
height: 40px;
text-align: center;
}
.tips p {
min-width: 300px;
max-width: 400px;
line-height: 40px;
margin: 0 auto;
color: #FFF;
display: none;
background-color: #C91623;
}
.submit:disabled {
background-color: gray;
cursor: not-allowed;
}
span {
line-height: 46px;
padding-left: 20px;
font-size: 20px;
color: yellowgreen;
text-shadow: 0 0 20px yellowgreen;
}
</style>
</head>
<body style="background-color: cornflowerblue;">
<div class="register">
<div class="tips">
<p>手机号不能为空</p>
</div>
<form id="ajaxForm">
<ul>
<li>
<label for="">验证手机</label>
<input type="text" name="mobile" class="mobile">
</li>
<li>
<label for="">短信验证码</label>
<input type="text" name="code" class="code">
<input type="button" value="获取验证码" class="verify">
</li>
<li>
<label for=""></label>
<input type="button" class="submit" value="立即登录">
</li>
</ul>
</form>
</div>
<!-- 提示信息 -->
</body>
</html>
<!-- 导入jQuery -->
<script src="/js/jquery.js"></script>
<script>
$('.verify').click(function () {
var $phone = $('.mobile').val();//获取输入的电话号
if(!$phone){
$(".tips p").show() ;
return
}else{
$(".tips p").hide() ;
}
$(this).addClass("disabled");//点击获取验证码后,禁用该按钮,开始倒计时
var time = 60;//倒计时时间,自定义
var $this = $(this);//备份,定时器是异步的,此this非彼this
var timer = setInterval(function () {
time--;//开始倒计时
if (time == 0) {//当倒计时为0秒时,关闭定时器,更改按钮显示文本并设置为可以点击
clearInterval(timer);
$this.val('获取验证码');
$this.removeClass("disabled");
return;
}
$this.val('还剩' + time + "S");//显示剩余秒数
}, 1000);
$.get('/sms/send', {
mobile: $phone
},
function (data) {
if(!data.success){return;}
}, 'jsonp');
});
</script>
wfx_util
生成验证码工具类
要求:新建wfx-util模块
这个模块需要依赖阿里大于的jar包
import java.util.Random;
public class VerifyCodeUtil {
public static final String VERIFY_CODES = "2345678";
public static String generateVerifyCode(int verifySize, String sources) {
if (sources == null || sources.length() == 0) {
sources = VERIFY_CODES;
}
int codesLen = sources.length();
Random rand = new Random(System.currentTimeMillis());
StringBuilder verifyCode = new StringBuilder(verifySize);
for (int i = 0; i < verifySize; i++) {
verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
}
return verifyCode.toString();
}
public static void main(String[] args) {
System.out.println(VerifyCodeUtil.generateVerifyCode(4, "asdfsxf12312335432wqwe"));
;
}
}
阿里大于:jar包
mvn install:install-file -Dfile=taobao-sdk-java-auto_1455552377940-20160607.jar -DgroupId=com.alimama -DartifactId=sms -Dversion=1.0 -Dpackaging=jar
发短信工具方法
package com.wfx.util;
import com.taobao.api.ApiException;
import com.taobao.api.DefaultTaobaoClient;
import com.taobao.api.request.AlibabaAliqinFcSmsNumSendRequest;
import com.taobao.api.response.AlibabaAliqinFcSmsNumSendResponse;
public class SendMessageUtil {
public static final String url = "http://gw.api.taobao.com/router/rest";
public static final String appkey = "23473071";
public static final String secret = "951efdcc9540d2c0c1646ed6d74892e6";
public static void sendMsg(String phone ,String content){
DefaultTaobaoClient client = new DefaultTaobaoClient(url, appkey, secret);
AlibabaAliqinFcSmsNumSendRequest req = new AlibabaAliqinFcSmsNumSendRequest();
req.setSmsType("normal");
req.setSmsFreeSignName("问道学院");
req.setSmsParamString("{\"code\":\""+content+"\"}");
req.setRecNum(phone);
req.setSmsTemplateCode("SMS_127820118");
try {
AlibabaAliqinFcSmsNumSendResponse rsp = (AlibabaAliqinFcSmsNumSendResponse) client.execute(req);
System.out.println(rsp.isSuccess());
} catch (ApiException arg10) {
arg10.printStackTrace();
}
}
public static void main(String[] args) {
SendMessageUtil.sendMsg("13147174932","top12123123123");
}
}
wfx_service依赖wfx_util
wfx_service 依赖spring-data-redis
<!--spring-data-redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<!--导入wfx-util-->
<dependency>
<groupId>com.wfx</groupId>
<artifactId>wfx_util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
功能一:发短信
前端
//发送验证码
$.get('/user/sendCode?phone='+$phone,function (res) {
alert(res.msg);
})
dao
@Select("select account from user_info where account=#{account}")
public UserInfo findByAccount(String account);
service
//发送短信
@Autowired
private RedisTemplate redisTemplate;
@Override
public ResultVO sendCode(String phone) {
//根据手机号码获取用户信息
UserInfo byAccount = userInfoDao.findByAccount(phone);
if (byAccount == null) {
return new ResultVO(false,"手机号码不存在");
}else{
//随机产生4位数的验证码
String code = VerifyCodeUtil.generateVerifyCode(4, "abcdqwertyuiop8912345672wqwe");
//发送短信
boolean success = SendMessageUtil.sendMsg(phone, code);
if(success){
//发送成功,将code保存到redis中
String key = phone;
System.out.println(key);
redisTemplate.boundValueOps(key).set(code);
redisTemplate.expire(key,60, TimeUnit.HOURS);
System.out.println(redisTemplate.boundValueOps(key).get());
return new ResultVO(true,"发送成功");
}else{
return new ResultVO(false,"短信发送失败");
}
}
}
controller
@RequestMapping("/sendCode")
@ResponseBody
public ResultVO sendCode(String phone){
return iuserInfoService.sendCode(phone);
}
功能二:验证短信
前端
//登录按钮
$("#loginBtn").click(function () {
$.get("/user/login?phone="+$('.mobile').val()+"&code="+$("#ucode").val(),function (res) {
alert(res.msg);
})
})
service
//校验短信
@Override
public ResultVO veryfiy(String phone, String code) {
String redisCode = (String) redisTemplate.boundValueOps(phone).get();
if (redisCode == null) {
return new ResultVO(false,"短信过期,请重发!!");
}
if(redisCode.equals(code)){
return new ResultVO(true,"success");
}else{
return new ResultVO(false,"您输入的验证码不匹对");
}
}
controller
@RequestMapping("/login")
@ResponseBody
public String login(String phone,String code,HttpSession session){
ResultVO veryfiy = iuserInfoService.veryfiy(phone, code);
if(veryfiy.isSuccess()){
//todo:根据phone获取用户的信息
UserInfo userByAccount = null;
session.setAttribute("user",userByAccount);
return "redirect:/index";
}else{//失败
return "redirect:/user/tologin";
}
}
十:Redis持久化
Redis持久化方案2种
RDB(Redis Databases):内存中的数据集快照写入磁盘,也就是 Snapshot 快照,恢复时是将快照文件直接读到内存里。
AOF(Append Of File):以日志的形式记录每个写操作
RDB:
RDB持久化方案的特点
Redis会单独fork一个子进程进行持久化工作,该子进程先将数据写入一个临时文件,等待持久化完毕,再将临时文件覆盖替换上此持久化好的文件,整个过程主进程是不会进行任何的IO操作,确保了极高的性能,而且当进行大规模数据恢复的时候RDB性能也非常高.
但是RDB有缺点,没法保证数据不丢失
rdb文件名与目录
dbfilename dump.rdb 可以通过该配置修改文件名
文件保存的路径
dir ./ #哪里开启的redis-server 就在哪里
触发方式
自动触发(满足条件)
save 30 5
手动触发:
手动触发Redis进行RDB持久化的命令有两种:
1、save
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。
显然该命令对于内存比较大的实例会造成长时间阻塞,这是致命的缺陷,为了解决此问题,Redis提供了第二种方式。
2、bgsave
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
3.正常关闭redis-server(shutdown,手动)
RDB的优缺点
优点:恢复数据快、节省磁盘空间
缺点:如果数据量比较大的情况会影响性能、数据持久化得不到很高的保证
AOF
AOF持久化方案的特点
将Redis所有的写操作记录下来(读操作不记录),以文件追加的方式存放起来,当Redis重启恢复数据时,将操作日志从头到尾执行一遍,恢复数据
AOF文件名与目录
appendfilename "appendonly.aof"
dir ./ #哪里开启的redis-server 就在哪里
触发方式
#默认关闭
appendonly no
appendfsync:aof持久化策略的配置;
no,write后不会有fsync调用,由操作系统自动调度刷磁盘,性能是最好的。
always表示每次写入都执行fsync,以保证数据同步到磁盘,效率很低;
everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。通常选择 everysec ,兼顾安全性和效率。
AOF优缺点
优点:丢失数据概率更低
缺点:恢复数据慢、比RDB占用磁盘空间、频率高对性能有一定影响
持久化方案的选择
如果对数据安全性要求很高,采用aof的持久化方案
如果redis只是做缓存、对数据的安全性要求不是很高,那么采用默认的rdb持久化方案即可
11:Redis主从复制
为什么要读写分离
答:为了解决redis的读写压力(不是解决存储压力),从而进行分工
master:主节点负责写,同时可以负责读
slave:从节点,只能读不能写
为什么要主从同步
答:为了保证数据一致性
为什么是一主多从
答:为了保证数据一致性
主从配置(差异配置)
修改配置项
pid文件名
port端口
rdb文件名
更换aof名字
7001节点配置 :redis7001.conf
include /export/server/redis/redis.conf
port 7001
pidfile /var/run/redis7001.pid
dbfilename redis7001.rdb
appendfilename "appendonly7001.aof"
7002节点配置 :redis7002.conf
include /export/server/redis/redis.conf
port 7002
pidfile /var/run/redis7002.pid
dbfilename redis7002.rdb
appendfilename "appendonly7002.aof"
slaveof 192.168.234.131 7001
masterauth 123456
7003节点配置 :redis7003.conf
include /export/server/redis/redis.conf
port 7003
pidfile /var/run/redis7003.pid
dbfilename redis7003.rdb
appendfilename "appendonly7003.aof"
slaveof 192.168.234.131 7001
masterauth 123456
启动集群
[root@zhuxm01 redis]# redis-server redis7001.conf
[root@zhuxm01 redis]# redis-server redis7002.conf
[root@zhuxm01 redis]# redis-server redis7003.conf
[root@zhuxm01 redis]# ps -ef|grep redis
root 13195 1 0 10:49 ? 00:00:00 redis-server 192.168.234.131:7001
root 13204 1 0 10:49 ? 00:00:00 redis-server 192.168.234.131:7002
root 13213 1 0 10:49 ? 00:00:00 redis-server 192.168.234.131:7003
12:哨兵模式sentinel
哨兵模式介绍
哨兵主要职责:
1:监控master状态,
2:master宕机,立马选举从slaves节点中选出新master
哨兵推荐搭建3个,保证哨兵的高可用
哨兵模式的redis集群(1主2从3哨兵)
哨兵模式搭建
先启动redis集群
三个哨兵就新建3个分别sentinel01.conf 、sentinel02.conf、sentinel03.conf,注意区分端口
bind 0.0.0.0
#哨兵的端口
port 26379
#mymaster自定义
sentinel monitor mymaster 192.168.234.122 7001 1
sentinel auth-pass mymaster 123456
启动哨兵
redis-sentinel sentinel01.conf
redis-sentinel sentinel02.conf
redis-sentinel sentinel03.conf
spring-data-redis连接哨兵
<?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.properties" ignore-unresolvable="true" />
<bean id="pool" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}"/>
<property name="maxIdle" value="${redis.pool.maxIdle}"/>
</bean>
<bean id="redisSentinelConfiguration"
class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="mymaster">
</property>
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.234.122" />
<constructor-arg name="port" value="26379" />
</bean>
</set>
</property>
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:password="123456" >
<constructor-arg name="sentinelConfig" ref="redisSentinelConfiguration"/>
<constructor-arg name="poolConfig" ref="pool"/>
</bean>
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<constructor-arg name="connectionFactory" ref="jedisConnectionFactory"/>
</bean>
</beans>
13:Redis集群
redis的高可用两种方式:1:redis哨兵模式,2:集群模式
redis哨兵模式的弊端:
1:redis的写操作会遇到瓶颈
2:内存存储的瓶颈
集群架构
hash slot只分配给master,不会分配给slave
集群搭建
说明:
1:伪集群,一台服务器,启动6个redis服务、通过端口区分(7001~7006)
2:真集群:6个节点或者3个节点
第一步:从源码包复制redis.conf到指定目录
[root@zhuxm01 server]# cp redis-6.0.8/redis.conf /export/server/redis/
第二步:分别配置每个redis的配置文件,详细配置如下,注意区分端口
include /export/server/redis/redis.conf
port 7001
#redis的进程文件
pidfile /var/run/redis7001.pid
#rdb文件名
dbfilename redis7001.rdb
#aof文件名
appendfilename "appendonly7001.aof"
# 生成的node文件
cluster-config-file nodes7001.conf
daemonize yes
#aof、rdb文件存储目录
dir /export/server/redis/data/
# 集群
cluster-enabled yes
bind 192.168.234.122
#如果redis设置密码,加上如下配置
requirepass "123456"
masterauth "123456"
第三步:启动每个redis服务
[root@zhuxm01 redis]# redis-server /export/server/redis/redis7001.conf
[root@zhuxm01 redis]# redis-server /export/server/redis/redis7002.conf
[root@zhuxm01 redis]# redis-server /export/server/redis/redis7003.conf
[root@zhuxm01 redis]# redis-server /export/server/redis/redis7004.conf
[root@zhuxm01 redis]# redis-server /export/server/redis/redis7005.conf
[root@zhuxm01 redis]# redis-server /export/server/redis/redis7006.conf
[root@zhuxm01 redis]# ps -ef|grep redis
root 11186 1 0 09:51 ? 00:00:00 redis-server 192.168.234.131:7001 [cluster]
root 11195 1 0 09:51 ? 00:00:00 redis-server 192.168.234.131:7002 [cluster]
root 11203 1 0 09:51 ? 00:00:00 redis-server 192.168.234.131:7003 [cluster]
root 11212 1 0 09:51 ? 00:00:00 redis-server 192.168.234.131:7004 [cluster]
root 11220 1 0 09:51 ? 00:00:00 redis-server 192.168.234.131:7005 [cluster]
root 11229 1 0 09:51 ? 00:00:00 redis-server 192.168.234.131:7006 [cluster]
root 11240 7691 0 09:52 pts/0 00:00:00 grep --color=auto redis
[root@zhuxm01 redis]#
第四步:创建集群
redis-cli --cluster create 192.168.234.131:7001 \
192.168.234.131:7002 \
192.168.234.131:7003 \
192.168.234.131:7004 \
192.168.234.131:7005 \
192.168.234.131:7006 \
--cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.234.131:7005 to 192.168.234.131:7001
Adding replica 192.168.234.131:7006 to 192.168.234.131:7002
Adding replica 192.168.234.131:7004 to 192.168.234.131:7003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 7e88abdfa256140ab11dc3c2173b00c5868a0479 192.168.234.131:7001
slots:[0-5460] (5461 slots) master
M: e5845e3494d58b5400fa21d4b3c34ccafce43024 192.168.234.131:7002
slots:[5461-10922] (5462 slots) master
M: 5dbec321866665aceb8ae0c6791e2ded39464368 192.168.234.131:7003
slots:[10923-16383] (5461 slots) master
S: 8ad824d12b9f1a16a75e227266e9940aa8610422 192.168.234.131:7004
replicates 5dbec321866665aceb8ae0c6791e2ded39464368
S: 8ca84b32509e178181585206eaa736c6323dbd3a 192.168.234.131:7005
replicates 7e88abdfa256140ab11dc3c2173b00c5868a0479
S: 7a518a28534593c44694215e3a83b3311704e4d4 192.168.234.131:7006
replicates e5845e3494d58b5400fa21d4b3c34ccafce43024
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
第五步:连接集群
redis-cli -h 192.168.234.122 -p 7002 -c
springdata-redis连接redis集群
pom.xml
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.0.12.RELEASE</version>
</dependency>
applicationContext-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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 配置Cluster -->
<bean id="redisClusterConfiguration"
class="org.springframework.data.redis.connection.RedisClusterConfiguration">
<!-- 节点配置 -->
<property name="clusterNodes">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.234.122"></constructor-arg>
<constructor-arg name="port" value="7001"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.234.122"></constructor-arg>
<constructor-arg name="port" value="7002"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.234.122"></constructor-arg>
<constructor-arg name="port" value="7003"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.234.122"></constructor-arg>
<constructor-arg name="port" value="7004"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.234.122"></constructor-arg>
<constructor-arg name="port" value="7005"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.234.122"></constructor-arg>
<constructor-arg name="port" value="7006"></constructor-arg>
</bean>
</set>
</property>
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="100" />
<property name="maxTotal" value="600" />
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg ref="redisClusterConfiguration" />
<constructor-arg ref="jedisPoolConfig" />
</bean>
<!-- redis 访问的模版 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
</beans>
package com.wfx.test;
import com.wfx.dao.SysRoleDao;
import com.wfx.entity.SysRole;
import com.wfx.service.IsysRoleService;
import com.wfx.vo.TreeNodeVO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:applicationContext-*.xml")
public class TestSysRoleServie {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testString (){
redisTemplate.boundValueOps("demo").set("1234");
redisTemplate.expire("demo",30 , TimeUnit.SECONDS);
System.out.println(redisTemplate.boundValueOps("demo").get());
}
@Test
public void testList (){
redisTemplate.boundListOps("names").leftPush("张飞");
redisTemplate.boundListOps("names").leftPush("刘备");
redisTemplate.boundListOps("names").leftPush("关羽");
redisTemplate.boundListOps("names").leftPush("曹操");
System.out.println(redisTemplate.boundListOps("names").rightPop());
System.out.println(redisTemplate.boundListOps("names").rightPop());
System.out.println(redisTemplate.boundListOps("names").rightPop());
System.out.println(redisTemplate.boundListOps("names").rightPop());
}
@Test
public void testSet (){
redisTemplate.boundSetOps("ips").add("123","123","345","678");
System.out.println(redisTemplate.boundSetOps("ips").members());
System.out.println(redisTemplate.boundSetOps("ips").size());
}
@Test
public void testHash (){
redisTemplate.boundHashOps("person").put("name","jack");
redisTemplate.boundHashOps("person").put("age","12");
System.out.println(redisTemplate.boundHashOps("person").get("name"));
System.out.println(redisTemplate.boundHashOps("person").size());
}
}
14:Redis缓存问题
缓存击穿
请求的某个热点数据、数据库存在,但是redis中不存在(redis第一次启动、热点数据过期了)、高并发访问热点数据,导致数据库造成对DB的周期性压力(极端情况下导致数据库宕机)
jmeter
压测工具
第一步:创建线程组
第二步:在线程组上创建http请求
第三步:在http请求上右键,添加请求结果的监听
缓存击穿解决办法:
加锁(分布式锁),两次判断
核心逻辑
@RequestMapping("/detail/{goodsId}")
public Goods detail(@PathVariable String goodsId){
//从redis中获取该热门商品信息
try {
String goodsJsonStr = (String) stringRedisTemplate.boundHashOps("goods").get("goods"+goodsId);
if (goodsJsonStr == null) {//redis中不存在
String goodsJsonStrAgain = null;
Goods goods = null;
synchronized (this) {
//再从redis中获取一次
goodsJsonStrAgain = (String) stringRedisTemplate.boundHashOps("goods").get("goods"+goodsId);
if (goodsJsonStrAgain == null) {
System.out.println("load data from db!!");
//从数据库中获取的热点商品信息
goods = new Goods(goodsId,"华为");
//回写到redis
stringRedisTemplate.boundHashOps("goods").put("goods"+goodsId,jsonMap.writeValueAsString(goods));
}else{
goods = jsonMap.readValue(goodsJsonStrAgain, Goods.class);
}
return goods;
}
}else{
Goods goods = jsonMap.readValue(goodsJsonStr, Goods.class);
return goods;
}
} catch (JsonProcessingException e) {
e.printStackTrace();
return new Goods();
}
}
缓存穿透
请求的某个热点数据、数据库并不存在,缓存也无法命中,请求都会到数据源,从而可能压垮数据源
方案1:缓存空值
数据库中获取不到,就插入{},注意设置过期时间
//从数据库中获取的热点商品信息
goods = new Goods(goodsId,"华为");
if (goods == null) {//从数据库中获取不到对应的商品信息
goods = new Goods();
//回写到redis,同时设置过期时间(<设计的正常的过期时间)
stringRedisTemplate.boundHashOps("goods").put("goods"+goodsId,jsonMap.writeValueAsString(goods));
stringRedisTemplate.expire("goods"+goodsId,60, TimeUnit.SECONDS);
}else{
stringRedisTemplate.boundHashOps("goods").put("goods"+goodsId,jsonMap.writeValueAsString(goods));
stringRedisTemplate.expire("goods"+goodsId,120, TimeUnit.SECONDS);
}
方案2:布隆过滤
提前将数据库中所有的商品id放入布隆过滤器,当添加商品和删除商品(维护布隆过滤器)
核心逻辑
if(布隆过滤器中存在){//从数据库中获取
}else{//垃圾数据
//直接返回空数据
}
缓存雪崩
当redis服务器重启或者大量缓存集中在某一个时间段失效,即使不是高并发,访问量大,导致数据库访问压力大
解决方案
让过期时间平均分布 过期时间=预设置的过期时间+随机数