Redis的实战篇

Redis的实战篇

1.短信的登入注册

可以使用RandomUtil.randomNumbers(6)生成6位随机数

1.1 使用session来存储验证码

题外话:session与Cookie的关系:

  • 当用户登录的时候,服务器在Session中新增一个新记录,并把SessionID返回给客户端(通过HTTP响应的Set-Cookie字段返回)。
  • 客户端后续再给服务器发送请求的时候,需要在请求中带上SessionID(通过HTTP请求中的Cookie字段带上)
  • 服务器收到请求之后,根据SessionID在Session信息中获取到对应的用户信息,再进行后续操作,找不到则重新创建Session,并把SessionID返回。
1.1.1 验证用户的登入状态

使用拦截器(为什么要使用拦截器?)
1.保证用户的登入状态
2.在集群模式下,可以获得用户的信息,存储在ThreadLocal下
拦截器的使用 类 implements HandlerInterceptor(三个方法), 然后需要在Configuraion中配置addInterceptor

1.1.2 需要注意用户的隐藏信息

返回的数据要有限的,可以使用BeanUtils.copyProperties()来复制对象,然后再返回

1.2.3问题

每个tomcat中都有一份属于自己的session,假设用户第一次访问第一台tomcat,并且把自己的信息存放到第一台服务器的session中,但是第二次这个用户访问到了第二台tomcat,那么在第二台服务器上,肯定没有第一台服务器存放的session,所以此时 整个登录拦截功能就会出现问题。

1.2 使用redis来存储session

使用UUID.randomUUID().toString()来生成一个唯一的token
在去返回给前端,前端在请求的时候,需要在请求头中带上这个token
在经过拦截器的时候,我们可以通过这个token来获取到用户的信息

1.3 优化

有一些不用登入的接口,我们可以不用拦截器,直接放行
可以设置2个拦截器。(解决刷新登录token令牌的问题)

  • 第一个拦截器,用来拦截token,把用户的信息存放到ThreadLocal中
  • 第二个拦截器,用来判断用户是否登入,如果没有登入,就返回一个错误信息

2.商户查询缓存

缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码
浏览器缓存:主要是存在于浏览器端的缓存

**应用层缓存:**可以分为tomcat本地缓存,比如之前提到的map,或者是使用redis作为缓存

**数据库缓存:**在数据库中有一片空间是 buffer pool,增改查数据都会先加载到mysql的缓存中

2.1使用redis来缓存商户数据

较为常规,使用redis的String类型来存储数据

2.2 缓存更新策略

  • 内存淘汰:redis自动进行,当redis内存达到咱们设定的max-memery的时候,会自动触发淘汰机制,淘汰掉一些不重要的数据(可以自己设置策略方式)
  • 超时剔除: 设置一个过期时间,当过期时间到了,就会自动删除
  • 主动更新:当数据发生变化的时候,我们可以主动去更新缓存
  • 问题:数据库与redis出现数据不一致的情况
    • 1.是写少读多的情况,可以使用以把缓存删除,等待再次查询时,将缓存中的数据加载出来
    • 2.删除删除缓存还是更新缓存
    • 3.何保证缓存与数据库的操作的同时成功或失败
  • 需要先操作数据库,再删除缓存,防止高并发时候,出现了脏数据

2.3 缓存穿透

防止黑客通过一些特殊的字符,来绕过缓存,直接访问数据库

  • 1.缓存空对象
  • 就是当数据库中没有这个数据的时候,我们也把这个空对象存放到缓存中,访问时候,先去缓存中查找,如果没有,再去数据库中查找
  • 2.布隆过滤器(可能会出现误判)
    解决方案:
    • 缓存null值
    • 布隆过滤
    • 增强id的复杂度,避免被猜测id规律
    • 做好数据的基础格式校验
    • 加强用户权限校验
    • 做好热点参数的限流

2.4缓存雪崩问题及解决思路

解决方案:

  • 给不同的Key的TTL添加随机值(Random)
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

2.5 缓存击穿问题(热点Key问题)

就是在高并发的情况下,一个热点key过期了,导致大量的请求直接访问数据库,导致数据库压力过大
解决方案:

  • 1.使用互斥锁
  • 2.使用逻辑过期
2.5.1 使用互斥锁
  • 1.在获取缓存的时候,先去获取锁,如果获取不到锁,就等待一段时间,再去获取锁,使用了串行的方式

  • 2.获得锁的去查询数据库,然后再去更新缓存
    问题:1.性能不行 2.可能有死锁的情况
    如何实现:1.s使用redis的setIfabsent 来去设置锁

    private boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, “1”, 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
    }
    private void unlock(String key) {
    stringRedisTemplate.delete(key);
    }

2.5.2 使用逻辑过期

什么是逻辑过期:
就是在存储redis的时候,不去设置过期时间,而是在对象的元素设置一个过期时间
在使用redis的时候,先去判断这个过期时间,如果过期了,开启一个线程去更新缓存,
在这个线程去更新缓存的时候,其他的线程去访问缓存,发现缓存过期了,就去返回的是脏数据。
代码实现:

   //使用线程的模式import java.util.concurrent.ExecutorService;private static final ExecutorService executorService = Executors.newFixedThreadPool(10);
//核心代码
boolean isLock = tryLock(lockKey);
// 6.2.判断是否获取锁成功if (isLock){CACHE_REBUILD_EXECUTOR.submit( ()->{try{//重建缓存this.saveShop2Redis(id,20L);}catch (Exception e){throw new RuntimeException(e);}finally {
unlock(lockKey);}});}
2.5.3 使用泛型来去使用工具类
根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
    public <R ,ID> R ShopThrough(String keyprefix,Class<R>Type,ID id,Long time,TimeUnit timeUnit,Function<ID,R>dpFallBack){String key = keyprefix+id;String s = stringRedisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(s)){return BeanUtil.toBean(s,Type);}R r = dpFallBack.apply(id);if(r==null){stringRedisTemplate.opsForValue().set(key,"",time,timeUnit);return null;}this.set(key,r,time,timeUnit);return r;}
根据指定的key查询缓存,并反序列化为指定类型,利用逻辑过期时间解决缓存击穿问题
    public <R, ID> R ShopLogicDelete(String keyprefix, Class<R> Type, ID id, Long time, TimeUnit timeUnit, Function<ID, R> dpFollBack) {String key = keyprefix + id;String s = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isBlank(s)) {return null;}RedisData redisData = BeanUtil.copyProperties(s, RedisData.class);JSONObject data = (JSONObject) redisData.getData();R r = BeanUtil.toBean(data, Type);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {return r;}boolean lock = onLock(key);if (lock) {CACHE_REBUILD_EXECUTOR.submit(() -> {try {R r1 = dpFollBack.apply(id);this.setWithLogicalExpire(key, r1, time, timeUnit);} catch (Exception e) {throw new RuntimeException(e);} finally {offLock(key);}});}return r;}

3.优惠卷秒杀

3.1 全局唯一ID

为什么使用全局ID:

  • 1.id具有太明显的规则,用户或者说商业对手很容易猜测出来我们的一些敏感信息
  • 2.mysql的单表的容量不宜超过500W,数据量过大之后,我们要进行拆库拆表,但拆分表了之后,他们从逻辑上讲他们是同一张表,所以他们的id是不能一样的, 于是乎我们需要保证id的唯一性
    如何生成Id:时间戳+序列号,是一个64位的数字,前32位是时间戳,后32位是序列号
@Component
public class RedisIdWorker {/*** 开始时间戳*/private static final long BEGIN_TIMESTAMP = 1640995200L;/*** 序列号的位数*/private static final int COUNT_BITS = 32;private StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public long nextId(String keyPrefix) {// 1.生成时间戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timestamp = nowSecond - BEGIN_TIMESTAMP;// 2.生成序列号// 2.1.获取当前日期,精确到天String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 2.2.自增长long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接并返回return timestamp << COUNT_BITS | count;}
}

3.2 实现秒杀下单

3.2.1 超卖问题

注意的是:由于基础的秒杀的代码不同,如使用MyBatisPlus和MyBatis的代码不同,结果会有不同的结果
如:超卖或少卖问题

超卖问题出现的原因:高并发的问题与原子性问题

3.2.2 解决方案
  • 1.使用悲观锁: 悲观锁可以实现对于数据的串行化执行,比如syn,和lock都是悲观锁的代表,同时,悲观锁中又可以再细分为公平锁,非公平锁,可重入锁,等等
  • 2.使用乐观锁: 乐观锁是一种乐观的思想,他认为数据的读取是不会出现问题的,只有在更新的时候才会出现问题,所以在更新的时候,他会去判断数据是否被修改,如果没有被修改,就会去更新,如果被修改了,就会去重新读取数据,再去更新(cas)
3.2.3 悲观锁

就是加入一个锁,这里就不说了

3.2.4 乐观锁
boolean success = seckillVoucherService.update().setSql("stock= stock -1").eq("voucher_id", voucherId).update().gt("stock",0); 

就是在更新的时候,去判断库存是否大于0,如果大于0,就去更新,如果小于0,就不去更新

3.3 优惠券秒杀-一人一单

还是因为高并发的问题,导致了一个用户可以多次下单,乐观锁比较适合更新数据,而现在是插入数据,所以我们需要使用悲观锁操作

我们使用悲观锁时要考虑控制锁粒度:

  • 使用了用户的Id来作为锁的粒度,这样就可以保证一个用户只能下一单
  • 但是需要注意的是 Id.toString每一次都是不一样的,所以我们需要使用Id.toString.intern()来去保证是同一个对象

还是有问题的:问题的原因在于当前方法被spring的事务控制,如果你在方法内部加锁,可能会导致当前方法事务还没有提交,但是锁已经释放也会导致问题,所以我们选择将当前方法整体包裹起来,确保事务不会出现问题:

但是要用事务生效,需要去代理来生效IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();

  • 在在SpringBoot的启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)来暴露代理对象

3.4 集群环境下的并发问题

在多个tomcat中,使用了集群,每一个集群都会有一个自己jvm,使用syn锁是不行的,因为每一个jvm都会有自己的锁,所以我们需要使用redis来去实现锁

3.4.1 使用分布式锁
  • 获取锁:

    • 互斥:确保只能有一个线程获取锁
    • 非阻塞:尝试一次,成功返回true,失败返回false
    • 使用setnx命令去保证原子性
  • 释放锁:

    • 手动释放
    • 超时释放:获取锁时添加一个超时时间
3.4.2 分布式锁的问题

持有锁的线程在锁的内部出现了阻塞,导致他的锁自动释放,这时其他线程,线程2来尝试获得锁,就拿到了这把锁,然后线程2在持有锁执行过程中,线程1反应过来,继续执行,而线程1执行过程中,走到了删除锁逻辑,此时就会把本应该属于线程2的锁进行删除,这就是误删别人锁的情况说明
解决方案:要去判断锁是否是自己的

  • 但是要去保证原子性的,使用lua的脚本来去实现
    -- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示-- 获取锁中的标示,判断是否与当前线程标示一致if (redis.call('GET', KEYS[1]) == ARGV[1]) then-- 一致,则删除锁return redis.call('DEL', KEYS[1])end-- 不一致,则直接返回return 0 

使用

 StringRedisTemple stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId());`

去执行lua脚本

3.5 Redission去实现分布式锁

3.5.1 使用redis的问题:
  • 1.重入问题
  • 2.不可重试
  • 3.超时释放(就是在获取的时候,时间过长,就会自动释放,就是同时有2个线程获取锁。)
  • 4.主从一致性

Redission 提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现

3.5.2 使用redission的准备

依赖:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version>
</dependency>

配置文件:

@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();config.useSingleServer().setAddress("redis://*:6379").setPassword("123321");// 创建RedissonClient对象return Redisson.create(config);}
}

idea中使用redission:

Lock lock = redisClient.getLock("lock");
boolean success = lock.getLock();

3.5.3 在redisson的可重入锁与看门狗机制

这一部分需要去看源码,这里就不说了

  • 看门狗机制就是在获取锁的时候,设置一个超时时间,如果业务未完成,就会去延长超时时间

  • 可重入锁就是在获取锁的时候,可以多次获取锁,但是要去释放多次,用一个计数器来去记录

3.5.4 tryLock的一些用法
  • 1.尝试获取锁,如果获取不到,就返回false
RLock lock = redissonClient.getLock("myLock");
boolean isLocked = lock.tryLock();
  • 2.尝试获取锁,如果获取不到,就等待一段时间
RLock lock = redissonClient.getLock("myLock");
boolean isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
//TODO:100是等待时间,10是超时时间(设置看门狗只有waittime,不要超时时间)

3.6 MutiLock锁

此时我们去写命令,写在主机上, 主机会将数据同步给从机,但是假设 在主机还没有来得及把数据写入到从机去的时候,此时主机宕机,哨兵会发现主机宕机,并且选举一个slave变成master,而此时新的master中实际上并没有锁信息,此时锁信息就已经丢掉了。

为了解决这个问题,redission提出来了MutiLock锁,使用这把锁咱们就不使用主从了,每个节点的地位都是一样的, 这把锁加锁的逻辑需要写入到每一个主丛节点上,只有所有的服务器都写入成功,此时才是加锁成功,假设现在某个节点挂了,那么他去获得锁的时候,只要有一个节点拿不到,都不能算是加锁成功,就保证了加锁的可靠性。

3.7 秒杀优化

串行的方式会导致我们的程序执行的很慢,可以使用异步的方式来去执行

  • 1.去判断是否有库存
  • 2.去redis使用lua脚本去减少库存,完成秒杀资格判断
  • 3.异步去生成订单
1、初步使用阻塞队列
   import jakarta.annotation.PostConstruct;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;private BlockingQueue<VoucherOrder>orderTasks = new LinkedBlockingQueue<>(1024*1024);
private static final ExecutorService executorService = Executors.newFixedThreadPool(10);@PostConstruct
public void init() {executorService.submit(new VoucherOrderHandle());private class VoucherOrderHandle implements Runnable {@Overridepublic void run() {while (true) {try {// 1.获取队列中的订单信息VoucherOrder voucherOrder = orderTasks.take();// 2.创建订单handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("处理订单异常", e);}}}}
}

存在的问题:

  • 内存限制问题
  • 数据安全问题

3.8 使用消息队列

3.8.1 基于List结构模拟消息队列

优点:

  • 利用Redis存储,不受限于JVM内存上限
  • 基于Redis的持久化机制,数据安全性有保证
  • 可以满足消息有序性

缺点:

  • 无法避免消息丢失
  • 只支持单消费者
3.8.2 基于Stream的消息队列

1.基于Stream的消息队列

  • xadd key * filed value *是自动生成的id
  • xrange [count] [blocktime] STREAMS key ID [0是从头开始读取,$是最新的信息] count是读取的数量 blocktime是阻塞时间

STREAM类型消息队列的XREAD命令特点:

  • 消息可回溯
  • 一个消息可以被多个消费者读取
  • 可以阻塞读取
  • 有消息漏读的风险

2.基于Stream的消息队列-消费者
创建消费者组:

  • XGROUP CREATE key groupname 0 [MKSTREAM] (0是从头开始读取)($是队列的最后的一个信息)
    groupName是消费者组的名字 [MKSTREAM]是创建一个新的stream

给指定的消费者组添加消费者

  • XGROUP CREATECONSUMER key groupname consumername

从消费者组读取消息

  • XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key …] ID [ID …]
    ID是 > 表示从最新的开始读取,0表示从头开始读取

从消费者组确认消息:

  • XACK key group ID [ID ...] key是消费人 , group 是消费组 , Id为ID

  • 查询未确认的消息: XPENDING key group [[IDLE min-idle-time] start end count [consumer]]

  • 实验的过程:

  • 1.创建一个线程池来,去执行

  • 2.在主线程中判断是否有库存,如果有库存,就去发送消息,执行lua脚本,去减少库存

  • 3.副线程去消费消息,去生成订单(注意副线程不要使用LocalThread因为是异步的)

4.达人探店

4.1 点赞的实现

使用Zset的数据结构,因为Zset是有序的,可以根据时间来排序,分数就是时间的大小

4.2 好友关注
  • 关注的好友
  • 共同的关注
4.3 关注的人的动态(类型微信的朋友圈),使用feed的流

Feed流的设计:

  • 1.拉模式,:比较节约空间,但是延迟大
  • 2.推模式:比较节约时间,但是空间大
  • 3.混合模式:拉模式+推模式
    使用了推模式,与Feed流的滚动分页,如果不使用滚动分页,就会导致数据量的重复显现
    要注意的是顺序的问题,MyBatiesPlus的分页是根据id来排序的,所以我们要去根据时间来排序,所以要
    加入last(“order by field(id,” + strIds + “)”)来去排序
    需要去注意的是:1.时间的最小值 2.offset 3.分页的大小
    @Overridepublic Result queryBlogOfFollow(long max, Integer offset) {//1。先查询关注的人blogUser user = UserHolder.getUser();if(user == null){return Result.fail("请先登录");}String key = FEED_KEY + user.getId();//3.查询redisSet<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 2);//2.null的判断if(typedTuples == null||typedTuples.isEmpty()){return Result.ok();}// 有Blog的id,和scoreList<Long> blogIds = new ArrayList<>(typedTuples.size());  //防止扩容long minTime = 0 ; //最小时间int oc = 1; //偏移量//3.minTime , offsetfor (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {blogIds.add(Long.valueOf(typedTuple.getValue()));  //获取博文idLong time = typedTuple.getScore().longValue();if(time == minTime) {oc++;}else {minTime = time;oc=1;}}String strIds = StrUtil.join(",", blogIds);//4.查询数据库的blog的数据List<Blog> blogList = query().in("id", blogIds).last("order by field(id," + strIds + ")").list();//5.判断是否为空if(blogList == null||blogList.isEmpty()){return Result.ok();}//5.要带上用户信息for (Blog blog : blogList) {isBlogLike(blog);}ScrollResult scrollResult =new ScrollResult();scrollResult.setList(blogList);scrollResult.setOffset(oc);scrollResult.setMinTime(minTime);return Result.ok(scrollResult);}

5.使用GEO去处理店铺的位置信息

要注意的是:StringRedisData 最好使用3.2的版本

    @Overridepublic Result queryShopType(Integer typeId, Integer current, Double x, Double y) {if(x==null || y==null){// 根据类型分页查询Page<Shop> page = query().eq("type_id", typeId).page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));// 返回数据return Result.ok(page.getRecords());}int start=  (current-1)*SystemConstants.DEFAULT_PAGE_SIZE;int end =  current * SystemConstants.DEFAULT_PAGE_SIZE;// 2.根据redis的geo查询String key = SHOP_GEO_KEY + typeId;GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key,GeoReference.fromCoordinate(x, y),new Distance(5000),RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end));//查询出来了shop的Id与distanceif(results == null){return Result.ok(Collections.emptyList());}//3.获得idList<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();if (list.size() <= start) {// 没有下一页了,结束return Result.ok(Collections.emptyList());}List<Long>ids= new ArrayList<>();//存放idMap<String,Distance>distanceMap=new HashMap<>();//跳过start个,搜集List<id>与Map<id,distance>list.stream().skip(start).forEach(result->{ids.add(Long.valueOf(result.getContent().getName()));distanceMap.put(result.getContent().getName(),result.getDistance());});String StrId = StrUtil.join(",", ids);//4.需要根据id查询商铺信息(需要去数据库查询,把distance放到shop对象中)List<Shop> shopList = query().in("id", ids).last("Order by field(id," + StrId + ")").list();for (Shop shop : shopList) {shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());}//5.返回数据return Result.ok(shopList);}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第十六天|513. 找树左下角的值 112. 路径总和 106. 从中序与后序遍历序列构造二叉树

513. 找树左下角的值 题目 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 题目解析---迭代法 层序遍历&#xff0c;然后只需要记录最后一行第一个节点的数值就可…

xtu oj 原根

文章目录 回顾杂思路c 语言代码 回顾 AB III问题 H: 三角数问题 G: 3个数等式 数组下标查询&#xff0c;降低时间复杂度1405 问题 E: 世界杯xtu 数码串xtu oj 神经网络xtu oj 1167 逆序数&#xff08;大数据&#xff09; 杂 有一些题可能是往年的程设的题&#xff0c;现在搬到…

001 Hadoop安装、Spring整合测试

Hadoop安装、整合测试 文章目录 Hadoop安装、整合测试1.简介1.优点2.组成 2.安装1.安装jdk&#xff08;如已安装可跳过&#xff09;2.安装hadoop1.安装2. 修改配置文件core-site.xml3. 修改配置文件hdfs-site.xml4.启动hadoop5.启动yarn6.执行jps查看7.相关端口及配置位置8.访问…

Zilliz获Forrester报告全球第一;OB支持向量能力;Azure发布DiskANN;阿里云PG发布内置分析引擎

重要更新 1. Azure发布PostgreSQL向量索引扩展DiskANN&#xff0c;声称在构建HNSW/IVFFlat索引上&#xff0c;速度、精准度都超越pg_vector&#xff0c;并解决了pg_vector长期存在的偶发性返回错误结果的问题( [1] )。 2. 阿里云RDS PostgreSQL 发布AP加速引擎&#xff08;rds…

修改MYSQL库的默认字符集和校验规则

修改mysql的默认配置文件my.cnf。 vim /etc/my.cnf 如果没有这个文件就可能在这个路径&#xff1a;/etc/mysql/my.cnf 在 [mysqld] 部分下&#xff0c;添加或修改以下设置&#xff1a; character-set-server [要修改的字符集] collation-server [要修改的校验规则] 保存文…

Git客户端使用之命令行

一、git客户端命令行的使用 1、创建本地用户并绑定ssh-key到gitlab #在本地注册用户,这个用户随便创建它就是与git仓库连接的一个用户&#xff0c;不过最好喝git仓库用户一样有利于区分。 git config --global user.name "wenqiang1" git config --global user.ema…

SpringBoot+Vue+Uniapp智能社区服务小程序系统(源码+lw+部署文档+讲解等)

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

web 0基础第一节 文本标签

学习web语言 首先推荐安装一个vs code 的软件 这个普及度更广一点 兼容性好 网上有很多下载的教程 这里就直接从 html5 的内容开始说了. 这是一个html文件的基本结构 在vs code 中使用英文的 ! 可快捷设置这样的结构 <!-- --> 是在html写注释的结构 以后的…

【Java数据结构】优先级队列(堆)

【本节目标】 1. 掌握堆的概念及实现 2. 掌握 PriorityQueue 的使用 一. 优先级队列 1 概念 前面学过队列&#xff0c;队列是一种先进先出 (FIFO) 的数据结构 &#xff0c;但有些情况下&#xff0c; 操作的数据可能带有优先级&#xff0c;一般出队 列时&#xff0c;可…

【前端】如何制作一个自己的网页(8)

以下内容接上文。 CSS的出现&#xff0c;使得网页的样式与内容分离开来。 HTML负责网页中有哪些内容&#xff0c;CSS负责以哪种样式来展现这些内容。因此&#xff0c;CSS必须和HTML协同工作&#xff0c;那么如何在HTML中引用CSS呢&#xff1f; CSS的引用方式有三种&#xff1…

【LeetCode算法笔记】Day1:动态规划基础

目录 动态规划简介动态规划的定义动态规划的核心思想动态规划的简单例子 动态规划特征最优子结构性质重复子问题性质无后效应 动态规划的基本思路 动态规划简介 动态规划的定义 简称DP,是一种求解多阶段决策过程最优化问题的方法。在动态规划中&#xff0c;通过把原问题分解为…

Golang | Leetcode Golang题解之第478题在圆内随机生成点

题目&#xff1a; 题解&#xff1a; type Solution struct {radius, xCenter, yCenter float64 }func Constructor(radius, xCenter, yCenter float64) Solution {return Solution{radius, xCenter, yCenter} }func (s *Solution) RandPoint() []float64 {r : math.Sqrt(rand.…

MySQL面试专题-索引

一、MySQL为什么要选择B树来存储索引&#xff1f; MySQL的索引选择B树作为数据结构来进行存储&#xff0c;其本质原因在于可以减少IO次数&#xff0c;提高查询效率&#xff0c;简单来说就是保证在树的高度不变的情况下可以存储更多的数据。 &#xff08;一&#xff09;IO角度 在…

约克VRF打造舒适绿色无污染的生活环境

在生活的各个方面&#xff0c;约克VRF都采取了多种措施助力碳中和。 采用国际领先的空气源热泵技术&#xff0c;只需少量电力就可将空气中的能量转化为室内热量&#xff0c;被称为“大自然的搬运工”&#xff01;COP能效值最高可达4.24&#xff08;每用一度电产生4.24度电热量&…

第 3 章:使用 Vue 脚手架

1. 初始化脚手架 1.1 说明 Vue 脚手架是 Vue 官方提供的标准化开发工具&#xff08;开发平台&#xff09;。最新的版本是 5.x。文档: https://cli.vuejs.org/zh/ 1.2 具体步骤 第一步&#xff08;仅第一次执行&#xff09;&#xff1a;全局安装vue/cli。 npm install -g vu…

衡石分析平台系统分析人员手册-仪表盘控件概述

控件​ 控件是仪表盘的基本组成单位。控件种类很多&#xff0c;有展示分析数据的图表类类控件&#xff0c;有展示图片、文字的展示类控件&#xff0c;还有可导出数据、刷新数据、过滤数据等功能类控件。一个完整的仪表盘由多种不同功能的控件构成。 控件类型​ 根据控件是否展…

海外动态代理IP的优缺点有哪些? 动态代理IP与静态代理IP的区别是什么?

海外动态代理IP的优缺点分析 在全球化的数字时代&#xff0c;网络安全和隐私保护的重要性日益凸显。海外动态代理IP作为一种灵活的网络工具&#xff0c;因其独特的特性在多个领域得到了广泛应用。然而&#xff0c;正如任何技术一样&#xff0c;它也有其优点和局限性。以下&…

Shell案例之一键部署mysql

1.问题 我认为啊学习就是一个思考的过程&#xff0c;思考问题的一个流程应该是&#xff1a;提出问题&#xff0c;分析问题&#xff0c;解决问题 在shell里部署mysql服务时&#xff0c;我出现一些问题&#xff1a; 1.安装mysql-server时&#xff0c;没有密钥&#xff0c;安装…

PE结构之导入表

流程图: 文件中\样式 加载到进程中时 加载到进程中时的过程,一张图不够放 续图 整个流程 补充导入表结构IMAGE_IMPORT_DESCRIPTOR 中的ForwarderChain字段, 该解释为 "某个导入模块涉及转发&#xff08;即该模块的某些函数从其他模块转发过来&#xff09;&#xff0c;那么…

windows安装deepspeed setup.py 207行找不到文件

一直报莫名奇妙的错误&#xff0c;查了半天也没查到 去看了一下源码&#xff0c;需要安装git&#xff0c;我没有安装 git命令获得信息也没啥用 直接注释掉 成功运行