因为商城项目使用的是微服务的架构,比如产品服务、订单服务、物流服务等等,不同的服务可能会部署在不同的电脑上,以前我们优化查询是用过缓存实现的,即将用户所需要访问的数据,在用户第一次查询的时候,就进入数据库进行查询,然后将所有的数据存放到缓存中,之后用户若再次访问就直接从内存中进行获取,不需要再访问数据库,这在性能上面优化了。并且当出现并发的情况时,比如:几千个用户同时访问同一个资源,这会给数据库或内存造成很大的压力。
但是分布式架构出现,以前的缓存的方式就不适用了,因为不同的计算机是不共享内存的,解决方案就是,再起一个缓存服务,作为中央缓存,用户直接通过路由网关,访问中央缓存内的数据。
技术选型:
选择Reids实现中央缓存,因为其数据存放在内存中,并且数据会不定期持久化到硬盘内,可以保证数据的安全性,且其支持存储的数据类型也比较多(比如String、List、Set 、HashSet),支持跨语言(客户端的选择,有利于发展)、支持超大并发,可以实现集群。
服务端:
(扩展: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字符串
pom.xml内导入依赖:
com.alibaba
fastjson
1.2.47
对象转json字符串:
JSON.toJSONString(new User(1L,"木兰"));
json字符串转对象:
JSON.parseObject(jsonString,User.class)
list对象json字符串的相互转换
List
商品分类进行redis的存取缓存,其他服务也可能进行缓存的处理,因此将redis抽取为单独的一个服务,作为公共的缓存服务。以供其他服务进行调用(服务之间的调用通过Fegin进行调用)
服务之间的调用时内部的调用,使用fegin或rabbin,推荐采用fegin的形式进行服务接口的调用。
fegin如何玩?
导入依赖
定义接口(配置服务)
controller实现接口,覆写方法(规范,因为我们对外暴露的是接口)
在实现类内部写业务逻辑,因为是controller,所以也需要将controller暴露出去,并配置托底数据,需要有一个fallback的类
调接口,fegin直接注入进行调,类型与注入的形式,类似于调本地接口
使用豪猪,则使用托底数据,因此在使用的时候,需要开启熔断支持,即超时时时间配置,并告诉给用户
导入redis依赖,提供存取两个接口,同时集成豪猪
controller接口得实现,并暴露出去,此时redis的服务写好
最后使用这个接口,调用redis服务
aigou_common_parent
aigou_common_interface
aigou_common_service
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视频为什么注入的是接口而不是控制层的接口