Easymall项目分布式拆分整合(五) 你的名字 2022-01-20 02:33 310阅读 0赞 # Easymall项目分布式拆分整合(五) # **目录** Easymall项目分布式拆分整合(五) 一.搭建后台商品系统 1.1搭建步骤 1.maven quickstart(springboot) 2.继承parent,依赖common 3.product工程还需要访问持久层,redis(common依赖的传递) 4.application.properties 5.添加一个redis底层连接池的配置类 1.代码模板 2.具体位置 6.nginx配置文件和hosts映射 1.nginx配置文件 2.hosts配置文件 7.商品后台系统的启动类StarterProduct 二.根据接口文件实现后台查询分页逻辑 1.接口文件 2.准备好所有内容(红色内容从以前项目粘贴过来) 1.ProductController(之前的Colltroller被前端使用了,需要重写) 2.ProductService 3.ProductMapper 4.ProductMapper.xml 5.运行代码 二.首页单个商品根据id查询 1.接口文件 2.ProductController 3.ProductService 1.缓存逻辑的使用 2.判断Key是否早redis存在, 3.redis缓存数据需要和数据库保持一致; 三.商品数据的查询后台分页 1.ProductController 2.ProductService 3.分页数据的缓存考虑问题; 四.商品数据的新增 1.ProductController 2.ProductService 五.商品数据的修改(更新锁) 1.ProductController 2.ProductService 添加更新锁 -------------------- # 一.搭建后台商品系统 # # 1.1搭建步骤 # ## **1.maven quickstart(springboot)** ## ![20190610162414687.png][] ### 2.继承parent,依赖common ### ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70][] ## 3.product工程还需要访问**持久层**,redis(common依赖的传递) ## <dependencies> <dependency> <groupId>cn..tedu</groupId> <artifactId>springboot-common-easymall</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> </dependencies> ## 4.application.properties ## server.port=8092 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql:///easydb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true spring.datasource.password=root spring.datasource.username=root mybatis.configuration.mapUnderscoreToCamelCase=true mybatis.mapperLocations=classpath:mapper/*.xml mybatis.typeAliasesPackage=com.jt.common.pojo redis.nodes=10.9.151.60:6379 redis.maxTotal=200 redis.maxIdle=8 redis.minIdle=3 ## 5.添加一个redis底层连接池的配置类 ## ### 1.代码模板 ### /** * 在当前工程扫描范围之内的configuration配置类 * 读取前缀是redis的所有key值,根据私有属性名称的getter/stter * 赋值给属性 * @author TEDU * */ @Configuration @ConfigurationProperties(prefix="redis") public class RedisCumConfiguration { //根据前缀读取数据,私有属性名称,必须和 //properties中的值相同 private String nodes; private Integer maxTotal; private Integer maxIdle; private Integer minIdle; //编写初始化JedisShardPool对象的方法,@Bean将返回对象 //作为框架管理的bean @Bean public ShardedJedisPool initJedisPool(){ //利用本类中读取的属性,创建连接池对象 //先做一个config对象 GenericObjectPoolConfig config=new GenericObjectPoolConfig(); config.setMaxIdle(maxIdle); config.setMaxTotal(maxTotal); //config.setMinIdle(minIdle); //解析nodes,生成一个list对象 //准备一个空内容 List<JedisShardInfo> infoList=new ArrayList<JedisShardInfo>(); String[] node = nodes.split(",");//{"10.9.9.9:6379","10l.9.9.9:6380",""} for (String hostAndPort : node) { String host=hostAndPort.split(":")[0]; int port=Integer.parseInt(hostAndPort.split(":")[1]); infoList.add(new JedisShardInfo(host, port)); } //list,config,构造连接池对象返回 return new ShardedJedisPool(config,infoList); } public String getNodes() { return nodes; } public void setNodes(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; } } ### 2.具体位置 ### ![20190610163211518.png][] ## 6.nginx配置文件和hosts映射 ## ### 1.nginx配置文件 ### ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 1][] ### 2.hosts配置文件 ### ![20190610163109778.png][] ## 7.商品后台系统的启动类StarterProduct ## @SpringBootApplication @MapperScan("com.jt.product.mapper") public class StarterProduct { public static void main(String[] args) { SpringApplication.run(StarterProduct.class, args); } } # 二.根据接口文件实现后台查询分页逻辑 # ## 1.接口文件 ## <table style="margin-left:.3333in;"> <tbody> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求地址</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:5.0694in;"> <p style="margin-left:0in;"><a href="http://product.jt.com/product/queryByPage?page=1&rows=5" rel="nofollow">http://product.jt.com/product/queryByPage?page=1&rows=5</a></p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求参数</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:5.0694in;"> <p style="margin-left:0in;">Integer page,Integer rows</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求方式</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:5.0694in;"> <p style="margin-left:0in;">get提交</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">相应数据</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:5.0694in;"> <p style="margin-left:0in;">Page对象,totalPage总页数,currentPage当前页数,products分页查询结果;</p> <p style="margin-left:0in;">{"total":56,"currentPage":2;"products":[{},{},{},{},{}]}</p> </td> </tr> </tbody> </table> ## 2.准备好所有内容(红色内容从以前项目粘贴过来) ## ### 1.ProductController(之前的Colltroller被前端使用了,需要重写) ### @RestController public class ProductController { @Autowired private ProductService productService; @RequestMapping("product/queryByPage") public Page queryByPage(Integer page,Integer rows){ return productService.queryByPage(page, rows); } //根据商品id查询某个商品对象 @RequestMapping("product/queryById/{productId}") public Product queryById(@PathVariable String productId){ return productService.queryById(productId); } //查询管理商品页面的分页数据 @RequestMapping("product/manage/queryByPage") public EasyUIResult queryManageByPage(Integer page,Integer rows){ return productService.queryByPageManage(page, rows); } //新增商品数据到数据库 @RequestMapping("product/saveProduct") //productName=**&productPrice=**&productCategory=** public Integer saveProduct(Product product){ try{ productService.saveProduct(product); return 1; }catch(Exception e){ e.printStackTrace(); return 0; } } @RequestMapping("product/updateProduct") public Integer updateProduct(Product product){ try{ productService.updateProductById(product); return 1; }catch(Exception e){ e.printStackTrace(); return 0; } } } ### 2.ProductService ### * 修改包com.jt.product.service; * 重新导入Page的包com.jt.common.vo * 重新导入ProductMapper的包 ### 3.ProductMapper ### * 修改包路径com.jt.product.mapper * 重新导入所有的新包Product com.jt.common.product ### 4.ProductMapper.xml ### * namespace="com.jt.product.mapper.ProductMapper" ### 5.运行代码 ### ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 2][] # 二.首页单个商品根据id查询 # ## 1.接口文件 ## <table style="margin-left:.3333in;"> <tbody> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求地址</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:4.8305in;"> <p style="margin-left:0in;"><a href="http://product.easymall.com/product/queryById/%7BproductId%7D" rel="nofollow">http://product.easymall.com/product/queryById/{productId}</a></p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求参数</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:4.8305in;"> <p style="margin-left:0in;">路径传参String productId</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求方式</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:4.8305in;"> <p style="margin-left:0in;">get提交</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">相应数据</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:4.8305in;"> <p style="margin-left:0in;">Product对象的json字符串</p> </td> </tr> </tbody> </table> ## 2.ProductController ## **根据接口文件内容编写方法和返回值** //根据商品id查询某个商品对象 @RequestMapping("product/queryById/{productId}") public Product queryById(@PathVariable String productId){ return productService.queryById(productId); } ## 3.ProductService ## ### 1.缓存逻辑的使用 ### **引入缓存技术redis,生成productKey** ### 2.判断Key是否早redis存在, ### * 存在:直接返回redis数据解析的对象 * 不存:到数据库查询,存放到redis中,供后续使用返回对象 ### 3.redis缓存数据需要和数据库保持一致; ### **查询缓存高并发下,会发生在更新删除缓存之后,由于高并发的查询导致还没有更新数据之前,就由别的用户将缓存个重新添加了旧数据,最终执行更新导致缓存和数据库数据不一致** ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 3][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 4][] * 基本思路:在系统对数据库做改操作时,一并将缓存数据修改,覆盖删除 * 可以在更新数据时,删除缓存之前添加一个更新的锁product\_productId.update.lock删除缓存完毕,更新数据库完毕,lock释放(超时时间) * 查询的逻辑: * 判断是否锁:有锁,不使用缓存,查数据库 * 没有锁:说明没有更新操作,执行缓存逻辑 @Autowired private RedisCumUtils jedis; public Product queryById(String productId) { //定义更新锁的key值 String productLockKey="product_"+productId+".lock"; //定义缓存的key String productKey="product_"+productId; //先判断锁的存在 try{ if(jedis.isExist(productLockKey)){ //说明有人正在更新,不能操作缓存 return productMapper.queryById(productId); }else{//正常执行缓存逻辑 if(jedis.isExist(productKey)){//缓存有数据 String productJson=jedis.query(productKey); return MapperUtils.MP.readValue(productJson, Product.class); }else{ //查询数据库,并且再缓存中添加数据 Product product=productMapper.queryById(productId); String json=MapperUtils.MP.writeValueAsString(product); jedis.addOrUpdate(productKey, json); return product; } } }catch(Exception e){ e.printStackTrace(); return null; } } # 三.商品数据的查询后台分页 # <table style="margin-left:.3333in;"> <tbody> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求地址</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.7576in;"> <p style="margin-left:0in;"><a href="http://product.easymall.com/product/manage/queryByPage?page=1&rows=5" rel="nofollow">http://product.easymall.com/product/manage/queryByPage?page=1&rows=5</a></p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求参数</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.7576in;"> <p style="margin-left:0in;">Integer page,Integer rows</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求方式</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.7576in;"> <p style="margin-left:0in;">get提交</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">相应数据</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.7576in;"> <p style="margin-left:0in;">EasyUIResult</p> <p style="margin-left:0in;">rows:分查询结果</p> <p style="margin-left:0in;">total:总数量</p> <p style="margin-left:0in;"> </p> </td> </tr> </tbody> </table> ## 1.ProductController ## //查询管理商品页面的分页数据 @RequestMapping("product/manage/queryByPage") public EasyUIResult queryManageByPage(Integer page,Integer rows){ return productService.queryByPageManage(page, rows); } ## 2.ProductService ## public EasyUIResult queryByPageManage(Integer page, Integer rows) { EasyUIResult result=new EasyUIResult(); //总条数,count查询 int total=productMapper.queryTotal(); result.setTotal(total); //查询的分页结果,计算start int start=(page-1)*rows; List<Product> pList = productMapper.queryByPage(start, rows); result.setpList(pList); return result; } ### 3.分页数据的缓存考虑问题; ### * 也可以做缓存,缓存逻辑和商品id查询逻辑一样 * key设计根据page和rows的值定义分页的缓存数据 * 分页数据太庞大了,直接使用String类型的数据无法高效实现 * 一旦出现了新增商品,删除商品,整体的所有分页数据都错误 # 四.商品数据的新增 # <table style="margin-left:.3333in;"> <tbody> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求地址</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;"><a href="http://product.easymall.com/product/saveProduct" rel="nofollow">http://product.easymall.com/product/saveProduct</a></p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求参数</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;">Product product(productName=**&productPrice=**&)</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求方式</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;">post</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">相应数据</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;">如果两个系统对接需要传递详细信息</p> <p style="margin-left:0in;"><span style="color:#FF0000;">1返回一个SysResult status代表各种状态数字,data,msg传递复杂的信息</span></p> <p style="margin-left:0in;">2 返回一个int字符串1成功,0表示失败</p> </td> </tr> </tbody> </table> ## 1.ProductController ## //新增商品数据到数据库 @RequestMapping("product/saveProduct") //productName=**&productPrice=**&productCategory=** public Integer saveProduct(Product product){ try{ productService.saveProduct(product); return 1; }catch(Exception e){ e.printStackTrace(); return 0; } } ## 2.ProductService ## **可以在新增数据商品到数据库时,直接添加缓存逻辑,但是由于重要的步骤是新增数据库数据,可以将缓存新增异步执行;** public void saveProduct(Product product) { //新增调用持久层之前,将数据补充完整 product.setProductId(UUID.randomUUID().toString()); productMapper.saveProduct(product); /* rabbit.convertAndSend("dir","saveKeyRedis",product.getProductId()) */ } * rabbitmq,将本来应该同步执行的redis缓存添加逻辑 * 封装成消息形式,异步获取消息执行缓存添加 # 五.商品数据的修改(更新锁) # <table style="margin-left:.3333in;"> <tbody> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求地址</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;"><a href="http://product.easymall.com/product/updateProduct" rel="nofollow">http://product.easymall.com/product/updateProduct</a></p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求参数</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;">Product product(productName=**&productPrice=**&)</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">请求方式</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;">post</p> </td> </tr> <tr> <td style="border-color:#a3a3a3;vertical-align:top;width:1.0402in;"> <p style="margin-left:0in;">相应数据</p> </td> <td style="border-color:#a3a3a3;vertical-align:top;width:6.309in;"> <p style="margin-left:0in;">如果两个系统对接需要传递详细信息</p> <p style="margin-left:0in;"><span style="color:#FF0000;">1返回一个SysResult status代表各种状态数字,data,msg传递复杂的信息</span></p> <p style="margin-left:0in;">2 返回一个int字符串1成功,0表示失败</p> </td> </tr> </tbody> </table> ## 1.ProductController ## @RequestMapping("product/updateProduct") public Integer updateProduct(Product product){ try{ productService.updateProductById(product); return 1; }catch(Exception e){ e.printStackTrace(); return 0; } } ## 2.ProductService 添加更新锁 ## public void updateProductById(Product product) { //更新锁添加到redis String productLockKey="product_"+product.getProductId()+".lock"; //定义缓存的key String productKey="product_"+product.getProductId(); //添加锁//可以定义锁的超时 jedis.addOrUpdate(productLockKey, ""); //删除已有的缓存 jedis.delete(productKey); productMapper.updateProduct(product); //释放锁 jedis.delete(productLockKey); } [20190610162414687.png]: /images/20220120/8d2211e12de343459fdc3816d6e907d8.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70]: /images/20220120/2fa2a944ac71428ba16ed68b5cb5bede.png [20190610163211518.png]: /images/20220120/0c81071e10d0418b9bda652499f06559.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 1]: /images/20220120/aa73092ce61d4fd787bd961d31a92508.png [20190610163109778.png]: /images/20220120/f6e7154610ac41fc9f8a5b1f0c6331bc.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 2]: /images/20220120/f3e98b66605c4b1bbf74d8f7946d4cec.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 3]: /images/20220120/d111229c88ed488c99e94a7991f7a97b.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpdVk1MjE_size_16_color_FFFFFF_t_70 4]: /images/20220120/e668e847d5a6464d8cbc165b98da701e.png
相关 分布式AKF拆分原则 目录 1 前言 2 什么是AKF 3 如何基于 AKF X 轴扩展系统? 4 如何基于 AKF Y 轴扩展系统? 5 如何基于 AKF Z 淡淡的烟草味﹌/ 2024年03月03日 08:08/ 0 赞/ 97 阅读
相关 Easymall项目分布式拆分整合(七) Easymall项目分布式拆分整合(七) -------------------- 目录 不念不忘少年蓝@/ 2022年05月01日 12:51/ 0 赞/ 257 阅读
相关 Easymall项目分布式拆分整合(四) Easymall项目分布式拆分整合(四) -------------------- 目录 桃扇骨/ 2022年04月27日 19:52/ 0 赞/ 339 阅读
相关 Easymall项目分布式拆分整合(二) Easymall项目分布式拆分整合(二) -------------------- 目录 r囧r小猫/ 2022年04月27日 16:54/ 0 赞/ 335 阅读
相关 Easymall项目分布式拆分整合(六) 前台系统和后台系统的功能整合 -------------------- 目录 刺骨的言语ヽ痛彻心扉/ 2022年01月20日 08:45/ 0 赞/ 192 阅读
相关 Easymall项目分布式拆分整合(五) Easymall项目分布式拆分整合(五) 目录 Easymall项目分布式拆分整合(五) 一.搭建后台 你的名字/ 2022年01月20日 02:33/ 0 赞/ 311 阅读
相关 Easymall项目分布式拆分整合(八) Easymall项目分布式拆分整合(八) -------------------- 目录 系统管理员/ 2022年01月15日 16:43/ 0 赞/ 254 阅读
相关 Easymall项目分布式拆分整合(九) Easymall项目分布式拆分整合(九) -------------------- 一.购物车系统搭建 古城微笑少年丶/ 2022年01月15日 03:35/ 0 赞/ 299 阅读
还没有评论,来说两句吧...