Hrm-人力资源系统开发笔记05

1.无限极树优化

1.1.为什么要优化

每次都要从数据库查询一次.

使用的地方&问题:
1)后台管理
课程类型列表要使用
课程类型树,在后面添加课程时会反复使用。通过下拉选择对应的类型
就算每个人使用时只查询一次,如果人比较多.也要对数据库进行频繁操作.

2)课程主页
如果一亿并发,也会频繁访问数据库。
100w redis? ===页面静态化

1.2.优化方案

1)后台管理
缓存:用内存查询替换数据库磁盘查询.
经常查询,很少修改
2)课程主页
不能用缓存,还是会高并发访问缓存中数据.
页面静态化:以不发请求静态页面代替要发请求静态页面或者动态页面.没有对后台数据获取.
1)每次访问都是html静态页面,不能访问数据
2)只要数据发生改变,就要重新生成新的静态页面替换原来的静态页面.
页面基本不会变,除了数据

1.3.实现

.搭建缓存服务-hrm-common-service-2090
pom

<?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">
    <parent>
        <artifactId>hrm-common-parent</artifactId>
        <groupId>com.penny</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hrm-common-service-2090</artifactId>

    <dependencies>
        <!--作为中间依赖包,提供公共代码供feign和service调用-->
        <dependency>
            <groupId>com.penny</groupId>
            <artifactId>hrm-common-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Eureka 客户端依赖 -->
        <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-config</artifactId>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--fastdfs包-->
        <dependency>
            <groupId>cn.bestwu</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27</version>
        </dependency>
        <!--redis客户端-jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

    </dependencies>


</project>

配置-redis.properties

redis.host=127.0.0.1
redis.port=6379
redis.password=admin
redis.timeout=1000

RedisUtils工具类

/**
 * 获取连接池对象
 */
public enum RedisUtils {
    INSTANCE;
    static JedisPool jedisPool = null;

    static {
        //1 创建连接池配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        //2 进行配置-四个配置
        config.setMaxIdle(1);//最小连接数
        config.setMaxTotal(11);//最大连接数
        config.setMaxWaitMillis(10 * 1000L);//最长等待时间
        config.setTestOnBorrow(true);//测试连接时是否畅通
        //3 通过配置对象创建连接池对象
        Properties properties = null;
        try {
            properties = new Properties();
            properties.load(RedisUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        String host = properties.getProperty("redis.host");
        String port = properties.getProperty("redis.port");
        String password = properties.getProperty("redis.password");
        String timeout = properties.getProperty("redis.timeout");
        System.out.println(host);
        System.out.println(port);
        System.out.println(password);
        System.out.println(timeout);
        jedisPool = new JedisPool(config, host, Integer.valueOf(port),Integer.valueOf(timeout), password);
    }

    //获取连接
    public Jedis getSource() {
        return jedisPool.getResource();
    }

    //关闭资源
    public void closeSource(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }

    }
    /**
     * 设置字符值
     *
     * @param key
     * @param
     */
    public void del(String key) {
        Jedis jedis = getSource();
        jedis.del(key);
        closeSource(jedis);
    }
    /**
     * 设置字符值
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        Jedis jedis = getSource();
        jedis.set(key, value);
        closeSource(jedis);
    }

    /**
     * 设置
     * @param key
     * @param value
     */
    public void set(byte[] key, byte[] value) {
        Jedis jedis = getSource();
        jedis.set(key, value);
        closeSource(jedis);
    }

    /**
     *
     * @param key
     * @return
     */
    public byte[]  get(byte[] key) {
        Jedis jedis = getSource();
        try {
            return jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeSource(jedis);
        }
        return null;

    }

    /**
     * 设置字符值
     *
     * @param key
     */
    public String get(String key) {
        Jedis jedis = getSource();
        try {
            return jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeSource(jedis);
        }

        return null;

    }
}

Contorler

@RestController
/**
 * 增/修改 set
 * 删除 del
 * 查询 get
 */
@RequestMapping("/redis")
public class RedisController {
    @PostMapping
    public AjaxResult add(
            @RequestParam(value = "key",required = true) String key,
            @RequestParam(value = "value",required = true) String value)
    {
        try
        {
            RedisUtils.INSTANCE.set(key,value);
            return AjaxResult.me();
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.me().setSuccess(false).setMessage("添加失败");
        }
    }

    @DeleteMapping
    public AjaxResult del(@RequestParam(value = "key",required = true) String key)
    {
        try
        {
            RedisUtils.INSTANCE.del(key);
            return AjaxResult.me();
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.me().setSuccess(false).setMessage("删除失败");
        }
    }

    @GetMapping
    public String get(@RequestParam(value = "key",required = true) String key)
    {
        try
        {
            return RedisUtils.INSTANCE.get(key);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

1.4.把服务通过client暴露出去 hrm-common-client

Pom

<?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">
    <parent>
        <artifactId>hrm-common-parent</artifactId>
        <groupId>com.penny</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hrm-common-client</artifactId>

    <dependencies>
        <!--作为中间依赖包,防止feign报错-->
        <dependency>
            <groupId>com.penny</groupId>
            <artifactId>hrm-common-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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


        <!--给调用的模块来转换json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
        <!--客户端feign支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>


</project>

Client

package com.penny.client;

import com.penny.util.AjaxResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
//服务调用兼托底方法
@FeignClient(value = "HRM-COMMON", fallbackFactory = RedisClientFallbackFactory.class )//服务提供
@RequestMapping("/redis")
public interface RedisClient {
    @PostMapping
    AjaxResult add(@RequestParam(value = "key",required = true) String key,@RequestParam(value = "value",required = true) String value);
    @DeleteMapping
    AjaxResult del(@RequestParam(value = "key",required = true) String key);
    @GetMapping
    String get(@RequestParam(value = "key",required = true) String key);
}


Factory

package com.penny.client;

import com.penny.util.AjaxResult;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class RedisClientFallbackFactory implements FallbackFactory<RedisClient> {
    @Override
    public RedisClient create(Throwable throwable) {
        return new RedisClient() {
            @Override
            public AjaxResult add(String key, String value) {
                return AjaxResult.me().setSuccess(false).setMessage("redis调用失败");
            }

            @Override
            public AjaxResult del(String key) {
                return AjaxResult.me().setSuccess(false).setMessage("redis调用失败");
            }

            @Override
            public String get(String key) {
                return "redis调用失败";
            }
        };
    }
}

通过feign完成服务间调用

package com.penny;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class CourseService2020App {
    public static void main(String[] args) {
        SpringApplication.run(CourseService2020App.class,args);
    }
}

缓存逻辑实现
cache

@Component
public class CourseTypeCache {
    @Autowired
    private RedisClient redisClient;

    private static  final  String TYPETREEDATA_IN_REDIS = "typetreedata_in_redis";
    /**
     * 从redis获取数据
     * @return
     */
    public List<CourseType> getCourseTypes() {
        String redisData = redisClient.get(TYPETREEDATA_IN_REDIS);
       return JSONArray.parseArray(redisData,CourseType.class);
    }

    /**
     * 设置数据到redis
     * @param courseTypesDb
     */
    public void setCourseTypes(List<CourseType> courseTypesDb) {
        String jsonStr = JSONArray.toJSONString(courseTypesDb);
        redisClient.set(TYPETREEDATA_IN_REDIS,jsonStr);
    }
}

Service

package com.penny.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.penny.cache.ICourseTypeCache;
import com.penny.domain.CourseType;
import com.penny.mapper.CourseTypeMapper;
import com.penny.service.ICourseTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author Penny
 * @since 2020-02-17
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    @Autowired
    private CourseTypeMapper courseTypeMapper;

    //和mapper差不多,mapper操作的是数据库的数据,而换成操作的是redis数据
    @Autowired
    private ICourseTypeCache courseTypeCache;
    @Override
    public List<CourseType> treeData(long pid) {

        //如果换成中有就直接返回
        List<CourseType>courseTypes = courseTypeCache.getTreeData();
        if (courseTypes!=null && courseTypes.size()>0){
            System.out.println("cache ....");
            return courseTypes;
        }else{
            System.out.println("db....");
            //否则先查询数据库,放入换成后再返回
            //递归思想 每一个节点都会发一天sql
            //return treeDataRecursion(pid);
            //循环方案 一条sql
            List<CourseType> courseTypesOfDb = treeDataLoop(pid);
            courseTypeCache.setTreeData(courseTypesOfDb);
            return courseTypesOfDb;
        }

    }

    //循环方案-一条sql自己组织关系
    private List<CourseType> treeDataLoop(Long pid) {
        //1查询所有的节点
        List<CourseType> allNodes = courseTypeMapper.selectList(null);

        //为了查询父亲方便,我建立所有节点的一个key(Id)-vue(Node)集合 n
        Map<Long,CourseType>  allNodeDto = new HashMap<>();
        for (CourseType courseType : allNodes) {
            allNodeDto.put(courseType.getId(),courseType);
        }
        //2组织关系
        List<CourseType> result = new ArrayList<>();
        for (CourseType courseType : allNodes) { //n
            //2.1 如果是一级节点之间放入result
            if (courseType.getPid().intValue()==0) {
                System.out.println("**********此处放入result**********");
                result.add(courseType);
                for (CourseType type : result) {
                    System.out.println(type);
                }
            }else{
                //2.2 不是一级节点要建立父子关系-把自己作为父亲一个儿子添加进去
                //2.2.1 获取父亲
                Long pidTmp = courseType.getPid(); //不能发sql
                System.out.println("pidTmp为:"+pidTmp);
                //方案1:遍历获取父亲,效率低下 两层for n*N
                /*
                for (CourseType courseType1 : allNodes) {
                    if (courseType1.getId() == pidTmp) {
                        //获取到了父亲了。。。。
                    }
                }*/
                //方案2:提前建立id和node直接关系,直接获取 //2n
                CourseType parent = allNodeDto.get(pidTmp);
                System.out.println("**********此处放入parent**********");
                //2.2.2 给父亲添加儿子(自己)
                parent.getChildren().add(courseType);
                System.out.println("parent为:"+parent);
                for (CourseType type : result) {
                    System.out.println(type);
                }

            }

        }
        return  result;
    }

    /**
     * 通过父id查询儿子,有儿子就设置为自己的儿子,没有就返回
     * 自己调用自己
     * //返回条件
     * @param pid
     * @return
     */

    private List<CourseType> treeDataRecursion(Long pid) {
        //发一条sql
        List<CourseType> children = courseTypeMapper
                .selectList(new EntityWrapper<CourseType>().eq("pid", pid));
        //返回条件
        if (children==null || children.size()<1){
            return null;
        }
        for (CourseType child : children) {
            //自己调用自己
            List<CourseType> cTmp = treeDataRecursion(child.getId());
            child.setChildren(cTmp);
        }

        return children;

    }

    //重写增删改方法-同步缓存


    @Override
    public boolean insert(CourseType entity) {

        courseTypeMapper.insert(entity);
        courseTypeCache.setTreeData(treeDataLoop(0L));
        return true;
    }

    @Override
    public boolean deleteById(Serializable id) {
        courseTypeMapper.deleteById(id);
        courseTypeCache.setTreeData(treeDataLoop(0L));
        return true;
    }

    @Override
    public boolean updateById(CourseType entity) {
        courseTypeMapper.updateById(entity);
        courseTypeCache.setTreeData(treeDataLoop(0L));
        return true;
    }

}

2.缓存穿透,缓存击穿,缓存雪崩解决方案:

可详见:缓存穿透,缓存击穿,缓存雪崩解决方案
缓存穿透:
高并发访问数据库中不存在数据,放入缓存的数据也没有,击穿缓存每次都要查询数据库.
如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存击穿:
一个key过期,又来高并发访问. 直接高并发访问数据库.
1)让热点数据永远不过期.
2)加互斥锁,互斥锁参考代码如下 我在访问数据库是你就不要访问
缓存雪崩:
一堆key同时过期
设置过期时间不一致就ok. 热点数据永远不过期

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值