BolunWu

BolunWu 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

BolunWu 发布了文章 · 11月13日

Redis分片

1.Redis分片机制

1.1为什么需要分片机制

如果需要存储海量的内存数据,如果只使用一台redis,无法保证redis工作效率.大量的时间都浪费在了寻址当中.所以需要一种机制,来满足这种需求.

采用分片机制实现:

image

1.2Redis分片搭建

1.2.1搭建注意事项

Redis服务启动需要依赖redis.conf的配置文件.如果需要准备3台redis则需要准备3个redis.conf的配置.
准备端口号
6379
6380
6381

1.2.2分片实现

image

修改端口号:间各自的端口号进行修改

image

启动3台redis服务器

image

校验服务器是否正常运行

1.2.3关于分片的注意事项

1.问题描述
当启动多台redis服务器之后,多台redis暂时没有必然的联系,各自都是独立的实体,可以数据的存储.
2.如果将分片通过程序的方式进行操作,要把3台redis当做一个整体,所以与上述的操作完全不同,不会出现一个key同时保存多个redis的现象.

image

1.3分片入门案例

 /**
     * 测试Redis分片机制
     * 思考: shards 如何确定应该存储到哪台redis中呢???
     */
    @Test
    public void testShards(){
        List<JedisShardInfo> shards = new ArrayList<>();
        shards.add(new JedisShardInfo("192.168.126.129",6379));
        shards.add(new JedisShardInfo("192.168.126.129",6380));
        shards.add(new JedisShardInfo("192.168.126.129",6381));
        //准备分片对象
        ShardedJedis shardedJedis = new ShardedJedis(shards);
        shardedJedis.set("shards","redis分片测试");
        System.out.println(shardedJedis.get("shards"));
    }

1.4一致性hash算法

1.4.1一致性hash算法介绍

一致性hash算法在1997年由麻省理工学院提出,是一种特殊的算法,目的是解决分布式缓存问题.在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 。

image

1.4.2特性1-平衡性

概念:平衡性是指hash的结果应该平均分配各个节点,这样从算法上解决了负载均衡问题.(大致平分)
问题描述:由于节点都是通过hash方式进行算计,所以可能出现如图中的现象,导致负载严重不平衡.

image

解决方法:引入虚拟节点

image

1.4.3特性2-单调性

特点:单调性是指在新增或者删除节点时,不影响系统正常运行.

image

1.4.4特性3-分散性

谚语:鸡蛋不要放到一个篮里.
分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储的数据.

1.5SpringBoot整合Redis分片

1.5.1编辑配置文件

# 配置redis单台服务器
redis.host=192.168.126.129
redis.port=6379

# 配置redis分片机制
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

1.5.2编辑配置类

@Configuration
@PropertySource("classpath:/properties/redis.properties")
public class JedisConfig {

    @Value("${redis.nodes}")
    private String nodes;  //node,node,node.....

    //配置redis分片机制
    @Bean
    public ShardedJedis shardedJedis(){
        nodes = nodes.trim();   //去除两边多余的空格
        List<JedisShardInfo> shards = new ArrayList<>();
        String[] nodeArray = nodes.split(",");
        for (String strNode : nodeArray){   //strNode = host:port
            String host = strNode.split(":")[0];
            int port = Integer.parseInt(strNode.split(":")[1]);
            JedisShardInfo info = new JedisShardInfo(host, port);
            shards.add(info);
        }
        return new ShardedJedis(shards);
    }
   }

1.5.3修改AOP注入项

image~~~~

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 11月13日

Redis总结

1.Redis介绍

redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis常见用法

1.Redis可以当做缓存使用
2.Redis可以当做数据库验证码使用
3.Redis可以当做消息中间件

1.1Redis安装

1)解压安装包
[root@localhost src]# tar -zxvf redis-5.0.4.tar.gz

image

2)安装Redis
说明:在Redis根目录执行命令
1.make
2.make install

image

1.2修改Redis配置文件

命令:展现行号:set nu

image

修改位置1:注释IP绑定

image

修改位置2:关闭保护模式

image

修改位置3:开启后台启动

image

1.3redis服务命令

1.启动命令:redis-server redis.conf
2.检索命令:ps-ef | grep redis
3.进入客户端:redis-cli -p 6379

4.关闭redis:kill-9 PID号 | redis-cli -p 6379 shutdown

image

2.Redis入门案例

2.1引入jar包文件

        <!--spring整合redis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>

2.2编辑测试API

package com.jt.test;

import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;

public class TestRedis {

    /**
     * 1.测试redis程序链接是否正常
     * 步骤:
     *      1.实例化jedis工具API对象(host:port)
     *      2.根据实例 操作redis  方法就是命令
     *
     * 关于链接不通的说明:
     *      1.检查Linux防火墙
     *      2.检查Redis配置文件修改项
     *          2.1 IP绑定
     *          2.2 保护模式
     *          2.3 后台启动
     *      3.检查redis启动方式  redis-server redis.conf
     *      4.检查IP 端口 及redis是否启动...
     *
     *      */
    @Test
    public void test01(){
        String host = "192.168.126.129";
        int port = 6379;
        Jedis jedis = new Jedis(host,port);
        jedis.set("cgb2006","好好学习 天天向上");
        System.out.println(jedis.get("cgb2006"));

        //2.练习是否存在key
        if(jedis.exists("cgb2006")){
            jedis.del("cgb2006");
        }else{
            jedis.set("cgb2006", "xxxx");
            jedis.expire("cgb2006", 100);
        }


    }
}

2.3Redis常见用法

2.3.1setex学习


/**
     * 2.需求:
     *      1.向redis中插入数据  k-v
     *      2.为key设定超时时间  60秒后失效.
     *      3.线程sleep 3秒
     *      4.获取key的剩余的存活时间.
     *
     *   问题描述: 数据一定会被删除吗??????
     *   问题说明: 如果使用redis 并且需要添加超时时间时 一般需要满足原子性要求.
     *   原子性:   操作时要么成功 要么失败.但是必须同时完成.
     */
    @Test
    public void test02() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.setex("宝可梦", 60, "小火龙 妙蛙种子");
        System.out.println(jedis.get("宝可梦"));

       /* Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.set("宝可梦", "小火龙 妙蛙种子");
        int a = 1/0;    //可能会出异常
        jedis.expire("宝可梦", 60);
        Thread.sleep(3000);
        System.out.println(jedis.ttl("宝可梦"));*/
    }

2.3.2setnx学习

/**
     * 3.需求如果发现key已经存在时 不修改数据.如果key不存在时才会修改数据.
     *
     */
    @Test
    public void test03() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        jedis.setnx("aaaa", "测试nx的方法");
        /*if(jedis.exists("aaa")){
            System.out.println("key已经存在 不做修改");
        }else {
            jedis.set("aaa", "测试数据");
        }*/
        System.out.println(jedis.get("aaaa"));
    }

2.3.3set超时时间原子性操作

 /**
     * 需求:
     *  1.要求用户赋值时,如果数据存在则不赋值.  setnx
     *  2.要求在赋值操作时,必须设定超时的时间 并且要求满足原子性 setex
     */
    @Test
    public void test04() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        SetParams setParams = new SetParams();
        setParams.nx().ex(20);
        jedis.set("bbbb", "实现业务操作AAAA", setParams);
        System.out.println(jedis.get("bbbb"));


    }

2.3.4list集合练习

  @Test
    public void testList() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        jedis.lpush("list", "1","2","3");
        System.out.println(jedis.rpop("list"));
    }

2.3.5redis事务控制

 @Test
    public void testTx() throws InterruptedException {
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //1.开启事务
        Transaction transaction = jedis.multi();
        try {
            transaction.set("aa", "aa");
            //提交事务
            transaction.exec();
        }catch (Exception e){
            e.printStackTrace();
            //回滚事务
            transaction.discard();
        }
    }

3.1常规锁操作

3.1.1超卖的原因

image

3.1.2同步锁的问题

说明同步锁只能解决tomcat内部问题,不能解决多个tomcat并发问题

image

3.1.3分布锁机制

思想
1.锁应该使用第三方操作,锁应该共用
2.原则:如果锁正在被人使用时,其他用户不能操作
3.策略:用户向redis中保存一个key,如果redis中有key表示正在有人使用这把锁,其他用户不允许操作,redis中没有key,则表示可以使用这把锁.
4.风险:如何解决死锁问题,设置超时时间.

image

3.SpringBoot整合Redis

3.1编辑配置文件redis.pro

说明:由于该配置被其他项目共同使用,则应该写入jt-common中.

image

3.2编辑配置类

说明:编辑redis配置类将Jedis对象交给Spring容器管理
@Configuration
@PropertySource("classpath:/properties/redis.properties")
public class JedisConfig {

    @Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private Integer port;

    @Bean
    public Jedis jedis(){

        return new Jedis(host,port);
    }
}

3.3对象与JSON转化 ObjectMapper介绍

3.3.1简单对象转化

/**
     * 测试简单对象的转化
     */
    @Test
    public void test01() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(100L).setItemDesc("商品详情信息")
                .setCreated(new Date()).setUpdated(new Date());
        //对象转化为json
        String json = objectMapper.writeValueAsString(itemDesc);
        System.out.println(json);

        //json转化为对象
        ItemDesc itemDesc2 = objectMapper.readValue(json, ItemDesc.class);
        System.out.println(itemDesc2.getItemDesc());
    }

3.3.2集合对象转化

 /**
     * 测试集合对象的转化
     */
    @Test
    public void test02() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(100L).setItemDesc("商品详情信息1")
                .setCreated(new Date()).setUpdated(new Date());
        ItemDesc itemDesc2 = new ItemDesc();
        itemDesc2.setItemId(100L).setItemDesc("商品详情信息2")
                .setCreated(new Date()).setUpdated(new Date());
        List<ItemDesc> lists = new ArrayList<>();
        lists.add(itemDesc);
        lists.add(itemDesc2);
        //[{key:value},{}]
        String json = objectMapper.writeValueAsString(lists);
        System.out.println(json);

        //将json串转化为对象
        List<ItemDesc> list2 = objectMapper.readValue(json, lists.getClass());
        System.out.println(list2);
    }

3.4编辑工具API

package com.jt.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.ItemDesc;
import org.springframework.util.StringUtils;

public class ObjectMapperUtil {

    /**
     * 1.将用户传递的数据转化为json串
     * 2.将用户传递的json串转化为对象
     */
    private static final ObjectMapper MAPPER = new ObjectMapper();

     //1.将用户传递的数据转化为json串
    public static String toJSON(Object object){

        if(object == null) {
            throw new RuntimeException("传递的数据为null.请检查");
        }

        try {
            String json = MAPPER.writeValueAsString(object);
            return json;
        } catch (JsonProcessingException e) {
            //将检查异常,转化为运行时异常
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    //需求: 要求用户传递什么样的类型,我返回什么样的对象  泛型的知识
    public static <T> T toObj(String json,Class<T> target){
        if(StringUtils.isEmpty(json) || target ==null){
            throw new RuntimeException("参数不能为null");
        }
        try {
           return  MAPPER.readValue(json, target);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

3.5商品分类的缓存实现

3.5.1实现步骤

1.定义Redis中的key key必须是唯一不能重复的,存取key="ITEM_CAT_PARENTID::70"
2.根据key去redis中进行查询 有数据 没有数据
3.没有数据 则查询数据库获取记录,之后将数据保存到redis中方便后续使用.
4.有数据 表示用户不是第一次查询 可以将缓存的数据直接返回即可.

3.5.2编辑ItemController

image

3.5.3编辑ItemCatService

@Override
    public List<EasyUITree> findItemCatListCache(Long parentId) {
        //0.定义公共的返回值对象
        List<EasyUITree> treeList = new ArrayList<>();
        //1.定义key
        String key = "ITEM_CAT_PARENTID::"+parentId;
        //2.检索redis服务器,是否含有该key

        //记录时间
        Long startTime = System.currentTimeMillis();
        if(jedis.exists(key)){
            //数据存在
            String json = jedis.get(key);
            Long endTime = System.currentTimeMillis();
            //需要将json串转化为对象
            treeList = ObjectMapperUtil.toObj(json,treeList.getClass());
            System.out.println("从redis中获取数据 耗时:"+(endTime-startTime)+"毫秒");
        }else{
            //3.数据不存在  查询数据库
            treeList = findItemCatList(parentId);
            Long endTime = System.currentTimeMillis();
            //3.将数据保存到缓存中
            String json = ObjectMapperUtil.toJSON(treeList);
            jedis.set(key, json);
            System.out.println("查询数据库 耗时:"+(endTime-startTime)+"毫秒");
        }
        return treeList;
    }

3.5.4使用Redis的速度差

image

4.AOP实现Redis缓存

4.1自定义缓存注解

1.注解名称:cacheFind
2.属性参数
2.1key:应该由用户自己手动添加一般添加业务名称之后动态拼接形成唯一的key
2.2seconds:用户可以指定数据的超时时间
@Target(ElementType.METHOD) //注解对方法有效
@Retention(RetentionPolicy.RUNTIME)  //运行期有效
public @interface CacheFind {

    public String preKey();          //用户标识key的前缀.
    public int seconds() default 0;  //如果用户不写表示不需要超时. 如果写了以用户为准.
}

image

4.2编辑CacheAOP

package com.jt.aop;

import com.jt.anno.CacheFind;
import com.jt.config.JedisConfig;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Method;
import java.util.Arrays;

@Aspect     //我是一个AOP切面类
@Component  //将类交给spring容器管理
public class CacheAOP {

    @Autowired
    private Jedis jedis;

    /**
     * 切面 = 切入点 + 通知方法
     *        注解相关 + 环绕通知  控制目标方法是否执行
     *
     *  难点:
     *      1.如何获取注解对象
     *      2.动态生成key  prekey + 用户参数数组
     *      3.如何获取方法的返回值类型
     */
    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
        Object result = null;
        try {
            //1.拼接redis存储数据的key
            Object[] args = joinPoint.getArgs();
            String key = cacheFind.preKey() +"::" + Arrays.toString(args);

            //2. 查询redis 之后判断是否有数据
            if(jedis.exists(key)){
                //redis中有记录,无需执行目标方法
                String json = jedis.get(key);
                //动态获取方法的返回值类型   向上造型  向下造型
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                Class returnType = methodSignature.getReturnType();
                result = ObjectMapperUtil.toObj(json,returnType);
                System.out.println("AOP查询redis缓存");
            }else{
                //表示数据不存在,需要查询数据库
                result = joinPoint.proceed();  //执行目标方法及通知
                //将查询的结果保存到redis中去
                String json = ObjectMapperUtil.toJSON(result);
                //判断数据是否需要超时时间
                if(cacheFind.seconds()>0){
                    jedis.setex(key,cacheFind.seconds(),json);
                }else {
                    jedis.set(key, json);
                }
                System.out.println("aop执行目标方法查询数据库");
            }

        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }

}

5.关于Redis配置说明

5.1关于Redis持久化说明

redis默认条件下支持数据的持久化操作,当redis中有数据时会定期将数据保存到磁盘中,当redis服务器重启时,会根据配置文件读取指定的持久化文件.实现内存数据的恢复.

5.2持久化方式介绍

5.2.1RDB模式

特点

1.RDB模式是redis的默认的持久化策略.
2.RDB模式记录的是Redis内存数据的快照,最新的快照会覆盖之前的内容,所有RDB持久化文件占用空间更小,持久化效率高.
3.RDB模式由于是定期持久化,所以可能导致数据丢失.

命令:

1. save 要求立即马上持久化 同步的操作 其他的redis操作会陷入阻塞的状态.
2. bgsave 开启后台运行 异步的操作 由于是异步操作,所以无法保证rdb文件一定是最新的需要等待.

配置

1.持久化文件名称:

image

2.持久化文件位置:

image

3.RDB模式持久化策略

image

5.2.2AOF模式

特点:

1.AOF模式默认条件下是关闭的,需要用户手动开启

image

2.AOF模式是异步操作 记录的是用户的操作过程可以防止用户数据的丢失.
3.由于AOF模式记录的是程序的运行状态,所以持久化文件较大,恢复数据的时间长,需要人为的优化持久化文件.

配置:

image

5.2.3关于持久化操作的总结

1.如不不允许数据丢失采用AOF模式
2.如果追求追求效率,运行少量数据丢失 采用RDB模式
3.如果既要保证效率 又要保证数据 则配置redis的集群 主机使用RDB 从机使用AOF

5.3关于Redis的内存策略

5.3.1关于内存策略的说明

说明:Redis数据的存储都在内存中,如果一直想内存中存储数据,必然会导致内存数据的溢出.

解决方式

1.尽可能为保存在redis中的数据添加超时时间.
2.利用算法优化旧数据

5.3.2LRU算法

特点:最好用的内存优化方法.
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰.该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间t,当必须淘汰一个页面时,选择现有页面中t值最大的,即最近最少使用被淘汰.

5.3.3LFU算法

LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页面置换引用计数最小的页,因为经常使用的页应该有一个较大的使用次数.但是有些页开始时使用次数最多,但是以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数.
维度:使用次数.

5.3.4 RANDOM算法

随机删除数据

5.3.5TTL算法

把设定了超时时间的数据将要移除的提前删除算法.

5.3.6Redis内存数据优化

1.volatile-lru 设定了超时时间的数据采用lru算法
2.allkeys-lru 所有的数据采用LRU算法
3.volatile-lfu 设定了超时时间的数据采用lfu算法删除
4.allkeys-lfu 所有数据采用lfu算法删除
5.volatile-random 设定超时时间的数据采用随机算法
6.allkeys-random 所有数据的随机算法
7.volatile-ttl 设定超时时间的数据的TTL算法
8.noeviction 如果内存溢出了 则报错返回. 不做任何操作. 默认值

image

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 11月13日

Dubbo总结

1.Dubbo

1.1 Dubbo介绍

Apache Dubbo是一款高性能,轻量级的开源Java RPC框架,他提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现.

image

1.2 Dubbo特点

image

2.Dubbo入门案例

2.1定义公共接口项目

说明:接口项目一般定义公共的部分,并且被第三方依赖.

image

2.2服务提供者介绍

2.2.1提供者代码结构

image

2.2.2编辑实现类

package com.jt.dubbo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.dubbo.mapper.UserMapper;
import com.jt.dubbo.pojo.User;
@Service(timeout=3000)    //3秒超时 内部实现了rpc
//@org.springframework.stereotype.Service//将对象交给spring容器管理
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public List<User> findAll() {
        
        System.out.println("我是第一个服务的提供者");
        return userMapper.selectList(null);
    }
    
    @Override
    public void saveUser(User user) {
        
        userMapper.insert(user);
    }
}

2.2.3编辑提供者配置文件

server:
  port: 9000   #定义端口

spring:
  datasource:
    #引入druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

#关于Dubbo配置   
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径 扫描dubbo注解
  application:              #应用名称
    name: provider-user     #一个接口对应一个服务名称   一个接口可以有多个实现
  registry:  #注册中心 用户获取数据从机中获取 主机只负责监控整个集群 实现数据同步
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复.

      
mybatis-plus:
  type-aliases-package: com.jt.dubbo.pojo       #配置别名包路径
  mapper-locations: classpath:/mybatis/mappers/*.xml  #添加mapper映射文件
  configuration:
    map-underscore-to-camel-case: true                #开启驼峰映射规则

2.3服务消费者介绍

2.3.1编辑Controller

package com.jt.dubbo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.dubbo.pojo.User;
import com.jt.dubbo.service.UserService;

@RestController
public class UserController {
    
    //利用dubbo的方式为接口创建代理对象 利用rpc调用
    @Reference
    private UserService userService; 
    
    /**
     * Dubbo框架调用特点:远程RPC调用就像调用自己本地服务一样简单
     * @return
     */
    @RequestMapping("/findAll")
    public List<User> findAll(){
        
        //远程调用时传递的对象数据必须序列化.
        return userService.findAll();
    }
    
    @RequestMapping("/saveUser/{name}/{age}/{sex}")
    public String saveUser(User user) {
        
        userService.saveUser(user);
        return "用户入库成功!!!";
    }
}

2.3.2 编辑配置YML文件

server:
  port: 9001
dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-user   #定义消费者名称
  registry:               #注册中心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183

2.3.3Dubbo入门测试

image

2.4关于Dubbo框架知识点

2.4.1问题1:如果其中一个服务器宕机,用户访问受限?

由于zk的帮助,使得程序永远可以访问正确的服务器,并且当服务重启时,dubbo有服务自动发现功能,消费者不需要重启即可以访问新的服务.

2.4.2问题2:如果ZK集群短时间宕机,用户访问是否受限?

用户的访问不受影响,由于消费者在本地存储列表信息,当访问故障机时,自动的将标识信息改为down属性.

2.5Dubbo负载均衡策略

2.5.1负载均衡种类

1.客户端负载均衡
Dubbo/SpringCloud等微服务框架

image

2.服务端负载均衡
说明:客户端发起请求后,必须由统一的服务器进行负载均衡,所有压力都在服务器中.
NGINX

image

2.5.2Dubbo负载均衡的方式

@RestController
public class UserController {
    
    //利用dubbo的方式为接口创建代理对象 利用rpc调用
    //@Reference(loadbalance = "random")            //默认策略  负载均衡随机策略
    //@Reference(loadbalance = "roundrobin")        //轮询方式
    //@Reference(loadbalance = "consistenthash")    //一致性hash  消费者绑定服务器提供者
    @Reference(loadbalance = "leastactive")            //挑选当前负载小的服务器进行访问
    private UserService userService; 

}

3.京淘项目Dubbo改造

3.1改造JT-SSO

3.1.1 添加jar包文件

        <!--引入dubbo配置 -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

3.1.2创建DubboUserService接口

image

3.1.3创建提供者实现类

image

3.1.4编辑提供者YML配置文件

server:
  port: 8093
  servlet:
    context-path: /
spring:
  datasource:
    #引入druid数据源
    #type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level: 
    com.jt.mapper: debug

#关于Dubbo配置
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径 扫描dubbo注解
  application:              #应用名称
    name: provider-user     #一个接口对应一个服务名称   一个接口可以有多个实现
  registry:  #注册中心 用户获取数据从机中获取 主机只负责监控整个集群 实现数据同步
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复.

3.1.5启动服务提供者

测试Dubbo服务器启动是否正常

image

3.2改造服务消费者JT-WEB

3.2.1注入Service接口

image

3.2.2编辑消费者配置文件

server:
  port: 8092    
spring:     #定义springmvc视图解析器
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp

dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-web   #定义消费者名称
  registry:               #注册中心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183

  

3.1.3启动效果测试

image~~~~

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 11月11日

Spring AOP简介

1.1 AOP概述

1.1.1 AOP是什么?

AOP是一种设计思想,是软件设计领域的面向切面编程,他是面向对象编程OOP的一种补充和完善.它以通过预编译的方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术.

1.1.2 AOP应用场景分析?

实际项目中通常会将系统分为两部分,一部分是核心业务,一部分是非核心业务.在编程实现时我们首先要完成的是核心业务的实现,非核心业务我们一般通过特定的方式切入到系统中,这种方式一般借助AOP实现.
AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以控制对象执行.例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等.

1.1.3 AOP应用原理分析

1)假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口).
2)假如目标对象(被代理对象)没有实现接口,则底层可以采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型).
AOP在默认的情况下使用CGLIB代理,加入使用JDK动态代理可以在配置文件(application.properties)中配置如下:
spring.aop.proxy-target-class=false

1.2AOP相关术语分析

切面(asprct):横切面对象,一般为一个具体类对象(可以借助@Acpect声明).
通知(Advice):在横切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等.
连接点(joinpoint):程序执行中的某个特定点一般指被拦截到的方法.
切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合.

2.Spring AOP快速实践

2.1业务描述

基于项目中的核心业务,简单添加日志操作,借助SLF4J日志API输出目标方法的执行时长.(前提,不能修改目标方法的代码-遵循OCP原则)

2.2项目创建及配置

创建maven项目或在已有的项目基础上添加AOP启动依赖:
<dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-aop</artifactId>

</dependency>

说明:基于此依赖spring可以整合AspectJ框架快锁完成AOP的基本操作,AspectJ是一个面向切面的框架,它定义了AOP的一些语法,有一个专门的自己吗生成器来生成遵守java规范的class文件.

2.3 扩展业务分析及实现

2.3.1创建日志切面类对象

将此日志切面类作为核心业务增强(一个横切面对象)类,用于输出业务执行时长.
package com.cy.pj.common.aspect;

@Aspect

@Slf4j

@Component

public class SysLogAspect {

         @Pointcut("bean(sysUserServiceImpl)")

         public void logPointCut() {}


         @Around("logPointCut()")

         public Object around(ProceedingJoinPoint jp)

         throws Throwable{

                 try {

                   log.info("start:{}"+System.currentTimeMillis());

                   Object result=jp.proceed();//最终会调用目标方法

                   log.info("after:{}"+System.currentTimeMillis());

                   return result;

                 }catch(Throwable e) {

                   log.error("after:{}",e.getMessage());

                   throw e;

                 }

         }
说明:
@Aspect注解用于标识或者描述AOP中切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行.
@Pointcut注解用于描述切面中的方法,并定义切面中的切入点,(基于特定表达式的方式进行描述).
@Around注解用以描述切面中的方法,这样的方法会被认为是一个环绕通知(核心业务方法执行之前和执行之后要执行的一个动作),@Around注解内部value属性的值为一个切入点表达式或者是切入点表达式的一个引用(这个引用为@Pointcut注解描述的方法的方法名).
ProceedingJoinPoint类作为一个连接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息.只能用于@Around注解描述的方法参数.

2.3.1业务切面测试实现

启动项目测试或者进行单元测试:
@SpringBootTest

public class AopTests {

         @Autowired

         private SysUserService userService;

         @Test

         public void testSysUserService() {

                 PageObject<SysUserDeptVo> po=

                 userService.findPageObjects("admin",1);

                 System.out.println("rowCount:"+po.getRowCount());

         }

}

2.4 扩展业务织入增强分析

2.4.1基于JDK代理方式实现

假如目标对象有实现接口,则可以基于JDK为目标对象创建代理对象,然后对目标对象进行功能扩展.

2.4.2基于CGLIB代理方式实现

加入目标对象没有实现接口(当然实现了接口也是可以的),可以基于CGLIB代理方式为目标对象织入功能扩展.
说明:目标对象实现了接口也可以基于CGLIB为目标对象创建对象.

3.Spring AOP增强

3.1切面通知应用增强

3.1.1通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述是一种扩展业务),它们分别是:
@Before
@AfterReturning
@AfterThrowing
@After
@Around重点掌握(优先级最高)
说明:在切面类中使用什么通知,由业务决定,并不是说,在切面中要把所有的通知都写上.

3.1.2 通知执行顺序

image

说明:实际项目中可能不会在切面中定义所有通知,具体定义那些通知要结合业务进行实现.

3.1.3通知实践过程分析

@Component

@Aspect

public class SysTimeAspect {

        @Pointcut("bean(sysUserServiceImpl)")

        public void doTime(){}


        @Before("doTime()")

        public void doBefore(JoinPoint jp){

                System.out.println("time doBefore()");

        }

        @After("doTime()")

        public void doAfter(){

                System.out.println("time doAfter()");

        }

        /**核心业务正常结束时执行* 说明:假如有after,先执行after,再执行returning*/

        @AfterReturning("doTime()")

        public void doAfterReturning(){

                System.out.println("time doAfterReturning");

        }

        /**核心业务出现异常时执行说明:假如有after,先执行after,再执行Throwing*/

        @AfterThrowing("doTime()")

        public void doAfterThrowing(){

                System.out.println("time doAfterThrowing");

        }

        @Around("doTime()")

        public Object doAround(ProceedingJoinPoint jp)

                        throws Throwable{

                System.out.println("doAround.before");

         try{

                 Object obj=jp.proceed();

           System.out.println("doAround.after");

          return obj;

                 }catch(Throwable e){

          System.out.println(e.getMessage());

          throw e;

         }

                

        }

}
说明:对于@AfterThrowing通知只有在出现有异常时才会执行,所以当做一些异常监控时可在此方法中进行代码实现.

3.2切入点表达式增强

3.2.1bean表达式(重点)

bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean("userServiceImpl")指定一个userServiceImpl中的所有方法.
bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法.
说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中的某个bean的name.

3.2.2within表达式(了解)

within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within("aop.service.UserServiceImpl")指定当前包中这个类内部的所有方法.
within("aop.service.*") 指定当前目录下的所有类的所有方法.
within("aop.service..*") 指定当前目录以及子目录中类的所有方法.

3.2.3execution表达式(了解)

execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表)).
execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法
execution( aop.service...*(..)) 万能配置.

3.2.4@annotation表达式(重点)

@annontation(anno.RequiredLog)匹配有此注解描述的方法.
@annontation(anno.RequiredCache)匹配有此注解描述的方法.
其中:RequiredLog为我们自定义注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行日扩展操作.
:定义一Cache相关切面,使用注解表达式定义切入点,并使用此注解对需要使用cache的业务方法进行描述,代码分析如下:
第一步:定义注解RequiredCache
package com.cy.pj.common.annotation;

/**

 * 自定义注解,一个特殊的类,所有注解都默认继承Annotation接口

 */

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface RequiredCache {

   //...

}
第二步:定义SysCacheAspect切面对象
package com.cy.pj.common.aspect;

@Aspect

@Component

public class SysCacheAspect {

            @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")

          public void doCache() {}


          @Around("doCache()")

          public Object around(ProceedingJoinPoint jp)

    throws Throwable{

                  System.out.println("Get data from cache");

                  Object obj=jp.proceed();

                  System.out.println("Put data to cache");

                  return obj;

          }

   

}
第三步: 使用@RequiredCache注解对特定业务目标对象中的查询方法进行描述.
 @RequiredCache

        @Override

        public List<Map<String, Object>> findObjects() {

                ….

                return list;

        }

3.3切面优先级设置实现

切面的优先级需要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较低。例如:
定义日志切面并指定优先级
@Order(1)

@Aspect

@Component

public class SysLogAspect {

 …

}
定义缓存切面并指定优先级
@Order(2)

@Aspect

@Component

public class SysCacheAspect {

        …

}
说明: 当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链.

3.4关键对象与术语总结

Spring 基于AspectJ框架实现AOP设计的关键对象概览,如图-10所示:

image

4.Spring AOP中的事务处理

4.1Spring AOP中事务简介

4.1.1事务定义

事务是一个业务,是一个不可分割的逻辑工作单元,基于事务可以更好的保证业务的正确性.

4.1.2事物的特性

事务具备的ACID特性,分别是:
原子性:一个事务中的多个操作要么都成功要么都失败。
一致性:例如存钱操作,存之前和存之后的总钱数应该是一致的。
隔离性:事务与事务应该是相互隔离的。
持久性:事务一旦提交,数据要持久保存。

4.2Spring中的事务处理

4.2.1Spring中事务方式概述

Spring框架中提供了一种声明式事务的处理方式,此方式基于AOP代理,可以将具体业务逻辑与事务处理进行解耦.也就是让我们的业务代码逻辑不受污染或少量污染,就可以实现事务控制.
在SpringBoot项目中,其内部提供了事务的自动配置,当我们在项目中添加了指定依赖spring-boot-starter-jdbc时,框架会自动为我们的项目注入事务管理器对象,最常用的为DataSourceTransactionMannager对象.

4.2.2Spring 中事务管理实现

1)启用声明式事务管理,在项目启动类上添加@EnableTransactionManagement,新版本可以不用添加(例如SpringBoot项目)
2)将@Transactional注解添加到合适的业务类或方法上,并设置合适的属性信息.
代码示例:
@Transactional(timeout = 30,

               readOnly = false,

               isolation = Isolation.READ_COMMITTED,

               rollbackFor = Throwable.class,

               propagation = Propagation.REQUIRED)

  @Service

  public class implements SysUserService {

        @Transactional(readOnly = true)

    @Override

         public PageObject<SysUserDeptVo> findPageObjects(

                        String username, Integer pageCurrent) {

      …

       }

}

当@Transactional注解应用在类上时表示类中所有方法启动事务管理,并且一般用于事务共性的定义
当@Transactional描述方法时表示此方法要进行事务管理,假如类和方法上都有@Transactional注解,则方法上的事务特性优先级比较高。
@Transactional 常用属性应用说明:
timeout:事务的超时时间,默认值为-1,表示没有超时显示。如果配置了具体时间,则超过该时间限制但事务还没有完成,则自动回滚事务。这个时间的记录方式是在事务开启以后到sql语句执行之前.
read-only:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true。对添加,修改,删除业务read-only的值应该为false。
rollback-for:用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for: 抛出no-rollback-for 指定的异常类型,不回滚事务
isolation事务的隔离级别,默认值采用 DEFAULT。当多个事务并发执行时,可能会出现脏读,不可重复读,幻读等现象时,但假如不希望出现这些现象可考虑修改事务的隔离级别(但隔离级别越高并发就会越小,性能就会越差).

5.Spring AOP 异步操作实现

5.1异步场景分析

在开发系统的过程中,通常会考虑到系统的性能问题,提升系统性能的一个重要思想就是“串行”改“并行”。说起“并行”自然离不开“异步”,今天我们就来聊聊如何使用Spring的@Async的异步注解。

5.2Sping 业务的异步实现

5.2.1启动异步配置

再基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,SpringBoot版本的项目,将@EnableAsync注解应用到启动类上,代码示例如下:
  @EnableAsync //spring容器启动时会创建线程池

   @SpringBootApplication

   public class Application {

        public static void main(String[] args) {

                SpringApplication.run(Application.class, args);

        }

}

5.2.2Spring中@Async注解应用

在需要异步执行的业务方法上,使用@Async方法进行异步声明.
@Async

        @Transactional(propagation = Propagation.REQUIRES_NEW)

        @Override

        public void saveObject(SysLog entity) {

      System.out.println("SysLogServiceImpl.save:"+

Thread.currentThread().getName());

          sysLogDao.insertObject(entity);

          //try{Thread.sleep(5000);}catch(Exception e) {}

        }
假如需要获取业务层异步方法的执行结果,可以参考如下代码设计进行实现:
 @Transactional(propagation = Propagation.REQUIRES_NEW)

   @Async

        @Override

        public Future<Integer> saveObject(SysLog entity) {

                System.out.println("SysLogServiceImpl.save:"+

Thread.currentThread().getName());

                int rows=sysLogDao.insertObject(entity);

                //try{Thread.sleep(5000);}catch(Exception e) {}

            return new AsyncResult<Integer>(rows);

        }
其中,AsyncResult对象可以对异步方法的执行结果进行封装,假如外界需要异步方法结果时,可以通过Future对象的get方法获取结果。
我们需要自己对spring框架提供的连接池进行一些简易配置,可以参考如下代码:
spring:

  task:

    execution:

      pool:

        queue-capacity: 128

        core-size: 5

        max-size: 128

        keep-alive: 60000

      thread-name-prefix: db-service-task-

查看原文

赞 1 收藏 1 评论 0

BolunWu 发布了文章 · 11月11日

Shiro安全框架

1.Shiro安全框架

1.1Shiro框架简介

Shiro是apache旗下一个开源安全框架(http://shiro.apache.org),它将...

1.2Shiro的概要架构

1)Subject:主体对象,负责提交用户认证和授权信息.
2)SecuritvManager:安全管理器,负责认证,授权等业务实现.
3)Realm:领域对象,负责从数据层获取业务数据.

1.3Shiro框架认证拦截实现

1.3.1Shiro基本环境配置

1)添加shiro依赖
<dependency>

   <groupId>org.apache.shiro</groupId>

   <artifactId>shiro-spring</artifactId>

   <version>1.5.3</version>

</dependency>
2)shiro核心对象配置
在SpringBoot项目中没有提供shiro的自动化配置,需要我们自己配置
第一步创建SpringShiroConfig类,关键代码如下:
package com.cy.pj.common.config;

/**@Configuration 注解描述的类为一个配置对象,

 * 此对象也会交给spring管理

 */

@Configuration

public class SpringShiroConfig {


}
第二步在Shiro配置类中添加SecurityManager配置(这里一定要使用org.apache.shiro.mgt.SecurityManager这个接口对象),关键代码如下:
@Bean

public SecurityManager securityManager() {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 return sManager;

}

第三步:在Shiro配置类中添加ShiroFilterFactoryBean对象的配置。通过此对象设置资源匿名访问,认证访问.关键代码如下:
@Bean

public ShiroFilterFactoryBean shiroFilterFactory (

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

                 //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)

                 LinkedHashMap<String,String> map= new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/build/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");

                 //除了匿名访问的资源,其它都要认证("authc")后访问

                 map.put("/**","authc");

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

         }

1.4Shiro登录页面实现

业务描述当服务端拦截到用户请求以后,判定此请求是否已经被认证,假如没有认证应该先跳转到登录页面.
第一步在Controller层添加一个呈现登录页面的方法,关键代码如下:
@RequestMapping("doLoginUI")

public String doLoginUI(){

                return "login";

}
第二步:修改SpringShiroConfig类中shiroFilterFactorybean的配置,添加登录的url的设置,关键代码如下:
@Bean

public ShiroFilterFactoryBean shiroFilterFactory (

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

 sfBean.setLoginUrl("/doLoginUI");

//定义map指定请求过滤规则(哪些资源允许匿名访问,

哪些必须认证访问)

                 LinkedHashMap<String,String> map=

                                 new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/modules/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");

                 //除了匿名访问的资源,其它都要认证("authc")后访问

                 map.put("/**","authc");

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

}

1.4Shiro框架认证业务实现

1.4.1认证流程分析

身份认证即判定用户是否是系统的合法用户,用户访问系统资源时的认证(对用户身份信息的认证)
1)系统调用subject的login方法将用户信息提交给SecurityManager
2)SecurityManager将认证操作委托给认证器对象Authenticator
3)Authenticator将用户输入的身份认证信息传递个Realm
4)Realm访问数据库获取用户信息然后对信息进行封装并返回
5)Authenticator对realm返回的信息进行身份认证.

1.4.2具体业务代码

Dao层
SysUser findUserByUserName(String username)。
SysUserMapper文件添加SQL
 <select id="findUserByUserName"

           resultType="com.cy.pj.sys.entity.SysUser">

      select *

      from sys_users  

      where username=#{username}

   </select>
Service接口及实现
第一步定义ShiroUserRealm类,关键代码如下:
package com.cy.pj.sys.service.realm;

@Service

public class ShiroUserRealm extends AuthorizingRealm {


        @Autowired

        private SysUserDao sysUserDao;

                

        /**

         * 设置凭证匹配器(与用户添加操作使用相同的加密算法)

         */

        @Override

        public void setCredentialsMatcher(

              CredentialsMatcher credentialsMatcher) {

                //构建凭证匹配对象

                HashedCredentialsMatcher cMatcher=

                new HashedCredentialsMatcher();

                //设置加密算法

                cMatcher.setHashAlgorithmName("MD5");

                //设置加密次数

                cMatcher.setHashIterations(1);

                super.setCredentialsMatcher(cMatcher);

        }

        /**

         * 通过此方法完成认证数据的获取及封装,系统

         * 底层会将认证数据传递认证管理器,由认证

         * 管理器完成认证操作。

         */

        @Override

        protected AuthenticationInfo doGetAuthenticationInfo(

                        AuthenticationToken token)

                        throws AuthenticationException {

                //1.获取用户名(用户页面输入)

                UsernamePasswordToken upToken=

                (UsernamePasswordToken)token;

                String username=upToken.getUsername();

                //2.基于用户名查询用户信息

                SysUser user=

                sysUserDao.findUserByUserName(username);

                //3.判定用户是否存在

                if(user==null)

                throw new UnknownAccountException();

                //4.判定用户是否已被禁用。

                if(user.getValid()==0)

                throw new LockedAccountException();

                

                //5.封装用户信息

                ByteSource credentialsSalt=

                ByteSource.Util.bytes(user.getSalt());

                //记住:构建什么对象要看方法的返回值

                SimpleAuthenticationInfo info=

                new SimpleAuthenticationInfo(

                                user,//principal (身份)

                                user.getPassword(),//hashedCredentials

                                credentialsSalt, //credentialsSalt

                                getName());//realName

                //6.返回封装结果

                return info;//返回值会传递给认证管理器(后续

                //认证管理器会通过此信息完成认证操作)

        }

    ....

}
第二步:对此realm,需要在SpringShiroConfig配置类中,注入SecurityManager对象,修改securityManager方法
@Bean

public SecurityManager securityManager(Realm realm) {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 sManager.setRealm(realm);

                 return sManager;

}
Controller层实现
第一步: 在SysUserController中添加处理登陆的方法.关键代码如下:
   public JsonResult doLogin(String username,String password){

                   //1.获取Subject对象

                   Subject subject=SecurityUtils.getSubject();

                   //2.通过Subject提交用户信息,交给shiro框架进行认证操作

                   //2.1对用户进行封装

                   UsernamePasswordToken token=

                   new UsernamePasswordToken(

                                   username,//身份信息

                                   password);//凭证信息

                   //2.2对用户信息进行身份认证

                   subject.login(token);

                   //分析:

                   //1)token会传给shiro的SecurityManager

                   //2)SecurityManager将token传递给认证管理器

                   //3)认证管理器会将token传递给realm

                   return new JsonResult("login ok");

           }
第二步: 修改shiroFilerFactory的配置,对/user/doLogin这个路径进行匿名访问的配置:
@Bean

public ShiroFilterFactoryBean shiroFilterFactory (

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

                 //假如没有认证请求先访问此认证的url

                 sfBean.setLoginUrl("/doLoginUI");

                 //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)

                 LinkedHashMap<String,String> map=

                                 new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/build/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");


  map.put("/user/doLogin","anon");                       //authc表示,除了匿名访问的资源,其它都要认证("authc")后才能访问访问

                 map.put("/**","authc");

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

         }
第三步: 当我们在执行登录操作时,为了提高用户体验,可对系统中的异常信息进行处理,在统一的异常类中处理:
  @ExceptionHandler(ShiroException.class)

   @ResponseBody

        public JsonResult doHandleShiroException(

                        ShiroException e) {

                JsonResult r=new JsonResult();

                r.setState(0);

                if(e instanceof UnknownAccountException) {

                        r.setMessage("账户不存在");

                }else if(e instanceof LockedAccountException) {

                        r.setMessage("账户已被禁用");

                }else if(e instanceof IncorrectCredentialsException) {

                        r.setMessage("密码不正确");

                }else if(e instanceof AuthorizationException) {

                        r.setMessage("没有此操作权限");

                }else {

                        r.setMessage("系统维护中");

                }

                e.printStackTrace();

                return r;

        }

1.5Shiro框架授权过程实现

1.5.1 shiro框架授权过程分析

1)系统调用subject相关方法将用户信息(例如isPermitted)递交给SecurityManager.
2)SecurityManager将权限检测信息交给Authorize
3)Authorize将用户信息委托给realm
4)Realm访问数据库获取用户权限信息并封装
5)Autrorize对用户授权信息进行判定

1.5.2添加授权配置

在SpringShiroConfig类中添加授权时的相关配置:
第一步: 配置bean对象的生命周期管理(SpringBoot可以不用配置)
@Bean

public LifecycleBeanPostProcessor   lifecycleBeanPostProcessor() {

                 return new LifecycleBeanPostProcessor();

}
第二步: 通过如下配置为目标业务对象创建代理对象:(SpringBoot中可以省略)

@DependsOn("lifecycleBeanPostProcessor")

@Bean

public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

                 return new DefaultAdvisorAutoProxyCreator();

}
第三步: 配置advisor对象,shiro框架会通过此对象的matchs方法返回值(类似于切入点)决定是否创建代理对象,进行权限控制.
@Bean

public AuthorizationAttributeSourceAdvisor

authorizationAttributeSourceAdvisor (

                                SecurityManager securityManager) {

                        AuthorizationAttributeSourceAdvisor advisor=

                                new AuthorizationAttributeSourceAdvisor();

advisor.setSecurityManager(securityManager);

        return advisor;

}

//说明:使用框架最重要的尊重规则,框架规则指定了什么方式就使用什么方式。

1.5.3授权代码实现

Dao实现
第一步: 在SysUserRoleDao中定义基于用户id查找角色id的方法:
   List<Integer> findRoleIdsByUserId(Integer id);
第二步: 在RoleMenuDao中定义基于角色id查找菜单的id方法:
  List<Integer> findMenuIdsByRoleIds(

                        @Param("roleIds")List<Integer> roleIds);
第三步: 在SysMenuDao中基于菜单id查找权限标识的方法:
 List<String> findPermissions(

                        @Param("menuIds")

                        List<Integer> menuIds);
Mapper实现
第一步: 在SysUserRoleMapper中定义findRoleIdsByUserId元素:
<select id="findRoleIdsByUserId"

            resultType="int">

           select role_id

           from sys_user_roles

           where user_id=#{userId}        

</select>
第二步: 在SysRoleMenuMapper中定义findMenuIdsByRoleIds元素:
 <select id="findMenuIdsByRoleIds"

         resultType="int">

         select menu_id

         from sys_role_menus

         where role_id in

         <foreach collection="roleIds"

                  open="("

                  close=")"

                  separator=","

                  item="item">

               #{item}

         </foreach>

</select>
第三步: 在SysMenuMapper中定义findPermission元素:
  <select id="findPermissions"

           resultType="string">

       select permission <!-- sys:user:update -->

       from sys_menus

       where id in

       <foreach collection="menuIds"

                open="("

                close=")"

                separator=","

                item="item">

            #{item}

       </foreach>

   </select>
Service实现
@Service

public class ShiroUserRealm extends AuthorizingRealm {

        @Autowired

        private SysUserDao sysUserDao;

        @Autowired

        private SysUserRoleDao sysUserRoleDao;

        @Autowired

        private SysRoleMenuDao sysRoleMenuDao;

        @Autowired

        private SysMenuDao sysMenuDao;

        /**通过此方法完成授权信息的获取及封装*/

        @Override

        protected AuthorizationInfo doGetAuthorizationInfo(

                PrincipalCollection principals) {

                //1.获取登录用户信息,例如用户id

                SysUser user=(SysUser)principals.getPrimaryPrincipal();

                Integer userId=user.getId();

                //2.基于用户id获取用户拥有的角色(sys_user_roles)

                List<Integer> roleIds=

                sysUserRoleDao.findRoleIdsByUserId(userId);

                if(roleIds==null||roleIds.size()==0)

                throw new AuthorizationException();

                //3.基于角色id获取菜单id(sys_role_menus)

                List<Integer> menuIds=

                sysRoleMenuDao.findMenuIdsByRoleIds(roleIds);

            if(menuIds==null||menuIds.size()==0)

            throw new AuthorizationException();

                //4.基于菜单id获取权限标识(sys_menus)

            List<String> permissions=

            sysMenuDao.findPermissions(menuIds);

                //5.对权限标识信息进行封装并返回

            Set<String> set=new HashSet<>();

            for(String per:permissions){

                    if(!StringUtils.isEmpty(per)){

                            set.add(per);

                    }

            }

            SimpleAuthorizationInfo info=

            new SimpleAuthorizationInfo();

            info.setStringPermissions(set);

                return info;//返回给授权管理器

        }




}

1.6 Shiro记住我实现

1.6.1 服务端业务实现的具体步骤如下:

第一步:在SysUserController中的doLogin方法中基于是否选中记住我,设置token的setRememberMe方法.
@RequestMapping("doLogin")

         @ResponseBody

         public JsonResult doLogin(

                         boolean isRememberMe,

                         String username,

                         String password) {

                 //1.封装用户信息

                 UsernamePasswordToken token=

                 new UsernamePasswordToken(username, password);

                 if(isRememberMe) {

                        token.setRememberMe(true);

                 }

                 //2.提交用户信息

                 Subject subject=SecurityUtils.getSubject();

                 subject.login(token);//token会提交给securityManager

                 return new JsonResult("login ok");

         }
第二步: 在SpringShiroConfig配置类中添加记住我配置,关键代码如下:
@Bean

         public RememberMeManager rememberMeManager() {

                 CookieRememberMeManager cManager=

                 new CookieRememberMeManager();

  SimpleCookie cookie=new SimpleCookie("rememberMe");

                 cookie.setMaxAge(7*24*60*60);

                 cManager.setCookie(cookie);

                 return cManager;

         }
第三步: 在SpringShiroConfig中修改securityManager的配置,为securityManager注入rememberManager对象:
 @Bean

         public SecurityManager securityManager(

                        Realm realm,CacheManager cacheManager

RememberMeManager rememberManager) {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 sManager.setRealm(realm);

                 sManager.setCacheManager(cacheManager);

                 sManager.setRememberMeManager(rememberManager);

                 return sManager;

         }
第四步: 修改shiro的过滤认证级别,将/=author修改为/=user:
@Bean

         public ShiroFilterFactoryBean shiroFilterFactory(

                         SecurityManager securityManager) {

                 ShiroFilterFactoryBean sfBean=

                 new ShiroFilterFactoryBean();

                 sfBean.setSecurityManager(securityManager);

                 //假如没有认证请求先访问此认证的url

                 sfBean.setLoginUrl("/doLoginUI");

                 //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)

                 LinkedHashMap<String,String> map=

                                 new LinkedHashMap<>();

                 //静态资源允许匿名访问:"anon"

                 map.put("/bower_components/**","anon");

                 map.put("/build/**","anon");

                 map.put("/dist/**","anon");

                 map.put("/plugins/**","anon");

                 map.put("/user/doLogin","anon");

                 map.put("/doLogout", "logout");//自动查LoginUrl

                 //除了匿名访问的资源,其它都要认证("authc")后访问

                 map.put("/**","user");//authc

                 sfBean.setFilterChainDefinitionMap(map);

                 return sfBean;

         }

1.7shiro会话时长配置

使用shiro框架实现认证操作,用户登陆成功会将用户信息写入到会话对象中,其默认时长为30分钟,假如需要对此进行配置,可以参考如下代码:
第一步: 在SpringShiroConfig类中,添加会话管理器配置:
@Bean  

public SessionManager sessionManager() {

                 DefaultWebSessionManager sManager=

                                 new DefaultWebSessionManager();

                 sManager.setGlobalSessionTimeout(60*60*1000);

                 return sManager;

}
第二步: 在SpringShiroConfig配置类中,对安全管理器 securityManager增加sessionManager值的注入:
@Bean

public SecurityManager securityManager(

                        Realm realm,CacheManager cacheManager,

RememberMeManager rememberManager,

SessionManager sessionManager) {

                 DefaultWebSecurityManager sManager=

                 new DefaultWebSecurityManager();

                 sManager.setRealm(realm);

                 sManager.setCacheManager(cacheManager);

                 sManager.setRememberMeManager(rememberMeManager);

                 sManager.setSessionManager(sessionManager);

                 return sManager;

}
查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 11月10日

动吧旅游资源整合

1.Spring Boot核心特性

1.1Springboot是一个脚手架,构建于Spring框架(framwork)基础之上,基于快速构建理念,提供了自动配置功能,可实现其开箱即用特性(创建完一个基本的项目以后,可零配置或者少量配置即可运行我们的项目),其核心主要有如下几个方面:
起步依赖--创建项目时底层帮你关联依赖.
自动配置
健康检查
其中,Spring Boot官方网址为:https://spring.io/projects/spring-boot

2.常见的连接池有哪些?

DBCP C3P0 DRUID HikariCP等.

3.整合HikariCP连接池

1.添加依赖
#1.mysql数据库驱动依赖
<dependency>

        <groupId>mysql</groupId>

        <artifactId>mysql-connector-java</artifactId>

        <scope>runtime</scope>

</dependency>
#2.spring对象jdbc支持(此时会默认帮我们下载HikariCP连接池)
<dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-starter-jdbc</artifactId>

</dependency>
2.配置连接池
打开application.properties配置文件,添加如下内容
spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8

spring.datasource.username=root

spring.datasource.password=root

3.单元测试(测试包中编写)
package com.cy.pj.common.datasource;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest

public class DataSourceTests {

  @Autowired

        private DataSource dataSource;

        

  @Test

        public void testConnection() throws Exception{

                System.out.println(dataSource.getConnection());

        }

   }

4.整合MyBatis框架

MyBatis是一个优秀的持久层框架,底层基于JDBC实现数据交互.并在JDBC操作的基础上做了封装和优化,他借助灵活的SQL定制,参数及结果集的映射方式,更好的适应了当前互联网技术的发展.
1.添加mybatis启动依赖
   <dependency>

                        <groupId>org.mybatis.spring.boot</groupId>

                        <artifactId>mybatis-spring-boot-starter</artifactId>

                        <version>2.1.1</version>

        </dependency>
2.Mybatis简易配置(applicaion.properties)

mybatis.configuration.default-statement-timeout=30

mybatis.configuration.map-underscore-to-camel-case=true

5.整合SpringMVC应用

MVC是软件工程中的一种软件架构模式,基于此模式把软件系统分为三个基本部分,模型(Model) 视图(View) 控制器(Controller)目的是通过这样的设计使程序更加简洁,直观,降低问题的复杂度.其中各个组成部分的职责为:
模型(Model)-实现业务逻辑和数据逻辑
视图(View)-UI设计人员进行图形界面设计,负责实现与用户进行交互.
控制器(Controller)-负责获取请求,处理请求,响应结果.
核心组件
DispatcherServlet:前端控制器,处理请求的入口.
HandlerMapping:映射器对象,用于管理url与对应controller的映射关系.
Interceptors:拦截器,实现请求响应的共性处理.
Controller:后端控制器-handler,负责处理请求的控制逻辑.
ViewResolver:视图解析器,解析对应的视图关系(前缀+viewname+后缀).
1.添加springmvc依赖
编辑pom.xml文件,添加spring-web依赖,Thymeleaf依赖,代码如下:
Web依赖(提供了Spring MVC核心API,同时会嵌入一个Tomcat服务器)
<dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-starter-web</artifactId>

</dependency>

Thymeleaf依赖(提供了一个视图解析器对象以及数据绑定机制)
<dependency>

           <groupId>org.springframework.boot</groupId>

           <artifactId>spring-boot-starter-thymeleaf</artifactId>~~~~

</dependency>

2.配置SpringMVC核心对象(application.properties)
spring.thymeleaf.prefix=classpath:/templates/pages/

spring.thymeleaf.suffix=.html

6.健康检查配置

1.添加依赖
项目中添加健康检查依赖:

<dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-starter-actuator</artifactId>

</dependency>
2.修改application.properties文件
management.endpoints.web.exposure.include=*
3.在Google浏览器中下载json View插件

7.热部署的配置

基于SpringBoot的Web项目,在修改le某个类以后,默认不会再自动部署和加载,需要为我们手动重启服务器.假如我们希望自动部署,可以添加热部署依赖.
<dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-devtools</artifactId>

                <scope>runtime</scope>

</dependency>

查看原文

赞 0 收藏 0 评论 0

BolunWu 关注了用户 · 11月9日

廾匸 @s8_5f4486e4c4351

关注 3

BolunWu 关注了用户 · 11月9日

吴欣赏 @wuxinshang

关注 1

BolunWu 发布了文章 · 11月7日

京淘项目知识点总结

1.restFul

业务需求:

实现用户的页面跳转
url:http://localhost:8091/page/item-add 页面:item-add
http://localhost:8091/page/item-list 页面:item-list
能否利用一个方法来实现通用页面的跳转功能
如果能动态获取url中的参数就可以实现页面的跳转.restFul风格...

1.1restFul语法:

1.参数必须使用"/"分隔;
2.参数必须使用{}形式包裹
3.参数接收时必须使用@PathVariable 获取

1.2restFul风格用法

利用不同的请求类型,定义不用的业务功能
type="GET" 查询业务
type="POST" 新增操作
type="PUT" 更新操作
type="DELETE"删除操作

2.反向代理

业务需求:当完成文件上传时,业务返回页面的是虚拟地址路径

url地址: http://image.jt.com/2020/09/30/a.jpg
真实图片地址: file:///D:/JT-SOFT/image/2020/09/30/d534bed912c748b0ac979ee40222490a.jpg
问题: 如何让用户通过url访问 找到真实的磁盘地址的图片.

2.1反向代理机制

2.1.1反向代理机制介绍

反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源.同时,用户不需要知道目标服务器的地址,也无需在用户端作任何设定.反向代理服务器通常可以用来做WEB加速,即用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。
概括
1.位于用户(客户端)-服务器之间.
2.用户访问反向代理服务器,以为是真是的服务器.
3.用户根本不清楚真实的服务器信息到底是谁.
4.一般反向代理机制保护了真实的服务器信息,所以称之为服务器端代理.

image

2.2正向代理机制

2.2.1正向代理介绍

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
总结
1.正向代理位于客户与服务器之间.
2.客户端在发起请求之前 确定了目标服务器的地址.
3.服务器不清楚到底是哪台客户端访问的我,以为只是路由器访问的.~~~~
4.正向代理保护了客户的信息,所以也称之为 客户端代理.
查看原文

赞 0 收藏 0 评论 0

BolunWu 关注了用户 · 11月7日

浅殇 @qian_5f448162251d1

关注 1

BolunWu 发布了文章 · 11月7日

IDEA创建SpringBoot项目报Invalid artifact id

在创建SpringBoot项目时报Invalid artifact id错误

image

解决方法

1.Artifact id命名时字母小写

2.不要带特殊符号

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 11月6日

解决打包时Process terminated错误

这种情况一般是因为maven 配置的setting.xml文件有误.

image

maven项目编译报错如下:

image

点击【项目名】提示

image

也就是说找不到xml文件

解决方案

1.双击shift 找到setting.xml 重新引入即可

2.再次执行maven install打包操作

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月31日

SpringCloud梳理-Feign

1.Feign

为服务应用中,ribbon和hystrix同时出现,feign整合两者,并提供了声明式消费者客户端.
feign是一种集成工具功能有:远程调用,ribbon-负载均衡和重试,hystrix-降级 熔断

1.1Feign远程调用

Fegin提供了声明式客户端,只需要定义一个接口,就可以通过接口做远程调用.具体调用代码通过动态代理添加.
// 调用商品服务的远程调用接口

// 通过注解配置3件事:调用哪个服务,调用什么路径,向这个路径提交什么参数

@FeignClient(name="item-service")

public interface ItemFeignClient {

 @GetMapping("/{orderId}")

 JsonResult<List<Item>> getItems(@PathVariable String orderId);

}

1.1.1添加声明式客户端

1.OpenFegin依赖
2.启动类添加@EnableFeignClient
3.定义声明式客户端接口 例如:ItemFeignClient UserFeignClient OrderFeignClient
4.添加一个测试用的控制器,使用三个声明式客户端接口调用远程服务.

1.2Feign集成Ribbon

负载均衡 重试
Feign默认启用了Ribbon的负载均衡和重试 0配置.
默认重试参数:MaxAutoRetries=0 MaxAutoRetriesNextServer=1 ReadTimeout=1000
重试参数配置
# 对所有服务都有效

ribbon:

 MaxAutoRetries: 1

# 对 item-service 单独配置,对其他服务无效

item-service:

 ribbon: 

 MaxAutoRetries: 0

1.3Feign集成Hystrix

默认不启用hystrix,Feign不推荐启用hystrix

1.3.1如果有特殊需求要启用hystrix,首先做基础配置

1.添加hystrix完整依赖
2.添加@EnableCircuitBreaker
3.yml配置feign.hystrix.enable=true

1.3.2降级

在声明式客户端接口的注解中,制定一个降级类(之前的是降级方法)
@FeignClient(name="item-service", fallback=降级类.class)

public interface ItemFeignClient {

 ....

}
降级类要作为声明式客户端接口的子类来定义.

1.3.3hystrix监控

用actuator暴露hystrix.stream监控端点
1.actuator依赖
2.暴露监控端点
m.e.w.e.i=hystrix.stream
3.http://localhost:3001/actuator/hystrix.stream
4.通过调用后台服务,产生监控数据.

1.4熔断

10秒内20次请求,50%失败执行了降级代码.

2.代码展示

用feign代替hystrix+ribbon

image

2.1新建fegin项目

image

image

2.2pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>sp09-feign</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp09-feign</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.3application.yml

spring:
  application:
    name: feign
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

2.4主程序添加@EnableDiscoveryClient和@EnableFeignClients注解

package cn.tedu.sp09;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Sp09FeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp09FeignApplication.class, args);
    }

}

2.5.1 ItemFeignService

package cn.tedu.sp09.service;

import java.util.List;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@FeignClient("item-service")
public interface ItemFeignService {
    @GetMapping("/{orderId}")
    JsonResult<List<Item>> getItems(@PathVariable String orderId);

    @PostMapping("/decreaseNumber")
    JsonResult decreaseNumber(@RequestBody List<Item> items);
}

2.5.2 UserFeignService

package cn.tedu.sp09.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@FeignClient("user-service")
public interface UserFeignService {
    @GetMapping("/{userId}")
    JsonResult<User> getUser(@PathVariable Integer userId);

    // 拼接路径 /{userId}/score?score=新增积分
    @GetMapping("/{userId}/score") 
    JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}

2.5.3 UserFeignService

package cn.tedu.sp09.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;

@FeignClient("order-service")
public interface OrderFeignService {
    @GetMapping("/{orderId}")
    JsonResult<Order> getOrder(@PathVariable String orderId);

    @GetMapping("/")
    JsonResult addOrder();

}

2.5.4 FeignController

package cn.tedu.sp09.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.service.ItemFeignService;
import cn.tedu.sp09.service.OrderFeignService;
import cn.tedu.sp09.service.UserFeignService;
import cn.tedu.web.util.JsonResult;

@RestController
public class FeignController {
    @Autowired
    private ItemFeignService itemService;
    @Autowired
    private UserFeignService userService;
    @Autowired
    private OrderFeignService orderService;
    
    @GetMapping("/item-service/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
        return itemService.getItems(orderId);
    }

    @PostMapping("/item-service/decreaseNumber")
    public JsonResult decreaseNumber(@RequestBody List<Item> items) {
        return itemService.decreaseNumber(items);
    }

    /
    
    @GetMapping("/user-service/{userId}")
    public JsonResult<User> getUser(@PathVariable Integer userId) {
        return userService.getUser(userId);
    }

    @GetMapping("/user-service/{userId}/score") 
    public JsonResult addScore(@PathVariable Integer userId, Integer score) {
        return userService.addScore(userId, score);
    }
    
    /
    
    @GetMapping("/order-service/{orderId}")
    public JsonResult<Order> getOrder(@PathVariable String orderId) {
        return orderService.getOrder(orderId);
    }

    @GetMapping("/order-service")
    public JsonResult addOrder() {
        return orderService.addOrder();
    }
}

2.6调用流程

image

2.7启动服务并测试

image

http://eureka1:2001

http://localhost:3001/item-service/35

http://localhost:3001/user-service/7

http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc

http://localhost:3001/order-service/

源码: https://github.com/benwang6/spring-cloud-repo

3.Feign+Ribbon负载均衡和重试

无需额外配置,feign默认已启用了ribbon负载均衡和重试机制,可以通过配置对参数进行调整.
重试的默认配置参数
ConnectTimeout=1000
ReadTimeout=1000
MaxAutoRetries=0
MaxAutoRetriesNextServer=1

3.1application.yml配置ribbon超时和重试

spring:
  application:
    name: feign
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  
item-service:
  ribbon:
    MaxAutoRetries: 1
    MaxAutoRetriesNextServer: 2
    ConnectTimeout: 1000
    ReadTimeout: 500

3.2启动服务,访问测试

http://localhost:3001/item-service/35

4.Feign+hystrix降级

feign默认没有启用hystrix,添加配置,启用hystrix

4.1application.yml配置

feign:
  hystrix:
    enabled: true
启用后访问服务器:

http://localhost:3001/item-service/35

默认一秒会快速失败,没有降级方法,会显示白半页.

image

可以添加配置暂时减少降级超时时间,以便后续对降级进行测试
......

feign:
  hystrix:
    enabled: true
    
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

4.feign+hystrix降级

4.1feign远程接口中指定降级类

远程调用失败后会执行降级代码

4.2指定降级类

4.2.1 ItemFeignService

...
@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
...

4.2.2UserFeignService

...
@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
...

4.2.3OrderFeignService

...
@FeignClient(name="order-service",fallback = OrderFeignServiceFB.class)
public interface OrderFeignService {
...

4.3降级类

4.3.1 ItemFeignServiceFB

package cn.tedu.sp09.service;

import java.util.List;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@Component
public class ItemFeignServiceFB implements ItemFeignService {

    @Override
    public JsonResult<List<Item>> getItems(String orderId) {
        return JsonResult.err("无法获取订单商品列表");
    }

    @Override
    public JsonResult decreaseNumber(List<Item> items) {
        return JsonResult.err("无法修改商品库存");
    }

}

4.3.2UserFeignServiceFB

package cn.tedu.sp09.service;

import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@Component
public class UserFeignServiceFB implements UserFeignService {

    @Override
    public JsonResult<User> getUser(Integer userId) {
        return JsonResult.err("无法获取用户信息");
    }

    @Override
    public JsonResult addScore(Integer userId, Integer score) {
        return JsonResult.err("无法增加用户积分");
    }

}

4.3.3 OrderFeignServiceFB

package cn.tedu.sp09.service;

import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;

@Component
public class OrderFeignServiceFB implements OrderFeignService {

    @Override
    public JsonResult<Order> getOrder(String orderId) {
        return JsonResult.err("无法获取商品订单");
    }

    @Override
    public JsonResult addOrder() {
        return JsonResult.err("无法保存订单");
    }

}

4.4启动服务,访问测试

image

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月30日

SpringCloud梳理-hystrix断路器

1.Hyxtrix

系统容错工具

hystrix的主要功能:降级熔断

1.1降级

调用远程服务失败(异常、超时、服务不存在),可以通过执行当前服务中的一段代码来向客户端发回响应

降级响应

错误提示
返回缓存数据

快速失败

即使后台服务故障,也要让客户端尽快得到错误提示,而不能让客户端等待

1.2添加降级

1.添加 Hystrix 依赖
2.启动类添加 @EnableCircuitBreaker
3.添加降级代码 在远程调用方法上添加@HystrixCommand(fallbackMethod="降级方法")完成降级方法,返回降级响应.

1.2hystrix 超时

默认1秒超时,执行降级
如果配置了ribbon重试,重试还会继续执行,最终重试结果无效
Hystrix超时 >= Ribbon 总的超时时长

1.3hystrix 熔断

当请求量增大、出现过多错误,hystrix可以和后台服务断开连接(过热保护)
可以避免雪崩效应、故障传播

限流措施

流量过大时造成服务故障,可以断开服务,降低它的流量

在特定条件下会自动触发熔断

10秒内20次请求(必须首先满足)
50%出错,执行降级代码.

半开状态下可以自动恢复.

断路器打开几秒后,进入半开状态,尝试发送请求
如果请求成功自动关闭断路器恢复正常
如果请求失败,再保持打开几秒钟

1.4Hystrix Dashboard

Hystrix监控仪表盘,监控Hystrix降级和熔断的错误信息.

1.4.1 actuator

springboot提供的项目监控工具,提供了多种项目的监控数据.
1.健康状态
2.系统环境
3.beans-spring容器中所有的对象
4.mappings - spring mvc 所有映射的路径
......
hystrix在actuator中,添加了自己的监控数据.

1.4.2添加actuator

1.添加actuator依赖
2.yml配置暴露监控信息
m.e.w.e.i="*" - 暴露所有监控
m.e.w.e.i=["health", "beans", "mappings"]
m.e.w.e.i=bean
3.http://xxxxxxxxx/actuator/

1.5搭建Hystrix Dashboard

1.添加Hystrix Dashboard依赖
2.yml配置配置端口
3.添加@EnableHystrixDashboard注解
4.访问访问 http://xxxxxxx/hystrix在输入框填写要监控的数据路径.

2.代码演示

2.1、ribbon + hystrix 断路器

https://github.com/Netflix/Hystrix/wiki

image

2.2微服务宕机时,ribbon无法转发请求.

关闭item-service

image

image

2.3复制ribbon项目,改名为hytrix

image

2.3.1修改pom.xml文件

image

2.3.2添加hystrix起步依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

2.3.3修改application.yml文件

image


spring:
  application:
    name: hystrix
    
server:
  port: 3001
  
eureka:
  client:    
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  MaxAutoRetries: 1
  MaxAutoRetriesNextServer: 2
  OkToRetryOnAllOperations: true

2.3.4启动类添加@EnableCircuitBreaker注解启用hystrix断路器

启动断路器,断路器提供的两大核心
降级,超时 出错 不可到达时 对服务器降级 返回错误信息或者是缓存数据.
熔断当服务压力过大时 错误比例过多时 熔断有所请求时 所有请求直接降级.
package com.tedu.sp06;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

//@EnableCircuitBreaker
//@EnableDiscoveryClient
//@SpringBootApplication

@SpringCloudApplication
public class Sp06RibbonApplication {

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate() {
        SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory();
        f.setConnectTimeout(1000);
        f.setReadTimeout(1000);
        return new RestTemplate(f);
        
        //RestTemplate 中默认的 Factory 实例中,两个超时属性默认是 -1,
        //未启用超时,也不会触发重试
        //return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(Sp06RibbonApplication.class, args);
    }

}

2.4RibbonController中添加降级方法

为每个方法添加降级方法.
添加@HystrixCommand注解,指定降级方法名
package com.tedu.sp06.consoller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.tedu.sp01.pojo.Item;
import com.tedu.sp01.pojo.Order;
import com.tedu.sp01.pojo.User;
import com.tedu.web.util.JsonResult;

@RestController
public class RibbonController {
    @Autowired
    private RestTemplate rt;
    
    @GetMapping("/item-service/{orderId}")
    @HystrixCommand(fallbackMethod = "getItemsFB") //指定降级方法的方法名
    public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
        return rt.getForObject("http://item-service/{1}", JsonResult.class, orderId);
    }

    @PostMapping("/item-service/decreaseNumber")
    @HystrixCommand(fallbackMethod = "decreaseNumberFB")
    public JsonResult decreaseNumber(@RequestBody List<Item> items) {
        return rt.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
    }

    /
    
    @GetMapping("/user-service/{userId}")
    @HystrixCommand(fallbackMethod = "getUserFB")
    public JsonResult<User> getUser(@PathVariable Integer userId) {
        return rt.getForObject("http://user-service/{1}", JsonResult.class, userId);
    }

    @GetMapping("/user-service/{userId}/score") 
    @HystrixCommand(fallbackMethod = "addScoreFB")
    public JsonResult addScore(@PathVariable Integer userId, Integer score) {
        return rt.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class, userId, score);
    }
    
    /
    
    @GetMapping("/order-service/{orderId}")
    @HystrixCommand(fallbackMethod = "getOrderFB")
    public JsonResult<Order> getOrder(@PathVariable String orderId) {
        return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId);
    }

    @GetMapping("/order-service")
    @HystrixCommand(fallbackMethod = "addOrderFB")
    public JsonResult addOrder() {
        return rt.getForObject("http://order-service/", JsonResult.class);
    }
    
    /

    //降级方法的参数和返回值,需要和原始方法一致,方法名任意
    public JsonResult<List<Item>> getItemsFB(String orderId) {
        return JsonResult.err("获取订单商品列表失败");
    }
    public JsonResult decreaseNumberFB(List<Item> items) {
        return JsonResult.err("更新商品库存失败");
    }
    public JsonResult<User> getUserFB(Integer userId) {
        return JsonResult.err("获取用户信息失败");
    }
    public JsonResult addScoreFB(Integer userId, Integer score) {
        return JsonResult.err("增加用户积分失败");
    }
    public JsonResult<Order> getOrderFB(String orderId) {
        return JsonResult.err("获取订单失败");
    }
    public JsonResult addOrderFB() {
        return JsonResult.err("添加订单失败");
    }

}

2.5hystrix短路超时设置

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
为了测试hystrix短路功能,我们把hystrix等待超时设置得非常小(500毫秒)
此设置一般应大于ribbon的重试超时时长,例如10秒

spring:
  application:
    name: hystrix
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  MaxAutoRetriesNextServer: 2
  MaxAutoRetries: 1
  OkToRetryOnAllOperations: true
  
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500
            

2.6启动项目测试

image

通过hystrix服务,访问可能超时失败的item-service(设置了延迟)

http://localhost:3001/item-service/35

通过hystrix服务,访问未启动的user-service

http://localhost:3001/user-service/7

可以看到,如果item-service请求超时时,hystrix会立即执行降级方法.
访问user-service,由于该服务未启动,hystrix也会立即执行降级方法.

image

3.hystrix dashboard 断路器仪表盘

image

hystrix对请求的熔断和断路处理,可以产生监控信息,提供了各种监控信息的监控端点.
management.endpoints.web.exposure.include 配置选项,可以指定端点名,来暴露监控端点.
如果要暴露全部端点可以用"*"

3.1修改pom.xml文件

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3.2调整application配置,并暴露hystrix监控端点

spring:
  application:
    name: hystrix
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  MaxAutoRetriesNextServer: 1
  MaxAutoRetries: 1
  OkToRetryOnAllOperations: true
  
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

3.3访问actuator路径,查看监控端点

http://localhost:3001/actuator

image

4.新建hystrix-dashboard项目

image

image

4.1修改pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tedu</groupId>
    <artifactId>sp08-hystrix-dashboard</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp08-hystrix-dashboard</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


4.2修改application.yml

spring:
  application:
    name: hystrix-dashboard
    
server:
  port: 4001

eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

4.3修改启动类添加@EnableHystrixDashboard注解和@EnableDiscoveryClient注解

package com.tedu.sp08;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@EnableDiscoveryClient
@EnableHystrixDashboard
@SpringBootApplication
public class Sp08HystrixDashboardApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp08HystrixDashboardApplication.class, args);
    }

}

4.4启动,访问测试

image

访问hystrix dashboard

http://localhost:4001/hystrix
image

填入hystrix的监控点 开启监控

http://localhost:3001/actuator/hystrix.stream
image

通过hystrix多次访问 观察监控信息

http://localhost:3001/item-service/35

http://localhost:3001/user-service/7

http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc

http://localhost:3001/order-service/

image

image

4.5hystrix熔断

整个链路达到一定的阀值,默认情况下 10秒内产生超过20次请求,则符合第一个条件.
满足第一个条件的情况下,如果请求的错误百分比大于阈值,则会打开断路器,默认为50%.
hystrix逻辑,先判断是否满足第一个条件,在判断第二个条件,如果两个条件都满足,则会开启断路器.
短路器开启5秒后,会处于半开状态,会尝试转发请求,如果仍然失败,保持打开状态,如果成功,则关闭断路器.

4.6使用apache的并发访问测试工具ab

http://httpd.apache.org/docs/current/platform/windows.html#down
image

使用ab工具,以并发50次,来发送2000个请求.
ab -n 20000 -c 50 http://localhost:3001/item-service/35
断路器状态为Open,所有请求会被短路,直接降级执行fallback方法.

image

4.7hystrix配置

https://github.com/Netflix/Hystrix/wiki/Configuration

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 请求超时时间,超时后触发失败降级.
hystrix.command.default.circuitBreaker.requestVolumeThreshold 10秒内请求数量,默认20,如果没有达到改数量,既使请求全部失败,也不会触发断路器打开.
hystrix.command.default.circuitBreaker.errorThresholdPercentage 失败请求百分比,达到该比例则触发断路器打开.
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 断路器打开多长时间后,再次允许访问尝试(半开),仍失败则继续保持打开状态,如果成功访问关闭断路器,默认5000.
查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月30日

SpringCloud梳理-ribbon

1.ribbon服务消费者

image

ribbon提供了负载均衡和重试的功能

1.1负载均衡

1.从eureka获得地址表
2.使用多个地址来回调用
3.拿到一个地址,使用RestTemplate执行远程调用

添加负载均衡

1.添加ribbon依赖(eureka client中已经包含,不要重复添加)
2.添加@LoadBalance注解,对RestTemplate进行增强
3.用RestTemplate调用的地址,改成service-id(注册中心注册的服务名) rt.getForObject("http://item-service/{1}", ......)

1.2ribbon重试

一种容错方式,调用远程服务失败(异常,超时)时,可以自动进行重试调用.

添加重试

1.添加spring-retry依赖
2.配置重试参数
MaxAutoRtries-单台服务器的重试次数
MaxAutoRtriesNextServer - 更换服务器的次数
OkToRetryOnAllOperations - 是否对所有类型请求都进行重试,默认只对get重试
ConnectTimeout - 和远程服务建立连接的等待超时时长
ReadTimeout - 建立连接并发送请求后,等待响应的超时时长

注意:两个超时时长不能再配置文件中设置,而是在java代码中设置.

2.代码演示

2.1新建ribbon项目

image
image

2.2添加依赖

eureka-client中已经包含ribbon依赖
需要添加sp01-commons依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tedu</groupId>
    <artifactId>springcloud-012-ribbon</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-012-ribbon</name>
    <description>Demo project for Spring Cloud</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.tedu</groupId>
            <artifactId>sp01-commons</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.3配置application.yml文件

spring:
  application:
    name: ribbon
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka

2.3代码设置

创建RestTemplate实例
RestTemplate是用来调用其他微服务的工具类,封装了远程调用代码,提供了一组用于远程调用的模板方法,例如:getForObject() postForObject()等

2.3.1启动类设置

package com.tedu.sp06;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class Sp06RibbonApplication {
    
    //创建 RestTemplate 实例,并存入 spring 容器
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(Sp06RibbonApplication.class, args);
    }

}

2.3.2 RibbonController

package com.tedu.sp06.consoller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.tedu.sp01.pojo.Item;
import com.tedu.sp01.pojo.Order;
import com.tedu.sp01.pojo.User;
import com.tedu.web.util.JsonResult;

@RestController
public class RibbonController {
    @Autowired
    private RestTemplate rt;
    
    @GetMapping("/item-service/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
        //向指定微服务地址发送 get 请求,并获得该服务的返回结果 
        //{1} 占位符,用 orderId 填充
        return rt.getForObject("http://localhost:8001/{1}", JsonResult.class, orderId);
    }

    @PostMapping("/item-service/decreaseNumber")
    public JsonResult decreaseNumber(@RequestBody List<Item> items) {
        //发送 post 请求
        return rt.postForObject("http://localhost:8001/decreaseNumber", items, JsonResult.class);
    }

    /
    
    @GetMapping("/user-service/{userId}")
    public JsonResult<User> getUser(@PathVariable Integer userId) {
        return rt.getForObject("http://localhost:8101/{1}", JsonResult.class, userId);
    }

    @GetMapping("/user-service/{userId}/score") 
    public JsonResult addScore(
            @PathVariable Integer userId, Integer score) {
        return rt.getForObject("http://localhost:8101/{1}/score?score={2}", JsonResult.class, userId, score);
    }
    
    /
    
    @GetMapping("/order-service/{orderId}")
    public JsonResult<Order> getOrder(@PathVariable String orderId) {
        return rt.getForObject("http://localhost:8201/{1}", JsonResult.class, orderId);
    }

    @GetMapping("/order-service")
    public JsonResult addOrder() {
        return rt.getForObject("http://localhost:8201/", JsonResult.class);
    }
}

2.4启动服务,并访问测试

image

2.4.1测试地址

http://eureka1:2001
http://localhost:3001/item-service/35
http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100
http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/

3.ribbon负载均衡引入

修改sp06-ribbon项目
1.添加ribbon起步依赖(可选)
2.RestTemplate设置@LoadBanlance
3.访问路径修改为服务id

3.1添加ribbon起步依赖

eureka依赖中已经包含了ribbon依赖,可以不添加.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

3.2RestTemplate添加@LoadBalance注解

@LoadBalance负载均衡注解,会对RestTemplate实例进行封装,创建动态代理对象,并切入(AOP)负载均衡代码,把请求分散分发到集群中的服务器中.
启动类修改
package com.tedu.sp06;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class Sp06RibbonApplication {
    
    @LoadBalanced //负载均衡注解
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(Sp06RibbonApplication.class, args);
    }

}

3.3访问路径设置为服务id

package com.tedu.sp06.consoller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.tedu.sp01.pojo.Item;
import com.tedu.sp01.pojo.Order;
import com.tedu.sp01.pojo.User;
import com.tedu.web.util.JsonResult;

@RestController
public class RibbonController {
    @Autowired
    private RestTemplate rt;
    
    @GetMapping("/item-service/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
        //这里服务器路径用 service-id 代替,ribbon 会向服务的多台集群服务器分发请求
        return rt.getForObject("http://item-service/{1}", JsonResult.class, orderId);
    }

    @PostMapping("/item-service/decreaseNumber")
    public JsonResult decreaseNumber(@RequestBody List<Item> items) {
        return rt.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
    }

    /
    
    @GetMapping("/user-service/{userId}")
    public JsonResult<User> getUser(@PathVariable Integer userId) {
        return rt.getForObject("http://user-service/{1}", JsonResult.class, userId);
    }

    @GetMapping("/user-service/{userId}/score") 
    public JsonResult addScore(
            @PathVariable Integer userId, Integer score) {
        return rt.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class, userId, score);
    }
    
    /
    
    @GetMapping("/order-service/{orderId}")
    public JsonResult<Order> getOrder(@PathVariable String orderId) {
        return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId);
    }

    @GetMapping("/order-service")
    public JsonResult addOrder() {
        return rt.getForObject("http://order-service/", JsonResult.class);
    }
}

3.4访问测试

http://localhost:3001/item-service/34

4.ribbon重试引入

4.1添加依赖 pom.xml添加spring-retry依赖

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

4.2application.yml文件配置ribbon重试

spring:
  application:
    name: ribbon
    
server:
  port: 3001
  
eureka:
  client:    
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
ribbon:
  MaxAutoRetriesNextServer: 2
  MaxAutoRetries: 1
  OkToRetryOnAllOperations: true

4.3主启动类### 设置 RestTemplate 的请求工厂的超时属性

package com.tedu.sp06;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class Sp06RibbonApplication {

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate() {
        SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory();
        f.setConnectTimeout(1000);
        f.setReadTimeout(1000);
        return new RestTemplate(f);
        
        //RestTemplate 中默认的 Factory 实例中,两个超时属性默认是 -1,
        //未启用超时,也不会触发重试
        //return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(Sp06RibbonApplication.class, args);
    }

}

4.4item-service 的 ItemController 添加延迟代码,以便测试 ribbon 的重试机制

package com.tedu.sp02.item.controller;

import java.util.List;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.tedu.sp01.pojo.Item;
import com.tedu.sp01.service.ItemService;
import com.tedu.web.util.JsonResult;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
public class ItemController {
    @Autowired
    private ItemService itemService;
    
    @Value("${server.port}")
    private int port;
    
    @GetMapping("/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable String orderId) throws Exception {
        log.info("server.port="+port+", orderId="+orderId);

        ///--设置随机延迟
        long t = new Random().nextInt(5000);
        if(Math.random()<0.6) { 
            log.info("item-service-"+port+" - 暂停 "+t);
            Thread.sleep(t);
        }
        ///~~
        
        List<Item> items = itemService.getItems(orderId);
        return JsonResult.ok(items).msg("port="+port);
    }
    
    @PostMapping("/decreaseNumber")
    public JsonResult decreaseNumber(@RequestBody List<Item> items) {
        itemService.decreaseNumbers(items);
        return JsonResult.ok();
    }
}

访问测试重启机制

http://localhost:3001/item-service/35

ribbon的重试机制,在 feign 和 zuul 中进一步进行了封装,后续可以使用feign或zuul的重试机制

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月29日

SpringCloud梳理-eureka

SpringCloud介绍

spring cloud 是一系列框架的集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。spring cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 spring boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
spring cloud 对于中小型互联网公司来说是一种福音,因为这类公司往往没有实力或者没有足够的资金投入去开发自己的分布式系统基础设施,使用 spring cloud 一站式解决方案能在从容应对业务发展的同时大大减少开发成本。同时,随着近几年微服务架构和 docker 容器概念的火爆,也会让 spring cloud 在未来越来越“云”化的软件开发风格中立有一席之地,尤其是在目前五花八门的分布式解决方案中提供了标准化的、一站式的技术方案,意义可能会堪比当年 servlet 规范的诞生,有效推进服务端软件系统技术水平的进步。

SpringCloud技术组成

eureka

微服务治理,服务注册和发现

ribbon

负载均衡,请求重试

hystrix

短路器,服务降级,熔断

feign

ribbon+hystrix继承,并提供生命式客户端

hystrix dashboard和turbine

hystrix微服务监控

zuul

API网关,提供微服务的统一入口,并提供统一的权限验证

config

配置中心

bus

消息总线,配置刷新

sleuth+zipkin

链路跟踪

SpringCloud和Dubbo对比

Dubbo

Dubbo只是一种远程调用(RPC)框架
默认基于长连接,支持多种序列化格式

SpringCloud

框架集
提供了一整套微服务解决方案(全家桶)

eureka注册中心

作用:服务注册和发现

image

提供者(Provider)
向注册中心注册自己的地址
消费者(Consumer)
从注册中心发现其他服务

eureka的运行机制

注册: 一次次反反复复连接eureka,直到注册成功为止.
拉取: 每隔30秒拉取一次注册表,更新注册信息.
心跳: 每30秒发送一次心跳,3次收不到心跳eureka会删除这个服务.
自我保护模式: 特殊情况,由于网络不稳定15分钟内85%的服务器出现心跳异常.
保护所有注册信息不删除
网络恢复后可以自动退出保护模式
开发测试期间,可以关闭保护模式

搭建eureka服务

1.创建eureka项目

image
image

2.配置依赖pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tedu</groupId>
    <artifactId>sp05-eureka</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp05-eureka</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3.配置application.yml文件

spring:
  application:
    name: eureka
    
server:
  port: 2001
  
eureka:
  server:
    enable-self-preservation: false  #关闭保护模式
  instance:
    hostname: eureka1    #主机名(集群中区分每台服务器)
  client:
    register-with-eureka: false 
    fetch-registry: false
    #针对单台服务器,不向自己注册,不从自己拉取
  

4.启动类注解添加 :@EnableEurekaServer 触发eureka服务器的自动配置

package com.tedu.sp05;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer  //触发eureka服务器的自动配置
@SpringBootApplication
public class Sp05EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp05EurekaApplication.class, args);
    }

}

5.修改host文件, 添加eureka域名映射

添加内容
127.0.0.1       eureka1
127.0.0.1       eureka2

image

6.启动并访问测试

访问:* http://eureka1:2001

效果如下

image

项目中添加eureka

1.添加eureka client依赖

 <dependency>

 <groupId>org.springframework.cloud</groupId>

 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

 </dependency>
<dependencyManagement>

 <dependencies>

 <dependency>

 <groupId>org.springframework.cloud</groupId>

 <artifactId>spring-cloud-dependencies</artifactId>

 <version>Hoxton.SR8</version>

 <type>pom</type>

 <scope>import</scope>

 </dependency>

 </dependencies>

 </dependencyManagement>

2.yml配置eureka连接地址

eureka:

  client:

    service-url:

      defaultZone: http://eureka1:2001/eureka

3.启动类添加注解 @EnableDiscoveryClient

4.启动并访问测试

访问:http://eureka1:2001

效果如下

image

eureka的高可用

1.配置application.yml文件

spring:
  application:
    name: eureka-server
    
#server:
#  port: 2001

eureka:
  server:
    enable-self-preservation: false
#  instance:
#    hostname: eureka1
#  client:
#    register-with-eureka: false
#    fetch-registry: false

---
spring:
  profiles: eureka1

server:
  port: 2001
  
# eureka1 向 eureka2 注册
eureka:
  instance:
    hostname: eureka1
  client:
    service-url: 
      defaultZone: http://eureka2:2002/eureka

---
spring:
  profiles: eureka2

server:
  port: 2002
  
# eureka2 向 eureka1 注册
eureka:
  instance:
    hostname: eureka2
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka
    

2.配置启动参数

eureka1启动参数

--spring.profiles.active=eureka1

image
image

eureka2启动参数

--spring.profiles.active=eureka2

image
image
image

3.访问eureka服务器 查看注册信息

访问地址:http://eureka1:2001/

image

访问地址:http://eureka2:2002/

image

4.eureka客户端注册时向两个服务器注册

在项目中的yml文件中添加eureka服务器路径

eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

当一个eureka服务器宕机时可以使用另一台.

效果如下

image

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月28日

京淘Day14

1.Redis分片机制

1.1为什么需要分片机制

如果需要储存海量的内存数据,如果只使用一台redis,无法保证redis的工作效率.大量时间都浪费在了寻址当中.所以需要一种机制满足该要求.

采用分片机制实现

image

1.2Redis分片搭建

1.2.1搭建注意事项

redis服务的启动都依赖于redis.conf的配置文件.如果需要准备3台redis.则需要准备3个redis.conf的配置.
准备端口号
1. 6379 2. 6380 3. 6381

1.2.2分片的实现

image

修改端口号:将各自的端口号进行修改

image

启动3台redis服务器

image

校验服务是否正常运行

image

1.2.3关于分片的注意事项

1.问题描述:
当启动多台redis服务器以后,多台redis暂时没有必然的联系,各自都是独立的实体.可以数据的储存.如图所示
2.如果将分片通过程序的方式进行操作,要把3台redis当做一个整体,所以与上述的操作完全不同.不会出现一个key同时保存到多个redis的现象.

image

1.3分片入门案例

 /**
     * 测试Redis分片机制
     * 思考: shards 如何确定应该存储到哪台redis中呢???
     */
    @Test
    public void testShards(){
        List<JedisShardInfo> shards = new ArrayList<>();
        shards.add(new JedisShardInfo("192.168.126.129",6379));
        shards.add(new JedisShardInfo("192.168.126.129",6380));
        shards.add(new JedisShardInfo("192.168.126.129",6381));
        //准备分片对象
        ShardedJedis shardedJedis = new ShardedJedis(shards);
        shardedJedis.set("shards","redis分片测试");
        System.out.println(shardedJedis.get("shards"));
    }

1.4一致性hash算法

1.4.0常识说明

常识1: 一般的hash是8位16进制数. 0-9 A-F (24)8 = 2^32
常识2:常识2: 如果对相同的数据进行hash运算 结果必然相同的.
常识3: 一个数据1M 与数据1G的hash运算的速度一致.

1.4.1一致性hash算法介绍

一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。 [1] 在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 [2] 。

image

1.4.2特性1:平衡性

概念:平衡性是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡的问题(大致平均).
问题描述:由于节点都是通过hash方式进行计算.所以可能出现 如图所示的现象导致负载严重不平衡.

image

解决方法:引入虚拟的节点

image

1.4.3特性2:单调性

特点:单调性是指在新增或者删减节点时,不影响系统正常运行

image

1.4.4特性3:分散性

谚语鸡蛋不要放到一个篮子里.

分散性是指数据应该分散的存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都储存数据.

1.5SpringBoot整合Redis分片

1.5.1编辑配置文件

# 配置redis单台服务器
redis.host=192.168.126.129
redis.port=6379

# 配置redis分片机制
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

1.5.2编辑配置类

@Configuration
@PropertySource("classpath:/properties/redis.properties")
public class JedisConfig {

    @Value("${redis.nodes}")
    private String nodes;  //node,node,node.....

    //配置redis分片机制
    @Bean
    public ShardedJedis shardedJedis(){
        nodes = nodes.trim();   //去除两边多余的空格
        List<JedisShardInfo> shards = new ArrayList<>();
        String[] nodeArray = nodes.split(",");
        for (String strNode : nodeArray){   //strNode = host:port
            String host = strNode.split(":")[0];
            int port = Integer.parseInt(strNode.split(":")[1]);
            JedisShardInfo info = new JedisShardInfo(host, port);
            shards.add(info);
        }
        return new ShardedJedis(shards);
    }
   }

1.5.3修改AOP注入项

image

Redis哨兵机制

2.1关于Redis分片说明

优点:实现内存数据的扩容
缺点:如果redis分片中有一个节点出现问题,则整个redis分片机制用户访问必然有问题 直接影响用户使用.
解决方案:实现redis的高可用

2.2配置Redis主从结构

策略划分:1主2从 6379主 6380/6381从
1.将分片的目录复制 改名为sentinel

image

2.重启3台redis服务器

image

3.检查redis节点的主从状态

image

4.实现主从挂载

image

5.检查主机状态

image

2.3哨兵的工作原理

image

原理说明:
1.配置redis主从结构
2.哨兵服务启动时,会监控当前的主机,同时获取主机的详情信息(主从结构)
3.当哨兵利用心跳检测机制(PING-PONG)连续三次都没有收到主机的反馈信息则判定主机宕机.
4.当哨兵发现主机宕机以后,则开启选举机制,在当前的从机中挑选一台Redis当做主机.

2.4配置哨兵配置文件

1.复制配置文件
cp sentinel.conf sentinel/
2.修改保护模式

image

3.开启后台运行

image

4.设置哨兵的监控
其中的1表示投票生成的票数 当前只有一个哨兵所以写1

image

5.修改宕机时间

image

6.选举失败的时间

image

7.启动哨兵服务

image

2.5Redis哨兵高可用性

测试步骤:
1.检查主机状态
2.将redis主服务器宕机 等待10秒 之后检查从机是否当选新的主机

image

3.重启6379服务器,检查是否成为新主机的从

image

2.6哨兵入门案例

/**
     * 测试Redis哨兵
     */
    @Test
    public void testSentinel(){
        Set<String> set = new HashSet<>();
        //1.传递哨兵的配置信息
        set.add("192.168.126.129:26379");
        JedisSentinelPool sentinelPool =
                new JedisSentinelPool("mymaster",set);
        Jedis jedis = sentinelPool.getResource();
        jedis.set("aa","哨兵测试");
        System.out.println(jedis.get("aa"));
    }

2.7SpringBoot整合Redis哨兵

2.7.1 编辑pro配置文件

# 配置redis单台服务器
redis.host=192.168.126.129
redis.port=6379

# 配置redis分片机制
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

# 配置哨兵节点
redis.sentinel=192.168.126.129:26379

2.7.2编辑redis配置类

@Configuration
@PropertySource("classpath:/properties/redis.properties")
public class JedisConfig {

    @Value("${redis.sentinel}")
    private String sentinel;        //暂时只有单台

    @Bean
    public JedisSentinelPool jedisSentinelPool(){

        Set<String> sentinels = new HashSet<>();
        sentinels.add(sentinel);
        return new JedisSentinelPool("mymaster",sentinels);
    }
  }

2.7.3修改CacheAOP中的注入项

package com.jt.aop;

import com.jt.anno.CacheFind;
import com.jt.config.JedisConfig;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.ShardedJedis;

import java.lang.reflect.Method;
import java.util.Arrays;

@Aspect     //我是一个AOP切面类
@Component  //将类交给spring容器管理
public class CacheAOP {

    @Autowired
    //private Jedis jedis;      //单台redis
    //private ShardedJedis jedis; //分片机制
    private JedisSentinelPool jedisSentinelPool;

    /**
     * 切面 = 切入点 + 通知方法
     *        注解相关 + 环绕通知  控制目标方法是否执行
     *
     *  难点:
     *      1.如何获取注解对象
     *      2.动态生成key  prekey + 用户参数数组
     *      3.如何获取方法的返回值类型
     */
    @Around("@annotation(cacheFind)")  //参数传递变量的传递
    //@Around("@annotation(com.jt.anno.CacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
        //从池中获取jedis对象
        Jedis jedis = jedisSentinelPool.getResource();
        Object result = null;
        try {
            //1.拼接redis存储数据的key
            Object[] args = joinPoint.getArgs();
            String key = cacheFind.preKey() +"::" + Arrays.toString(args);

            //2. 查询redis 之后判断是否有数据
            if(jedis.exists(key)){
                //redis中有记录,无需执行目标方法
                String json = jedis.get(key);
                //动态获取方法的返回值类型   向上造型  向下造型
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                Class returnType = methodSignature.getReturnType();
                result = ObjectMapperUtil.toObj(json,returnType);
                System.out.println("AOP查询redis缓存");
            }else{
                //表示数据不存在,需要查询数据库
                result = joinPoint.proceed();  //执行目标方法及通知
                //将查询的结果保存到redis中去
                String json = ObjectMapperUtil.toJSON(result);
                //判断数据是否需要超时时间
                if(cacheFind.seconds()>0){
                    jedis.setex(key,cacheFind.seconds(),json);
                }else {
                    jedis.set(key, json);
                }
                System.out.println("aop执行目标方法查询数据库");
            }

        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        jedis.close();  //将使用完成的链接记得关闭.
        return result;
    }

}

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月24日

京淘Day15

1.实现Redis集群的搭建

1.1为什么需要搭建集群

redis分片的特点:

1.可以实现redis内存数据的扩容

2.redis分片本身没有高可用效果的 如果宕机将直接影响用户使用.

redis哨兵的特点:

1.Redis哨兵可以实现Redis节点的高可用性,但是哨兵本身没有实现高可用性机制(最好不要引入第三方).

2.Redis哨兵有主从结构 实现了内存数据的备份.但是没有实现内存数据的扩容效果.

升级

需要Redis内容扩容的同时需要Redis高可用性所以应该使用Redis集群.

1.2关于Redis集群搭建问题说明

1.关闭所有Redis服务器

sh stop.sh

2.删除多余文件

image

3.重启Redis服务器 执行挂载命令

redis-cli --cluster create --cluster-replicas 1 192.168.126.129:7000 192.168.126.129:7001 192.168.126.129:7002 192.168.126.129:7003 192.168.126.129:7004 192.168.126.129:7005

1.3Redis入门案例

@Test
    public void testCluster(){
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.126.129", 7000));
        nodes.add(new HostAndPort("192.168.126.129", 7001));
        nodes.add(new HostAndPort("192.168.126.129", 7002));
        nodes.add(new HostAndPort("192.168.126.129", 7003));
        nodes.add(new HostAndPort("192.168.126.129", 7004));
        nodes.add(new HostAndPort("192.168.126.129", 7005));
        JedisCluster jedisCluster = new JedisCluster(nodes);
        jedisCluster.set("cluster", "集群的测试!!!!");
        System.out.println(jedisCluster.get("cluster"));

    }

1.4关于选举机制-脑裂现象

说明:当集群进行选举时,如果连续3次都出现了平票的结果则可能出现脑裂现象.

问题:出现脑裂现象的概率是多少????1/8

数学建模:

抛硬币连续3次出现平票的概率是多少?1/8

第一次: 正正 正反 反正 反反 1/2

第二次: 正正 正反 反正 反反 1/2

第三次: 正正 正反 反正 反反 1/2

预防:增加主节点的数量可以有效降低脑裂现象的发生.

1.5关于集群的面试题

问题1:Redis集群中最多存储16384个数据????

错的,分区只负责数据的划分,数据的存储由内存决定.

crc16(key1)%16384=1000

crc16(key2)%16384=1000

问题2:Redis集群中最多有多少台主机??16384

一个主机占用一个槽道

1.6SpringBoot整合Redis集群

1.6.1编辑pro配置文件

# 配置redis单台服务器
redis.host=192.168.126.129
redis.port=6379

# 配置redis分片机制
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381

# 配置哨兵节点
redis.sentinel=192.168.126.129:26379

# 配置redis集群
redis.clusters=192.168.126.129:7000,192.168.126.129:7001,192.168.126.129:7002,192.168.126.129:7003,192.168.126.129:7004,192.168.126.129:7005

1.6.2编辑RedisConfig配置类

@Configuration
@PropertySource("classpath:/properties/redis.properties")
public class JedisConfig {

    @Value("${redis.clusters}")
    private String clusters;

    @Bean
    public JedisCluster jedisCluster(){
        Set<HostAndPort> nodes = new HashSet<>();
        String[] nodesArray = clusters.split(",");
        for (String node : nodesArray){
            String host = node.split(":")[0];
            int port = Integer.parseInt(node.split(":")[1]);
            HostAndPort hostAndPort = new HostAndPort(host,port);
            nodes.add(hostAndPort);
        }
        return new JedisCluster(nodes);
    }
  }

1.6.3编辑CacheAOP

image

1.7关于京淘项目后台说明

知识点概括

1.框架加强阶段

1.1SpringBoot各个配置文件的说明pom.xml配置 常用注解 springboot启动执行流程.

1.2关于SpringBoot常见用法 属性赋值@Value 开发环境优化,配置文件引入,整合Mybatis,整合Mybatis-Plus,整合WEB资源(JSP)

1.3京淘后台搭建

1.3.1分布式思想 按照模块/按照层级划分

1.3.2聚合工程创建的思路,父级项目,统一管理jar包,工具API项目,业务功能系统

1.4UI工具前端与后端进行数据交互时,如果想要展现特定的格式结构,则必须按照要求返回VO对象.

1.5JSON结构形式1.Object类型2.Array类型3.复杂类型(可以进行无限层级的嵌套).

1.6后台商品/商品分类的CURD操作.

1.7引入富文本编辑器/实现文件上传业务.

1.8反向代理/正向代理

1.9NGINX实现图片的回显,NGINX安装/命令/进程项说明/域名的代理/负载均衡机制/相关属性说明/

1.10window tomcat服务器的部署

2Linux学习

2.1什么是VM虚拟机.网络配置说明 桥接/NAT模式

2.2介绍Linux的发展,介绍Linux的命令 安装Linux JDK tomcatLinux部署.Linux安装MySQL数据

2.3Linux安装nginx服务器:整个项目linux部署.

3.项目真实部署

3.1实现数据的读写分离/负载均衡/数据库高可用mycat

3.2Redis命令/redis单台操作/redis分片/redis哨兵/redis集群

3.3AOP相关知识

image

2.京淘项目前台搭建

2.1京淘项目架构图

image

2.2京淘前台项目搭建

2.2.1创建项目

image

2.2.2添加继承/依赖/插件

说明:编辑jt-web的pom.xml文件其中打包的方式注意改成war 其次添加继承/依赖/插件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jt-web</artifactId>
    <packaging>war</packaging>

    <parent>
        <artifactId>jt</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!--2.添加依赖信息-->
    <dependencies>
        <!--依赖实质依赖的是jar包文件-->
        <dependency>
            <groupId>com.jt</groupId>
            <artifactId>jt-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <!--3.添加插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

将课前资料中的文件导入.如图所示

image

2.2.3关于web项目数据源报错的说明

SringBoot程序启动时需要加载数据库但是没有数据源的配置信息.导致报错.

image

如何解决:添加排除数据源启动

image

2.2.4修改SpringBoot启动项

image

2.2.5启动效果测试

image

2.2.6编辑Nginx配置文件

#配置jt-web服务器
    server {
        listen 80;
        server_name www.jt.com;

        location / {
            proxy_pass http://127.0.0.1:8092;
        }
    }

编辑hosts文件

image

2.2.7关于谷歌浏览器https禁用问题

谷歌浏览器输入:chrome://net-internals/#hsts

修改完成后重启浏览器即可

image

2.3关于伪静态的说明

2.3.1业务说明

问题1:京东的商品有很多,如果都采用静态页面的形式为用户展现数据,如果有100万的商品,那么就需要100万个xxx.html页面,请问京东是如何做到的.

实现规则:

应该动态获取商品的ID号.之后查询数据库,然后调整指定的页面,将数据进行填充即可.

问题2:为什么京东采用.html结尾的请求展现商品那?

采用.html结尾的页面更加容易被搜索引擎收录,提高网站曝光率.

image

2.3.2搜索引擎工作原理

工作原理核心:倒排索引机制,根据关键字检索文章位置.

image

2.3.3伪静态思想

伪静态是相对真实静态来讲的,通常我们为了增强搜索引擎的友好面,都将文章内容生成静态页面,但是有的朋友为了实时的显示一些信息。或者还想运用动态脚本解决一些问题。不能用静态的方式来展示网站内容。但是这就损失了对搜索引擎的友好面。怎么样在两者之间找个中间方法呢,这就产生了伪静态技术。伪静态技术是指展示出来的是以html一类的静态页面形式,但其实是用ASP一类的动态脚本来处理的。

总结:以.html结尾的静态页面,增强搜索引擎的友好性.

2.3.4伪静态实现

说明:如果需要实现伪静态,则需要拦截.html结尾的请求即可,否则程序认为你访问的是具体的静态资源如图所示.

image

配置类介绍

@Configuration                          //web.xml配置文件
public class MvcConfigurer implements WebMvcConfigurer{
    
    //开启匹配后缀型配置
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {

        //开启后缀类型的匹配.  xxxx.html
        configurer.setUseSuffixPatternMatch(true);
    }
}
查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月24日

京淘Day16

1.实现用户模块的跳转

1.1需求说明

说明:当用户点击登陆/注册按钮时,需要跳转到指定的页面中.

image

url地址1:http://www.jt.com/user/regist...

url地址2:http://www.jt.com/user/login....

1.2编辑UseController

package com.jt.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller     //需要进行页面跳转
@RequestMapping("/user")
public class UserController {

    /**
     * 实现用户模块页面跳转
     * url1: http://www.jt.com/user/login.html     页面:login.jsp
     * url2: http://www.jt.com/user/register.html  页面:register.jsp
     * 要求:实现通用页面跳转
     * restFul方式: 1.动态获取url中的参数,之后实现通用的跳转.
     */
    @RequestMapping("/{moduleName}")
    public String module(@PathVariable String moduleName){

        return moduleName;
    }

}

1.3页面效果展现

image

2.创建JT-SSO项目创建

2.1创建项目

image

2.2添加继/依赖/插件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jt-sso</artifactId>
    <!--默认的打包方式就是jar  不写也没有关系-->
    <packaging>jar</packaging>

    <parent>
        <artifactId>jt</artifactId>
        <groupId>com.jt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!--2.添加依赖信息-->
    <dependencies>
        <!--依赖实质依赖的是jar包文件-->
        <dependency>
            <groupId>com.jt</groupId>
            <artifactId>jt-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <!--3.添加插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.3编辑User的POJO对象

@TableName("tb_user")
@Data
@Accessors(chain = true)
public class User extends BasePojo{

    @TableId(type = IdType.AUTO)//设定主键自增
    private Long id;            //用户ID号
    private String username;    //用户名
    private String password;    //密码 需要md5加密
    private String phone;       //电话号码
    private String email;       //暂时使用电话代替邮箱

}

2.4测试JT-SSO项目

用户通过sso.jt.com/findUserAll获取User表中的信息json返回.

代码结构如下

image

2.4.1编辑UserController

package com.jt.controller;

import com.jt.pojo.User;
import com.jt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 完成测试按钮
     * 1.url地址 :findUserAll
     * 2.参数信息: null
     * 3.返回值结果: List<User>
     *
     */
    @RequestMapping("/findUserAll")
    public List<User> findUserAll(){

        return userService.findUserAll();
    }
}

2.4.2编辑UserService

package com.jt.service;

import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> findUserAll() {

        return userMapper.selectList(null);
    }
}

2.4.3修改nginx配置

image

3.跨域的实现(重要)

3.1跨域访问测试

3.1.1同域测试

分析:

1.浏览器地址: http://manage.jt.com/test.html

2.ajax请求地址: http://manage.jt.com/test.json

结论:

当浏览器地址与ajax请求的地址(协议://域名:端口)相同时可以实现正常的业务调用.

<script type="text/javascript" data-original="http://manage.jt.com/js/jquery-easyui-1.4.1/jquery.min.js"></script>
    <!--引入类库之后,执行js代码-->
<script type="text/javascript">
    <!--让整个页面加载完成之后执行js-->
    $(function(){
        $.get("http://manage.jt.com/test.json",function(data){
            alert(data.name);
        })
    })
</script>

image

3.1.2跨域测试

分析:

1.浏览器地址: http://www.jt.com/test.html

2.ajax请求地址: http://manage.jt.com/test.json

结论:

如果请求地址(协议://域名:端口)不相同则导致请求调用失败

3.2浏览器-同源策略说明

说明:浏览器规定,发起ajax时如果请求协议/端口号如果3者有一个与当前的浏览器的地址不相同时,则违反了同源策略的规定,则浏览器不予解析返回值.

跨域问题:违反同源策略得的规定就是跨域请求.

3.3跨域1-JSONP

3.3.1JSONP跨域原理

1.利用javascript中的src属性实现跨域请求.

2.自定义回调函数function callback(xxxx).

3.将返回值结果进行特殊的格式封装callback(json)

4.由于利用src属性进行调用 所以只能支持get请求类型

封装返回值

hello({"id":"1","name":"tom"})

页面JS编辑

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试JSON跨域问题</title>    
    <script type="text/javascript">
        /*JS是解释执行的语言  */
        /*定义回调函数  */
        function hello(data){
            alert(data.name);
        }
    </script>
    <!--该json一直保存到浏览器中等待调用,但是没有函数名称无法调用  -->
    <script type="text/javascript" data-original="http://manage.jt.com/test.json"></script>
    <script type="text/javascript" data-original="http://manage.jt.com/js/jquery-easyui-1.4.1/jquery.min.js"></script>
</head>
<body>
    <h1>JS跨域问题</h1>
</body>
</html>

image

3.3.2JSONP

JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的

3.3.3JSONP优化

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSONP测试</title>
<script type="text/javascript" data-original="http://manage.jt.com/js/jquery-easyui-1.4.1/jquery.min.js"></script>
<script type="text/javascript">
    $(function(){
        alert("测试访问开始!!!!!")
        $.ajax({
            url:"http://manage.jt.com/web/testJSONP",
            type:"get",                //jsonp只能支持get请求
            dataType:"jsonp",       //dataType表示返回值类型
            jsonp: "callback",    //指定参数名称
            jsonpCallback: "hello",  //指定回调函数名称
            success:function (data){   //data经过jQuery封装返回就是json串
                console.log(data);
            }
        });    
    })
</script>
</head>
<body>
    <h1>JSON跨域请求测试</h1>
</body>
</html>

3.3.4编辑后端Controller

package com.jt.web.controller;

import com.jt.pojo.ItemDesc;
import com.jt.util.ObjectMapperUtil;
import jdk.nashorn.internal.runtime.regexp.JoniRegExp;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JSONPController {

    /**
     * 实现JSONP跨域请求
     * url地址: http://manage.jt.com/web/testJSONP?callback=xxxxxx
     * 参数:    暂时没有可以不接
     * 返回值:  callback(JSON);
     */
     @RequestMapping("/web/testJSONP")
     public String testJSONP(String callback){
         ItemDesc itemDesc = new ItemDesc();
         itemDesc.setItemId(1000L).setItemDesc("JSONP测试!!!");
         String json = ObjectMapperUtil.toJSON(itemDesc);
         return callback+"("+json+")";
     }

}

3.3.5后端控制台输出

image

3.3.6JSONObject说明

 @RequestMapping("/web/testJSONP")
    public JSONPObject testJSONP(String callback){
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(1000L).setItemDesc("JSONP测试!!!");
        return new JSONPObject(callback, itemDesc);
    }

3.4cors跨域方式

3.4.1cors调用原理

image

3.4.2实现cors调用

package com.jt.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration  //标识我是一个配置类
public class CorsConfig implements WebMvcConfigurer {

    //在后端 配置cors允许访问的策略
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("GET","POST") //定义允许跨域的请求类型
                .allowedOrigins("*")           //任意网址都可以访问
                .allowCredentials(true) //是否允许携带cookie
                .maxAge(1800);                 //设定请求长链接超时时间.
    }
}

3.4.3cors调用响应头解析

image

3.4.4cors跨域测试

image

json数据格式

image

3.5关于跨域总结

1.jsonp

jsonp本质是利用JavaScript中的src属性的get请求实现跨域 返回值必须经过特殊格式封装.

2.cors

添加在响应头中的信息.指定哪些服务器允许访问.

实现用户数据校验

4.1业务需求

当用户注册时,如果输入用户名,则应向jt-sso单点登录系统发起请求,校验用户数据是否存在.如果存在则提示用户.image

4.2业务接口文档说明

image

4.3前端JS分析

1.url分析

image

2.检索JS代码

image

3.JS分析

 $.ajax({
                url : "http://sso.jt.com/user/check/"+escape(pin)+"/1?r=" + Math.random(),
                dataType : "jsonp",
                success : function(data) {
                    checkpin = data.data?"1":"0";
                    if(data.status == 200){
                         if (!data.data) {
                            validateSettings.succeed.run(option);
                            namestate = true;
                         }else {
                            validateSettings.error.run(option, "该用户名已占用!");
                            namestate = false;
                         }
                    }else{
                          validateSettings.error.run(option, "服务器正忙,请稍后!");
                          namestate = false;
                    }

                }
            });

4.4编辑JT-SSO UserController

  /**
     * 业务说明: jt-web服务器获取jt-sso数据 JSONP跨域请求
     * url地址: http://sso.jt.com/user/check/{param}/{type}
     * 参数:    param: 需要校验的数据   type:校验的类型
     * 返回值:  SysResult对象
     * 真实的返回值: callback(SysResult的JSON)
     */
    @RequestMapping("/check/{param}/{type}")
    public JSONPObject checkUser(@PathVariable String param,
                                 @PathVariable Integer type,
                                 String callback){
        //true 表示数据存在     false 表示数据可以使用
        boolean flag = userService.checkUser(param,type);
        SysResult.success(flag);
        return new JSONPObject(callback, SysResult.success(flag));
    }

4.5 编辑JT-SSO UserService

 /**
     * 判断依据:  根据用户名查询 如果结果>0 用户已存在.
     * @param param
     * @param type
     * @return
     */
    @Override
    public boolean checkUser(String param, Integer type) {
        //1.需要将type类型转化为 具体字段信息  1=username  2=phone  3=email
        String column = columnMap.get(type);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(column, param);
        Integer count = userMapper.selectCount(queryWrapper);
        return  count > 0 ? true :false;
    }

4.6页面效果展现

image

查看原文

赞 0 收藏 0 评论 0

BolunWu 发布了文章 · 10月24日

京淘Day18

1.Dubbo

1.1Dubbo介绍

Apache Dubbo是一款高性能,轻量级的开源java RPC框架,它提供三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现.

image

1.2Dubbo的特点

image

2Dubbo入门案例

2.1定义公共接口项目

说明:接口项目一般定义公共部分,并且被第三方依赖.

image

2.2服务提供者介绍

2.2.1提供者代码结构

image

2.2.2编辑实现类

package com.jt.dubbo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.dubbo.config.annotation.Service;
import com.jt.dubbo.mapper.UserMapper;
import com.jt.dubbo.pojo.User;
@Service(timeout=3000)    //3秒超时 内部实现了rpc
//@org.springframework.stereotype.Service//将对象交给spring容器管理
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public List<User> findAll() {
        
        System.out.println("我是第一个服务的提供者");
        return userMapper.selectList(null);
    }
    
    @Override
    public void saveUser(User user) {
        
        userMapper.insert(user);
    }
}

2.2.3编辑提供者配置文件

server:
  port: 9000   #定义端口

spring:
  datasource:
    #引入druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

#关于Dubbo配置   
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径 扫描dubbo注解
  application:              #应用名称
    name: provider-user     #一个接口对应一个服务名称   一个接口可以有多个实现
  registry:  #注册中心 用户获取数据从机中获取 主机只负责监控整个集群 实现数据同步
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复.

      
mybatis-plus:
  type-aliases-package: com.jt.dubbo.pojo       #配置别名包路径
  mapper-locations: classpath:/mybatis/mappers/*.xml  #添加mapper映射文件
  configuration:
    map-underscore-to-camel-case: true                #开启驼峰映射规则

2.2服务消费者介绍

package com.jt.dubbo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.dubbo.pojo.User;
import com.jt.dubbo.service.UserService;

@RestController
public class UserController {
    
    //利用dubbo的方式为接口创建代理对象 利用rpc调用
    @Reference
    private UserService userService; 
    
    /**
     * Dubbo框架调用特点:远程RPC调用就像调用自己本地服务一样简单
     * @return
     */
    @RequestMapping("/findAll")
    public List<User> findAll(){
        
        //远程调用时传递的对象数据必须序列化.
        return userService.findAll();
    }
    
    @RequestMapping("/saveUser/{name}/{age}/{sex}")
    public String saveUser(User user) {
        
        userService.saveUser(user);
        return "用户入库成功!!!";
    }
}

2.3.2编辑YML配置文件

server:
  port: 9001
dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-user   #定义消费者名称
  registry:               #注册中心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183

2.3.3入门案例测试

image

2.4关于Dubbo框架知识点

2.4.1问题1:如果其中一个服务器宕机,用户访问是否受限?

由于ZK的帮助,使得程序可以永远访问正确的服务器,并且当服务重启时,dubbo有服务的自动发现功能,消费者不需要重启既可以访问新的服务.

2.4.2问题2:如果ZK集群短时间宕机,用户访问是否受限?

用户访问不受影响,由于消费者在本地储存服务列表信息,当访问故障机,自动将标识信息改为down属性.

2.5Dubbo负载均衡策略

2.5.1负载均衡种类

1.客户端负载均衡

Dobbo/SpringCloud等微服务框架

image

2.服务端负载均衡

说明:客户端发起请求后,必须由统一的服务器进行负载均衡,所有的压力都在服务器之中.

NGINX

image

2.5.2Dubbo负载均衡方式

@RestController
public class UserController {
    
    //利用dubbo的方式为接口创建代理对象 利用rpc调用
    //@Reference(loadbalance = "random")            //默认策略  负载均衡随机策略
    //@Reference(loadbalance = "roundrobin")        //轮询方式
    //@Reference(loadbalance = "consistenthash")    //一致性hash  消费者绑定服务器提供者
    @Reference(loadbalance = "leastactive")            //挑选当前负载小的服务器进行访问
    private UserService userService; 

}

京淘项目Dubbo改造

3.1改造JT-SSO

3.1.1添加jar包文件

        <!--引入dubbo配置 -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

3.1.2创建DubboUserService接口

image

3.1.3创建提供者实现类

image

3.1.3编辑提供者YML配置文件

server:
  port: 8093
  servlet:
    context-path: /
spring:
  datasource:
    #引入druid数据源
    #type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
#mybatis-plush配置
mybatis-plus:
  type-aliases-package: com.jt.pojo
  mapper-locations: classpath:/mybatis/mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true

logging:
  level: 
    com.jt.mapper: debug

#关于Dubbo配置
dubbo:
  scan:
    basePackages: com.jt    #指定dubbo的包路径 扫描dubbo注解
  application:              #应用名称
    name: provider-user     #一个接口对应一个服务名称   一个接口可以有多个实现
  registry:  #注册中心 用户获取数据从机中获取 主机只负责监控整个集群 实现数据同步
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
  protocol:  #指定协议
    name: dubbo  #使用dubbo协议(tcp-ip)  web-controller直接调用sso-Service
    port: 20880  #每一个服务都有自己特定的端口 不能重复.

3.1.4启动服务提供者

测试Dubbo服务类是否正常启动

image

3.2改造服务消费者JT-WEB

3.2.1注入Service接口

image

3.2.2编辑消费者配置文件

server:
  port: 8092    
spring:     #定义springmvc视图解析器
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp

dubbo:
  scan:
    basePackages: com.jt
  application:
    name: consumer-web   #定义消费者名称
  registry:               #注册中心地址
    address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183

  

3.2.3启动效果测试

image

4.用户模块实现

4.1用户注册

4.1.1URL分析

说明:根据url地址说明请求为同域请求

image

参数信息

image

4.1.2页面JS分析

image

4.1.3编辑UserController

 /**
     * 需求: 实现用户信息注册
     * 1.url请求地址:  http://www.jt.com/user/doRegister
     * 2.请求参数:     {password:_password,username:_username,phone:_phone},
     * 3.返回值结果:   SysResult对象
     */
    @RequestMapping("/doRegister")
    @ResponseBody   //将数据转化为JSON
    public SysResult saveUser(User user){
        //消费者给予dubbo协议将user对象进行远程网络数据传输.
        userService.saveUser(user);
        return SysResult.success();
    }

4.1.4编辑UserService

/**
     * 注意事项:
     *  1.暂时使用电话号码代替邮箱
     *  2.密码进行md5加密.
     *  3.入库操作注意事务控制
     * @param user
     */
    @Override
    public void saveUser(User user) {
        String md5Pass =
                DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setEmail(user.getPhone())
            .setPassword(md5Pass);
        userMapper.insert(user);
    }

4.1.5页面效果展示

image

4.2关于ZK数据储存结构

说明:在ZK的数据储存采用树形结构的方式保存.

命令:[root@localhost bin]# sh zkCli.sh

查询命令:ls/...

image

4.3用户单点登录原理介绍

4.3.1传统方式登录存在的问题

说明:如果采用Session的方式实现用户的登陆操作,由于nginx的负载均衡的策略,用户可以访问不同的服务器,但是Session不能共享,所以导致用户频繁登陆,用户体验不好.

image

4.3.2SSO

单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的 .

4.3.3京淘项目单点登录设计

image

实现步骤:

1.当用户输入用户名和密码点击登录时,将请求发送给JT-WEB消费者服务器.

2.JT-WEB服务器将用户信息传递给JT-SSO单点登录系统完成系统校验.

3.如果登陆成功,则动态生成秘钥信息,将user数据转换为json,保存到redis中,注意超时时间的设定.

4.JT-SSO将登陆的凭证传给JT-WEB服务器.

5.JT-WEB服务器将用户秘钥ticket信息保存到用户cookie中注意超时时间设定.

6.如果登陆不成功则直接返回错误信息即可.

4.4用户单点登陆实现.

4.4.1页面url分析

image

4.4.2页面参数分析

image

4.4.3页面JS分析

image

4.4.4编辑UserController

 /**
     * 完成用户登录操作
     * 1.url地址: http://www.jt.com/user/doLogin?r=0.9309436837648131
     * 2.参数:    {username:_username,password:_password},
     * 3.返回值结果:  SysResult对象
     *
     * 4.Cookie:
     *   4.1 setPath("/")  path表示如果需要获取cookie中的数据,则url地址所在路径设定.
     *       url:http://www.jt.com/person/findAll
     *       cookie.setPath("/");   一般都是/
     *       cookie.setPath("/person");
     *   4.2 setDomain("xxxxx")  设定cookie共享的域名地址.
     */
    @RequestMapping("/doLogin")
    @ResponseBody
    public SysResult doLogin(User user, HttpServletResponse response){
        String ticket = userService.doLogin(user);
        if(StringUtils.isEmpty(ticket)){
            //说明用户名或者密码错误
            return SysResult.fail();
        }else{
            //1.创建Cookie
            Cookie cookie = new Cookie("JT_TICKET",ticket);
            cookie.setMaxAge(7*24*60*60);   //设定cookie存活有效期
            cookie.setPath("/");            //设定cookie有效范围
            cookie.setDomain("jt.com");     //设定cookie共享的域名 是实现单点登录必备要素
            response.addCookie(cookie);
            return SysResult.success();     //表示用户登录成功!!
        }
    }

4.4.5编辑UserService

/**
     * 1.获取用户信息校验数据库中是否有记录
     * 2.有  开始执行单点登录流程
     * 3.没有 直接返回null即可
     * @param user
     * @return
     */
    @Override
    public String doLogin(User user) {  //username/password
        //1.将明文加密
        String md5Pass =
                DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(md5Pass);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
        //根据对象中不为null的属性当做where条件.
        User userDB = userMapper.selectOne(queryWrapper);
        if(userDB == null){
            //用户名或密码错误
            return null;
        }else{ //用户名和密码正确  实现单点登录操作
            String ticket = UUID.randomUUID().toString();
            //如果将数据保存到第三方 一般需要脱敏处理
            userDB.setPassword("123456你信不??");
            String userJSON = ObjectMapperUtil.toJSON(userDB);
            jedisCluster.setex(ticket, 7*24*60*60, userJSON);
            return ticket;
        }
    }

4.4.5页面效果展现

image

查看原文

赞 0 收藏 0 评论 0