商城项目_Redis中央缓存_页面静态化

因为商城项目使用的是微服务的架构,比如产品服务、订单服务、物流服务等等,不同的服务可能会部署在不同的电脑上,以前我们优化查询是用过缓存实现的,即将用户所需要访问的数据,在用户第一次查询的时候,就进入数据库进行查询,然后将所有的数据存放到缓存中,之后用户若再次访问就直接从内存中进行获取,不需要再访问数据库,这在性能上面优化了。并且当出现并发的情况时,比如:几千个用户同时访问同一个资源,这会给数据库或内存造成很大的压力。
但是分布式架构出现,以前的缓存的方式就不适用了,因为不同的计算机是不共享内存的,解决方案就是,再起一个缓存服务,作为中央缓存,用户直接通过路由网关,访问中央缓存内的数据。

技术选型
选择Reids实现中央缓存,因为其数据存放在内存中,并且数据会不定期持久化到硬盘内,可以保证数据的安全性,且其支持存储的数据类型也比较多(比如String、List、Set 、HashSet),支持跨语言(客户端的选择,有利于发展)、支持超大并发,可以实现集群。

(一)Redis知识储备

服务端:
(扩展:redis使用的是C语言编写的,Redis官方提供的就是C语言,C语言原生就是在Linux上面运行的,没有提供windows版,window上面不能直接运行,需要进行编译,所以微软编译成为了exe文件)

使用redis,需要先安装服务端:
		window:下载微软编译的压缩版,解压直接运行    
					① 结构分析 
							logs           	       //日志存放目录
							redis.conf     		   //配置文件
							redis-benchmark.exe    //性能测试工具
							redis-check-aof.exe    //检测持久化数据是否完好
							redis-check-dump.exe   //检测持久化数据是否完好
							redis-cli.exe   	   //客户端
							redis-server.exe       //服务端
					② 启动redis服务(以命令行客户端启动为例)
							带配置文件:redis-server.exe redis.conf
							不带配置文件:直接双击  redis-server.exe
							默认启动:redis-cli.exe  默认连接本机127.0.0.1、默认端口6379服务
							指定Ip和端口启动:  redis -cli -h i地址 -p 端口号
									例如:redis -cli -h 172.16.0.15 -p 6380
					③ 基本用法
							set key value//将字符串值value关联到key
							get key       //返回key关联的字符串值
							
							mset //同时设置一个或多个 key-value 对
							mget //返回所有(一个或多个)给定 key 的值
							
							incr key //将 key 中储存的数字值增1(key不存在,则初始化为0,再加1)
							decr key //将 key 中储存的数字值减1(key不存在,则初始化为0,再减1)
							incrBy key num//自增多少
							decrBy key num//自减多少

							keys *  //获取所有key列表
							del key  //删除key
							expire key xx //设置key的过期时间(xx秒后过期)
							ttl key //查看key的过期时间

							select 0-15 选择库  默认16个数据库
							flushall //清空整个redis服务器数据,所有的数据库全部清空
							flushdb  //清除当前库,redis中默认有16个数据库,名称分别为0,1,2.。。15
			
							lpush key value //将一个或多个值 value 插入到列表 key 的表头(最左边)
							rpush key value //将一个或多个值 value 插入到列表 key 的表尾(最右边

							lpop key //移除并返回列表 key 的头(最左边)元素。
							rpop key //移除并返回列表 key 的尾(最右边)元素。

							lrange key start stop//返回列表 key 中指定区间内的元素,查询所有的stop为-1即可
							lrem key count value//根据count值移除列表key中与参数 value 相等的元素								count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。count = 0 : 移除表中所有与 value 相等的值。
		
							lindex key index //返回列表 key 中,下标为 index 的元素
							ltrim key start stop //对一个列表进行修剪
	
							set集合是一个无序的不含重复值的队列
							sadd  key member //将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略
							srem key member//移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略
							smembers key    //返回集合 key 中的所有成员。

							hash类型类似于php的数组
							hset key name value//添加一个name=>value键值对到key这个hash类型
							例如: HSET sit redis xx
							hget key name //获取hash类型的name键对应的值
							例如:HGET sit redis
							hmset key name1 key1 name2 key2 //批量添加name=>value键值对到key这个hash类型
							hmget key name1 name2//批量获取hash类型的键对应的值
							hkeys //返回哈希表 key 中的所有键
							hvals //返回哈希表 key 中的所有值
							hgetall //返回哈希表 key 中,所有的键和值

							存:set name "小明"
							取:get name
							获取所有key:key *

							AUTH 123456  //输入密码进行认证
								
		linux:使用wget在线下载,
					① 下载redis源码
							wget http://download.redis.io/releases/redis-3.0.6.tar.gz
							或者使用ftp上传redis-3.0.6.tar.gz
						② 安装redis
							tar xf redis-3.0.6.tar.gz    //解压
							cd redis-3.0.6
							//不需要配置参数  不需要执行./configure
							make && make install//编译&&安装
					③ 将redis设置为系统服务
							cp (redis源码目录)utils/redis_init_script /etc/init.d/redis
							vim /etc/init.d/redis //修改红框处的代码
					④ 修改redis.conf配置文件
							mkdir /etc/redis //创建/etc/redis目录
							cp redis.conf /etc/redis/redis.conf 
							vim /etc/redis.conf
							//将redis配置文件复制到redis
							//设置为守护进程,以后台方式运行
							使用service redis start命令启动redis服务.
					⑤ 将redis服务设置为开机启动
							chkconfig –-add redis
							chkconfig –level 35 redis on

客户端:

	选择合适的客户端:
		① 命令行客户端
			默认启动:redis-cli.exe  默认连接本机127.0.0.1、默认端口6379服务
				指定Ip和端口启动:  redis -cli -h i地址 -p 端口号
				例如:redis -cli -h 172.16.0.15 -p 6380
		② 代码操作,Java的客户端jedis
				连接池的操作:
				通过连接池获取连接:
				API操作
				资源的还回

Redis的持久化策略:

	支持内存存储数据
	不定期持久化到磁盘
					两种模式rdb和aof--》需要在配置文件内进行修改
	先加载的是aof文件,如果有aof就会先选择数据的还原,如果没有就会选择rdb

redis的淘汰策略:

	其有一个内存的管理:内存是一个瓶颈,使用过期键,数据过期之后,就应该释放内存。过期键的删除策略--定时删除、随机删除等,但可能会没有将数据删除干净,所以此时使用数据的淘汰策略。
	设置过期键的和未设置过期键的,淘汰策略不同

使用场景:

	①作为中央缓存(以空间换取时间、提高性能)
				Hibernte(JPA)二级缓存,mybatis二级缓存,这些缓存默认都不支持在集群环境使用
	②内存队列(使用**list**来做)
		 		1)因为list左右两边的命令lpush/rpush放和lpop/rpop取---》  先进先出就实现了一个队列的效果,同方向的放和取
				2)若先进后出,则实现了一个栈的效果

	③排行榜、技术器(自增自减)
			使用incr key       decr key         incrBy key num      decrBy key num    进处理 

	④自动去重
			使用redis内部支持的set类型,将数据存放在set集合内,就可以实现自动去重

redis的造成的缓存穿透或缓存雪崩

(二)优化分析

针对品牌:因为品牌只是针对少量的操作,后台的压力不大。后台没有性能的问题,前台也比较少,无需优化

针对产品类型:类型数据量大,需要进行一个优化。因为从DB中获取是全部获取的,每次从DB的获取,会增加数据库的访问压力,数据传输的时间也延长,影响用户的体验。

对数据进行一个频繁的操作,应该使用缓存进行优化,但是缓存服务器压力也比较大,所以也使用一个页面的静态化。即后台使用缓存,前台使用缓存+页面的静态化

缓存分析:有二级缓存,默认情况下,一般不开启。
项目做完,需要发布到服务器中使用tomcat运行项目,二级缓存是存储在当前服务器的内存中,然后通过服务器的集群,实现支持超大并发的访问。但是不同机器之间的内存的缓存如何做内存的同步?无法实现。则使用Redis中央缓存服务器,将数据缓存到同一个redis服务器上,只操作服务器上的数据,避免了缓存同步的问题。

缓存是针对查询频繁,修改少的数据,以空间换取时间,即把数据从mysql中放入到redis中。

	缓存主要是做数据的查询,
		① 首先是查询redis中是否有数据,通过key值确定,
		② 如果没有数据,则从mysql中获取数据最后将数据放入redis并将数据返回给用户。
		③ 用户第二次请求数据时,redis内有数据,就直接将数据返回给用户。

需要保证redis和mysql数据的同步,当我们删除、添加、更新数据时,先操作数据库,再同步redis。

	mysql数据同步时机:
		① 在数据第一次使用的时候,将数据从mysql写入到redis,第一次访问速度较慢(常规业务使用)
		② 把需要缓存的数据在项目启动的时候,将数据从数据库中查询出来放入redis,启动比较慢(启动时,高并发时使用,避免启动时,用户访问数据导致数据库的压力剧增)

redis内如何存储数据对象:

		①序列化

		②json字符串。使用fastjson将数据对象转换为json字符串

(三)fastjson知识储备

pom.xml内导入依赖:

		
		    com.alibaba
		    fastjson
		    1.2.47
		

对象转json字符串:

JSON.toJSONString(new User(1L,"木兰"));

json字符串转对象:

JSON.parseObject(jsonString,User.class)

list对象json字符串的相互转换

List list1= Arrays.asList(new User(1L, "哈哈"), new Person(2L, "嘻嘻"));
JSON.toJSONString(list)

List list2= JSON.parseArray(string,User.class)

商品分类进行redis的存取缓存,其他服务也可能进行缓存的处理,因此将redis抽取为单独的一个服务,作为公共的缓存服务。以供其他服务进行调用(服务之间的调用通过Fegin进行调用)

服务之间的调用时内部的调用,使用fegin或rabbin,推荐采用fegin的形式进行服务接口的调用。

fegin如何玩?

(四)Fegin调用服务知识储备

导入依赖

定义接口(配置服务)

controller实现接口,覆写方法(规范,因为我们对外暴露的是接口)
在实现类内部写业务逻辑,因为是controller,所以也需要将controller暴露出去,并配置托底数据,需要有一个fallback的类

调接口,fegin直接注入进行调,类型与注入的形式,类似于调本地接口

使用豪猪,则使用托底数据,因此在使用的时候,需要开启熔断支持,即超时时时间配置,并告诉给用户

导入redis依赖,提供存取两个接口,同时集成豪猪
controller接口得实现,并暴露出去,此时redis的服务写好

最后使用这个接口,调用redis服务

(五)缓存服务搭建思路

  1. 搭建基本结构:
aigou_common_parent
          aigou_common_interface
          aigou_common_service
  1. 导入依赖
    aigou_common_service模块
    (common接口,Jedis客户端、注册中心)
		
            cn.lzi.aigou
            aigou_common_interface
            1.0-SNAPSHOT
        
		
		    redis.clients
		    jedis
		    2.9.0
		

接口模块导入依赖

		
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        

aigou_common_interface定义接口和托底方法
接口

package cn.lzj.aigou.client;

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

@FeignClient(value = "COMMON-PRIVODER",fallbackFactory = RedisClientFallbackFactory.class) //表示对哪一个服务进行处理
@RequestMapping(value = "/redis", method = RequestMethod.POST)
public interface RedisClient {

   /**
    * post参数接受(设置的时候传入的是非常多的,不要使用resultful风格)
    *      使用RequestParam进行接受请求参数,即从请求参数内,获取key,设置到key内
    * @param key
    * @param value
    */
   //设置(因为传入的值比较多,使用post传入)
   @RequestMapping(value = "/set", method = RequestMethod.POST)
   void set(@RequestParam("key") String key, @RequestParam("value") String value);

   /**
    * 因为使用的是在路径中,使用PathVariable注解进行接受
    * @param key
    * @return
    */
   //获取
   @RequestMapping(value = "/get/{key}", method = RequestMethod.GET)
   String get(@PathVariable("key") String key);
}

托底方法

package cn.lzj.aigou.client;

import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

/**
 * 托底函数,即回调函数,针对RedisClient
 */
@Component   //配置为组件
public class RedisClientFallbackFactory implements FallbackFactory {

    //因为RedisClient
    @Override
    public RedisClient create(Throwable throwable) {

        //进行托底方法的业务撰写
        return new RedisClient() {
            @Override
            public void set(String key, String value) {

            }

            @Override
            public String get(String key) {
                return null;   //null也是一个托底数据
            }
        };
    }
}

业务模块,创建接口

package cn.lzj.aigou.common.controller;

import cn.lzj.aigou.client.RedisClient;
import org.springframework.web.bind.annotation.*;

//暴露出去的redis服务接口,接口必须和redis的接口内部的参数一致
@RestController
@RequestMapping(value = "/redis", method = RequestMethod.POST)
public class RedisController implements RedisClient {


    @Override
    @RequestMapping(value = "/set", method = RequestMethod.POST)
    public void set(@RequestParam("key")String key, @RequestParam("value")String value) {

    }

    @Override
    @RequestMapping(value = "/get/{key}", method = RequestMethod.GET)
    public String get(@PathVariable("key") String key) {
        return null;
    }
}

抽取RedisUtil工具类

package cn.lzj.aigou.common.util;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Jedis;

public class RedisUtil {

    private static JedisPool jedisPool = null;

    static {

        //连接池配置
        GenericObjectPoolConfig poolConfig = new JedisPoolConfig();
        //参数设置:
        poolConfig.setMaxTotal(30);//最大连接数
        poolConfig.setMaxIdle(10);//最大空闲数
        poolConfig.setMaxWaitMillis(3*1000);//超时时间
        poolConfig.setTestOnBorrow(true);//借用资源的时候进行测试

        //硬编码数据应该放到properties的配置文件:直接用这个工具类
        String host = "127.0.0.1";
        int port = 6379;
        int timeout = 5 * 1000;
        String password = "root";
        jedisPool = new JedisPool(poolConfig, host, port, timeout, password);
    }

    /**
     * 设置
     * @param key
     * @param value
     */
    public static void set(String key, String value) {
        // 1:获取jedis实例
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //2:api操作
            jedis.set(key, value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(jedis!= null){
                //3:资源还回
                jedis.close();
            }
        }
    }

    /**
     * 获取
     * @param key
     * @return
     */
    public static String get(String key) {
        // 1:获取jedis实例
        Jedis jedis = jedisPool.getResource();
        try {
            //2:api操作
            return jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            //3:资源还回
            jedis.close();
        }
    }
}

服务端yml配置:


客户端业务导入依赖:开启openFegin
客户端配置开启熔断支持

客户端调用,需要扫描Fegin

产品分类客户端注入依赖:

业务层注入:

20视频为什么注入的是接口而不是控制层的接口

你可能感兴趣的:(爱购商城,性能优化,电商项目)