开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Docker 环境清理的常用方法

发表于 2021-11-20

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

如果你使用 docker 进行大规模开发,但是没有清理策略,那么你的磁盘马上就会被填满,当你真的必须因为产品火爆而要立即交付一些东西时,你就无法交付。

当我们在计算机中运行一个进程时,一旦这个进程完成,所有东西都会随之销毁。容器是我们现在很多人操作的基础设施。一切东西都运行在一个容器上,旨在每个容器有一个进程。当进程完成,容器就退出。但它不会自行清理。

Docker 积累的东西

你需要注意这些:

  • 已经停止的容器
  • 磁盘卷
  • 镜像
  • 网络

如果有足够的空间,你可能不太关心磁盘空间,但是网络也很重要。默认地,Docker 使用 bridge 网络,它的极限是 31 个网络。当达到极限时,你会看到下面这条消息:

1
sh复制代码could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

如果你是一个为每个项目创建一个网络的 docker-compose 重度用户,就会发生这种情况。你可以通过设置一个自定义--subnet子网来解决问题,例如:

1
sh复制代码docker network create dada --subnet 192.167.11.0/24

但无论如何,本文的重点是清理。

使用 docker 清理

清理已经停止的容器

1
css复制代码docker rm -v $(docker ps --all --quiet --filter 'status=exited')

这会找到所有处于已退出(exited)状态的容器,一行一个地输出它们的 ID,以便我们可以将它提供给其它 shell 指令。

我们使用docker rm -v来删除任何匿名卷(没有显式名称的卷)。

清理磁盘卷

上面的命令应该删除与该容器关联的卷。如果你手动创建卷,并要删除任何未被使用的卷:

1
bash复制代码docker volume rm $(docker volume ls --quiet --filter 'dangling=true')

清理镜像

通常删除所有 Docker 镜像是安全的。我们可以在需要的时候按需获取。通常在一个镜像被清理后,构建时间会更长,因为 docker 守护进程需要花时间再次下载镜像

1
css复制代码docker rm --force $(docker images --quiet)

这里,我们使用了--force来强制删除镜像,即使一个容器正在使用那个镜像。我们可以以后再获取这个镜像。

清理网络

这很简单。我们可以删除任何网络,它会在之后按需重建。

1
bash复制代码docker network rm $(docker network ls --quiet)

使用 docker-compose 清理

如果你使用 docker-compose 启动容器,我们有一种简单的方法来清理与特定 compose 文件关联的资源。

1
css复制代码docker-compose down --volumes --rmi all --remove-orphans

不幸的是,这个命令不会删除匿名卷,因此你必须处理这些匿名卷。

一条命令解决所有问题

docker 是短暂的,我们总是可以重新获取镜像,为了开发重新创建我们的数据库,或者这只是一个持续集成系统,我们可以删除一切。

1
css复制代码docker system prune --all --force --volumes

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

常用限流算法及中间件,总有一款适用你

发表于 2021-11-20

Image [2].png

概要

在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃。此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。
对一般的限流场景来说它具有两个维度的信息:
时间:限流基于某段时间范围或者某个时间点,也就是我们常说的“时间窗口”,比如对每分钟、每秒钟的时间窗口做限定
资源:基于可用资源的限制,比如设定最大访问次数,或最高可用连接数

上面两个维度结合起来看,限流就是在某个时间窗口对资源访问做限制,比如设定每秒最多100个访问请求。但在真正的场景里,我们不止设置一种限流规则,而是会设置多个限流规则共同作用。
主要的几种限流规则如下:

Image.png

QPS和连接数控制

对于图中连接数和QPS)限流来说,我们可设定IP维度的限流,也可以设置基于单个服务器的限流。

在真实环境中通常会设置多个维度的限流规则,比如设定同一个IP每秒访问频率小于10,连接数小于5,再设定每台机器QPS最高1000,连接数最大保持200。更进一步,我们可以把某个服务器组或整个机房的服务器当做一个整体,设置更high-level的限流规则,这些所有限流规则都会共同作用于流量控制。

传输速率

对于“传输速率”大家都不会陌生,比如资源的下载速度。有的网站在这方面的限流逻辑做的更细致,比如普通注册用户下载速度为100k/s,购买会员后是10M/s,这背后就是基于用户组或者用户标签的限流逻辑。

黑白名单

黑白名单是各个大型企业应用里很常见的限流和放行手段,而且黑白名单往往是动态变化的。举个例子,如果某个IP在一段时间的访问次数过于频繁,被系统识别为机器人用户或流量攻击,那么这个IP就会被加入到黑名单,从而限制其对系统资源的访问,这就是我们俗称的“封IP”。
我们平时见到的爬虫程序,比如说爬知乎上的美女图片,或者爬券商系统的股票分时信息,这类爬虫程序都必须实现更换IP的功能,以防被加入黑名单。有时我们还会发现公司的网络无法访问12306这类大型公共网站,这也是因为某些公司的出网IP是同一个地址,因此在访问量过高的情况下,这个IP地址就被对方系统识别,进而被添加到了黑名单。使用家庭宽带的同学们应该知道,大部分网络运营商都会将用户分配到不同出网IP段,或者时不时动态更换用户的IP地址。
白名单就更好理解了,相当于御赐金牌在身,可以自由穿梭在各种限流规则里,畅行无阻。比如某些电商公司会将超大卖家的账号加入白名单,因为这类卖家往往有自己的一套运维系统,需要对接公司的IT系统做大量的商品发布、补货等等操作。

分布式环境

分布式区别于单机限流的场景,它把整个分布式环境中所有服务器当做一个整体来考量。比如说针对IP的限流,我们限制了1个IP每秒最多10个访问,不管来自这个IP的请求落在了哪台机器上,只要是访问了集群中的服务节点,那么都会受到限流规则的制约。

从上面例子不难看出,我们最好将限流信息保存在一个“中心化”的组件上,这样它就可以获取到集群中所有机器的访问状态,目前有两个比较主流的限流方案:

网关层限流 将限流规则应用在所有流量的入口处
中间件限流 将限流信息存储在分布式环境中某个中间件里(比如Redis缓存),每个组件都可以从这里获取到当前时刻的流量统计,从而决定是拒绝服务还是放行流量
sentinel,springcloud生态圈为微服务量身打造的一款用于分布式限流、熔断降级等组件

限流方案常用算法介绍

说到限流,至少我们需要对限流的底层原理有个大致的了解,才好更深入的进行学习,下面我们挑选令牌桶算法、漏桶算法、滑动窗口和计数器算法来说一下

令牌桶算法

Token Bucket令牌桶算法是目前应用最为广泛的限流算法,顾名思义,它有以下两个关键角色:

令牌 获取到令牌的Request才会被处理,其他Requests要么排队要么被直接丢弃
桶 用来装令牌的地方,所有Request都从这个桶里面获取令牌
用图简单描述如下

Image [3].png

主要涉及到2个过程:

令牌生成

这个流程涉及到令牌生成器和令牌桶,前面我们提到过令牌桶是一个装令牌的地方,既然是个桶那么必然有一个容量,也就是说令牌桶所能容纳的令牌数量是一个固定的数值。
对于令牌生成器来说,它会根据一个预定的速率向桶中添加令牌,比如我们可以配置让它以每秒100个请求的速率发放令牌,或者每分钟50个。注意这里的发放速度是匀速,也就是说这50个令牌并非是在每个时间窗口刚开始的时候一次性发放,而是会在这个时间窗口内匀速发放。
在令牌发放器就是一个水龙头,假如在下面接水的桶子满了,那么自然这个水(令牌)就流到了外面。在令牌发放过程中也一样,令牌桶的容量是有限的,如果当前已经放满了额定容量的令牌,那么新来的令牌就会被丢弃掉。

令牌获取

每个访问请求到来后,必须获取到一个令牌才能执行后面的逻辑。假如令牌的数量少,而访问请求较多的情况下,一部分请求自然无法获取到令牌,那么这个时候我们可以设置一个“缓冲队列”来暂存这些多余的令牌。
缓冲队列其实是一个可选的选项,并不是所有应用了令牌桶算法的程序都会实现队列。当有缓存队列存在的情况下,那些暂时没有获取到令牌的请求将被放到这个队列中排队,直到新的令牌产生后,再从队列头部拿出一个请求来匹配令牌。
当队列已满的情况下,这部分访问请求将被丢弃。在实际应用中我们还可以给这个队列加一系列的特效,比如设置队列中请求的存活时间,或者将队列改造为PriorityQueue,根据某种优先级排序,而不是先进先出。

漏桶算法

Leaky Bucket,又是个桶,限流算法是跟桶杠上了,那么漏桶和令牌桶有什么不同呢?我们来看图说话:

Image [4].png

漏桶算法的前半段和令牌桶类似,但是操作的对象不同,令牌桶是将令牌放入桶里,而漏桶是将访问请求的数据包放到桶里。同样的是,如果桶满了,那么后面新来的数据包将被丢弃。
漏桶算法的后半程是有鲜明特色的,它永远只会以一个恒定的速率将数据包从桶内流出。打个比方,如果我设置了漏桶可以存放100个数据包,然后流出速度是1s一个,那么不管数据包以什么速率流入桶里,也不管桶里有多少数据包,漏桶能保证这些数据包永远以1s一个的恒定速度被处理。
漏桶 vs 令牌桶的区别

根据它们各自的特点不难看出来,这两种算法都有一个“恒定”的速率和“不定”的速率。令牌桶是以恒定速率创建令牌,但是访问请求获取令牌的速率“不定”,反正有多少令牌发多少,令牌没了就干等。而漏桶是以“恒定”的速率处理请求,但是这些请求流入桶的速率是“不定”的。
从这两个特点来说,漏桶的天然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。而令牌桶则不同,其特性可以“预存”一定量的令牌,因此在应对突发流量的时候可以在短时间消耗所有令牌,其突发流量处理效率会比漏桶高,但是导向后台系统的压力也会相应增多。

滑动窗口

根据图示,我们将时间窗口的限流原理拆解描述一下其过程:

Image [5].png

黑色的大框就是时间窗口,我们设定窗口时间为5秒,它会随着时间推移向后滑动。我们将窗口内的时间划分为五个小格子,每个格子代表1秒钟,同时这个格子还包含一个计数器,用来计算在当前时间内访问的请求数量。那么这个时间窗口内的总访问量就是所有格子计数器累加后的数值。
比如说,我们在每一秒内有5个用户访问,第5秒内有10个用户访问,那么在0到5秒这个时间窗口内访问量就是15。如果我们的接口设置了时间窗口内访问上限是20,那么当时间到第六秒的时候,这个时间窗口内的计数总和就变成了10,因为1秒的格子已经退出了时间窗口,因此在第六秒内可以接收的访问量就是20-10=10个。
滑动窗口其实也是一种计算器算法,它有一个显著特点,当时间窗口的跨度越长时,限流效果就越平滑。打个比方,如果当前时间窗口只有两秒,而访问请求全部集中在第一秒的时候,当时间向后滑动一秒后,当前窗口的计数量将发生较大的变化,拉长时间窗口可以降低这种情况的发生概率
那么有了上面的基础理论之后,我们来简单总结下目前常用的限流方案有哪些呢?

Guawa限流

说起Guava大家一定不陌生,它是Google出品的一款工具包,我们经常用它做一些集合操作比如Lists.newArrayList()等,它最早源于2007的”Google Collections Library”项目。Guava不甘于将自己平凡的一生都耗费在Collections上面,于是乎它开始了转型,慢慢扩展了自己在Java领域的影响力,从反射工具、函数式编程、安全验证、数学运算等等方面,都提供了响应的工具包

在限流领域中,Guava也贡献了一份绵薄之力,在其多线程模块下提供了以RateLimiter为首的几个限流支持类,但是作用范围仅限于“当前”这台服务器,也就是说Guawa的限流是单机的限流,跨了机器或者jvm进程就无能为力了

比如说,目前我有2台服务器[Server 1,Server 2],这两台服务器都部署了一个登陆服务,假如我希望对这两台机器的流量进行控制,比如将两台机器的访问量总和控制在每秒20以内,如果用Guava来做,只能独立控制每台机器的访问量<=10。

尽管Guava不是面对分布式系统的解决方案,但是其作为一个简单轻量级的客户端限流组件,非常适合来讲解限流算法

网关层限流

在整个分布式系统中,如果有这么一个“一夫当关,万夫莫开”的角色,非网关层莫属。服务网关,作为整个分布式链路中的第一道关卡,承接了所有用户来访请求,因此在网关层面进行限流是一个很好的切入点

网关层限流的架构思考

如果我们将这个系统的模型想象成为一个漏斗模型的话,抽象出来大概如下面的结构:
上面是一个最普通的流量模型,从上到下的路径依次是:

  1. 用户流量从网关层转发到后台服务
  2. 后台服务承接流量,调用缓存获取数据
  3. 缓存中无数据,则访问数据库

为什么说它是一个漏斗模型,因为流量自上而下是逐层递减的,在网关层聚集了最多最密集的用户访问请求,其次是后台服务。

然后经过后台服务的验证逻辑之后,刷掉了一部分错误请求,剩下的请求落在缓存上,如果缓存中没有数据才会请求漏斗最下方的数据库,因此数据库层面请求数量最小(相比较其他组件来说数据库往往是并发量能力最差的一环,阿里系的MySQL即便经过了大量改造,单机并发量也无法和Redis、Kafka之类的组件相比)

如果在上面这个漏斗模型中做流量限制,网关层首当其冲对不对?因为它是整个访问链路的源头,是所有流量途径的第一站。目前主流的网关层有以软件为代表的Nginx,还有Spring Cloud中的Gateway和Zuul这类网关层组件,也有以硬件+软件为代表的F5(F5价钱贵到你怀疑人生)

Nginx限流

在系统架构中,Nginx的代理与路由转发是其作为网关层的一个很重要的功能,由于Nginx天生的轻量级和优秀的设计,让它成为众多公司的首选,Nginx从网关这一层面考虑,可以作为最前置的网关,抵挡大部分的网络流量,因此使用Nginx进行限流也是一个很好的选择,在Nginx中,也提供了常用的基于限流相关的策略配置,后续我们将会使用简单的案例进行说明

中间件限流

对于分布式环境来说,无非是需要一个类似中心节点的地方存储限流数据。打个比方,如果我希望控制接口的访问速率为每秒100个请求,那么我就需要将当前1s内已经接收到的请求的数量保存在某个地方,并且可以让集群环境中所有节点都能访问。那我们可以用什么技术来存储这个临时数据呢?

那么想必大家都能想到,必然是redis了,利用Redis过期时间特性,我们可以轻松设置限流的时间跨度(比如每秒10个请求,或者每10秒10个请求)。同时Redis还有一个特殊技能–脚本编程,我们可以将限流逻辑编写成一段脚本植入到Redis中,这样就将限流的重任从服务层完全剥离出来,同时Redis强大的并发量特性以及高可用集群架构也可以很好的支持庞大集群的限流访问。【reids + lua】

限流组件

除了上面介绍的几种方式以外,目前也有一些开源组件提供了类似的功能,比如Sentinel就是一个不错的选择。Sentinel是阿里出品的开源组件,并且包含在了Spring Cloud Alibaba组件库中,Sentinel提供了相当丰富的用于限流的API以及可视化管控台,可以很方便的帮助我们对限流进行治理

从架构维度考虑限流设计
在真实的项目里,不会只使用一种限流手段,往往是几种方式互相搭配使用,让限流策略有一种层次感,达到资源的最大使用率。在这个过程中,限流策略的设计也可以参考前面提到的漏斗模型,上宽下紧,漏斗不同部位的限流方案设计要尽量关注当前组件的高可用。以我参与的实际项目为例,比如说我们研发了一个商品详情页的接口,通过手机淘宝导流,app端的访问请求首先会经过阿里的mtop网关,在网关层我们的限流会做的比较宽松,等到请求通过网关抵达后台的商品详情页服务之后,再利用一系列的中间件+限流组件,对服务进行更加细致的限流控制

【参考】

【1】blog.csdn.net/zhangcongyi…

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis缓存穿透、缓存雪崩及缓存一致性 缓存雪崩 缓存穿透

发表于 2021-11-20

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。

缓存雪崩

缓存同一时间大面积失效,后面的请求都会落到数据库上,造成数据库短时间内无法承受大量请求而崩溃

例如在电商首页,所有首页的key失效时间都是12小时,中午12点刷新,如果零点有个秒杀活动大量用户涌入,但是缓存当时所有key都失效,此时所有的请求会落到数据库,数据库扛不住,就直接就gg了,又或者redis宕机,也会让大量请求落到mysql,造成挂机。

解决方案

  • 所以像这种情况就应该把每个key的失效时间加个随机值,避免同一时间大量的key失效,如果是redis集群部署,可以将热点数据分布到各个不同的库。
  • 事前:尽量保证redis集群的高可用性,发现机器宕机尽快补上,选择合适的内存淘汰策略
  • 事中:本地ehcache缓存+hystrix限流加降级,避免mysql崩掉
  • 事后:里有redis持久化机制保存的数据尽快恢复缓存。

缓存穿透

大量请求的key不存在于缓存中,例如某个黑客制造缓存中不存在的key发起大量请求,导致大量请求落到数据库。

解决办法

  • 首先应该要做基本的入参校验,将不合法的参数直接拦截,例如查询数据库id不能小于0,校验邮箱格式等等
  • 如果缓存和数据库都查不到某个key的数据,就将key写入到redis,value为null,并设置过期时间,避免下次请求落到数据库上。
  • 通过布隆过滤器,布隆过滤器可以非常方便的判定一个给定的数据是否存在与海量数据中.可以将所有可能存在的请求的值存到布隆过滤器,当请求过来先判断用户发来的请求是否存在于布隆过滤器,不存在就直接拦截。

缓存击穿

缓存击穿指的是一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key失效瞬间,持续的大并发就穿破缓存,直接请求到数据库

缓存一致性

如果是要求强一致性,那就不能使用缓存,因为保证不了强一致性,只能保证最终一致性。

  • 先删除缓存,再更新数据库

如果数据库更新失败,那么数据库的还是旧数据,redis是空,数据不会不一致,读到空会去数据库进行查询,然后更新到缓存。

  • 加入队列,进行串行化操作

先删除缓存,再更新数据库,在高并发场景下也会出现问题,例如删除了缓存,这时还没更新数据库,另一个线程进来,发现redis是空,会去读数据库,然后更新到redis,而此时删除了缓存的线程接着更新数据库,就会造成数据库和redis数据不一致,这时候可以将更新数据的操作放到队列当中,串行化操作,不会出现,但一般不建议这样做,因为会降低效率。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1Windows安装和配置MongoDB 50

发表于 2021-11-20

1.下载

可以从官网下载,根据自己的需要选择不同的版本,本文选择的是社区版5.04。

image.png

2.软件安装与配置

2.1 选择安装路径

安装比较简单,中间主要是选择“Custom”,根据实际情况配置数据安装目录即可。

image.png

2.2 windows服务配置

image.png

  • 选择将MongoD安装为服务:Run the service as Network Service user(默认)推荐使用
  • 以本地或域用户身份运行服务:Run the service as a local or domain user
    • Server Name:服务名称。默认名称是MongoDB
    • Data Driectory:数据目录。对应参数 –-dbpath,如果目录不存在会自动创建
    • Log Driectory:日志目录。对应参数 –logpath,如果目录不存在会自动创建

2.3 取消安装图形界面

选择完服务信息后,会提示下载Install MongoDB Compass(图形界面),不建议勾选,文件很大,下载很慢,可以选择别的数据库管理工具。
image.png

2.4 查看服务

由于电脑上原先有MongoDB服务,所有服务命名成MongoDB-5
image.png
软件默认的启动命令

1
shell复制代码D:\6.MongoDB\Server\5.0\bin\mongod.exe --config "D:\6.MongoDB\Server\5.0\bin\mongod.cfg" --service

配置文件bin\mongod.cfg默认参数
image.png

在浏览器中输入地址:localhost:27017 出现以下内容说明服务已经安装成功,并运行正常!
image.png

  1. zip包方式安装MongoDB

3.1 解压安装

将zip包解压到安装目录,创建data(数据库目录)和log(日志)文件夹

3.2 配置系统环境变量

image.png

3.3 启动MongoDB并注册为系统服务

用管理员身份打开cmd命令窗口,执行以下命令

1
shell复制代码mongod --logpath D:\6.MongoDB\Server\5.0\log\mongodb.log --logappend --dbpath D:\6.MongoDB\Server\5.0\data --directoryperdb --serviceName "MongoDB" --install

也可以通过以下命令开启和关闭MongoDB服务

1
2
arduino复制代码net start MongoDB
net stop MongoDB

常用参数

参数 描述
bind_ip 绑定服务IP,若绑定127.0.0.1,则只能本机访问,不指定默认本地所有IP
logpath 指定MongoDB日志文件,注意是指定文件不是目录
logappend 使用追加的方式写日志
dbpath 指定数据库路径
port 指定服务端口号,默认端口27017
-serviceName 指定服务名称
–serviceDisplayName 指定服务名称,有多个mongodb服务时执行
–directoryperdb 设置每个数据库将被保存在一个单独的目录
–install 指定作为一个Windows服务安装

更多详细的参数说明可参考:(MongoDB mongod.exe - MongoDB服务启动工具)

4.问题

4.1 启动MongoDB服务时提示:Windows不能在本地计算机启动MongoDB,错误代码 100

image.png

查看日志发现是因为D盘空间不足导致的,最少需要3379MB空间。
image.png

  • 删除data目录下的mongod.lock文件
  • 删除现有的MongoDB服务
1
shell复制代码mongod.exe --remove --serviceName "MongoDB"
  • 重新安装
1
shell复制代码D:\6.MongoDB\Server\5.0\bin\mongod.exe --logpath D:\6.MongoDB\Server\5.0\log\mongodb.log --logappend --dbpath D:\6.MongoDB\Server\5.0\data --directoryperdb --serviceName "MongoDB" --install

这里要注意,如果之前启动mongoDB服务的时候,加了参数–directoryperdb,重新启动时也得加上,要不之前的数据库会读取不到。–directoryperdb参数的意思是设置每个数据库将被保存在一个单独的目录。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

VSCode配置 c++ 环境(小白教程) 配置c++环境

发表于 2021-11-20

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」

因为自己在VSCode配置过程中遇到许多问题,在此记录一下,希望能帮助到更多人

配置c++环境

前言


  • 本人环境:Windows10 64位
  • VSCode 使用起来还是很方便的,运行速度快,占用系统资源小,还有丰富的插件

软件安装


1. 下载安装MinGW-w64及环境变量配置

MinGW:可以理解成包含多种编译环境的可自由选择安装的其中的部分的软件

  • 下载地址:osdn.net/projects/mi…
    直接弹窗下载即可,下载完按照下面教程安装配置即可。
    在这里插入图片描述

鉴于有很多人安装失败,我将 MinGW 安装文件放在了网盘,链接如下:
链接:pan.baidu.com/s/1hng1o1ru…
提取码:r823

  • 安装
    在这里插入图片描述
    安装完成后界面
    在这里插入图片描述
  • 下载相关文件(重要步骤)

打开MinGW,勾选编译器软件

+ mingw32-gcc.bin(c语言文件编译器)
+ mingw32-gcc-g++.bin(c++语言编译器)
+ mingw32-gdb.bin(调试编译后文件)

若在安装的时候报“找不到 xxx 库”的错误,可以重新勾选此库和dll文件

在这里插入图片描述
选择完后,点击 Installation > applychange,等待安装完就ok
在这里插入图片描述

对于 mingw32-gdb.bin 等下载失败问题,这里我把所需的三个文件放在网盘,下载后将自己没下载成功的对应文件放到 MinGW\bin 目录下即可,链接如下:
链接:pan.baidu.com/s/1ZJFnR_MR…
提取码:18rx

  • 添加环境变量
    • 右击 我的电脑 点击 属性
      在这里插入图片描述
    • 点击 高级系统设置
      在这里插入图片描述
    • 点击 环境变量
      在这里插入图片描述
    • 在系统变量中选择 Path,新建
      在这里插入图片描述
    • 将 bin地址 加入环境变量 (我的是 “D:\MinGW\bin”)
      在这里插入图片描述
    • 检测是否安装成功:命令提示符中输入 gcc -v ,若显示版本号则说明安装成功
      在这里插入图片描述

2. 下载安装VSCode


  • 下载地址:code.visualstudio.com/Download
    在这里插入图片描述
    在这里插入图片描述

配置过程

1. VSCode插件安装


安装完成后,打开软件

  • 下载中文插件
    在这里插入图片描述
  • 下载 c/c++ 插件
    在这里插入图片描述

插件安装完成后 重启 VSCode

2. 创建工作区


  • 新建一个文件夹(放 c++ 代码文件)
  • 点击文件打开建的文件夹(快捷键 Ctrl+k Ctrl+O)在这里插入图片描述

3. 配置文件


  • 在此文件夹新建一个 .vscode 文件夹
    在这里插入图片描述
  • 在 .vscode 文件夹中新建三个文件
+ c\_cpp\_properties.json
+ launch.json
+ tasks.json
![在这里插入图片描述](https://gitee.com/songjianzaina/juejin_p18/raw/master/img/c1f120f69746402f5fbf8980be849c66ad348256fc3d08a710e296f2b5838e80)
  • 将下列代码复制到文件中

务必根据自己的安装位置修改文件路径

+ c\_cpp\_properties.json


"includePath" 路径获取方法 cmd 下输入: gcc -v -E -x c++ -
![在这里插入图片描述](https://gitee.com/songjianzaina/juejin_p18/raw/master/img/9a1c96ac04e36dd642983b06b9c6bacf6b67c248022a480f005634df739d7b14)
![在这里插入图片描述](https://gitee.com/songjianzaina/juejin_p18/raw/master/img/3c409f09ddf0c3a9eb808daeb2b9273840b636bcc15fa27e6d8c7d6e6fa23345)
修改 **"includePath"** 和 **"Path"**
将图示这几行代替其中路径即可



> "d:/mingw/include/\*\*" 此条路径也根据你的路径修改加入即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
json复制代码{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceRoot}",
"d:/mingw/include/**",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include/c++",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include/c++/mingw32",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include/c++/backward",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/../../../../include",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include-fixed"
],
"defines": [
"_DEBUG",
"UNICODE",
"__GNUC__=6",
"__cdecl=__attribute__((__cdecl__))"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": "",
"path": [
"${workspaceRoot}",
"d:/mingw/include/**",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include/c++",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include/c++/mingw32",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include/c++/backward",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/../../../../include",
"d:/mingw/bin/../lib/gcc/mingw32/8.2.0/include-fixed"
]
}
}
],
"version": 4
}
+ launch.json
根据自己路径修改 **"miDebuggerPath"**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
json复制代码{  
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch", // 配置名称,将会在启动配置的下拉菜单中显示
"type": "cppdbg", // 配置类型,这里只能为cppdbg
"request": "launch", // 请求配置类型,可以为launch(启动)或attach(附加)
"program": "${workspaceFolder}/exe/${fileBasenameNoExtension}.exe",// 将要进行调试的程序的路径
"args": [], // 程序调试时传递给程序的命令行参数,一般设为空即可
"stopAtEntry": false, // 设为true时程序将暂停在程序入口处,一般设置为false
"cwd": "${workspaceFolder}", // 调试程序时的工作目录,一般为${workspaceFolder}即代码所在目录
"environment": [],
"externalConsole": true, // 调试时是否显示控制台窗口,一般设置为true显示控制台
"MIMode": "gdb",
"miDebuggerPath": "D:/MinGW/bin/gdb.exe", // miDebugger的路径,注意这里要与MinGw的路径对应
"preLaunchTask": "g++", // 调试会话开始前执行的任务,一般为编译程序,c++为g++, c为gcc
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
+ tasks.json
直接复制粘贴即可,无需修改

这里我顺便把 exe 的路径改了。在 “args” 中,需要进行修改可自行百度,此处我的会将编译生成的 exe 文件单独放在一个 exe文件夹 中
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
json复制代码{
"version": "2.0.0",
"command": "g++",
"args": [
"-g",
"${file}",
"-o",
"${workspaceFolder}/exe/${fileBasenameNoExtension}.exe"
], // 编译命令参数
"problemMatcher": {
"owner": "cpp",
"fileLocation": [
"relative",
"\\"
],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
}

这里解释一下args中自定义 exe文件存放路径:
workspaceFolder:当前工作路径exe:自己建的文件夹{workspaceFolder}: 当前工作路径
exe: 自己建的文件夹
workspaceFolder:当前工作路径exe:自己建的文件夹{fileBasenameNoExtension}: 当前文件的文件名,NoExtension意思就是不带后缀名,然后加个 .exe 就是当前的exe文件,也就是编译生成的 exe 文件
上述路径放一起意思就编译生成的 exe 文件放在exe文件夹中, 把这条配置删除的话,编译生成的exe文件就默认放在了当前工作路径下。

顺便说一下 vscode 中一些变量代表什么意思
workspaceFolder:表示当前workspace文件夹路径,也即/home/Coding/Test{workspaceFolder} :表示当前workspace文件夹路径,也即/home/Coding/Test
workspaceFolder:表示当前workspace文件夹路径,也即/home/Coding/Test{workspaceRootFolderName}:表示workspace的文件夹名,也即Test
file:文件自身的绝对路径,也即/home/Coding/Test/.vscode/tasks.json{file}:文件自身的绝对路径,也即/home/Coding/Test/.vscode/tasks.json
file:文件自身的绝对路径,也即/home/Coding/Test/.vscode/tasks.json{relativeFile}:文件在workspace中的路径,也即.vscode/tasks.json
fileBasenameNoExtension:当前文件的文件名,不带后缀,也即tasks{fileBasenameNoExtension}:当前文件的文件名,不带后缀,也即tasks
fileBasenameNoExtension:当前文件的文件名,不带后缀,也即tasks{fileBasename}:当前文件的文件名,tasks.json
fileDirname:文件所在的文件夹路径,也即/home/Coding/Test/.vscode{fileDirname}:文件所在的文件夹路径,也即/home/Coding/Test/.vscode
fileDirname:文件所在的文件夹路径,也即/home/Coding/Test/.vscode{fileExtname}:当前文件的后缀,也即.json
lineNumber:当前文件光标所在的行号{lineNumber}:当前文件光标所在的行号
lineNumber:当前文件光标所在的行号{env:PATH}:系统中的环境变量

4. 测试


  • 新建一个 cpp 文件
    在这里插入图片描述
  • F5 编译,成功
    在这里插入图片描述

后序

VSCode 一些好用的插件


  • Bracket Pair Colorizer —— 为不同匹配括号提供不同的颜色高亮
    在这里插入图片描述
  • Markdown All in one —— 书写 Markdown 利器
    在这里插入图片描述
  • Path Intellisence —— 路径自动补齐
    在这里插入图片描述
  • vscode-icons —— 图标插件
    在这里插入图片描述

VSCode 修改背景图片

  • 以管理员权限启动 VSCode,安装 background 插件
    在这里插入图片描述Ctrl+P 打开搜索框,输入 settings.json
    在这里插入图片描述
  • settings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
json复制代码{
"editor.fontSize": 20,
"editor.mouseWheelZoom": true,
"window.zoomLevel": 0,
"files.autoGuessEncoding": true,

// background 的相关配置
"update.enableWindowsBackgroundUpdates": true,
"background.customImages": [
"file:///D:/vscode/vscode_bg/bg.png" // 图片地址
],
"background.style": {
"content":"''",
"pointer-events":"none",
"position":"absolute", // 图片位置
"width":"100%",
"height":"100%",
"z-index":"99999",
"background.repeat":"no-repeat",
"background-size":"25%,25%", // 图片大小
"opacity":0.2 // 透明度
},
"background.useFront": true,
"background.useDefault": false,
"workbench.iconTheme": "vscode-icons-mac", // 是否使用默认图片
}

修改图片地址即可
这是我的背景图片:smile:
在这里插入图片描述

VSCode 设置鼠标滚动改变字体大小

  • settings.json 中加入 “editor.mouseWheelZoom”: true 即可,上面我已经加上了,使用 Ctrl+鼠标滚轮 即可实现字体放大缩小
    在这里插入图片描述

最后,欢迎大家关注我的个人微信公众号 『小小猿若尘』,获取更多IT技术、干货知识、热点资讯

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java实现Gif图转字符动图

发表于 2021-11-20

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。

前面介绍了两篇基于jdk实现图片灰度处理、转字符图片的操作,接下来我们在将之前的能力扩展一下,支持将一个gif图灰度化或者转gif字符图

本文的实现主要在前面两篇文章的基础上来实现,推荐没有看过的小伙伴也可以瞅一眼

  • Java实现图片灰度化
  • Java实现图片转字符图片示例demo

单张图的灰度化与转字符实现之后,gif图的实现就简单多了;gif图无非是多张图组合而成,将每一张图转换之后,再重新组装成gif图就完事了

这里我们使用的gif工具类来自于github.com/liuyueyi/qu…

核心关键类为GifEncode与GifDecode;借助它来实现gif图的加载与保存

首先我们将上篇博文中的转字符图的方法抽取一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
java复制代码Color getAverage(BufferedImage image, int x, int y, int w, int h) {
int red = 0;
int green = 0;
int blue = 0;

int size = 0;
for (int i = y; (i < h + y) && (i < image.getHeight()); i++) {
for (int j = x; (j < w + x) && (j < image.getWidth()); j++) {
int color = image.getRGB(j, i);
red += ((color & 0xff0000) >> 16);
green += ((color & 0xff00) >> 8);
blue += (color & 0x0000ff);
++size;
}
}

red = Math.round(red / (float) size);
green = Math.round(green / (float) size);
blue = Math.round(blue / (float) size);
return new Color(red, green, blue);
}

private BufferedImage parseImg(BufferedImage img) {
int w = img.getWidth(), h = img.getHeight();
// 创建新的灰度图片画板
BufferedImage out = new BufferedImage(w, h, img.getType());
Graphics2D g2d = out.createGraphics();
g2d.setColor(null);
g2d.fillRect(0, 0, w, h);

int size = 12;
Font font = new Font("宋体", Font.BOLD, size);
g2d.setFont(font);
for (int x = 0; x < w; x += size) {
for (int y = 0; y < h; y += size) {
Color avgColor = getAverage(img, x, y, size, size);
g2d.setColor(avgColor);
g2d.drawString("灰", x, y);
}
}
g2d.dispose();
return out;
}

接着就是Gif的操作了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码@Test
public void testRender() throws IOException {
String file = "https://c-ssl.duitang.com/uploads/item/201707/11/20170711194634_nTiK5.thumb.1000_0.gif";
// 从网络上下载图片
GifDecoder decoder = new GifDecoder();
decoder.read(FileReadUtil.getStreamByFileName(file));

// 这里是核心的转换逻辑
List<ImmutablePair<BufferedImage, Integer>> frames = new ArrayList<>();
for (int i = 0; i < decoder.getFrameCount(); i++) {
BufferedImage img = decoder.getFrame(i);
frames.add(ImmutablePair.of(parseImg(img), decoder.getDelay(i)));
}

// 下面是保存gif图
File save = new File("/tmp/out2.gif");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GifHelper.saveGif(frames, outputStream);
FileOutputStream out = new FileOutputStream(save);
out.write(outputStream.toByteArray());
out.flush();
out.close();
System.out.printf("渲染完成");
}

上图转换成功之后,输出如下

00.gif

如果希望输出图片更像原图,可以修改上面的fontSize,比如上面用的是12,可以调整成8,6等值,根据实际情况进行选择

有的小伙伴可能会说了,动漫的gif图转换之后相似度还可以,那么真实人物图转换之后呢?

接下来我们借助开源项目 github.com/liuyueyi/qu… 来迅速的实现一个gif图转换

下图来自网络,有兴趣的自己打开查看,就不贴上了😏)
n.sinaimg.cn/sinacn/w390…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@Test
public void testGif() throws Exception {
String img = "http://n.sinaimg.cn/sinacn/w390h219/20171231/0ac1-fyqefvw5238474.gif";
ImgPixelWrapper.build().setSourceImg(img)
.setBlockSize(7)
.setPixelType(PixelStyleEnum.CHAR_COLOR)
// 生成的gif图放大为原来的两倍
.setRate(2d)
// 支持设置字体
.setFontStyle(Font.BOLD)
// 这里设置生成字符图中的字符集
.setChars("灰")
.build()
.asFile(prefix + "/out3.gif");
System.out.println("--------");
}

01.gif

最后提个小问题,gif图都能生成字符图了,那么视频也可以生成字符视频么?

一灰灰的联系方式

尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

  • 个人站点:blog.hhui.top
  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840
  • 微信公众号:一灰灰blog

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

看一看str和repr原理以及两个类的魔术方法 在交互模式下

发表于 2021-11-20

「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」

在交互模式下输出的交互信息与直接print的信息有些不同,背后的原理是 ?

str和repr原理:

return返回的必须是字符串。

print打印实例对象的时候(没有重写__repr__方法,直接是pass)显示的是 地址

如果重写了__repr__方法,那么打印的就是__repr__方法里面的东西

如果同时重写了__repr__和__str__方法,打印实例对象的时候,只会显示__str__里面的内容

例一:::

1
2
3
4
5
6
7
8
9
10
ruby复制代码class Person:

def __repr__(self):
return '这是一个repr方法'

def __str__(self):
return '这是一个str方法'

p = Person()
print(p) 输出为: 这是一个str方法

%s 使用的是__str__方法; %r 使用的是__repr__方法

例二:::

1
2
3
4
5
6
7
8
9
10
11
python复制代码class Person:

def __repr__(self):
return '这是一个repr方法'

def __str__(self):
return '这是一个str方法'

p = Person()
print(' %s'%p) 输出为:这是一个str方法
print(' %r'%p) 输出为:这是一个repr方法

在python中,str和repr方法在处理对象的时候,分别调用的是对象的__str__和__repr__方法

print打印对象,调用str函数,如果对象没有定义__str__方法,则调用__repr__方法处理

在交互模式下,直接输出对象,显示 repr 的返回值

魔术方法 def call(self)

正常情况下,实例是不能像函数一样被调用的,要想实例能够被调用,就需要定义 call 方法

1
2
3
4
5
6
7
8
9
10
11
ruby复制代码class Person:

def a(self):
print('this is a ')

#实例对象加上括号就会自动调用call方法
def __call__(self,*args,**kwargs):
print('this is call')

p = Person()
p() #输出为: this is call

拓展:

类中的一些查询相关信息的方法(了解既可)

1、class 查看类名

1
markdown复制代码格式:  实例.__class__

2、dict 查看全部属性,返回属性和属性值键值对形式

1
markdown复制代码格式:实例.__dict__

3、doc 查看对象文档,即类中(用三个引号引起来的部分)

1
markdown复制代码格式:类名.__dict__

4、bases 查看父类

1
markdown复制代码格式:类名.__base__

5.mro 查看多继承的情况下,子类调用父类方法时,搜索顺序

1
2
3
markdown复制代码格式:子类名.__mro__

实例.__class__.__mro__

再来个魔术方法

类每次实例化的时候都会创建一个新的对象,如果要求类只能被实例化一次该怎么做呢?

__new__方法

举个例子:::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
python复制代码class Person:

#初始化
def __init__(self):
print('this is init')

def __new__(cls, *args , **kwargs):
print('这new方法在init之前就进行了调用')
#重写new方法
#new方法是最先被调用的
#new 必须返回父类的new方法,程序才能继续往下运行,如果不返回,即没有下面这行程序就不会往下运行。
return super().__new__(cls)

hansha = Person()

四个点理解__new__方法:
1、__new__方法是在类创建实例的时候
自动调用的。

2、实例是通过类里面的__new__方法是在
类 创建出来的。

3、先调用__new__方法创建实例,再调用 __init__方法初始化实例。

4、__new__方法,后面括号里的cls代表
的是类本身

在上面的例子中,我们可以看到创建实例的时候,自动调用了__new__,方法和__init__方法,并且
是先调用的__new__(__new__方法会在内存当中开辟一个空间)再调用的__init__方法,打印 cls 的时候显示的这个Person类

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

EasyC++34,引用和指针的区别

发表于 2021-11-20

大家好,我是梁唐。

这是EasyC++系列的第34篇,引用与指针的区别。

想要追求更好阅读体验的同学,可以点击访问github仓库:EasyLeetCode。

引用与指针的区别

指针和引用的原理非常的相似,所以很多时候尤其是面试的时候经常会拿来比较。

本文来梳理一下引用和指针的一些异同。

相同点

两者都是关于地址的概念。

指针本身是一个变量,它存储的值是一块内存地址,而引用是某一个内存的别名。我们可以使用指针或引用修改对应内存的值。

区别

  • 引用必须在声明时初始化,而指针可以不用

我们无法声明一个变量引用再给它赋值,只能在声明的同时进行初始化:

1
2
3
C++复制代码int a = 3;
int &b; // 非法
int &c = a; // 合法

而指针没有这个限制:

1
C++复制代码int *p;	// 合法
  • 引用只能在声明时初始化一次,之后不能指向其他值,而指针可以

引用一旦声明无法更改,但指针可以。某种程度上来说,引用类似于常量指针。

1
2
3
C++复制代码int a = 3;
int &b = a;
int const *p = &a;
  • 引用必须指向有效变量,指针可以为空

这是两者一个使用上巨大的区别,我们拿到一个引用可以放心地使用, 因为它一定不会为空。而指针则不然,有可能为空指针,必须要经过判断才能使用。

  • sizeof运算结果不同

sizeof函数可以计算变量内存块的大小,但如果我们对指针使用sizeof得到的是指针这个变量本身的占用内存大小,而不是指针指向的变量的内存大小。而引用则没有这个问题。

  • 有指针的引用,但是没有引用的指针

我们先来看引用的指针:

1
2
3
C++复制代码int a = 3;
int &b = a;
int *p = &b;

这段代码并不会报错,但如果我们真的去运行了,会发现p就是一个普通的int型指针,它指向的是变量a。因为b是一个引用,它的地址和a相同。所以我们定义一个指向b的指针,实际上就是定义指向a的指针。这也是指向引用的指针不存在的原因。

再来看看指针的引用,指针的引用是存在的,也很好理解,本质上就是指针的一个别名:

1
2
3
C++复制代码int a = 3;
int *p = &a;
int *&pt = p;

pt也可以指向别的变量,也可以修改解引用的值,使用上它和p没有任何区别。

除了上面说的这些之外,指针和引用还在一些细小的方面有一些差别。例如自增和自减的含义不同,指针的自增和自减代表的是指针的移动,而引用的自增自减则是变量的值发生变化。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot基础之事务失效的各种原因

发表于 2021-11-20

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

前言

事务是一个需要同时保证原子性、隔离性、一致性和持久性的一个或多个数据库操作,但是在某些情况下会存在事务失效的情况,如果你不注意,那么他就会在不经意间突然地背刺你.

事务的一些介绍可以参看前文SpringBoot基础之声明式事务和切面事务和编程式事务,包含了本文需要了解的事务的四大特征,事务的隔离级别,事务的传播行为,还有@Transactional的参数解读

事务未生效场景

事务的正常使用依靠于是事务的正确配置和使用,如果使用错误了,则事务不会生效

数据库引擎不支持事务

对于MySQL来说当前只有InnoDB和NDB引擎支持事务,其中较为常用的MyISAM是不支持事务的.

如果使用了不支持事务的引擎,则事务不生效.(写这篇文章,做测试的时候我还出现了这个问题😂)

方法使用了final或者static修饰

使用了final或者static之后,不能被代理类重写,因此事务丢失.

IDEA会提示次错误,因此IDEA上少见,错误如下

1
csharp复制代码Reports the cases when your code prevents a class from being subclassed by some framework (e.g. Spring or Hibernate) at runtime.

非public修饰的方法

具体原因是因为调用的AbstractFallbackTransactionAttributeSource的computeTransactionAttribute方法会判断,该方法是否是public方法.

IDEA会提示次错误,因此IDEA上少见,错误如下

1
csharp复制代码Reports the cases when your code prevents a class from being subclassed by some framework (e.g. Spring or Hibernate) at runtime.

非事务方法内部调用同一个类中事务方法

当无事务的A方法内部用this的方法调用带事务的B方法的时候,B事务就是失效

1
2
3
4
5
6
7
8
9
10
11
typescript复制代码@Override
public void A() {
this.B();
}

@Override
@Transactional
public void B() {
mapper.saveStudent(new Student("A"));
int zdc = 8/0;
}

直接调用B方法可以正常回滚,但是调用A方法的时候 B方法不会回滚.

回滚失败或者阻止回滚场景

事务为什么回滚,在什么情况下回滚,这些都需要参数进行定界,而大部分的参数都在@Transactional中.

在这里集中说明一下@Transactional的参数

参数 作用
value 或 transactionManager 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
propagation 事务的传播行为,默认值为 Propagation.REQUIRED,更详细的解释请点击
isolation 事务的隔离级别,默认值为 Isolation.DEFAULT ,更详细的解释请点击
timeout 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务.
readOnly 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为 true.
rollbackFor 或 rollbackForClassName 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
noRollbackFor 或 noRollbackForClassName 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.

手动try-catch了异常

想要spring事务能够回滚,必须抛出能够捕获的异常,如果异常被try-catch了, 则程序认为没有抛出异常,则不会回滚

1
2
3
4
5
6
7
8
9
10
11
less复制代码@Override
@Transactional
public void B() {
try{
mapper.saveStudent(new Student("A"));
int zdc = 8/0;
}catch (Exception e){
e.printStackTrace();
}
}
//不会回滚

抛出的异常不属于RuntimeException或Error异常

JDK中 @Transactional的rollbackFor参数上注释了该内容

1
2
3
4
less复制代码By default, a transaction will be rolling back on {@link RuntimeException} 
and {@link Error} but not on checked exceptions (business exceptions).
See {@link org.springframework.transaction.interceptor.
DefaultTransactionAttribute#rollbackOn(Throwable)} for a detailed explanation.

默认情况下只处理RuntimeException或Error异常,如果你抛出其他异常则不会被处理

1
2
3
4
5
6
7
less复制代码@Override
@Transactional
public void B() throws NotFoundException {
mapper.saveStudent(new Student("A"));
throw new NotFoundException("zdc");
}
//不会回滚

因此为了能过抛出所有的异常,我们通常会在@Transactional上定义rollbackFor = Exception.class,这样能捕获所有的异常.当然你也可以赋值Exception.class的父类为Throwable.class

1
2
3
4
5
6
7
less复制代码@Override
@Transactional(rollbackFor = Exception.class)
public void B() throws NotFoundException {
mapper.saveStudent(new Student("A"));
throw new NotFoundException("zdc");
}
//会回滚

noRollbackFor定义错误

@Transactional中的noRollbackFor是 抛出指定的异常类型,不回滚事务,如果错误的设置了抛出异常则不会回滚事务

1
2
3
4
5
6
7
less复制代码 @Override
@Transactional(noRollbackFor = Exception.class)
public void B() throws NotFoundException {
mapper.saveStudent(new Student("A"));
int zdc = 1/0;
}
//不会回滚

错误的传播属性或者错误的嵌套关系

事务的传播行为,默认值为 Propagation.REQUIRED,更详细的解释请点击

本节例子 B为主方法, C子方法, 操作B的是否有事务, 操作C的传播属性 ,这个地方的情况太多,直接在下面表格中的本文中的解释部分说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
less复制代码@Service
@Slf4j
public class Transaction3ServiceImpl implements Transaction3Service {

@Autowired
private Transaction4Service transaction4Service;

@Autowired(required = false)
private StudentMapper mapper;

@Override
@Transactional(rollbackFor = Exception.class) //如果不存在事务,注释掉此行来表示
public void B(){
mapper.saveStudent(new Student("ZZZDC"));
transaction4Service.C();
// int zdc = 1/0; //如发生异常用此替代
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
less复制代码@Service
@Slf4j
public class Transaction4ServiceImpl implements Transaction4Service {

@Autowired(required = false)
private StudentMapper mapper;

@Override
@Transactional(rollbackFor = Exception.class) //传播行为会在这里操作
public void C() {
mapper.saveStudent(new Student("ZDDDC"));
//int zdc = 1/0; 如发生异常用此替代
}
}
传播行为 本文中的解释
REQUIRED 如果B存在事务,则C加入该事务(如果发生异常,则BC一起回滚);如果B不存在事务,则C创建一个新的事务(B不回滚,如果C发生异常则只有C部分回滚)
SUPPORTS 如果B存在事务,则C加入该事务(如果发生异常,则BC一起回滚);如果B不存在事务,则C以非事务的方式继续运行(BC任何情况都不回滚)
MANDATORY 如果B存在事务,则C加入该事务(如果发生异常,则一起回滚);如果B不存在事务,则C抛出异常.(C直接报错,无事务B不回滚)
REQUIRES_NEW 如果B不存在事务,C重新创建一个新的事务(无事务B发生异常不回滚,有事C发生异常则C回滚);如果B存在事务,C挂起B得事务并重新创建一个新的事务(这是两个事务,自己部分有异常,则自己部分回滚)
NOT_SUPPORTED 如果B不存在事务,C以非事务的方式运行(任何情况都不回滚);如果B存在事务,C暂停当前的事务并以非事务的方式运行(B部分报错,则B部分回滚,C不回滚;非事务C部分报错,则都不会回滚)
NEVER 如果B不存在事务,C以非事务的方式运行(任何情况都不回滚),如果B存在事务,C则抛出异常(C报错,B因为异常回滚)
NESTED 和REQUIRED效果一样.

在这个地方讲了事务的传播行为会影响事务的状态,在事务嵌套的情况下,如果某一部分报错回滚,根据情况可能全部回滚,也有可能部分回滚.

如果使用到了传播行为参数,则需要仔细分析,仔细测试,然后再交付

非SpringBoot场景

没有配置事务管理器或者配置错误

老SSM项目配置问题

SpringMVC扫描错误

老SSM项目配置问题

没有被spring管理

特殊情况特殊分析

1
2
3
4
arduino复制代码作者:ZOUZDC
链接:https://juejin.cn/post/7028963866063306760
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Cloud Alibaba 学习 -- 4、整

发表于 2021-11-20

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

学习视频(B站):www.bilibili.com/video/BV1Mt…

GitHub 源码地址:github.com/tyronczt/sp…

基本概念:RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。

详见:github.com/apache/rock…

一、Linux 安装

1、下载压缩包,rocketmq-all-4.9.0-bin-release.zip

网址:rocketmq.apache.org/dowloading/…

2、解压缩

1
shell复制代码unzip rocketmq-all-4.9.0-bin-release.zip

3、启动 NameServer

1
2
shell复制代码cd bin
nohup ./mqnamesrv &

4、检查是否启动成功

1
2
shell复制代码查看端口启动情况:netstat -an | grep 9876
查看启动日志:tail -f ~/logs/rocketmqlogs/namesrv.log

在这里插入图片描述

5、启动 Broker

启动之前需要编辑配置文件,修改 JVM 内存设置,默认给的内存 4 GB,超过现有虚拟机 JVM ,不然会报错
在这里插入图片描述

1
2
3
4
5
shell复制代码cd bin
vim runserver.sh
vim runbroker.sh
-Xms256m -Xmx256m -Xmn128m
Xms为虚拟机初始化堆大小,Xmx为最大堆大小,Xmn为最小堆大小

在这里插入图片描述

启动 Broker

1
shell复制代码nohup ./mqbroker -n localhost:9876 &

查看启动情况

1
shell复制代码tail -f ~/logs/rocketmqlogs/broker.log

在这里插入图片描述

6、测试 RocketMQ

测试消息发送

1
2
3
shell复制代码cd bin
export NAMESRV_ADDR=localhost:9876 【参考官网:RocketMQ provides multiple ways to achieve this. For simplicity, we use environment variable NAMESRV_ADDR,否则会报错:connect to null failed】
./tools.sh org.apache.rocketmq.example.quickstart.Producer

在这里插入图片描述

测试消息接收

1
2
3
shell复制代码cd bin
export NAMESRV_ADDR=localhost:9876
./tools.sh org.apache.rocketmq.example.quickstart.Consumer

在这里插入图片描述

7、关闭 RocketMQ

1
2
3
shell复制代码cd bin
./mqshutdown broker
./mqshutdown namesrv

在这里插入图片描述

8、报错

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell复制代码#关闭RocketMq
./mqshutdown broker
./mqshutdown namesrv

nohup ./mqnamesrv &

cd ../conf/
vim broker.conf

#最后两行进行添加
namesrvAddr=192.168.255.100:9876
brokerIP1=192.168.255.100

cd ..
nohup sh bin/mqbroker -n 192.168.255.100:9876 autoCreateTopicEnable=true -c conf/broker.conf &

参考:rocketmq.apache.org/docs/quick-…

二、控制台安装

1、下载源码,rocketmq-console

网址:github.com/apache/rock…

2、打包

1
shell复制代码mvn clean package -Dmaven.test.skip=true

3、修改配置文件

1
2
3
4
ini复制代码properties
server.port=9877
rocketmq.config.namesrvAddr=192.168.255.100:9876
rocketmq.config.isVIPChannel=false

4、运行

1
java复制代码java -jar target/rocketmq-console-ng-2.0.0.jar

在这里插入图片描述

http://localhost:9877/
在这里插入图片描述

5、报错

rocketmq:connect to 172.17.0.1:10911 failed

这是因为我们的 RocketMQ 安装在 Linux 中,控制台在 windows,Linux 需要开放端口才能访问,开放 10909 和 10911和9876 端口

1
2
3
4
5
cmd复制代码firewall-cmd --zone=public --add-port=10909/tcp --permanent
firewall-cmd --zone=public --add-port=10911/tcp --permanent
firewall-cmd --zone=public --add-port=9876/tcp --permanent
systemctl restart firewalld.service
firewall-cmd --reload

三、Java 实现消息发送

在provider项目下

1、引入依赖

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

2、生产消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class Test {
public static void main(String[] args) throws Exception {
//创建消息生产者
DefaultMQProducer producer = new DefaultMQProducer("myproducer-group");
//设置NameServer
producer.setNamesrvAddr("192.168.255.100:9876");
//启动生产者
producer.start();
//构建消息对象
Message message = new Message("myTopic", "myTag", ("Test MQ").getBytes());
//发送消息
SendResult result = producer.send(message, 1000);
System.out.println(result);
//关闭生产者
producer.shutdown();
}
}

3、查看消息

在这里插入图片描述
在这里插入图片描述

4、消费消息

处于阻塞状态,只要有消息发送过来,就会消费信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@Slf4j
public class ConsumerTest {
public static void main(String[] args) throws MQClientException {
//创建消息消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myconsumer-group");
//设置NameServer
consumer.setNamesrvAddr("192.168.255.100:9876");
//指定订阅的主题和标签
consumer.subscribe("myTopic", "*");
//回调函数
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
log.info("Message=>{}", list);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
}
}

5、查看消息
在这里插入图片描述

四、SpringBoot整合RocketMQ

1、生产者引入依赖

1
2
3
4
5
6
7
8
9
10
xml复制代码<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>

2、配置文件

1
2
3
4
yml复制代码rocketmq:
name-server: 192.168.255.100:9876
producer:
group: myprovider

3、生产者代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Autowired
private RocketMQTemplate rocketMQTemplate;

@GetMapping("/create")
public Order create(){
Order order = new Order(
1,
"张三",
"123123",
"软件园",
new Date()
);
this.rocketMQTemplate.convertAndSend("myTopic",order);
return order;
}

4、消费者引入依赖

1
2
3
4
5
6
7
8
9
10
xml复制代码<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.0</version>
</dependency>

5、配置文件

1
2
yaml复制代码rocketmq:
name-server: 192.168.255.100:9876

6、消费者代码

1
2
3
4
5
6
7
8
9
java复制代码@Slf4j
@Service
@RocketMQMessageListener(consumerGroup = "myConsumer", topic = "myTopic")
public class SmsService implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
log.info("新订单{},发短信", order);
}
}

只要生产者中的topic与消费者中的topic进行对应即可。
在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…261262263…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%