Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存

释放双眼,带上耳机,听听看~!

今日任务

Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存

文章目录

  • 课程类型的优化

  • 无限极树的优化

  • 课程类型的缓存

  • 常见的缓存实现方案:

    • 缓存的工作原理图
    • 数据存储-json(转换)
    • Fastjson
  • 缓存和项目集成

  • 与Redis的集成

  • 与Redis的交互部分
    * 测试:

    • 把服务通过client暴露出去
  • 课程类型调用client

    • 代码实现:
  • 题外话

课程类型的优化

无限极树的优化

之前我们做无限极树用的是递归 但是这样每次都要去访问一次数据库 这对我们的数据库来说无敌是压力巨大的 所以我们需要做进一步的优化

方案一:发送一天SQL 将所以数据都查询出来 然后在通过循环将子级节点放入到父级节点中
代码实现:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1//循环方案-一条sql自己组织关系
2private List<CourseType> treeDataLoop(long pid) {
3    //1查询所有的节点
4    List<CourseType> allNodes = courseTypeMapper.selectList(null);
5
6    //为了查询父亲方便,我建立所有节点的一个key(Id)-vue(Node)集合 n
7    Map<Long,CourseType>  allNodeDto = new HashMap<>();
8    for (CourseType courseType : allNodes) {
9        allNodeDto.put(courseType.getId(),courseType);
10    }
11    //2组织关系
12    List<CourseType> result = new ArrayList<>();
13    for (CourseType courseType : allNodes) { //n
14        //2.1 如果是一级节点之间放入result
15        if (courseType.getPid().intValue()==0) {
16            result.add(courseType);
17        }else{
18            //2.2 不是一级节点要建立父子关系-把自己作为父亲一个儿子添加进去
19            //2.2.1 获取父亲
20            Long pidTmp = courseType.getPid(); //不能发sql
21            //方案1:遍历获取父亲,效率低下 两层for n*N
22            /*
23            for (CourseType courseType1 : allNodes) {
24                if (courseType1.getId() == pidTmp) {
25                    //获取到了父亲了。。。。
26                }
27            }*/
28            //方案2:提前建立id和node直接关系,直接获取 //2n
29            CourseType parent = allNodeDto.get(pidTmp);
30            //2.2.2 给父亲添加儿子(自己)
31            parent.getChildren().add(courseType);
32        }
33    }
34    return  result;
35}
36
37

Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
启动后会报一个错 空指针异常 因为在我们的实体类中 如果第一次没有创建一个空的集合 在parent.getChildren()获取的时候就获取不到 所以我们需要在domain里面 初始化儿子的时候就新建一个没有数据的集合

这个时候还有一个问题:
虽然就算我们只发送了一条sql 但是如果有几万个人同时访问 这样对数据库的压力也是巨大的 所以需要进一步的优化 —- 缓存

缓存:用内存查询替换数据库磁盘查询.

经常查询,很少修改
但是这种技术也只适合于后台的前端 不适合前台的前端 因为即使做了缓存 如果有几万个人同时访问页面 那么缓存也是抵不住压力的 这里就涉及到了一个新的技术 —–
页面静态化


课程类型的缓存

常见的缓存实现方案:

  • 二级缓存 :jpa,Mybatis 默认情况下,

不支持集群环境使用

在没有集群的时候就可以使用它
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存

  • 中央缓存:redis/memcached 集群环境

Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存

缓存的工作原理图

Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存

数据存储-json(转换)


1
2
3
4
5
6
7
8
9
10
11
1queryCourseTypes{
2        List<CourseType> xxx = cache.queryCourseTypes(); // string--->Object
3        if(xxx ==null){
4             List<CourseType> yyy = maper.queryCourseTypes();
5             cache.setCourseTypes(yyy); //string  object->string
6              return yyy;
7       }
8       return xxx;
9}
10
11

说明: 当我们去查询一个数据时 我们首先去缓存中查询cache.queryCourseTypes() , 如果缓存中的数据不为空 则直接返回 如果数据xxx为空 就去数据库查询该数据 然后先将数据存入到缓存中 然后返回数据


但是缓存是不支持直接存放对象的 所以我们需要将对象转换为json字符串进行存储
而要获取数据 就要 把json字符串转换为java对象进行返回

java对象和json字符串之间相互转换?
需要使用到json的框架 json-lib,jackson,gson,fastjson

这个给大家一篇参考文章 各种JSON比较(josn-lib,jackson,gson,fastjson)

这里我们使用的是Alibaba的
fastjson

Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。

FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库

Fastjson

我们先来看一串数据
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
这分别是 1000 , 5000, 10000条数据下转换所用的时间 课件fastjson有多fast了

我们先来做一个测试
创建一个maven项目并添加依赖


1
2
3
4
5
6
7
8
1<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
2<dependency>
3    <groupId>com.alibaba</groupId>
4    <artifactId>fastjson</artifactId>
5    <version>1.2.58</version>
6</dependency>
7
8

Java对象转json字符串
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
输出:
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
可以看见第二排后面两条数据打印的是 引用地址 这也是为什么速度那么快的原因 这也是fastjson的优化之一

json字符串转Java对象
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
输出:
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存

缓存和项目集成

除了课程模块要用缓存,其他模块也要用缓存,所有抽取缓存服务供其他模块使用,并且把像缓存这样的简单服务全部封装在一个公共服务模块里面
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存

与Redis的集成

参考文章

NoSQL-Redis基本介绍
Redis客户端的基本使用和在java中使用

与Redis的交互部分

为了简化我们的代码 我的是将他放在之前的
hrm-common-service-2090中

首先还是先导入依赖 在原有的基础上再加上jedis


1
2
3
4
5
6
7
8
9
1<!-- redis客户端-jedis -->
2<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
3<dependency>
4   <groupId>redis.clients</groupId>
5   <artifactId>jedis</artifactId>
6   <version>2.9.0</version>
7</dependency>
8
9

编写配置类
redis.properties


1
2
3
4
5
6
1redis.host=127.0.0.1
2redis.port=6379
3redis.password=admin
4redis.timeout=1000
5
6

导入Redis工具类
RedisUtils.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
1/**
2 * 获取连接池对象
3 */
4public enum RedisUtils {
5    INSTANCE;
6    static JedisPool jedisPool = null;
7
8    static {
9        //1 创建连接池配置对象
10        JedisPoolConfig config = new JedisPoolConfig();
11        //2 进行配置-四个配置
12        config.setMaxIdle(1);//最小连接数
13        config.setMaxTotal(11);//最大连接数
14        config.setMaxWaitMillis(10 * 1000L);//最长等待时间
15        config.setTestOnBorrow(true);//测试连接时是否畅通
16        //3 通过配置对象创建连接池对象
17        Properties properties = null;
18        try {
19            properties = new Properties();
20            properties.load(RedisUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
21        } catch (IOException e) {
22            e.printStackTrace();
23        }
24        String host = properties.getProperty("redis.host");
25        String port = properties.getProperty("redis.port");
26        String password = properties.getProperty("redis.password");
27        String timeout = properties.getProperty("redis.timeout");
28        jedisPool = new JedisPool(config, host, Integer.valueOf(port),Integer.valueOf(timeout), password);
29    }
30
31    //获取连接
32    public Jedis getSource() {
33        return jedisPool.getResource();
34    }
35
36    //关闭资源
37    public void closeSource(Jedis jedis) {
38        if (jedis != null) {
39            jedis.close();
40        }
41
42    }
43    /**
44     * 设置字符值
45     *
46     * @param key
47     * @param
48     */
49    public void del(String key) {
50        Jedis jedis = getSource();
51        jedis.del(key);
52        closeSource(jedis);
53    }
54    /**
55     * 设置字符值
56     *
57     * @param key
58     * @param value
59     */
60    public void set(String key, String value) {
61        Jedis jedis = getSource();
62        jedis.set(key, value);
63        closeSource(jedis);
64    }
65
66    /**
67     * 设置
68     * @param key
69     * @param value
70     */
71    public void set(byte[] key, byte[] value) {
72        Jedis jedis = getSource();
73        jedis.set(key, value);
74        closeSource(jedis);
75    }
76
77    /**
78     *
79     * @param key
80     * @return
81     */
82    public byte[]  get(byte[] key) {
83        Jedis jedis = getSource();
84        try {
85            return jedis.get(key);
86        } catch (Exception e) {
87            e.printStackTrace();
88        } finally {
89            closeSource(jedis);
90        }
91        return null;
92
93    }
94
95    /**
96     * 设置字符值
97     * @param key
98     */
99    public String get(String key) {
100        Jedis jedis = getSource();
101        try {
102            return jedis.get(key);
103        } catch (Exception e) {
104            e.printStackTrace();
105        } finally {
106            closeSource(jedis);
107        }
108        return null;
109    }
110}
111
112

测试:

编写Controller


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
1@RestController
2/**
3 * 增/修改 set
4 * 删除 del
5 * 查询 get
6 */
7@RequestMapping("/redis")
8public class RedisController {
9    @PostMapping
10    public AjaxResult add(
11            @RequestParam(value = "key",required = true) String key,
12            @RequestParam(value = "value",required = true) String value)
13    {
14        try
15        {
16            RedisUtils.INSTANCE.set(key,value);
17            return AjaxResult.me();
18        }catch (Exception e){
19            e.printStackTrace();
20            return AjaxResult.me().setSuccess(false).setMessage("添加失败");
21        }
22    }
23
24    @DeleteMapping
25    public AjaxResult del(@RequestParam(value = "key",required = true) String key)
26    {
27        try
28        {
29            RedisUtils.INSTANCE.del(key);
30            return AjaxResult.me();
31        }catch (Exception e){
32            e.printStackTrace();
33            return AjaxResult.me().setSuccess(false).setMessage("删除失败");
34        }
35    }
36
37    @GetMapping
38    public String get(@RequestParam(value = "key",required = true) String key)
39    {
40        try
41        {
42            return RedisUtils.INSTANCE.get(key);
43        }catch (Exception e){
44            e.printStackTrace();
45        }
46        return null;
47    }
48}
49
50

启动服务:
修改redis的配置文件 修改密码
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
并启动redis
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
我们可以通过Swagger测试一下:
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
获取
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
测试成功


刚刚我们是通过浏览器访问的 如果要在服务间进行一个调用 我们要使用到
feign


把服务通过client暴露出去

我们的client有可能会被多个模块使用 我们需要通过client把服务暴露出去(服务之间的调用 Feign)

创建一个子模块
hrm-common-client
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
导入相关依赖


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1<dependency>
2    <groupId>org.leryoo</groupId>
3    <artifactId>hrm-common-common</artifactId>
4    <version>1.0-SNAPSHOT</version>
5</dependency>
6
7<dependency>
8    <groupId>org.springframework.boot</groupId>
9    <artifactId>spring-boot-starter-web</artifactId>
10</dependency>
11<dependency>
12    <groupId>org.springframework.boot</groupId>
13    <artifactId>spring-boot-starter-test</artifactId>
14    <scope>test</scope>
15</dependency>
16
17
18<!--        给调用的模块来转换json-->
19<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson  调用者需要转换-->
20<dependency>
21    <groupId>com.alibaba</groupId>
22    <artifactId>fastjson</artifactId>
23    <version>1.2.58</version>
24</dependency>
25<!--客户端feign支持-->
26<dependency>
27    <groupId>org.springframework.cloud</groupId>
28    <artifactId>spring-cloud-starter-openfeign</artifactId>
29</dependency>
30
31

这里需要注意的是 fastjson是给调用他的模块调用的 不是redis服务端调用的


hrm-common-client中创建 Redis的客户端
RedisClient.java


1
2
3
4
5
6
7
8
9
10
11
12
1@FeignClient(value = "HRM-COMMON", fallbackFactory = RedisClientFallbackFactory.class )//ζœεŠʽ提供
2@RequestMapping("/redis")
3public interface RedisClient {
4    @PostMapping
5    AjaxResult add(@RequestParam(value = "key",required = true) String key,@RequestParam(value = "value",required = true) String value);
6    @DeleteMapping
7    AjaxResult del(@RequestParam(value = "key",required = true) String key);
8    @GetMapping
9    String get(@RequestParam(value = "key",required = true) String key);
10}
11
12

然后是熔断后的托底回调函数
RedisClientFallbackFactory.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1@Component
2public class RedisClientFallbackFactory implements FallbackFactory<RedisClient> {
3    @Override
4    public RedisClient create(Throwable throwable) {
5        return new RedisClient() {
6            @Override
7            public AjaxResult add(String key, String value) {
8                return AjaxResult.me().setSuccess(false).setMessage("redis调用失败");
9            }
10
11            @Override
12            public AjaxResult del(String key) {
13                return AjaxResult.me().setSuccess(false).setMessage("redis调用失败");
14            }
15
16            @Override
17            public String get(String key) {
18                return "redis调用失败";
19            }
20        };
21    }
22}
23
24

课程类型调用client

通过feign来完成服务间的调用

首先要在
course-service-2020的服务中先去引入client


1
2
3
4
5
6
7
8
1<!-- 通过feign调用client-->
2<dependency>
3    <groupId>cn.itsource</groupId>
4    <artifactId>hrm-common-client</artifactId>
5    <version>1.0-SNAPSHOT</version>
6</dependency>
7
8

本来还应该要导一个
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
但是依赖了
hrm-common-client 而那里面已经导入了 所以就不需要在导入了

最后需要去扫描Client 在启动类上加上
Springcloud微服务项目——人力资源管理(HRM)Day05 fastjson & Redis缓存
这样才能去扫描到Feign 只有去扫描了才会产生代理对象 最终通过代理对象完成一个调用

之后我们想要调用它就很简单了 直接将
RedisClient注入就可以了

整体流程为:先在调用者的服务里面引入client的依赖 ——> 启动类里面加上@EnableFeignClients注解——> 这样我们就能通过@EnableFeignClients 去扫描到加了@FeignClient的RedisClient类 ——> 扫描到了就会产生代理对象 ——> 通过代理对象就能远程访问名字叫value = "HRM-COMMON"的服务 并且访问路径

1
1` ```` ` ```` `

@RequestMapping("/redis")要和服务里的@RequestMapping("/redis")

1
1`

一致


代码实现:

新建一个
cache的包 作用和
mapper一样 一个是从数据库查询数据 一个是从缓存中去查询数据
接口:ICourseTypeCache.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1public interface ICourseTypeCache {
2    /**
3     * 获取courseType的缓存
4     * @return
5     */
6    List<CourseType> getTreeData();
7
8    /**
9     * 设置courseType到缓存中
10     * @param courseTypesOfDb
11     */
12    void setTreeData(List<CourseType> courseTypesOfDb);
13}
14
15

实现类:
CourseTypeCacheImpl.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1@Component
2public class CourseTypeCacheImpl implements ICourseTypeCache {
3
4    //获取和设置都要使用
5    private final static String COURSETYPE_KEY_IN_CACHE = "courseType_Key_in_cache";
6    @Autowired
7    private RedisClient redisClient;
8    @Override
9    public List<CourseType> getTreeData() {
10        //获取的是字符串
11        String treeDataOfCache = redisClient.get(COURSETYPE_KEY_IN_CACHE);
12        //转换为java对象
13        return JSONArray.parseArray(treeDataOfCache,CourseType.class);
14    }
15
16    @Override
17    public void setTreeData(List<CourseType> courseTypesOfDb) {
18
19        if (courseTypesOfDb == null || courseTypesOfDb.size()<1){
20            //缓存穿透
21            redisClient.add(COURSETYPE_KEY_IN_CACHE,"[]");
22        }else{
23            //转换为json字符串
24            String jsonArrStr = JSONArray.toJSONString(courseTypesOfDb);
25            //设置获取
26            //缓存数据永远不过期 缓存击穿 缓存雪崩
27            redisClient.add(COURSETYPE_KEY_IN_CACHE,jsonArrStr);
28        }
29
30    }
31}
32
33

注意要加@Component注解

然后我们需要修改CourseTypeServiceImpl.java中的代码
查询:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1@Autowired
2private ICourseTypeCache courseTypeCache;
3
4@Override
5public List<CourseType> treeData(long pid) {
6
7    //如果缓冲中有就直接返回
8    List<CourseType>courseTypes = courseTypeCache.getTreeData();
9    if (courseTypes!=null && courseTypes.size()>0){
10        System.out.println("cache ....");
11        return courseTypes;
12    }else{
13        System.out.println("db....");
14        //否则先查询数据库,放入换成后再返回
15        List<CourseType> courseTypesOfDb = treeDataLoop(pid);
16        courseTypeCache.setTreeData(courseTypesOfDb);
17        return courseTypesOfDb;
18    }
19
20}
21
22

然后我们去
覆写默认的增删改的方法


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1@Override
2public boolean insert(CourseType entity) {
3
4    courseTypeMapper.insert(entity);
5    courseTypeCache.setTreeData(treeDataLoop(0));
6    return true;
7}
8
9@Override
10public boolean deleteById(Serializable id) {
11    courseTypeMapper.deleteById(id);
12    courseTypeCache.setTreeData(treeDataLoop(0));
13    return true;
14}
15
16@Override
17public boolean updateById(CourseType entity) {
18    courseTypeMapper.updateById(entity);
19    courseTypeCache.setTreeData(treeDataLoop(0));
20    return true;
21}
22
23

说明:courseTypeCache.setTreeData(treeDataLoop(0));是查询所有的语句 当我们修改了数据库的值后 就查询所有的值 然后存入缓存中

题外话

  1. 为什么要用缓存? 经常查询而很少修改
  2. 为什么要用redis缓存? 如果项目中有集群的话就建议用redis缓存 因为二级缓存不知道集群
  3. 使用缓存好处? 减轻数据库压力 提高访问速度,增强用户体验
  4. 缓存数据很多的的情况怎么办? redis集群

主从复制-单主故障
哨兵模式-每个节点数据都是一样
redis-cluster: 单点故障,高并发,大量数据

  1. 缓存穿透,缓存击穿,缓存雪崩

参考文章 缓存穿透、缓存击穿、缓存雪崩区别和解决方案

给TA打赏
共{{data.count}}人
人已打赏
安全经验

Google Adsense老手经验

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索