redis高级案列case

案列一 双写一致性

案例二 双锁策略

package com.redis.redis01.service;

import com.redis.redis01.bean.RedisBs;
import com.redis.redis01.mapper.RedisBsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.beans.Transient;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class RedisBsService {

    //定义key前缀/命名空间
    public static final String CACHE_KEY_USER = "user:";
    @Autowired
    private RedisBsMapper mapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private static ReentrantLock lock = new ReentrantLock();

    /**
     * 业务逻辑没有写错,对于中小长(qps<=1000)可以使用,但是大厂不行:大长需要采用双检加锁策略
     *
     * @param id
     * @return
     */
    @Transactional
    public RedisBs findUserById(Integer id,int type,int qps) {
        //qps<=1000
        if(qps<=1000){
            return qpsSmall1000(id);
        }
        //qps>1000
        return qpsBig1000(id, type);
    }

    /**
     * 加强补充,避免突然key失效了,或者不存在的key穿透redis打爆mysql,做一下预防,尽量不出现缓存击穿的情况,进行排队等候
     * @param id
     * @param type 0使用synchronized重锁,1ReentrantLock轻量锁
     * @return
     */
    private RedisBs qpsBig1000(Integer id, int type) {
        RedisBs redisBs = null;
        String key = CACHE_KEY_USER + id;
        //1先从redis里面查询,如果有直接返回,没有再去查mysql
        redisBs = (RedisBs) redisTemplate.opsForValue().get(key);
        if (null == redisBs) {
            switch (type) {
                case 0:
                    //加锁,假设请求量很大,缓存过期,大厂用,对于高qps的优化,进行加锁保证一个请求操作,让外面的redis等待一下,避免击穿mysql
                    synchronized (RedisBsService.class) {
                        //第二次查询缓存目的防止加锁之前刚好被其他线程缓存了
                        redisBs = (RedisBs) redisTemplate.opsForValue().get(key);
                        if (null != redisBs) {
                            //查询到数据直接返回
                            return redisBs;
                        } else {
                            //数据缓存
                            //查询mysql,回写到redis中
                            redisBs = mapper.findUserById(id);
                            if (null == redisBs) {
                                // 3 redis+mysql都没有数据,防止多次穿透(redis为防弹衣,mysql为人,穿透直接伤人,就是直接访问mysql),优化:记录这个null值的key,列入黑名单或者记录或者异常
                                return new RedisBs(-1, "当前值已经列入黑名单");
                            }
                            //4 mysql有,回写保证数据一致性
                            //setifabsent
                            redisTemplate.opsForValue().setIfAbsent(key, redisBs,7l, TimeUnit.DAYS);
                        }
                    }
                    break;
                case 1:
                    //加锁,大厂用,对于高qps的优化,进行加锁保证一个请求操作,让外面的redis等待一下,避免击穿mysql
                    lock.lock();
                    try {
                        //第二次查询缓存目的防止加锁之前刚好被其他线程缓存了
                        redisBs = (RedisBs) redisTemplate.opsForValue().get(key);
                        if (null != redisBs) {
                            //查询到数据直接返回
                            return redisBs;
                        } else {
                            //数据缓存
                            //查询mysql,回写到redis中
                            redisBs = mapper.findUserById(id);
                            if (null == redisBs) {
                                // 3 redis+mysql都没有数据,防止多次穿透(redis为防弹衣,mysql为人,穿透直接伤人,就是直接访问mysql),优化:记录这个null值的key,列入黑名单或者记录或者异常
                                return new RedisBs(-1, "当前值已经列入黑名单");
                            }
                            //4 mysql有,回写保证数据一致性
                            redisTemplate.opsForValue().set(key, redisBs);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //解锁
                        lock.unlock();
                    }
            }
        }
        return redisBs;
    }
    private RedisBs qpsSmall1000(Integer id) {
        RedisBs redisBs = null;
        String key = CACHE_KEY_USER + id;
        //1先从redis里面查询,如果有直接返回,没有再去查mysql
        redisBs = (RedisBs) redisTemplate.opsForValue().get(key);
        if (null == redisBs) {
            //2查询mysql,回写到redis中
            redisBs = mapper.findUserById(id);
            if (null == redisBs) {
                // 3 redis+mysql都没有数据,防止多次穿透(redis为防弹衣,mysql为人,穿透直接伤人,就是直接访问mysql),优化:记录这个null值的key,列入黑名单或者记录或者异常
                return new RedisBs(-1, "当前值已经列入黑名单");
            }
            //4 mysql有,回写保证数据一致性
            redisTemplate.opsForValue().set(key, redisBs);
        }
        return redisBs;
    }

}

案列三 mysql+redis实时同步

下载canal监控端admin和服务端deployer

https://github.com/alibaba/canal/releases/tag/canal-1.1.7

image.png

登录mysql授权canal连接mysql账户

DROP USER IF EXISTS 'canal'@'%';
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';  
GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal';  
FLUSH PRIVILEGES;

image.png

配置canal

修改mysql ip
image.png
启动

./startup.bat

image.png

Canal客户端(Java编写)

非springboot项目

<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>1.1.0</version>
</dependency>
server.port=8002
#连接数据源
spring.datasource.druid.username=root
spring.datasource.druid.password=xgm@2023..
spring.datasource.druid.url=jdbc:mysql://172.16.204.51:3306/redis?serverTimezone=GMT%2B8
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.initial-size=5

##指定缓存类型redis
#spring.cache.type=redis
##一个小时,以毫秒为单位
#spring.cache.redis.time-to-live=3600000
##给缓存的建都起一个前缀。  如果指定了前缀就用我们指定的,如果没有就默认使用缓存的名字作为前缀,一般不指定
#spring.cache.redis.key-prefix=CACHE_
##指定是否使用前缀
#spring.cache.redis.use-key-prefix=true
##是否缓存空值,防止缓存穿透
#spring.cache.redis.cache-null-values=true


#redis
spring.redis.host=172.16.204.51
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=1


# mybatis配置
mybatis:
check-config-location: true
#  mybatis框架配置文件,对mybatis的生命周期起作用
config-location: "classpath:mybatis/mybatis-config.xml"
#  配置xml路径
mapper-locations: "classpath:mybatis/mapper/*Mapper.xml"
#  配置model包路径
type-aliases-package: "com.redis.redis01.bean.*"

#日志
logging.level.root=info
#logging.level.io.lettuce.core=debug
#logging.level.org.springframework.data.redis=debug

#canal安装地址
canal.server=172.16.204.51:11111
canal.destination=example
#控制台刷新时间,每隔5秒检查一下数据库数据是否更新 根据需求设置其他时间
canal.timeout=5
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=canal
spring.datasource.password=canal
spring.datasource.url=jdbc:mysql://172.16.204.51:3306/redis?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&allowMultiQueries=true
package com.redis.redis01;

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONException;
import org.json.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Slf4j
public class CanalTest {
    public static final Integer _60SECONDS = 60;
    public static final String REDIS_IP_ADDR = "172.16.204.51";

    private  void redisInsert(List<Column> columns) throws JSONException {
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            jsonObject.put(column.getName(), column.getValue());
        }
        if (columns.size() > 0) {
            try (Jedis jedis = new RedisUtils().getJedis()){
                jedis.set(columns.get(0).getValue(), jsonObject.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public class RedisUtils {

        public  final String REDIS_IP_ADDR = "172.16.204.51";
        public  final String REDIS_pwd = "123456";

        public JedisPool jedisPool;

        public  Jedis getJedis() throws Exception {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(20);
            jedisPoolConfig.setMaxIdle(10);
            jedisPool=new JedisPool(jedisPoolConfig, REDIS_IP_ADDR,6379,10000,REDIS_pwd);
            if (null != jedisPool) {
                return jedisPool.getResource();
            }
            throw new Exception("Jedispool is not ok");
        }
    }

    private void redisDelete(List<Column> columns) throws JSONException {
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns) {
            jsonObject.put(column.getName(), column.getValue());
        }
        if (columns.size() > 0) {
            try (Jedis jedis = new RedisUtils().getJedis()) {
                jedis.del(columns.get(0).getValue());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void redisUpdate(List<Column> columns) throws JSONException {
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            jsonObject.put(column.getName(), column.getValue());
        }
        if (columns.size() > 0) {
            try (Jedis jedis =new RedisUtils().getJedis()){
                jedis.set(columns.get(0).getValue(), jsonObject.toString());
                System.out.println("---------update after: " + jedis.get(columns.get(0).getValue()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public  void printEntry(List<Entry> entrys) throws JSONException {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }

            RowChange rowChage = null;
            try {
                //获取变更的row数据
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error,data:" + entry.toString(), e);
            }
            //获取变动类型
            EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType));

            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.INSERT) {
                    redisInsert(rowData.getAfterColumnsList());
                } else if (eventType == EventType.DELETE) {
                    redisDelete(rowData.getBeforeColumnsList());
                } else {//EventType.UPDATE
                    redisUpdate(rowData.getAfterColumnsList());
                }
            }
        }
    }


    public static void main(String[] args) {
        System.out.println("---------O(∩_∩)O哈哈~ initCanal() main方法-----------");

        //=================================
        // 创建链接canal服务端
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(REDIS_IP_ADDR,
                11111), "example", "", "");  // 这里用户名和密码如果在这写了,会覆盖canal配置文件的账号密码,如果不填从配置文件中读
        int batchSize = 1000;
        //空闲空转计数器
        int emptyCount = 0;
        System.out.println("---------------------canal init OK,开始监听mysql变化------");
        try {
            connector.connect();
            //connector.subscribe(".*\\..*");
            connector.subscribe("redis.redis_syc");   // 设置监听哪个表
            connector.rollback();
            int totalEmptyCount = 10 * _60SECONDS;
            while (emptyCount < totalEmptyCount) {
                System.out.println("我是canal,每秒一次正在监听:" + UUID.randomUUID().toString());
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    emptyCount++;
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    //计数器重新置零
                    emptyCount = 0;
                    new CanalTest().printEntry(message.getEntries());
                }
                connector.ack(batchId); // 提交确认
                // connector.rollback(batchId); // 处理失败, 回滚数据
            }
            System.out.println("已经监听了" + totalEmptyCount + "秒,无任何消息,请重启重试......");
        } catch (JSONException e) {
            throw new RuntimeException(e);
        } finally {
            connector.disconnect();
        }
    }

}

截图
image.png

spingboot项目

        <dependency>
            <groupId>top.javatool</groupId>
            <artifactId>canal-spring-boot-starter</artifactId>
            <version>1.2.1-RELEASE</version>
        </dependency

# canal starter配置信息
canal.server=127.0.0.1:11111
canal.destination=example

logging.level.root=info
logging.level.top.javatool.canal.client.client.AbstractCanalClient=error

package com.redis.redis01.canal;


import com.redis.redis01.bean.RedisSyc;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.handler.EntryHandler;

@Component
@CanalTable(value = "redis_syc")
@Slf4j
public class RedisCanalClientExample implements EntryHandler<RedisSyc> {

    @Override
    public void insert(RedisSyc redisSyc) {
        EntryHandler.super.insert(redisSyc);
        log.info("新增 ---> {}",redisSyc);
    }

    @Override
    public void update(RedisSyc before, RedisSyc after) {
        EntryHandler.super.update(before, after);
        log.info("更新前 --->{} , 更新后 --->{} ", before, after);
    }

    @Override
    public void delete(RedisSyc redisSyc) {
        EntryHandler.super.delete(redisSyc);
        log.info("删除 --->{} " , redisSyc);

    }
}

image.png

私服监听

注意:canal依赖stater在中央仓库是不存在的,需要手动放进本地仓库或者你公司里面的nexus

	<!--canal依赖-->
      <dependency>
          <groupId>com.xpand</groupId>
          <artifactId>starter-canal</artifactId>
          <version>0.0.1-SNAPSHOT</version>
      </dependency>
@SpringBootApplication
@EnableCanalClient
public class CanalApplication {
  public static void main(String[] args) {
      SpringApplication.run(CanalApplication.class,args);
  }
}
@CanalEventListener
public class CanalDataEventListener {

  /***
   * 增加数据监听
   * @param eventType
   * @param rowData
   */
  @InsertListenPoint
  public void onEventInsert(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
      rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
  }

  /***
   * 修改数据监听
   * @param rowData
   */
  @UpdateListenPoint
  public void onEventUpdate(CanalEntry.RowData rowData) {
      System.out.println("UpdateListenPoint");
      rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
  }

  /***
   * 删除数据监听
   * @param eventType
   */
  @DeleteListenPoint
  public void onEventDelete(CanalEntry.EventType eventType) {
      System.out.println("DeleteListenPoint");
  }

  /***
   * 自定义数据修改监听
   * @param eventType
   * @param rowData
   */
  @ListenPoint(destination = "example", schema = "torlesse_test", table = {"tb_user", "tb_order"}, eventType = CanalEntry.EventType.UPDATE)
  public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {
      System.err.println("DeleteListenPoint");
      rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));
  }


  @ListenPoint(destination = "example",
          schema = "test_canal", //所要监听的数据库名
          table = {"tb_user"}, //所要监听的数据库表名
          eventType = {CanalEntry.EventType.UPDATE, CanalEntry.EventType.INSERT, CanalEntry.EventType.DELETE})
  public void onEventCustomUpdateForTbUser(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
      getChangeValue(eventType,rowData);
  }

  public static void getChangeValue(CanalEntry.EventType eventType, CanalEntry.RowData rowData){
      if(eventType == CanalEntry.EventType.DELETE){
          rowData.getBeforeColumnsList().forEach(column -> {
              //获取删除前的数据
              System.out.println(column.getName() + " == " + column.getValue());
          });
      }else {
          rowData.getBeforeColumnsList().forEach(column -> {
              //打印改变前的字段名和值
              System.out.println(column.getName() + " == " + column.getValue());
          });

          rowData.getAfterColumnsList().forEach(column -> {
              //打印改变后的字段名和值
              System.out.println(column.getName() + " == " + column.getValue());
          });
      }
  }
}

案列四 统计千亿级别PV

UV: Unique Visitor ,独立访客数,是指在一个统计周期内,访问网站的人数之和。一般理解客户ip,需要去重
PV : Page View,浏览量,是指在一个统计周期内,浏览页面的数之和。不需要去重
DAU: Daily Active User 日活跃用户数量;去重
DNU:Daily New User,日新增用户数
MAU:Monthly New User,月活跃用户;去重
需要使用redis hyperloglog基数统计数据结构来实现
基数统计:数据集中不重复的元素的个数

模拟后台1万用户点击首页

package com.redis.redis01.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class HyperLogService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 模拟后台1万用户点击首页,每个用户来自不同的ip地址
     */
    public void hyperloglogUvTest() {
        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        CountDownLatch countDownLatch=new CountDownLatch(10000);
        //主子线程传递共享连接资源redisTemplate
        ExecutorService executorService = Executors.newFixedThreadPool(200);
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //模拟1万用户
                for (int i = 0; i < 10000; i++) {
                    countDownLatch.countDown();
                    Random random = new Random();
                    String ipAddress = random.nextInt(256)
                            + "." + random.nextInt(256)
                            + "." + random.nextInt(256)
                            + "." + random.nextInt(256);
                   redisTemplate.opsForHyperLogLog().add("uv_click", ipAddress);
                    System.out.println("countDownLatch=" + countDownLatch.getCount());

                }
            }
        });
        try {
            countDownLatch.await();
            stopWatch.stop();
            Long uvClick1 = redisTemplate.opsForHyperLogLog().size("uv_click");
            //用户访问首页次数uv=10059
            System.out.println("用户访问首页次数uv=" + uvClick1);
            //共耗时=3:秒
            System.out.println("共耗时=" + stopWatch.getTotalTimeMillis()/1000+":秒");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }
}

image.png

案列五 布隆过滤器方案实现

利用bitmap实现,一个bitmap=2^32bit最大能存512M,一个用户一天签到用1个bit,一年365个bit就可以实现,1千万个用户一年只需要435MB还不到一个bitmap最大存储能力
优点

  • 高效地插入和查询,内存占用 bit 空间少

缺点

  • 不能删除元素
    • 因为删除元素会导致误判率增加,因为hash冲突同一个位置可能存的东西是多个共有的,你删除一个元素的同时可能也把其他的删除了
  • 存在误判,不能精准过滤
    • 有,可能有
    • 无,绝对无
package com.redis.redis01.service;

import com.google.common.collect.Lists;
import com.redis.redis01.bean.RedisBs;
import com.redis.redis01.mapper.RedisBsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class BitmapService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    private static ReentrantLock lock = new ReentrantLock();
    @Autowired
    private RedisBsMapper redisBsMapper;

    /**
     * 场景一:布隆过滤器解决缓存穿透问题(null/黑客攻击);利用redis+bitmap实现
     * 有可能有,没有一定没有
     *                                                    无-------------》mysql查询
     *                     有--------》redis查询----------》有-----------》返回
     * 请求-----》布隆过滤器-----------》
     *                      无-------终止
     *
     * @param type:0初始化,1常规查询
     */
    public void booleanFilterBitmap(int type, Integer id) {
        
        switch (type) {
            case 0://初始化数据
                for (int i = 0; i < 10; i++) {
                    RedisBs initBs = RedisBs.builder().id(i).name("赵三" + i).phone("1580080569" + i).build();
                    //1 插入数据库
                    redisBsMapper.insert(initBs);
                    //2 插入redis
                    redisTemplate.opsForValue().set("customer:info" + i, initBs);
                }
                //3 将用户id插入布隆过滤器中,作为白名单
                for (int i = 0; i < 10; i++) {
                    String booleanKey = "customer:booleanFilter:" + i;
                    //3.1 计算hashvalue
                    int abs = Math.abs(booleanKey.hashCode());
                    //3.2 通过abs和2的32次方取余,获得布隆过滤器/bitmap对应的下标坑位/index
                    long index = (long) (abs % Math.pow(2, 32));
                    log.info("坑位:{}", index);
                    //3.3 设置redis里面的bitmap对应类型的白名单
                    redisTemplate.opsForValue().setBit("whiteListCustomer", index, true);
                }
                break;
            case 1://常规查询
                //1 获取当前传过来的id对应的哈希值
                String inputBooleanKey = "customer:booleanFilter:" + id;
                int abs = Math.abs(inputBooleanKey.hashCode());
                long index = (long) (abs % Math.pow(2, 32));
                Boolean whiteListCustomer = redisTemplate.opsForValue().getBit("whiteListCustomer", index);
                //加入双检锁
                //加锁,大厂用,对于高qps的优化,进行加锁保证一个请求操作,让外面的redis等待一下,避免击穿mysql
                lock.lock();
                try {
                    if (null == whiteListCustomer) {
                        whiteListCustomer = redisTemplate.opsForValue().getBit("whiteListCustomer", index);
                        if (null != whiteListCustomer && whiteListCustomer) {//布隆过滤器中存在,则可能存在
                            //2 查找redis
                            Object queryCustomer = redisTemplate.opsForValue().get("customer:info" + id);
                            if (null != queryCustomer) {
                                log.info("返回客户信息:{}", queryCustomer);
                                break;
                            } else {
                                //3 redis没有查找mysql
                                RedisBs userById = redisBsMapper.findUserById(id);
                                if (null != userById) {
                                    log.info("返回客户信息:{}", queryCustomer);
                                    redisTemplate.opsForValue().set("customer:info" + id, userById);
                                    break;
                                } else {
                                    log.info("当前客户信息不存在:{}", id);
                                    break;
                                }
                            }
                        } else {//redis没有,去mysql中查询
                            //3 redis没有查找mysql
                            RedisBs userById = redisBsMapper.findUserById(id);
                            if (null != userById) {
                                log.info("返回客户信息:{}", userById);
                                redisTemplate.opsForValue().set("customer:info" + id, userById);
                                break;
                            } else {
                                log.info("当前客户信息不存在:{}", id);
                                break;
                            }
                        }

                    }
                } finally {
                    lock.unlock();
                }
                log.info("当前客户信息不存在:{}", id);

                break;
            default:
                break;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/153372.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

comfyui指北-1

https://colab.research.google.com/github/tieai/SDXL-ComfyUI-Colab/blob/main/SDXL_OneClick_ComfyUI.ipynb#scrollToSaAJk33ppFw1https://colab.research.google.com/github/tieai/SDXL-ComfyUI-Colab/blob/main/SDXL_OneClick_ComfyUI.ipynb#scrollToSaAJk33ppFw1 可以用上…

SpringCloud Alibaba组件入门全方面汇总(中):服务熔断降级-Sentinel

文章目录 Sentinel常见的容错思路Sentinel流量控制规则sentinel 自定义异常 sentinelresources 注解使用Feign整合Sentinel**面试题&#xff1a;结合Feign后&#xff0c;你在项目中的降级方法中会实现什么样的操作/功能&#xff1f;** Sentinel Sentinel是阿里巴巴开源的分布…

制作翻页电子书最简单的教程来也!

电子书是一种新时代的电子读物&#xff0c;由于比传统纸质书刊更加方便阅读&#xff0c;又非常便利储存&#xff0c;所以受到了很多人的喜爱。既然有这么多人喜爱阅读电子书&#xff0c;那你知道翻页电子书是怎么的吗&#xff1f; 其实翻页电子书制作并没有我们想象中的那么复…

rabbitMQ的direct模式的生产者与消费者使用案例

消费者C1的RoutingKey 规则按照info warn 两种RoutingKey匹配 绑定队列console package com.esint.rabbitmq.work03;import com.esint.rabbitmq.RabbitMQUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.DeliverCallback;/*** 消费者01的消息接受*/ p…

使用Ganache搭建本地测试网并实现远程连接

文章目录 前言1. 安装Ganache2. 安装cpolar3. 创建公网地址4. 公网访问连接5. 固定公网地址 前言 Ganache 是DApp的测试网络&#xff0c;提供图形化界面&#xff0c;log日志等&#xff1b;智能合约部署时需要连接测试网络。 Ganache 是一个运行在本地测试的网络,通过结合cpol…

2023年香港优才计划申请确实火爆,但请冷静!结合个人条件再考虑!

2023年香港优才计划申请确实火爆&#xff0c;但请冷静&#xff01;结合个人条件再考虑&#xff01; 自从香港优才计划实施取消年度限额&#xff0c;为期两年的优化措施以后&#xff0c;无论是申请数量还是获批数量都猛增。今年第二季度的获批量与第一季度的2,073宗相比&#xf…

win11无法打开文件资源管理器

不知道为啥&#xff0c;升级之后文件夹就打不开了&#xff0c;研究了n个方案 注意&#xff1a;先备份一下注册表&#xff0c;祸祸完失败之后恢复回去 最终方案 很悲剧&#xff0c;下面的我都失败了&#xff0c;不过其他人有成功的&#xff0c;所以&#xff0c;我放弃官方的文件…

软件自动化测试代码覆盖率

在<professional software testing with visual studio 2005 team system tools for software developer>中提到了代码覆盖率&#xff0c;我很久没有去书店了&#xff0c;不知道是不是出了新的版本&#xff0c;觉得书里面关于代码覆盖率方面的知识有些地方没有讲&#xf…

CMA认证和CNAS认可的联系和区别?哪个更权威?

一、CMA认证是什么?   CMA认证是指中国计量认证&#xff0c;省级以上的计量行政部门根据中国计量法的规定&#xff0c;对申请CMA测试资质的第三方检测机构进行评估&#xff0c;对检测水平和检测可靠性达到国家标准的实验室授予计量认证合格证书(CMA资质)。 二、CNAS认可是什…

Latex常用特殊字符汇总

本文汇总了博主在使用Latex写文档过程中遇到的所有常用疑难字符、表达式等等及对应的Latex形式 持续更新... 目录 常用字符波浪号1. 文本模式&#xff1a;~2. 数学模式&#xff1a; ∼ \sim ∼3. 字母上方的波浪号&#xff1a; a ˜ \~a a˜ 字母上方角标 (数学模式强调符)箭头…

HackTheBox-Starting Point--Tier 2---Oospsie

文章目录 一 Oospsie测试过程1.1 打点1.2 漏洞探测&权限获取1.3 横向移动1.4 权限提升&#xff08;SUID提权&#xff09; 二 题目 一 Oospsie测试过程 1.1 打点 1.端口扫描 nmap -sV -sC 10.129.130.104开放22和80端口&#xff0c;访问80端口&#xff0c;并且发现网站有关…

用人话讲解深度学习中CUDA,cudatookit,cudnn和pytorch的关系

参考链接 本人学习使用&#xff0c;侵权删谢谢。用人话讲解深度学习中CUDA&#xff0c;cudatookit&#xff0c;cudnn和pytorch的关系 CUDA CUDA是显卡厂商NVIDIA推出的运算平台。 CUDA™是一种由NVIDIA推出的通用并行计算架构&#xff0c;是一种并行计算平台和编程模型&…

ClickHouse联合创始人、前Google副总裁Yury到访杭州玖章算术公司,双方建立生态合作

10月31日&#xff0c;ClickHouse联合创始人Yury到访未来科技城&#xff0c;与玖章算术创始人叶正盛和国际总经理Ni Demai展开沟通与推进合作。 图片备注&#xff1a;Ni Demai(左),Yury(中),叶正盛(右) ClickHouse是深受开发者青睐的实时分析型数据库&#xff0c;成立2年就发展成…

rabbitMQ的Topic模式的生产者与消费者使用案例

topic模式 RoutingKey 按照英文单词点号多拼接规则填充。其中消费者匹配规则时候 * 代表一个单词&#xff0c;#表示多个单词 消费者C1的RoutingKey 规则按照*.orange.* 匹配 绑定队列Q1 package com.esint.rabbitmq.work05;import com.esint.rabbitmq.RabbitMQUtils; import …

业务代码到底需不需要用多线程???

1.dubbo场景下的多线程 先来讲讲dubbo场景&#xff0c;整个调用链路非常的清晰&#xff1a; 来&#xff0c;请你告诉我这里面有线程池吗&#xff1f; 没有&#xff01; 是的&#xff0c;在日常的开发中&#xff0c;我就是写个接口给别人调用嘛&#xff0c;在我的接口里面并没有…

RedCap推动5G规模应用,紫光展锐赋能产业高质量发展

5G R17 RedCap作为面向中高速物联网场景的关键技术和解决方案&#xff0c;可以大幅降低终端的复杂度、成本和功耗。在当前国内5G应用规模化发展关键时期&#xff0c;5G R17 RedCap拥有广大的市场潜力与广泛的应用场景&#xff0c;将有助于推动5G规模应用、构建融通发展的5G生态…

深眸科技革新升级OCR技术,与AI视觉实现有效融合赋能各行业应用

OCR即光学字符识别&#xff0c;是通过扫描仪或工业相机等电子设备检查打印的字符&#xff0c;并通过检测暗、亮的模式确定其形状&#xff0c;然后用字符识别方法将形状翻译成计算机文字的过程。 目前&#xff0c;随着机器视觉和人工智能技术的进一步升级&#xff0c;OCR技术实…

【Java实现图书管理系统】

图书管理系统 1. 设计背景2. 设计思路3. 模块展示代码演示3.1 Book类3.2 BookList类&#xff08;书架类&#xff09;3.4 用户类 - User类3.5 子类管理员类 -- AdminUser类3.6 子类普通用户类 -- NormalUser类3.7 操作接口3.8 操作类3.8.1 查找操作 -- FindOperation类3.8.2 增加…

浅谈青岛啤酒厂事件—论智能视频监控的重要性和必要性

近日&#xff0c;“青岛啤酒三厂有工人在原料仓小便”的视频曝光&#xff0c;引发舆论关注。虽然此次事件原委已经明了&#xff0c;但此次事件也给我们敲了一个警钟。啤酒厂生产的是入口的食品原料&#xff0c;就因一个工作口角就导致有人罔顾大众食品安全&#xff0c;作出严重…

2022年09月 Scratch(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题(共25题,每题2分,共50分) 第1题 点击绿旗,下面哪个选项可以实际播放放马叫声并在声音全部播放完后,马向右移动? A: B: C: D: 答案:D