Redis


Redis

下载地址 - https://github.com/tporadowski/redis/releases

Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value内存[但是也是可以进行持久化操作的] 数据库[非关系型数据库 - nosql]。分布式的数据库

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。

配置文件

redis.windows.conf - linux中的文件就是redis.conf文件

配置redis的密码

requirepass success

redis的数据库的编号

# 设置数据库的编号.默认的数据库的编号是0,范围是从[0.15]
# select 数据库编号
# 缺点 - 可读性不是特别高.需要程序员自己记录号数据是在哪个数据库编号中的.因为不同的编号的数据库的数据是隔离的.
databases 16

启动redis的服务器端

进入到/Users/admin/Downloads/Redis-x64-5.0.10 - redis解压目录

不推荐的方式 , 导致redis.windows.conf配置文件没有生效.选用的默认的配置文件.

redis-server.exe

读取redis.windows.conf配置文件

redis-server.exe redis.windows.conf

端口号 - 6379

启动redis客户端

进入redis解压目录

redis-cli.exe

远程连接

admindeMacBook-Pro:~ admin$ redis-cli -h localhost -p 6379 -a success

本地连接

redis-cli.exe -a success

命令

  1. 查看所有的key

    keys *
    
  1. 清空数据库

    flushdb
    
  1. 删除key

    del key
    

Redis数据类型

  • String: 字符串

    # 存储
    set username admin
    # 根据key来获取
    get username
    
  • Hash: 散列

    hash 特别适合用于存储对象

    hmset student id 200 name "tom" age 23
    
    遍历所有的key和value
    hgetall student
      
    根据key来获取
    hget student name
    
  • List: 列表 - 有序允许重复的

    lpush course java
    lpush course python
    lpush course redis
      
    lrange course 0 2
      
    llen course
      
    lindex course 1
    
  • Set: 集合 - 无序不可重复

    sadd name admin
    sadd name tom
    sadd name admin
      
    smembers name
    1) "admin"
    2) "tom"
    
  • Sorted Set: 有序集合,但是仍然是不可重复的

Springboot整合redis

  1. 导入依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. application-dev.yml

    spring:
        redis:
        host: localhost
        port: 6379
        database: 1
        password: success
    

测试RedisTemplate - API

操作String

 /**
     * 操作String - ValueOperations
     */
@Test
public void testString(){
  ValueOperations vop = redisTemplate.opsForValue();
  //放入数据
  //vop.set("username","admin");

  //根据key来获取
  String value = (String) vop.get("username");
  System.out.println(value);
}

/**
     * 数据放在redis - 设置过期时间的
     * 测试一下设置过期时间
     */
@Test
public void testStringExpired(){
  ValueOperations vop = redisTemplate.opsForValue();
  //放入数据
  vop.set("username","admin",5, TimeUnit.SECONDS);
}

操作list列表

@Test
public void testSetList(){
  ListOperations<String,List<Phone>> lop = redisTemplate.opsForList();

  List<Phone> phoneList = phoneMapper.findAll();

  lop.leftPush("phones",phoneList);
}

@Test
public void testGetList(){
  ListOperations<String,List<Phone>> lop = redisTemplate.opsForList();

  //获取redis的个数
  System.out.println(lop.size("phones"));//1

  List<List<Phone>> list = lop.range("phones",0,1);

  List<Phone> phoneList = list.get(0);

  for (Phone phone : phoneList) {
    System.out.println(phone);
  }
}

改变redis序列化的方式

package tech.aistar.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 本类用来演示: 修改redis默认的key,value的序列化方式
 *  序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
 *      * @param redisConnectionFactory
 *
 * @author: success
 */
@Configuration
public class RedisConfig {
    /**
     * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

应用场景

验证码放入到session空间 - 缺点

  1. session空间无法设置过期时间
  2. session空间是服务器内部的空间 - 内存 - 比较昂贵

验证码放入到redis中.

 redisTemplate.opsForValue().set("code",String.valueOf(n),20, TimeUnit.SECONDS);
 String sesCode = (String) redisTemplate.opsForValue().get("code");

传统的模式 - 登录成功之后的用户放入到session中

session.setAttribute("user",u);

用户服务 - 部署在服务器A

购物车服务 - 部署在服务器B

系统架构的演变

单体架构

单机就是所有的业务全部写在一个项目中,部署服务到一台服务器上,所有的请求业务都由这台服务器处理。显然,当业务增长到一定程度的时候,服务器的硬件会无法满足业务需求。自然而然地想到一个程序不行就部署多个喽,

缺点:

  1. 所有的请求的压力都由一台服务器来处理
  2. 因为所有的业务都在一个项目中,一旦其中某个业务崩溃了,会导致整个项目运行失败.
  3. 如果后期仅仅只是某个业务中的一小块需要改动,那么是需要将整个项目重新打包部署.

微服务架构

Springboot框架就是专门用来进行微服务开发的 - 微服务的”脚手架” - 快速搭建微服务的项目工程.

  1. 微服务的应用不一定是分散在多个服务器上也可以是同一个服务
  2. 微服务 - 架构的风格,将一个大的项目模块,拆分成若干个子的业务模块.并且每个子的业务模块都是一个独立的工程,可以进行单独的部署.好处 - 解耦合了各个模块.

分布式架构

将一个大的项目模块,拆分成若干个子的业务模块,但是不同的业务模块分散部署在不同的服务器上的 - 为了分散服务器的压力.

“着重于部署的方式”

“分布式一定是微服务的” - “微服务不一定是分布式”

“不同的人干不同的事情” - “饭店 - 厨师,洗碗工,买菜,洗菜,上菜 - 各个子业务模块”

集群架构

集群模式是不同服务器部署同一套服务对外访问,实现服务的负载均衡。区别集群的方式是根据部署多台服务器业务是否相同

“好多人干同一件事情”

“集群是为了高可用,高性能,高并发 - 三高”

“分布式的每个节点都可以进行集群”

相同的服务部署在不同的服务器上 - 不同的服务器上部署相同的服务.

电商系统 - 用户模块,订单模块,支付模块 - “拆的过程”

比如 - 订单服务 - 部署在服务器A,支付模块部署在服务器B,用户模块部署在服务器C - “分布式部署的过程”

为了防止哪天服务器C哪天不可用,会容易发生雪崩效应.因为服务器A中的订单服务依赖于服务器C的用户服务的.

可以对分布式上的每个节点进行集群操作.可以将用户模块同时部署在服务器C,服务器B,服务器N上…

一旦进行了集群,后期可以再配合一些负载均衡的策略.比如

订单服务 - 服务器A和服务器C上都存在.

原则 - 先微服务架构,然后进行分布式部署,最后再做集群.

客户端请求 -> 网关[负载均衡] -> 服务器A/服务器C

常见的集群

  1. 分布式的每个节点[微服务]可以进行集群
  2. redis做集群
  3. mysql做集群
  4. es集群

SOA架构

Springcloud

jjwt

Java Json Web Token[令牌]

JJWT是一个提供端到端的JWT创建和验证的Java库

三部分

  1. Header(头部) —— base64编码的Json字符串

    {
     'typ': 'JWT',
     'alg': 'HS256'
    }
    ewogJ3R5cCc6ICdKV1QnLAogJ2FsZyc6ICdIUzI1NicKfQ==
    
  2. Payload(载荷) —— base64编码的Json字符串 - 真正的有效的数据.比如登录的信息.

    {
     "sub": "1234567890",
     "name": "John Doe",
     "admin": true
    }
    
    ewogInN1YiI6ICIxMjM0NTY3ODkwIiwKICJuYW1lIjogIkpvaG4gRG9lIiwKICJhZG1pbiI6IHRydWUKfQ==
    
  3. Signature(签名)—— 使用指定算法,通过Header和Payload加盐计算的字符串

    JWT的第三部分是一个签证信息,这个签证信息由三部分组成:Base64编码后的header、Base64编码后的payload和一个自定义的私人密钥secret:
    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) 
         
    不可逆的
    HMACSHA256(ewogJ3R5cCc6ICdKV1QnLAogJ2FsZyc6ICdIUzI1NicKfQ==.ewogInN1YiI6ICIxMjM0NTY3ODkwIiwKICJuYW1lIjogIkpvaG4gRG9lIiwKICJhZG1pbiI6IHRydWUKfQ==,盐值加密)
       
    比如 - 设置的盐 - success
         
    签证 - token值 - 2602287a3aec62e36a3bc1a474f8c028270822f50800841a2d9135eb1a876cd2
    

具体操作

  1. 导入依赖

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    
  2. 工具类

    package tech.aistar.util;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import tech.aistar.model.entity.Users;
    
    import java.util.Date;
    
    public class JWTUtils {
        /**
         * 设置过期时间,一周
         */
        private static final long EXPIRE = 7 * 24 * 60 * 60 * 1000;
        //private static final long EXPIRE = 2000;
    
        /**
         * 设置秘钥
         */
        private static final String SECRET = "success";//盐值
    
        /**
         * 令牌前缀
         */
        private static final String TOKEN_PREFIX = "aistar";
    
        /**
         * 令牌主题
         */
        private static final String SUBJECT = "admin";
    
   /**
    * 根据用户信息来生成令牌(token)
    * @param user
    * @return
    */
   public static String geneJsonWebToken(Users user){
       //头部省略不写了

// {
// “typ”: “JJWT”
// “alg”: “HS256”
// }
String token = Jwts.builder().setSubject(SUBJECT)

               .claim("id",user.getId())//载荷 - 有效信息 - 登录用户的有效信息,是为了校验用户是否登录的信息
               .claim("name",user.getUsername())

               .setIssuedAt(new Date())//颁布时间
               //过期时间
               .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))

               //生成签名 - token值
               .signWith(SignatureAlgorithm.HS256,SECRET).compact();//签名

       token = TOKEN_PREFIX + token;
       return token;
   }

   /**
    * 校验token的方法
    * @param token
    * @return
    */
   public static Claims checkJWT(String token){
       try{
           final Claims claims = Jwts.parser().setSigningKey(SECRET)
                   .parseClaimsJws(token.replace(TOKEN_PREFIX,"")).getBody();

           return claims;

       }catch (Exception e){
           return null;
       }
   }

}


测试

package tech.aistar.jjwt;

import io.jsonwebtoken.Claims;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import tech.aistar.mapper.UsersMapper;
import tech.aistar.model.entity.Users;
import tech.aistar.util.JWTUtils;

/**
 * 本类用来演示:
 *
 * @author: success
 * @date: 2021/10/8 1:40 下午
 */
@SpringBootTest
public class TestJJwt {

    @Autowired
    private UsersMapper usersMapper;

    @Test
    public void createToken(){
        Users users = usersMapper.findByEmail("849962874@qq.com");

        System.out.println(users);

        //令牌
        String token = JWTUtils.geneJsonWebToken(users);
        /*
        * 如果一旦登录成功,在服务器端就会生成token,并且将这个token返回给客户端
        * 客户端需要将这个token放在h5的本地存储中.
        * 后续的请求需要带上token值[载荷信息].
        *
         * aistareyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlkIjoxLCJuYW1lIjoiYWRtaW4iLCJlbWFpbCI6Ijg0OTk2Mjg3NEBxcS5jb20iLCJpYXQiOjE2MzM2NzE3ODYsImV4cCI6MTYzNDI3NjU4Nn0.kvk1CKSpHatz4CQgkXIaYLOOVGLe-3vg-h3je6CYsyk

         */
        System.out.println(token);
    }

    @Test
    public void testParseToken(){
        //server接收到客户端的请求中的token - 首先要去除
        String token = "aistareyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlkIjoxLCJuYW1lIjoiYWRtaW4iLCJlbWFpbCI6Ijg0OTk2Mjg3NEBxcS5jb20iLCJpYXQiOjE2MzM2NzIzMTEsImV4cCI6MTYzNDI3NzExMX0.WULibokcNM5U5cDW07eQsj8lyVj_qf7FImKlhAFJEtk";
        Claims c = JWTUtils.checkJWT(token);
        System.out.println("c:"+c);

        //获取载荷中的单个key
        String email = (String) c.get("email");
        System.out.println(email);
    }

}

流程

  1. 登录成功代码片段

    a. 在server端生成token,并且返回给client端 - 保存在本地存储中.

    并且在

    //生成一个token
    String token = JWTUtils.geneJsonWebToken(u);
    
    return new Result("200","邮箱发送成功",token);
    
  2. 后续的请求,比如点击我的购物车,我的订单,我的个人中心都是需要校验token

    思考在哪里进行校验呢? - 拦截器

fastjson

将java对象转换成json字符串.

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.78</version>
</dependency>
package tech.aistar.fastjson;

import com.alibaba.fastjson.JSON;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import tech.aistar.mapper.UsersMapper;
import tech.aistar.model.entity.Users;

/**
 * 本类用来演示:
 *
 * @author: success
 * @date: 2021/10/8 2:53 下午
 */
@SpringBootTest
public class TestFastJSon {

    @Autowired
    private UsersMapper usersMapper;

    @Test
    public void testJavaToJsonString(){
        Users users = usersMapper.findByEmail("849962874@qq.com");

        String jsonStr = JSON.toJSONString(users);
        System.out.println(jsonStr);
    }
}
  //设置文档的输出类型
response.setContentType("application/json;charset=utf-8");
//获取文档输出流
PrintWriter out = response.getWriter();
String jsonResult = JSON.toJSONString(new Result("507","非法的token"));
out.println(jsonResult);

密码加密

  1. 导入依赖 - 权限认证的框架 - SpringSecurity,还有shiro

     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  1. 单侧

    package tech.aistar.pwd;
       
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
       
    /**
     * 本类用来演示: 加密技术
     *
     * @author: success
     * @date: 2021/10/8 3:18 下午
     */
    @SpringBootTest
    public class PasswordTest {
        //思考 - 为什么RedisTemplate可以直接使用
       
        //springboot底层的自动化配置原理有关系.
       
        @Autowired
        private BCryptPasswordEncoder encoder;
       
        @Test
        public void testEncoder(){
            //页面上输入的数据.
            String pwd = "admin123";
       
            //加密之后保存到db中
            //db的password的字段的长度修改长一点
       
            //不可逆 - 底层随机盐
            String encoderPwd = encoder.encode(pwd);
       
            //密码需要进行密文的处理的
            //$2a$10$XHCcsh3SWU/gjOzht17CPOX2W4pGFiBogyUCfUVBGyIfuJRY6UX5m
            System.out.println(encoderPwd);
        }
       
        @Test
        public void testGet(){
            //直接进行判断
            boolean flag = encoder.matches("admin123","$2a$10$XHCcsh3SWU/gjOzht17CPOX2W4pGFiBogyUCfUVBGyIfuJRY6UX5m");
            System.out.println(flag);
        }
    }
    
  1. 需要对拦截的请求处理一下 - 放行,否则会自动跳转默认的登录界面里面

    package tech.aistar.config;
       
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
       
    /**
     * 本类用来演示:
     *
     * @author: success
     * @date: 2021/10/8 3:17 下午
     */
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
       
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/**").permitAll()
                    .anyRequest().authenticated()
                    .and().csrf().disable();
        }
    }
    

redis+token如何防止表单重复提交

  1. /user/registerView - server端生成token[令牌] - 存储到redis中,并且将这个token存储到request作用域中.

  2. 转发到WEB-INF/register.jsp文件 - form表单

    <input type='hidden' value='${token}' name='token'>
    
  3. 点击注册按钮 - 都可以将这个隐藏域的token值一起发送给server - /user/register

  4. /user/register后端是可以拿到redis中的token,然后和form表单提交过来的token值进行比较.

    如果一样,则直接顺利执行.执行注册操作之后,将redis中的token删除.

  5. 再次发送,隐藏域中的token会再次和redis中的token进行比较[已经被删除了] - 比较失败! - 重复提交了.

RabbitMQ

ActiveMQ:基于JMS,Apache

RocketMQ:(Rocket,火箭)阿里巴巴的产品,基于JMS,目前由Apache基于会维护

Kafka:分布式消息系统,亮点:吞吐量超级高,没秒中数十万的并发。

RabbitMQ:(Rabbit,兔子)由erlang语言开发,基于AMQP协议,在erlang语言特性的加持下,RabbitMQ稳定性要比其他的MQ产品好一些,而且erlang语言本身是面向高并发的编程的语言,所以RabbitMQ速度也非常快。且它基于AMQP协议,对分布式、微服务更友好。

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)

特点

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题实现高性能,高可用,可伸缩和最终一致性[架构]

使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。其使用场景为:异步处理,应用解耦,流量削锋和消息通讯四个场景。

安装

  1. Erlang - http://www.erlang.org/downloads
  2. RabbitMQ - https://github.com/rabbitmq/rabbitmq-server/releases
  1. 注意一下两者的版本,需要相互兼容.否则安装运行失败!

文章作者: 码农耕地人
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 码农耕地人 !
  目录