Redis笔记

一:为什么使用Redis

image-20200928144732388

二: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

image-20200928150936812

image-20200928151038244

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

image-20200928152525279

前台启动

image-20200928152611117

后台启动

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应用

短信登录功能

image-20200929164122599

<%@ 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哨兵)

image-20201005234502889

哨兵模式搭建

先启动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:内存存储的瓶颈


集群架构

image-20201009093335822

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"

image-20201009095058410

第三步:启动每个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

压测工具

第一步:创建线程组

image-20201009113159604

image-20201009113517143

第二步:在线程组上创建http请求

image-20201009113252906

image-20201009113821350

第三步:在http请求上右键,添加请求结果的监听

image-20201009133422064

缓存击穿解决办法:

加锁(分布式锁),两次判断

核心逻辑

@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服务器重启或者大量缓存集中在某一个时间段失效,即使不是高并发,访问量大,导致数据库访问压力大

解决方案

让过期时间平均分布 过期时间=预设置的过期时间+随机数

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值