互联网框架day11(redis-cluster高可用[项目整合])

1.redis的客户端逻辑
1.1JedisCluster整合springboot完成
user用户系统使用redis
product使用redis
order/cart/seckill/search都可以使用
redis
封装成一个maven工程
代码满足所有的工程扫描范围(cn.tedu)
user/product等系统依赖这个maven工程
1.2user系统测试jedisCluster
8000 8001 8002 这三个节点信息创建spring管理的bean对象JedisCluster

set/get 方法
name—>8001 只要8004替换成功
key2–>8002 8005
key3—>8000 8003
1.3实现的jediscluster底层的原理
初始化过程创建的内存数据结构
set/get方法滤清对内存对象数据的使用逻辑
2.user最终版本代码
注入jedisCluster使用
2.1登录顶替
2.2超时突然断开
3.product的商品缓存和高并发锁
3.1商品缓存
接收请求,就收参数productId,创建key值(不同的商品时不同的redis的key值数据)product-query_+productId
到缓存redis获取数据
非空:缓存有数据不访问持久层
返回
空:缓存没数据(第一次使用缓存),到持久层获取,并且存储在缓存一份,供后续使用
3.2更新商品的高并发锁
查询缓存逻辑,如果更新时,对数据库做数据的更新造成的缓存数据不一致

4.cart购物车
4.1查询购物车
4.2新增购物车
4.3更新购物车num
4.4删除购物车

JedisCluster整合springboot

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<parent>
  	<groupId>cn.tedu</groupId>
  	<artifactId>easymall-parent</artifactId>
  	<version>0.0.1-SNAPSHOT</version>
  </parent>
<!--springboot 传递spring内容  -->
  	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter</artifactId>
  	</dependency>
  	<!-- redis -->
  	<dependency>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-redis</artifactId>
  	</dependency>

编写配置代码
配置类

package cn.tedu.common.redis.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class ClusterConfig {
	
}

在这里插入图片描述

package cn.tedu.common.redis.config;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix="redis.cluster")
public class ClusterConfig {
	//四个属性
	private List<String> nodes;
	private Integer maxTotal;
	private Integer maxIdle;
	private Integer minIdle;
	public List<String> getNodes() {
		return nodes;
	}
	public void setNodes(List<String> nodes) {
		this.nodes = nodes;
	}
	public Integer getMaxTotal() {
		return maxTotal;
	}
	public void setMaxTotal(Integer maxTotal) {
		this.maxTotal = maxTotal;
	}
	public Integer getMaxIdle() {
		return maxIdle;
	}
	public void setMaxIdle(Integer maxIdle) {
		this.maxIdle = maxIdle;
	}
	public Integer getMinIdle() {
		return minIdle;
	}
	public void setMinIdle(Integer minIdle) {
		this.minIdle = minIdle;
	}
	
}

//初始化读取属性后创建的bean对象JedisCluster
	@Bean
	public JedisCluster initJedisCluster(){
		//手集节点信息
		Set<HostAndPort> set=new HashSet<HostAndPort>();
		for(String node:nodes){
			//每次循环拿到ip:port
			String host=node.split(":")[0];
			int port=Integer.parseInt(node.split(":")[1]);
			set.add(new HostAndPort(host, port));
		}
		//配置对象
		GenericObjectPoolConfig config=new GenericObjectPoolConfig();
		config.setMaxTotal(maxTotal);
		config.setMaxIdle(maxIdle);
		config.setMinIdle(minIdle);
		//创建JedisCluster
		return new JedisCluster(set,config);
		
	}

2.3其它工程依赖common-redis
业务系统依赖
product
user依赖
业务系统根据读取属性配置properties
user中实现一个测试代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

业务逻辑使用redis
1.user的业务逻辑
已经实现了user的登录功能,解决session共享的问题
1.1使用JedisCluster替换连接池
private JedisCluster jedis;
代码中的finally不需要了可以删除
1.2登录顶替
问题:一个用户,可以被多个客户端同时登录,同时具备一个用户的所有的权限
解决办法:实现登录顶替,一个用户在redis中只允许存一个ticket ticket-userJson 将ticket作为有效key值和userId userName唯一值绑定

在这里插入图片描述
扩展:一个账号三个人使用
在这里插入图片描述

注意:【更改了脚本,因为所删除的文件跑到了src目录下】

for port in {8000..8005};do ./redis-cli -c -p $port shutdown;done;
rm -rf dump800*;
rm -rf appendonly800*;
rm -rf nodes-800*;
for port in {8000..8005};do ./redis-server /usr/local/redis/bin/$port/redis-cluster.conf;done;
echo yes|./redis-trib.rb create --replicas 1 192.168.253.129:8000 192.168.253.129:8001 192.168.253.129:8002 192.168.253.129:8003 192.168.253.129:8004 192.168.253.129:8005;
/**
		 * 顶替逻辑:
		 * login=login_caoyang  存在吗?删除login_caoyang的值的值:不用管
		 * newTicket=caoyangtimeid   jsonuser
		 * login=login_caoyang   caoyangtimeid
		 * 
		 */
		//判断登录的权限检验select where user_name and password
		//加密
		user.setUserPassword(MD5Util.md5(user.getUserPassword()));
		User exist=userMapper.selectUserByUserNameAndPassword(user);
		String loginKey="login_"+user.getUserName();
		String newTicket="";
		//判断loginKey是不是存在
		if(jedis.exists(loginKey)){
			//曾经有人登录过
			//将oldTicket
			String oldTicket=jedis.get(loginKey);
			jedis.del(oldTicket);
		}
		//正常设置newTicket-userJson
		try {
			newTicket="EM_TICKET"+System.currentTimeMillis()+exist.getUserId();
			//对象转化userJson
			String userJson=MapperUtil.MP.writeValueAsString(exist);
			jedis.setex(newTicket, 60*60*2, userJson);
			//设置有效的ticket使用
			jedis.setex(loginKey, 60*60*2, newTicket);
			
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}
		return newTicket;

1.3突然超时
问题:如果超时设置2小时,用户访问超过2小时临界点突然断开
在这里插入图片描述

在这里插入图片描述
pttl是获取剩余的时间
pexpire是更改剩余的时间
在这里插入图片描述

//判断剩余的时间[增加续约]
		Long leftTime=jedis.pttl(ticket);
		if(leftTime<1000*60*30){//小于30分钟
			//续约 续约1小时
			jedis.pexpire(ticket, leftTime+1000*60*60);
		}
		return jedis.get(ticket);

2.商品系统
在这里插入图片描述

在这里插入图片描述
简单来说,就是在高并发情况下,查询的速度比更更新的速度快,导致查到的商品与数据库中已经更改的商品还是不一致,或者说有可能查询一直在等待更改操作,一直卡顿。
在这里插入图片描述
高并发情况下造成顺序错误
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<dependency>
    	<groupId>cn.tedu</groupId>
        <artifactId>easymall-common-redis</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
#redis-cluster
redis.cluster.nodes=192.168.253.129:8000,192.168.253.129:8001,192.168.253.129:8002
redis.cluster.maxTotal=200
redis.cluster.maxIdle=8
redis.cluster.minIdle=3
@Autowired
	private JedisCluster cluster;
	public Product queryPageProducts(String productId) {
		//引入缓存判断更新锁的存在
		//生成当前逻辑中需要的key
		//锁的key  product_update_productId+.lock
		String updateLock="product_update_"+productId+".lock";
		String productKey="product_"+productId;
		//判断锁是否存在
		try {
			if(cluster.exists(updateLock)){
				//锁存在,有人更新数据,缓存就不能使用
				return productMapper.selectProductById(productId);
			}else{
				//锁不存在,判断缓存逻辑
				if(cluster.exists(productKey)){
					//缓存命中,解析productJson
					//src就是json字符串
					//value  class 对象解析的类反射对象
					return MapperUtil.MP.readValue(cluster.get(productKey), Product.class);
				}else{
					//查持久层
					Product product=productMapper.selectProductById(productId);
					//加入缓存
					cluster.setex(productKey, 60*60*2,MapperUtil.MP.writeValueAsString(product));
					return product;
					}
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		
		
		
		//TODO 商品查询的缓存
		//return productMapper.selectProductById(productId);
	}
	public void deployProduct(Product product) {
		try {
			//TODO新增缓存
			//补齐数据 uuid保存productId
			product.setProductId(UUID.randomUUID().toString());
			//1030-dsaf-sdfa-saf-saf
			productMapper.insertProduct(product);
			//加入缓存
			String productKey="product_"+product.getProductId();
			cluster.setex(productKey, 60*60*2,MapperUtil.MP.writeValueAsString(product));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void renewProduct(Product product) {
		/*
		 * 1.加锁(超时时间5分钟)
		 * 2.删除缓存productKey
		 * 3.更新数据库
		 * 4.释放锁
		 */
		String updateLock="product_update_"+product.getProductId()+".lock";
		String productKey="product_"+product.getProductId();
		cluster.setex(updateLock,60*5,"");
		cluster.del(productKey);
		productMapper.updateProductById(product);
		cluster.del(updateLock);
		
	}

2.4扩展redis使用的思路
需求:
注册登录时填写账号表单提交前的验证码发送的逻辑
前端限制发送频率,后端代码也要限制各种规则(5分钟最多发3次,超过3次非法,当前手机号锁定10分钟不能发送)
用redis存储发送的验证码
手机号.code
关键逻辑生成5分钟超过三次的逻辑,判断是否生成一个10分钟超时的锁

解决方案:
在这里插入图片描述
在这里插入图片描述

购物车系统
1.功能演示
1.1查询我的购物车
1.2新增购物车
1.3修改购物车num
1.4删除购物车
2.数据库表格的结构
t_cart
user-id/product_id:购物车表格的复合主键。两个字段同时存在定义一行数据的意义:某个用户想要购买某个商品

在这里插入图片描述
num:购买的数量

3.根据接口文件开发功能
3.1搭建一个购物车系统
user/product
eureka client注册在eureka使用
用到ribbon(新增购物车时,数据使用ribbon调用商品服务)
持久层
代码架子
在这里插入图片描述
启动类

package cn.tedu;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@MapperScan("cn.tedu.cart.mapper")
@EnableEurekaClient
public class StarterCartService {
	public static void main(String[] args) {
		SpringApplication.run(StarterCartService.class, args);
		
	}
	//ribbon 创建一个可以负载均衡访问服务的对象
	@Bean
	@LoadBalanced
	public RestTemplate initRestTemplate(){
		return new RestTemplate();
	}
}

代码架子
在这里插入图片描述

在这里插入图片描述
zuul网关添加cartservice服务的路由转发

 zuul.routes.cart.path=/zuul-cart/**
 zuul.routes.cart.serviceId=cartservice

在这里插入图片描述
3.3购物车新增
接口文件

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
controller

package cn.tedu.cart.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.jt.common.pojo.Cart;
import com.jt.common.vo.SysResult;

import cn.tedu.cart.service.CartService;

@RestController
@RequestMapping("cart/manage")
public class CartController {
	@Autowired
	private CartService cartService;
	//查询我的购物车
	@RequestMapping("query")
	public List<Cart> queryMyCarts(String userId){
		return cartService.queryMyCarts(userId);
	}
	
	//新增商品到我的购物车
	@RequestMapping("save")
	public SysResult saveMyCart(Cart cart){
		//userd productId num
		try {
			cartService.saveMyCart(cart);
			return SysResult.ok();
		} catch (Exception e) {
			e.printStackTrace();
			return SysResult.build(201, "新增购物车失败", null);
		}
	}
	
	//更新购物车num数量
	@RequestMapping("update")
	public SysResult updateMyCartNum(Cart cart){
		try {
			cartService.updateMyCartNum(cart);
			return SysResult.ok();
		} catch (Exception e) {
			e.printStackTrace();
			return SysResult.build(201, "更新失败", null);
		}
	}
	
	//购物车的删除
	@RequestMapping("delete")
	public SysResult deleteMyCart(Cart cart){
		try {
			cartService.deleteMyCart(cart);
			return SysResult.ok();
		} catch (Exception e) {
			e.printStackTrace();
			return SysResult.build(201, "删除失败", null);
		}
	} 
}

service

package cn.tedu.cart.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.jt.common.pojo.Cart;
import com.jt.common.pojo.Product;

import cn.tedu.cart.mapper.CartMapper;

@Service
public class CartService {
	@Autowired
	private CartMapper cartMapper;
	public List<Cart> queryMyCarts(String userId) {
		
		return cartMapper.selectCartsByUserId(userId);
	}
	@Autowired
	private RestTemplate client;
	public void saveMyCart(Cart cart) {
		//查询已有
		Cart exist=cartMapper.selectExistByUserIdAndProductId(cart);
		if(exist!=null){
			//已经存在 更新num的数量
			//更行内存对象数据
			cart.setNum(cart.getNum()+exist.getNum());
			cartMapper.updateCartByUserIdAndProductId(cart);
		}else{//没数据,新增购物车
			//获取商品服务返回的数据,封装补齐cart对象
			Product prod=client.getForObject("http://productservice/product/manage/item/"+cart.getProductId(),Product.class);
			cart.setProductImage(prod.getProductImgurl());
			cart.setProductName(prod.getProductName());
			cart.setProductPrice(prod.getProductPrice());
			//调用insert语句
			cartMapper.insertCart(cart);
			
		}
	}
	public void updateMyCartNum(Cart cart) {
		cartMapper.updateCartByUserIdAndProductId(cart);
		
	}
	public void deleteMyCart(Cart cart) {
		cartMapper.deleteCartByUserIdAndProductId(cart);
		
	}

}

mapper

package cn.tedu.cart.mapper;

import java.util.List;

import com.jt.common.pojo.Cart;

public interface CartMapper {

	List<Cart> selectCartsByUserId(String userId);

	Cart selectExistByUserIdAndProductId(Cart cart);

	void updateCartByUserIdAndProductId(Cart cart);

	void insertCart(Cart cart);

	void deleteCartByUserIdAndProductId(Cart cart);

}

mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.cart.mapper.CartMapper">
	<select id="selectCartsByUserId" parameterType="String" resultType="Cart">
		select * from t_cart where user_id=#{userId};
	</select>
	<select id="selectExistByUserIdAndProductId" parameterType="Cart" resultType="Cart">
		select * from t_cart where user_id=#{userId} and product_id=#{productId};
	</select>
	<update id="updateCartByUserIdAndProductId" parameterType="Cart">
		update t_cart set num=#{num} where user_id=#{userId} and product_id=#{productId};
	</update>
	<insert id="insertCart"  parameterType="Cart">
		insert into t_cart (user_id,product_id,product_price,product_image,product_name,num) values(#{userId},#{productId},#{productPrice},#{productImage},#{productName},#{num})
	</insert>
	<delete id="deleteCartByUserIdAndProductId" parameterType="Cart">
		delete from t_cart where user_id=#{userId} and product_id=#{productId};
	</delete>
</mapper>

单机的数据库策略
在这里插入图片描述
在这里插入图片描述
上图违反三范式
在这里插入图片描述
解决方式1为上图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值