Browse Source

day18
jd-hotkey

it_lv 2 months ago
parent
commit
b30b3e2b03

+ 7 - 1
common/common-util/src/main/java/com/atguigu/tingshu/common/util/AuthContextHolder.java

@@ -7,13 +7,19 @@ import com.alibaba.ttl.TransmittableThreadLocal;
  */
 public class AuthContextHolder {
 
-    //private static ThreadLocal<Long> userId = new ThreadLocal<Long>();
+    //private static ThreadLocal<Long> tl1 = new ThreadLocal<Long>();
+    //private static ThreadLocal<Long> tl2 = new InheritableThreadLocal<>();
     private static ThreadLocal<Long> userId = new TransmittableThreadLocal<Long>();
+    private static ThreadLocal<String> nameTL = new TransmittableThreadLocal<String>();
 
     public static void setUserId(Long _userId) {
         userId.set(_userId);
     }
 
+    public static void setUserName(String _userId) {
+        nameTL.set(_userId);
+    }
+
     public static Long getUserId() {
         return userId.get();
     }

+ 2 - 0
common/service-util/src/main/java/com/atguigu/tingshu/common/cache/GuiGuCacheAspect.java

@@ -42,6 +42,8 @@ public class GuiGuCacheAspect {
     @Around("@annotation(guiGuCache)")
     public Object around(ProceedingJoinPoint joinPoint, GuiGuCache guiGuCache) throws Throwable {
         try {
+            //0.TODO 判断是否为热点Key,如果是热点Key,方式一:则直接走本地缓存返回  方式二:将热点key剩余有效时间获取到,小于阈值,设置过期时间
+
             //1.优先从Redis缓存中获取业务数据,命中缓存直接返回
             //1.1 构建业务key 形式:前缀:方法参数值(多个参数用_拼接)
             String params = "none";

+ 3 - 0
common/service-util/src/main/java/com/atguigu/tingshu/common/config/mybatisPlus/MybatisPlusConfig.java

@@ -24,8 +24,11 @@ public class MybatisPlusConfig {
     @Bean
     public MybatisPlusInterceptor optimisticLockerInnerInterceptor(){
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        //向Mybatis过滤器链中乐观锁拦截器
+        //interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
         //向Mybatis过滤器链中添加分页拦截器
         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+
         return interceptor;
     }
 

+ 2 - 1
common/service-util/src/main/java/com/atguigu/tingshu/common/login/GuiGuLoginAspect.java

@@ -71,10 +71,11 @@ public class GuiGuLoginAspect {
         if(userInfoVo!=null){
             //5.1 判断key存活时间,如果小于规定阈值:6小时,则刷新key存活时间
             Long expireHours = redisTemplate.getExpire(loginKey, TimeUnit.HOURS);
-            if(expireHours<=6){
+            if(expireHours<=24){
                 redisTemplate.expire(loginKey, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
             }
             AuthContextHolder.setUserId(userInfoVo.getId());
+            AuthContextHolder.setUserName("axxx");
         }
 
         //二、目标方法执行

+ 2 - 1
pom.xml

@@ -30,7 +30,8 @@
         <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
         <mysql.version>8.0.30</mysql.version>
         <knife4j.version>4.1.0</knife4j.version>
-        <fastjson.version>1.2.29</fastjson.version>
+        <fastjson.version>1.2.83</fastjson.version>
+        <!--<fastjson.version>1.2.29</fastjson.version>-->
         <vod_api.version>2.1.4</vod_api.version>
         <minio.version>8.2.0</minio.version>
         <jodatime.version>2.10.1</jodatime.version>

+ 27 - 0
service/service-album/pom.xml

@@ -66,6 +66,33 @@
             <artifactId>tencentcloud-sdk-java-tms</artifactId>
             <version>3.1.1010</version>
         </dependency>
+   <!--     <dependency>
+            <groupId>com.jd.platform.hotkey</groupId>
+            <artifactId>common</artifactId>
+            <version>0.0.4-SNAPSHOT</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>cn.hutool</groupId>
+                    <artifactId>hutool-all</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>-->
+        <dependency>
+            <groupId>com.jd.platform.hotkey</groupId>
+            <artifactId>hotkey-client</artifactId>
+            <version>0.0.4-SNAPSHOT</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>cn.hutool</groupId>
+                    <artifactId>hutool-all</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>28.2-jre</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 93 - 4
service/service-album/src/main/java/com/atguigu/tingshu/album/api/TestController.java

@@ -1,14 +1,19 @@
 package com.atguigu.tingshu.album.api;
 
+import com.atguigu.tingshu.album.service.CacheService;
 import com.atguigu.tingshu.album.service.TestService;
+import com.jd.platform.hotkey.client.callback.JdHotKeyStore;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RBlockingQueue;
+import org.redisson.api.RDelayedQueue;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.Cacheable;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author: atguigu
@@ -60,4 +65,88 @@ public class TestController {
     }
 
 
+    @Autowired
+    private RedissonClient redissonClient;
+
+    @GetMapping("/sendDelayMessage/{msg}/{ttl}")
+    public String sendDelayMessage(@PathVariable String msg, @PathVariable Long ttl) {
+        //1.创建分布式阻塞队列
+        RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("delayQueue");
+        //2.创建延迟队列
+        RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
+        //3.发送延迟消息
+        delayedQueue.offer(msg, ttl, TimeUnit.SECONDS);
+        return "发送延时消息成功";
+    }
+
+
+    /**
+     * 监听阻塞队列消息
+     */
+    //@PostConstruct
+    //public void init() {
+    //    //1.创建分布式阻塞队列
+    //    RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("delayQueue");
+    //    //开启单独线程 专门拉取延迟消息
+    //    ExecutorService executor = Executors.newSingleThreadExecutor();
+    //    executor.submit(() -> {
+    //        while (true) {
+    //            try {
+    //                String result = blockingQueue.poll(15, TimeUnit.SECONDS);
+    //                if (StringUtil.isNotBlank(result)) {
+    //                    log.info("监听到延时队列消息:{}", result);
+    //                }
+    //            } catch (Exception e) {
+    //                log.error("监听延时队列异常:{}", e.getMessage());
+    //            }
+    //        }
+    //    });
+    //}
+
+
+    @Autowired
+    private CacheService cacheService;
+
+    @GetMapping("addKey")
+    public Object add(Integer count) {
+        for (int i = 0; i < 20; i++) {
+            cacheService.set("key" + i, "我是一个用来做测试的value:" + i);
+        }
+        return "success";
+    }
+
+
+    /**
+     * 使用热key查询,从redis查询key
+     */
+    @GetMapping("findHot/{key}")
+    public Object findWithHotKey(@PathVariable String key) {
+        return cacheService.get(key);
+    }
+
+
+    /**
+     * 判断是否为热key
+     * isHotKey(key)该方法会返回该key是否是热key,如果是返回true,如果不是返回false.
+     * 并且会将key上报到探测集群进行数量计算。该方法通常用于判断只需要判断key是否热、不需要缓存value的场景,如刷子用户、接口访问频率等。
+     *
+     * @param key
+     * @return
+     */
+    @GetMapping("/hotKey/{key}")
+    public Object hotKey(@PathVariable String key) {
+        if (StringUtils.isNotBlank(key) && JdHotKeyStore.isHotKey(key)) {
+            return "isHot";
+        } else {
+            return "noHot";
+        }
+    }
+
+
+    @DeleteMapping("")
+    public Object aDelete(String key) {
+        JdHotKeyStore.remove(key);
+        return 1;
+    }
+
 }

+ 25 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/config/EtcdConfig.java

@@ -0,0 +1,25 @@
+package com.atguigu.tingshu.album.config;
+
+import com.jd.platform.hotkey.common.configcenter.IConfigCenter;
+import com.jd.platform.hotkey.common.configcenter.etcd.JdEtcdBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author atguigu
+ * @version 1.0
+ */
+@Configuration
+public class EtcdConfig {
+
+    @Value("${etcd.server}")
+    private String etcd;
+
+
+    @Bean
+    public IConfigCenter client() {
+        //连接多个时,逗号分隔
+        return JdEtcdBuilder.build(etcd);
+    }
+}

+ 25 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/config/Starter.java

@@ -0,0 +1,25 @@
+    package com.atguigu.tingshu.album.config;
+
+    import com.jd.platform.hotkey.client.ClientStarter;
+    import jakarta.annotation.PostConstruct;
+    import org.springframework.beans.factory.annotation.Value;
+    import org.springframework.stereotype.Component;
+
+    /**
+     * @author atguigu
+     * @version 1.0
+     */
+    @Component
+    public class Starter {
+        @Value("${etcd.server}")
+        private String etcd;
+        @Value("${spring.application.name}")
+        private String appName;
+
+        @PostConstruct
+        public void init() {
+            ClientStarter.Builder builder = new ClientStarter.Builder();
+            ClientStarter starter = builder.setAppName(appName).setEtcdServer(etcd).build();
+            starter.startPipeline();
+        }
+    }

+ 54 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/service/CacheService.java

@@ -0,0 +1,54 @@
+package com.atguigu.tingshu.album.service;
+
+import com.jd.platform.hotkey.client.callback.JdHotKeyStore;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * @version 1.0
+ */
+@Slf4j
+@Component
+public class CacheService {
+    @Resource
+    private RedisTemplate<String, String> redisTemplate;
+
+
+    public Object getFromRedis(String key) {
+        return redisTemplate.opsForValue().get(key);
+    }
+
+
+    /**
+     * Object getValue(String key),该方法是一个整合方法,相当于isHotKey和get两个方法的整合,该方法直接返回本地缓存的value。
+     * 如果是热key,则存在两种情况,
+     *      1是返回value 说明是一个热点key,已经加入到本地缓存,从本地缓存获取
+     *      2是返回null。如果不是热key,从Redis中获取即可,并且将key上报到探测集群进行数量探测。
+     * @param key
+     * @return
+     */
+    public Object get(String key) {
+        Object localCache = JdHotKeyStore.getValue(key);
+        //如果已经缓存过了
+        if (localCache != null) {
+            System.out.println("is hot key");
+            return localCache;
+        } else {
+            Object redisCache = getFromRedis(key);
+            JdHotKeyStore.smartSet(key, redisCache);
+            return redisCache;
+        }
+    }
+
+    public void set(String key, String value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    public void remove(String key) {
+        JdHotKeyStore.remove(key);
+    }
+
+}

+ 6 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/AlbumInfoServiceImpl.java

@@ -61,6 +61,11 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
     @Autowired
     private TrackInfoMapper trackInfoMapper;
 
+    //@Transactional(propagation = Propagation.REQUIRES_NEW)
+    public void a1(){
+        b();
+    }
+
     /**
      * 保存专辑
      *
@@ -108,6 +113,7 @@ public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo
         //4.对专辑中文本内容进行审核 异步审核
         vodService.reviewAlbumText(albumInfo);
         log.info("{},调用异步方法去进行审核内容:", Thread.currentThread().getName());
+
     }
 
 

+ 0 - 4
service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/FileUploadServiceImpl.java

@@ -2,7 +2,6 @@ package com.atguigu.tingshu.album.service.impl;
 
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.file.FileNameUtil;
 import cn.hutool.core.util.IdUtil;
 import com.atguigu.tingshu.album.config.MinioConstantProperties;
 import com.atguigu.tingshu.album.service.FileUploadService;
@@ -10,7 +9,6 @@ import com.atguigu.tingshu.album.service.VodService;
 import com.atguigu.tingshu.common.execption.GuiguException;
 import io.minio.MinioClient;
 import io.minio.PutObjectArgs;
-import io.minio.errors.*;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -19,8 +17,6 @@ import org.springframework.web.multipart.MultipartFile;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
 
 /**
  * @author: atguigu

+ 3 - 0
service/service-album/src/main/resources/application.yml

@@ -3,3 +3,6 @@ spring:
     type: redis # 缓存类型
 server:
   port: 8501
+#etcd的地址,如有多个用逗号分隔 通过ETCD动态获取work信息 根据worker建立netty连接
+etcd:
+  server: ${etcdServer:http://192.168.200.6:2379}

+ 3 - 3
service/service-cdc/src/main/java/com/atguigu/tingshu/listener/UserListener.java

@@ -1,6 +1,6 @@
 package com.atguigu.tingshu.listener;
 
-import com.atguigu.tingshu.model.CDCEntity;
+import com.atguigu.tingshu.model.user.UserInfo;
 import io.xzxj.canal.core.annotation.CanalListener;
 import io.xzxj.canal.core.listener.EntryListener;
 import lombok.extern.slf4j.Slf4j;
@@ -15,7 +15,7 @@ import java.util.Set;
  */
 @Slf4j
 @CanalListener(destination = "tingshuTopic", schemaName = "tingshu_user", tableName = "user_info")
-public class UserListener implements EntryListener<CDCEntity> {
+public class UserListener implements EntryListener<UserInfo> {
 
     @Autowired
     private RedisTemplate redisTemplate;
@@ -27,7 +27,7 @@ public class UserListener implements EntryListener<CDCEntity> {
      * @param fields
      */
     @Override
-    public void update(CDCEntity before, CDCEntity after, Set<String> fields) {
+    public void update(UserInfo before, UserInfo after, Set<String> fields) {
         log.info("[cdc]监听到变更数据");
         String redisKey = "user:userinfo:"+after.getId();
         redisTemplate.delete(redisKey);

+ 19 - 0
service/service-dispatch/src/main/java/com/atguigu/tingshu/dispatch/job/DispatchHandler.java

@@ -2,6 +2,7 @@ package com.atguigu.tingshu.dispatch.job;
 
 import com.atguigu.tingshu.search.client.SearchFeignClient;
 import com.atguigu.tingshu.user.client.UserFeignClient;
+import com.xxl.job.core.context.XxlJobHelper;
 import com.xxl.job.core.handler.annotation.XxlJob;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -36,4 +37,22 @@ public class DispatchHandler {
         userFeignClient.updateVipExpireStatus();
     }
 
+
+    /**
+     * 测试任务:任务逻辑中需要有些参数动态传入在调度中心管理页面动态设置
+     * 需求:银行贷款客户30W,在还款日21号前几天(17-20)给客户发邮件通知,邮件内容:或者发送短信通知。内容:客户还款日,请及时还款,如果客户还款了,则发送短信通知客户,短信内容:客户还款了,请及时查看账单,如果客户没有还款,则发送短信通知客户,短信内容:客户没有还款,请及时查看账单,如果客户没有还款,则发送短信通知客户,短信内容:客户没有还款,请及时查看账单,如果客户没有还款,则发送短信通知客户
+     * 场景:大量任务需要并发执行,如何提升任务的执行效率? 解决:采用分片广播,一次任务调度调度所有分片全部同时执行,提高任务的执行效率。
+     * 问题:如何避免任务重复执行 解决:使用分片参数开发
+     * 每隔5秒触发一次广播任务:某个节点执行任务查询条件:任务状态:未执行 and 用户ID % 分片总数 = 分片序号 limit 100;
+     */
+    @XxlJob("testJob")
+    public void testJob() {
+        String jobParam = XxlJobHelper.getJobParam();
+        long jobId = XxlJobHelper.getJobId();
+        log.info("测试任务,任务ID:{},任务参数:{}", jobId, jobParam);
+
+        int shardIndex = XxlJobHelper.getShardIndex();
+        int shardTotal = XxlJobHelper.getShardTotal();
+        log.info("测试任务,分片参数:当前分片序号={},总分片数={}", shardIndex, shardTotal);
+    }
 }

+ 3 - 2
service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderInfoServiceImpl.java

@@ -504,9 +504,10 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
     @Override
     public void cancelOrder(Long orderId) {
         //1.根据订单ID查询订单信息
-        OrderInfo orderInfo = baseMapper.selectById(orderId);
+        OrderInfo orderInfo = baseMapper.selectById(orderId);  //未支付
         //2.判断订单状态:如果未支付则更新订单状态为已取消
-        if (orderInfo != null && ORDER_STATUS_UNPAID.equals(orderInfo.getOrderStatus())) {
+        if (orderInfo != null && ORDER_STATUS_UNPAID.equals(orderInfo.getOrderStatus())) {  //最后一刻微信支付付款
+            //解决方案:不要先查再更新。采用乐观锁带着期望状态(未支付)更新 如果订单已支付,则更新失败。启动退款流程
             orderInfo.setOrderStatus(ORDER_STATUS_CANCEL);
             baseMapper.updateById(orderInfo);
             //TODO 远程调用支付服务,修改本地交易记录状态:已关闭