p2p金融项目+用redis缓存:年化收益率、平台注册总人数、平台累计投资金额、用户投资排行榜

问题来源:前端有三个值需要显示:年化收益率、平台注册总人数、平台累计投资金额、用户投资排行榜

如果每次都要去数据库库中查,很费时间

所以:放到redis缓存里面,提高用户体验,提高用户执行效率

年化收益率

要想操作redis,就要连redis,用什么?我这里用的是jedis,就好比是个驱动

第一步:maven依赖

        <!-- spring-data-redis依赖的JAR配置 -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>

        <!-- jedis依赖的JAR配置 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

第二步:applicationContext-redis.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd ">

    <!-- jedis Connection Factory -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:usePool="${redis.usePool}"
          p:hostName="${redis.hostName}"
          p:port="${redis.port}"
          p:timeout="${redis.timeout}"
          p:password="${redis.password}"/>


    <!-- redis template definition -->
    <bean id="redisTemplate"
          class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
</beans>

第三步:

先整体看一下代码:

LoanInfoServiceImpl.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Service("loanInfoServiceImpl")
public class LoanInfoServiceImpl implements LoanInfoService {

    @Autowired
    private LoanInfoMapper loanInfoMapper;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
    //这里注入进来了redis模板对象

    @Override
    public Double queryHistoryAverageRate() {
        //先去redis缓存中获取该,有:直接使用,没有:去数据库查询并存放到redis缓存中

        //修改key值的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        //获取操作key=value的数据类型的redis的操作对象,并获取指定key的value值
        Double historyAverageRate = (Double) redisTemplate.opsForValue().get(Constants.HISTORY_AVERAGE_RATE);

        //判断是否有值
        if (null == historyAverageRate) {
            //没有值:去数据库查询
            historyAverageRate = loanInfoMapper.selectHistoryAverageRate();

            //将该值存放到redis缓存中
            redisTemplate.opsForValue().set(Constants.HISTORY_AVERAGE_RATE,historyAverageRate,15, TimeUnit.SECONDS);
        }

        return historyAverageRate;
    }

第三步:引进jar包

import org.springframework.data.redis.core.RedisTemplate;

第四步:注入进来了redis模板对象

注入进来了redis模板对象:

@Autowired

private RedisTemplate<Object,Object> redisTemplate;

 

这里注入进来了redis模板对象,这个是在dataservice中的配置文件applicationContext-redis.xml中自动生成的bean,把该bean加载到spring容器中,

applicationContext-redis.xml

<!-- redis template definition -->

<bean id="redisTemplate"

class="org.springframework.data.redis.core.RedisTemplate">

<property name="connectionFactory" ref="jedisConnectionFactory"/>

</bean>

第五步

先去redis缓存中获取该值,如果有就直接使用,如果没有就去数据库中查询并存放到redis缓存中

@Override
public Double queryHistoryAverageRate() {
    //先去redis缓存中获取该,有:直接使用,没有:去数据库查询并存放到redis缓存中

    //修改key值的序列化方式
(问:为什么要修改key值的序列化方式?因为Redis默认序列化时会使用JDK序列化器,
使得Redis中的key和value值不可读。我们可以改造RedisTemplate,配置自定义序列化器取代默认)
    redisTemplate.setKeySerializer(new StringRedisSerializer());

    //获取操作key=value的数据类型的redis的操作对象,并获取指定key的value值
    Double historyAverageRate = (Double) redisTemplate.opsForValue().get(Constants.HISTORY_AVERAGE_RATE);

    //判断是否有值
    if (null == historyAverageRate) {
        //没有值:去数据库查询
        historyAverageRate = loanInfoMapper.selectHistoryAverageRate();

        //将该值存放到redis缓存中
        redisTemplate.opsForValue().set(Constants.HISTORY_AVERAGE_RATE,historyAverageRate,15, TimeUnit.SECONDS);
    }

    return historyAverageRate;
}

1、修改key值的序列化方式

(问:为什么要修改key值的序列化方式?因为Redis默认序列化时会使用JDK序列化器,
使得Redis中的key和value值不可读。我们可以改造RedisTemplate,配置自定义序列化器取代默认)  

 redisTemplate.setKeySerializer(new StringRedisSerializer());

2、获取操作key=value的数据类型的redis的操作对象,并获取指定key的value值

Double historyAverageRate = (Double) redisTemplate.opsForValue().get(Constants.HISTORY_AVERAGE_RATE);

分析:

get(Constants.HISTORY_AVERAGE_RATE)  get里面放的是key,这里的key是HISTORY_AVERAGE_RATE,由于这个key经常会用到,这里的“HISTORY_AVERAGE_RATE”是一个常量,所以放到了p2p-common里面了

opsForValue()  获取key-value对象

redisTemplate 返回的是个Object

3、判断是否有值

if (null == historyAverageRate)

4、如果没有值,就去数据库中查询:      

 historyAverageRate = loanInfoMapper.selectHistoryAverageRate();

其中:selectHistoryAverageRate()

<!--获取平台历史平均年化收益率-->
    <select id="selectHistoryAverageRate" resultType="java.lang.Double">
      select cast(avg(rate) as DECIMAL(10,2)) from b_loan_info
    </select>

5、从数据库中查到,将该值存放到redis缓存中

redisTemplate.opsForValue().set(Constants.HISTORY_AVERAGE_RATE,historyAverageRate,15, TimeUnit.SECONDS);

set(有三种)

  1. Object k ,  Object v:放一个key,value,不设置失效时间,俺么就在缓存中永久不失效
  2. Object k ,  Object v,long l , :这是一个毫秒数
  3. Object k ,  Object v,long l ,TimeUnit  timeUnit:时间单位

我这里呢,是15分钟,

其实这里可以改进,这个15分钟其实是根据具体应用场景来设置的,后台需要有个参数来管理这个参数

其他:

1、decimal

decimal(10,2)中的“2”表示小数部分的位数,如果插入的值未指定小数部分或者小数部分不足两位则会自动补到2位小数,若插入的值小数部分超过了2为则会发生截断,截取前2位小数。

“10”指的是整数部分加小数部分的总长度,也即插入的数字整数部分不能超过“10-2”位,否则不能成功插入,会报超出范围的错误。

 

DECIMAL从MySQL 5.1引入,列的声明语法是DECIMAL(M,D)。在MySQL 5.1中,参量的取值范围如下:

·M是数字的最大数(精度)。其范围为1~65(在较旧的MySQL版本中,允许的范围是1~254),M 的默认值是10。

·D是小数点右侧数字的数目(标度)。其范围是0~30,但不得超过M。

 

阿里的 Java 手册里写着:

6.  [强制] 小数类型为 decimal,禁止使用 float 和 double。
说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不
正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。

我这里呢,java中用的是duble,数据库中用的是decimal,显示可能不准确 但是数据库是准确的,

前提是 所有计算发生在数据库,而不是 java计算完再保存进数据库

比如 扣手续费,那么 一定要在sql中写 计算式,而不要 java算完了 把结果存入数据库

平台注册总人数

userServiceImpl

 第一步:注入redis模板对象

@Autowired
private RedisTemplate<Object,Object> redisTemplate;

第二步:

整个代码先看一下

@Service("userServiceImpl")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private FinanceAccountMapper financeAccountMapper;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Override
    public Long queryAllUserCount() {
        //首先去redis缓存中查询,有:直接用

        //修改redis中key值的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        //获取指定操作某一个key的操作对象
        BoundValueOperations<Object, Object> boundValueOps = redisTemplate.boundValueOps(Constants.ALL_USER_COUNT);

        //获取指定key的value值
        Long allUserCount = (Long) boundValueOps.get();

        //判断是否有值
        if (null == allUserCount) {

            //去数据库查询
            allUserCount = userMapper.selectAllUserCount();

            //将该值存放到redis缓存中
            boundValueOps.set(allUserCount,15, TimeUnit.SECONDS);

        }


        return allUserCount;
    }

1、修改redis中key值的序列化方式

redisTemplate.setKeySerializer(new StringRedisSerializer());

2、获取指定操作某一个key的操作对象

BoundValueOperations<Object, Object> boundValueOps = redisTemplate.boundValueOps(Constants.ALL_USER_COUNT);

分析:这里换了一种方法取,方法boundValueOps(Constants.ALL_USER_COUNT);绑定某个key的value对象,这里的key就是Constants.ALL_USER_COUNT。

返回的对象boundValueOps只能操作这个指定key的值

3、获取指定key的value值

Long allUserCount = (Long) boundValueOps.get();

这个get不需要加key了,因为已经绑定好了

4、之后,就和上面同样的方法了

剩余可投金额

bidInfoServiceImpl
@Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Override
    public Double queryAllBidMoney() {

        //获取指定key的操作对象
        BoundValueOperations<Object, Object> boundValueOps = redisTemplate.boundValueOps(Constants.ALL_BID_MONEY);

        //获取指定key的value值
        Double allBidMoney = (Double) boundValueOps.get();

        //判断是否有值
        if (null == allBidMoney) {
            //去数据库查询
            allBidMoney = bidInfoMapper.selectAllBidMoney();

            //存放到redis缓存中
            boundValueOps.set(allBidMoney,15, TimeUnit.MINUTES);

        }

        return allBidMoney;
    }

前面三个都是p2p-dataservice中的

用户投资排行榜

一、获取

p2p-web

LoanInfoController.java
//用户投资排行榜
List<BidUserTop> bidUserTopList = bidInfoService.queryBidUserTop();
model.addAttribute("bidUserTopList",bidUserTopList);

然后去方法bidInfoService.queryBidUserTop():

p2p-dataservice

bidInfoServiceImpl.java
@Override
    public List<BidUserTop> queryBidUserTop() {
        List<BidUserTop> bidUserTopList = new ArrayList<BidUserTop>();

        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(Constants.INVEST_TOP, 0, 9);

        Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTuples.iterator();

        while (iterator.hasNext()) {
            ZSetOperations.TypedTuple<Object> next = iterator.next();
            String phone = (String) next.getValue();
            Double score = next.getScore();
            BidUserTop bidUserTop = new BidUserTop();
            bidUserTop.setPhone(phone);
            bidUserTop.setScore(score);

            bidUserTopList.add(bidUserTop);
        }


        return bidUserTopList;
    }

过程:

1、获得set集合对象

Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(Constants.INVEST_TOP, 0, 9);

分析:opsForZSet():获得set集合对象

reverseRangeWithScores(Constants.INVEST_TOP, 0, 9):按照scores倒序来取(key,0,9);后面两个数是取的范围

例子:

//从高到低的排序集中获取分数在最小和最大值之间的元素。
Set<Object> reverseRangeByScore = opsForZSet.reverseRangeByScore("fan9", -1, 2);

2、通过set集合拿到迭代器

Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTuples.iterator();

3、遍历set集合,放到list中

 

二、存放

在超卖现象,投资的时候,最后投资成功了:就将用户的投资金额存放到redis缓存中

redisTemplate.opsForZSet().incrementScore(Constants.INVEST_TOP, phone,(Double) paramMap.get("bidMoney"));

分析:

ZSetOperations提供了一系列方法对有序集合进行操作

Double incrementScore(K key, V value, double delta);  通过增量增加排序集中的元素的分数

其他--zset

一、描述

redis其中一个数据结构为zset(sorted set-有序集合),其主要作用用于排行榜实现,你可以获取排名第几到第几的数据


二、数据结构

sorted set-有序集合在redis中有两种实现

1.ziplist,压缩双向链表,相关链接

2.skiplist,跳表实现
 

三、skiplist数据结构


score:分值,用于排序

backward:是第一层的前一个数据,即span=1

level[]:每一个层所代表的节点node

forward:该层级的下一个节点

span:到达该层级的下一个节点,实际跨越了多少个节点,也是方便用于zrange等排行榜查询的用处


四、注意点

4.1 插入数据

1.在插入数据的时候,通过level[]快速跳过不需要比较的节点,快速定位节点位置

2.在插入新的节点的时候,level[]的层级由随机决定该层级的高度

3.然后更新每一次受到影响的span,span代表该层节点到达下一节点应该要跨越的节点数量


4.2 查询排行榜,如zrange

1.先根据start,通过level[]和每一次的rank来快速定位第一层的起始位置

2.然后再根据end,来将结果输出给用户


五、参数控制

redis配置文件中用来控制zset到底是使用ziplist(压缩双向链表)还是skiplist(跳表)的参数:

zset-max-ziplist-entries 128

zset-max-ziplist-value 64
zset-max-ziplist-entries zset使用ziplist存储的时候,最大限制存储entries的个数

zset-max-ziplist-value zset使用ziplist存储的时候,每个节点最大存储字节数

违反上述两个限制条件,均会导致zset将ziplist的数据结构切换为skiplist数据结构

zset使用ziplist的原因,主要是出于在零散数据量少的时候,节省内容的占用
 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Sure!以下是一个示例代码,展示了如何在Java Spring Boot使用布隆过滤器和Redis来解决缓存穿透问题: 首先,你需要在pom.xml文件添加相应的依赖: ```xml <dependencies> <!-- Spring Boot Starter Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Guava Bloom Filter --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency> </dependencies> ``` 接下来,创建一个布隆过滤器的工具类 BloomFilterUtil.java: ```java import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class BloomFilterUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; private BloomFilter<String> bloomFilter; // 设置布隆过滤器的预计插入数据量和误判率 private static final int EXPECTED_INSERTIONS = 1000000; private static final double FPP = 0.001; @PostConstruct public void init() { // 创建布隆过滤器,并将其保存到Redis bloomFilter = BloomFilter.create(Funnels.stringFunnel(), EXPECTED_INSERTIONS, FPP); redisTemplate.opsForValue().set("bloomFilter", bloomFilter); } public boolean mightContain(String key) { // 从Redis获取布隆过滤器 bloomFilter = (BloomFilter<String>) redisTemplate.opsForValue().get("bloomFilter"); // 使用布隆过滤器判断key是否可能存在 return bloomFilter.mightContain(key); } public void put(String key) { // 从Redis获取布隆过滤器 bloomFilter = (BloomFilter<String>) redisTemplate.opsForValue().get("bloomFilter"); // 将key添加到布隆过滤器 bloomFilter.put(key); // 将更新后的布隆过滤器保存到Redis redisTemplate.opsForValue().set("bloomFilter", bloomFilter); } } ``` 然后,在你需要使用布隆过滤器解决缓存穿透的地方,注入 BloomFilterUtil,并使用它来判断数据是否存在于缓存: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class CacheController { @Autowired private BloomFilterUtil bloomFilterUtil; @GetMapping("/data/{key}") public String getData(@PathVariable String key) { // 先使用布隆过滤器判断key是否可能存在于缓存 if (bloomFilterUtil.mightContain(key)) { // 如果可能存在,再从缓存获取数据 String data = redisTemplate.opsForValue().get(key); if (data != null) { return data; } } // 如果数据不在缓存,进行其他操作(例如从数据库查询数据) // ... return null; } } ``` 这样,当有大量的请求同时访问某个缓存时,在经过布隆过滤器的判断后,可以避免无效的缓存查询请求,减轻了数据库的负载压力。 请注意,以上代码只是示例,实际使用时需要根据具体的业务需求进行适当的修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值